14807: Load API host/token from cluster config if present.
authorTom Clegg <tclegg@veritasgenetics.com>
Thu, 21 Mar 2019 18:36:29 +0000 (14:36 -0400)
committerTom Clegg <tclegg@veritasgenetics.com>
Thu, 21 Mar 2019 20:55:41 +0000 (16:55 -0400)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg@veritasgenetics.com>

lib/dispatchcloud/cmd.go
lib/dispatchcloud/dispatcher.go
lib/dispatchcloud/dispatcher_test.go
lib/dispatchcloud/test/stub_driver.go
lib/service/cmd.go
lib/service/token.go [new file with mode: 0644]
sdk/go/arvados/client.go
sdk/go/arvados/config.go

index 7231e839475639c2aa5e6c720091c15b4d4b5ed7..82205c74262458d39ac4c25c9a6327815a34c332 100644 (file)
@@ -15,7 +15,11 @@ import (
 var Command cmd.Handler = service.Command(arvados.ServiceNameDispatchCloud, newHandler)
 
 func newHandler(ctx context.Context, cluster *arvados.Cluster, _ *arvados.NodeProfile) service.Handler {
-       d := &dispatcher{Cluster: cluster, Context: ctx}
+       d := &dispatcher{
+               Cluster:   cluster,
+               Context:   ctx,
+               AuthToken: service.Token(ctx),
+       }
        go d.Start()
        return d
 }
index 9245d5de30928e038da47172dd526cb6d5ca3b8a..147b0c015ce00dc1bf7ba62bd3575335d6921ee2 100644 (file)
@@ -46,6 +46,7 @@ type pool interface {
 type dispatcher struct {
        Cluster       *arvados.Cluster
        Context       context.Context
+       AuthToken     string
        InstanceSetID cloud.InstanceSetID
 
        logger      logrus.FieldLogger
@@ -108,7 +109,15 @@ func (disp *dispatcher) setup() {
 }
 
 func (disp *dispatcher) initialize() {
-       arvClient := arvados.NewClientFromEnv()
+       disp.logger = ctxlog.FromContext(disp.Context)
+
+       arvClient, err := arvados.NewClientFromConfig(disp.Cluster)
+       if err != nil {
+               disp.logger.WithError(err).Warn("error initializing client from cluster config, falling back to ARVADOS_API_HOST(_INSECURE) environment variables")
+               arvClient = arvados.NewClientFromEnv()
+       }
+       arvClient.AuthToken = disp.AuthToken
+
        if disp.InstanceSetID == "" {
                if strings.HasPrefix(arvClient.AuthToken, "v2/") {
                        disp.InstanceSetID = cloud.InstanceSetID(strings.Split(arvClient.AuthToken, "/")[1])
@@ -120,7 +129,6 @@ func (disp *dispatcher) initialize() {
        }
        disp.stop = make(chan struct{}, 1)
        disp.stopped = make(chan struct{})
-       disp.logger = ctxlog.FromContext(disp.Context)
 
        if key, err := ssh.ParsePrivateKey([]byte(disp.Cluster.Dispatch.PrivateKey)); err != nil {
                disp.logger.Fatalf("error parsing configured Dispatch.PrivateKey: %s", err)
index b0033353c2e822d1e6883f7d72d117ac8474e84f..d7e841e7329d021e81c1fe7bcb1f6085a46dbdd2 100644 (file)
@@ -17,6 +17,7 @@ import (
 
        "git.curoverse.com/arvados.git/lib/dispatchcloud/test"
        "git.curoverse.com/arvados.git/sdk/go/arvados"
+       "git.curoverse.com/arvados.git/sdk/go/arvadostest"
        "git.curoverse.com/arvados.git/sdk/go/ctxlog"
        "golang.org/x/crypto/ssh"
        check "gopkg.in/check.v1"
@@ -81,10 +82,16 @@ func (s *DispatcherSuite) SetUpTest(c *check.C) {
                                DispatchCloud: arvados.SystemServiceInstance{Listen: ":"},
                        },
                },
+               Services: arvados.Services{
+                       DispatchCloud: arvados.Service{InternalURLs: map[arvados.URL]arvados.ServiceInstance{
+                               arvados.URL{Scheme: "https", Host: os.Getenv("ARVADOS_API_HOST")}: {},
+                       }},
+               },
        }
        s.disp = &dispatcher{
-               Cluster: s.cluster,
-               Context: s.ctx,
+               Cluster:   s.cluster,
+               Context:   s.ctx,
+               AuthToken: arvadostest.AdminToken,
        }
        // Test cases can modify s.cluster before calling
        // initialize(), and then modify private state before calling
index 02346a97076d7168869266c8078028a667b39f81..873d987327eafed2a53f6d63f0dcc17230dbeb0d 100644 (file)
@@ -245,7 +245,7 @@ func (svm *StubVM) Exec(env map[string]string, command string, stdin io.Reader,
                }
                for _, name := range []string{"ARVADOS_API_HOST", "ARVADOS_API_TOKEN"} {
                        if stdinKV[name] == "" {
-                               fmt.Fprintf(stderr, "%s env var missing from stdin %q\n", name, stdin)
+                               fmt.Fprintf(stderr, "%s env var missing from stdin %q\n", name, stdinData)
                                return 1
                        }
                }
index d99af0eea15428054fd5adc16596ca89b1de7820..e56f52eec5d2460dfb9de761ec65d0ef09b63bd2 100644 (file)
@@ -78,6 +78,17 @@ func (c *command) RunCommand(prog string, args []string, stdin io.Reader, stdout
                "PID": os.Getpid(),
        })
        ctx := ctxlog.Context(context.Background(), log)
+
+       // Currently all components use SystemRootToken if configured,
+       // otherwise ARVADOS_API_TOKEN. In future, per-process tokens
+       // will be generated/obtained here.
+       token := cluster.SystemRootToken
+       if token == "" {
+               log.Warn("SystemRootToken missing from cluster config, falling back to ARVADOS_API_TOKEN environment variable")
+               token = os.Getenv("ARVADOS_API_TOKEN")
+       }
+       ctx = tokenContext(ctx, token)
+
        profileName := *nodeProfile
        if profileName == "" {
                profileName = os.Getenv("ARVADOS_NODE_PROFILE")
diff --git a/lib/service/token.go b/lib/service/token.go
new file mode 100644 (file)
index 0000000..5070ae5
--- /dev/null
@@ -0,0 +1,30 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package service
+
+import (
+       "context"
+)
+
+type contextKey string
+
+var contextKeyServiceToken contextKey = "serviceToken"
+
+// Token returns the privileged system token suitable for the given
+// service context.
+//
+// It only works on contexts that were generated by Command() and
+// passed to a Handler. For other contexts it returns the empty
+// string.
+func Token(ctx context.Context) string {
+       t, _ := ctx.Value(contextKeyServiceToken).(string)
+       return t
+}
+
+// tokenContext returns a child context with the given token attached
+// so it can be retrieved by Token().
+func tokenContext(ctx context.Context, t string) context.Context {
+       return context.WithValue(ctx, contextKeyServiceToken, t)
+}
index 787e01ab8f7dc8be892e7c754bca4a29cba84b13..2b82df9b8a941d9d6305d5d6c80d91e33f55d5e7 100644 (file)
@@ -69,6 +69,21 @@ var InsecureHTTPClient = &http.Client{
 var DefaultSecureClient = &http.Client{
        Timeout: 5 * time.Minute}
 
+// NewClientFromConfig creates a new Client that uses the endpoints in
+// the given cluster.
+//
+// AuthToken is left empty for the caller to populate.
+func NewClientFromConfig(cluster *Cluster) (*Client, error) {
+       ctrlURL := cluster.Services.Controller.ExternalURL
+       if ctrlURL.Host == "" {
+               return nil, fmt.Errorf("no host in config Services.Controller.ExternalURL: %s", ctrlURL)
+       }
+       return &Client{
+               APIHost:  fmt.Sprintf("%s", ctrlURL),
+               Insecure: cluster.TLS.Insecure,
+       }, nil
+}
+
 // NewClientFromEnv creates a new Client that uses the default HTTP
 // client with the API endpoint and credentials given by the
 // ARVADOS_API_* environment variables.
index 7c87ff0293052762641019f29d7ff442aa09e75d..2965d5ecb0dc8aa89da2354eea231464d9fa202f 100644 (file)
@@ -8,6 +8,7 @@ import (
        "encoding/json"
        "errors"
        "fmt"
+       "net/url"
        "os"
 
        "git.curoverse.com/arvados.git/sdk/go/config"
@@ -58,6 +59,8 @@ type RequestLimits struct {
 type Cluster struct {
        ClusterID          string `json:"-"`
        ManagementToken    string
+       SystemRootToken    string
+       Services           Services
        NodeProfiles       map[string]NodeProfile
        InstanceTypes      InstanceTypeMap
        CloudVMs           CloudVMs
@@ -67,8 +70,43 @@ type Cluster struct {
        PostgreSQL         PostgreSQL
        RequestLimits      RequestLimits
        Logging            Logging
+       TLS                TLS
 }
 
+type Services struct {
+       Controller    Service
+       DispatchCloud Service
+       Health        Service
+       Keepbalance   Service
+       Keepproxy     Service
+       Keepstore     Service
+       Keepweb       Service
+       Nodemanager   Service
+       RailsAPI      Service
+       Websocket     Service
+       Workbench     Service
+}
+
+type Service struct {
+       InternalURLs map[URL]ServiceInstance
+       ExternalURL  URL
+}
+
+// URL is a url.URL that is also usable as a JSON key/value.
+type URL url.URL
+
+// UnmarshalText implements encoding.TextUnmarshaler so URL can be
+// used as a JSON key/value.
+func (su *URL) UnmarshalText(text []byte) error {
+       u, err := url.Parse(string(text))
+       if err == nil {
+               *su = URL(*u)
+       }
+       return err
+}
+
+type ServiceInstance struct{}
+
 type Logging struct {
        Level  string
        Format string
@@ -309,3 +347,9 @@ type SystemServiceInstance struct {
        TLS      bool
        Insecure bool
 }
+
+type TLS struct {
+       Certificate string
+       Key         string
+       Insecure    bool
+}