diff --git a/commands/auth.go b/commands/auth.go index fa2d3cd70..51fbe017a 100644 --- a/commands/auth.go +++ b/commands/auth.go @@ -353,13 +353,22 @@ func writeConfig() error { // defaultConfigFileWriter returns a writer to a newly created config.yaml file in the default config home. // When using the default config file path the default config home directory will be created; Otherwise -// the custom config home directory must exist and be writable to the user issuing the auth command. +// the custom config home directory will be created if it doesn't exist. func defaultConfigFileWriter() (io.WriteCloser, error) { cfgFile := viper.GetString("config") defaultCfgFile := filepath.Join(defaultConfigHome(), defaultConfigName) if cfgFile == defaultCfgFile { configHome() + } else { + // For custom config paths, ensure the parent directory exists + parentDir := filepath.Dir(cfgFile) + if _, err := os.Stat(parentDir); os.IsNotExist(err) { + err = os.MkdirAll(parentDir, 0755) + if err != nil { + return nil, err + } + } } f, err := os.Create(cfgFile) diff --git a/commands/doit.go b/commands/doit.go index ed583893a..3430b8352 100644 --- a/commands/doit.go +++ b/commands/doit.go @@ -149,8 +149,22 @@ func defaultConfigHome() string { func configHome() string { ch := defaultConfigHome() - err := os.MkdirAll(ch, 0755) - checkErr(err) + + // Get the parent directory to check if it exists + parentDir := filepath.Dir(ch) + + // Check if parent directory exists (could be a symlink or directory) + if _, err := os.Stat(parentDir); os.IsNotExist(err) { + // Parent doesn't exist, create it with MkdirAll + err = os.MkdirAll(parentDir, 0755) + checkErr(err) + } + + // Now create the final directory (doctl) if it doesn't exist + if _, err := os.Stat(ch); os.IsNotExist(err) { + err = os.Mkdir(ch, 0755) + checkErr(err) + } return ch } diff --git a/integration/auth_test.go b/integration/auth_test.go index ab37f3fce..cc60f97cf 100644 --- a/integration/auth_test.go +++ b/integration/auth_test.go @@ -284,68 +284,51 @@ context: default ) output, err := cmd.CombinedOutput() - expect.NoError(err, string(output)) - - err = os.Remove(testConfig) expect.NoError(err) + + expect.Contains(string(output), "next (current)") }) }) - when("switching contexts containing a period", func() { - it("does not mangle that context", func() { - var testConfigBytes = []byte(`access-token: first-token -auth-contexts: - test@example.com: second-token -context: default -`) - + when("config directory parent is a symlink", func() { + it("successfully creates config directory through symlink", func() { tmpDir := t.TempDir() - testConfig := filepath.Join(tmpDir, "test-config.yml") - expect.NoError(os.WriteFile(testConfig, testConfigBytes, 0644)) - cmd := exec.Command(builtBinaryPath, - "-u", server.URL, - "auth", - "switch", - "--config", testConfig, - ) - _, err := cmd.CombinedOutput() + // Create actual config directory + actualConfigDir := filepath.Join(tmpDir, "actual-config") + err := os.MkdirAll(actualConfigDir, 0755) expect.NoError(err) - fileBytes, err := os.ReadFile(testConfig) + // Create a symlink .config pointing to the actual config directory + symlinkConfigDir := filepath.Join(tmpDir, ".config") + err = os.Symlink(actualConfigDir, symlinkConfigDir) expect.NoError(err) - expect.Contains(string(fileBytes), "test@example.com: second-token") - - err = os.Remove(testConfig) - expect.NoError(err) - }) - }) - when("the DIGITALOCEAN_CONTEXT variable is set", func() { - it("uses that context for commands", func() { - var testConfigBytes = []byte(`access-token: first-token -auth-contexts: - next: second-token -context: default -`) - - tmpDir := t.TempDir() - testConfig := filepath.Join(tmpDir, "test-config.yml") - expect.NoError(os.WriteFile(testConfig, testConfigBytes, 0644)) + // Set up config path to use the symlink + configPath := filepath.Join(symlinkConfigDir, "doctl", "config.yml") cmd := exec.Command(builtBinaryPath, "-u", server.URL, + "--config", configPath, "auth", - "list", - "--config", testConfig, + "init", + "--access-token", "some-magic-token", + "--token-validation-server", server.URL, ) - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "DIGITALOCEAN_CONTEXT=next") - output, err := cmd.CombinedOutput() - expect.NoError(err, string(output)) + _, err = cmd.CombinedOutput() + expect.NoError(err) - expect.Contains(string(output), "next (current)") + // Verify the config file was created + fileBytes, err := os.ReadFile(configPath) + expect.NoError(err) + expect.Contains(string(fileBytes), "access-token: some-magic-token") + + // Verify the directory structure exists through the symlink + doctlDir := filepath.Join(symlinkConfigDir, "doctl") + info, err := os.Stat(doctlDir) + expect.NoError(err) + expect.True(info.IsDir()) }) }) })