main: allow overriding host-based discovery in CLI config
For situations where the default network-based discovery is inappropriate or inconvenient, this allows users to provide a hard-coded discovery document for a particular hostname in the CLI config. This is a new config block, rather than combined with the existing "credentials" block, because credentials should ideally live in separate files from other config so that they can be managed more carefully. However, this new "host" block _is_ designed to have room for additional host-specific configuration _other than_ credentials in future, which might include TLS certificate overrides or other such things used during the discovery step.
This commit is contained in:
parent
74180229d0
commit
e9816c60f1
|
@ -39,6 +39,15 @@ func initCommands(config *Config) {
|
||||||
credsSrc := credentialsSource(config)
|
credsSrc := credentialsSource(config)
|
||||||
services := disco.NewDisco()
|
services := disco.NewDisco()
|
||||||
services.SetCredentialsSource(credsSrc)
|
services.SetCredentialsSource(credsSrc)
|
||||||
|
for userHost, hostConfig := range config.Hosts {
|
||||||
|
host, err := svchost.ForComparison(userHost)
|
||||||
|
if err != nil {
|
||||||
|
// We expect the config was already validated by the time we get
|
||||||
|
// here, so we'll just ignore invalid hostnames.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
services.ForceHostServices(host, hostConfig.Services)
|
||||||
|
}
|
||||||
|
|
||||||
meta := command.Meta{
|
meta := command.Meta{
|
||||||
Color: true,
|
Color: true,
|
||||||
|
|
29
config.go
29
config.go
|
@ -32,10 +32,19 @@ type Config struct {
|
||||||
// avoid repeatedly re-downloading over the Internet.
|
// avoid repeatedly re-downloading over the Internet.
|
||||||
PluginCacheDir string `hcl:"plugin_cache_dir"`
|
PluginCacheDir string `hcl:"plugin_cache_dir"`
|
||||||
|
|
||||||
|
Hosts map[string]*ConfigHost `hcl:"host"`
|
||||||
|
|
||||||
Credentials map[string]map[string]interface{} `hcl:"credentials"`
|
Credentials map[string]map[string]interface{} `hcl:"credentials"`
|
||||||
CredentialsHelpers map[string]*ConfigCredentialsHelper `hcl:"credentials_helper"`
|
CredentialsHelpers map[string]*ConfigCredentialsHelper `hcl:"credentials_helper"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConfigHost is the structure of the "host" nested block within the CLI
|
||||||
|
// configuration, which can be used to override the default service host
|
||||||
|
// discovery behavior for a particular hostname.
|
||||||
|
type ConfigHost struct {
|
||||||
|
Services map[string]interface{} `hcl:"services"`
|
||||||
|
}
|
||||||
|
|
||||||
// ConfigCredentialsHelper is the structure of the "credentials_helper"
|
// ConfigCredentialsHelper is the structure of the "credentials_helper"
|
||||||
// nested block within the CLI configuration.
|
// nested block within the CLI configuration.
|
||||||
type ConfigCredentialsHelper struct {
|
type ConfigCredentialsHelper struct {
|
||||||
|
@ -204,6 +213,16 @@ func (c *Config) Validate() tfdiags.Diagnostics {
|
||||||
// to give proper source references to any errors. We should improve
|
// to give proper source references to any errors. We should improve
|
||||||
// on this when we change the CLI config parser to use HCL2.
|
// on this when we change the CLI config parser to use HCL2.
|
||||||
|
|
||||||
|
// Check that all "host" blocks have valid hostnames.
|
||||||
|
for givenHost := range c.Hosts {
|
||||||
|
_, err := svchost.ForComparison(givenHost)
|
||||||
|
if err != nil {
|
||||||
|
diags = diags.Append(
|
||||||
|
fmt.Errorf("The host %q block has an invalid hostname: %s", givenHost, err),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check that all "credentials" blocks have valid hostnames.
|
// Check that all "credentials" blocks have valid hostnames.
|
||||||
for givenHost := range c.Credentials {
|
for givenHost := range c.Credentials {
|
||||||
_, err := svchost.ForComparison(givenHost)
|
_, err := svchost.ForComparison(givenHost)
|
||||||
|
@ -256,6 +275,16 @@ func (c1 *Config) Merge(c2 *Config) *Config {
|
||||||
result.PluginCacheDir = c2.PluginCacheDir
|
result.PluginCacheDir = c2.PluginCacheDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (len(c1.Hosts) + len(c2.Hosts)) > 0 {
|
||||||
|
result.Hosts = make(map[string]*ConfigHost)
|
||||||
|
for name, host := range c1.Hosts {
|
||||||
|
result.Hosts[name] = host
|
||||||
|
}
|
||||||
|
for name, host := range c2.Hosts {
|
||||||
|
result.Hosts[name] = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (len(c1.Credentials) + len(c2.Credentials)) > 0 {
|
if (len(c1.Credentials) + len(c2.Credentials)) > 0 {
|
||||||
result.Credentials = make(map[string]map[string]interface{})
|
result.Credentials = make(map[string]map[string]interface{})
|
||||||
for host, creds := range c1.Credentials {
|
for host, creds := range c1.Credentials {
|
||||||
|
|
|
@ -54,6 +54,27 @@ func TestLoadConfig_env(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadConfig_hosts(t *testing.T) {
|
||||||
|
got, diags := loadConfigFile(filepath.Join(fixtureDir, "hosts"))
|
||||||
|
if len(diags) != 0 {
|
||||||
|
t.Fatalf("%s", diags.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
want := &Config{
|
||||||
|
Hosts: map[string]*ConfigHost{
|
||||||
|
"example.com": {
|
||||||
|
Services: map[string]interface{}{
|
||||||
|
"modules.v1": "https://example.com/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadConfig_credentials(t *testing.T) {
|
func TestLoadConfig_credentials(t *testing.T) {
|
||||||
got, err := loadConfigFile(filepath.Join(fixtureDir, "credentials"))
|
got, err := loadConfigFile(filepath.Join(fixtureDir, "credentials"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -95,6 +116,22 @@ func TestConfigValidate(t *testing.T) {
|
||||||
&Config{},
|
&Config{},
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
|
"host good": {
|
||||||
|
&Config{
|
||||||
|
Hosts: map[string]*ConfigHost{
|
||||||
|
"example.com": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
"host with bad hostname": {
|
||||||
|
&Config{
|
||||||
|
Hosts: map[string]*ConfigHost{
|
||||||
|
"example..com": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1, // host block has invalid hostname
|
||||||
|
},
|
||||||
"credentials good": {
|
"credentials good": {
|
||||||
&Config{
|
&Config{
|
||||||
Credentials: map[string]map[string]interface{}{
|
Credentials: map[string]map[string]interface{}{
|
||||||
|
@ -157,6 +194,13 @@ func TestConfig_Merge(t *testing.T) {
|
||||||
"local": "local",
|
"local": "local",
|
||||||
"remote": "bad",
|
"remote": "bad",
|
||||||
},
|
},
|
||||||
|
Hosts: map[string]*ConfigHost{
|
||||||
|
"example.com": {
|
||||||
|
Services: map[string]interface{}{
|
||||||
|
"modules.v1": "http://example.com/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
Credentials: map[string]map[string]interface{}{
|
Credentials: map[string]map[string]interface{}{
|
||||||
"foo": {
|
"foo": {
|
||||||
"bar": "baz",
|
"bar": "baz",
|
||||||
|
@ -175,6 +219,13 @@ func TestConfig_Merge(t *testing.T) {
|
||||||
Provisioners: map[string]string{
|
Provisioners: map[string]string{
|
||||||
"remote": "remote",
|
"remote": "remote",
|
||||||
},
|
},
|
||||||
|
Hosts: map[string]*ConfigHost{
|
||||||
|
"example.net": {
|
||||||
|
Services: map[string]interface{}{
|
||||||
|
"modules.v1": "https://example.net/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
Credentials: map[string]map[string]interface{}{
|
Credentials: map[string]map[string]interface{}{
|
||||||
"fee": {
|
"fee": {
|
||||||
"bur": "bez",
|
"bur": "bez",
|
||||||
|
@ -195,6 +246,18 @@ func TestConfig_Merge(t *testing.T) {
|
||||||
"local": "local",
|
"local": "local",
|
||||||
"remote": "remote",
|
"remote": "remote",
|
||||||
},
|
},
|
||||||
|
Hosts: map[string]*ConfigHost{
|
||||||
|
"example.com": {
|
||||||
|
Services: map[string]interface{}{
|
||||||
|
"modules.v1": "http://example.com/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"example.net": {
|
||||||
|
Services: map[string]interface{}{
|
||||||
|
"modules.v1": "https://example.net/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
Credentials: map[string]map[string]interface{}{
|
Credentials: map[string]map[string]interface{}{
|
||||||
"foo": {
|
"foo": {
|
||||||
"bar": "baz",
|
"bar": "baz",
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
host "example.com" {
|
||||||
|
services = {
|
||||||
|
"modules.v1" = "https://example.com/",
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue