Skip to content

Commit

Permalink
ssh/config: refactoring the Config merge
Browse files Browse the repository at this point in the history
This changes rename method [Config.Prepend] to [Config.Merge].

The way that how the other Config merged is changed.
Instead of appending all of other's sections into the current Config,
append the other Config instance to the current instance of Config.

During [Config.Get] the top Config will be evaluated first, and then the
other Config is evaluated in order of Merge.
  • Loading branch information
shuLhan committed Dec 25, 2023
1 parent b12a886 commit bbefc9b
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 19 deletions.
33 changes: 24 additions & 9 deletions lib/ssh/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ type Config struct {
homeDir string

sections []*Section

// others Config, as result of [Config.Merge].
others []*Config
}

// newConfig create new SSH Config instance from file.
Expand Down Expand Up @@ -156,6 +159,16 @@ func (cfg *Config) Get(host string) (section *Section) {
section.merge(hostMatch)
}
}

var (
other *Config
subsec *Section
)
for _, other = range cfg.others {
subsec = other.Get(host)
section.merge(subsec)
}

section.setDefaults()

if host != `` && section.Field[KeyHostname] == `` {
Expand All @@ -165,16 +178,18 @@ func (cfg *Config) Get(host string) (section *Section) {
return section
}

// Prepend other Config's sections to this Config.
// The other's sections will be at the top of the list.
// Merge other Config as part of this Config.
// This function can be used to combine multiple SSH config files into one.
//
// This function can be useful if we want to load another SSH config file
// without using Include directive.
func (cfg *Config) Prepend(other *Config) {
newSections := make([]*Section, 0, len(cfg.sections)+len(other.sections))
newSections = append(newSections, other.sections...)
newSections = append(newSections, cfg.sections...)
cfg.sections = newSections
// For example after the user's "~/.ssh/config" has been loaded, we can
// merge it with system "/etc/ssh/ssh_config".
// During [Config.Get] the top Config will be evaluated first, and then the
// other Config is evaluated in order of Merge-d.
func (cfg *Config) Merge(other *Config) {
if other == nil {
return
}
cfg.others = append(cfg.others, other)
}

// loadEnvironments get all environments variables and store it in the map for
Expand Down
39 changes: 39 additions & 0 deletions lib/ssh/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,45 @@ func TestConfigGet(t *testing.T) {
}
}

func TestConfigMerge(t *testing.T) {
var (
tdata *test.Data
err error
)

tdata, err = test.LoadData(`testdata/config_merge_test.txt`)
if err != nil {
t.Fatal(err)
}

var cfg *Config

cfg, err = Load(`./testdata/sub/config`)
if err != nil {
t.Fatal(err)
}

var topcfg *Config

topcfg, err = Load(`./testdata/config`)
if err != nil {
t.Fatal(err)
}

cfg.Merge(topcfg)

var (
host = `my.example.local`
gotSection = cfg.Get(host)

buf bytes.Buffer
)

gotSection.WriteTo(&buf)

test.Assert(t, host, string(tdata.Output[host]), buf.String())
}

func TestParseKeyValue(t *testing.T) {
cases := []struct {
line string
Expand Down
3 changes: 2 additions & 1 deletion lib/ssh/config/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestReadLines(t *testing.T) {
`User test`,
`IdentityFile ~/.ssh/notexist`,
`Host *.example.local`,
`Include sub/config`,
`Include sub/include`,
`Host foo.local`,
`Hostname 127.0.0.3`,
`Port 28022`,
Expand Down Expand Up @@ -123,6 +123,7 @@ func TestConfigParser_load(t *testing.T) {
`Hostname 127.0.0.2`,
`User wildcard`,
`IdentityFile ~/.ssh/notexist`,
`UserKnownHostsFile ~/.ssh/known_hosts_example_local`,
`Host foo.local`,
`Hostname 127.0.0.3`,
`Port 28022`,
Expand Down
25 changes: 20 additions & 5 deletions lib/ssh/config/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,16 @@ func (section *Section) MarshalText() (text []byte, err error) {
}
continue
}
if key == KeyUserKnownHostsFile {
for _, val = range section.knownHostsFile {
buf.WriteString(` `)
buf.WriteString(key)
buf.WriteByte(' ')
buf.WriteString(section.pathFold(val))
buf.WriteByte('\n')
}
continue
}

buf.WriteString(` `)
buf.WriteString(key)
Expand All @@ -688,13 +698,18 @@ func (section *Section) WriteTo(w io.Writer) (n int64, err error) {
return int64(c), err
}

// pathFold replace the home directory prefix with '~'.
// pathFold remove the path prefix from input file "in", start from the
// "config" directory and then the user's home directory.
func (section *Section) pathFold(in string) (out string) {
if !strings.HasPrefix(in, section.homeDir) {
return in
if filepath.HasPrefix(in, section.dir) {
out, _ = filepath.Rel(section.dir, in)
return out
}
if filepath.HasPrefix(in, section.homeDir) {
out, _ = filepath.Rel(section.homeDir, in)
return `~/` + out
}
out = `~` + in[len(section.homeDir):]
return out
return in
}

// pathUnfold expand the file to make it absolute.
Expand Down
2 changes: 1 addition & 1 deletion lib/ssh/config/testdata/config
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Host example.local

# comment
Host *.example.local
Include sub/config
Include sub/include

Host foo.local
Hostname 127.0.0.3
Expand Down
1 change: 1 addition & 0 deletions lib/ssh/config/testdata/config_get_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Host my.example.local
identityfile ~/.ssh/notexist
port 22
user wildcard
userknownhostsfile ~/.ssh/known_hosts_example_local
xauthlocation /usr/X11R6/bin/xauth

<<< foo.local
Expand Down
15 changes: 15 additions & 0 deletions lib/ssh/config/testdata/config_merge_test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Test data for [Config.Merge].

<<< my.example.local
Host my.example.local
challengeresponseauthentication yes
checkhostip yes
connectionattempts 1
hostname 127.0.0.2
identityfile my-example-local
identityfile ~/.ssh/notexist
port 22
user wildcard
userknownhostsfile my_known_hosts
userknownhostsfile ~/.ssh/known_hosts_example_local
xauthlocation /usr/X11R6/bin/xauth
6 changes: 3 additions & 3 deletions lib/ssh/config/testdata/sub/config
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Hostname 127.0.0.2
User wildcard
IdentityFile ~/.ssh/notexist
Host my.example.local
UserKnownHostsFile my_known_hosts
IdentityFile my-example-local
4 changes: 4 additions & 0 deletions lib/ssh/config/testdata/sub/include
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Hostname 127.0.0.2
User wildcard
IdentityFile ~/.ssh/notexist
UserKnownHostsFile ~/.ssh/known_hosts_example_local

0 comments on commit bbefc9b

Please sign in to comment.