+
+func jsonReader(rscName string, ob interface{}) io.Reader {
+ j, err := json.Marshal(ob)
+ if err != nil {
+ panic(err)
+ }
+ v := url.Values{}
+ v[rscName] = []string{string(j)}
+ return bytes.NewBufferString(v.Encode())
+}
+
+// GetRemoteGroups fetches all remote groups with their members
+func GetRemoteGroups(cfg *ConfigParams, allUsers map[string]arvados.User) (remoteGroups map[string]*GroupInfo, groupNameToUUID map[string]string, err error) {
+ remoteGroups = make(map[string]*GroupInfo)
+ groupNameToUUID = make(map[string]string) // Index by group name
+
+ params := arvados.ResourceListParams{
+ Filters: []arvados.Filter{{
+ Attr: "owner_uuid",
+ Operator: "=",
+ Operand: cfg.ParentGroupUUID,
+ }},
+ }
+ results, err := GetAll(cfg.Client, "groups", params, &GroupList{})
+ if err != nil {
+ return remoteGroups, groupNameToUUID, fmt.Errorf("error getting remote groups: %s", err)
+ }
+ for _, item := range results {
+ group := item.(arvados.Group)
+ // Group -> User filter
+ g2uFilter := arvados.ResourceListParams{
+ Filters: []arvados.Filter{{
+ Attr: "owner_uuid",
+ Operator: "=",
+ Operand: cfg.SysUserUUID,
+ }, {
+ Attr: "link_class",
+ Operator: "=",
+ Operand: "permission",
+ }, {
+ Attr: "name",
+ Operator: "=",
+ Operand: "can_read",
+ }, {
+ Attr: "tail_uuid",
+ Operator: "=",
+ Operand: group.UUID,
+ }, {
+ Attr: "head_kind",
+ Operator: "=",
+ Operand: "arvados#user",
+ }},
+ }
+ // User -> Group filter
+ u2gFilter := arvados.ResourceListParams{
+ Filters: []arvados.Filter{{
+ Attr: "owner_uuid",
+ Operator: "=",
+ Operand: cfg.SysUserUUID,
+ }, {
+ Attr: "link_class",
+ Operator: "=",
+ Operand: "permission",
+ }, {
+ Attr: "name",
+ Operator: "=",
+ Operand: "can_write",
+ }, {
+ Attr: "head_uuid",
+ Operator: "=",
+ Operand: group.UUID,
+ }, {
+ Attr: "tail_kind",
+ Operator: "=",
+ Operand: "arvados#user",
+ }},
+ }
+ g2uLinks, err := GetAll(cfg.Client, "links", g2uFilter, &LinkList{})
+ if err != nil {
+ return remoteGroups, groupNameToUUID, fmt.Errorf("error getting member (can_read) links for group %q: %s", group.Name, err)
+ }
+ u2gLinks, err := GetAll(cfg.Client, "links", u2gFilter, &LinkList{})
+ if err != nil {
+ return remoteGroups, groupNameToUUID, fmt.Errorf("error getting member (can_write) links for group %q: %s", group.Name, err)
+ }
+ // Build a list of user ids (email or username) belonging to this group
+ membersSet := make(map[string]bool)
+ u2gLinkSet := make(map[string]bool)
+ for _, l := range u2gLinks {
+ linkedMemberUUID := l.(arvados.Link).TailUUID
+ u2gLinkSet[linkedMemberUUID] = true
+ }
+ for _, item := range g2uLinks {
+ link := item.(arvados.Link)
+ // We may have received an old link pointing to a removed account.
+ if _, found := allUsers[link.HeadUUID]; !found {
+ continue
+ }
+ // The matching User -> Group link may not exist if the link
+ // creation failed on a previous run. If that's the case, don't
+ // include this account on the "previous members" list.
+ if _, found := u2gLinkSet[link.HeadUUID]; !found {
+ continue
+ }
+ memberID, err := GetUserID(allUsers[link.HeadUUID], cfg.UserID)
+ if err != nil {
+ return remoteGroups, groupNameToUUID, err
+ }
+ membersSet[memberID] = true
+ }
+ remoteGroups[group.UUID] = &GroupInfo{
+ Group: group,
+ PreviousMembers: membersSet,
+ CurrentMembers: make(map[string]bool), // Empty set
+ }
+ groupNameToUUID[group.Name] = group.UUID
+ }
+ return remoteGroups, groupNameToUUID, nil
+}
+
+// RemoveMemberFromGroup remove all links related to the membership
+func RemoveMemberFromGroup(cfg *ConfigParams, user arvados.User, group arvados.Group) error {
+ if cfg.Verbose {
+ log.Printf("Getting group membership links for user %q (%s) on group %q (%s)", user.Email, user.UUID, group.Name, group.UUID)
+ }
+ var links []interface{}
+ // Search for all group<->user links (both ways)
+ for _, filterset := range [][]arvados.Filter{
+ // Group -> User
+ {{
+ Attr: "link_class",
+ Operator: "=",
+ Operand: "permission",
+ }, {
+ Attr: "tail_uuid",
+ Operator: "=",
+ Operand: group.UUID,
+ }, {
+ Attr: "head_uuid",
+ Operator: "=",
+ Operand: user.UUID,
+ }},
+ // Group <- User
+ {{
+ Attr: "link_class",
+ Operator: "=",
+ Operand: "permission",
+ }, {
+ Attr: "tail_uuid",
+ Operator: "=",
+ Operand: user.UUID,
+ }, {
+ Attr: "head_uuid",
+ Operator: "=",
+ Operand: group.UUID,
+ }},
+ } {
+ l, err := GetAll(cfg.Client, "links", arvados.ResourceListParams{Filters: filterset}, &LinkList{})
+ if err != nil {
+ userID, _ := GetUserID(user, cfg.UserID)
+ return fmt.Errorf("error getting links needed to remove user %q from group %q: %s", userID, group.Name, err)
+ }
+ for _, link := range l {
+ links = append(links, link)
+ }
+ }
+ for _, item := range links {
+ link := item.(arvados.Link)
+ userID, _ := GetUserID(user, cfg.UserID)
+ if cfg.Verbose {
+ log.Printf("Removing %q permission link for %q on group %q", link.Name, userID, group.Name)
+ }
+ if err := DeleteLink(cfg, link.UUID); err != nil {
+ return fmt.Errorf("error removing user %q from group %q: %s", userID, group.Name, err)
+ }
+ }
+ return nil
+}
+
+// AddMemberToGroup create membership links
+func AddMemberToGroup(cfg *ConfigParams, user arvados.User, group arvados.Group) error {
+ var newLink arvados.Link
+ linkData := map[string]string{
+ "owner_uuid": cfg.SysUserUUID,
+ "link_class": "permission",
+ "name": "can_read",
+ "tail_uuid": group.UUID,
+ "head_uuid": user.UUID,
+ }
+ if err := CreateLink(cfg, &newLink, linkData); err != nil {
+ userID, _ := GetUserID(user, cfg.UserID)
+ return fmt.Errorf("error adding group %q -> user %q read permission: %s", group.Name, userID, err)
+ }
+ linkData = map[string]string{
+ "owner_uuid": cfg.SysUserUUID,
+ "link_class": "permission",
+ "name": "can_write",
+ "tail_uuid": user.UUID,
+ "head_uuid": group.UUID,
+ }
+ if err := CreateLink(cfg, &newLink, linkData); err != nil {
+ userID, _ := GetUserID(user, cfg.UserID)
+ return fmt.Errorf("error adding user %q -> group %q write permission: %s", userID, group.Name, err)
+ }
+ return nil
+}
+
+// CreateGroup creates a group with groupData parameters, assigns it to dst
+func CreateGroup(cfg *ConfigParams, dst *arvados.Group, groupData map[string]string) error {
+ return cfg.Client.RequestAndDecode(dst, "POST", "/arvados/v1/groups", jsonReader("group", groupData), nil)
+}
+
+// GetGroup fetches a group by its UUID
+func GetGroup(cfg *ConfigParams, dst *arvados.Group, groupUUID string) error {
+ return cfg.Client.RequestAndDecode(&dst, "GET", "/arvados/v1/groups/"+groupUUID, nil, nil)
+}
+
+// CreateLink creates a link with linkData parameters, assigns it to dst
+func CreateLink(cfg *ConfigParams, dst *arvados.Link, linkData map[string]string) error {
+ return cfg.Client.RequestAndDecode(dst, "POST", "/arvados/v1/links", jsonReader("link", linkData), nil)
+}
+
+// DeleteLink deletes a link by its UUID
+func DeleteLink(cfg *ConfigParams, linkUUID string) error {
+ if linkUUID == "" {
+ return fmt.Errorf("cannot delete link with invalid UUID: %q", linkUUID)
+ }
+ return cfg.Client.RequestAndDecode(&arvados.Link{}, "DELETE", "/arvados/v1/links/"+linkUUID, nil, nil)
+}
+
+// GetResourceList fetches res list using params
+func GetResourceList(c *arvados.Client, dst *resourceList, res string, params interface{}) error {
+ return c.RequestAndDecode(dst, "GET", "/arvados/v1/"+res, nil, params)
+}