diff --git a/commands/displayers/size.go b/commands/displayers/size.go index 2dbe2270c..8f51659eb 100644 --- a/commands/displayers/size.go +++ b/commands/displayers/size.go @@ -32,13 +32,14 @@ func (si *Size) JSON(out io.Writer) error { func (si *Size) Cols() []string { return []string{ - "Slug", "Memory", "VCPUs", "Disk", "PriceMonthly", "PriceHourly", + "Slug", "Description", "Memory", "VCPUs", "Disk", "PriceMonthly", "PriceHourly", } } func (si *Size) ColMap() map[string]string { return map[string]string{ - "Slug": "Slug", "Memory": "Memory", "VCPUs": "VCPUs", + "Slug": "Slug", "Description": "Description", + "Memory": "Memory", "VCPUs": "VCPUs", "Disk": "Disk", "PriceMonthly": "Price Monthly", "PriceHourly": "Price Hourly", } @@ -49,7 +50,8 @@ func (si *Size) KV() []map[string]interface{} { for _, s := range si.Sizes { o := map[string]interface{}{ - "Slug": s.Slug, "Memory": s.Memory, "VCPUs": s.Vcpus, + "Slug": s.Slug, "Description": s.Description, + "Memory": s.Memory, "VCPUs": s.Vcpus, "Disk": s.Disk, "PriceMonthly": fmt.Sprintf("%0.2f", s.PriceMonthly), "PriceHourly": s.PriceHourly, } diff --git a/commands/functions.go b/commands/functions.go index dacc39f37..4ac391157 100644 --- a/commands/functions.go +++ b/commands/functions.go @@ -127,10 +127,11 @@ func appendParams(c *CmdConfig, args []string) ([]string, error) { } for _, param := range params { parts := strings.Split(param, ":") - if len(parts) != 2 { + if len(parts) < 2 { return args, errors.New("values for --params must have KEY:VALUE form") } - args = append(args, dashdashParam, parts[0], parts[1]) + parts1 := strings.Join(parts[1:], ":") + args = append(args, dashdashParam, parts[0], parts1) } return args, nil } diff --git a/commands/functions_test.go b/commands/functions_test.go index 96c0a4dd8..a69b119c6 100644 --- a/commands/functions_test.go +++ b/commands/functions_test.go @@ -147,6 +147,12 @@ func TestFunctionsInvoke(t *testing.T) { doctlFlags: map[string]interface{}{"param": []string{"name:world", "address:everywhere"}}, expectedNimArgs: []string{"hello", "--param", "name", "world", "--param", "address", "everywhere"}, }, + { + name: "param flag colon-value", + doctlArgs: "hello", + doctlFlags: map[string]interface{}{"param": []string{"url:https://example.com"}}, + expectedNimArgs: []string{"hello", "--param", "url", "https://example.com"}, + }, } for _, tt := range tests { diff --git a/commands/sandbox.go b/commands/sandbox.go index f7bc7c11f..cbc85e541 100644 --- a/commands/sandbox.go +++ b/commands/sandbox.go @@ -99,10 +99,10 @@ connect to the cloud component of the sandbox provided with your account). Othe }, } - CmdBuilder(cmd, RunSandboxInstall, "install", "Installs the sandbox support", + cmdBuilderWithInit(cmd, RunSandboxInstall, "install", "Installs the sandbox support", `This command installs additional software under `+"`"+`doctl`+"`"+` needed to make the other sandbox commands work. The install operation is long-running, and a network connection is required.`, - Writer) + Writer, false) CmdBuilder(cmd, RunSandboxUpgrade, "upgrade", "Upgrades sandbox support to match this version of doctl", `This command upgrades the sandbox support software under `+"`"+`doctl`+"`"+` by installing over the existing version. @@ -142,16 +142,21 @@ the entire packages are removed.`, Writer) // RunSandboxInstall performs the network installation of the 'nim' adjunct to support sandbox development func RunSandboxInstall(c *CmdConfig) error { status := c.checkSandboxStatus(c) - if status == ErrSandboxNeedsUpgrade { + switch status { + case nil: + fmt.Fprintln(c.Out, "Sandbox support is already installed at an appropriate version. No action needed.") + return nil + case ErrSandboxNeedsUpgrade: fmt.Fprintln(c.Out, "Sandbox support is already installed, but needs an upgrade for this version of `doctl`.") fmt.Fprintln(c.Out, "Use `doctl sandbox upgrade` to upgrade the support.") return nil - } - if status == nil { - fmt.Fprintln(c.Out, "Sandbox support is already installed at an appropriate version. No action needed.") + case ErrSandboxNotConnected: + fmt.Fprintln(c.Out, "Sandbox support is already installed at an appropriate version, but not connected to a function namespace. Use `doctl sandbox connect`.") return nil } + sandboxDir, _ := getSandboxDirectory() + return c.installSandbox(c, sandboxDir, false) } @@ -159,16 +164,21 @@ func RunSandboxInstall(c *CmdConfig) error { // the existing version is inadequate as detected by checkSandboxStatus() func RunSandboxUpgrade(c *CmdConfig) error { status := c.checkSandboxStatus(c) - if status == nil { + switch status { + case nil: fmt.Fprintln(c.Out, "Sandbox support is already installed at an appropriate version. No action needed.") // TODO should there be an option to upgrade beyond the minimum needed? return nil - } - if status == ErrSandboxNotInstalled { + case ErrSandboxNotInstalled: fmt.Fprintln(c.Out, "Sandbox support was never installed. Use `doctl sandbox install`.") return nil + case ErrSandboxNotConnected: + fmt.Fprintln(c.Out, "Sandbox support is already installed at an appropriate version, but not connected to a function namespace. Use `doctl sandbox connect`.") + return nil } + sandboxDir, _ := getSandboxDirectory() + return c.installSandbox(c, sandboxDir, true) } @@ -199,6 +209,19 @@ func RunSandboxConnect(c *CmdConfig) error { if err != nil && err != ErrSandboxNotConnected { return err } + + // Create the credentials dir if run as a snap as this might not have + // happened yet since the initial install happens on the build host. + _, isSnap := os.LookupEnv("SNAP") + if isSnap { + sandboxDir, _ := getSandboxDirectory() + credsDir := getCredentialDirectory(c, sandboxDir) + err = os.MkdirAll(credsDir, 0700) + if err != nil { + return nil + } + } + result, err := sandboxExecNoCheck(c, "auth/login", []string{"--auth", creds.Auth, "--apihost", creds.APIHost}) if err != nil { return err @@ -458,7 +481,7 @@ func InstallSandbox(c *CmdConfig, sandboxDir string, upgrading bool) error { // Note: we don't let this be allocated in the system temporaries area because // that might be on a separate file system, meaning that the final install step // will require an additional copy rather than a simple rename. - tmp, err := ioutil.TempDir(defaultConfigHome(), "sbx-install") + tmp, err := ioutil.TempDir(configHome(), "sbx-install") if err != nil { return err } @@ -551,10 +574,16 @@ func InstallSandbox(c *CmdConfig, sandboxDir string, upgrading bool) error { } else { // Make new empty credentials directory emptyCreds := filepath.Join(srcPath, credsDir) - err = os.Mkdir(emptyCreds, 0700) + err = os.MkdirAll(emptyCreds, 0700) if err != nil { return nil } + + // Create the sandbox directory if necessary. + err := os.MkdirAll(sandboxDir, 0755) + if err != nil { + return err + } } // Remove former sandboxDir before moving in the new one err = os.RemoveAll(sandboxDir) @@ -565,6 +594,7 @@ func InstallSandbox(c *CmdConfig, sandboxDir string, upgrading bool) error { if err != nil { return err } + if nodeFileName != "" { if goos == "win" { srcPath = filepath.Join(tmp, nodeDir, nodeBin) @@ -601,7 +631,7 @@ func preserveCreds(c *CmdConfig, stagingDir string, sandboxDir string) error { // There was no creds directory. Check for legacy form and convert as part // of preserving. legacyCredPath := filepath.Join(sandboxDir, ".nimbella") - err = os.Mkdir(relocPath, 0700) + err = os.MkdirAll(relocPath, 0700) if err != nil { return err } @@ -674,12 +704,13 @@ func download(URL, targetFile string) error { // (and the only one that customers are expected to use) is relative to the defaultConfigHome. // For testing purposes, an override can be provided via an environment variable. func getSandboxDirectory() (string, bool) { - sandboxDir := os.Getenv("sandboxDirectory") - if sandboxDir == "" { + sandboxDir, shouldOverride := os.LookupEnv("OVERRIDE_SANDBOX_DIR") + if !shouldOverride { sandboxDir = filepath.Join(defaultConfigHome(), "sandbox") } _, err := os.Stat(sandboxDir) exists := !os.IsNotExist(err) + return sandboxDir, exists } @@ -692,6 +723,14 @@ func getCredentialDirectory(c *CmdConfig, sandboxDir string) string { hasher.Write([]byte(token)) sha := hasher.Sum(nil) leafDir := hex.EncodeToString(sha[:4]) + + // When running as a snap, the credential are stored separately from the + // actual sandbox install. So we ignore any override of the sandboxDir here. + _, isSnap := os.LookupEnv("SNAP") + if isSnap { + sandboxDir = filepath.Join(configHome(), "sandbox") + } + return filepath.Join(sandboxDir, credsDir, leafDir) } diff --git a/integration/auth_test.go b/integration/auth_test.go index 7a4e64986..7fee4bc07 100644 --- a/integration/auth_test.go +++ b/integration/auth_test.go @@ -97,6 +97,56 @@ var _ = suite("auth/init", func(t *testing.T, when spec.G, it spec.S) { }) }) + when("the --access-token flag is used", func() { + it("validates and saves the provided token non-interactively", func() { + tmpDir := t.TempDir() + + testConfig := filepath.Join(tmpDir, "test-config.yml") + + cmd := exec.Command(builtBinaryPath, + "-u", server.URL, + "--config", testConfig, + "auth", + "init", + "--access-token", "some-magic-token", + ) + + _, err := cmd.CombinedOutput() + expect.NoError(err) + + fileBytes, err := ioutil.ReadFile(testConfig) + expect.NoError(err) + + expect.Contains(string(fileBytes), "access-token: some-magic-token") + }) + + it("validates and overwrites an existing token non-interactively", func() { + var testConfigBytes = []byte(`access-token: first-token +context: default +`) + + tmpDir := t.TempDir() + testConfig := filepath.Join(tmpDir, "test-config.yml") + expect.NoError(ioutil.WriteFile(testConfig, testConfigBytes, 0644)) + + cmd := exec.Command(builtBinaryPath, + "-u", server.URL, + "--config", testConfig, + "auth", + "init", + "--access-token", "some-magic-token", + ) + + _, err := cmd.CombinedOutput() + expect.NoError(err) + + fileBytes, err := ioutil.ReadFile(testConfig) + expect.NoError(err) + + expect.Contains(string(fileBytes), "access-token: some-magic-token") + }) + }) + when("no custom config is provided", func() { it("saves the auth token to the default config path", func() { cmd := exec.Command(builtBinaryPath, diff --git a/integration/size_list_test.go b/integration/size_list_test.go index 6ededccad..058fab423 100644 --- a/integration/size_list_test.go +++ b/integration/size_list_test.go @@ -106,6 +106,7 @@ const ( { "slug": "512mb", "memory": 512, + "description": "Basic", "vcpus": 1, "disk": 20, "transfer": 1, @@ -119,6 +120,7 @@ const ( { "slug": "s-1vcpu-1gb", "memory": 1024, + "description": "Basic", "vcpus": 1, "disk": 25, "transfer": 1, @@ -137,9 +139,9 @@ const ( } ` sizeListOutput = ` -Slug Memory VCPUs Disk Price Monthly Price Hourly -512mb 512 1 20 5.00 0.007440 -s-1vcpu-1gb 1024 1 25 5.00 0.007440 +Slug Description Memory VCPUs Disk Price Monthly Price Hourly +512mb Basic 512 1 20 5.00 0.007440 +s-1vcpu-1gb Basic 1024 1 25 5.00 0.007440 ` sizeListFormatOutput = ` Slug Price Monthly @@ -148,7 +150,7 @@ s-1vcpu-1gb 5.00 ` sizeListNoHeaderOutput = ` -512mb 512 1 20 5.00 0.007440 -s-1vcpu-1gb 1024 1 25 5.00 0.007440 +512mb Basic 512 1 20 5.00 0.007440 +s-1vcpu-1gb Basic 1024 1 25 5.00 0.007440 ` ) diff --git a/snap/local/doctl-launch b/snap/local/doctl-launch index 533a0e249..c81e91f54 100644 --- a/snap/local/doctl-launch +++ b/snap/local/doctl-launch @@ -1,3 +1,7 @@ #!/bin/sh + +# /snap/doctl/current/opt/sandbox +export OVERRIDE_SANDBOX_DIR="$SNAP/opt/sandbox" + real_home=$(getent passwd "$(id -u)" | cut -d ':' -f 6) HOME=$real_home doctl.real "$@" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 58ad6fba1..b5830084c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -29,6 +29,11 @@ parts: after: [launcher] source: . plugin: go + build-environment: + # Results in installation to /snap/doctl/current/opt/sandbox + - OVERRIDE_SANDBOX_DIR: "$SNAPCRAFT_PART_INSTALL/opt/sandbox" + # Override to prevent 'invalid cross-device link' calling doctl + - XDG_CONFIG_HOME: "$SNAPCRAFT_PART_INSTALL/tmp/.config" override-pull: | git clone https://github.com/digitalocean/doctl.git . override-build: | @@ -49,6 +54,10 @@ parts: chmod +x doctl mkdir -p $SNAPCRAFT_PART_INSTALL/bin mv doctl $SNAPCRAFT_PART_INSTALL/bin/ + + # Install the sandbox + $SNAPCRAFT_PART_INSTALL/bin/doctl sandbox install + rm -r $XDG_CONFIG_HOME/doctl organize: bin/doctl: bin/doctl.real