From: Ward Vandewege Date: Tue, 9 Mar 2021 21:43:27 +0000 (-0500) Subject: 17119: Merge branch 'master' into 17119-virtual-folder-from-query X-Git-Tag: 2.2.0~111^2 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/38104975556f7a0a59c1a21a97aa37cd0e178d69?hp=c39fcfbee2de3df0dc3229175316be9e2e647e1f 17119: Merge branch 'master' into 17119-virtual-folder-from-query Arvados-DCO-1.1-Signed-off-by: Ward Vandewege --- diff --git a/lib/controller/federation/conn.go b/lib/controller/federation/conn.go index b86266d67e..a9352098d3 100644 --- a/lib/controller/federation/conn.go +++ b/lib/controller/federation/conn.go @@ -402,6 +402,38 @@ func (conn *Conn) ContainerRequestDelete(ctx context.Context, options arvados.De return conn.chooseBackend(options.UUID).ContainerRequestDelete(ctx, options) } +func (conn *Conn) GroupCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Group, error) { + return conn.chooseBackend(options.ClusterID).GroupCreate(ctx, options) +} + +func (conn *Conn) GroupUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Group, error) { + return conn.chooseBackend(options.UUID).GroupUpdate(ctx, options) +} + +func (conn *Conn) GroupGet(ctx context.Context, options arvados.GetOptions) (arvados.Group, error) { + return conn.chooseBackend(options.UUID).GroupGet(ctx, options) +} + +func (conn *Conn) GroupList(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) { + return conn.generated_GroupList(ctx, options) +} + +func (conn *Conn) GroupContents(ctx context.Context, options arvados.GroupContentsOptions) (arvados.ObjectList, error) { + return conn.chooseBackend(options.UUID).GroupContents(ctx, options) +} + +func (conn *Conn) GroupShared(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) { + return conn.chooseBackend(options.ClusterID).GroupShared(ctx, options) +} + +func (conn *Conn) GroupDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Group, error) { + return conn.chooseBackend(options.UUID).GroupDelete(ctx, options) +} + +func (conn *Conn) GroupUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Group, error) { + return conn.chooseBackend(options.UUID).GroupUntrash(ctx, options) +} + func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) { return conn.generated_SpecimenList(ctx, options) } @@ -432,6 +464,7 @@ var userAttrsCachedFromLoginCluster = map[string]bool{ "modified_at": true, "prefs": true, "username": true, + "kind": true, "etag": false, "full_name": false, diff --git a/lib/controller/federation/generate.go b/lib/controller/federation/generate.go index 9ce7fdcb21..6a2a485856 100644 --- a/lib/controller/federation/generate.go +++ b/lib/controller/federation/generate.go @@ -52,7 +52,7 @@ func main() { defer out.Close() out.Write(regexp.MustCompile(`(?ms)^.*package .*?import.*?\n\)\n`).Find(buf)) io.WriteString(out, "//\n// -- this file is auto-generated -- do not edit -- edit list.go and run \"go generate\" instead --\n//\n\n") - for _, t := range []string{"Container", "ContainerRequest", "Specimen", "User"} { + for _, t := range []string{"Container", "ContainerRequest", "Group", "Specimen", "User"} { _, err := out.Write(bytes.ReplaceAll(orig, []byte("Collection"), []byte(t))) if err != nil { panic(err) diff --git a/lib/controller/federation/generated.go b/lib/controller/federation/generated.go index ab9db93a4d..49a2e5b751 100755 --- a/lib/controller/federation/generated.go +++ b/lib/controller/federation/generated.go @@ -99,6 +99,47 @@ func (conn *Conn) generated_ContainerRequestList(ctx context.Context, options ar return merged, err } +func (conn *Conn) generated_GroupList(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) { + var mtx sync.Mutex + var merged arvados.GroupList + var needSort atomic.Value + needSort.Store(false) + err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) { + options.ForwardedFor = conn.cluster.ClusterID + "-" + options.ForwardedFor + cl, err := backend.GroupList(ctx, options) + if err != nil { + return nil, err + } + mtx.Lock() + defer mtx.Unlock() + if len(merged.Items) == 0 { + merged = cl + } else if len(cl.Items) > 0 { + merged.Items = append(merged.Items, cl.Items...) + needSort.Store(true) + } + uuids := make([]string, 0, len(cl.Items)) + for _, item := range cl.Items { + uuids = append(uuids, item.UUID) + } + return uuids, nil + }) + if needSort.Load().(bool) { + // Apply the default/implied order, "modified_at desc" + sort.Slice(merged.Items, func(i, j int) bool { + mi, mj := merged.Items[i].ModifiedAt, merged.Items[j].ModifiedAt + return mj.Before(mi) + }) + } + if merged.Items == nil { + // Return empty results as [], not null + // (https://github.com/golang/go/issues/27589 might be + // a better solution in the future) + merged.Items = []arvados.Group{} + } + return merged, err +} + func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) { var mtx sync.Mutex var merged arvados.SpecimenList diff --git a/lib/controller/handler.go b/lib/controller/handler.go index 5f6fb192e1..40f335e952 100644 --- a/lib/controller/handler.go +++ b/lib/controller/handler.go @@ -104,6 +104,8 @@ func (h *Handler) setup() { mux.Handle("/arvados/v1/connect/", rtr) mux.Handle("/arvados/v1/container_requests", rtr) mux.Handle("/arvados/v1/container_requests/", rtr) + mux.Handle("/arvados/v1/groups", rtr) + mux.Handle("/arvados/v1/groups/", rtr) mux.Handle("/login", rtr) mux.Handle("/logout", rtr) } diff --git a/lib/controller/router/request.go b/lib/controller/router/request.go index 977a15f3ab..eae9e0a8ce 100644 --- a/lib/controller/router/request.go +++ b/lib/controller/router/request.go @@ -170,6 +170,8 @@ var boolParams = map[string]bool{ "redirect_to_new_user": true, "send_notification_email": true, "bypass_federation": true, + "recursive": true, + "exclude_home_project": true, } func stringToBool(s string) bool { diff --git a/lib/controller/router/response.go b/lib/controller/router/response.go index d554ab930f..6e933fc00e 100644 --- a/lib/controller/router/response.go +++ b/lib/controller/router/response.go @@ -74,32 +74,39 @@ func (rtr *router) sendResponse(w http.ResponseWriter, req *http.Request, resp i if respKind != "" { tmp["kind"] = respKind } + if included, ok := tmp["included"]; ok && included == nil { + tmp["included"] = make([]interface{}, 0) + } defaultItemKind := "" if strings.HasSuffix(respKind, "List") { defaultItemKind = strings.TrimSuffix(respKind, "List") } - if items, ok := tmp["items"].([]interface{}); ok { - for i, item := range items { - // Fill in "kind" by inspecting UUID/PDH if - // possible; fall back on assuming each - // Items[] entry in an "arvados#fooList" - // response should have kind="arvados#foo". - item, _ := item.(map[string]interface{}) - infix := "" - if uuid, _ := item["uuid"].(string); len(uuid) == 27 { - infix = uuid[6:11] - } - if k := kind(infixMap[infix]); k != "" { - item["kind"] = k - } else if pdh, _ := item["portable_data_hash"].(string); pdh != "" { - item["kind"] = "arvados#collection" - } else if defaultItemKind != "" { - item["kind"] = defaultItemKind + if _, isListResponse := tmp["items"].([]interface{}); isListResponse { + items, _ := tmp["items"].([]interface{}) + included, _ := tmp["included"].([]interface{}) + for _, slice := range [][]interface{}{items, included} { + for i, item := range slice { + // Fill in "kind" by inspecting UUID/PDH if + // possible; fall back on assuming each + // Items[] entry in an "arvados#fooList" + // response should have kind="arvados#foo". + item, _ := item.(map[string]interface{}) + infix := "" + if uuid, _ := item["uuid"].(string); len(uuid) == 27 { + infix = uuid[6:11] + } + if k := kind(infixMap[infix]); k != "" { + item["kind"] = k + } else if pdh, _ := item["portable_data_hash"].(string); pdh != "" { + item["kind"] = "arvados#collection" + } else if defaultItemKind != "" { + item["kind"] = defaultItemKind + } + item = applySelectParam(opts.Select, item) + rtr.mungeItemFields(item) + slice[i] = item } - item = applySelectParam(opts.Select, item) - rtr.mungeItemFields(item) - items[i] = item } if opts.Count == "none" { delete(tmp, "items_available") @@ -125,7 +132,15 @@ func (rtr *router) sendError(w http.ResponseWriter, err error) { var infixMap = map[string]interface{}{ "4zz18": arvados.Collection{}, + "xvhdp": arvados.ContainerRequest{}, + "dz642": arvados.Container{}, "j7d0g": arvados.Group{}, + "8i9sb": arvados.Job{}, + "d1hrv": arvados.PipelineInstance{}, + "p5p6p": arvados.PipelineTemplate{}, + "j58dm": arvados.Specimen{}, + "q1cn2": arvados.Trait{}, + "7fd4e": arvados.Workflow{}, } var mungeKind = regexp.MustCompile(`\..`) diff --git a/lib/controller/router/router.go b/lib/controller/router/router.go index 83c89d322a..d7096ab9f4 100644 --- a/lib/controller/router/router.go +++ b/lib/controller/router/router.go @@ -228,6 +228,69 @@ func (rtr *router) addRoutes() { return rtr.backend.ContainerSSH(ctx, *opts.(*arvados.ContainerSSHOptions)) }, }, + { + arvados.EndpointGroupCreate, + func() interface{} { return &arvados.CreateOptions{} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupCreate(ctx, *opts.(*arvados.CreateOptions)) + }, + }, + { + arvados.EndpointGroupUpdate, + func() interface{} { return &arvados.UpdateOptions{} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupUpdate(ctx, *opts.(*arvados.UpdateOptions)) + }, + }, + { + arvados.EndpointGroupList, + func() interface{} { return &arvados.ListOptions{Limit: -1} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupList(ctx, *opts.(*arvados.ListOptions)) + }, + }, + { + arvados.EndpointGroupContents, + func() interface{} { return &arvados.GroupContentsOptions{Limit: -1} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupContents(ctx, *opts.(*arvados.GroupContentsOptions)) + }, + }, + { + arvados.EndpointGroupContentsUUIDInPath, + func() interface{} { return &arvados.GroupContentsOptions{Limit: -1} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupContents(ctx, *opts.(*arvados.GroupContentsOptions)) + }, + }, + { + arvados.EndpointGroupShared, + func() interface{} { return &arvados.ListOptions{Limit: -1} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupShared(ctx, *opts.(*arvados.ListOptions)) + }, + }, + { + arvados.EndpointGroupGet, + func() interface{} { return &arvados.GetOptions{} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupGet(ctx, *opts.(*arvados.GetOptions)) + }, + }, + { + arvados.EndpointGroupDelete, + func() interface{} { return &arvados.DeleteOptions{} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupDelete(ctx, *opts.(*arvados.DeleteOptions)) + }, + }, + { + arvados.EndpointGroupUntrash, + func() interface{} { return &arvados.UntrashOptions{} }, + func(ctx context.Context, opts interface{}) (interface{}, error) { + return rtr.backend.GroupUntrash(ctx, *opts.(*arvados.UntrashOptions)) + }, + }, { arvados.EndpointSpecimenCreate, func() interface{} { return &arvados.CreateOptions{} }, diff --git a/lib/controller/rpc/conn.go b/lib/controller/rpc/conn.go index 3a19f4ab5a..b3713d938b 100644 --- a/lib/controller/rpc/conn.go +++ b/lib/controller/rpc/conn.go @@ -416,6 +416,62 @@ func (conn *Conn) ContainerRequestDelete(ctx context.Context, options arvados.De return resp, err } +func (conn *Conn) GroupCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Group, error) { + ep := arvados.EndpointGroupCreate + var resp arvados.Group + err := conn.requestAndDecode(ctx, &resp, ep, nil, options) + return resp, err +} + +func (conn *Conn) GroupUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Group, error) { + ep := arvados.EndpointGroupUpdate + var resp arvados.Group + err := conn.requestAndDecode(ctx, &resp, ep, nil, options) + return resp, err +} + +func (conn *Conn) GroupGet(ctx context.Context, options arvados.GetOptions) (arvados.Group, error) { + ep := arvados.EndpointGroupGet + var resp arvados.Group + err := conn.requestAndDecode(ctx, &resp, ep, nil, options) + return resp, err +} + +func (conn *Conn) GroupList(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) { + ep := arvados.EndpointGroupList + var resp arvados.GroupList + err := conn.requestAndDecode(ctx, &resp, ep, nil, options) + return resp, err +} + +func (conn *Conn) GroupContents(ctx context.Context, options arvados.GroupContentsOptions) (arvados.ObjectList, error) { + ep := arvados.EndpointGroupContents + var resp arvados.ObjectList + err := conn.requestAndDecode(ctx, &resp, ep, nil, options) + return resp, err +} + +func (conn *Conn) GroupShared(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) { + ep := arvados.EndpointGroupShared + var resp arvados.GroupList + err := conn.requestAndDecode(ctx, &resp, ep, nil, options) + return resp, err +} + +func (conn *Conn) GroupDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Group, error) { + ep := arvados.EndpointGroupDelete + var resp arvados.Group + err := conn.requestAndDecode(ctx, &resp, ep, nil, options) + return resp, err +} + +func (conn *Conn) GroupUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Group, error) { + ep := arvados.EndpointGroupUntrash + var resp arvados.Group + err := conn.requestAndDecode(ctx, &resp, ep, nil, options) + return resp, err +} + func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) { ep := arvados.EndpointSpecimenCreate var resp arvados.Specimen diff --git a/sdk/go/arvados/api.go b/sdk/go/arvados/api.go index 37a3e007b1..bcb51d6b2e 100644 --- a/sdk/go/arvados/api.go +++ b/sdk/go/arvados/api.go @@ -51,6 +51,15 @@ var ( EndpointContainerRequestGet = APIEndpoint{"GET", "arvados/v1/container_requests/{uuid}", ""} EndpointContainerRequestList = APIEndpoint{"GET", "arvados/v1/container_requests", ""} EndpointContainerRequestDelete = APIEndpoint{"DELETE", "arvados/v1/container_requests/{uuid}", ""} + EndpointGroupCreate = APIEndpoint{"POST", "arvados/v1/groups", "group"} + EndpointGroupUpdate = APIEndpoint{"PATCH", "arvados/v1/groups/{uuid}", "group"} + EndpointGroupGet = APIEndpoint{"GET", "arvados/v1/groups/{uuid}", ""} + EndpointGroupList = APIEndpoint{"GET", "arvados/v1/groups", ""} + EndpointGroupContents = APIEndpoint{"GET", "arvados/v1/groups/contents", ""} + EndpointGroupContentsUUIDInPath = APIEndpoint{"GET", "arvados/v1/groups/{uuid}/contents", ""} // Alternative HTTP route; client-side code should always use EndpointGroupContents instead + EndpointGroupShared = APIEndpoint{"GET", "arvados/v1/groups/shared", ""} + EndpointGroupDelete = APIEndpoint{"DELETE", "arvados/v1/groups/{uuid}", ""} + EndpointGroupUntrash = APIEndpoint{"POST", "arvados/v1/groups/{uuid}/untrash", ""} EndpointUserActivate = APIEndpoint{"POST", "arvados/v1/users/{uuid}/activate", ""} EndpointUserCreate = APIEndpoint{"POST", "arvados/v1/users", "user"} EndpointUserCurrent = APIEndpoint{"GET", "arvados/v1/users/current", ""} @@ -109,6 +118,7 @@ type ListOptions struct { IncludeOldVersions bool `json:"include_old_versions"` BypassFederation bool `json:"bypass_federation"` ForwardedFor string `json:"forwarded_for,omitempty"` + Include string `json:"include"` } type CreateOptions struct { @@ -124,6 +134,18 @@ type UpdateOptions struct { BypassFederation bool `json:"bypass_federation"` } +type GroupContentsOptions struct { + UUID string `json:"uuid,omitempty"` + Select []string `json:"select"` + Filters []Filter `json:"filters"` + Limit int64 `json:"limit"` + Offset int64 `json:"offset"` + Order []string `json:"order"` + Include string `json:"include"` + Recursive bool `json:"recursive"` + ExcludeHomeProject bool `json:"exclude_home_project"` +} + type UpdateUUIDOptions struct { UUID string `json:"uuid"` NewUUID string `json:"new_uuid"` @@ -203,6 +225,14 @@ type API interface { ContainerRequestGet(ctx context.Context, options GetOptions) (ContainerRequest, error) ContainerRequestList(ctx context.Context, options ListOptions) (ContainerRequestList, error) ContainerRequestDelete(ctx context.Context, options DeleteOptions) (ContainerRequest, error) + GroupCreate(ctx context.Context, options CreateOptions) (Group, error) + GroupUpdate(ctx context.Context, options UpdateOptions) (Group, error) + GroupGet(ctx context.Context, options GetOptions) (Group, error) + GroupList(ctx context.Context, options ListOptions) (GroupList, error) + GroupContents(ctx context.Context, options GroupContentsOptions) (ObjectList, error) + GroupShared(ctx context.Context, options ListOptions) (GroupList, error) + GroupDelete(ctx context.Context, options DeleteOptions) (Group, error) + GroupUntrash(ctx context.Context, options UntrashOptions) (Group, error) SpecimenCreate(ctx context.Context, options CreateOptions) (Specimen, error) SpecimenUpdate(ctx context.Context, options UpdateOptions) (Specimen, error) SpecimenGet(ctx context.Context, options GetOptions) (Specimen, error) diff --git a/sdk/go/arvados/container.go b/sdk/go/arvados/container.go index b8530db342..f542634708 100644 --- a/sdk/go/arvados/container.go +++ b/sdk/go/arvados/container.go @@ -43,7 +43,6 @@ type ContainerRequest struct { ModifiedByUserUUID string `json:"modified_by_user_uuid"` ModifiedAt time.Time `json:"modified_at"` Href string `json:"href"` - Kind string `json:"kind"` Etag string `json:"etag"` Name string `json:"name"` Description string `json:"description"` diff --git a/sdk/go/arvados/group.go b/sdk/go/arvados/group.go index bf2fe72ff8..d5243dc170 100644 --- a/sdk/go/arvados/group.go +++ b/sdk/go/arvados/group.go @@ -4,20 +4,46 @@ package arvados +import ( + "time" +) + // Group is an arvados#group record type Group struct { - UUID string `json:"uuid"` - Name string `json:"name"` - OwnerUUID string `json:"owner_uuid"` - GroupClass string `json:"group_class"` + UUID string `json:"uuid"` + Name string `json:"name"` + OwnerUUID string `json:"owner_uuid"` + GroupClass string `json:"group_class"` + Etag string `json:"etag"` + Href string `json:"href"` + TrashAt *time.Time `json:"trash_at"` + CreatedAt time.Time `json:"created_at"` + ModifiedAt time.Time `json:"modified_at"` + ModifiedByClientUUID string `json:"modified_by_client_uuid"` + ModifiedByUserUUID string `json:"modified_by_user_uuid"` + DeleteAt *time.Time `json:"delete_at"` + IsTrashed bool `json:"is_trashed"` + Properties map[string]interface{} `json:"properties"` + WritableBy []string `json:"writable_by,omitempty"` + Description string `json:"description"` } // GroupList is an arvados#groupList resource. type GroupList struct { - Items []Group `json:"items"` - ItemsAvailable int `json:"items_available"` - Offset int `json:"offset"` - Limit int `json:"limit"` + Items []Group `json:"items"` + ItemsAvailable int `json:"items_available"` + Offset int `json:"offset"` + Limit int `json:"limit"` + Included []interface{} `json:"included"` +} + +// ObjectList is an arvados#objectList resource. +type ObjectList struct { + Included []interface{} `json:"included"` + Items []interface{} `json:"items"` + ItemsAvailable int `json:"items_available"` + Offset int `json:"offset"` + Limit int `json:"limit"` } func (g Group) resourceName() string { diff --git a/sdk/go/arvados/job.go b/sdk/go/arvados/job.go new file mode 100644 index 0000000000..ccf752ce7c --- /dev/null +++ b/sdk/go/arvados/job.go @@ -0,0 +1,48 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +package arvados + +import "time" + +// Job is an arvados#job record +type Job struct { + UUID string `json:"uuid"` + Etag string `json:"etag"` + OwnerUUID string `json:"owner_uuid"` + ModifiedByClientUUID string `json:"modified_by_client_uuid"` + ModifiedByUserUUID string `json:"modified_by_user_uuid"` + ModifiedAt time.Time `json:"modified_at"` + SubmitID string `json:"submit_id"` + Script string `json:"script"` + CancelledByClientUUID string `json:"cancelled_by_client_uuid"` + CancelledByUserUUID string `json:"cancelled_by_user_uuid"` + CancelledAt time.Time `json:"cancelled_at"` + StartedAt time.Time `json:"started_at"` + FinishedAt time.Time `json:"finished_at"` + Running bool `json:"running"` + Success bool `json:"success"` + Output string `json:"output"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + IsLockedByUUID string `json:"is_locked_by_uuid"` + Log string `json:"log"` + TasksSummary map[string]interface{} `json:"tasks_summary"` + RuntimeConstraints map[string]interface{} `json:"runtime_constraints"` + Nondeterministic bool `json:"nondeterministic"` + Repository string `json:"repository"` + SuppliedScriptVersion string `json:"supplied_script_version"` + DockerImageLocator string `json:"docker_image_locator"` + Priority int `json:"priority"` + Description string `json:"description"` + State string `json:"state"` + ArvadosSDKVersion string `json:"arvados_sdk_version"` + Components map[string]interface{} `json:"components"` + ScriptParametersDigest string `json:"script_parameters_digest"` + WritableBy []string `json:"writable_by,omitempty"` +} + +func (g Job) resourceName() string { + return "job" +} diff --git a/sdk/go/arvados/pipeline_instance.go b/sdk/go/arvados/pipeline_instance.go new file mode 100644 index 0000000000..ace1826850 --- /dev/null +++ b/sdk/go/arvados/pipeline_instance.go @@ -0,0 +1,33 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +package arvados + +import "time" + +// PipelineInstance is an arvados#pipelineInstance record +type PipelineInstance struct { + UUID string `json:"uuid"` + Etag string `json:"etag"` + OwnerUUID string `json:"owner_uuid"` + CreatedAt time.Time `json:"created_at"` + ModifiedByClientUUID string `json:"modified_by_client_uuid"` + ModifiedByUserUUID string `json:"modified_by_user_uuid"` + ModifiedAt time.Time `json:"modified_at"` + PipelineTemplateUUID string `json:"pipeline_template_uuid"` + Name string `json:"name"` + Components map[string]interface{} `json:"components"` + UpdatedAt time.Time `json:"updated_at"` + Properties map[string]interface{} `json:"properties"` + State string `json:"state"` + ComponentsSummary map[string]interface{} `json:"components_summary"` + StartedAt time.Time `json:"started_at"` + FinishedAt time.Time `json:"finished_at"` + Description string `json:"description"` + WritableBy []string `json:"writable_by,omitempty"` +} + +func (g PipelineInstance) resourceName() string { + return "pipelineInstance" +} diff --git a/sdk/go/arvados/pipeline_template.go b/sdk/go/arvados/pipeline_template.go new file mode 100644 index 0000000000..31d9e8b2fe --- /dev/null +++ b/sdk/go/arvados/pipeline_template.go @@ -0,0 +1,27 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +package arvados + +import "time" + +// PipelineTemplate is an arvados#pipelineTemplate record +type PipelineTemplate struct { + UUID string `json:"uuid"` + Etag string `json:"etag"` + OwnerUUID string `json:"owner_uuid"` + CreatedAt time.Time `json:"created_at"` + ModifiedByClientUUID string `json:"modified_by_client_uuid"` + ModifiedByUserUUID string `json:"modified_by_user_uuid"` + ModifiedAt time.Time `json:"modified_at"` + Name string `json:"name"` + Components map[string]interface{} `json:"components"` + UpdatedAt time.Time `json:"updated_at"` + Description string `json:"description"` + WritableBy []string `json:"writable_by,omitempty"` +} + +func (g PipelineTemplate) resourceName() string { + return "pipelineTemplate" +} diff --git a/sdk/go/arvados/trait.go b/sdk/go/arvados/trait.go new file mode 100644 index 0000000000..fb0e799b6b --- /dev/null +++ b/sdk/go/arvados/trait.go @@ -0,0 +1,26 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 + +package arvados + +import "time" + +// Trait is an arvados#trait record +type Trait struct { + UUID string `json:"uuid"` + Etag string `json:"etag"` + OwnerUUID string `json:"owner_uuid"` + CreatedAt time.Time `json:"created_at"` + ModifiedByClientUUID string `json:"modified_by_client_uuid"` + ModifiedByUserUUID string `json:"modified_by_user_uuid"` + ModifiedAt time.Time `json:"modified_at"` + Name string `json:"name"` + Properties map[string]interface{} `json:"properties"` + UpdatedAt time.Time `json:"updated_at"` + WritableBy []string `json:"writable_by,omitempty"` +} + +func (g Trait) resourceName() string { + return "trait" +} diff --git a/sdk/go/arvadostest/api.go b/sdk/go/arvadostest/api.go index 930eabf27e..d9708e3b1a 100644 --- a/sdk/go/arvadostest/api.go +++ b/sdk/go/arvadostest/api.go @@ -129,6 +129,38 @@ func (as *APIStub) ContainerRequestDelete(ctx context.Context, options arvados.D as.appendCall(ctx, as.ContainerRequestDelete, options) return arvados.ContainerRequest{}, as.Error } +func (as *APIStub) GroupCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Group, error) { + as.appendCall(ctx, as.GroupCreate, options) + return arvados.Group{}, as.Error +} +func (as *APIStub) GroupUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Group, error) { + as.appendCall(ctx, as.GroupUpdate, options) + return arvados.Group{}, as.Error +} +func (as *APIStub) GroupGet(ctx context.Context, options arvados.GetOptions) (arvados.Group, error) { + as.appendCall(ctx, as.GroupGet, options) + return arvados.Group{}, as.Error +} +func (as *APIStub) GroupList(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) { + as.appendCall(ctx, as.GroupList, options) + return arvados.GroupList{}, as.Error +} +func (as *APIStub) GroupContents(ctx context.Context, options arvados.GroupContentsOptions) (arvados.ObjectList, error) { + as.appendCall(ctx, as.GroupContents, options) + return arvados.ObjectList{}, as.Error +} +func (as *APIStub) GroupShared(ctx context.Context, options arvados.ListOptions) (arvados.GroupList, error) { + as.appendCall(ctx, as.GroupShared, options) + return arvados.GroupList{}, as.Error +} +func (as *APIStub) GroupDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Group, error) { + as.appendCall(ctx, as.GroupDelete, options) + return arvados.Group{}, as.Error +} +func (as *APIStub) GroupUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Group, error) { + as.appendCall(ctx, as.GroupUntrash, options) + return arvados.Group{}, as.Error +} func (as *APIStub) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) { as.appendCall(ctx, as.SpecimenCreate, options) return arvados.Specimen{}, as.Error diff --git a/services/api/app/models/group.rb b/services/api/app/models/group.rb index 7e015f3564..870e0d0c45 100644 --- a/services/api/app/models/group.rb +++ b/services/api/app/models/group.rb @@ -42,14 +42,14 @@ class Group < ArvadosModel end def ensure_filesystem_compatible_name - # project groups need filesystem-compatible names, but others + # project and filter groups need filesystem-compatible names, but others # don't. - super if group_class == 'project' + super if group_class == 'project' || group_class == 'filter' end def check_group_class - if group_class != 'project' && group_class != 'role' - errors.add :group_class, "value must be one of 'project' or 'role', was '#{group_class}'" + if group_class != 'project' && group_class != 'role' && group_class != 'filter' + errors.add :group_class, "value must be one of 'project', 'role' or 'filter', was '#{group_class}'" end if group_class_changed? && !group_class_was.nil? errors.add :group_class, "cannot be modified after record is created" diff --git a/services/api/test/unit/group_test.rb b/services/api/test/unit/group_test.rb index 30fddfa5b8..d7a33a4515 100644 --- a/services/api/test/unit/group_test.rb +++ b/services/api/test/unit/group_test.rb @@ -62,7 +62,7 @@ class GroupTest < ActiveSupport::TestCase assert g_foo.errors.messages[:owner_uuid].join(" ").match(/ownership cycle/) end - test "cannot create a group that is not a 'role' or 'project'" do + test "cannot create a group that is not a 'role' or 'project' or 'filter'" do set_user_from_auth :active_trustedclient assert_raises(ActiveRecord::RecordInvalid) do @@ -278,6 +278,7 @@ class GroupTest < ActiveSupport::TestCase Rails.configuration.Collections.ForwardSlashNameSubstitution = subst proj = Group.create group_class: "project" role = Group.create group_class: "role" + filt = Group.create group_class: "filter" [[nil, true], ["", true], [".", false], @@ -292,6 +293,8 @@ class GroupTest < ActiveSupport::TestCase assert_equal true, role.valid? proj.name = name assert_equal valid, proj.valid?, "#{name.inspect} should be #{valid ? "valid" : "invalid"}" + filt.name = name + assert_equal valid, filt.valid?, "#{name.inspect} should be #{valid ? "valid" : "invalid"}" end end end