[ARVADOS] created: 2.1.0-2169-ge7640f63a
Git user
git at public.arvados.org
Wed Mar 30 16:58:15 UTC 2022
at e7640f63af91ce7ec808a45b95ef336aea52abc0 (commit)
commit e7640f63af91ce7ec808a45b95ef336aea52abc0
Author: Tom Clegg <tom at curii.com>
Date: Wed Mar 30 12:55:38 2022 -0400
18940: Go clients load $HOME/.config/arvados/settings.conf
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at curii.com>
diff --git a/sdk/go/arvados/client.go b/sdk/go/arvados/client.go
index 5ec828667..efd9a197a 100644
--- a/sdk/go/arvados/client.go
+++ b/sdk/go/arvados/client.go
@@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"io"
+ "io/fs"
"io/ioutil"
"log"
"net/http"
@@ -102,11 +103,59 @@ func NewClientFromConfig(cluster *Cluster) (*Client, error) {
}
// NewClientFromEnv creates a new Client that uses the default HTTP
-// client with the API endpoint and credentials given by the
-// ARVADOS_API_* environment variables.
+// client, and loads API endpoint and credentials from ARVADOS_*
+// environment variables (if set) and
+// $HOME/.config/arvados/settings.conf (if readable).
+//
+// If a config exists in both locations, the environment variable is
+// used.
+//
+// If there is an error (other than ENOENT) reading settings.conf,
+// NewClientFromEnv logs the error to log.Default(), then proceeds as
+// if settings.conf did not exist.
+//
+// Space characters are trimmed when reading the settings file, so
+// these are equivalent:
+//
+// ARVADOS_API_HOST=localhost\n
+// ARVADOS_API_HOST=localhost\r\n
+// ARVADOS_API_HOST = localhost \n
+// \tARVADOS_API_HOST = localhost\n
func NewClientFromEnv() *Client {
+ vars := map[string]string{}
+ home := os.Getenv("HOME")
+ conffile := home + "/.config/arvados/settings.conf"
+ if home == "" {
+ // no $HOME => just use env vars
+ } else if settings, err := os.ReadFile(conffile); errors.Is(err, fs.ErrNotExist) {
+ // no config file => just use env vars
+ } else if err != nil {
+ // config file unreadable => log message, then use env vars
+ log.Printf("continuing without loading %s: %s", conffile, err)
+ } else {
+ for _, line := range bytes.Split(settings, []byte{'\n'}) {
+ kv := bytes.SplitN(line, []byte{'='}, 2)
+ if len(kv) != 2 || !strings.HasPrefix(k, "ARVADOS_") {
+ // Same behavior as python sdk:
+ // silently skip leading # (comments),
+ // blank lines, typos, and non-Arvados
+ // vars.
+ continue
+ }
+ vars[string(bytes.TrimSpace(kv[0]))] = string(bytes.TrimSpace(kv[1]))
+ }
+ }
+ for _, env := range os.Environ() {
+ if !strings.HasPrefix(env, "ARVADOS_") {
+ continue
+ }
+ kv := strings.SplitN(env, "=", 2)
+ if len(kv) == 2 {
+ vars[kv[0]] = kv[1]
+ }
+ }
var svcs []string
- for _, s := range strings.Split(os.Getenv("ARVADOS_KEEP_SERVICES"), " ") {
+ for _, s := range strings.Split(vars["ARVADOS_KEEP_SERVICES"], " ") {
if s == "" {
continue
} else if u, err := url.Parse(s); err != nil {
@@ -118,13 +167,13 @@ func NewClientFromEnv() *Client {
}
}
var insecure bool
- if s := strings.ToLower(os.Getenv("ARVADOS_API_HOST_INSECURE")); s == "1" || s == "yes" || s == "true" {
+ if s := strings.ToLower(vars["ARVADOS_API_HOST_INSECURE"]); s == "1" || s == "yes" || s == "true" {
insecure = true
}
return &Client{
Scheme: "https",
- APIHost: os.Getenv("ARVADOS_API_HOST"),
- AuthToken: os.Getenv("ARVADOS_API_TOKEN"),
+ APIHost: vars["ARVADOS_API_HOST"],
+ AuthToken: vars["ARVADOS_API_TOKEN"],
Insecure: insecure,
KeepServiceURIs: svcs,
Timeout: 5 * time.Minute,
diff --git a/sdk/go/arvados/client_test.go b/sdk/go/arvados/client_test.go
index df938008d..52c076371 100644
--- a/sdk/go/arvados/client_test.go
+++ b/sdk/go/arvados/client_test.go
@@ -10,9 +10,12 @@ import (
"io/ioutil"
"net/http"
"net/url"
+ "os"
+ "strings"
"sync"
- "testing"
"testing/iotest"
+
+ check "gopkg.in/check.v1"
)
type stubTransport struct {
@@ -68,43 +71,36 @@ func (stub *timeoutTransport) RoundTrip(req *http.Request) (*http.Response, erro
}, nil
}
-func TestCurrentUser(t *testing.T) {
- t.Parallel()
+var _ = check.Suite(&clientSuite{})
+
+type clientSuite struct{}
+
+func (*clientSuite) TestCurrentUser(c *check.C) {
stub := &stubTransport{
Responses: map[string]string{
"/arvados/v1/users/current": `{"uuid":"zzzzz-abcde-012340123401234"}`,
},
}
- c := &Client{
+ client := &Client{
Client: &http.Client{
Transport: stub,
},
APIHost: "zzzzz.arvadosapi.com",
AuthToken: "xyzzy",
}
- u, err := c.CurrentUser()
- if err != nil {
- t.Fatal(err)
- }
- if x := "zzzzz-abcde-012340123401234"; u.UUID != x {
- t.Errorf("got uuid %q, expected %q", u.UUID, x)
- }
- if len(stub.Requests) < 1 {
- t.Fatal("empty stub.Requests")
- }
+ u, err := client.CurrentUser()
+ c.Check(err, check.IsNil)
+ c.Check(u.UUID, check.Equals, "zzzzz-abcde-012340123401234")
+ c.Check(stub.Requests, check.Not(check.HasLen), 0)
hdr := stub.Requests[len(stub.Requests)-1].Header
- if hdr.Get("Authorization") != "OAuth2 xyzzy" {
- t.Errorf("got headers %+q, expected Authorization header", hdr)
- }
+ c.Check(hdr.Get("Authorization"), check.Equals, "OAuth2 xyzzy")
- c.Client.Transport = &errorTransport{}
- u, err = c.CurrentUser()
- if err == nil {
- t.Errorf("got nil error, expected something awful")
- }
+ client.Client.Transport = &errorTransport{}
+ u, err = client.CurrentUser()
+ c.Check(err, check.NotNil)
}
-func TestAnythingToValues(t *testing.T) {
+func (*clientSuite) TestAnythingToValues(c *check.C) {
type testCase struct {
in interface{}
// ok==nil means anythingToValues should return an
@@ -158,17 +154,65 @@ func TestAnythingToValues(t *testing.T) {
ok: nil,
},
} {
- t.Logf("%#v", tc.in)
+ c.Logf("%#v", tc.in)
out, err := anythingToValues(tc.in)
- switch {
- case tc.ok == nil:
- if err == nil {
- t.Errorf("got %#v, expected error", out)
- }
- case err != nil:
- t.Errorf("got err %#v, expected nil", err)
- case !tc.ok(out):
- t.Errorf("got %#v but tc.ok() says that is wrong", out)
+ if tc.ok == nil {
+ c.Check(err, check.NotNil)
+ continue
}
+ c.Check(err, check.IsNil)
+ c.Check(tc.ok(out), check.Equals, true)
}
}
+
+func (*clientSuite) TestLoadConfig(c *check.C) {
+ oldenv := os.Environ()
+ defer func() {
+ os.Clearenv()
+ for _, s := range oldenv {
+ i := strings.IndexRune(s, '=')
+ os.Setenv(s[:i], s[i+1:])
+ }
+ }()
+
+ tmp := c.MkDir()
+ os.Setenv("HOME", tmp)
+ for _, s := range os.Environ() {
+ if strings.HasPrefix(s, "ARVADOS_") {
+ i := strings.IndexRune(s, '=')
+ os.Unsetenv(s[:i])
+ }
+ }
+ os.Mkdir(tmp+"/.config", 0777)
+ os.Mkdir(tmp+"/.config/arvados", 0777)
+
+ // Use $HOME/.config/arvados/settings.conf if no env vars are
+ // set
+ os.WriteFile(tmp+"/.config/arvados/settings.conf", []byte(`
+ ARVADOS_API_HOST = localhost:1
+ ARVADOS_API_TOKEN = token_from_settings_file1
+ `), 0777)
+ client := NewClientFromEnv()
+ c.Check(client.AuthToken, check.Equals, "token_from_settings_file1")
+ c.Check(client.APIHost, check.Equals, "localhost:1")
+ c.Check(client.Insecure, check.Equals, false)
+
+ // ..._INSECURE=true and comments in settings.conf
+ os.WriteFile(tmp+"/.config/arvados/settings.conf", []byte(`
+ #ARVADOS_API_HOST = localhost:2
+ ARVADOS_API_TOKEN = token_from_settings_file2
+ ARVADOS_API_HOST_INSECURE = true
+ `), 0777)
+ client = NewClientFromEnv()
+ c.Check(client.AuthToken, check.Equals, "token_from_settings_file2")
+ c.Check(client.APIHost, check.Equals, "")
+ c.Check(client.Insecure, check.Equals, true)
+
+ // Environment variables override settings.conf
+ os.Setenv("ARVADOS_API_HOST", "[::]:3")
+ os.Setenv("ARVADOS_API_HOST_INSECURE", "0")
+ client = NewClientFromEnv()
+ c.Check(client.AuthToken, check.Equals, "token_from_settings_file2")
+ c.Check(client.APIHost, check.Equals, "[::]:3")
+ c.Check(client.Insecure, check.Equals, false)
+}
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list