[ARVADOS] created: 1.3.0-1920-g0d11f61dd
Git user
git at public.curoverse.com
Mon Nov 25 21:15:23 UTC 2019
at 0d11f61ddd0bee6a679956ef2c15b868fa825add (commit)
commit 0d11f61ddd0bee6a679956ef2c15b868fa825add
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date: Mon Nov 25 15:32:21 2019 -0500
15720: Warn about empty ManagementToken or SystemRootToken.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>
diff --git a/doc/admin/management-token.html.textile.liquid b/doc/admin/management-token.html.textile.liquid
index 5380f38f9..cf3e273ce 100644
--- a/doc/admin/management-token.html.textile.liquid
+++ b/doc/admin/management-token.html.textile.liquid
@@ -16,17 +16,6 @@ Services must have ManagementToken configured. This is used to authorize access
To access a monitoring endpoint, the requester must provide the HTTP header @Authorization: Bearer (ManagementToken)@.
-h2. API server
-
-Set @ManagementToken@ in the appropriate section of @application.yml@
-
-<pre>
-production:
- # Token to be included in all healthcheck requests. Disabled by default.
- # Server expects request header of the format "Authorization: Bearer xxx"
- ManagementToken: xxx
-</pre>
-
h2. Node Manager
Set @port@ (the listen port) and @ManagementToken@ in the @Manage@ section of @node-manager.ini at .
@@ -45,12 +34,26 @@ Set @port@ (the listen port) and @ManagementToken@ in the @Manage@ section of @n
ManagementToken = xxx
</pre>
-h2. Other services
+h2. API server and other services
-The following services also support monitoring. Set @ManagementToken@ in the respective yaml config file for each service.
+The following services also support monitoring.
+* API server
+* arv-git-httpd
+* controller
+* keep-balance
+* keepproxy
* keepstore
* keep-web
-* keepproxy
-* arv-git-httpd
* websockets
+
+Set @ManagementToken@ in the appropriate section of @/etc/arvados/config.yml at .
+
+<notextile>
+<pre><code>Clusters:
+ <span class="userinput">uuid_prefix</span>:
+ # Token to be included in all healthcheck requests. Disabled by default.
+ # Server expects request header of the format "Authorization: Bearer xxx"
+ ManagementToken: xxx
+</code></pre>
+</notextile>
diff --git a/lib/config/cmd.go b/lib/config/cmd.go
index e9ceaca86..1ca278391 100644
--- a/lib/config/cmd.go
+++ b/lib/config/cmd.go
@@ -12,6 +12,7 @@ import (
"os"
"os/exec"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/ctxlog"
"github.com/ghodss/yaml"
"github.com/sirupsen/logrus"
@@ -124,6 +125,10 @@ func (checkCommand) RunCommand(prog string, args []string, stdin io.Reader, stdo
if err != nil {
return 1
}
+ problems := false
+ if warnAboutProblems(logger, withDepr) {
+ problems = true
+ }
cmd := exec.Command("diff", "-u", "--label", "without-deprecated-configs", "--label", "relying-on-deprecated-configs", "/dev/fd/3", "/dev/fd/4")
for _, obj := range []interface{}{withoutDepr, withDepr} {
y, _ := yaml.Marshal(obj)
@@ -153,7 +158,27 @@ func (checkCommand) RunCommand(prog string, args []string, stdin io.Reader, stdo
if logbuf.Len() > 0 {
return 1
}
- return 0
+
+ if problems {
+ return 1
+ } else {
+ return 0
+ }
+}
+
+func warnAboutProblems(logger logrus.FieldLogger, cfg *arvados.Config) bool {
+ warned := false
+ for id, cc := range cfg.Clusters {
+ if cc.SystemRootToken == "" {
+ logger.Warnf("Clusters.%s.SystemRootToken is empty; see https://doc.arvados.org/master/install/install-keepstore.html", id)
+ warned = true
+ }
+ if cc.ManagementToken == "" {
+ logger.Warnf("Clusters.%s.ManagementToken is empty; see https://doc.arvados.org/admin/management-token.html", id)
+ warned = true
+ }
+ }
+ return warned
}
var DumpDefaultsCommand defaultsCommand
diff --git a/lib/config/cmd_test.go b/lib/config/cmd_test.go
index fb1cba38b..c275e4c35 100644
--- a/lib/config/cmd_test.go
+++ b/lib/config/cmd_test.go
@@ -30,25 +30,27 @@ func (s *CommandSuite) SetUpSuite(c *check.C) {
os.Unsetenv("ARVADOS_API_TOKEN")
}
-func (s *CommandSuite) TestBadArg(c *check.C) {
+func (s *CommandSuite) TestDump_BadArg(c *check.C) {
var stderr bytes.Buffer
code := DumpCommand.RunCommand("arvados config-dump", []string{"-badarg"}, bytes.NewBuffer(nil), bytes.NewBuffer(nil), &stderr)
c.Check(code, check.Equals, 2)
c.Check(stderr.String(), check.Matches, `(?ms)flag provided but not defined: -badarg\nUsage:\n.*`)
}
-func (s *CommandSuite) TestEmptyInput(c *check.C) {
+func (s *CommandSuite) TestDump_EmptyInput(c *check.C) {
var stdout, stderr bytes.Buffer
code := DumpCommand.RunCommand("arvados config-dump", []string{"-config", "-"}, &bytes.Buffer{}, &stdout, &stderr)
c.Check(code, check.Equals, 1)
c.Check(stderr.String(), check.Matches, `config does not define any clusters\n`)
}
-func (s *CommandSuite) TestCheckNoDeprecatedKeys(c *check.C) {
+func (s *CommandSuite) TestCheck_NoWarnings(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
z1234:
+ ManagementToken: xyzzy
+ SystemRootToken: xyzzy
API:
MaxItemsPerResponse: 1234
PostgreSQL:
@@ -73,7 +75,7 @@ Clusters:
c.Check(stderr.String(), check.Equals, "")
}
-func (s *CommandSuite) TestCheckDeprecatedKeys(c *check.C) {
+func (s *CommandSuite) TestCheck_DeprecatedKeys(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
@@ -86,7 +88,7 @@ Clusters:
c.Check(stdout.String(), check.Matches, `(?ms).*\n\- +.*MaxItemsPerResponse: 1000\n\+ +MaxItemsPerResponse: 1234\n.*`)
}
-func (s *CommandSuite) TestCheckOldKeepstoreConfigFile(c *check.C) {
+func (s *CommandSuite) TestCheck_OldKeepstoreConfigFile(c *check.C) {
f, err := ioutil.TempFile("", "")
c.Assert(err, check.IsNil)
defer os.Remove(f.Name())
@@ -106,7 +108,7 @@ Clusters:
c.Check(stderr.String(), check.Matches, `(?ms).*you should remove the legacy keepstore config file.*\n`)
}
-func (s *CommandSuite) TestCheckUnknownKey(c *check.C) {
+func (s *CommandSuite) TestCheck_UnknownKey(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
@@ -130,7 +132,7 @@ Clusters:
c.Check(stderr.String(), check.Matches, `(?ms).*unexpected object in config entry: Clusters.z1234.PostgreSQL.ConnectionPool"\n.*`)
}
-func (s *CommandSuite) TestDumpFormatting(c *check.C) {
+func (s *CommandSuite) TestDump_Formatting(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
@@ -149,7 +151,7 @@ Clusters:
c.Check(stdout.String(), check.Matches, `(?ms).*http://localhost:12345: {}\n.*`)
}
-func (s *CommandSuite) TestDumpUnknownKey(c *check.C) {
+func (s *CommandSuite) TestDump_UnknownKey(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
commit f6c2b3f1f1fc0ee7144fbd200b8bb17f0b0f9b63
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date: Mon Nov 25 16:14:47 2019 -0500
15720: Update generated code.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>
diff --git a/lib/controller/federation/generated.go b/lib/controller/federation/generated.go
index 0a6664498..56a55d137 100755
--- a/lib/controller/federation/generated.go
+++ b/lib/controller/federation/generated.go
@@ -100,6 +100,8 @@ func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.Li
func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
var mtx sync.Mutex
var merged arvados.UserList
+ 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) {
cl, err := backend.UserList(ctx, options)
if err != nil {
@@ -109,8 +111,9 @@ func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOp
defer mtx.Unlock()
if len(merged.Items) == 0 {
merged = cl
- } else {
+ } 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 {
@@ -118,6 +121,18 @@ func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOp
}
return uuids, nil
})
- sort.Slice(merged.Items, func(i, j int) bool { return merged.Items[i].UUID < merged.Items[j].UUID })
+ 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.User{}
+ }
return merged, err
}
commit 5ee93e408c0e547dfb03b2f3d039a7715126395b
Merge: 233a2b6bd 607d2b119
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date: Mon Nov 25 15:20:21 2019 -0500
15720: Merge branch 'master' into 15720-fed-user-list
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>
diff --cc lib/controller/federation/generated.go
index 961cd5a40,fb91a8496..0a6664498
--- a/lib/controller/federation/generated.go
+++ b/lib/controller/federation/generated.go
@@@ -16,9 -17,11 +17,11 @@@ import
// -- this file is auto-generated -- do not edit -- edit list.go and run "go generate" instead --
//
-func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
+func (conn *Conn) generated_ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
var mtx sync.Mutex
var merged arvados.ContainerList
+ 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) {
cl, err := backend.ContainerList(ctx, options)
if err != nil {
@@@ -41,9 -57,11 +57,11 @@@
return merged, err
}
-func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
+func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
var mtx sync.Mutex
var merged arvados.SpecimenList
+ 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) {
cl, err := backend.SpecimenList(ctx, options)
if err != nil {
@@@ -62,31 -81,18 +81,43 @@@
}
return uuids, nil
})
- sort.Slice(merged.Items, func(i, j int) bool { return merged.Items[i].UUID < merged.Items[j].UUID })
+ 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.Specimen{}
+ }
return merged, err
}
+
+func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ var mtx sync.Mutex
+ var merged arvados.UserList
+ err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
+ cl, err := backend.UserList(ctx, options)
+ if err != nil {
+ return nil, err
+ }
+ mtx.Lock()
+ defer mtx.Unlock()
+ if len(merged.Items) == 0 {
+ merged = cl
+ } else {
+ merged.Items = append(merged.Items, cl.Items...)
+ }
+ uuids := make([]string, 0, len(cl.Items))
+ for _, item := range cl.Items {
+ uuids = append(uuids, item.UUID)
+ }
+ return uuids, nil
+ })
+ sort.Slice(merged.Items, func(i, j int) bool { return merged.Items[i].UUID < merged.Items[j].UUID })
+ return merged, err
+}
diff --cc lib/controller/federation/list.go
index 7178d7b0a,54f59812a..26b6b254e
--- a/lib/controller/federation/list.go
+++ b/lib/controller/federation/list.go
@@@ -20,9 -21,11 +21,11 @@@ import
// CollectionList is used as a template to auto-generate List()
// methods for other types; see generate.go.
-func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
+func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
var mtx sync.Mutex
var merged arvados.CollectionList
+ 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) {
cl, err := backend.CollectionList(ctx, options)
if err != nil {
diff --cc lib/controller/federation/list_test.go
index 5a630a945,35d201028..a9c4f588f
--- a/lib/controller/federation/list_test.go
+++ b/lib/controller/federation/list_test.go
@@@ -8,9 -8,18 +8,10 @@@ import
"context"
"fmt"
"net/http"
- "net/url"
- "os"
+ "sort"
- "testing"
- "git.curoverse.com/arvados.git/lib/controller/router"
- "git.curoverse.com/arvados.git/lib/controller/rpc"
"git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/arvadostest"
- "git.curoverse.com/arvados.git/sdk/go/auth"
- "git.curoverse.com/arvados.git/sdk/go/ctxlog"
- "git.curoverse.com/arvados.git/sdk/go/httpserver"
check "gopkg.in/check.v1"
)
commit 233a2b6bd23a3e2054cfc0690f2bc06c0f9f7323
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date: Tue Nov 19 09:49:15 2019 -0500
15720: Batch user update API.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>
diff --git a/lib/controller/federation/conn.go b/lib/controller/federation/conn.go
index 47ae0ba9e..1d8fa7e46 100644
--- a/lib/controller/federation/conn.go
+++ b/lib/controller/federation/conn.go
@@ -15,6 +15,7 @@ import (
"net/url"
"regexp"
"strings"
+ "time"
"git.curoverse.com/arvados.git/lib/config"
"git.curoverse.com/arvados.git/lib/controller/localdb"
@@ -350,12 +351,25 @@ func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (ar
if err != nil {
return resp, err
}
- ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
+ batchOpts := arvados.UserBatchUpdateOptions{Updates: map[string]map[string]interface{}{}}
for _, user := range resp.Items {
if !strings.HasPrefix(user.UUID, id) {
continue
}
- logger.Debug("cache user info for uuid %q", user.UUID)
+ logger.Debugf("cache user info for uuid %q", user.UUID)
+
+ // If the remote cluster has null timestamps
+ // (e.g., test server with incomplete
+ // fixtures) use dummy timestamps (instead of
+ // the zero time, which causes a Rails API
+ // error "year too big to marshal: 1 UTC").
+ if user.ModifiedAt.IsZero() {
+ user.ModifiedAt = time.Now()
+ }
+ if user.CreatedAt.IsZero() {
+ user.CreatedAt = time.Now()
+ }
+
var allFields map[string]interface{}
buf, err := json.Marshal(user)
if err != nil {
@@ -380,19 +394,13 @@ func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (ar
}
}
}
- _, err = conn.local.UserUpdate(ctxRoot, arvados.UpdateOptions{
- UUID: user.UUID,
- Attrs: updates,
- })
- if errStatus(err) == http.StatusNotFound {
- updates["uuid"] = user.UUID
- _, err = conn.local.UserCreate(ctxRoot, arvados.CreateOptions{
- Attrs: updates,
- })
- }
+ batchOpts.Updates[user.UUID] = updates
+ }
+ if len(batchOpts.Updates) > 0 {
+ ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
+ _, err = conn.local.UserBatchUpdate(ctxRoot, batchOpts)
if err != nil {
- logger.WithError(err).WithField("UUID", user.UUID).Error("error updating local user record")
- return arvados.UserList{}, fmt.Errorf("error updating local user record: %s", err)
+ return arvados.UserList{}, fmt.Errorf("error updating local user records: %s", err)
}
}
return resp, nil
@@ -445,6 +453,10 @@ func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions)
return conn.chooseBackend(options.UUID).UserDelete(ctx, options)
}
+func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ return conn.local.UserBatchUpdate(ctx, options)
+}
+
func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
return conn.chooseBackend(options.UUID).APIClientAuthorizationCurrent(ctx, options)
}
diff --git a/lib/controller/federation/user_test.go b/lib/controller/federation/user_test.go
index 993be9b0b..8202a668f 100644
--- a/lib/controller/federation/user_test.go
+++ b/lib/controller/federation/user_test.go
@@ -53,7 +53,7 @@ func (s *UserSuite) TestLoginClusterUserList(c *check.C) {
c.Check(stub.Calls(nil), check.HasLen, 0)
} else if updateFail {
c.Logf("... err %#v", err)
- calls := stub.Calls(stub.UserUpdate)
+ calls := stub.Calls(stub.UserBatchUpdate)
if c.Check(calls, check.HasLen, 1) {
c.Logf("... stub.UserUpdate called with options: %#v", calls[0].Options)
shouldUpdate := map[string]bool{
@@ -84,8 +84,11 @@ func (s *UserSuite) TestLoginClusterUserList(c *check.C) {
}
}
}
+ var uuid string
+ for uuid = range calls[0].Options.(arvados.UserBatchUpdateOptions).Updates {
+ }
for k, shouldFind := range shouldUpdate {
- _, found := calls[0].Options.(arvados.UpdateOptions).Attrs[k]
+ _, found := calls[0].Options.(arvados.UserBatchUpdateOptions).Updates[uuid][k]
c.Check(found, check.Equals, shouldFind, check.Commentf("offending attr: %s", k))
}
}
@@ -93,13 +96,13 @@ func (s *UserSuite) TestLoginClusterUserList(c *check.C) {
updates := 0
for _, d := range spy.RequestDumps {
d := string(d)
- if strings.Contains(d, "PATCH /arvados/v1/users/zzzzz-tpzed-") {
+ if strings.Contains(d, "PATCH /arvados/v1/users/batch") {
c.Check(d, check.Matches, `(?ms).*Authorization: Bearer `+arvadostest.SystemRootToken+`.*`)
updates++
}
}
c.Check(err, check.IsNil)
- c.Check(updates, check.Equals, len(userlist.Items))
+ c.Check(updates, check.Equals, 1)
c.Logf("... response items %#v", userlist.Items)
}
}
diff --git a/lib/controller/router/router.go b/lib/controller/router/router.go
index 250f3cb45..47082197a 100644
--- a/lib/controller/router/router.go
+++ b/lib/controller/router/router.go
@@ -283,6 +283,13 @@ func (rtr *router) addRoutes() {
},
},
{
+ arvados.EndpointUserBatchUpdate,
+ func() interface{} { return &arvados.UserBatchUpdateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserBatchUpdate(ctx, *opts.(*arvados.UserBatchUpdateOptions))
+ },
+ },
+ {
arvados.EndpointUserDelete,
func() interface{} { return &arvados.DeleteOptions{} },
func(ctx context.Context, opts interface{}) (interface{}, error) {
diff --git a/lib/controller/rpc/conn.go b/lib/controller/rpc/conn.go
index 25efcfd43..66523e5ac 100644
--- a/lib/controller/rpc/conn.go
+++ b/lib/controller/rpc/conn.go
@@ -406,3 +406,10 @@ func (conn *Conn) UserSessionCreate(ctx context.Context, options UserSessionCrea
err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
return resp, err
}
+
+func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ ep := arvados.APIEndpoint{Method: "PATCH", Path: "arvados/v1/users/batch_update"}
+ var resp arvados.UserList
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
diff --git a/sdk/go/arvados/api.go b/sdk/go/arvados/api.go
index d86df9ef3..7d6ddb317 100644
--- a/sdk/go/arvados/api.go
+++ b/sdk/go/arvados/api.go
@@ -54,6 +54,7 @@ var (
EndpointUserUnsetup = APIEndpoint{"POST", "arvados/v1/users/{uuid}/unsetup", ""}
EndpointUserUpdate = APIEndpoint{"PATCH", "arvados/v1/users/{uuid}", "user"}
EndpointUserUpdateUUID = APIEndpoint{"POST", "arvados/v1/users/{uuid}/update_uuid", ""}
+ EndpointUserBatchUpdate = APIEndpoint{"PATCH", "arvados/v1/users/batch", ""}
EndpointAPIClientAuthorizationCurrent = APIEndpoint{"GET", "arvados/v1/api_client_authorizations/current", ""}
)
@@ -119,6 +120,12 @@ type UserMergeOptions struct {
NewUserToken string `json:"new_user_token,omitempty"`
}
+type UserBatchUpdateOptions struct {
+ Updates map[string]map[string]interface{} `json:"updates"`
+}
+
+type UserBatchUpdateResponse struct{}
+
type DeleteOptions struct {
UUID string `json:"uuid"`
}
@@ -166,5 +173,6 @@ type API interface {
UserGetSystem(ctx context.Context, options GetOptions) (User, error)
UserList(ctx context.Context, options ListOptions) (UserList, error)
UserDelete(ctx context.Context, options DeleteOptions) (User, error)
+ UserBatchUpdate(context.Context, UserBatchUpdateOptions) (UserList, error)
APIClientAuthorizationCurrent(ctx context.Context, options GetOptions) (APIClientAuthorization, error)
}
diff --git a/sdk/go/arvadostest/api.go b/sdk/go/arvadostest/api.go
index f18af8caa..91e3ee8ba 100644
--- a/sdk/go/arvadostest/api.go
+++ b/sdk/go/arvadostest/api.go
@@ -169,6 +169,10 @@ func (as *APIStub) UserMerge(ctx context.Context, options arvados.UserMergeOptio
as.appendCall(as.UserMerge, ctx, options)
return arvados.User{}, as.Error
}
+func (as *APIStub) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ as.appendCall(as.UserBatchUpdate, ctx, options)
+ return arvados.UserList{}, as.Error
+}
func (as *APIStub) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
as.appendCall(as.APIClientAuthorizationCurrent, ctx, options)
return arvados.APIClientAuthorization{}, as.Error
diff --git a/services/api/app/controllers/arvados/v1/users_controller.rb b/services/api/app/controllers/arvados/v1/users_controller.rb
index 2889eacee..ddf74cec6 100644
--- a/services/api/app/controllers/arvados/v1/users_controller.rb
+++ b/services/api/app/controllers/arvados/v1/users_controller.rb
@@ -4,12 +4,31 @@
class Arvados::V1::UsersController < ApplicationController
accept_attribute_as_json :prefs, Hash
+ accept_param_as_json :updates
skip_before_action :find_object_by_uuid, only:
- [:activate, :current, :system, :setup, :merge]
+ [:activate, :current, :system, :setup, :merge, :batch_update]
skip_before_action :render_404_if_no_object, only:
- [:activate, :current, :system, :setup, :merge]
- before_action :admin_required, only: [:setup, :unsetup, :update_uuid]
+ [:activate, :current, :system, :setup, :merge, :batch_update]
+ before_action :admin_required, only: [:setup, :unsetup, :update_uuid, :batch_update]
+
+ # Internal API used by controller to update local cache of user
+ # records from LoginCluster.
+ def batch_update
+ @objects = []
+ params[:updates].andand.each do |uuid, attrs|
+ begin
+ u = User.find_or_create_by(uuid: uuid)
+ rescue ActiveRecord::RecordNotUnique
+ retry
+ end
+ u.update_attributes!(attrs)
+ @objects << u
+ end
+ @offset = 0
+ @limit = -1
+ render_list
+ end
def current
if current_user
diff --git a/services/api/config/routes.rb b/services/api/config/routes.rb
index b54c3c5bf..8afd22192 100644
--- a/services/api/config/routes.rb
+++ b/services/api/config/routes.rb
@@ -83,6 +83,7 @@ Server::Application.routes.draw do
post 'unsetup', on: :member
post 'update_uuid', on: :member
post 'merge', on: :collection
+ patch 'batch_update', on: :collection
end
resources :virtual_machines do
get 'logins', on: :member
diff --git a/services/api/test/functional/arvados/v1/users_controller_test.rb b/services/api/test/functional/arvados/v1/users_controller_test.rb
index d5db10396..74f017a67 100644
--- a/services/api/test/functional/arvados/v1/users_controller_test.rb
+++ b/services/api/test/functional/arvados/v1/users_controller_test.rb
@@ -1043,6 +1043,47 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
assert_nil(users(:project_viewer).redirect_to_user_uuid)
end
+ test "batch update fails for non-admin" do
+ authorize_with(:active)
+ patch(:batch_update, params: {updates: {}})
+ assert_response(403)
+ end
+
+ test "batch update" do
+ existinguuid = 'remot-tpzed-foobarbazwazqux'
+ newuuid = 'remot-tpzed-newnarnazwazqux'
+ act_as_system_user do
+ User.create!(uuid: existinguuid, email: 'root at existing.example.com')
+ end
+
+ authorize_with(:admin)
+ patch(:batch_update,
+ params: {
+ updates: {
+ existinguuid => {
+ 'first_name' => 'root',
+ 'email' => 'root at remot.example.com',
+ 'is_active' => true,
+ 'is_admin' => true,
+ 'prefs' => {'foo' => 'bar'},
+ },
+ newuuid => {
+ 'first_name' => 'noot',
+ 'email' => 'root at remot.example.com',
+ },
+ }})
+ assert_response(:success)
+
+ assert_equal('root', User.find_by_uuid(existinguuid).first_name)
+ assert_equal('root at remot.example.com', User.find_by_uuid(existinguuid).email)
+ assert_equal(true, User.find_by_uuid(existinguuid).is_active)
+ assert_equal(true, User.find_by_uuid(existinguuid).is_admin)
+ assert_equal({'foo' => 'bar'}, User.find_by_uuid(existinguuid).prefs)
+
+ assert_equal('noot', User.find_by_uuid(newuuid).first_name)
+ assert_equal('root at remot.example.com', User.find_by_uuid(newuuid).email)
+ end
+
NON_ADMIN_USER_DATA = ["uuid", "kind", "is_active", "email", "first_name",
"last_name", "username"].sort
commit b047cc92acab06d4c1f1d80173486f951b63d729
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date: Mon Nov 18 11:22:06 2019 -0500
15720: Defer user listing to login cluster, cache results locally.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>
diff --git a/lib/controller/federation/conn.go b/lib/controller/federation/conn.go
index 9d01f1f7c..47ae0ba9e 100644
--- a/lib/controller/federation/conn.go
+++ b/lib/controller/federation/conn.go
@@ -323,8 +323,82 @@ func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOpti
return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options)
}
+var userAttrsCachedFromLoginCluster = map[string]bool{
+ "created_at": true,
+ "email": true,
+ "first_name": true,
+ "is_active": true,
+ "is_admin": true,
+ "last_name": true,
+ "modified_at": true,
+ "modified_by_client_uuid": true,
+ "modified_by_user_uuid": true,
+ "prefs": true,
+ "username": true,
+
+ "full_name": false,
+ "identity_url": false,
+ "is_invited": false,
+ "owner_uuid": false,
+ "uuid": false,
+}
+
func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
- return conn.generated_UserList(ctx, options)
+ logger := ctxlog.FromContext(ctx)
+ if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID {
+ resp, err := conn.chooseBackend(id).UserList(ctx, options)
+ if err != nil {
+ return resp, err
+ }
+ ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
+ for _, user := range resp.Items {
+ if !strings.HasPrefix(user.UUID, id) {
+ continue
+ }
+ logger.Debug("cache user info for uuid %q", user.UUID)
+ var allFields map[string]interface{}
+ buf, err := json.Marshal(user)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error encoding user record from remote response: %s", err)
+ }
+ err = json.Unmarshal(buf, &allFields)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error transcoding user record from remote response: %s", err)
+ }
+ updates := allFields
+ if len(options.Select) > 0 {
+ updates = map[string]interface{}{}
+ for _, k := range options.Select {
+ if v, ok := allFields[k]; ok && userAttrsCachedFromLoginCluster[k] {
+ updates[k] = v
+ }
+ }
+ } else {
+ for k := range updates {
+ if !userAttrsCachedFromLoginCluster[k] {
+ delete(updates, k)
+ }
+ }
+ }
+ _, err = conn.local.UserUpdate(ctxRoot, arvados.UpdateOptions{
+ UUID: user.UUID,
+ Attrs: updates,
+ })
+ if errStatus(err) == http.StatusNotFound {
+ updates["uuid"] = user.UUID
+ _, err = conn.local.UserCreate(ctxRoot, arvados.CreateOptions{
+ Attrs: updates,
+ })
+ }
+ if err != nil {
+ logger.WithError(err).WithField("UUID", user.UUID).Error("error updating local user record")
+ return arvados.UserList{}, fmt.Errorf("error updating local user record: %s", err)
+ }
+ }
+ return resp, nil
+ } else {
+ return conn.generated_UserList(ctx, options)
+ }
}
func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
diff --git a/lib/controller/federation/federation_test.go b/lib/controller/federation/federation_test.go
new file mode 100644
index 000000000..60164b462
--- /dev/null
+++ b/lib/controller/federation/federation_test.go
@@ -0,0 +1,75 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package federation
+
+import (
+ "context"
+ "net/url"
+ "os"
+ "testing"
+
+ "git.curoverse.com/arvados.git/lib/controller/router"
+ "git.curoverse.com/arvados.git/lib/controller/rpc"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
+ "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+ "git.curoverse.com/arvados.git/sdk/go/auth"
+ "git.curoverse.com/arvados.git/sdk/go/ctxlog"
+ "git.curoverse.com/arvados.git/sdk/go/httpserver"
+ check "gopkg.in/check.v1"
+)
+
+// Gocheck boilerplate
+func Test(t *testing.T) {
+ check.TestingT(t)
+}
+
+// FederationSuite does some generic setup/teardown. Don't add Test*
+// methods to FederationSuite itself.
+type FederationSuite struct {
+ cluster *arvados.Cluster
+ ctx context.Context
+ fed *Conn
+}
+
+func (s *FederationSuite) SetUpTest(c *check.C) {
+ s.cluster = &arvados.Cluster{
+ ClusterID: "aaaaa",
+ SystemRootToken: arvadostest.SystemRootToken,
+ RemoteClusters: map[string]arvados.RemoteCluster{
+ "aaaaa": arvados.RemoteCluster{
+ Host: os.Getenv("ARVADOS_API_HOST"),
+ },
+ },
+ }
+ arvadostest.SetServiceURL(&s.cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
+ s.cluster.TLS.Insecure = true
+ s.cluster.API.MaxItemsPerResponse = 3
+
+ ctx := context.Background()
+ ctx = ctxlog.Context(ctx, ctxlog.TestLogger(c))
+ ctx = auth.NewContext(ctx, &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
+ s.ctx = ctx
+
+ s.fed = New(s.cluster)
+}
+
+func (s *FederationSuite) addDirectRemote(c *check.C, id string, backend backend) {
+ s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
+ Host: "in-process.local",
+ }
+ s.fed.remotes[id] = backend
+}
+
+func (s *FederationSuite) addHTTPRemote(c *check.C, id string, backend backend) {
+ srv := httpserver.Server{Addr: ":"}
+ srv.Handler = router.New(backend)
+ c.Check(srv.Start(), check.IsNil)
+ s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
+ Scheme: "http",
+ Host: srv.Addr,
+ Proxy: true,
+ }
+ s.fed.remotes[id] = rpc.NewConn(id, &url.URL{Scheme: "http", Host: srv.Addr}, true, saltedTokenProvider(s.fed.local, id))
+}
diff --git a/lib/controller/federation/list_test.go b/lib/controller/federation/list_test.go
index c9b981fc1..5a630a945 100644
--- a/lib/controller/federation/list_test.go
+++ b/lib/controller/federation/list_test.go
@@ -8,74 +8,13 @@ import (
"context"
"fmt"
"net/http"
- "net/url"
- "os"
- "testing"
- "git.curoverse.com/arvados.git/lib/controller/router"
- "git.curoverse.com/arvados.git/lib/controller/rpc"
"git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/arvadostest"
- "git.curoverse.com/arvados.git/sdk/go/auth"
- "git.curoverse.com/arvados.git/sdk/go/ctxlog"
- "git.curoverse.com/arvados.git/sdk/go/httpserver"
check "gopkg.in/check.v1"
)
-// Gocheck boilerplate
-func Test(t *testing.T) {
- check.TestingT(t)
-}
-
-var (
- _ = check.Suite(&FederationSuite{})
- _ = check.Suite(&CollectionListSuite{})
-)
-
-type FederationSuite struct {
- cluster *arvados.Cluster
- ctx context.Context
- fed *Conn
-}
-
-func (s *FederationSuite) SetUpTest(c *check.C) {
- s.cluster = &arvados.Cluster{
- ClusterID: "aaaaa",
- RemoteClusters: map[string]arvados.RemoteCluster{
- "aaaaa": arvados.RemoteCluster{
- Host: os.Getenv("ARVADOS_API_HOST"),
- },
- },
- }
- arvadostest.SetServiceURL(&s.cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
- s.cluster.TLS.Insecure = true
- s.cluster.API.MaxItemsPerResponse = 3
-
- ctx := context.Background()
- ctx = ctxlog.Context(ctx, ctxlog.TestLogger(c))
- ctx = auth.NewContext(ctx, &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
- s.ctx = ctx
-
- s.fed = New(s.cluster)
-}
-
-func (s *FederationSuite) addDirectRemote(c *check.C, id string, backend backend) {
- s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
- Host: "in-process.local",
- }
- s.fed.remotes[id] = backend
-}
-
-func (s *FederationSuite) addHTTPRemote(c *check.C, id string, backend backend) {
- srv := httpserver.Server{Addr: ":"}
- srv.Handler = router.New(backend)
- c.Check(srv.Start(), check.IsNil)
- s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
- Host: srv.Addr,
- Proxy: true,
- }
- s.fed.remotes[id] = rpc.NewConn(id, &url.URL{Scheme: "http", Host: srv.Addr}, true, saltedTokenProvider(s.fed.local, id))
-}
+var _ = check.Suite(&CollectionListSuite{})
type collectionLister struct {
arvadostest.APIStub
diff --git a/lib/controller/federation/login_test.go b/lib/controller/federation/login_test.go
index e001014e2..f83f5fb93 100644
--- a/lib/controller/federation/login_test.go
+++ b/lib/controller/federation/login_test.go
@@ -13,7 +13,13 @@ import (
check "gopkg.in/check.v1"
)
-func (s *FederationSuite) TestDeferToLoginCluster(c *check.C) {
+var _ = check.Suite(&LoginSuite{})
+
+type LoginSuite struct {
+ FederationSuite
+}
+
+func (s *LoginSuite) TestDeferToLoginCluster(c *check.C) {
s.addHTTPRemote(c, "zhome", &arvadostest.APIStub{})
s.cluster.Login.LoginCluster = "zhome"
diff --git a/lib/controller/federation/user_test.go b/lib/controller/federation/user_test.go
new file mode 100644
index 000000000..993be9b0b
--- /dev/null
+++ b/lib/controller/federation/user_test.go
@@ -0,0 +1,121 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package federation
+
+import (
+ "encoding/json"
+ "errors"
+ "net/url"
+ "os"
+ "strings"
+
+ "git.curoverse.com/arvados.git/lib/controller/rpc"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
+ "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+ check "gopkg.in/check.v1"
+)
+
+var _ = check.Suite(&UserSuite{})
+
+type UserSuite struct {
+ FederationSuite
+}
+
+func (s *UserSuite) TestLoginClusterUserList(c *check.C) {
+ s.cluster.ClusterID = "local"
+ s.cluster.Login.LoginCluster = "zzzzz"
+ s.fed = New(s.cluster)
+ s.addDirectRemote(c, "zzzzz", rpc.NewConn("zzzzz", &url.URL{Scheme: "https", Host: os.Getenv("ARVADOS_API_HOST")}, true, rpc.PassthroughTokenProvider))
+
+ for _, updateFail := range []bool{false, true} {
+ for _, opts := range []arvados.ListOptions{
+ {Offset: 0, Limit: -1, Select: nil},
+ {Offset: 1, Limit: 1, Select: nil},
+ {Offset: 0, Limit: 2, Select: []string{"uuid"}},
+ {Offset: 0, Limit: 2, Select: []string{"uuid", "email"}},
+ } {
+ c.Logf("updateFail %v, opts %#v", updateFail, opts)
+ spy := arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
+ stub := &arvadostest.APIStub{Error: errors.New("local cluster failure")}
+ if updateFail {
+ s.fed.local = stub
+ } else {
+ s.fed.local = rpc.NewConn(s.cluster.ClusterID, spy.URL, true, rpc.PassthroughTokenProvider)
+ }
+ userlist, err := s.fed.UserList(s.ctx, opts)
+ if updateFail && err == nil {
+ // All local updates fail, so the only
+ // cases expected to succeed are the
+ // ones with 0 results.
+ c.Check(userlist.Items, check.HasLen, 0)
+ c.Check(stub.Calls(nil), check.HasLen, 0)
+ } else if updateFail {
+ c.Logf("... err %#v", err)
+ calls := stub.Calls(stub.UserUpdate)
+ if c.Check(calls, check.HasLen, 1) {
+ c.Logf("... stub.UserUpdate called with options: %#v", calls[0].Options)
+ shouldUpdate := map[string]bool{
+ "uuid": false,
+ "email": true,
+ "first_name": true,
+ "last_name": true,
+ "is_admin": true,
+ "is_active": true,
+ "prefs": true,
+ // can't safely update locally
+ "owner_uuid": false,
+ "identity_url": false,
+ // virtual attrs
+ "full_name": false,
+ "is_invited": false,
+ }
+ if opts.Select != nil {
+ // Only the selected
+ // fields (minus uuid)
+ // should be updated.
+ for k := range shouldUpdate {
+ shouldUpdate[k] = false
+ }
+ for _, k := range opts.Select {
+ if k != "uuid" {
+ shouldUpdate[k] = true
+ }
+ }
+ }
+ for k, shouldFind := range shouldUpdate {
+ _, found := calls[0].Options.(arvados.UpdateOptions).Attrs[k]
+ c.Check(found, check.Equals, shouldFind, check.Commentf("offending attr: %s", k))
+ }
+ }
+ } else {
+ updates := 0
+ for _, d := range spy.RequestDumps {
+ d := string(d)
+ if strings.Contains(d, "PATCH /arvados/v1/users/zzzzz-tpzed-") {
+ c.Check(d, check.Matches, `(?ms).*Authorization: Bearer `+arvadostest.SystemRootToken+`.*`)
+ updates++
+ }
+ }
+ c.Check(err, check.IsNil)
+ c.Check(updates, check.Equals, len(userlist.Items))
+ c.Logf("... response items %#v", userlist.Items)
+ }
+ }
+ }
+}
+
+// userAttrsCachedFromLoginCluster must have an entry for every field
+// in the User struct.
+func (s *UserSuite) TestUserAttrsUpdateWhitelist(c *check.C) {
+ buf, err := json.Marshal(&arvados.User{})
+ c.Assert(err, check.IsNil)
+ var allFields map[string]interface{}
+ err = json.Unmarshal(buf, &allFields)
+ c.Assert(err, check.IsNil)
+ for k := range allFields {
+ _, ok := userAttrsCachedFromLoginCluster[k]
+ c.Check(ok, check.Equals, true, check.Commentf("field name %q missing from userAttrsCachedFromLoginCluster", k))
+ }
+}
diff --git a/sdk/go/arvados/user.go b/sdk/go/arvados/user.go
index 27d2b28a4..30bc094d0 100644
--- a/sdk/go/arvados/user.go
+++ b/sdk/go/arvados/user.go
@@ -4,13 +4,26 @@
package arvados
+import "time"
+
// User is an arvados#user record
type User struct {
- UUID string `json:"uuid"`
- IsActive bool `json:"is_active"`
- IsAdmin bool `json:"is_admin"`
- Username string `json:"username"`
- Email string `json:"email"`
+ UUID string `json:"uuid"`
+ IsActive bool `json:"is_active"`
+ IsAdmin bool `json:"is_admin"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ FullName string `json:"full_name"`
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ IdentityURL string `json:"identity_url"`
+ IsInvited bool `json:"is_invited"`
+ OwnerUUID string `json:"owner_uuid"`
+ CreatedAt time.Time `json:"created_at"`
+ ModifiedAt time.Time `json:"modified_at"`
+ ModifiedByUserUUID string `json:"modified_by_user_uuid"`
+ ModifiedByClientUUID string `json:"modified_by_client_uuid"`
+ Prefs map[string]interface{} `json:"prefs"`
}
// UserList is an arvados#userList resource.
diff --git a/sdk/go/arvadostest/api.go b/sdk/go/arvadostest/api.go
index 96b7c75f1..f18af8caa 100644
--- a/sdk/go/arvadostest/api.go
+++ b/sdk/go/arvadostest/api.go
@@ -185,7 +185,6 @@ func (as *APIStub) Calls(method interface{}) []APIStubCall {
defer as.mtx.Unlock()
var calls []APIStubCall
for _, call := range as.calls {
-
if method == nil || (runtime.FuncForPC(reflect.ValueOf(call.Method).Pointer()).Name() ==
runtime.FuncForPC(reflect.ValueOf(method).Pointer()).Name()) {
calls = append(calls, call)
diff --git a/sdk/go/arvadostest/fixtures.go b/sdk/go/arvadostest/fixtures.go
index be29bc23e..10b95c037 100644
--- a/sdk/go/arvadostest/fixtures.go
+++ b/sdk/go/arvadostest/fixtures.go
@@ -13,6 +13,7 @@ const (
AdminToken = "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h"
AnonymousToken = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi"
DataManagerToken = "320mkve8qkswstz7ff61glpk3mhgghmg67wmic7elw4z41pke1"
+ SystemRootToken = "systemusertesttoken1234567890aoeuidhtnsqjkxbmwvzpy"
ManagementToken = "jg3ajndnq63sywcd50gbs5dskdc9ckkysb0nsqmfz08nwf17nl"
ActiveUserUUID = "zzzzz-tpzed-xurymjxw79nv3jz"
FederatedActiveUserUUID = "zbbbb-tpzed-xurymjxw79nv3jz"
diff --git a/sdk/python/tests/run_test_server.py b/sdk/python/tests/run_test_server.py
index 48aabbbe4..dd74df236 100644
--- a/sdk/python/tests/run_test_server.py
+++ b/sdk/python/tests/run_test_server.py
@@ -719,7 +719,7 @@ def setup_config():
"zzzzz": {
"EnableBetaController14287": ('14287' in os.environ.get('ARVADOS_EXPERIMENTAL', '')),
"ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
- "SystemRootToken": auth_token('data_manager'),
+ "SystemRootToken": auth_token('system_user'),
"API": {
"RequestTimeout": "30s",
},
diff --git a/services/keep-balance/integration_test.go b/services/keep-balance/integration_test.go
index 5b0dc123a..43816a213 100644
--- a/services/keep-balance/integration_test.go
+++ b/services/keep-balance/integration_test.go
@@ -39,7 +39,7 @@ func (s *integrationSuite) SetUpSuite(c *check.C) {
arvadostest.StartKeep(4, true)
arv, err := arvadosclient.MakeArvadosClient()
- arv.ApiToken = arvadostest.DataManagerToken
+ arv.ApiToken = arvadostest.SystemRootToken
c.Assert(err, check.IsNil)
s.keepClient, err = keepclient.MakeKeepClient(arv)
@@ -71,7 +71,7 @@ func (s *integrationSuite) SetUpTest(c *check.C) {
s.client = &arvados.Client{
APIHost: os.Getenv("ARVADOS_API_HOST"),
- AuthToken: arvadostest.DataManagerToken,
+ AuthToken: arvadostest.SystemRootToken,
Insecure: true,
}
}
diff --git a/services/keepstore/handler_test.go b/services/keepstore/handler_test.go
index 54b4871fa..8247ce480 100644
--- a/services/keepstore/handler_test.go
+++ b/services/keepstore/handler_test.go
@@ -46,7 +46,7 @@ func testCluster(t TB) *arvados.Cluster {
if err != nil {
t.Fatal(err)
}
- cluster.SystemRootToken = arvadostest.DataManagerToken
+ cluster.SystemRootToken = arvadostest.SystemRootToken
cluster.ManagementToken = arvadostest.ManagementToken
cluster.Collections.BlobSigning = false
return cluster
diff --git a/services/keepstore/mounts_test.go b/services/keepstore/mounts_test.go
index dd6247f8b..301934153 100644
--- a/services/keepstore/mounts_test.go
+++ b/services/keepstore/mounts_test.go
@@ -55,7 +55,7 @@ func (s *HandlerSuite) TestMounts(c *check.C) {
c.Check(resp.Body.String(), check.Equals, "Unauthorized\n")
}
- tok := arvadostest.DataManagerToken
+ tok := arvadostest.SystemRootToken
// Nonexistent mount UUID
resp = s.call("GET", "/mounts/X/blocks", tok, nil)
diff --git a/services/keepstore/proxy_remote_test.go b/services/keepstore/proxy_remote_test.go
index 6483d6cf0..fd98aa9cb 100644
--- a/services/keepstore/proxy_remote_test.go
+++ b/services/keepstore/proxy_remote_test.go
@@ -89,7 +89,7 @@ func (s *ProxyRemoteSuite) SetUpTest(c *check.C) {
s.remoteAPI.StartTLS()
s.cluster = testCluster(c)
s.cluster.Collections.BlobSigningKey = knownKey
- s.cluster.SystemRootToken = arvadostest.DataManagerToken
+ s.cluster.SystemRootToken = arvadostest.SystemRootToken
s.cluster.RemoteClusters = map[string]arvados.RemoteCluster{
s.remoteClusterID: arvados.RemoteCluster{
Host: strings.Split(s.remoteAPI.URL, "//")[1],
diff --git a/tools/keep-rsync/keep-rsync_test.go b/tools/keep-rsync/keep-rsync_test.go
index 9c37e3890..22979dc98 100644
--- a/tools/keep-rsync/keep-rsync_test.go
+++ b/tools/keep-rsync/keep-rsync_test.go
@@ -89,13 +89,13 @@ func setupRsync(c *C, enforcePermissions bool, replications int) {
// srcConfig
var srcConfig apiConfig
srcConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- srcConfig.APIToken = arvadostest.DataManagerToken
+ srcConfig.APIToken = arvadostest.SystemRootToken
srcConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
// dstConfig
var dstConfig apiConfig
dstConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- dstConfig.APIToken = arvadostest.DataManagerToken
+ dstConfig.APIToken = arvadostest.SystemRootToken
dstConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
if enforcePermissions {
@@ -370,7 +370,7 @@ func (s *ServerNotRequiredSuite) TestLoadConfig(c *C) {
c.Check(err, IsNil)
c.Assert(srcConfig.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
- c.Assert(srcConfig.APIToken, Equals, arvadostest.DataManagerToken)
+ c.Assert(srcConfig.APIToken, Equals, arvadostest.SystemRootToken)
c.Assert(srcConfig.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
c.Assert(srcConfig.ExternalClient, Equals, false)
@@ -378,7 +378,7 @@ func (s *ServerNotRequiredSuite) TestLoadConfig(c *C) {
c.Check(err, IsNil)
c.Assert(dstConfig.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
- c.Assert(dstConfig.APIToken, Equals, arvadostest.DataManagerToken)
+ c.Assert(dstConfig.APIToken, Equals, arvadostest.SystemRootToken)
c.Assert(dstConfig.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
c.Assert(dstConfig.ExternalClient, Equals, false)
@@ -401,7 +401,7 @@ func (s *ServerNotRequiredSuite) TestLoadConfig_ErrorLoadingSrcConfig(c *C) {
func (s *ServerNotRequiredSuite) TestSetupKeepClient_NoBlobSignatureTTL(c *C) {
var srcConfig apiConfig
srcConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- srcConfig.APIToken = arvadostest.DataManagerToken
+ srcConfig.APIToken = arvadostest.SystemRootToken
srcConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
_, ttl, err := setupKeepClient(srcConfig, srcKeepServicesJSON, false, 0, 0)
@@ -415,7 +415,7 @@ func setupConfigFile(c *C, name string) *os.File {
c.Check(err, IsNil)
fileContent := "ARVADOS_API_HOST=" + os.Getenv("ARVADOS_API_HOST") + "\n"
- fileContent += "ARVADOS_API_TOKEN=" + arvadostest.DataManagerToken + "\n"
+ fileContent += "ARVADOS_API_TOKEN=" + arvadostest.SystemRootToken + "\n"
fileContent += "ARVADOS_API_HOST_INSECURE=" + os.Getenv("ARVADOS_API_HOST_INSECURE") + "\n"
fileContent += "ARVADOS_EXTERNAL_CLIENT=false\n"
fileContent += "ARVADOS_BLOB_SIGNING_KEY=abcdefg"
commit ef9107221d53f19bf848d3dca0b570f468519550
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date: Wed Nov 13 14:47:38 2019 -0500
15720: Add /users/* endpoints to internal API.
diff --git a/lib/controller/federation/conn.go b/lib/controller/federation/conn.go
index 3829d0a40..9d01f1f7c 100644
--- a/lib/controller/federation/conn.go
+++ b/lib/controller/federation/conn.go
@@ -251,6 +251,10 @@ func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions)
}
}
+func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
+ return conn.generated_CollectionList(ctx, options)
+}
+
func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
return conn.chooseBackend(options.UUID).CollectionProvenance(ctx, options)
}
@@ -271,6 +275,10 @@ func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.Untrash
return conn.chooseBackend(options.UUID).CollectionUntrash(ctx, options)
}
+func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
+ return conn.generated_ContainerList(ctx, options)
+}
+
func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
return conn.chooseBackend(options.ClusterID).ContainerCreate(ctx, options)
}
@@ -295,6 +303,10 @@ func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOption
return conn.chooseBackend(options.UUID).ContainerUnlock(ctx, options)
}
+func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
+ return conn.generated_SpecimenList(ctx, options)
+}
+
func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
return conn.chooseBackend(options.ClusterID).SpecimenCreate(ctx, options)
}
@@ -311,6 +323,54 @@ func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOpti
return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options)
}
+func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ return conn.generated_UserList(ctx, options)
+}
+
+func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.ClusterID).UserCreate(ctx, options)
+}
+
+func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUpdate(ctx, options)
+}
+
+func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUpdateUUID(ctx, options)
+}
+
+func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.OldUserUUID).UserMerge(ctx, options)
+}
+
+func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserActivate(ctx, options)
+}
+
+func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ return conn.chooseBackend(options.UUID).UserSetup(ctx, options)
+}
+
+func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUnsetup(ctx, options)
+}
+
+func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGet(ctx, options)
+}
+
+func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGetCurrent(ctx, options)
+}
+
+func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGetSystem(ctx, options)
+}
+
+func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserDelete(ctx, options)
+}
+
func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
return conn.chooseBackend(options.UUID).APIClientAuthorizationCurrent(ctx, options)
}
diff --git a/lib/controller/federation/generate.go b/lib/controller/federation/generate.go
index 11f021e51..ab5d9966a 100644
--- a/lib/controller/federation/generate.go
+++ b/lib/controller/federation/generate.go
@@ -31,7 +31,7 @@ func main() {
if err != nil {
panic(err)
}
- orig := regexp.MustCompile(`(?ms)\nfunc [^\n]*CollectionList\(.*?\n}\n`).Find(buf)
+ orig := regexp.MustCompile(`(?ms)\nfunc [^\n]*generated_CollectionList\(.*?\n}\n`).Find(buf)
if len(orig) == 0 {
panic("can't find CollectionList func")
}
@@ -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", "Specimen"} {
+ for _, t := range []string{"Container", "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 b34b9b165..961cd5a40 100755
--- a/lib/controller/federation/generated.go
+++ b/lib/controller/federation/generated.go
@@ -16,7 +16,7 @@ import (
// -- this file is auto-generated -- do not edit -- edit list.go and run "go generate" instead --
//
-func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
+func (conn *Conn) generated_ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
var mtx sync.Mutex
var merged arvados.ContainerList
err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
@@ -41,7 +41,7 @@ func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions
return merged, err
}
-func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
+func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
var mtx sync.Mutex
var merged arvados.SpecimenList
err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
@@ -65,3 +65,28 @@ func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions)
sort.Slice(merged.Items, func(i, j int) bool { return merged.Items[i].UUID < merged.Items[j].UUID })
return merged, err
}
+
+func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ var mtx sync.Mutex
+ var merged arvados.UserList
+ err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
+ cl, err := backend.UserList(ctx, options)
+ if err != nil {
+ return nil, err
+ }
+ mtx.Lock()
+ defer mtx.Unlock()
+ if len(merged.Items) == 0 {
+ merged = cl
+ } else {
+ merged.Items = append(merged.Items, cl.Items...)
+ }
+ uuids := make([]string, 0, len(cl.Items))
+ for _, item := range cl.Items {
+ uuids = append(uuids, item.UUID)
+ }
+ return uuids, nil
+ })
+ sort.Slice(merged.Items, func(i, j int) bool { return merged.Items[i].UUID < merged.Items[j].UUID })
+ return merged, err
+}
diff --git a/lib/controller/federation/list.go b/lib/controller/federation/list.go
index 6ba184c47..7178d7b0a 100644
--- a/lib/controller/federation/list.go
+++ b/lib/controller/federation/list.go
@@ -20,7 +20,7 @@ import (
// CollectionList is used as a template to auto-generate List()
// methods for other types; see generate.go.
-func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
+func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
var mtx sync.Mutex
var merged arvados.CollectionList
err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
diff --git a/lib/controller/handler.go b/lib/controller/handler.go
index f925233ba..a0c245009 100644
--- a/lib/controller/handler.go
+++ b/lib/controller/handler.go
@@ -83,6 +83,8 @@ func (h *Handler) setup() {
if h.Cluster.EnableBetaController14287 {
mux.Handle("/arvados/v1/collections", rtr)
mux.Handle("/arvados/v1/collections/", rtr)
+ mux.Handle("/arvados/v1/users", rtr)
+ mux.Handle("/arvados/v1/users/", rtr)
mux.Handle("/login", rtr)
}
diff --git a/lib/controller/router/router.go b/lib/controller/router/router.go
index 709ddfb82..250f3cb45 100644
--- a/lib/controller/router/router.go
+++ b/lib/controller/router/router.go
@@ -205,6 +205,90 @@ func (rtr *router) addRoutes() {
return rtr.fed.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
},
},
+ {
+ arvados.EndpointUserCreate,
+ func() interface{} { return &arvados.CreateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserCreate(ctx, *opts.(*arvados.CreateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserMerge,
+ func() interface{} { return &arvados.UserMergeOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserMerge(ctx, *opts.(*arvados.UserMergeOptions))
+ },
+ },
+ {
+ arvados.EndpointUserActivate,
+ func() interface{} { return &arvados.UserActivateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserActivate(ctx, *opts.(*arvados.UserActivateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserSetup,
+ func() interface{} { return &arvados.UserSetupOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserSetup(ctx, *opts.(*arvados.UserSetupOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUnsetup,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUnsetup(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGetCurrent,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGetCurrent(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGetSystem,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGetSystem(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGet,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGet(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUpdateUUID,
+ func() interface{} { return &arvados.UpdateUUIDOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUpdateUUID(ctx, *opts.(*arvados.UpdateUUIDOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUpdate,
+ func() interface{} { return &arvados.UpdateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUpdate(ctx, *opts.(*arvados.UpdateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserList,
+ func() interface{} { return &arvados.ListOptions{Limit: -1} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserList(ctx, *opts.(*arvados.ListOptions))
+ },
+ },
+ {
+ arvados.EndpointUserDelete,
+ func() interface{} { return &arvados.DeleteOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserDelete(ctx, *opts.(*arvados.DeleteOptions))
+ },
+ },
} {
rtr.addRoute(route.endpoint, route.defaultOpts, route.exec)
if route.endpoint.Method == "PATCH" {
@@ -250,6 +334,11 @@ func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() int
}
creds := auth.CredentialsFromRequest(req)
+ err = creds.LoadTokensFromHTTPRequestBody(req)
+ if err != nil {
+ rtr.sendError(w, fmt.Errorf("error loading tokens from request body: %s", err))
+ return
+ }
if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
for _, t := range rt {
if t, ok := t.(string); ok {
diff --git a/lib/controller/rpc/conn.go b/lib/controller/rpc/conn.go
index afe749fb0..25efcfd43 100644
--- a/lib/controller/rpc/conn.go
+++ b/lib/controller/rpc/conn.go
@@ -308,6 +308,79 @@ func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOpti
return resp, err
}
+func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserCreate
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdate
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp map[string]interface{}
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGet
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGetCurrent
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGetSystem
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ ep := arvados.EndpointUserList
+ var resp arvados.UserList
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserDelete
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+
func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
ep := arvados.EndpointAPIClientAuthorizationCurrent
var resp arvados.APIClientAuthorization
diff --git a/sdk/go/arvados/api.go b/sdk/go/arvados/api.go
index 5de94d73e..d86df9ef3 100644
--- a/sdk/go/arvados/api.go
+++ b/sdk/go/arvados/api.go
@@ -40,6 +40,20 @@ var (
EndpointContainerDelete = APIEndpoint{"DELETE", "arvados/v1/containers/{uuid}", ""}
EndpointContainerLock = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/lock", ""}
EndpointContainerUnlock = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/unlock", ""}
+ EndpointUserActivate = APIEndpoint{"POST", "arvados/v1/users/{uuid}/activate", ""}
+ EndpointUserCreate = APIEndpoint{"POST", "arvados/v1/users", "user"}
+ EndpointUserCurrent = APIEndpoint{"GET", "arvados/v1/users/current", ""}
+ EndpointUserDelete = APIEndpoint{"DELETE", "arvados/v1/users/{uuid}", ""}
+ EndpointUserGet = APIEndpoint{"GET", "arvados/v1/users/{uuid}", ""}
+ EndpointUserGetCurrent = APIEndpoint{"GET", "arvados/v1/users/current", ""}
+ EndpointUserGetSystem = APIEndpoint{"GET", "arvados/v1/users/system", ""}
+ EndpointUserList = APIEndpoint{"GET", "arvados/v1/users", ""}
+ EndpointUserMerge = APIEndpoint{"POST", "arvados/v1/users/merge", ""}
+ EndpointUserSetup = APIEndpoint{"POST", "arvados/v1/users/setup", ""}
+ EndpointUserSystem = APIEndpoint{"GET", "arvados/v1/users/system", ""}
+ EndpointUserUnsetup = APIEndpoint{"POST", "arvados/v1/users/{uuid}/unsetup", ""}
+ EndpointUserUpdate = APIEndpoint{"PATCH", "arvados/v1/users/{uuid}", "user"}
+ EndpointUserUpdateUUID = APIEndpoint{"POST", "arvados/v1/users/{uuid}/update_uuid", ""}
EndpointAPIClientAuthorizationCurrent = APIEndpoint{"GET", "arvados/v1/api_client_authorizations/current", ""}
)
@@ -80,6 +94,31 @@ type UpdateOptions struct {
Attrs map[string]interface{} `json:"attrs"`
}
+type UpdateUUIDOptions struct {
+ UUID string `json:"uuid"`
+ NewUUID string `json:"new_uuid"`
+}
+
+type UserActivateOptions struct {
+ UUID string `json:"uuid"`
+}
+
+type UserSetupOptions struct {
+ UUID string `json:"uuid"`
+ Email string `json:"email"`
+ OpenIDPrefix string `json:"openid_prefix"`
+ RepoName string `json:"repo_name"`
+ VMUUID string `json:"vm_uuid"`
+ SendNotificationEmail bool `json:"send_notification_email"`
+ Attrs map[string]interface{} `json:"attrs"`
+}
+
+type UserMergeOptions struct {
+ NewUserUUID string `json:"new_user_uuid,omitempty"`
+ OldUserUUID string `json:"old_user_uuid,omitempty"`
+ NewUserToken string `json:"new_user_token,omitempty"`
+}
+
type DeleteOptions struct {
UUID string `json:"uuid"`
}
@@ -115,5 +154,17 @@ type API interface {
SpecimenGet(ctx context.Context, options GetOptions) (Specimen, error)
SpecimenList(ctx context.Context, options ListOptions) (SpecimenList, error)
SpecimenDelete(ctx context.Context, options DeleteOptions) (Specimen, error)
+ UserCreate(ctx context.Context, options CreateOptions) (User, error)
+ UserUpdate(ctx context.Context, options UpdateOptions) (User, error)
+ UserUpdateUUID(ctx context.Context, options UpdateUUIDOptions) (User, error)
+ UserMerge(ctx context.Context, options UserMergeOptions) (User, error)
+ UserActivate(ctx context.Context, options UserActivateOptions) (User, error)
+ UserSetup(ctx context.Context, options UserSetupOptions) (map[string]interface{}, error)
+ UserUnsetup(ctx context.Context, options GetOptions) (User, error)
+ UserGet(ctx context.Context, options GetOptions) (User, error)
+ UserGetCurrent(ctx context.Context, options GetOptions) (User, error)
+ UserGetSystem(ctx context.Context, options GetOptions) (User, error)
+ UserList(ctx context.Context, options ListOptions) (UserList, error)
+ UserDelete(ctx context.Context, options DeleteOptions) (User, error)
APIClientAuthorizationCurrent(ctx context.Context, options GetOptions) (APIClientAuthorization, error)
}
diff --git a/sdk/go/arvadostest/api.go b/sdk/go/arvadostest/api.go
index 24e9f1908..96b7c75f1 100644
--- a/sdk/go/arvadostest/api.go
+++ b/sdk/go/arvadostest/api.go
@@ -121,6 +121,54 @@ func (as *APIStub) SpecimenDelete(ctx context.Context, options arvados.DeleteOpt
as.appendCall(as.SpecimenDelete, ctx, options)
return arvados.Specimen{}, as.Error
}
+func (as *APIStub) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ as.appendCall(as.UserCreate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ as.appendCall(as.UserUpdate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ as.appendCall(as.UserUpdateUUID, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ as.appendCall(as.UserActivate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ as.appendCall(as.UserSetup, ctx, options)
+ return nil, as.Error
+}
+func (as *APIStub) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserUnsetup, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGet, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGetCurrent, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGetSystem, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ as.appendCall(as.UserList, ctx, options)
+ return arvados.UserList{}, as.Error
+}
+func (as *APIStub) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ as.appendCall(as.UserDelete, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ as.appendCall(as.UserMerge, ctx, options)
+ return arvados.User{}, as.Error
+}
func (as *APIStub) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
as.appendCall(as.APIClientAuthorizationCurrent, ctx, options)
return arvados.APIClientAuthorization{}, as.Error
commit d12160e0b194eed8b059a54d92075fa1c6dfcd10
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date: Thu Nov 14 14:29:14 2019 -0500
15720: Switch httprouter to gorilla/mux.
In httprouter, "/users/:uuid" and "/users/:uuid/activate" are
conflicting routes.
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>
diff --git a/lib/controller/router/request.go b/lib/controller/router/request.go
index 377f7243c..4d18395b6 100644
--- a/lib/controller/router/request.go
+++ b/lib/controller/router/request.go
@@ -13,7 +13,7 @@ import (
"strconv"
"strings"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
)
// Parse req as an Arvados V1 API request and return the request
@@ -109,9 +109,8 @@ func (rtr *router) loadRequestParams(req *http.Request, attrsKey string) (map[st
}
}
- routeParams, _ := req.Context().Value(httprouter.ParamsKey).(httprouter.Params)
- for _, p := range routeParams {
- params[p.Key] = p.Value
+ for k, v := range mux.Vars(req) {
+ params[k] = v
}
if v, ok := params[attrsKey]; ok && attrsKey != "" {
diff --git a/lib/controller/router/router.go b/lib/controller/router/router.go
index d3bdce527..709ddfb82 100644
--- a/lib/controller/router/router.go
+++ b/lib/controller/router/router.go
@@ -14,18 +14,18 @@ import (
"git.curoverse.com/arvados.git/sdk/go/auth"
"git.curoverse.com/arvados.git/sdk/go/ctxlog"
"git.curoverse.com/arvados.git/sdk/go/httpserver"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type router struct {
- mux *httprouter.Router
+ mux *mux.Router
fed arvados.API
}
func New(fed arvados.API) *router {
rtr := &router{
- mux: httprouter.New(),
+ mux: mux.NewRouter(),
fed: fed,
}
rtr.addRoutes()
@@ -214,16 +214,16 @@ func (rtr *router) addRoutes() {
rtr.addRoute(endpointPUT, route.defaultOpts, route.exec)
}
}
- rtr.mux.NotFound = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusNotFound)
})
- rtr.mux.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusMethodNotAllowed)
})
}
func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() interface{}, exec routableFunc) {
- rtr.mux.HandlerFunc(endpoint.Method, "/"+endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.Methods(endpoint.Method).Path("/" + endpoint.Path).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
logger := ctxlog.FromContext(req.Context())
params, err := rtr.loadRequestParams(req, endpoint.AttrsKey)
if err != nil {
diff --git a/lib/controller/router/router_test.go b/lib/controller/router/router_test.go
index 3a7045aa4..6a9fd311b 100644
--- a/lib/controller/router/router_test.go
+++ b/lib/controller/router/router_test.go
@@ -19,7 +19,7 @@ import (
"git.curoverse.com/arvados.git/lib/controller/rpc"
"git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/arvadostest"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
check "gopkg.in/check.v1"
)
@@ -38,7 +38,7 @@ type RouterSuite struct {
func (s *RouterSuite) SetUpTest(c *check.C) {
s.stub = arvadostest.APIStub{}
s.rtr = &router{
- mux: httprouter.New(),
+ mux: mux.NewRouter(),
fed: &s.stub,
}
s.rtr.addRoutes()
diff --git a/lib/controller/rpc/conn.go b/lib/controller/rpc/conn.go
index 7d7cb486f..afe749fb0 100644
--- a/lib/controller/rpc/conn.go
+++ b/lib/controller/rpc/conn.go
@@ -118,9 +118,9 @@ func (conn *Conn) requestAndDecode(ctx context.Context, dst interface{}, ep arva
params["reader_tokens"] = tokens[1:]
}
path := ep.Path
- if strings.Contains(ep.Path, "/:uuid") {
+ if strings.Contains(ep.Path, "/{uuid}") {
uuid, _ := params["uuid"].(string)
- path = strings.Replace(path, "/:uuid", "/"+uuid, 1)
+ path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
delete(params, "uuid")
}
return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
diff --git a/sdk/go/arvados/api.go b/sdk/go/arvados/api.go
index 5531cf71d..5de94d73e 100644
--- a/sdk/go/arvados/api.go
+++ b/sdk/go/arvados/api.go
@@ -20,26 +20,26 @@ var (
EndpointConfigGet = APIEndpoint{"GET", "arvados/v1/config", ""}
EndpointLogin = APIEndpoint{"GET", "login", ""}
EndpointCollectionCreate = APIEndpoint{"POST", "arvados/v1/collections", "collection"}
- EndpointCollectionUpdate = APIEndpoint{"PATCH", "arvados/v1/collections/:uuid", "collection"}
- EndpointCollectionGet = APIEndpoint{"GET", "arvados/v1/collections/:uuid", ""}
+ EndpointCollectionUpdate = APIEndpoint{"PATCH", "arvados/v1/collections/{uuid}", "collection"}
+ EndpointCollectionGet = APIEndpoint{"GET", "arvados/v1/collections/{uuid}", ""}
EndpointCollectionList = APIEndpoint{"GET", "arvados/v1/collections", ""}
- EndpointCollectionProvenance = APIEndpoint{"GET", "arvados/v1/collections/:uuid/provenance", ""}
- EndpointCollectionUsedBy = APIEndpoint{"GET", "arvados/v1/collections/:uuid/used_by", ""}
- EndpointCollectionDelete = APIEndpoint{"DELETE", "arvados/v1/collections/:uuid", ""}
- EndpointCollectionTrash = APIEndpoint{"POST", "arvados/v1/collections/:uuid/trash", ""}
- EndpointCollectionUntrash = APIEndpoint{"POST", "arvados/v1/collections/:uuid/untrash", ""}
+ EndpointCollectionProvenance = APIEndpoint{"GET", "arvados/v1/collections/{uuid}/provenance", ""}
+ EndpointCollectionUsedBy = APIEndpoint{"GET", "arvados/v1/collections/{uuid}/used_by", ""}
+ EndpointCollectionDelete = APIEndpoint{"DELETE", "arvados/v1/collections/{uuid}", ""}
+ EndpointCollectionTrash = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/trash", ""}
+ EndpointCollectionUntrash = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/untrash", ""}
EndpointSpecimenCreate = APIEndpoint{"POST", "arvados/v1/specimens", "specimen"}
- EndpointSpecimenUpdate = APIEndpoint{"PATCH", "arvados/v1/specimens/:uuid", "specimen"}
- EndpointSpecimenGet = APIEndpoint{"GET", "arvados/v1/specimens/:uuid", ""}
+ EndpointSpecimenUpdate = APIEndpoint{"PATCH", "arvados/v1/specimens/{uuid}", "specimen"}
+ EndpointSpecimenGet = APIEndpoint{"GET", "arvados/v1/specimens/{uuid}", ""}
EndpointSpecimenList = APIEndpoint{"GET", "arvados/v1/specimens", ""}
- EndpointSpecimenDelete = APIEndpoint{"DELETE", "arvados/v1/specimens/:uuid", ""}
+ EndpointSpecimenDelete = APIEndpoint{"DELETE", "arvados/v1/specimens/{uuid}", ""}
EndpointContainerCreate = APIEndpoint{"POST", "arvados/v1/containers", "container"}
- EndpointContainerUpdate = APIEndpoint{"PATCH", "arvados/v1/containers/:uuid", "container"}
- EndpointContainerGet = APIEndpoint{"GET", "arvados/v1/containers/:uuid", ""}
+ EndpointContainerUpdate = APIEndpoint{"PATCH", "arvados/v1/containers/{uuid}", "container"}
+ EndpointContainerGet = APIEndpoint{"GET", "arvados/v1/containers/{uuid}", ""}
EndpointContainerList = APIEndpoint{"GET", "arvados/v1/containers", ""}
- EndpointContainerDelete = APIEndpoint{"DELETE", "arvados/v1/containers/:uuid", ""}
- EndpointContainerLock = APIEndpoint{"POST", "arvados/v1/containers/:uuid/lock", ""}
- EndpointContainerUnlock = APIEndpoint{"POST", "arvados/v1/containers/:uuid/unlock", ""}
+ EndpointContainerDelete = APIEndpoint{"DELETE", "arvados/v1/containers/{uuid}", ""}
+ EndpointContainerLock = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/lock", ""}
+ EndpointContainerUnlock = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/unlock", ""}
EndpointAPIClientAuthorizationCurrent = APIEndpoint{"GET", "arvados/v1/api_client_authorizations/current", ""}
)
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list