17170: Add "arvados-client shell" subcommand and backend support.
[arvados.git] / lib / controller / federation / conn.go
index b2d158b9a5fa447da7d9daabde7f7058d757ed44..a32382ce254afa9a8018792fa9fbee932f73476b 100644 (file)
@@ -79,6 +79,14 @@ func saltedTokenProvider(local backend, remoteID string) rpc.TokenProvider {
                                } else if err != nil {
                                        return nil, err
                                }
+                               if strings.HasPrefix(aca.UUID, remoteID) {
+                                       // We have it cached here, but
+                                       // the token belongs to the
+                                       // remote target itself, so
+                                       // pass it through unmodified.
+                                       tokens = append(tokens, token)
+                                       continue
+                               }
                                salted, err := auth.SaltToken(aca.TokenV2(), remoteID)
                                if err != nil {
                                        return nil, err
@@ -114,9 +122,8 @@ func (conn *Conn) chooseBackend(id string) backend {
 func (conn *Conn) localOrLoginCluster() backend {
        if conn.cluster.Login.LoginCluster != "" {
                return conn.chooseBackend(conn.cluster.Login.LoginCluster)
-       } else {
-               return conn.local
        }
+       return conn.local
 }
 
 // Call fn with the local backend; then, if fn returned 404, call fn
@@ -204,9 +211,8 @@ func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arva
                return arvados.LoginResponse{
                        RedirectLocation: target.String(),
                }, nil
-       } else {
-               return conn.local.Login(ctx, options)
        }
+       return conn.local.Login(ctx, options)
 }
 
 func (conn *Conn) Logout(ctx context.Context, options arvados.LogoutOptions) (arvados.LogoutResponse, error) {
@@ -243,40 +249,39 @@ func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions)
                        c.ManifestText = rewriteManifest(c.ManifestText, options.UUID[:5])
                }
                return c, err
-       } else {
-               // UUID is a PDH
-               first := make(chan arvados.Collection, 1)
-               err := conn.tryLocalThenRemotes(ctx, options.ForwardedFor, func(ctx context.Context, remoteID string, be backend) error {
-                       remoteOpts := options
-                       remoteOpts.ForwardedFor = conn.cluster.ClusterID + "-" + options.ForwardedFor
-                       c, err := be.CollectionGet(ctx, remoteOpts)
-                       if err != nil {
-                               return err
-                       }
-                       // options.UUID is either hash+size or
-                       // hash+size+hints; only hash+size need to
-                       // match the computed PDH.
-                       if pdh := arvados.PortableDataHash(c.ManifestText); pdh != options.UUID && !strings.HasPrefix(options.UUID, pdh+"+") {
-                               err = httpErrorf(http.StatusBadGateway, "bad portable data hash %q received from remote %q (expected %q)", pdh, remoteID, options.UUID)
-                               ctxlog.FromContext(ctx).Warn(err)
-                               return err
-                       }
-                       if remoteID != "" {
-                               c.ManifestText = rewriteManifest(c.ManifestText, remoteID)
-                       }
-                       select {
-                       case first <- c:
-                               return nil
-                       default:
-                               // lost race, return value doesn't matter
-                               return nil
-                       }
-               })
+       }
+       // UUID is a PDH
+       first := make(chan arvados.Collection, 1)
+       err := conn.tryLocalThenRemotes(ctx, options.ForwardedFor, func(ctx context.Context, remoteID string, be backend) error {
+               remoteOpts := options
+               remoteOpts.ForwardedFor = conn.cluster.ClusterID + "-" + options.ForwardedFor
+               c, err := be.CollectionGet(ctx, remoteOpts)
                if err != nil {
-                       return arvados.Collection{}, err
+                       return err
+               }
+               // options.UUID is either hash+size or
+               // hash+size+hints; only hash+size need to
+               // match the computed PDH.
+               if pdh := arvados.PortableDataHash(c.ManifestText); pdh != options.UUID && !strings.HasPrefix(options.UUID, pdh+"+") {
+                       err = httpErrorf(http.StatusBadGateway, "bad portable data hash %q received from remote %q (expected %q)", pdh, remoteID, options.UUID)
+                       ctxlog.FromContext(ctx).Warn(err)
+                       return err
+               }
+               if remoteID != "" {
+                       c.ManifestText = rewriteManifest(c.ManifestText, remoteID)
+               }
+               select {
+               case first <- c:
+                       return nil
+               default:
+                       // lost race, return value doesn't matter
+                       return nil
                }
-               return <-first, nil
+       })
+       if err != nil {
+               return arvados.Collection{}, err
        }
+       return <-first, nil
 }
 
 func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
@@ -331,6 +336,10 @@ func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOption
        return conn.chooseBackend(options.UUID).ContainerUnlock(ctx, options)
 }
 
+func (conn *Conn) ContainerSSH(ctx context.Context, options arvados.ContainerSSHOptions) (arvados.ContainerSSHConnection, error) {
+       return conn.chooseBackend(options.UUID).ContainerSSH(ctx, options)
+}
+
 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
        return conn.generated_SpecimenList(ctx, options)
 }
@@ -445,9 +454,8 @@ func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (ar
                        return arvados.UserList{}, err
                }
                return resp, nil
-       } else {
-               return conn.generated_UserList(ctx, options)
        }
+       return conn.generated_UserList(ctx, options)
 }
 
 func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
@@ -458,7 +466,18 @@ func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions)
        if options.BypassFederation {
                return conn.local.UserUpdate(ctx, options)
        }
-       return conn.chooseBackend(options.UUID).UserUpdate(ctx, options)
+       resp, err := conn.chooseBackend(options.UUID).UserUpdate(ctx, options)
+       if err != nil {
+               return resp, err
+       }
+       if !strings.HasPrefix(options.UUID, conn.cluster.ClusterID) {
+               // Copy the updated user record to the local cluster
+               err = conn.batchUpdateUsers(ctx, arvados.ListOptions{}, []arvados.User{resp})
+               if err != nil {
+                       return arvados.User{}, err
+               }
+       }
+       return resp, err
 }
 
 func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
@@ -474,40 +493,29 @@ func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivate
 }
 
 func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
-       var setupVM string
-       var setupRepo string
-       if conn.cluster.Login.LoginCluster != "" {
-               if options.VMUUID != "" && options.VMUUID[0:5] != options.UUID[0:5] {
-                       // When LoginCluster is in effect, and we're
-                       // setting up a remote user, and we want to
-                       // give that user access to a local VM, then
-                       // we need to set up the user on the remote
-                       // LoginCluster first, followed by calling
-                       // setup on the local instance to give access
-                       // to the VM.
-                       setupVM = options.VMUUID
-                       options.VMUUID = ""
-               }
-               if options.RepoName != "" {
-                       // Similarly, if we want to create a git repo,
-                       // it should be created on the local cluster,
-                       // not the remote one.
-                       setupRepo = options.RepoName
-                       options.RepoName = ""
+       upstream := conn.localOrLoginCluster()
+       if upstream != conn.local {
+               // When LoginCluster is in effect, and we're setting
+               // up a remote user, and we want to give that user
+               // access to a local VM, we can't include the VM in
+               // the setup call, because the remote cluster won't
+               // recognize it.
+
+               // Similarly, if we want to create a git repo,
+               // it should be created on the local cluster,
+               // not the remote one.
+
+               upstreamOptions := options
+               upstreamOptions.VMUUID = ""
+               upstreamOptions.RepoName = ""
+
+               ret, err := upstream.UserSetup(ctx, upstreamOptions)
+               if err != nil {
+                       return ret, err
                }
        }
 
-       ret, err := conn.localOrLoginCluster().UserSetup(ctx, options)
-       if err != nil {
-               return ret, err
-       }
-
-       if setupVM != "" || setupRepo != "" {
-               options.VMUUID = setupVM
-               options.RepoName = setupRepo
-               ret, err = conn.local.UserSetup(ctx, options)
-       }
-       return ret, err
+       return conn.local.UserSetup(ctx, options)
 }
 
 func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
@@ -515,11 +523,24 @@ func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (
 }
 
 func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
-       return conn.chooseBackend(options.UUID).UserGet(ctx, options)
+       resp, err := conn.chooseBackend(options.UUID).UserGet(ctx, options)
+       if err != nil {
+               return resp, err
+       }
+       if options.UUID != resp.UUID {
+               return arvados.User{}, httpErrorf(http.StatusBadGateway, "Had requested %v but response was for %v", options.UUID, resp.UUID)
+       }
+       if options.UUID[:5] != conn.cluster.ClusterID {
+               err = conn.batchUpdateUsers(ctx, arvados.ListOptions{Select: options.Select}, []arvados.User{resp})
+               if err != nil {
+                       return arvados.User{}, err
+               }
+       }
+       return resp, nil
 }
 
 func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
-       return conn.chooseBackend(options.UUID).UserGetCurrent(ctx, options)
+       return conn.local.UserGetCurrent(ctx, options)
 }
 
 func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
@@ -555,7 +576,6 @@ func (notFoundError) Error() string   { return "not found" }
 func errStatus(err error) int {
        if httpErr, ok := err.(interface{ HTTPStatus() int }); ok {
                return httpErr.HTTPStatus()
-       } else {
-               return http.StatusInternalServerError
        }
+       return http.StatusInternalServerError
 }