diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go new file mode 100644 index 00000000..55c0e636 --- /dev/null +++ b/api/v1alpha1/common.go @@ -0,0 +1,12 @@ +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" +) + +type OverrideRunnerSpec struct { + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` +} diff --git a/api/v1alpha1/terraformlayer_types.go b/api/v1alpha1/terraformlayer_types.go index cdf34986..df83d47d 100644 --- a/api/v1alpha1/terraformlayer_types.go +++ b/api/v1alpha1/terraformlayer_types.go @@ -34,7 +34,7 @@ type TerraformLayerSpec struct { Repository TerraformLayerRepository `json:"repository,omitempty"` RemediationStrategy TerraformLayerRemediationStrategy `json:"remediationStrategy,omitempty"` PlanOnPullRequest bool `json:"planOnPullRequest,omitempty"` - // RunnerPodTemplate corev1.PodSpec `json:"template,omitempty"` + OverrideRunnerSpec OverrideRunnerSpec `json:"overrideRunnerSpec,omitempty"` } type TerraformLayerRemediationStrategy struct { diff --git a/api/v1alpha1/terraformrepository_types.go b/api/v1alpha1/terraformrepository_types.go index 45057f00..f1367c0b 100644 --- a/api/v1alpha1/terraformrepository_types.go +++ b/api/v1alpha1/terraformrepository_types.go @@ -28,7 +28,8 @@ type TerraformRepositorySpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - Repository TerraformRepositoryRepository `json:"repository,omitempty"` + Repository TerraformRepositoryRepository `json:"repository,omitempty"` + OverrideRunnerSpec OverrideRunnerSpec `json:"overrideRunnerSpec,omitempty"` } type TerraformRepositoryRepository struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f7874666..14b024b4 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -22,16 +22,51 @@ limitations under the License. package v1alpha1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OverrideRunnerSpec) DeepCopyInto(out *OverrideRunnerSpec) { + *out = *in + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]v1.LocalObjectReference, len(*in)) + copy(*out, *in) + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]v1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OverrideRunnerSpec. +func (in *OverrideRunnerSpec) DeepCopy() *OverrideRunnerSpec { + if in == nil { + return nil + } + out := new(OverrideRunnerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TerraformLayer) DeepCopyInto(out *TerraformLayer) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -120,6 +155,7 @@ func (in *TerraformLayerSpec) DeepCopyInto(out *TerraformLayerSpec) { *out = *in out.Repository = in.Repository out.RemediationStrategy = in.RemediationStrategy + in.OverrideRunnerSpec.DeepCopyInto(&out.OverrideRunnerSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerraformLayerSpec. @@ -137,7 +173,7 @@ func (in *TerraformLayerStatus) DeepCopyInto(out *TerraformLayerStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) + *out = make([]metav1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -159,7 +195,7 @@ func (in *TerraformRepository) DeepCopyInto(out *TerraformRepository) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } @@ -232,6 +268,7 @@ func (in *TerraformRepositoryRepository) DeepCopy() *TerraformRepositoryReposito func (in *TerraformRepositorySpec) DeepCopyInto(out *TerraformRepositorySpec) { *out = *in out.Repository = in.Repository + in.OverrideRunnerSpec.DeepCopyInto(&out.OverrideRunnerSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerraformRepositorySpec. @@ -249,7 +286,7 @@ func (in *TerraformRepositoryStatus) DeepCopyInto(out *TerraformRepositoryStatus *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) + *out = make([]metav1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/config/crd/bases/config.terraform.padok.cloud_terraformlayers.yaml b/config/crd/bases/config.terraform.padok.cloud_terraformlayers.yaml index 5b0e01c8..9c3ccff1 100644 --- a/config/crd/bases/config.terraform.padok.cloud_terraformlayers.yaml +++ b/config/crd/bases/config.terraform.padok.cloud_terraformlayers.yaml @@ -37,6 +37,67 @@ spec: properties: branch: type: string + overrideRunnerSpec: + properties: + imagePullSecrets: + items: + description: LocalObjectReference contains enough information + to let you locate the referenced object inside the same namespace. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + type: array + nodeSelector: + additionalProperties: + type: string + type: object + serviceAccountName: + type: string + tolerations: + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object path: type: string planOnPullRequest: diff --git a/config/crd/bases/config.terraform.padok.cloud_terraformrepositories.yaml b/config/crd/bases/config.terraform.padok.cloud_terraformrepositories.yaml index 17d0f056..96fb389c 100644 --- a/config/crd/bases/config.terraform.padok.cloud_terraformrepositories.yaml +++ b/config/crd/bases/config.terraform.padok.cloud_terraformrepositories.yaml @@ -36,6 +36,67 @@ spec: spec: description: TerraformRepositorySpec defines the desired state of TerraformRepository properties: + overrideRunnerSpec: + properties: + imagePullSecrets: + items: + description: LocalObjectReference contains enough information + to let you locate the referenced object inside the same namespace. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + x-kubernetes-map-type: atomic + type: array + nodeSelector: + additionalProperties: + type: string + type: object + serviceAccountName: + type: string + tolerations: + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + type: object repository: properties: secretName: diff --git a/go.mod b/go.mod index ee1cea0d..63ff8604 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/hashicorp/hc-install v0.4.0 github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.17.3 - github.com/imdario/mergo v0.3.13 // indirect + github.com/imdario/mergo v0.3.13 github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/internal/controllers/terraformlayer/pod.go b/internal/controllers/terraformlayer/pod.go index d69f17e2..eefd8e0c 100644 --- a/internal/controllers/terraformlayer/pod.go +++ b/internal/controllers/terraformlayer/pod.go @@ -16,25 +16,22 @@ const ( ) func (r *Reconciler) getPod(layer *configv1alpha1.TerraformLayer, repository *configv1alpha1.TerraformRepository, action Action) corev1.Pod { - pod := corev1.Pod{ - Spec: defaultPodSpec(r.Config, layer, repository), - } - pod.SetNamespace(layer.Namespace) - pod.SetGenerateName(fmt.Sprintf("%s-%s-", layer.Name, action)) + defaultSpec := defaultPodSpec(r.Config, layer, repository) + switch action { case PlanAction: - pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, corev1.EnvVar{ + defaultSpec.Containers[0].Env = append(defaultSpec.Containers[0].Env, corev1.EnvVar{ Name: "BURRITO_RUNNER_ACTION", Value: "plan", }) case ApplyAction: - pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, corev1.EnvVar{ + defaultSpec.Containers[0].Env = append(defaultSpec.Containers[0].Env, corev1.EnvVar{ Name: "BURRITO_RUNNER_ACTION", Value: "apply", }) } if repository.Spec.Repository.SecretName != "" { - pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, corev1.EnvVar{ + defaultSpec.Containers[0].Env = append(defaultSpec.Containers[0].Env, corev1.EnvVar{ Name: "BURRITO_RUNNER_REPOSITORY_USERNAME", ValueFrom: &corev1.EnvVarSource{ SecretKeyRef: &corev1.SecretKeySelector{ @@ -46,7 +43,7 @@ func (r *Reconciler) getPod(layer *configv1alpha1.TerraformLayer, repository *co }, }, }) - pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, corev1.EnvVar{ + defaultSpec.Containers[0].Env = append(defaultSpec.Containers[0].Env, corev1.EnvVar{ Name: "BURRITO_RUNNER_REPOSITORY_PASSWORD", ValueFrom: &corev1.EnvVarSource{ SecretKeyRef: &corev1.SecretKeySelector{ @@ -58,7 +55,7 @@ func (r *Reconciler) getPod(layer *configv1alpha1.TerraformLayer, repository *co }, }, }) - pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, corev1.EnvVar{ + defaultSpec.Containers[0].Env = append(defaultSpec.Containers[0].Env, corev1.EnvVar{ Name: "BURRITO_RUNNER_REPOSITORY_SSHPRIVATEKEY", ValueFrom: &corev1.EnvVarSource{ SecretKeyRef: &corev1.SecretKeySelector{ @@ -71,6 +68,13 @@ func (r *Reconciler) getPod(layer *configv1alpha1.TerraformLayer, repository *co }, }) } + + pod := corev1.Pod{ + Spec: mergeSpecs(defaultSpec, repository.Spec.OverrideRunnerSpec, layer.Spec.OverrideRunnerSpec), + } + pod.SetNamespace(layer.Namespace) + pod.SetGenerateName(fmt.Sprintf("%s-%s-", layer.Name, action)) + return pod } @@ -157,3 +161,34 @@ func defaultPodSpec(config *config.Config, layer *configv1alpha1.TerraformLayer, }, } } + +func mergeSpecs(defaultSpec corev1.PodSpec, repositorySpec configv1alpha1.OverrideRunnerSpec, layerSpec configv1alpha1.OverrideRunnerSpec) corev1.PodSpec { + if len(repositorySpec.ImagePullSecrets) > 0 { + defaultSpec.ImagePullSecrets = repositorySpec.ImagePullSecrets + } + if len(layerSpec.ImagePullSecrets) > 0 { + defaultSpec.ImagePullSecrets = layerSpec.ImagePullSecrets + } + + if len(repositorySpec.Tolerations) > 0 { + defaultSpec.Tolerations = repositorySpec.Tolerations + } + if len(layerSpec.Tolerations) > 0 { + defaultSpec.Tolerations = layerSpec.Tolerations + } + + if len(repositorySpec.NodeSelector) > 0 { + defaultSpec.NodeSelector = repositorySpec.NodeSelector + } + if len(layerSpec.NodeSelector) > 0 { + defaultSpec.NodeSelector = layerSpec.NodeSelector + } + + if len(repositorySpec.ServiceAccountName) > 0 { + defaultSpec.ServiceAccountName = repositorySpec.ServiceAccountName + } + if len(layerSpec.ServiceAccountName) > 0 { + defaultSpec.ServiceAccountName = layerSpec.ServiceAccountName + } + return defaultSpec +}