diff --git a/controllers/depresolver/depresolver.go b/controllers/depresolver/depresolver.go index 35c195c23..2ee36c932 100644 --- a/controllers/depresolver/depresolver.go +++ b/controllers/depresolver/depresolver.go @@ -4,7 +4,6 @@ // - provides predefined values when configuration is missing // - validates configuration // - executes once -// TODO: Add the rest of configuration to be resolved package depresolver import ( @@ -14,17 +13,57 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -//Config holds operator configuration +// EdgeDNSType specifies to which edge DNS is k8gb connecting +type EdgeDNSType int + +const ( + // DNSTypeNoEdgeDNS is default DNSType. Is used during integration testing when no edgeDNS provider exists + DNSTypeNoEdgeDNS EdgeDNSType = 1 << iota + // DNSTypeInfoblox type + DNSTypeInfoblox + // DNSTypeRoute53 type + DNSTypeRoute53 +) + +//Infoblox configuration +// TODO: consider to make this private after refactor +type Infoblox struct { + // Host + Host string + // Version + Version string + // Port + Port int + // Username + Username string + // Password + Password string +} + +// Config is operator configuration returned by depResolver type Config struct { - //Reschedule of Reconcile loop to pickup external Gslb targets + // Reschedule of Reconcile loop to pickup external Gslb targets ReconcileRequeueSeconds int - // Cluster Geo Tag to determine specific location + // ClusterGeoTag to determine specific location ClusterGeoTag string - // Route53 switch + // ExtClustersGeoTags to identify clusters in other locations in format separated by comma. i.e.: "eu,uk,us" + ExtClustersGeoTags []string + // EdgeDNSType is READONLY and is set automatically by configuration + EdgeDNSType EdgeDNSType + // EdgeDNSServer + EdgeDNSServer string + // EdgeDNSZone main zone which would contain gslb zone to delegate; e.g. example.com + EdgeDNSZone string + // DNSZone controlled by gslb; e.g. cloud.example.com + DNSZone string + // DNSTypeRoute53 switch + // TODO: hide for depresolver subscriber as depresolver retrieves EdgeDNSType. Maybe we can change configuration and set EdgeDNSType directly instead of DNSTypeRoute53 boolean Route53Enabled bool + // Infoblox configuration + Infoblox Infoblox } -//DependencyResolver resolves configuration for GSLB +// DependencyResolver resolves configuration for GSLB type DependencyResolver struct { client client.Client config *Config @@ -35,13 +74,7 @@ type DependencyResolver struct { errorSpec error } -const ( - lessOrEqualToZeroErrorMessage = "\"%s is less or equal to zero\"" - lessThanZeroErrorMessage = "\"%s is less than zero\"" - doesNotMatchRegexMessage = "\"%s does not match /%s/ regexp rule\"" -) - -//NewDependencyResolver returns a new depresolver.DependencyResolver +// NewDependencyResolver returns a new depresolver.DependencyResolver func NewDependencyResolver(context context.Context, client client.Client) *DependencyResolver { resolver := new(DependencyResolver) resolver.client = client diff --git a/controllers/depresolver/depresolver_config.go b/controllers/depresolver/depresolver_config.go index 336d55dcb..6b8fa34f4 100644 --- a/controllers/depresolver/depresolver_config.go +++ b/controllers/depresolver/depresolver_config.go @@ -2,15 +2,25 @@ package depresolver import ( "fmt" - "regexp" "github.com/AbsaOSS/k8gb/controllers/internal/env" ) +// Environment variables keys const ( - reconcileRequeueSecondsKey = "RECONCILE_REQUEUE_SECONDS" - clusterGeoTagKey = "CLUSTER_GEO_TAG" - route53EnabledKey = "ROUTE53_ENABLED" + ReconcileRequeueSecondsKey = "RECONCILE_REQUEUE_SECONDS" + ClusterGeoTagKey = "CLUSTER_GEO_TAG" + ExtClustersGeoTagsKey = "EXT_GSLB_CLUSTERS_GEO_TAGS" + Route53EnabledKey = "ROUTE53_ENABLED" + EdgeDNSServerKey = "EDGE_DNS_SERVER" + EdgeDNSZoneKey = "EDGE_DNS_ZONE" + DNSZoneKey = "DNS_ZONE" + InfobloxGridHostKey = "INFOBLOX_GRID_HOST" + InfobloxVersionKey = "INFOBLOX_WAPI_VERSION" + InfobloxPortKey = "INFOBLOX_WAPI_PORT" + InfobloxUsernameKey = "EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME" + // #nosec G101; ignore false positive gosec; see: https://securego.io/docs/rules/g101.html + InfobloxPasswordKey = "EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD" ) // ResolveOperatorConfig executes once. It reads operator's configuration @@ -18,23 +28,92 @@ const ( func (dr *DependencyResolver) ResolveOperatorConfig() (*Config, error) { dr.onceConfig.Do(func() { dr.config = &Config{} - // set predefined values when not read from the environment variables - dr.config.ReconcileRequeueSeconds, _ = env.GetEnvAsIntOrFallback(reconcileRequeueSecondsKey, 30) - dr.config.ClusterGeoTag = env.GetEnvAsStringOrFallback(clusterGeoTagKey, "unset") - dr.config.Route53Enabled = env.GetEnvAsBoolOrFallback(route53EnabledKey, false) + dr.config.ReconcileRequeueSeconds, _ = env.GetEnvAsIntOrFallback(ReconcileRequeueSecondsKey, 30) + dr.config.ClusterGeoTag = env.GetEnvAsStringOrFallback(ClusterGeoTagKey, "") + dr.config.ExtClustersGeoTags = env.GetEnvAsArrayOfStringsOrFallback(ExtClustersGeoTagsKey, []string{}) + dr.config.Route53Enabled = env.GetEnvAsBoolOrFallback(Route53EnabledKey, false) + dr.config.EdgeDNSServer = env.GetEnvAsStringOrFallback(EdgeDNSServerKey, "") + dr.config.EdgeDNSZone = env.GetEnvAsStringOrFallback(EdgeDNSZoneKey, "") + dr.config.DNSZone = env.GetEnvAsStringOrFallback(DNSZoneKey, "") + dr.config.Infoblox.Host = env.GetEnvAsStringOrFallback(InfobloxGridHostKey, "") + dr.config.Infoblox.Version = env.GetEnvAsStringOrFallback(InfobloxVersionKey, "") + dr.config.Infoblox.Port, _ = env.GetEnvAsIntOrFallback(InfobloxPortKey, 0) + dr.config.Infoblox.Username = env.GetEnvAsStringOrFallback(InfobloxUsernameKey, "") + dr.config.Infoblox.Password = env.GetEnvAsStringOrFallback(InfobloxPasswordKey, "") dr.errorConfig = dr.validateConfig(dr.config) + dr.config.EdgeDNSType = getEdgeDNSType(dr.config) }) return dr.config, dr.errorConfig } -func (dr *DependencyResolver) validateConfig(config *Config) error { - if config.ReconcileRequeueSeconds <= 0 { - return fmt.Errorf(lessOrEqualToZeroErrorMessage, "ReconcileRequeueSeconds") +func (dr *DependencyResolver) validateConfig(config *Config) (err error) { + err = field("reconcileRequeueSeconds", config.ReconcileRequeueSeconds).isHigherThanZero().err + if err != nil { + return err } - geoTagRegexString := "^[a-zA-Z\\-\\d]*$" - geoTagRegex, _ := regexp.Compile(geoTagRegexString) - if !geoTagRegex.Match([]byte(config.ClusterGeoTag)) { - return fmt.Errorf(doesNotMatchRegexMessage, "ClusterGeoTag", geoTagRegexString) + err = field("clusterGeoTag", config.ClusterGeoTag).isNotEmpty().matchRegexp(geoTagRegex).err + if err != nil { + return err + } + err = field("extClusterGeoTags", config.ExtClustersGeoTags).hasItems().hasUniqueItems().err + if err != nil { + return err + } + for i, geoTag := range config.ExtClustersGeoTags { + err = field(fmt.Sprintf("extClustersGeoTags[%v]", i), geoTag).isNotEmpty().matchRegexp(geoTagRegex).isNotEqualTo(config.ClusterGeoTag).err + if err != nil { + return err + } + } + err = field("edgeDNSServer", config.EdgeDNSServer).isNotEmpty().matchRegexps(hostNameRegex, ipAddressRegex).err + if err != nil { + return err + } + err = field("edgeDNSZone", config.EdgeDNSZone).isNotEmpty().matchRegexp(hostNameRegex).err + if err != nil { + return err + } + err = field("DNSZone", config.DNSZone).isNotEmpty().matchRegexp(hostNameRegex).err + if err != nil { + return err + } + // do full Infoblox validation only in case that Host exists + if isNotEmpty(config.Infoblox.Host) { + err = field("InfobloxGridHost", config.Infoblox.Host).matchRegexps(hostNameRegex, ipAddressRegex).err + if err != nil { + return err + } + err = field("InfobloxVersion", config.Infoblox.Version).isNotEmpty().matchRegexp(versionNumberRegex).err + if err != nil { + return err + } + err = field("InfobloxPort", config.Infoblox.Port).isHigherThanZero().isLessOrEqualTo(65535).err + if err != nil { + return err + } + err = field("InfobloxUsername", config.Infoblox.Username).isNotEmpty().err + if err != nil { + return err + } + err = field("InfobloxPassword", config.Infoblox.Password).isNotEmpty().err + if err != nil { + return err + } } return nil } + +// getEdgeDNSType contains logic retrieving EdgeDNSType +func getEdgeDNSType(config *Config) EdgeDNSType { + var t = DNSTypeNoEdgeDNS + if config.Route53Enabled { + t = t | DNSTypeRoute53 + } + if isNotEmpty(config.Infoblox.Host) { + t = t | DNSTypeInfoblox + } + if t > DNSTypeNoEdgeDNS { + t = t - DNSTypeNoEdgeDNS + } + return t +} diff --git a/controllers/depresolver/depresolver_spec.go b/controllers/depresolver/depresolver_spec.go index cf8087d87..fd18ded4d 100644 --- a/controllers/depresolver/depresolver_spec.go +++ b/controllers/depresolver/depresolver_spec.go @@ -1,8 +1,6 @@ package depresolver import ( - "fmt" - k8gbv1beta1 "github.com/AbsaOSS/k8gb/api/v1beta1" ) @@ -32,12 +30,14 @@ func (dr *DependencyResolver) ResolveGslbSpec(gslb *k8gbv1beta1.Gslb) error { return dr.errorSpec } -func (dr *DependencyResolver) validateSpec(strategy *k8gbv1beta1.Strategy) error { - if strategy.DNSTtlSeconds < 0 { - return fmt.Errorf(lessThanZeroErrorMessage, "DNSTtlSeconds") +func (dr *DependencyResolver) validateSpec(strategy *k8gbv1beta1.Strategy) (err error) { + err = field("DNSTtlSeconds", strategy.DNSTtlSeconds).isHigherOrEqualToZero().err + if err != nil { + return } - if strategy.SplitBrainThresholdSeconds < 0 { - return fmt.Errorf(lessThanZeroErrorMessage, "SplitBrainThresholdSeconds") + err = field("SplitBrainThresholdSeconds", strategy.SplitBrainThresholdSeconds).isHigherOrEqualToZero().err + if err != nil { + return } - return nil + return } diff --git a/controllers/depresolver/depresolver_test.go b/controllers/depresolver/depresolver_test.go index a03ca6baa..f3fafad7f 100644 --- a/controllers/depresolver/depresolver_test.go +++ b/controllers/depresolver/depresolver_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "strconv" + "strings" "testing" k8gbv1beta1 "github.com/AbsaOSS/k8gb/api/v1beta1" @@ -19,258 +20,813 @@ import ( externaldns "sigs.k8s.io/external-dns/endpoint" ) -var defaultConfig = Config{30, "unset", false} +var predefinedConfig = Config{ + 30, + "us", + []string{"uk", "eu"}, + DNSTypeInfoblox, + "cloud.example.com", + "8.8.8.8", + "example.com", + false, + Infoblox{ + "Infoblox.host.com", + "0.0.3", + 443, + "Infoblox", + "secret", + }, +} func TestResolveSpecWithFilledFields(t *testing.T) { - //arrange + // arrange cl, gslb := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act err := resolver.ResolveGslbSpec(gslb) - //assert + // assert assert.NoError(t, err) assert.Equal(t, 35, gslb.Spec.Strategy.DNSTtlSeconds) assert.Equal(t, 305, gslb.Spec.Strategy.SplitBrainThresholdSeconds) } func TestResolveSpecWithoutFields(t *testing.T) { - //arrange + // arrange cl, gslb := getTestContext("./testdata/free_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act err := resolver.ResolveGslbSpec(gslb) - //assert + // assert assert.NoError(t, err) assert.Equal(t, predefinedStrategy.DNSTtlSeconds, gslb.Spec.Strategy.DNSTtlSeconds) assert.Equal(t, predefinedStrategy.SplitBrainThresholdSeconds, gslb.Spec.Strategy.SplitBrainThresholdSeconds) } func TestResolveSpecWithZeroSplitBrain(t *testing.T) { - //arrange + // arrange cl, gslb := getTestContext("./testdata/filled_omitempty_with_zero_splitbrain.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act err := resolver.ResolveGslbSpec(gslb) - //assert + // assert assert.NoError(t, err) assert.Equal(t, 35, gslb.Spec.Strategy.DNSTtlSeconds) assert.Equal(t, predefinedStrategy.SplitBrainThresholdSeconds, gslb.Spec.Strategy.SplitBrainThresholdSeconds) } func TestResolveSpecWithEmptyFields(t *testing.T) { - //arrange + // arrange cl, gslb := getTestContext("./testdata/invalid_omitempty_empty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act err := resolver.ResolveGslbSpec(gslb) - //assert + // assert assert.NoError(t, err) assert.Equal(t, predefinedStrategy.DNSTtlSeconds, gslb.Spec.Strategy.DNSTtlSeconds) assert.Equal(t, predefinedStrategy.SplitBrainThresholdSeconds, gslb.Spec.Strategy.SplitBrainThresholdSeconds) } func TestResolveSpecWithNegativeFields(t *testing.T) { - //arrange + // arrange cl, gslb := getTestContext("./testdata/invalid_omitempty_negative.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act err := resolver.ResolveGslbSpec(gslb) - //assert + // assert assert.Error(t, err) } func TestSpecRunOnce(t *testing.T) { - //arrange + // arrange cl, gslb := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act err1 := resolver.ResolveGslbSpec(gslb) gslb.Spec.Strategy.DNSTtlSeconds = -100 err2 := resolver.ResolveGslbSpec(gslb) - //assert + // assert assert.NoError(t, err1) // err2 would not be empty assert.NoError(t, err2) } -func TestResolveConfigWithOneValidEnv(t *testing.T) { - //arrange +func TestResolveConfigWithMultipleInvalidEnv(t *testing.T) { + // arrange defer cleanup() - cl, _ := getTestContext("./testdata/filled_omitempty.yaml") - resolver := NewDependencyResolver(context.TODO(), cl) - expected := Config{50, "unset", false} - _ = os.Setenv(reconcileRequeueSecondsKey, strconv.Itoa(expected.ReconcileRequeueSeconds)) - //act - config, err := resolver.ResolveOperatorConfig() - //assert - assert.NoError(t, err) - assert.Equal(t, expected, *config) + expected := predefinedConfig + expected.EdgeDNSZone = "" + expected.EdgeDNSServer = "" + expected.ExtClustersGeoTags = []string{} + arrangeVariablesAndAssert(t, expected, assert.Error) } -func TestResolveConfigWithoutEnv(t *testing.T) { - //arrange +func TestResolveConfigWithoutEnvVarsSet(t *testing.T) { + // arrange defer cleanup() + defaultConfig := Config{} + defaultConfig.ReconcileRequeueSeconds = 30 + defaultConfig.EdgeDNSType = DNSTypeNoEdgeDNS + defaultConfig.ExtClustersGeoTags = []string{} cl, _ := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act config, err := resolver.ResolveOperatorConfig() - //assert - assert.NoError(t, err) + // assert + assert.Error(t, err) assert.Equal(t, defaultConfig, *config) } -func TestResolveConfigWithZeroReconcileRequeueSecondsSync(t *testing.T) { - //arrange +func TestResolveConfigWithReconcileRequeueSecondsSync(t *testing.T) { + // arrange defer cleanup() - _ = os.Setenv(reconcileRequeueSecondsKey, "0") - cl, _ := getTestContext("./testdata/filled_omitempty.yaml") - resolver := NewDependencyResolver(context.TODO(), cl) - //act - _, err := resolver.ResolveOperatorConfig() - //assert - assert.Error(t, err) + expected := predefinedConfig + expected.ReconcileRequeueSeconds = 3 + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) } func TestResolveConfigWithTextReconcileRequeueSecondsSync(t *testing.T) { - //arrange + // arrange defer cleanup() - _ = os.Setenv(reconcileRequeueSecondsKey, "invalid") + configureEnvVar(predefinedConfig) + _ = os.Setenv(ReconcileRequeueSecondsKey, "invalid") cl, _ := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act config, err := resolver.ResolveOperatorConfig() - //assert + // assert assert.NoError(t, err) - assert.Equal(t, defaultConfig, *config) + assert.Equal(t, predefinedConfig, *config) } func TestResolveConfigWithEmptyReconcileRequeueSecondsSync(t *testing.T) { - //arrange + // arrange defer cleanup() - _ = os.Setenv(reconcileRequeueSecondsKey, "") + configureEnvVar(predefinedConfig) + _ = os.Setenv(ReconcileRequeueSecondsKey, "") cl, _ := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act config, err := resolver.ResolveOperatorConfig() - //assert + // assert assert.NoError(t, err) - assert.Equal(t, defaultConfig, *config) + assert.Equal(t, predefinedConfig, *config) } func TestResolveConfigWithNegativeReconcileRequeueSecondsKey(t *testing.T) { - //arrange + // arrange defer cleanup() - _ = os.Setenv(reconcileRequeueSecondsKey, "-1") - cl, _ := getTestContext("./testdata/filled_omitempty.yaml") - resolver := NewDependencyResolver(context.TODO(), cl) - //act - _, err := resolver.ResolveOperatorConfig() - //assert - assert.Error(t, err) + expected := predefinedConfig + expected.ReconcileRequeueSeconds = -1 + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) } func TestResolveConfigWithZeroReconcileRequeueSecondsKey(t *testing.T) { - //arrange + // arrange defer cleanup() - _ = os.Setenv(reconcileRequeueSecondsKey, "0") - cl, _ := getTestContext("./testdata/filled_omitempty.yaml") - resolver := NewDependencyResolver(context.TODO(), cl) - //act - _, err := resolver.ResolveOperatorConfig() - //assert - assert.Error(t, err) + expected := predefinedConfig + expected.ReconcileRequeueSeconds = 0 + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) } func TestResolveConfigWithEmptyReconcileRequeueSecondsKey(t *testing.T) { - //arrange + // arrange defer cleanup() - _ = os.Setenv(reconcileRequeueSecondsKey, "") + configureEnvVar(predefinedConfig) + _ = os.Setenv(ReconcileRequeueSecondsKey, "") cl, _ := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act config, err := resolver.ResolveOperatorConfig() - //assert + // assert assert.NoError(t, err) - assert.Equal(t, defaultConfig, *config) + assert.Equal(t, predefinedConfig, *config) } -func TestResolveConfigWithMalformedGeoTag(t *testing.T) { - //arrange +func TestResolveConfigWithoutReconcileRequeueSecondsKey(t *testing.T) { + // arrange defer cleanup() - _ = os.Setenv(clusterGeoTagKey, "i.am.wrong??.") - cl, _ := getTestContext("./testdata/filled_omitempty.yaml") - resolver := NewDependencyResolver(context.TODO(), cl) - //act - _, err := resolver.ResolveOperatorConfig() - //assert - assert.Error(t, err) + // act,assert + arrangeVariablesAndAssert(t, predefinedConfig, assert.NoError, ReconcileRequeueSecondsKey) +} + +func TestResolveConfigWithMalformedGeoTag(t *testing.T) { + // arrange + for _, tag := range []string{"eu-west.1", "eu?", " ", "eu west1", "?/"} { + defer cleanup() + expected := predefinedConfig + expected.ClusterGeoTag = tag + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) + } } func TestResolveConfigWithProperGeoTag(t *testing.T) { - //arrange - defer cleanup() - _ = os.Setenv(clusterGeoTagKey, "eu-west-1") - cl, _ := getTestContext("./testdata/filled_omitempty.yaml") - resolver := NewDependencyResolver(context.TODO(), cl) - //act - _, err := resolver.ResolveOperatorConfig() - //assert - assert.NoError(t, err) + // arrange + for _, tag := range []string{"eu-west-1", "eu-west1", "us", "1", "US"} { + defer cleanup() + expected := predefinedConfig + expected.ClusterGeoTag = tag + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) + } } func TestConfigRunOnce(t *testing.T) { - //arrange + // arrange defer cleanup() + configureEnvVar(predefinedConfig) cl, _ := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act config1, err1 := resolver.ResolveOperatorConfig() - _ = os.Setenv(reconcileRequeueSecondsKey, "100") - //resolve again with new values + _ = os.Setenv(ReconcileRequeueSecondsKey, "100") + // resolve again with new values config2, err2 := resolver.ResolveOperatorConfig() - //assert + // assert assert.NoError(t, err1) - assert.Equal(t, defaultConfig, *config1) - //config2, err2 would be equal + assert.Equal(t, predefinedConfig, *config1) + // config2, err2 would be equal assert.NoError(t, err2) assert.Equal(t, *config1, *config2) } func TestResolveConfigWithMalformedRoute53Enabled(t *testing.T) { - //arrange + // arrange defer cleanup() - _ = os.Setenv(route53EnabledKey, "i.am.wrong??.") + configureEnvVar(predefinedConfig) + _ = os.Setenv(Route53EnabledKey, "i.am.wrong??.") cl, _ := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act config, err := resolver.ResolveOperatorConfig() - //assert + // assert assert.NoError(t, err) assert.Equal(t, false, config.Route53Enabled) } func TestResolveConfigWithProperRoute53Enabled(t *testing.T) { - //arrange + // arrange + defer cleanup() + expected := predefinedConfig + expected.Route53Enabled = true + expected.Infoblox.Host = "" + expected.EdgeDNSType = DNSTypeRoute53 + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestResolveConfigWithoutRoute53(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.Route53Enabled = false + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError, Route53EnabledKey) +} + +func TestResolveConfigWithEmptyRoute53(t *testing.T) { + // arrange defer cleanup() - _ = os.Setenv(route53EnabledKey, "true") + configureEnvVar(predefinedConfig) + _ = os.Setenv(Route53EnabledKey, "") cl, _ := getTestContext("./testdata/filled_omitempty.yaml") resolver := NewDependencyResolver(context.TODO(), cl) - //act + // act config, err := resolver.ResolveOperatorConfig() - //assert + // assert assert.NoError(t, err) - assert.Equal(t, true, config.Route53Enabled) + assert.Equal(t, false, config.Route53Enabled) } -func cleanup() { - if os.Unsetenv(reconcileRequeueSecondsKey) != nil { - panic(fmt.Errorf("cleanup %s", reconcileRequeueSecondsKey)) +func TestResolveConfigWithEmptyEdgeDnsServer(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSServer = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestResolveConfigWithNoEdgeDnsServer(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSServer = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, EdgeDNSServerKey) +} + +func TestResolveConfigWithEmptyIpAddressInEdgeDnsServer(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSServer = "22.147.90.2" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestResolveConfigWithHostnameEdgeDnsServer(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSServer = "server-nonprod.on.domain.l3.2l.com" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) + +} + +func TestResolveConfigWithInvalidHostnameEdgeDnsServer(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSServer = "https://server-nonprod.on.domain.l3.2l.com" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestResolveConfigWithInvalidIpAddressEdgeDnsServer(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSServer = "22.147.90.2." + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestResolveConfigWithEmptyEdgeDnsZone(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSZone = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestResolveConfigWithoutEdgeDnsZone(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSZone = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, EdgeDNSZoneKey) +} + +func TestResolveConfigWithHostnameEdgeDnsZone(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSZone = "company.2l.com" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestResolveConfigWithInvalidHostnameEdgeDnsZone(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSZone = "https://zone.com" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestResolveConfigWithValidHostnameDnsZone(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.DNSZone = "3l2.zo-ne.com" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestResolveConfigWithEmptyHostnameDnsZone(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.DNSZone = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestResolveConfigWithInvalidHostnameDnsZone(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.DNSZone = "dns-zo?ne" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestResolveConfigWithoutDnsZone(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.DNSZone = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, DNSZoneKey) +} + +func TestResolveEmptyExtGeoTags(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.DNSZone = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, DNSZoneKey) +} + +func TestResolveOneExtGeoTags(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.ExtClustersGeoTags = []string{"foo"} + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestResolveMultipleExtGeoTags(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.ExtClustersGeoTags = []string{"foo", "blah", "boom"} + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestResolveUnsetExtGeoTags(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.ExtClustersGeoTags = []string{} + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, ExtClustersGeoTagsKey) +} + +func TestResolveInvalidExtGeoTags(t *testing.T) { + // arrange + for _, arr := range [][]string{{"good-tag", ".wrong.tag?"}, {"", ""}} { + defer cleanup() + expected := predefinedConfig + expected.ExtClustersGeoTags = arr + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) + } +} + +func TestResolveGeoTagExistsWithinExtGeoTags(t *testing.T) { + // arrange + defer cleanup() + tag := "us-west1" + expected := predefinedConfig + expected.ClusterGeoTag = tag + expected.ExtClustersGeoTags = []string{"us-east1", tag} + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestResolveGeoTagWithRepeatingExtGeoTags(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.ExtClustersGeoTags = []string{"foo", "blah", "foo"} + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestRoute53IsEnabledAndInfobloxIsConfigured(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.Route53Enabled = true + expected.EdgeDNSType = DNSTypeRoute53 | DNSTypeInfoblox + expected.Infoblox.Host = "Infoblox.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestRoute53IsDisabledAndInfobloxIsNotConfigured(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeNoEdgeDNS + expected.Route53Enabled = false + expected.Infoblox.Host = "" + // act,assert + // that's how our integration tests are running + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestRoute53IsDisabledButInfobloxIsConfigured(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.Route53Enabled = false + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "Infoblox.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestRoute53IsEnabledButInfobloxIsNotConfigured(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.Route53Enabled = true + expected.EdgeDNSType = DNSTypeRoute53 + expected.Infoblox.Host = "" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestInfobloxGridHostIsEmpty(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeRoute53 + expected.Route53Enabled = true + expected.Infoblox.Host = "" + expected.Infoblox.Version = "" + expected.Infoblox.Port = 0 + expected.Infoblox.Username = "" + expected.Infoblox.Password = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestInfobloxGridHostIsNotEmpty(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestInfobloxGridHostIsNotEmptyButInfobloxPropsAreEmpty(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "" + expected.Infoblox.Port = 0 + expected.Infoblox.Username = "" + expected.Infoblox.Password = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestInfobloxGridHostIsEmptyButInfobloxPropsAreFilled(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeRoute53 + expected.Route53Enabled = true + expected.Infoblox.Host = "" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestInfobloxGridHostIsUnset(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeNoEdgeDNS + expected.Route53Enabled = false + expected.Infoblox.Host = "" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + // values are ignored and not validated + arrangeVariablesAndAssert(t, expected, assert.NoError, InfobloxGridHostKey) +} + +func TestInfobloxGridHostIsInvalid(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Route53Enabled = false + expected.Infoblox.Host = "dnfkjdnf kj" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestInfobloxVersionIsValid(t *testing.T) { + // arrange + defer cleanup() + // version can be empty! + for _, v := range []string{"0.0.1", "v0.0.1", "v0.0.0-patch1", "2.3.5-patch1"} { + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = v + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) + } +} + +func TestInfobloxVersionIsInvalid(t *testing.T) { + // arrange + defer cleanup() + for _, v := range []string{"0.1.*", "kkojo", "k12k", ""} { + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = v + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) + } +} + +func TestInfobloxVersionIsUnset(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, InfobloxVersionKey) +} + +func TestInvalidInfobloxPort(t *testing.T) { + // arrange + defer cleanup() + for _, p := range []int{-1, 0, 65536} { + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = p + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) + } +} + +func TestUnsetInfobloxPort(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 0 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, InfobloxPortKey) +} + +func TestValidInfobloxUserPasswordAndPort(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "infobloxUser" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.NoError) +} + +func TestEmptyInfobloxUser(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestUnsetInfobloxUser(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "" + expected.Infoblox.Password = "blah" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, InfobloxUsernameKey) +} + +func TestEmptyInfobloxPassword(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "infobloxUser" + expected.Infoblox.Password = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error) +} + +func TestUnsetInfobloxPassword(t *testing.T) { + // arrange + defer cleanup() + expected := predefinedConfig + expected.EdgeDNSType = DNSTypeInfoblox + expected.Infoblox.Host = "test.domain" + expected.Infoblox.Version = "0.0.1" + expected.Infoblox.Port = 443 + expected.Infoblox.Username = "foo" + expected.Infoblox.Password = "" + // act,assert + arrangeVariablesAndAssert(t, expected, assert.Error, InfobloxPasswordKey) +} + +// arrangeVariablesAndAssert sets string environment variables and asserts `expected` argument with ResolveOperatorConfig() output. The last parameter unsets the values +func arrangeVariablesAndAssert(t *testing.T, expected Config, errf func(t assert.TestingT, err error, msgAndArgs ...interface{}) bool, unset ...string) { + configureEnvVar(expected) + for _, v := range unset { + _ = os.Unsetenv(v) } - if os.Unsetenv(clusterGeoTagKey) != nil { - panic(fmt.Errorf("cleanup %s", clusterGeoTagKey)) + cl, _ := getTestContext("./testdata/filled_omitempty.yaml") + resolver := NewDependencyResolver(context.TODO(), cl) + // act + config, err := resolver.ResolveOperatorConfig() + // assert + assert.Equal(t, expected, *config) + errf(t, err) +} + +func cleanup() { + for _, s := range []string{ReconcileRequeueSecondsKey, ClusterGeoTagKey, ExtClustersGeoTagsKey, EdgeDNSZoneKey, DNSZoneKey, EdgeDNSServerKey, + Route53EnabledKey, InfobloxGridHostKey, InfobloxVersionKey, InfobloxPortKey, InfobloxUsernameKey, InfobloxPasswordKey} { + if os.Unsetenv(s) != nil { + panic(fmt.Errorf("cleanup %s", s)) + } } } +func configureEnvVar(config Config) { + _ = os.Setenv(ReconcileRequeueSecondsKey, strconv.Itoa(config.ReconcileRequeueSeconds)) + _ = os.Setenv(ClusterGeoTagKey, config.ClusterGeoTag) + _ = os.Setenv(ExtClustersGeoTagsKey, strings.Join(config.ExtClustersGeoTags, ",")) + _ = os.Setenv(EdgeDNSServerKey, config.EdgeDNSServer) + _ = os.Setenv(EdgeDNSZoneKey, config.EdgeDNSZone) + _ = os.Setenv(DNSZoneKey, config.DNSZone) + _ = os.Setenv(Route53EnabledKey, strconv.FormatBool(config.Route53Enabled)) + _ = os.Setenv(InfobloxGridHostKey, config.Infoblox.Host) + _ = os.Setenv(InfobloxVersionKey, config.Infoblox.Version) + _ = os.Setenv(InfobloxPortKey, strconv.Itoa(config.Infoblox.Port)) + _ = os.Setenv(InfobloxUsernameKey, config.Infoblox.Username) + _ = os.Setenv(InfobloxPasswordKey, config.Infoblox.Password) +} + func getTestContext(testData string) (client.Client, *k8gbv1beta1.Gslb) { // Create a fake client to mock API calls. var gslbYaml, err = ioutil.ReadFile(testData) diff --git a/controllers/depresolver/depresolver_validator.go b/controllers/depresolver/depresolver_validator.go new file mode 100644 index 000000000..b4d922dd0 --- /dev/null +++ b/controllers/depresolver/depresolver_validator.go @@ -0,0 +1,159 @@ +package depresolver + +import ( + "fmt" + "regexp" + "strings" +) + +const ( + // hostNameRegex allows cloud region formats; e.g. af-south-1 + geoTagRegex = "^[a-zA-Z\\-\\d]*$" + // hostNameRegex is valid as per RFC 1123 that allows hostname segments could start with a digit + hostNameRegex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$" + // ipAddressRegex matches valid IPv4 addresses + ipAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" + // versionNumberRegex matches version in formats 0.1.2, v0.1.2, v0.1.2-alpha + versionNumberRegex = "^(v){0,1}(0|(?:[1-9]\\d*))(?:\\.(0|(?:[1-9]\\d*))(?:\\.(0|(?:[1-9]\\d*)))?(?:\\-([\\w][\\w\\.\\-_]*))?)?$" +) + +// validator wrapper against field to be verified +type validator struct { + strValue string + strArr []string + intValue int + name string + err error +} + +// field creates validator +func field(name string, value interface{}) *validator { + validator := new(validator) + validator.name = name + switch v := value.(type) { + case int: + validator.intValue = v + case int8: + validator.intValue = int(v) + case int16: + validator.intValue = int(v) + case int32: + validator.intValue = int(v) + case int64: + validator.intValue = int(v) + case string: + validator.strValue = v + case []string: + validator.strArr = v + default: + // float32, float64, bool, interface{}, maps, slices, Custom Types + validator.err = fmt.Errorf("can't parse %v of type %T as int or string", v, v) + } + return validator +} + +func (v *validator) isNotEmpty() *validator { + if v.err != nil { + return v + } + if v.strValue == "" { + v.err = fmt.Errorf("%s is empty", v.name) + } + return v +} + +func (v *validator) matchRegexp(regex string) *validator { + if v.err != nil { + return v + } + if v.strValue == "" { + return v + } + r, _ := regexp.Compile(regex) + if !r.Match([]byte(v.strValue)) { + v.err = fmt.Errorf(`"%s" does not match given criteria. see: https://www.regextester.com (%s)`, v.strValue, regex) + } + return v +} + +// matchRegexps returns error if value is not matched by any of regexp +func (v *validator) matchRegexps(regex ...string) *validator { + if v.err != nil { + return v + } + for _, r := range regex { + v.err = nil + if v.matchRegexp(r).err == nil { + return v + } + } + return v +} + +func (v *validator) isHigherThanZero() *validator { + if v.err != nil { + return v + } + if v.intValue <= 0 { + v.err = fmt.Errorf(`"%s" is less or equal to zero`, v.name) + } + return v +} + +func (v *validator) isHigherOrEqualToZero() *validator { + if v.err != nil { + return v + } + if v.intValue < 0 { + v.err = fmt.Errorf(`"%s" is less than zero`, v.name) + } + return v +} + +func (v *validator) isLessOrEqualTo(num int) *validator { + if v.err != nil { + return v + } + if v.intValue > num { + v.err = fmt.Errorf(`"%s" is higher than %v`, v.name, num) + } + return v +} + +func (v *validator) isNotEqualTo(value string) *validator { + if v.err != nil { + return v + } + if v.strValue == value { + v.err = fmt.Errorf(`"%s" can't be equal to "%s"`, v.name, value) + } + return v +} + +func (v *validator) hasItems() *validator { + if v.err != nil { + return v + } + if len(v.strArr) == 0 { + v.err = fmt.Errorf(`"%s" can't be empty`, v.name) + } + return v +} + +func (v *validator) hasUniqueItems() *validator { + if v.err != nil { + return v + } + m := make(map[string]bool) + for _, s := range v.strArr { + m[s] = true + } + if len(m) != len(v.strArr) { + v.err = fmt.Errorf(`"%s" contains redundant values %s`, v.name, v.strArr) + } + return v +} + +func isNotEmpty(s string) bool { + return strings.ReplaceAll(s, " ", "") != "" +} diff --git a/controllers/dnsupdate.go b/controllers/dnsupdate.go index 627ead5b6..1ae1c4b77 100644 --- a/controllers/dnsupdate.go +++ b/controllers/dnsupdate.go @@ -454,6 +454,7 @@ func (r *GslbReconciler) configureZoneDelegation(gslb *k8gbv1beta1.Gslb) (*recon clusterGeoTag := os.Getenv("CLUSTER_GEO_TAG") extClusterGeoTags := os.Getenv("EXT_GSLB_CLUSTERS_GEO_TAGS") infobloxGridHost := os.Getenv("INFOBLOX_GRID_HOST") + // TODO: Rute53Enabled private, use `r.Config.EdgeDNSType==depresolver.Route53` instead. if r.Config.Route53Enabled { ttl := externaldns.TTL(gslb.Spec.Strategy.DNSTtlSeconds) gslbZoneName := os.Getenv("DNS_ZONE") diff --git a/controllers/gslb_controller_test.go b/controllers/gslb_controller_test.go index 0084fcbe4..520569210 100644 --- a/controllers/gslb_controller_test.go +++ b/controllers/gslb_controller_test.go @@ -6,6 +6,8 @@ import ( "io/ioutil" "os" "reflect" + "strconv" + "strings" "testing" "time" @@ -38,15 +40,29 @@ func TestGslbController(t *testing.T) { // Start fakedns server for external dns tests fakedns() // Isolate the unit tests from interaction with real infoblox grid - err := os.Setenv("INFOBLOX_GRID_HOST", "fakeinfoblox.example.com") + + err := os.Setenv("FAKE_INFOBLOX", "true") if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } - err = os.Setenv("FAKE_INFOBLOX", "true") - if err != nil { - t.Fatalf("Can't setup env var: (%v)", err) + predefinedConfig := depresolver.Config{ + ReconcileRequeueSeconds: 30, + ClusterGeoTag: "us-west-1", + ExtClustersGeoTags: []string{"us-east-1"}, + EdgeDNSServer: "8.8.8.8", + EdgeDNSZone: "example.com", + DNSZone: "cloud.example.com", + Route53Enabled: false, + Infoblox: depresolver.Infoblox{ + Host: "fakeinfoblox.example.com", + Username: "foo", + Password: "blah", + Port: 443, + Version: "0.0.0", + }, } + configureEnvVar(predefinedConfig) _, err = os.Stat(crSampleYaml) if os.IsNotExist(err) { @@ -350,11 +366,11 @@ func TestGslbController(t *testing.T) { }) t.Run("Generates proper external NS target FQDNs according to the geo tags", func(t *testing.T) { - err := os.Setenv("EDGE_DNS_ZONE", "example.com") + err := os.Setenv(depresolver.EdgeDNSZoneKey, "example.com") if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } - err = os.Setenv("EXT_GSLB_CLUSTERS_GEO_TAGS", "za") + err = os.Setenv(depresolver.ExtClustersGeoTagsKey, "za") if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } @@ -493,7 +509,7 @@ func TestGslbController(t *testing.T) { if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } - err = os.Setenv("CLUSTER_GEO_TAG", "eu") + err = os.Setenv(depresolver.ClusterGeoTagKey, "eu") if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } @@ -554,7 +570,7 @@ func TestGslbController(t *testing.T) { if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } - err = os.Setenv("CLUSTER_GEO_TAG", "za") + err = os.Setenv(depresolver.ClusterGeoTagKey, "za") if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } @@ -631,7 +647,7 @@ func TestGslbController(t *testing.T) { t.Run("Reflect GeoTag in the Status as unset by default", func(t *testing.T) { reconcileAndUpdateGslb(t, r, req, cl, gslb) got := gslb.Status.GeoTag - want := "unset" + want := "us-west-1" if got != want { t.Errorf("got: '%s' GeoTag status, want:'%s'", got, want) @@ -640,13 +656,13 @@ func TestGslbController(t *testing.T) { t.Run("Reflect GeoTag in the Status", func(t *testing.T) { defer func() { - err = os.Unsetenv("CLUSTER_GEO_TAG") + err = os.Unsetenv(depresolver.ClusterGeoTagKey) if err != nil { t.Fatalf("Can't unset env var: (%v)", err) } }() - err = os.Setenv("CLUSTER_GEO_TAG", "eu") + err = os.Setenv(depresolver.ClusterGeoTagKey, "eu") if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } @@ -666,12 +682,12 @@ func TestGslbController(t *testing.T) { t.Run("Detects Ingress hostname mismatch", func(t *testing.T) { defer func() { - err := os.Setenv("EDGE_DNS_ZONE", "example.com") + err := os.Setenv(depresolver.EdgeDNSZoneKey, "example.com") if err != nil { t.Fatalf("Can't set env var: (%v)", err) } }() - err := os.Setenv("EDGE_DNS_ZONE", "otherdnszone.com") + err := os.Setenv(depresolver.EdgeDNSZoneKey, "otherdnszone.com") if err != nil { t.Fatalf("Can't set env var: (%v)", err) } @@ -690,7 +706,7 @@ func TestGslbController(t *testing.T) { }) t.Run("Creates NS DNS records for route53", func(t *testing.T) { - err = os.Setenv("EDGE_DNS_SERVER", "1.1.1.1") + err = os.Setenv(depresolver.EdgeDNSServerKey, "1.1.1.1") if err != nil { t.Fatalf("Can't setup env var: (%v)", err) } @@ -714,19 +730,19 @@ func TestGslbController(t *testing.T) { t.Fatalf("Failed to update coredns service lb hostname: (%v)", err) } - err := os.Setenv("ROUTE53_ENABLED", "true") + err := os.Setenv(depresolver.Route53EnabledKey, "true") if err != nil { t.Fatalf("Can't set env var: (%v)", err) } - err = os.Setenv("CLUSTER_GEO_TAG", "eu") + err = os.Setenv(depresolver.ClusterGeoTagKey, "eu") if err != nil { t.Fatalf("Can't set env var: (%v)", err) } - err = os.Setenv("EXT_GSLB_CLUSTERS_GEO_TAGS", "za,us") + err = os.Setenv(depresolver.ExtClustersGeoTagsKey, "za,us") if err != nil { t.Fatalf("Can't set env var: (%v)", err) } - err = os.Setenv("DNS_ZONE", "cloud.example.com") + err = os.Setenv(depresolver.DNSZoneKey, "cloud.example.com") if err != nil { t.Fatalf("Can't set env var: (%v)", err) } @@ -754,7 +770,7 @@ func TestGslbController(t *testing.T) { wantEp := []*externaldns.Endpoint{ { - DNSName: os.Getenv("DNS_ZONE"), + DNSName: os.Getenv(depresolver.DNSZoneKey), RecordTTL: 30, RecordType: "NS", Targets: externaldns.Targets{ @@ -974,3 +990,18 @@ func reconcileAndUpdateGslb(t *testing.T, t.Fatalf("Failed to get expected gslb: (%v)", err) } } + +func configureEnvVar(config depresolver.Config) { + _ = os.Setenv(depresolver.ReconcileRequeueSecondsKey, strconv.Itoa(config.ReconcileRequeueSeconds)) + _ = os.Setenv(depresolver.ClusterGeoTagKey, config.ClusterGeoTag) + _ = os.Setenv(depresolver.ExtClustersGeoTagsKey, strings.Join(config.ExtClustersGeoTags, ",")) + _ = os.Setenv(depresolver.EdgeDNSServerKey, config.EdgeDNSServer) + _ = os.Setenv(depresolver.EdgeDNSZoneKey, config.EdgeDNSZone) + _ = os.Setenv(depresolver.DNSZoneKey, config.DNSZone) + _ = os.Setenv(depresolver.Route53EnabledKey, strconv.FormatBool(config.Route53Enabled)) + _ = os.Setenv(depresolver.InfobloxGridHostKey, config.Infoblox.Host) + _ = os.Setenv(depresolver.InfobloxVersionKey, config.Infoblox.Version) + _ = os.Setenv(depresolver.InfobloxPortKey, strconv.Itoa(config.Infoblox.Port)) + _ = os.Setenv(depresolver.InfobloxUsernameKey, config.Infoblox.Username) + _ = os.Setenv(depresolver.InfobloxPasswordKey, config.Infoblox.Password) +} diff --git a/controllers/internal/env/env.go b/controllers/internal/env/env.go index cc06d1125..7a3083239 100644 --- a/controllers/internal/env/env.go +++ b/controllers/internal/env/env.go @@ -19,6 +19,7 @@ package env import ( "os" "strconv" + "strings" ) // GetEnvAsStringOrFallback returns the env variable for the given key @@ -30,6 +31,20 @@ func GetEnvAsStringOrFallback(key, defaultValue string) string { return defaultValue } +// GetEnvAsArrayOfStringsOrFallback returns the env variable for the given key +// and falls back to the given defaultValue if not set +// GetEnvAsArrayOfStringsOrFallback trims all whitespaces from input i.e. "us, fr, au" -> {"us","fr","au"} +func GetEnvAsArrayOfStringsOrFallback(key string, defaultValue []string) []string { + v := GetEnvAsStringOrFallback(key, "") + if v != "" { + arr := strings.Split(strings.ReplaceAll(v, " ", ""), ",") + if len(arr) != 0 { + return arr + } + } + return defaultValue +} + // GetEnvAsIntOrFallback returns the env variable (parsed as integer) for // the given key and falls back to the given defaultValue if not set func GetEnvAsIntOrFallback(key string, defaultValue int) (int, error) {