diff --git a/internal/dinosaur/pkg/gitops/config.go b/internal/dinosaur/pkg/gitops/config.go index 87950ef0d4..ec9a3fa428 100644 --- a/internal/dinosaur/pkg/gitops/config.go +++ b/internal/dinosaur/pkg/gitops/config.go @@ -8,8 +8,9 @@ import ( // Config represents the gitops configuration type Config struct { - Centrals CentralsConfig `json:"centrals"` - RHACSOperators operator.OperatorConfigs `json:"rhacsOperators"` + Centrals CentralsConfig `json:"centrals"` + RHACSOperators operator.OperatorConfigs `json:"rhacsOperators"` + DataPlaneClusters []DataPlaneClusterConfig `json:"dataPlaneClusters"` } // CentralsConfig represents the declarative configuration for Central instances defaults and overrides. @@ -28,11 +29,25 @@ type CentralOverride struct { Patch string `json:"patch"` } +// DataPlaneClusterConfig represents the configuration to be applied for a data plane cluster. +type DataPlaneClusterConfig struct { + ClusterID string `json:"clusterID"` + Addons []AddonConfig `json:"addons"` +} + +// AddonConfig represents the addon configuration to be installed on a cluster +type AddonConfig struct { + ID string `json:"id"` + Version string `json:"version"` + Parameters map[string]string `json:"parameters"` +} + // ValidateConfig validates the GitOps configuration. func ValidateConfig(config Config) field.ErrorList { var errs field.ErrorList errs = append(errs, validateCentralsConfig(field.NewPath("centrals"), config.Centrals)...) errs = append(errs, operator.Validate(field.NewPath("rhacsOperators"), config.RHACSOperators)...) + errs = append(errs, validateDataPlaneClusterConfigs(field.NewPath("dataPlaneClusters"), config.DataPlaneClusters)...) return errs } @@ -130,3 +145,46 @@ func validateInstanceID(path *field.Path, instanceID string) field.ErrorList { } return errs } + +func validateDataPlaneClusterConfigs(path *field.Path, clusters []DataPlaneClusterConfig) field.ErrorList { + var errs field.ErrorList + var seenCluster = make(map[string]struct{}) + for i, cluster := range clusters { + errs = append(errs, validateClusterID(path.Index(i).Child("clusterID"), cluster.ClusterID)...) + if _, ok := seenCluster[cluster.ClusterID]; ok { + errs = append(errs, field.Duplicate(path, cluster)) + } + seenCluster[cluster.ClusterID] = struct{}{} + errs = append(errs, validateAddons(path.Index(i).Child("addons"), cluster.Addons)...) + } + return errs +} + +func validateClusterID(path *field.Path, clusterID string) field.ErrorList { + var errs field.ErrorList + if len(clusterID) == 0 { + errs = append(errs, field.Required(path, "clusterID is required")) + } + return errs +} + +func validateAddons(path *field.Path, addons []AddonConfig) field.ErrorList { + var errs field.ErrorList + var seenAddon = make(map[string]struct{}) + for i, addon := range addons { + errs = append(errs, validateAddonID(path.Index(i).Child("id"), addon.ID)...) + if _, ok := seenAddon[addon.ID]; ok { + errs = append(errs, field.Duplicate(path, addon)) + } + seenAddon[addon.ID] = struct{}{} + } + return errs +} + +func validateAddonID(path *field.Path, addonID string) field.ErrorList { + var errs field.ErrorList + if len(addonID) == 0 { + errs = append(errs, field.Required(path, "id is required")) + } + return errs +} diff --git a/internal/dinosaur/pkg/gitops/config_test.go b/internal/dinosaur/pkg/gitops/config_test.go index c4e84db290..c6c862d123 100644 --- a/internal/dinosaur/pkg/gitops/config_test.go +++ b/internal/dinosaur/pkg/gitops/config_test.go @@ -93,6 +93,43 @@ centrals: resources: requests: memory: "a" +`, + }, + { + name: "valid cluster config", + assert: func(t *testing.T, c *Config, err field.ErrorList) { + require.Empty(t, err) + }, + yaml: ` +dataPlaneClusters: + - clusterID: 1234567890abcdef1234567890abcdef + addons: + - id: acs-fleetshard +`, + }, + { + name: "invalid cluster config when no clusterID", + assert: func(t *testing.T, c *Config, err field.ErrorList) { + require.Len(t, err, 1) + assert.Equal(t, field.Required(field.NewPath("dataPlaneClusters").Index(0).Child("clusterID"), "clusterID is required"), err[0]) + }, + yaml: ` +dataPlaneClusters: + - addons: + - id: acs-fleetshard +`, + }, + { + name: "invalid cluster config when no addon ID", + assert: func(t *testing.T, c *Config, err field.ErrorList) { + require.Len(t, err, 1) + assert.Equal(t, field.Required(field.NewPath("dataPlaneClusters").Index(0).Child("addons").Index(0).Child("id"), "id is required"), err[0]) + }, + yaml: ` +dataPlaneClusters: + - clusterID: 1234567890abcdef1234567890abcdef + addons: + - version: 0.2.0 `, }, } diff --git a/internal/dinosaur/pkg/gitops/provider_test.go b/internal/dinosaur/pkg/gitops/provider_test.go index 1925509c41..a7f917af4e 100644 --- a/internal/dinosaur/pkg/gitops/provider_test.go +++ b/internal/dinosaur/pkg/gitops/provider_test.go @@ -177,3 +177,98 @@ centrals: assert.Equal(t, 2, validationFnCalls) } + +func TestProviderGet_DataPlaneClusters(t *testing.T) { + successfulValidation := func(config Config) error { + return nil + } + type tc struct { + name string + file string + expectedConfigs []DataPlaneClusterConfig + } + + tcs := []tc{ + { + name: "should return nil when no data plane clusters defined", + file: "", + expectedConfigs: nil, + }, + { + name: "should return empty slice when the list of clusters is empty", + file: "dataPlaneClusters: []", + expectedConfigs: []DataPlaneClusterConfig{}, + }, + { + name: "should return config when no addons defined in the cluster", + file: ` +dataPlaneClusters: + - clusterID: 1234567890abcdef1234567890abcdef +`, + expectedConfigs: []DataPlaneClusterConfig{ + { + ClusterID: "1234567890abcdef1234567890abcdef", // pragma: allowlist secret + }, + }, + }, + { + name: "should return config when cluster with the empty addon slice is defined", + file: ` +dataPlaneClusters: + - clusterID: 1234567890abcdef1234567890abcdef + addons: [] +`, + expectedConfigs: []DataPlaneClusterConfig{ + { + ClusterID: "1234567890abcdef1234567890abcdef", // pragma: allowlist secret + Addons: []AddonConfig{}, + }, + }, + }, + { + name: "should return config when cluster with an addon is defined", + file: ` +dataPlaneClusters: + - clusterID: 1234567890abcdef1234567890abcdef + addons: + - id: acs-fleetshard + version: 0.2.0 + parameters: + acscsEnvironment: test +`, + expectedConfigs: []DataPlaneClusterConfig{ + { + ClusterID: "1234567890abcdef1234567890abcdef", // pragma: allowlist secret + Addons: []AddonConfig{ + { + ID: "acs-fleetshard", + Version: "0.2.0", + Parameters: map[string]string{ + "acscsEnvironment": "test", + }, + }, + }, + }, + }, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + tmpDir := t.TempDir() + tmpFile := filepath.Join(tmpDir, "config.yaml") + err := os.WriteFile(tmpFile, []byte(tc.file), 0644) + if err != nil { + t.Fatal(err) + } + p := &provider{ + reader: NewFileReader(tmpFile), + lastWorkingConfig: atomic.Pointer[Config]{}, + validationFn: successfulValidation, + } + config, err := p.Get() + require.NoError(t, err) + assert.Equal(t, tc.expectedConfigs, config.DataPlaneClusters) + }) + } + +}