diff --git a/internal/authflow/flow.go b/internal/authflow/flow.go index c809e455a..175579e14 100644 --- a/internal/authflow/flow.go +++ b/internal/authflow/flow.go @@ -26,6 +26,7 @@ var ( type iconfig interface { Set(string, string, string) error Write() error + WriteHosts() error } func AuthFlowWithConfig(cfg iconfig, IO *iostreams.IOStreams, hostname, notice string, additionalScopes []string, isInteractive bool) (string, error) { @@ -46,7 +47,7 @@ func AuthFlowWithConfig(cfg iconfig, IO *iostreams.IOStreams, hostname, notice s return "", err } - return token, cfg.Write() + return token, cfg.WriteHosts() } func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, additionalScopes []string, isInteractive bool) (string, string, error) { diff --git a/internal/config/config_file_test.go b/internal/config/config_file_test.go index 100c065f3..188ee68cd 100644 --- a/internal/config/config_file_test.go +++ b/internal/config/config_file_test.go @@ -9,6 +9,7 @@ import ( "runtime" "testing" + "github.com/MakeNowJust/heredoc" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" ) @@ -269,6 +270,31 @@ func Test_configFile_Write_toDisk(t *testing.T) { } } +func Test_configFile_WriteHosts_toDisk(t *testing.T) { + configDir := filepath.Join(t.TempDir(), ".config", "gh") + _ = os.MkdirAll(configDir, 0755) + os.Setenv(GH_CONFIG_DIR, configDir) + defer os.Unsetenv(GH_CONFIG_DIR) + + cfg := NewFromString(heredoc.Doc(` + hosts: + github.com: + user: monalisa + oauth_token: TOKEN + `)) + err := cfg.WriteHosts() + if err != nil { + t.Fatal(err) + } + + expectedConfig := "github.com:\n user: monalisa\n oauth_token: TOKEN\n" + actualConfig, err := ioutil.ReadFile(filepath.Join(configDir, "hosts.yml")) + assert.NoError(t, err) + assert.Equal(t, expectedConfig, string(actualConfig)) + _, nonExistErr := os.Stat(filepath.Join(configDir, "config.yml")) + assert.Error(t, nonExistErr) +} + func Test_autoMigrateConfigDir_noMigration_notExist(t *testing.T) { homeDir := t.TempDir() migrateDir := t.TempDir() diff --git a/internal/config/config_type.go b/internal/config/config_type.go index 68da2af29..a4f63e069 100644 --- a/internal/config/config_type.go +++ b/internal/config/config_type.go @@ -21,6 +21,7 @@ type Config interface { Aliases() (*AliasConfig, error) CheckWriteable(string, string) error Write() error + WriteHosts() error } type ConfigOption struct { diff --git a/internal/config/from_file.go b/internal/config/from_file.go index 3c1cfd65b..a4adec2f4 100644 --- a/internal/config/from_file.go +++ b/internal/config/from_file.go @@ -135,13 +135,10 @@ func (c *fileConfig) CheckWriteable(hostname, key string) error { func (c *fileConfig) Write() error { mainData := yaml.Node{Kind: yaml.MappingNode} - hostsData := yaml.Node{Kind: yaml.MappingNode} nodes := c.documentRoot.Content[0].Content for i := 0; i < len(nodes)-1; i += 2 { - if nodes[i].Value == "hosts" { - hostsData.Content = append(hostsData.Content, nodes[i+1].Content...) - } else { + if nodes[i].Value != "hosts" { mainData.Content = append(mainData.Content, nodes[i], nodes[i+1]) } } @@ -151,12 +148,26 @@ func (c *fileConfig) Write() error { return err } - filename := ConfigFile() - err = WriteConfigFile(filename, yamlNormalize(mainBytes)) + err = WriteConfigFile(ConfigFile(), yamlNormalize(mainBytes)) if err != nil { return err } + return c.WriteHosts() +} + +// Write the hosts config file only, so as to allow logging in when the main +// config file is not writable. +func (c *fileConfig) WriteHosts() error { + hostsData := yaml.Node{Kind: yaml.MappingNode} + + nodes := c.documentRoot.Content[0].Content + for i := 0; i < len(nodes)-1; i += 2 { + if nodes[i].Value == "hosts" { + hostsData.Content = append(hostsData.Content, nodes[i+1].Content...) + } + } + hostsBytes, err := yaml.Marshal(&hostsData) if err != nil { return err diff --git a/internal/config/stub.go b/internal/config/stub.go index aeb2e5526..4b1704849 100644 --- a/internal/config/stub.go +++ b/internal/config/stub.go @@ -67,6 +67,10 @@ func (c ConfigStub) Write() error { return nil } +func (c ConfigStub) WriteHosts() error { + return nil +} + func (c ConfigStub) DefaultHost() (string, error) { return "", nil } diff --git a/pkg/cmd/auth/login/login.go b/pkg/cmd/auth/login/login.go index e8353fd4a..48469a37c 100644 --- a/pkg/cmd/auth/login/login.go +++ b/pkg/cmd/auth/login/login.go @@ -161,7 +161,7 @@ func loginRun(opts *LoginOptions) error { return fmt.Errorf("error validating token: %w", err) } - return cfg.Write() + return cfg.WriteHosts() } existingToken, _ := cfg.Get(hostname, "oauth_token") diff --git a/pkg/cmd/auth/logout/logout.go b/pkg/cmd/auth/logout/logout.go index 3873da324..60b1ce6ef 100644 --- a/pkg/cmd/auth/logout/logout.go +++ b/pkg/cmd/auth/logout/logout.go @@ -151,7 +151,7 @@ func logoutRun(opts *LogoutOptions) error { } cfg.UnsetHost(hostname) - err = cfg.Write() + err = cfg.WriteHosts() if err != nil { return fmt.Errorf("failed to write config, authentication configuration not updated: %w", err) } diff --git a/pkg/cmd/auth/shared/login_flow.go b/pkg/cmd/auth/shared/login_flow.go index c33051e44..4f14e2ea6 100644 --- a/pkg/cmd/auth/shared/login_flow.go +++ b/pkg/cmd/auth/shared/login_flow.go @@ -18,6 +18,7 @@ type iconfig interface { Get(string, string) (string, error) Set(string, string, string) error Write() error + WriteHosts() error } type LoginOptions struct { @@ -173,7 +174,7 @@ func Login(opts *LoginOptions) error { fmt.Fprintf(opts.IO.ErrOut, "%s Configured git protocol\n", cs.SuccessIcon()) } - err := cfg.Write() + err := cfg.WriteHosts() if err != nil { return err } diff --git a/pkg/cmd/auth/shared/login_flow_test.go b/pkg/cmd/auth/shared/login_flow_test.go index 6f1b35ebe..5349bc653 100644 --- a/pkg/cmd/auth/shared/login_flow_test.go +++ b/pkg/cmd/auth/shared/login_flow_test.go @@ -30,6 +30,10 @@ func (c tinyConfig) Write() error { return nil } +func (c tinyConfig) WriteHosts() error { + return nil +} + func TestLogin_ssh(t *testing.T) { dir := t.TempDir() io, _, stdout, stderr := iostreams.Test()