Add reload config (#2)

* add function that can clear cached config files

Once the first call to `Get()`, `GetStrict()`, `GetAll()`,
or`GetAllStrict()` has been made, the contents of the config files will
be cached for all future calls to any of those functions. This can be
frustrating if the user wants to capture changes to the config file that
were made externally. This
change adds the `ClearCachedConfigs()` function to provide control over
that.

* change ClearCachedConfigs to ReloadConfigs

Clearing cached data is somewhat vague and could be more confusing to
users who are not familiar with this library. But Reloading the config
data is very straightforward. For this reason, the ability to clear the
cache has been replaced with the ability to reload.

The reload simply clears the cache and then loads the contents again.

Additionally, this contains a bug fix that ensure the loadConfigs
pointer is set to a non-null value before it is used.

* add tests for reloading ssh config files

This adds 2 tests. The first checks that ssh config data is cached and
does not update if the file changes afterward. The second checks that
the `ReloadConfigs()` function will discard the cached data and load the
current config file contents.
This commit is contained in:
Sylvie Crowe
2024-03-05 19:38:17 -08:00
committed by GitHub
parent 1d09c0b505
commit cba6b6a60f
4 changed files with 100 additions and 2 deletions
+10
View File
@@ -42,6 +42,16 @@ Some SSH arguments have default values - for example, the default value for
given Host/keyword pair exists in the config, we'll return a default for the
keyword if one exists.
### Reloading SSH config files
Once the first call to `Get()`, `GetStrict()`, `GetAll()`, or `GetAllStrict()`
has been made, the contents of the config files will be cached for all future
calls to any of those functions. The `ReloadConfigs()` function will reset
this cache and replace it with the current config file contents.
```go
ssh_config.ReloadConfigs()
```
### Manipulating SSH config files
Here's how you can manipulate an SSH config file, and then write it back to
+20 -2
View File
@@ -8,7 +8,7 @@
// the host name to match on ("example.com"), and the second argument is the key
// you want to retrieve ("Port"). The keywords are case insensitive.
//
// port := ssh_config.Get("myhost", "Port")
// port := ssh_config.Get("myhost", "Port")
//
// You can also manipulate an SSH config file and then print it or write it back
// to disk.
@@ -59,7 +59,7 @@ type UserSettings struct {
systemConfigFinder configFinder
userConfig *Config
userConfigFinder configFinder
loadConfigs sync.Once
loadConfigs *sync.Once
onceErr error
}
@@ -167,6 +167,14 @@ func GetAllStrict(alias, key string) ([]string, error) {
return DefaultUserSettings.GetAllStrict(alias, key)
}
// ReloadConfigs clears the cached config data and freshly loads the config
// files again.
//
// ReloadConfigs is a wrapper around DefaultUserSettings.ReloadConfigs.
func ReloadConfigs() {
DefaultUserSettings.ReloadConfigs()
}
// Get finds the first value for key within a declaration that matches the
// alias. Get returns the empty string if no value was found, or if IgnoreErrors
// is false and we could not parse the configuration file. Use GetStrict to
@@ -272,6 +280,9 @@ func (u *UserSettings) ConfigFinder(f func() string) {
}
func (u *UserSettings) doLoadConfigs() {
if u.loadConfigs == nil {
u.loadConfigs = new(sync.Once)
}
u.loadConfigs.Do(func() {
var filename string
var err error
@@ -310,6 +321,13 @@ func (u *UserSettings) doLoadConfigs() {
})
}
// ReloadConfigs clears the cached config data and freshly loads the config
// files again.
func (u *UserSettings) ReloadConfigs() {
u.loadConfigs = new(sync.Once)
u.doLoadConfigs()
}
func parseFile(filename string) (*Config, error) {
return parseWithDepth(filename, 0)
}
+66
View File
@@ -259,6 +259,72 @@ func TestGetEqsign(t *testing.T) {
}
}
var modified1 = []byte(`
Host wap
User modified1
KexAlgorithms diffie-hellman-group1-sha1
`)
var modified2 = []byte(`
Host wap
User modified2
KexAlgorithms diffie-hellman-group1-sha1
`)
func TestCachedConfig(t *testing.T) {
us := &UserSettings{
userConfigFinder: testConfigFinder("testdata/modified"),
}
err1 := os.WriteFile("testdata/modified", modified1, 0644)
if err1 != nil {
t.Errorf("error writing to file: %v", err1)
}
val1 := us.Get("wap", "User")
if val1 != "modified1" {
t.Errorf("expected to find User modified1, got %q", val1)
}
err2 := os.WriteFile("testdata/modified", modified2, 0644)
if err1 != nil {
t.Errorf("error writing to file: %v", err2)
}
val2 := us.Get("wap", "User")
if val2 != "modified1" {
t.Errorf("expected to find User modified1, got %q", val2)
}
}
func TestReloadConfigs(t *testing.T) {
us := &UserSettings{
userConfigFinder: testConfigFinder("testdata/modified"),
}
err1 := os.WriteFile("testdata/modified", modified1, 0644)
if err1 != nil {
t.Errorf("error writing to file: %v", err1)
}
val1 := us.Get("wap", "User")
if val1 != "modified1" {
t.Errorf("expected to find User modified1, got %q", val1)
}
err2 := os.WriteFile("testdata/modified", modified2, 0644)
if err1 != nil {
t.Errorf("error writing to file: %v", err2)
}
us.ReloadConfigs()
val2 := us.Get("wap", "User")
if val2 != "modified2" {
t.Errorf("expected to find User modified2, got %q", val2)
}
}
var includeFile = []byte(`
# This host should not exist, so we can use it for test purposes / it won't
# interfere with any other configurations.
+4
View File
@@ -0,0 +1,4 @@
Host wap
User modified2
KexAlgorithms diffie-hellman-group1-sha1