diff --git a/cmd/juju/commands/bootstrap.go b/cmd/juju/commands/bootstrap.go index 5b81402591ca..472d59c0addd 100644 --- a/cmd/juju/commands/bootstrap.go +++ b/cmd/juju/commands/bootstrap.go @@ -13,6 +13,7 @@ import ( "github.com/juju/cmd" "github.com/juju/errors" "github.com/juju/gnuflag" + "github.com/juju/schema" "github.com/juju/utils" "github.com/juju/utils/featureflag" "github.com/juju/version" @@ -372,14 +373,14 @@ func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { return errors.Trace(err) } + provider, err := environs.Provider(cloud.Type) + if err != nil { + return errors.Trace(err) + } // Custom clouds may not have explicitly declared support for any auth- // types, in which case we'll assume that they support everything that // the provider supports. if len(cloud.AuthTypes) == 0 { - provider, err := environs.Provider(cloud.Type) - if err != nil { - return errors.Trace(err) - } for authType := range provider.CredentialSchemas() { cloud.AuthTypes = append(cloud.AuthTypes, authType) } @@ -452,9 +453,33 @@ func (c *bootstrapCommand) Run(ctx *cmd.Context) (resultErr error) { if err != nil { return errors.Trace(err) } + + // The provider may define some custom attributes specific + // to the provider. These will be added to the model config. + providerAttrs := make(map[string]interface{}) + if ps, ok := provider.(config.ConfigSource); ok { + for attr := range ps.ProviderConfig() { + if v, ok := userConfigAttrs[attr]; ok { + providerAttrs[attr] = v + } + } + fields := schema.FieldMap(ps.ProviderConfig(), ps.ProviderConfigDefaults()) + if coercedAttrs, err := fields.Coerce(providerAttrs, nil); err != nil { + return errors.Annotatef(err, "invalid attribute value(s) for %v cloud", cloud.Type) + } else { + providerAttrs = coercedAttrs.(map[string]interface{}) + } + } + logger.Debugf("provider attrs: %v", providerAttrs) for k, v := range userConfigAttrs { modelConfigAttrs[k] = v } + // Provider specific attributes are either already specified in model + // config (but may have been coerced), or were not present. Either way, + // copy them in. + for k, v := range providerAttrs { + modelConfigAttrs[k] = v + } bootstrapConfigAttrs := make(map[string]interface{}) controllerConfigAttrs := make(map[string]interface{}) // Based on the attribute names in clouds.yaml, create diff --git a/cmd/juju/commands/bootstrap_test.go b/cmd/juju/commands/bootstrap_test.go index e5eb585daaae..19f55ad68325 100644 --- a/cmd/juju/commands/bootstrap_test.go +++ b/cmd/juju/commands/bootstrap_test.go @@ -261,6 +261,10 @@ func (s *BootstrapSuite) run(c *gc.C, test bootstrapTest) testing.Restorer { "type": "dummy", "default-series": "raring", "authorized-keys": "public auth key\n", + // Dummy provider defaults + "broken": "", + "secret": "pork", + "controller": false, } for k, v := range config.ConfigDefaults() { if _, ok := expected[k]; !ok { @@ -1157,7 +1161,7 @@ func (s *BootstrapSuite) TestBootstrapConfigFile(c *gc.C) { c, s.newBootstrapCommand(), "ctrl", "dummy", "--config", configFile, ) - c.Assert(err, gc.ErrorMatches, `controller: expected bool, got string.*`) + c.Assert(err, gc.ErrorMatches, `invalid attribute value\(s\) for dummy cloud: controller: expected bool, got string.*`) } func (s *BootstrapSuite) TestBootstrapMultipleConfigFiles(c *gc.C) { @@ -1210,9 +1214,9 @@ func (s *BootstrapSuite) TestBootstrapCloudConfigAndAdHoc(c *gc.C) { "--auto-upgrade", // Configuration specified on the command line overrides // anything specified in files, no matter what the order. - "--config", "controller=false", + "--config", "controller=not-a-bool", ) - c.Assert(err, gc.ErrorMatches, "failed to bootstrap model: dummy.Bootstrap is broken") + c.Assert(err, gc.ErrorMatches, `invalid attribute value\(s\) for dummy cloud: controller: expected bool, got .*`) } func (s *BootstrapSuite) TestBootstrapPrintClouds(c *gc.C) { diff --git a/environs/config/config.go b/environs/config/config.go index 75c8297db179..5bcf14b17518 100644 --- a/environs/config/config.go +++ b/environs/config/config.go @@ -1017,7 +1017,7 @@ func (cfg *Config) ValidateUnknownAttrs(fields schema.Fields, defaults schema.De if fields[name] == nil { if val, isString := value.(string); isString && val != "" { // only warn about attributes with non-empty string values - logger.Errorf("unknown config field %q", name) + logger.Warningf("unknown config field %q", name) } result[name] = value } diff --git a/environs/config/source.go b/environs/config/source.go index d817deb9ad4a..e60d0faf2b2d 100644 --- a/environs/config/source.go +++ b/environs/config/source.go @@ -3,6 +3,10 @@ package config +import ( + "github.com/juju/schema" +) + // These constants define named sources of model config attributes. // After a call to UpdateModelConfig, any attributes added/removed // will have a source of JujuModelConfigSource. @@ -42,3 +46,15 @@ func (c ConfigValues) AllAttrs() map[string]interface{} { } return result } + +// ConfigSource instances provide information on config attributes +// and the default attribute values. +type ConfigSource interface { + // ProviderConfig returns extra config attributes specific + // to this provider only. + ProviderConfig() schema.Fields + + // ProviderConfigDefaults returns the default values for the + // provider specific config attributes. + ProviderConfigDefaults() schema.Defaults +} diff --git a/provider/dummy/environs.go b/provider/dummy/environs.go index fac9ba0895bd..dad0b75c0b7e 100644 --- a/provider/dummy/environs.go +++ b/provider/dummy/environs.go @@ -509,6 +509,20 @@ func (p *environProvider) Schema() environschema.Fields { return fields } +var _ config.ConfigSource = (*environProvider)(nil) + +// ProviderConfig returns extra config attributes specific +// to this provider only. +func (p environProvider) ProviderConfig() schema.Fields { + return configFields +} + +// ProviderConfigDefaults returns the default values for the +// provider specific config attributes. +func (p environProvider) ProviderConfigDefaults() schema.Defaults { + return configDefaults +} + func (p *environProvider) CredentialSchemas() map[cloud.AuthType]cloud.CredentialSchema { return map[cloud.AuthType]cloud.CredentialSchema{cloud.EmptyAuthType: {}} } diff --git a/provider/ec2/config.go b/provider/ec2/config.go index 4ec17dde28c7..61533c77906a 100644 --- a/provider/ec2/config.go +++ b/provider/ec2/config.go @@ -71,6 +71,18 @@ func (environProvider) Schema() environschema.Fields { return fields } +// ProviderConfig returns extra config attributes specific +// to this provider only. +func (p environProvider) ProviderConfig() schema.Fields { + return configFields +} + +// ProviderConfigDefaults returns the default values for the +// provider specific config attributes. +func (p environProvider) ProviderConfigDefaults() schema.Defaults { + return configDefaults +} + func validateConfig(cfg, old *config.Config) (*environConfig, error) { // Check for valid changes for the base config values. if err := config.Validate(cfg, old); err != nil { diff --git a/provider/ec2/environ_test.go b/provider/ec2/environ_test.go index da587af2ad2c..b9684e0b48ef 100644 --- a/provider/ec2/environ_test.go +++ b/provider/ec2/environ_test.go @@ -9,6 +9,7 @@ import ( "github.com/juju/juju/constraints" "github.com/juju/juju/environs" + "github.com/juju/juju/environs/config" "github.com/juju/juju/environs/simplestreams" "github.com/juju/juju/instance" "github.com/juju/juju/network" @@ -18,6 +19,7 @@ import ( // Ensure EC2 provider supports the expected interfaces, var ( _ environs.NetworkingEnviron = (*environ)(nil) + _ config.ConfigSource = (*environProvider)(nil) _ simplestreams.HasRegion = (*environ)(nil) _ state.Prechecker = (*environ)(nil) _ instance.Distributor = (*environ)(nil) diff --git a/provider/gce/provider.go b/provider/gce/provider.go index 1ab5d4e112ba..1f7904c53e5f 100644 --- a/provider/gce/provider.go +++ b/provider/gce/provider.go @@ -5,6 +5,7 @@ package gce import ( "github.com/juju/errors" + "github.com/juju/schema" "gopkg.in/juju/environschema.v1" "github.com/juju/juju/cloud" @@ -59,6 +60,18 @@ func (environProvider) Schema() environschema.Fields { return fields } +// ProviderConfig returns extra config attributes specific +// to this provider only. +func (p environProvider) ProviderConfig() schema.Fields { + return configFields +} + +// ProviderConfigDefaults returns the default values for the +// provider specific config attributes. +func (p environProvider) ProviderConfigDefaults() schema.Defaults { + return configDefaults +} + // UpgradeModelConfig is specified in the ModelConfigUpgrader interface. func (environProvider) UpgradeConfig(cfg *config.Config) (*config.Config, error) { return configWithDefaults(cfg) diff --git a/provider/gce/testing_test.go b/provider/gce/testing_test.go index 1d6f4a695c6e..7d2939668d4f 100644 --- a/provider/gce/testing_test.go +++ b/provider/gce/testing_test.go @@ -31,6 +31,11 @@ import ( coretools "github.com/juju/juju/tools" ) +// Ensure GCE provider supports the expected interfaces. +var ( + _ config.ConfigSource = (*environProvider)(nil) +) + // These values are fake GCE auth credentials for use in tests. const ( ClientName = "ba9876543210-0123456789abcdefghijklmnopqrstuv" diff --git a/provider/lxd/provider.go b/provider/lxd/provider.go index 5bbcf7a2298a..bcda241f8e2a 100644 --- a/provider/lxd/provider.go +++ b/provider/lxd/provider.go @@ -7,6 +7,7 @@ package lxd import ( "github.com/juju/errors" + "github.com/juju/schema" "gopkg.in/juju/environschema.v1" "github.com/juju/juju/cloud" @@ -95,3 +96,15 @@ func (environProvider) Schema() environschema.Fields { } return fields } + +// ProviderConfig returns extra config attributes specific +// to this provider only. +func (p environProvider) ProviderConfig() schema.Fields { + return configFields +} + +// ProviderConfigDefaults returns the default values for the +// provider specific config attributes. +func (p environProvider) ProviderConfigDefaults() schema.Defaults { + return configDefaults +} diff --git a/provider/lxd/testing_test.go b/provider/lxd/testing_test.go index b72faa39007f..0d237f5a6356 100644 --- a/provider/lxd/testing_test.go +++ b/provider/lxd/testing_test.go @@ -30,6 +30,11 @@ import ( "github.com/juju/version" ) +// Ensure LXD provider supports the expected interfaces. +var ( + _ config.ConfigSource = (*environProvider)(nil) +) + // These values are stub LXD client credentials for use in tests. const ( PublicKey = `-----BEGIN CERTIFICATE----- diff --git a/provider/maas/config.go b/provider/maas/config.go index c792fe4b26f0..96928271b5cf 100644 --- a/provider/maas/config.go +++ b/provider/maas/config.go @@ -47,6 +47,18 @@ func (maasEnvironProvider) Schema() environschema.Fields { return fields } +// ProviderConfig returns extra config attributes specific +// to this provider only. +func (p maasEnvironProvider) ProviderConfig() schema.Fields { + return configFields +} + +// ProviderConfigDefaults returns the default values for the +// provider specific config attributes. +func (p maasEnvironProvider) ProviderConfigDefaults() schema.Defaults { + return configDefaults +} + func (prov maasEnvironProvider) Validate(cfg, oldCfg *config.Config) (*config.Config, error) { // Validate base configuration change before validating MAAS specifics. err := config.Validate(cfg, oldCfg) diff --git a/provider/maas/config_test.go b/provider/maas/config_test.go index d3566e8b16ce..b7fdd0cb37ad 100644 --- a/provider/maas/config_test.go +++ b/provider/maas/config_test.go @@ -13,6 +13,11 @@ import ( "github.com/juju/juju/testing" ) +// Ensure MAAS provider supports the expected interfaces. +var ( + _ config.ConfigSource = (*maasEnvironProvider)(nil) +) + type configSuite struct { testing.BaseSuite } diff --git a/provider/openstack/config.go b/provider/openstack/config.go index 3fdcc9c4f468..693c78d176b4 100644 --- a/provider/openstack/config.go +++ b/provider/openstack/config.go @@ -27,6 +27,8 @@ var configSchema = environschema.Fields{ }, } +var configDefaults = schema.Defaults{} + var configFields = func() schema.Fields { fs, _, err := configSchema.ValidationSchema() if err != nil { @@ -69,6 +71,18 @@ func (EnvironProvider) Schema() environschema.Fields { return fields } +// ProviderConfig returns extra config attributes specific +// to this provider only. +func (p EnvironProvider) ProviderConfig() schema.Fields { + return configFields +} + +// ProviderConfigDefaults returns the default values for the +// provider specific config attributes. +func (p EnvironProvider) ProviderConfigDefaults() schema.Defaults { + return configDefaults +} + func (p EnvironProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { // Check for valid changes for the base config values. if err := config.Validate(cfg, old); err != nil { diff --git a/provider/openstack/provider.go b/provider/openstack/provider.go index 3883f741d827..8e867a824b3b 100644 --- a/provider/openstack/provider.go +++ b/provider/openstack/provider.go @@ -49,7 +49,10 @@ type EnvironProvider struct { FirewallerFactory FirewallerFactory } -var _ environs.EnvironProvider = (*EnvironProvider)(nil) +var ( + _ environs.EnvironProvider = (*EnvironProvider)(nil) + _ environs.ProviderSchema = (*EnvironProvider)(nil) +) var providerInstance *EnvironProvider = &EnvironProvider{ OpenstackCredentials{}, diff --git a/state/internal_test.go b/state/internal_test.go index 8c32af7bf4af..72a7ae38e60a 100644 --- a/state/internal_test.go +++ b/state/internal_test.go @@ -114,3 +114,7 @@ func (internalStatePolicy) InstanceDistributor() (instance.Distributor, error) { func (internalStatePolicy) StorageProviderRegistry() (storage.ProviderRegistry, error) { return provider.CommonStorageProviders(), nil } + +func (internalStatePolicy) ProviderConfigSource() (config.ConfigSource, error) { + return nil, errors.NotImplementedf("ConfigSource") +} diff --git a/state/modelconfig.go b/state/modelconfig.go index 095bf8ced6ed..399e5d71b9c4 100644 --- a/state/modelconfig.go +++ b/state/modelconfig.go @@ -5,6 +5,7 @@ package state import ( "github.com/juju/errors" + "github.com/juju/schema" "github.com/juju/juju/controller" "github.com/juju/juju/environs/config" @@ -283,7 +284,7 @@ type modelConfigSource struct { // overall config values, later values override earlier ones. func modelConfigSources(st *State) []modelConfigSource { return []modelConfigSource{ - {config.JujuDefaultSource, func() (attrValues, error) { return config.ConfigDefaults(), nil }}, + {config.JujuDefaultSource, st.defaultInheritedConfig}, {config.JujuControllerSource, st.controllerInheritedConfig}, {config.JujuControllerSource, st.regionInheritedConfig}, } @@ -295,6 +296,30 @@ const ( controllerInheritedSettingsGlobalKey = "controller" ) +// defaultInheritedConfig returns config values which are defined +// as defaults in either Juju or the state's environ provider. +func (st *State) defaultInheritedConfig() (attrValues, error) { + var defaults = make(map[string]interface{}) + for k, v := range config.ConfigDefaults() { + defaults[k] = v + } + providerDefaults, err := st.environsProviderConfigSource() + if errors.IsNotImplemented(err) { + return defaults, nil + } else if err != nil { + return nil, errors.Trace(err) + } + fields := schema.FieldMap(providerDefaults.ProviderConfig(), providerDefaults.ProviderConfigDefaults()) + if coercedAtts, err := fields.Coerce(defaults, nil); err != nil { + return nil, errors.Trace(err) + } else { + for k, v := range coercedAtts.(map[string]interface{}) { + defaults[k] = v + } + } + return defaults, nil +} + // controllerInheritedConfig returns the inherited config values // sourced from the local cloud config. func (st *State) controllerInheritedConfig() (attrValues, error) { diff --git a/state/modelconfig_test.go b/state/modelconfig_test.go index 621b75897f28..8f6814e7cdfe 100644 --- a/state/modelconfig_test.go +++ b/state/modelconfig_test.go @@ -39,6 +39,9 @@ func (s *ModelConfigSuite) SetUpTest(c *gc.C) { validator.RegisterUnsupported([]string{constraints.CpuPower}) return validator, nil } + s.policy.GetProviderConfigSource = func() (config.ConfigSource, error) { + return &statetesting.MockConfigSource{}, nil + } } func (s *ModelConfigSuite) TestAdditionalValidation(c *gc.C) { @@ -104,6 +107,7 @@ func (s *ModelConfigSuite) TestComposeNewModelConfig(c *gc.C) { c.Assert(err, jc.ErrorIsNil) expected := expectedCfg.AllAttrs() expected["apt-mirror"] = "http://cloud-mirror" + expected["providerAttr"] = "vulch" // config.New() adds logging-config so remove it. expected["logging-config"] = "" c.Assert(cfgAttrs, jc.DeepEquals, expected) @@ -117,18 +121,20 @@ func (s *ModelConfigSuite) TestUpdateModelConfigRejectsControllerConfig(c *gc.C) func (s *ModelConfigSuite) TestUpdateModelConfigRemoveInherited(c *gc.C) { attrs := map[string]interface{}{ - "apt-mirror": "http://different-mirror", + "apt-mirror": "http://different-mirror", // controller "arbitrary-key": "shazam!", + "providerAttr": "beef", // provider } err := s.State.UpdateModelConfig(attrs, nil, nil) c.Assert(err, jc.ErrorIsNil) - err = s.State.UpdateModelConfig(nil, []string{"apt-mirror", "arbitrary-key"}, nil) + err = s.State.UpdateModelConfig(nil, []string{"apt-mirror", "arbitrary-key", "providerAttr"}, nil) c.Assert(err, jc.ErrorIsNil) cfg, err := s.State.ModelConfig() c.Assert(err, jc.ErrorIsNil) allAttrs := cfg.AllAttrs() c.Assert(allAttrs["apt-mirror"], gc.Equals, "http://cloud-mirror") + c.Assert(allAttrs["providerAttr"], gc.Equals, "vulch") _, ok := allAttrs["arbitrary-key"] c.Assert(ok, jc.IsFalse) } @@ -159,20 +165,23 @@ func (s *ModelConfigSuite) TestUpdateModelConfigCoerce(c *gc.C) { func (s *ModelConfigSuite) TestUpdateModelConfigPreferredOverRemove(c *gc.C) { attrs := map[string]interface{}{ - "apt-mirror": "http://different-mirror", + "apt-mirror": "http://different-mirror", // controller "arbitrary-key": "shazam!", + "providerAttr": "beef", // provider } err := s.State.UpdateModelConfig(attrs, nil, nil) c.Assert(err, jc.ErrorIsNil) err = s.State.UpdateModelConfig(map[string]interface{}{ - "apt-mirror": "http://another-mirror", + "apt-mirror": "http://another-mirror", + "providerAttr": "pork", }, []string{"apt-mirror", "arbitrary-key"}, nil) c.Assert(err, jc.ErrorIsNil) cfg, err := s.State.ModelConfig() c.Assert(err, jc.ErrorIsNil) allAttrs := cfg.AllAttrs() c.Assert(allAttrs["apt-mirror"], gc.Equals, "http://another-mirror") + c.Assert(allAttrs["providerAttr"], gc.Equals, "pork") _, ok := allAttrs["arbitrary-key"] c.Assert(ok, jc.IsFalse) } diff --git a/state/policy.go b/state/policy.go index b20675771882..0222d7bd750d 100644 --- a/state/policy.go +++ b/state/policy.go @@ -4,8 +4,6 @@ package state import ( - "fmt" - "github.com/juju/errors" "github.com/juju/juju/constraints" @@ -32,6 +30,9 @@ type Policy interface { // Prechecker returns a Prechecker or an error. Prechecker() (Prechecker, error) + // ProviderConfigSource returns a config.ConfigSource or an error. + ProviderConfigSource() (config.ConfigSource, error) + // ConfigValidator returns a config.Validator or an error. ConfigValidator() (config.Validator, error) @@ -72,7 +73,7 @@ func (st *State) precheckInstance(series string, cons constraints.Value, placeme return err } if prechecker == nil { - return fmt.Errorf("policy returned nil prechecker without an error") + return errors.New("policy returned nil prechecker without an error") } return prechecker.PrecheckInstance(series, cons, placement) } @@ -89,7 +90,7 @@ func (st *State) constraintsValidator() (constraints.Validator, error) { } else if err != nil { return nil, err } else if validator == nil { - return nil, fmt.Errorf("policy returned nil constraints validator without an error") + return nil, errors.New("policy returned nil constraints validator without an error") } } else { validator = constraints.NewValidator() @@ -160,7 +161,7 @@ func (st *State) validate(cfg, old *config.Config) (valid *config.Config, err er return nil, err } if configValidator == nil { - return nil, fmt.Errorf("policy returned nil configValidator without an error") + return nil, errors.New("policy returned nil configValidator without an error") } return configValidator.Validate(cfg, old) } @@ -171,3 +172,10 @@ func (st *State) storageProviderRegistry() (storage.ProviderRegistry, error) { } return st.policy.StorageProviderRegistry() } + +func (st *State) environsProviderConfigSource() (config.ConfigSource, error) { + if st.policy == nil { + return nil, errors.NotImplementedf("config.ProviderConfigSource") + } + return st.policy.ProviderConfigSource() +} diff --git a/state/stateenvirons/policy.go b/state/stateenvirons/policy.go index 851105b18c9c..e00c564ff157 100644 --- a/state/stateenvirons/policy.go +++ b/state/stateenvirons/policy.go @@ -40,16 +40,19 @@ func (p environStatePolicy) Prechecker() (state.Prechecker, error) { // ConfigValidator implements state.Policy. func (p environStatePolicy) ConfigValidator() (config.Validator, error) { - model, err := p.st.Model() + return environProvider(p.st) +} + +// ProviderConfigSource implements state.Policy. +func (p environStatePolicy) ProviderConfigSource() (config.ConfigSource, error) { + provider, err := environProvider(p.st) if err != nil { - return nil, errors.Annotate(err, "getting model") + return nil, errors.Trace(err) } - cloud, err := p.st.Cloud(model.Cloud()) - if err != nil { - return nil, errors.Annotate(err, "getting cloud") + if cs, ok := provider.(config.ConfigSource); ok { + return cs, nil } - // EnvironProvider implements state.ConfigValidator. - return environs.Provider(cloud.Type) + return nil, errors.NotImplementedf("config.ConfigSource") } // ConstraintsValidator implements state.Policy. @@ -87,3 +90,16 @@ func (p environStatePolicy) StorageProviderRegistry() (storage.ProviderRegistry, func NewStorageProviderRegistry(env environs.Environ) storage.ProviderRegistry { return storage.ChainedProviderRegistry{env, provider.CommonStorageProviders()} } + +func environProvider(st *state.State) (environs.EnvironProvider, error) { + model, err := st.Model() + if err != nil { + return nil, errors.Annotate(err, "getting model") + } + cloud, err := st.Cloud(model.Cloud()) + if err != nil { + return nil, errors.Annotate(err, "getting cloud") + } + // EnvironProvider implements state.ConfigValidator. + return environs.Provider(cloud.Type) +} diff --git a/state/testing/policy.go b/state/testing/policy.go index 739bb4bd3aa3..91052a0f9023 100644 --- a/state/testing/policy.go +++ b/state/testing/policy.go @@ -5,6 +5,8 @@ package testing import ( "github.com/juju/errors" + "github.com/juju/schema" + "gopkg.in/juju/environschema.v1" "github.com/juju/juju/constraints" "github.com/juju/juju/environs/config" @@ -16,6 +18,7 @@ import ( type MockPolicy struct { GetPrechecker func() (state.Prechecker, error) GetConfigValidator func() (config.Validator, error) + GetProviderConfigSource func() (config.ConfigSource, error) GetConstraintsValidator func() (constraints.Validator, error) GetInstanceDistributor func() (instance.Distributor, error) GetStorageProviderRegistry func() (storage.ProviderRegistry, error) @@ -55,3 +58,31 @@ func (p *MockPolicy) StorageProviderRegistry() (storage.ProviderRegistry, error) } return nil, errors.NotImplementedf("StorageProviderRegistry") } + +func (p *MockPolicy) ProviderConfigSource() (config.ConfigSource, error) { + if p.GetProviderConfigSource != nil { + return p.GetProviderConfigSource() + } + return nil, errors.NotImplementedf("ProviderConfigSource") +} + +type MockConfigSource struct{} + +func (m *MockConfigSource) ProviderConfig() schema.Fields { + configSchema := environschema.Fields{ + "providerAttr": { + Type: environschema.Tstring, + }, + } + fs, _, err := configSchema.ValidationSchema() + if err != nil { + panic(err) + } + return fs +} + +func (m *MockConfigSource) ProviderConfigDefaults() schema.Defaults { + return schema.Defaults{ + "providerAttr": "vulch", + } +}