19088: Export collection/project properties as x-amz-meta tags.
[arvados.git] / sdk / go / arvados / fs_project.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "log"
9         "strings"
10 )
11
12 func (fs *customFileSystem) defaultUUID(uuid string) (string, error) {
13         if uuid != "" {
14                 return uuid, nil
15         }
16         var resp User
17         err := fs.RequestAndDecode(&resp, "GET", "arvados/v1/users/current", nil, nil)
18         if err != nil {
19                 return "", err
20         }
21         return resp.UUID, nil
22 }
23
24 // loadOneChild loads only the named child, if it exists.
25 func (fs *customFileSystem) projectsLoadOne(parent inode, uuid, name string) (inode, error) {
26         uuid, err := fs.defaultUUID(uuid)
27         if err != nil {
28                 return nil, err
29         }
30
31         var contents CollectionList
32         for _, subst := range []string{"/", fs.forwardSlashNameSubstitution} {
33                 contents = CollectionList{}
34                 err = fs.RequestAndDecode(&contents, "GET", "arvados/v1/groups/"+uuid+"/contents", nil, ResourceListParams{
35                         Count: "none",
36                         Filters: []Filter{
37                                 {"name", "=", strings.Replace(name, subst, "/", -1)},
38                                 {"uuid", "is_a", []string{"arvados#collection", "arvados#group"}},
39                                 {"groups.group_class", "=", "project"},
40                         },
41                         Select: []string{"uuid", "name", "modified_at", "properties"},
42                 })
43                 if err != nil {
44                         return nil, err
45                 }
46                 if len(contents.Items) > 0 || fs.forwardSlashNameSubstitution == "/" || fs.forwardSlashNameSubstitution == "" || !strings.Contains(name, fs.forwardSlashNameSubstitution) {
47                         break
48                 }
49                 // If the requested name contains the configured "/"
50                 // replacement string and didn't match a
51                 // project/collection exactly, we'll try again with
52                 // "/" in its place, so a lookup of a munged name
53                 // works regardless of whether the directory listing
54                 // has been populated with escaped names.
55                 //
56                 // Note this doesn't handle items whose names contain
57                 // both "/" and the substitution string.
58         }
59         if len(contents.Items) == 0 {
60                 return nil, nil
61         }
62         coll := contents.Items[0]
63
64         if strings.Contains(coll.UUID, "-j7d0g-") {
65                 // Group item was loaded into a Collection var -- but
66                 // we only need the Name and UUID anyway, so it's OK.
67                 return fs.newProjectNode(parent, coll.Name, coll.UUID, coll.Properties), nil
68         } else if strings.Contains(coll.UUID, "-4zz18-") {
69                 return deferredCollectionFS(fs, parent, coll), nil
70         } else {
71                 log.Printf("group contents: unrecognized UUID in response: %q", coll.UUID)
72                 return nil, ErrInvalidArgument
73         }
74 }
75
76 func (fs *customFileSystem) projectsLoadAll(parent inode, uuid string) ([]inode, error) {
77         uuid, err := fs.defaultUUID(uuid)
78         if err != nil {
79                 return nil, err
80         }
81
82         var inodes []inode
83
84         // When #17424 is resolved, remove the outer loop here and use
85         // []string{"arvados#collection", "arvados#group"} directly as the uuid
86         // filter.
87         for _, class := range []string{"arvados#collection", "arvados#group"} {
88                 // Note: the "filters" slice's backing array might be reused
89                 // by append(filters,...) below. This isn't goroutine safe,
90                 // but all accesses are in the same goroutine, so it's OK.
91                 filters := []Filter{
92                         {"uuid", "is_a", class},
93                 }
94                 if class == "arvados#group" {
95                         filters = append(filters, Filter{"group_class", "=", "project"})
96                 }
97
98                 params := ResourceListParams{
99                         Count:   "none",
100                         Filters: filters,
101                         Order:   "uuid",
102                         Select:  []string{"uuid", "name", "modified_at", "properties"},
103                 }
104
105                 for {
106                         // The groups content endpoint returns Collection and Group (project)
107                         // objects. This function only accesses the UUID and Name field. Both
108                         // collections and groups have those fields, so it is easier to just treat
109                         // the ObjectList that comes back as a CollectionList.
110                         var resp CollectionList
111                         err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/groups/"+uuid+"/contents", nil, params)
112                         if err != nil {
113                                 return nil, err
114                         }
115                         if len(resp.Items) == 0 {
116                                 break
117                         }
118                         for _, i := range resp.Items {
119                                 if fs.forwardSlashNameSubstitution != "" {
120                                         i.Name = strings.Replace(i.Name, "/", fs.forwardSlashNameSubstitution, -1)
121                                 }
122                                 if !permittedName(i.Name) {
123                                         continue
124                                 }
125                                 if strings.Contains(i.UUID, "-j7d0g-") {
126                                         inodes = append(inodes, fs.newProjectNode(parent, i.Name, i.UUID, i.Properties))
127                                 } else if strings.Contains(i.UUID, "-4zz18-") {
128                                         inodes = append(inodes, deferredCollectionFS(fs, parent, i))
129                                 } else {
130                                         log.Printf("group contents: unrecognized UUID in response: %q", i.UUID)
131                                         return nil, ErrInvalidArgument
132                                 }
133                         }
134                         params.Filters = append(filters, Filter{"uuid", ">", resp.Items[len(resp.Items)-1].UUID})
135                 }
136         }
137         return inodes, nil
138 }