diff --git a/api/types/state.go b/api/types/state.go index 5a1072f5..925e237c 100644 --- a/api/types/state.go +++ b/api/types/state.go @@ -21,20 +21,23 @@ type ConfigurationState string // Reasons a resource is or is not ready. const ( - Authorizing ConfigurationState = "Authorizing" - ProviderNotFound ConfigurationState = "ProviderNotFound" - ProviderNotReady ConfigurationState = "ProviderNotReady" - ConfigurationStaticCheckFailed ConfigurationState = "ConfigurationSpecNotValid" - Available ConfigurationState = "Available" - ConfigurationProvisioningAndChecking ConfigurationState = "ProvisioningAndChecking" - ConfigurationDestroying ConfigurationState = "Destroying" - ConfigurationApplyFailed ConfigurationState = "ApplyFailed" - ConfigurationDestroyFailed ConfigurationState = "DestroyFailed" - ConfigurationReloading ConfigurationState = "ConfigurationReloading" - GeneratingOutputs ConfigurationState = "GeneratingTerraformOutputs" - InvalidRegion ConfigurationState = "InvalidRegion" - TerraformInitError ConfigurationState = "TerraformInitError" - InvalidGitCredentialsSecretReference ConfigurationState = "InvalidGitCredentialsSecretReference" + Authorizing ConfigurationState = "Authorizing" + ProviderNotFound ConfigurationState = "ProviderNotFound" + ProviderNotReady ConfigurationState = "ProviderNotReady" + ConfigurationStaticCheckFailed ConfigurationState = "ConfigurationSpecNotValid" + Available ConfigurationState = "Available" + ConfigurationProvisioningAndChecking ConfigurationState = "ProvisioningAndChecking" + ConfigurationDestroying ConfigurationState = "Destroying" + ConfigurationApplyFailed ConfigurationState = "ApplyFailed" + ConfigurationDestroyFailed ConfigurationState = "DestroyFailed" + ConfigurationReloading ConfigurationState = "ConfigurationReloading" + GeneratingOutputs ConfigurationState = "GeneratingTerraformOutputs" + InvalidRegion ConfigurationState = "InvalidRegion" + TerraformInitError ConfigurationState = "TerraformInitError" + InvalidGitCredentialsSecretReference ConfigurationState = "InvalidGitCredentialsSecretReference" + InvalidTerraformCredentialsSecretReference ConfigurationState = "InvalidTerraformCredentialsSecretReference" + InvalidTerraformRCConfigMapReference ConfigurationState = "InvalidTerraformRCConfigMapReference" + InvalidTerraformCredentialsHelperConfigMapReference ConfigurationState = "InvalidTerraformCredentialsHelperConfigMapReference" ) // Stage is the Terraform stage diff --git a/api/v1beta1/configuration_types.go b/api/v1beta1/configuration_types.go index 060cb2a5..1f2adb0b 100644 --- a/api/v1beta1/configuration_types.go +++ b/api/v1beta1/configuration_types.go @@ -51,6 +51,15 @@ type ConfigurationSpec struct { // GitCredentialsSecretReference specifies the reference to the secret containing the git credentials GitCredentialsSecretReference *v1.SecretReference `json:"gitCredentialsSecretReference,omitempty"` + + // TerraformCredentialsSecretReference specifies the reference to the secret containing the terraform credentials + TerraformCredentialsSecretReference *v1.SecretReference `json:"terraformCredentialsSecretReference,omitempty"` + + // TerraformRCConfigMapReference specifies the reference to a config map containing the terraform registry configuration + TerraformRCConfigMapReference *v1.SecretReference `json:"terraformRCConfigMapReference,omitempty"` + + // TerraformCredentialsHelperConfigMapReference specifies the reference to a configmap containing the terraform registry credentials helper + TerraformCredentialsHelperConfigMapReference *v1.SecretReference `json:"terraformCredentialsHelperConfigMapReference,omitempty"` } // BaseConfigurationSpec defines the common fields of a ConfigurationSpec diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 21d9e64b..454b670f 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -182,6 +182,21 @@ func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) { *out = new(v1.SecretReference) **out = **in } + if in.TerraformCredentialsSecretReference != nil { + in, out := &in.TerraformCredentialsSecretReference, &out.TerraformCredentialsSecretReference + *out = new(v1.SecretReference) + **out = **in + } + if in.TerraformRCConfigMapReference != nil { + in, out := &in.TerraformRCConfigMapReference, &out.TerraformRCConfigMapReference + *out = new(v1.SecretReference) + **out = **in + } + if in.TerraformCredentialsHelperConfigMapReference != nil { + in, out := &in.TerraformCredentialsHelperConfigMapReference, &out.TerraformCredentialsHelperConfigMapReference + *out = new(v1.SecretReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSpec. diff --git a/api/v1beta2/configuration_types.go b/api/v1beta2/configuration_types.go index 146e8ddf..8cee4e68 100644 --- a/api/v1beta2/configuration_types.go +++ b/api/v1beta2/configuration_types.go @@ -79,6 +79,15 @@ type ConfigurationSpec struct { // GitCredentialsSecretReference specifies the reference to the secret containing the git credentials GitCredentialsSecretReference *v1.SecretReference `json:"gitCredentialsSecretReference,omitempty"` + + // TerraformCredentialsSecretReference specifies the reference to the secret containing the terraform credentials and terraform registry details + TerraformCredentialsSecretReference *v1.SecretReference `json:"terraformCredentialsSecretReference,omitempty"` + + // TerraformRCConfigMapReference specifies the reference to a config map containing the terraform registry configuration + TerraformRCConfigMapReference *v1.SecretReference `json:"terraformRCConfigMapReference,omitempty"` + + // TerraformCredentialsHelperConfigMapReference specifies the reference to a configmap containing the terraform registry credentials helper + TerraformCredentialsHelperConfigMapReference *v1.SecretReference `json:"terraformCredentialsHelperConfigMapReference,omitempty"` } // ConfigurationStatus defines the observed state of Configuration diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 64795fa0..56fadfce 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -191,6 +191,21 @@ func (in *ConfigurationSpec) DeepCopyInto(out *ConfigurationSpec) { *out = new(v1.SecretReference) **out = **in } + if in.TerraformCredentialsSecretReference != nil { + in, out := &in.TerraformCredentialsSecretReference, &out.TerraformCredentialsSecretReference + *out = new(v1.SecretReference) + **out = **in + } + if in.TerraformRCConfigMapReference != nil { + in, out := &in.TerraformRCConfigMapReference, &out.TerraformRCConfigMapReference + *out = new(v1.SecretReference) + **out = **in + } + if in.TerraformCredentialsHelperConfigMapReference != nil { + in, out := &in.TerraformCredentialsHelperConfigMapReference, &out.TerraformCredentialsHelperConfigMapReference + *out = new(v1.SecretReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationSpec. diff --git a/chart/crds/terraform.core.oam.dev_configurations.yaml b/chart/crds/terraform.core.oam.dev_configurations.yaml index 8b964b24..ae35b5d9 100644 --- a/chart/crds/terraform.core.oam.dev_configurations.yaml +++ b/chart/crds/terraform.core.oam.dev_configurations.yaml @@ -111,6 +111,46 @@ spec: description: Remote is a git repo which contains hcl files. Currently, only public git repos are supported. type: string + terraformCredentialsHelperConfigMapReference: + description: TerraformCredentialsHelperConfigMapReference specifies + the reference to a configmap containing the terraform registry credentials + helper + properties: + name: + description: Name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + terraformCredentialsSecretReference: + description: TerraformCredentialsSecretReference specifies the reference + to the secret containing the terraform credentials + properties: + name: + description: Name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + terraformRCConfigMapReference: + description: TerraformRCConfigMapReference specifies the reference + to a config map containing the terraform registry configuration + properties: + name: + description: Name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object variable: type: object x-kubernetes-preserve-unknown-fields: true @@ -318,6 +358,47 @@ spec: description: Remote is a git repo which contains hcl files. Currently, only public git repos are supported. type: string + terraformCredentialsHelperConfigMapReference: + description: TerraformCredentialsHelperConfigMapReference specifies + the reference to a configmap containing the terraform registry credentials + helper + properties: + name: + description: Name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + terraformCredentialsSecretReference: + description: TerraformCredentialsSecretReference specifies the reference + to the secret containing the terraform credentials and terraform + registry details + properties: + name: + description: Name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object + terraformRCConfigMapReference: + description: TerraformRCConfigMapReference specifies the reference + to a config map containing the terraform registry configuration + properties: + name: + description: Name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: Namespace defines the space within which the secret + name must be unique. + type: string + type: object variable: type: object x-kubernetes-preserve-unknown-fields: true diff --git a/controllers/configuration_controller.go b/controllers/configuration_controller.go index 83a2695a..f2ed61c2 100644 --- a/controllers/configuration_controller.go +++ b/controllers/configuration_controller.go @@ -69,6 +69,18 @@ const ( GitAuthConfigVolumeName = "git-auth-configuration" // GitAuthConfigVolumeMountPath is the volume mount path for git auth configurtaion GitAuthConfigVolumeMountPath = "/root/.ssh" + // TerraformCredentialsConfigVolumeName is the volume name for terraform auth configurtaion + TerraformCredentialsConfigVolumeName = "terraform-credentials-configuration" + // TerraformCredentialsConfigVolumeMountPath is the volume mount path for terraform auth configurtaion + TerraformCredentialsConfigVolumeMountPath = "/root/.terraform.d" + // TerraformRCConfigVolumeName is the volume name of the terraform registry configuration + TerraformRCConfigVolumeName = "terraform-rc-configuration" + // TerraformRCConfigVolumeMountPath is the volume mount path for registry configuration + TerraformRCConfigVolumeMountPath = "/root" + // TerraformCredentialsHelperConfigVolumeName is the volume name for terraform auth configurtaion + TerraformCredentialsHelperConfigVolumeName = "terraform-credentials-helper-configuration" + // TerraformCredentialsHelperConfigVolumeMountPath is the volume mount path for terraform auth configurtaion + TerraformCredentialsHelperConfigVolumeMountPath = "/root/.terraform.d/plugins" ) const ( @@ -98,6 +110,10 @@ const ( const ( GitCredsKnownHosts = "known_hosts" + // Terraform credentials + TerraformCredentials = "credentials.tfrc.json" + // Terraform Registry Configuration + TerraformRegistryConfig = ".terraformrc" ) // ConfigurationReconciler reconciles a Configuration object. @@ -236,27 +252,30 @@ type ResourceQuota struct { // TFConfigurationMeta is all the metadata of a Configuration type TFConfigurationMeta struct { - Name string - Namespace string - ControllerNamespace string - ConfigurationType types.ConfigurationType - CompleteConfiguration string - RemoteGit string - RemoteGitPath string - ConfigurationChanged bool - EnvChanged bool - ConfigurationCMName string - ApplyJobName string - DestroyJobName string - Envs []v1.EnvVar - ProviderReference *crossplane.Reference - VariableSecretName string - VariableSecretData map[string][]byte - DeleteResource bool - Region string - Credentials map[string]string - JobEnv map[string]interface{} - GitCredentialsSecretReference *v1.SecretReference + Name string + Namespace string + ControllerNamespace string + ConfigurationType types.ConfigurationType + CompleteConfiguration string + RemoteGit string + RemoteGitPath string + ConfigurationChanged bool + EnvChanged bool + ConfigurationCMName string + ApplyJobName string + DestroyJobName string + Envs []v1.EnvVar + ProviderReference *crossplane.Reference + VariableSecretName string + VariableSecretData map[string][]byte + DeleteResource bool + Region string + Credentials map[string]string + JobEnv map[string]interface{} + GitCredentialsSecretReference *v1.SecretReference + TerraformCredentialsSecretReference *v1.SecretReference + TerraformRCConfigMapReference *v1.SecretReference + TerraformCredentialsHelperConfigMapReference *v1.SecretReference Backend backend.Backend // JobNodeSelector Expose the node selector of job to the controller level @@ -322,6 +341,18 @@ func initTFConfigurationMeta(req ctrl.Request, configuration v1beta2.Configurati meta.GitCredentialsSecretReference = configuration.Spec.GitCredentialsSecretReference } + if configuration.Spec.TerraformCredentialsSecretReference != nil { + meta.TerraformCredentialsSecretReference = configuration.Spec.TerraformCredentialsSecretReference + } + + if configuration.Spec.TerraformRCConfigMapReference != nil { + meta.TerraformRCConfigMapReference = configuration.Spec.TerraformRCConfigMapReference + } + + if configuration.Spec.TerraformCredentialsHelperConfigMapReference != nil { + meta.TerraformCredentialsHelperConfigMapReference = configuration.Spec.TerraformCredentialsHelperConfigMapReference + } + return meta } @@ -557,18 +588,13 @@ func (r *ConfigurationReconciler) preCheck(ctx context.Context, configuration *v } } - if meta.GitCredentialsSecretReference != nil { - gitCreds, err := GetGitCredentialsSecret(ctx, k8sClient, meta.GitCredentialsSecretReference) - if gitCreds == nil { - msg := string(types.InvalidGitCredentialsSecretReference) - if err != nil { - msg = err.Error() - } - if updateStatusErr := meta.updateApplyStatus(ctx, k8sClient, types.InvalidGitCredentialsSecretReference, msg); updateStatusErr != nil { - return errors.Wrap(updateStatusErr, msg) - } - return errors.New(msg) - } + /* validate the following secret references and configmap references. + 1. GitCredentialsSecretReference + 2. TerraformCredentialsSecretReference + 3. TerraformRCConfigMapReference + 4. TerraformCredentialsHelperConfigMapReference*/ + if err := meta.validateSecretAndConfigMap(ctx, k8sClient); err != nil { + return err } // Render configuration with backend @@ -637,6 +663,64 @@ func (r *ConfigurationReconciler) preCheck(ctx context.Context, configuration *v return createTerraformExecutorClusterRole(ctx, k8sClient, fmt.Sprintf("%s-%s", meta.ControllerNamespace, ClusterRoleName)) } +func (meta *TFConfigurationMeta) validateSecretAndConfigMap(ctx context.Context, k8sClient client.Client) error { + + secretConfigMapToCheck := []struct { + ref *v1.SecretReference + notFoundState types.ConfigurationState + isSecret bool + neededKeys []string + errKey string + }{ + { + ref: meta.GitCredentialsSecretReference, + notFoundState: types.InvalidGitCredentialsSecretReference, + isSecret: true, + neededKeys: []string{GitCredsKnownHosts, v1.SSHAuthPrivateKey}, + errKey: "git credentials", + }, + { + ref: meta.TerraformCredentialsSecretReference, + notFoundState: types.InvalidTerraformCredentialsSecretReference, + isSecret: true, + neededKeys: []string{TerraformCredentials}, + errKey: "terraform credentials", + }, + { + ref: meta.TerraformRCConfigMapReference, + notFoundState: types.InvalidTerraformRCConfigMapReference, + isSecret: false, + neededKeys: []string{TerraformRegistryConfig}, + errKey: "terraformrc configuration", + }, + { + ref: meta.TerraformCredentialsHelperConfigMapReference, + notFoundState: types.InvalidTerraformCredentialsHelperConfigMapReference, + isSecret: false, + neededKeys: []string{}, + errKey: "terraform credentials helper", + }, + } + for _, check := range secretConfigMapToCheck { + if check.ref != nil { + var object metav1.Object + var err error + object, err = GetSecretOrConfigMap(ctx, k8sClient, check.isSecret, check.ref, check.neededKeys, check.errKey) + if object == nil { + msg := string(check.notFoundState) + if err != nil { + msg = err.Error() + } + if updateStatusErr := meta.updateApplyStatus(ctx, k8sClient, check.notFoundState, msg); updateStatusErr != nil { + return errors.Wrap(updateStatusErr, msg) + } + return errors.New(msg) + } + } + } + return nil +} + func (meta *TFConfigurationMeta) updateApplyStatus(ctx context.Context, k8sClient client.Client, state types.ConfigurationState, message string) error { var configuration v1beta2.Configuration if err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.Name, Namespace: meta.Namespace}, &configuration); err == nil { @@ -738,6 +822,30 @@ func (meta *TFConfigurationMeta) assembleTerraformJob(executionType TerraformExe }, } + if meta.TerraformCredentialsSecretReference != nil { + initContainerVolumeMounts = append(initContainerVolumeMounts, + v1.VolumeMount{ + Name: TerraformCredentialsConfigVolumeName, + MountPath: TerraformCredentialsConfigVolumeMountPath, + }) + } + + if meta.TerraformRCConfigMapReference != nil { + initContainerVolumeMounts = append(initContainerVolumeMounts, + v1.VolumeMount{ + Name: TerraformRCConfigVolumeName, + MountPath: TerraformRCConfigVolumeMountPath, + }) + } + + if meta.TerraformCredentialsHelperConfigMapReference != nil { + initContainerVolumeMounts = append(initContainerVolumeMounts, + v1.VolumeMount{ + Name: TerraformCredentialsHelperConfigVolumeName, + MountPath: TerraformCredentialsHelperConfigVolumeMountPath, + }) + } + // prepare local Terraform .tf files initContainer = v1.Container{ Name: "prepare-input-terraform-configurations", @@ -897,9 +1005,36 @@ func (meta *TFConfigurationMeta) assembleExecutorVolumes() []v1.Volume { inputTFConfigurationVolume := meta.createConfigurationVolume() tfBackendVolume := meta.createTFBackendVolume() executorVolumes := []v1.Volume{workingVolume, inputTFConfigurationVolume, tfBackendVolume} - if meta.GitCredentialsSecretReference != nil { - gitAuthConfigVolume := meta.createGitAuthConfigVolume() - executorVolumes = append(executorVolumes, gitAuthConfigVolume) + secretOrConfigMapReferences := []struct { + ref *v1.SecretReference + volumeName string + isSecret bool + }{ + { + ref: meta.GitCredentialsSecretReference, + volumeName: GitAuthConfigVolumeName, + isSecret: true, + }, + { + ref: meta.TerraformCredentialsSecretReference, + volumeName: TerraformCredentialsConfigVolumeName, + isSecret: true, + }, + { + ref: meta.TerraformRCConfigMapReference, + volumeName: TerraformRCConfigVolumeName, + isSecret: false, + }, + { + ref: meta.TerraformCredentialsHelperConfigMapReference, + volumeName: TerraformCredentialsHelperConfigVolumeName, + isSecret: false, + }, + } + for _, ref := range secretOrConfigMapReferences { + if ref.ref != nil { + executorVolumes = append(executorVolumes, meta.createSecretOrConfigMapVolume(ref.isSecret, ref.ref.Name, ref.volumeName)) + } } return executorVolumes } @@ -919,14 +1054,21 @@ func (meta *TFConfigurationMeta) createTFBackendVolume() v1.Volume { return gitVolume } -func (meta *TFConfigurationMeta) createGitAuthConfigVolume() v1.Volume { - var gitSecretDefaultMode int32 = 0400 - gitAuthSecretVolumeSource := v1.SecretVolumeSource{} - gitAuthSecretVolumeSource.SecretName = meta.GitCredentialsSecretReference.Name - gitAuthSecretVolumeSource.DefaultMode = &gitSecretDefaultMode - gitAuthSecretVolume := v1.Volume{Name: GitAuthConfigVolumeName} - gitAuthSecretVolume.Secret = &gitAuthSecretVolumeSource - return gitAuthSecretVolume +func (meta *TFConfigurationMeta) createSecretOrConfigMapVolume(isSecret bool, secretOrConfigMapReferenceName string, volumeName string) v1.Volume { + var defaultMode int32 = 0400 + volume := v1.Volume{Name: volumeName} + if isSecret { + volumeSource := v1.SecretVolumeSource{} + volumeSource.SecretName = secretOrConfigMapReferenceName + volumeSource.DefaultMode = &defaultMode + volume.Secret = &volumeSource + } else { + volumeSource := v1.ConfigMapVolumeSource{} + volumeSource.Name = secretOrConfigMapReferenceName + volumeSource.DefaultMode = &defaultMode + volume.ConfigMap = &volumeSource + } + return volume } // TfStateProperty is the tf state property for an output @@ -1336,25 +1478,44 @@ func (meta *TFConfigurationMeta) RenderConfiguration(configuration *v1beta2.Conf } } -// GetGitCredentialsSecret will get the secret containing the SSH private key & known_hosts -func GetGitCredentialsSecret(ctx context.Context, k8sClient client.Client, secretRef *v1.SecretReference) (*v1.Secret, error) { +func GetSecretOrConfigMap(ctx context.Context, k8sClient client.Client, isSecret bool, ref *v1.SecretReference, neededKeys []string, errKey string) (metav1.Object, error) { secret := &v1.Secret{} - - namespacedName := client.ObjectKey{Name: secretRef.Name, Namespace: secretRef.Namespace} - err := k8sClient.Get(ctx, namespacedName, secret) + configMap := &v1.ConfigMap{} + var err error + // key to determine if it is a secret or config map + var typeKey string + if isSecret { + namespacedName := client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace} + err = k8sClient.Get(ctx, namespacedName, secret) + typeKey = "secret" + } else { + namespacedName := client.ObjectKey{Name: ref.Name, Namespace: ref.Namespace} + err = k8sClient.Get(ctx, namespacedName, configMap) + typeKey = "configmap" + } + errMsg := fmt.Sprintf("Failed to get %s %s", errKey, typeKey) if err != nil { - errMsg := "Failed to get git credentials secret" - klog.ErrorS(err, errMsg, "Name", secretRef.Name, "Namespace", secretRef.Namespace) + klog.ErrorS(err, errMsg, "Name", ref.Name, "Namespace", ref.Namespace) return nil, errors.Wrap(err, errMsg) } - - needSecretKeys := []string{GitCredsKnownHosts, v1.SSHAuthPrivateKey} - for _, key := range needSecretKeys { - if _, ok := secret.Data[key]; !ok { - err := errors.Errorf("'%s' not in git credentials secret", key) - return nil, err + for _, key := range neededKeys { + var keyErr bool + if isSecret { + if _, ok := secret.Data[key]; !ok { + keyErr = true + } + } else { + if _, ok := configMap.Data[key]; !ok { + keyErr = true + } + } + if keyErr { + keyErr := errors.Errorf("'%s' not in %s %s", key, errKey, typeKey) + return nil, keyErr } } - - return secret, nil + if isSecret { + return secret, nil + } + return configMap, nil } diff --git a/controllers/configuration_controller_test.go b/controllers/configuration_controller_test.go index 68ff702e..9d486665 100644 --- a/controllers/configuration_controller_test.go +++ b/controllers/configuration_controller_test.go @@ -1639,6 +1639,140 @@ func TestAssembleTerraformJobWithGitCredentialsSecretRef(t *testing.T) { assert.Contains(t, spec.Volumes, gitAuthSecretVolume) } +func TestAssembleTerraformJobWithTerraformRCAndCredentials(t *testing.T) { + meta := &TFConfigurationMeta{ + Name: "a", + ConfigurationCMName: "b", + BusyboxImage: "c", + GitImage: "d", + Namespace: "e", + TerraformImage: "f", + RemoteGit: "g", + TerraformRCConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry-config", + }, + TerraformCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-credentials", + }, + TerraformCredentialsHelperConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-credentials-helper", + }, + } + + job := meta.assembleTerraformJob(TerraformApply) + spec := job.Spec.Template.Spec + + var terraformSecretDefaultMode int32 = 0400 + + terraformRegistryConfigMapVolume := corev1.Volume{Name: TerraformRCConfigVolumeName} + terraformRegistryConfigMapVolume.ConfigMap = &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "terraform-registry-config", + }, + DefaultMode: &terraformSecretDefaultMode, + } + + terraformRegistryConfigVolumeMount := corev1.VolumeMount{ + Name: TerraformRCConfigVolumeName, + MountPath: TerraformRCConfigVolumeMountPath, + } + terraformCredentialsSecretVolume := corev1.Volume{Name: TerraformCredentialsConfigVolumeName} + terraformCredentialsSecretVolume.Secret = &corev1.SecretVolumeSource{ + SecretName: "terraform-credentials", + DefaultMode: &terraformSecretDefaultMode, + } + + terraformCredentialsSecretVolumeMount := corev1.VolumeMount{ + Name: TerraformCredentialsConfigVolumeName, + MountPath: TerraformCredentialsConfigVolumeMountPath, + } + + terraformCredentialsHelperConfigVolume := corev1.Volume{Name: TerraformCredentialsHelperConfigVolumeName} + terraformCredentialsHelperConfigVolume.ConfigMap = &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "terraform-credentials-helper", + }, + DefaultMode: &terraformSecretDefaultMode, + } + + terraformCredentialsHelperConfigVolumeMount := corev1.VolumeMount{ + Name: TerraformCredentialsHelperConfigVolumeName, + MountPath: TerraformCredentialsHelperConfigVolumeMountPath, + } + + assert.Contains(t, spec.InitContainers[0].VolumeMounts, terraformCredentialsHelperConfigVolumeMount) + assert.Contains(t, spec.Volumes, terraformCredentialsHelperConfigVolume) + + assert.Contains(t, spec.InitContainers[0].VolumeMounts, terraformRegistryConfigVolumeMount) + assert.Contains(t, spec.Volumes, terraformRegistryConfigMapVolume) + + assert.Contains(t, spec.InitContainers[0].VolumeMounts, terraformCredentialsSecretVolumeMount) + assert.Contains(t, spec.Volumes, terraformCredentialsSecretVolume) + + assert.Contains(t, spec.InitContainers[0].VolumeMounts, terraformRegistryConfigVolumeMount) + assert.Contains(t, spec.Volumes, terraformRegistryConfigMapVolume) +} + +func TestAssembleTerraformJobWithTerraformRCAndCredentialsHelper(t *testing.T) { + meta := &TFConfigurationMeta{ + Name: "a", + ConfigurationCMName: "b", + BusyboxImage: "c", + GitImage: "d", + Namespace: "e", + TerraformImage: "f", + RemoteGit: "g", + TerraformRCConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry-config", + }, + TerraformCredentialsHelperConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-credentials-helper", + }, + } + + job := meta.assembleTerraformJob(TerraformApply) + spec := job.Spec.Template.Spec + + var terraformSecretDefaultMode int32 = 0400 + + terraformRegistryConfigMapVolume := corev1.Volume{Name: TerraformRCConfigVolumeName} + terraformRegistryConfigMapVolume.ConfigMap = &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "terraform-registry-config", + }, + DefaultMode: &terraformSecretDefaultMode, + } + + terraformRegistryConfigVolumeMount := corev1.VolumeMount{ + Name: TerraformRCConfigVolumeName, + MountPath: TerraformRCConfigVolumeMountPath, + } + terraformCredentialsHelperConfigVolume := corev1.Volume{Name: TerraformCredentialsHelperConfigVolumeName} + terraformCredentialsHelperConfigVolume.ConfigMap = &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "terraform-credentials-helper", + }, + DefaultMode: &terraformSecretDefaultMode, + } + + terraformCredentialsHelperConfigVolumeMount := corev1.VolumeMount{ + Name: TerraformCredentialsHelperConfigVolumeName, + MountPath: TerraformCredentialsHelperConfigVolumeMountPath, + } + + assert.Contains(t, spec.InitContainers[0].VolumeMounts, terraformRegistryConfigVolumeMount) + assert.Contains(t, spec.Volumes, terraformRegistryConfigMapVolume) + + assert.Contains(t, spec.InitContainers[0].VolumeMounts, terraformCredentialsHelperConfigVolumeMount) + assert.Contains(t, spec.Volumes, terraformCredentialsHelperConfigVolume) + +} + func TestTfStatePropertyToToProperty(t *testing.T) { testcases := []TfStateProperty{ { @@ -2644,10 +2778,120 @@ func TestCheckGitCredentialsSecretReference(t *testing.T) { }, }, } + neededKeys := []string{GitCredsKnownHosts, corev1.SSHAuthPrivateKey} + errKey := "git credentials" for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - sec, err := GetGitCredentialsSecret(ctx, tc.args.k8sClient, tc.args.GitCredentialsSecretReference) + sec, err := GetSecretOrConfigMap(ctx, tc.args.k8sClient, true, tc.args.GitCredentialsSecretReference, neededKeys, errKey) + if err != nil { + assert.EqualError(t, err, tc.want.errMsg) + } + if tc.want.secret != nil { + assert.EqualValues(t, sec, tc.want.secret) + } + }) + } +} + +func TestCheckTerraformCredentialsSecretReference(t *testing.T) { + ctx := context.Background() + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + k8sClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + credentialstfrcjson := []byte("tfcreds") + terraformrc := []byte("tfrc") + + secret := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "terraform-creds", + }, + Data: map[string][]byte{ + "credentials.tfrc.json": credentialstfrcjson, + "terraformrc": terraformrc, + }, + Type: corev1.SecretTypeSSHAuth, + } + assert.Nil(t, k8sClient.Create(ctx, secret)) + assert.Nil(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(secret), secret)) + + secretNotTerraformCreds := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "terraform-creds-no-creds", + }, + Data: map[string][]byte{ + "terraformrc": terraformrc, + }, + Type: corev1.SecretTypeSSHAuth, + } + + assert.Nil(t, k8sClient.Create(ctx, secretNotTerraformCreds)) + + type args struct { + k8sClient client.Client + TerraformCredentialsSecretReference *corev1.SecretReference + } + + type want struct { + secret *corev1.Secret + errMsg string + } + + testcases := []struct { + name string + args args + want want + }{ + { + name: "secret not found", + args: args{ + k8sClient: k8sClient, + TerraformCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "secret-not-exists", + }, + }, + want: want{ + errMsg: "Failed to get terraform credentials secret: secrets \"secret-not-exists\" not found", + }, + }, + { + name: "key 'credentials.tfrc.json' not in terraform credentials secret", + args: args{ + k8sClient: k8sClient, + TerraformCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-creds-no-creds", + }, + }, + want: want{ + errMsg: fmt.Sprintf("'%s' not in terraform credentials secret", TerraformCredentials), + }, + }, + { + name: "secret exists", + args: args{ + k8sClient: k8sClient, + TerraformCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-creds", + }, + }, + want: want{ + secret: secret, + }, + }, + } + + neededKeys := []string{TerraformCredentials} + errKey := "terraform credentials" + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + sec, err := GetSecretOrConfigMap(ctx, tc.args.k8sClient, true, tc.args.TerraformCredentialsSecretReference, neededKeys, errKey) if err != nil { assert.EqualError(t, err, tc.want.errMsg) @@ -2657,4 +2901,383 @@ func TestCheckGitCredentialsSecretReference(t *testing.T) { } }) } + +} + +func TestCheckTerraformRCConfigMapReference(t *testing.T) { + ctx := context.Background() + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + k8sClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + configMap := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "terraform-registry-config", + }, + Data: map[string]string{ + ".terraformrc": "tfrc", + }, + } + + assert.Nil(t, k8sClient.Create(ctx, configMap)) + assert.Nil(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(configMap), configMap)) + + configMapNotTerraformRc := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "terraform-registry-config-no-terraformrc", + }, + Data: map[string]string{ + "terraform": "tfrc", + }, + } + + assert.Nil(t, k8sClient.Create(ctx, configMapNotTerraformRc)) + assert.Nil(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(configMapNotTerraformRc), configMapNotTerraformRc)) + + type args struct { + k8sClient client.Client + TerraformRCConfigMapReference *corev1.SecretReference + } + + type want struct { + configMap *corev1.ConfigMap + errMsg string + } + + testcases := []struct { + name string + args args + want want + }{ + { + name: "configmap not found", + args: args{ + k8sClient: k8sClient, + TerraformRCConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "configmap-not-exists", + }, + }, + want: want{ + errMsg: "Failed to get terraformrc configuration configmap: configmaps \"configmap-not-exists\" not found", + }, + }, + { + name: "key '.terraformrc' not in terraform registry config", + args: args{ + k8sClient: k8sClient, + TerraformRCConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry-config-no-terraformrc", + }, + }, + want: want{ + errMsg: fmt.Sprintf("'%s' not in terraformrc configuration configmap", TerraformRegistryConfig), + }, + }, + { + name: "configmap exists", + args: args{ + k8sClient: k8sClient, + TerraformRCConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry-config", + }, + }, + want: want{ + configMap: configMap, + }, + }, + } + + neededKeys := []string{TerraformRegistryConfig} + errKey := "terraformrc configuration" + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + configMap, err := GetSecretOrConfigMap(ctx, tc.args.k8sClient, false, tc.args.TerraformRCConfigMapReference, neededKeys, errKey) + + if err != nil { + assert.EqualError(t, err, tc.want.errMsg) + } + if tc.want.configMap != nil { + assert.EqualValues(t, configMap, tc.want.configMap) + } + }) + } +} + +func TestTerraformCredentialsHelperConfigMap(t *testing.T) { + ctx := context.Background() + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + k8sClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + configMap := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "terraform-credentials-helper", + }, + Data: map[string]string{ + "terraform-credentials-artifactory": "tfrc", + }, + } + + assert.Nil(t, k8sClient.Create(ctx, configMap)) + assert.Nil(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(configMap), configMap)) + + type args struct { + k8sClient client.Client + TerraformCredentialsHelperConfigMapReference *corev1.SecretReference + } + + type want struct { + configMap *corev1.ConfigMap + errMsg string + } + + testcases := []struct { + name string + args args + want want + }{ + { + name: "configmap not found", + args: args{ + k8sClient: k8sClient, + TerraformCredentialsHelperConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry", + }, + }, + want: want{ + errMsg: "Failed to get terraform credentials helper configmap: configmaps \"terraform-registry\" not found", + }, + }, + { + name: "configmap exists", + args: args{ + k8sClient: k8sClient, + TerraformCredentialsHelperConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-credentials-helper", + }, + }, + want: want{ + configMap: configMap, + }, + }, + } + + neededKeys := []string{} + errKey := "terraform credentials helper" + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + configMap, err := GetSecretOrConfigMap(ctx, tc.args.k8sClient, false, tc.args.TerraformCredentialsHelperConfigMapReference, neededKeys, errKey) + + if err != nil { + assert.EqualError(t, err, tc.want.errMsg) + } + if tc.want.configMap != nil { + assert.EqualValues(t, configMap, tc.want.configMap) + } + }) + } +} + +func TestCheckValidateSecretAndConfigMap(t *testing.T) { + ctx := context.Background() + scheme := runtime.NewScheme() + corev1.AddToScheme(scheme) + k8sClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + privateKey := []byte("aaa") + knownHosts := []byte("zzz") + secretGitCreds := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "git-ssh", + }, + Data: map[string][]byte{ + corev1.SSHAuthPrivateKey: privateKey, + "known_hosts": knownHosts, + }, + Type: corev1.SecretTypeSSHAuth, + } + assert.Nil(t, k8sClient.Create(ctx, secretGitCreds)) + assert.Nil(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(secretGitCreds), secretGitCreds)) + + credentialstfrcjson := []byte("tfcreds") + terraformrc := []byte("tfrc") + secretTerraformCredentials := &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "terraform-creds", + }, + Data: map[string][]byte{ + "credentials.tfrc.json": credentialstfrcjson, + "terraformrc": terraformrc, + }, + Type: corev1.SecretTypeSSHAuth, + } + assert.Nil(t, k8sClient.Create(ctx, secretTerraformCredentials)) + assert.Nil(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(secretTerraformCredentials), secretTerraformCredentials)) + + configMapTerraformRC := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "terraform-registry-config", + }, + Data: map[string]string{ + ".terraformrc": "tfrc", + }, + } + + assert.Nil(t, k8sClient.Create(ctx, configMapTerraformRC)) + assert.Nil(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(configMapTerraformRC), configMapTerraformRC)) + + configMapCredentialsHelper := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "default", + Name: "terraform-credentials-helper", + }, + Data: map[string]string{ + "terraform-credentials-artifactory": "tfrc", + }, + } + + assert.Nil(t, k8sClient.Create(ctx, configMapCredentialsHelper)) + assert.Nil(t, k8sClient.Get(ctx, client.ObjectKeyFromObject(configMapCredentialsHelper), configMapCredentialsHelper)) + + type args struct { + k8sClient client.Client + meta TFConfigurationMeta + } + + type want struct { + configMap *corev1.ConfigMap + errMsg string + } + + testcases := []struct { + name string + args args + want want + }{ + { + name: "configmap not found", + args: args{ + k8sClient: k8sClient, + meta: TFConfigurationMeta{ + Name: "a", + ConfigurationCMName: "b", + BusyboxImage: "c", + GitImage: "d", + Namespace: "e", + TerraformImage: "f", + RemoteGit: "g", + GitCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "git-ssh", + }, + TerraformCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-creds", + }, + TerraformRCConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry-config", + }, + TerraformCredentialsHelperConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-credentials-helper", + }, + }, + }, + want: want{ + errMsg: "NoError", + }, + }, + { + name: "terraform credentials configmap not found", + args: args{ + k8sClient: k8sClient, + meta: TFConfigurationMeta{ + Name: "a", + ConfigurationCMName: "b", + BusyboxImage: "c", + GitImage: "d", + Namespace: "e", + TerraformImage: "f", + RemoteGit: "g", + GitCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "git-ssh", + }, + TerraformCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-creds", + }, + TerraformRCConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry-config", + }, + TerraformCredentialsHelperConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry", + }, + }, + }, + want: want{ + errMsg: "Failed to get terraform credentials helper configmap: configmaps \"terraform-registry\" not found", + }, + }, + { + name: "terraformrc configmap not found", + args: args{ + k8sClient: k8sClient, + meta: TFConfigurationMeta{ + Name: "a", + ConfigurationCMName: "b", + BusyboxImage: "c", + GitImage: "d", + Namespace: "e", + TerraformImage: "f", + RemoteGit: "g", + GitCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "git-ssh", + }, + TerraformCredentialsSecretReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-creds", + }, + TerraformRCConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-registry", + }, + TerraformCredentialsHelperConfigMapReference: &corev1.SecretReference{ + Namespace: "default", + Name: "terraform-credentials-helper", + }, + }, + }, + want: want{ + errMsg: "Failed to get terraformrc configuration configmap: configmaps \"terraform-registry\" not found", + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + err := tc.args.meta.validateSecretAndConfigMap(ctx, k8sClient) + if err != nil { + assert.EqualError(t, err, tc.want.errMsg) + } + }) + } + }