From 49b466fff0372356024d0ef9282a0a31eabe9bc4 Mon Sep 17 00:00:00 2001 From: Lucas Marques Date: Fri, 18 Oct 2024 15:48:48 +0200 Subject: [PATCH] feat: add support for opentofu (#328) --- api/v1alpha1/common.go | 39 ++- api/v1alpha1/common_test.go | 287 +++++++++++++++--- api/v1alpha1/terraformlayer_types.go | 4 + api/v1alpha1/terraformrepository_types.go | 4 + api/v1alpha1/zz_generated.deepcopy.go | 30 +- docs/getting-started.md | 4 +- docs/guides/iac-drift-detection.md | 7 +- docs/guides/pr-mr-workflow.md | 2 + docs/user-guide/additionnal-trigger-path.md | 14 +- docs/user-guide/override-runner.md | 6 +- docs/user-guide/private-modules.md | 14 +- docs/user-guide/terraform-version.md | 7 +- internal/annotations/testdata/layer.yaml | 7 +- .../controllers/terraformlayer/controller.go | 15 +- .../terraformlayer/testdata/cleanup-case.yaml | 2 + .../terraformlayer/testdata/error-case.yaml | 33 +- .../terraformlayer/testdata/merge-case.yaml | 7 +- .../terraformlayer/testdata/nominal-case.yaml | 56 ++-- .../terraformlayer/testdata/repository.yaml | 2 + .../testdata/unknown-cases.yaml | 9 +- .../testdata/webhook-issue-case.yaml | 7 +- .../testdata/nominal-case.yaml | 24 +- .../testdata/repository.yaml | 2 + .../testdata/repository-layers.yaml | 20 +- internal/lock/testdata/layer.yaml | 6 +- internal/runner/actions.go | 32 +- internal/runner/runner.go | 2 +- internal/runner/testdata/error-case.yaml | 2 + internal/runner/testdata/nominal-case.yaml | 8 +- internal/runner/testdata/repo.yaml | 2 + internal/runner/tools/common.go | 4 +- internal/runner/tools/install.go | 45 ++- internal/runner/tools/opentofu/opentofu.go | 79 +++++ internal/runner/tools/terraform/terraform.go | 8 + .../runner/tools/terragrunt/terragrunt.go | 53 ++-- internal/server/api/layers.go | 4 +- internal/server/api/repositories.go | 2 +- internal/utils/cmd/cmd.go | 11 + internal/webhook/event/pullrequest.go | 8 +- internal/webhook/event/push.go | 16 +- internal/webhook/event/testdata/layers.yaml | 42 +-- .../webhook/event/testdata/repositories.yaml | 12 + ...terraform.padok.cloud_terraformlayers.yaml | 34 ++- ...orm.padok.cloud_terraformrepositories.yaml | 34 ++- manifests/install.yaml | 56 +++- 45 files changed, 801 insertions(+), 261 deletions(-) create mode 100644 internal/runner/tools/opentofu/opentofu.go create mode 100644 internal/utils/cmd/cmd.go diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go index f4b360a9..4753a523 100644 --- a/api/v1alpha1/common.go +++ b/api/v1alpha1/common.go @@ -44,25 +44,48 @@ type OnErrorRemediationStrategy struct { } type TerraformConfig struct { - Version string `json:"version,omitempty"` - TerragruntConfig TerragruntConfig `json:"terragrunt,omitempty"` + Version string `json:"version,omitempty"` + Enabled *bool `json:"enabled,omitempty"` } -type TerragruntConfig struct { +type OpenTofuConfig struct { + Version string `json:"version,omitempty"` Enabled *bool `json:"enabled,omitempty"` +} + +type TerragruntConfig struct { Version string `json:"version,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +func GetTerraformEnabled(repository *TerraformRepository, layer *TerraformLayer) bool { + if isEnabled(layer.Spec.OpenTofuConfig.Enabled) { + return false + } + return chooseBool(repository.Spec.TerraformConfig.Enabled, layer.Spec.TerraformConfig.Enabled, false) +} + +func GetOpenTofuEnabled(repository *TerraformRepository, layer *TerraformLayer) bool { + if isEnabled(layer.Spec.TerraformConfig.Enabled) { + return false + } + return chooseBool(repository.Spec.OpenTofuConfig.Enabled, layer.Spec.OpenTofuConfig.Enabled, false) } func GetTerraformVersion(repository *TerraformRepository, layer *TerraformLayer) string { return chooseString(repository.Spec.TerraformConfig.Version, layer.Spec.TerraformConfig.Version) } -func GetTerragruntVersion(repository *TerraformRepository, layer *TerraformLayer) string { - return chooseString(repository.Spec.TerraformConfig.TerragruntConfig.Version, layer.Spec.TerraformConfig.TerragruntConfig.Version) +func GetOpenTofuVersion(repository *TerraformRepository, layer *TerraformLayer) string { + return chooseString(repository.Spec.OpenTofuConfig.Version, layer.Spec.OpenTofuConfig.Version) } func GetTerragruntEnabled(repository *TerraformRepository, layer *TerraformLayer) bool { - return chooseBool(repository.Spec.TerraformConfig.TerragruntConfig.Enabled, layer.Spec.TerraformConfig.TerragruntConfig.Enabled, false) + return chooseBool(repository.Spec.TerragruntConfig.Enabled, layer.Spec.TerragruntConfig.Enabled, false) +} + +func GetTerragruntVersion(repository *TerraformRepository, layer *TerraformLayer) string { + return chooseString(repository.Spec.TerragruntConfig.Version, layer.Spec.TerragruntConfig.Version) } func GetOverrideRunnerSpec(repository *TerraformRepository, layer *TerraformLayer) OverrideRunnerSpec { @@ -99,6 +122,10 @@ func GetAutoApplyEnabled(repo *TerraformRepository, layer *TerraformLayer) bool return chooseBool(repo.Spec.RemediationStrategy.AutoApply, layer.Spec.RemediationStrategy.AutoApply, false) } +func isEnabled(enabled *bool) bool { + return enabled != nil && *enabled +} + func chooseBool(a, b *bool, defaultVal bool) bool { if b != nil { return *b diff --git a/api/v1alpha1/common_test.go b/api/v1alpha1/common_test.go index 4da313be..50486449 100644 --- a/api/v1alpha1/common_test.go +++ b/api/v1alpha1/common_test.go @@ -9,6 +9,233 @@ import ( configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1" ) +func TestGetTerraformEnabled(t *testing.T) { + tt := []struct { + name string + repository *configv1alpha1.TerraformRepository + layer *configv1alpha1.TerraformLayer + expected bool + }{ + {"OnlyRepositoryEnabling", + &configv1alpha1.TerraformRepository{ + Spec: configv1alpha1.TerraformRepositorySpec{ + TerraformConfig: configv1alpha1.TerraformConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + &configv1alpha1.TerraformLayer{}, + true, + }, + {"OnlyLayerEnabling", + &configv1alpha1.TerraformRepository{}, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + TerraformConfig: configv1alpha1.TerraformConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + true, + }, + // Edge case: Merging config from repository and layer should + // never set several base execution tools to true at the same time. + {"OverrideRepositoryTerraformWithLayerOpenTofu", + &configv1alpha1.TerraformRepository{ + Spec: configv1alpha1.TerraformRepositorySpec{ + TerraformConfig: configv1alpha1.TerraformConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + false, + }, + {"OverrideRepositoryOpentofuWithLayerTerraform", + &configv1alpha1.TerraformRepository{ + Spec: configv1alpha1.TerraformRepositorySpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + TerraformConfig: configv1alpha1.TerraformConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + true, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + result := configv1alpha1.GetTerraformEnabled(tc.repository, tc.layer) + if tc.expected != result { + t.Errorf("different base exec enabled status computed: expected %t go %t", tc.expected, result) + } + }) + } +} + +func TestGetOpenTofuEnabled(t *testing.T) { + tt := []struct { + name string + repository *configv1alpha1.TerraformRepository + layer *configv1alpha1.TerraformLayer + expected bool + }{ + {"OnlyRepositoryEnabling", + &configv1alpha1.TerraformRepository{ + Spec: configv1alpha1.TerraformRepositorySpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + &configv1alpha1.TerraformLayer{}, + true, + }, + {"OnlyLayerEnabling", + &configv1alpha1.TerraformRepository{}, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + true, + }, + // Edge case: Merging config from repository and layer should + // never set several base execution tools to true at the same time. + {"OverrideRepositoryTerraformWithLayerOpenTofu", + &configv1alpha1.TerraformRepository{ + Spec: configv1alpha1.TerraformRepositorySpec{ + TerraformConfig: configv1alpha1.TerraformConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + true, + }, + {"OverrideRepositoryOpentofuWithLayerTerraform", + &configv1alpha1.TerraformRepository{ + Spec: configv1alpha1.TerraformRepositorySpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + TerraformConfig: configv1alpha1.TerraformConfig{ + Enabled: &[]bool{true}[0], + }, + }, + }, + false, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + result := configv1alpha1.GetOpenTofuEnabled(tc.repository, tc.layer) + if tc.expected != result { + t.Errorf("different base exec enabled status computed: expected %t go %t", tc.expected, result) + } + }) + } +} + +func TestGetOpenTofuVersion(t *testing.T) { + tt := []struct { + name string + repository *configv1alpha1.TerraformRepository + layer *configv1alpha1.TerraformLayer + expectedVersion string + }{ + { + "OnlyRepositoryVersion", + &configv1alpha1.TerraformRepository{ + Spec: configv1alpha1.TerraformRepositorySpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Version: "1.0.1", + }, + }, + }, + &configv1alpha1.TerraformLayer{}, + "1.0.1", + }, + { + "OnlyLayerVersion", + &configv1alpha1.TerraformRepository{}, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Version: "1.0.1", + }, + }, + }, + "1.0.1", + }, + { + "OnlyLayerVersion", + &configv1alpha1.TerraformRepository{}, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Version: "1.0.1", + }, + }, + }, + "1.0.1", + }, + { + "OverrideRepositoryWithLayer", + &configv1alpha1.TerraformRepository{ + Spec: configv1alpha1.TerraformRepositorySpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Version: "1.0.1", + }, + }, + }, + &configv1alpha1.TerraformLayer{ + Spec: configv1alpha1.TerraformLayerSpec{ + OpenTofuConfig: configv1alpha1.OpenTofuConfig{ + Version: "1.0.6", + }, + }, + }, + "1.0.6", + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + result := configv1alpha1.GetOpenTofuVersion(tc.repository, tc.layer) + if tc.expectedVersion != result { + t.Errorf("different version computed: expected %s go %s", tc.expectedVersion, result) + } + }) + } +} + func TestGetTerraformVersion(t *testing.T) { tt := []struct { name string @@ -81,10 +308,8 @@ func TestGetTerragruntVersion(t *testing.T) { "OnlyRepositoryVersion", &configv1alpha1.TerraformRepository{ Spec: configv1alpha1.TerraformRepositorySpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Version: "0.43.0", - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Version: "0.43.0", }, }, }, @@ -96,10 +321,8 @@ func TestGetTerragruntVersion(t *testing.T) { &configv1alpha1.TerraformRepository{}, &configv1alpha1.TerraformLayer{ Spec: configv1alpha1.TerraformLayerSpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Version: "0.43.0", - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Version: "0.43.0", }, }, }, @@ -109,19 +332,15 @@ func TestGetTerragruntVersion(t *testing.T) { "OverrideRepositoryWithLayer", &configv1alpha1.TerraformRepository{ Spec: configv1alpha1.TerraformRepositorySpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Version: "0.43.0", - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Version: "0.43.0", }, }, }, &configv1alpha1.TerraformLayer{ Spec: configv1alpha1.TerraformLayerSpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Version: "0.45.0", - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Version: "0.45.0", }, }, }, @@ -150,10 +369,8 @@ func TestGetTerragruntEnabled(t *testing.T) { "OnlyRepositoryEnabling", &configv1alpha1.TerraformRepository{ Spec: configv1alpha1.TerraformRepositorySpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Enabled: &[]bool{true}[0], - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Enabled: &[]bool{true}[0], }, }, }, @@ -165,10 +382,8 @@ func TestGetTerragruntEnabled(t *testing.T) { &configv1alpha1.TerraformRepository{}, &configv1alpha1.TerraformLayer{ Spec: configv1alpha1.TerraformLayerSpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Enabled: &[]bool{true}[0], - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Enabled: &[]bool{true}[0], }, }, }, @@ -178,19 +393,15 @@ func TestGetTerragruntEnabled(t *testing.T) { "DisabledInRepositoryEnabledInLayer", &configv1alpha1.TerraformRepository{ Spec: configv1alpha1.TerraformRepositorySpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Enabled: &[]bool{false}[0], - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Enabled: &[]bool{false}[0], }, }, }, &configv1alpha1.TerraformLayer{ Spec: configv1alpha1.TerraformLayerSpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Enabled: &[]bool{true}[0], - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Enabled: &[]bool{true}[0], }, }, }, @@ -200,19 +411,15 @@ func TestGetTerragruntEnabled(t *testing.T) { "EnabledInRepositoryDisabledInLayer", &configv1alpha1.TerraformRepository{ Spec: configv1alpha1.TerraformRepositorySpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Enabled: &[]bool{true}[0], - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Enabled: &[]bool{true}[0], }, }, }, &configv1alpha1.TerraformLayer{ Spec: configv1alpha1.TerraformLayerSpec{ - TerraformConfig: configv1alpha1.TerraformConfig{ - TerragruntConfig: configv1alpha1.TerragruntConfig{ - Enabled: &[]bool{false}[0], - }, + TerragruntConfig: configv1alpha1.TerragruntConfig{ + Enabled: &[]bool{false}[0], }, }, }, diff --git a/api/v1alpha1/terraformlayer_types.go b/api/v1alpha1/terraformlayer_types.go index 4dee2958..daa95b4a 100644 --- a/api/v1alpha1/terraformlayer_types.go +++ b/api/v1alpha1/terraformlayer_types.go @@ -24,6 +24,8 @@ import ( // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. // TerraformLayerSpec defines the desired state of TerraformLayer +// +kubebuilder:validation:XValidation:rule="!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) && has(self.opentofu.enabled) && self.terraform.enabled == true && self.opentofu.enabled == true)",message="Both terraform.enabled and opentofu.enabled cannot be true at the same time" +// +kubebuilder:validation:XValidation:rule="!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) && has(self.opentofu.enabled) && self.terraform.enabled == false && self.opentofu.enabled == false)",message="Both terraform.enabled and opentofu.enabled cannot be false at the same time" type TerraformLayerSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file @@ -31,6 +33,8 @@ type TerraformLayerSpec struct { Path string `json:"path,omitempty"` Branch string `json:"branch,omitempty"` TerraformConfig TerraformConfig `json:"terraform,omitempty"` + OpenTofuConfig OpenTofuConfig `json:"opentofu,omitempty"` + TerragruntConfig TerragruntConfig `json:"terragrunt,omitempty"` Repository TerraformLayerRepository `json:"repository,omitempty"` RemediationStrategy RemediationStrategy `json:"remediationStrategy,omitempty"` OverrideRunnerSpec OverrideRunnerSpec `json:"overrideRunnerSpec,omitempty"` diff --git a/api/v1alpha1/terraformrepository_types.go b/api/v1alpha1/terraformrepository_types.go index 41e44494..f36a488f 100644 --- a/api/v1alpha1/terraformrepository_types.go +++ b/api/v1alpha1/terraformrepository_types.go @@ -24,12 +24,16 @@ import ( // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. // TerraformRepositorySpec defines the desired state of TerraformRepository +// +kubebuilder:validation:XValidation:rule="!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) && has(self.opentofu.enabled) && self.terraform.enabled == true && self.opentofu.enabled == true)",message="Both terraform.enabled and opentofu.enabled cannot be true at the same time" +// +kubebuilder:validation:XValidation:rule="!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) && has(self.opentofu.enabled) && self.terraform.enabled == false && self.opentofu.enabled == false)",message="Both terraform.enabled and opentofu.enabled cannot be false at the same time" 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"` TerraformConfig TerraformConfig `json:"terraform,omitempty"` + TerragruntConfig TerragruntConfig `json:"terragrunt,omitempty"` + OpenTofuConfig OpenTofuConfig `json:"opentofu,omitempty"` RemediationStrategy RemediationStrategy `json:"remediationStrategy,omitempty"` OverrideRunnerSpec OverrideRunnerSpec `json:"overrideRunnerSpec,omitempty"` RunHistoryPolicy RunHistoryPolicy `json:"runHistoryPolicy,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3630e412..9686d221 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -105,6 +105,26 @@ func (in *OnErrorRemediationStrategy) DeepCopy() *OnErrorRemediationStrategy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OpenTofuConfig) DeepCopyInto(out *OpenTofuConfig) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OpenTofuConfig. +func (in *OpenTofuConfig) DeepCopy() *OpenTofuConfig { + if in == nil { + return nil + } + out := new(OpenTofuConfig) + in.DeepCopyInto(out) + return out +} + // 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 @@ -218,7 +238,11 @@ func (in *RunHistoryPolicy) DeepCopy() *RunHistoryPolicy { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TerraformConfig) DeepCopyInto(out *TerraformConfig) { *out = *in - in.TerragruntConfig.DeepCopyInto(&out.TerragruntConfig) + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TerraformConfig. @@ -325,6 +349,8 @@ func (in *TerraformLayerRun) DeepCopy() *TerraformLayerRun { func (in *TerraformLayerSpec) DeepCopyInto(out *TerraformLayerSpec) { *out = *in in.TerraformConfig.DeepCopyInto(&out.TerraformConfig) + in.OpenTofuConfig.DeepCopyInto(&out.OpenTofuConfig) + in.TerragruntConfig.DeepCopyInto(&out.TerragruntConfig) out.Repository = in.Repository in.RemediationStrategy.DeepCopyInto(&out.RemediationStrategy) in.OverrideRunnerSpec.DeepCopyInto(&out.OverrideRunnerSpec) @@ -547,6 +573,8 @@ func (in *TerraformRepositorySpec) DeepCopyInto(out *TerraformRepositorySpec) { *out = *in out.Repository = in.Repository in.TerraformConfig.DeepCopyInto(&out.TerraformConfig) + in.TerragruntConfig.DeepCopyInto(&out.TerragruntConfig) + in.OpenTofuConfig.DeepCopyInto(&out.OpenTofuConfig) in.RemediationStrategy.DeepCopyInto(&out.RemediationStrategy) in.OverrideRunnerSpec.DeepCopyInto(&out.OverrideRunnerSpec) in.RunHistoryPolicy.DeepCopyInto(&out.RunHistoryPolicy) diff --git a/docs/getting-started.md b/docs/getting-started.md index f18d86b1..83442c2e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -47,7 +47,7 @@ stringData: -----END OPENSSH PRIVATE KEY----- ``` -Then, create a `TerraformRepository` Kubernetes resource: +Then, create a `TerraformRepository` Kubernetes resource. The `spec.terraform.enabled` set the repository as a terraform repository (as opposed to an opentofu repository). This setting will propagate to all layers linked to this repository by default, but can be overridden at the layer level. ```yaml apiVersion: config.terraform.padok.cloud/v1alpha1 @@ -59,6 +59,8 @@ spec: repository: url: secretName: burrito-repo + terraform: + enabled: true ``` !!! info diff --git a/docs/guides/iac-drift-detection.md b/docs/guides/iac-drift-detection.md index 76d9cbea..351b8fc4 100644 --- a/docs/guides/iac-drift-detection.md +++ b/docs/guides/iac-drift-detection.md @@ -66,7 +66,8 @@ Create a TerraformRepository resource in the `burrito-system` namespace: kubectl apply -f https://raw.githubusercontent.com/padok-team/burrito/main/docs/examples/terraform-repository.yaml ``` -Here is the content of the `TerraformRepository` resource that you have created +Here is the content of the `TerraformRepository` resource that you have created. It references the GitHub repository containing the Terraform code. +It also specifies that the IaC is Terraform code (as opposed to OpenTofu code). This setting will propagate to all layers linked to this repository by default, but can be overridden at the layer level. ```yaml apiVersion: config.terraform.padok.cloud/v1alpha1 @@ -77,9 +78,11 @@ metadata: spec: repository: url: https://github.com/padok-team/burrito-examples + terraform: + enabled: true ``` -Create a `TerraformLayer` resource in the `burrito-system` namespace, referencing the `TerraformRepository` you just created. For now, the `autoApply` is set to false, so the layer will only plan the Terraform code and not apply it. +Create a `TerraformLayer` resource in the `burrito-system` namespace, referencing the `TerraformRepository` you just created. For now, the `autoApply` is set to false, so the layer will only plan the Terraform code and not apply it. ```bash kubectl apply -f https://raw.githubusercontent.com/padok-team/burrito/main/docs/examples/terraform-layer.yaml diff --git a/docs/guides/pr-mr-workflow.md b/docs/guides/pr-mr-workflow.md index bdb1db66..75eeb11f 100644 --- a/docs/guides/pr-mr-workflow.md +++ b/docs/guides/pr-mr-workflow.md @@ -21,6 +21,8 @@ metadata: spec: repository: url: https://github.com//burrito-examples + terraform: + enabled: true ``` You will also need to setup a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) to allow Burrito to comment on your PRs/MRs. Follow the instructions in the [PR/MR workflow](../operator-manual/pr-mr-workflow.md#configuration) section of the operator manual to set up the GitHub app. diff --git a/docs/user-guide/additionnal-trigger-path.md b/docs/user-guide/additionnal-trigger-path.md index 427c3b0e..23710880 100644 --- a/docs/user-guide/additionnal-trigger-path.md +++ b/docs/user-guide/additionnal-trigger-path.md @@ -15,10 +15,11 @@ metadata: name: random-pets-terragrunt spec: terraform: + enabled: true version: "1.3.1" - terragrunt: - enabled: true - version: "0.45.4" + terragrunt: + enabled: true + version: "0.45.4" remediationStrategy: autoApply: true path: "terragrunt/random-pets/test" @@ -41,10 +42,11 @@ metadata: config.terraform.padok.cloud/additionnal-trigger-paths: "modules/random-pets" spec: terraform: + enabled: true version: "1.3.1" - terragrunt: - enabled: true - version: "0.45.4" + terragrunt: + enabled: true + version: "0.45.4" remediationStrategy: autoApply: true path: "terragrunt/random-pets/test" diff --git a/docs/user-guide/override-runner.md b/docs/user-guide/override-runner.md index 4dfba3f7..e239e2e2 100644 --- a/docs/user-guide/override-runner.md +++ b/docs/user-guide/override-runner.md @@ -34,6 +34,8 @@ metadata: spec: repository: url: https://github.com/padok-team/burrito + terraform: + enabled: true overrideRunnerSpec: imagePullSecrets: - name: ghcr-creds @@ -71,7 +73,9 @@ metadata: spec: repository: url: https://github.com/padok-team/burrito - overridePodSpec: + terraform: + enabled: true + overrideRunnerSpec: imagePullSecrets: - name: ghcr-creds tolerations: diff --git a/docs/user-guide/private-modules.md b/docs/user-guide/private-modules.md index 395bfa94..807fb6d9 100644 --- a/docs/user-guide/private-modules.md +++ b/docs/user-guide/private-modules.md @@ -47,10 +47,11 @@ metadata: name: terragrunt-private-module spec: terraform: + enabled: true version: "1.3.1" - terragrunt: - enabled: true - version: "0.45.4" + terragrunt: + enabled: true + version: "0.45.4" remediationStrategy: autoApply: true path: "terragrunt/random-pets-private-module/test" @@ -111,9 +112,10 @@ metadata: spec: terraform: version: "1.3.1" - terragrunt: - enabled: true - version: "0.45.4" + enabled: true + terragrunt: + enabled: true + version: "0.45.4" remediationStrategy: autoApply: true path: "terragrunt/random-pets-private-module-ssh/test" diff --git a/docs/user-guide/terraform-version.md b/docs/user-guide/terraform-version.md index 1d3f7f2d..e13dc583 100644 --- a/docs/user-guide/terraform-version.md +++ b/docs/user-guide/terraform-version.md @@ -24,9 +24,10 @@ metadata: spec: terraform: version: "~> 1.3.0" - terragrunt: - enabled: true - version: "0.44.5" + enabled: true + terragrunt: + enabled: true + version: "0.44.5" remediationStrategy: autoApply: false path: "internal/e2e/testdata/terragrunt/random-pets/prod" diff --git a/internal/annotations/testdata/layer.yaml b/internal/annotations/testdata/layer.yaml index a84e3f02..a19b7d4d 100644 --- a/internal/annotations/testdata/layer.yaml +++ b/internal/annotations/testdata/layer.yaml @@ -12,7 +12,8 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 diff --git a/internal/controllers/terraformlayer/controller.go b/internal/controllers/terraformlayer/controller.go index ed0fe345..fcfb43b0 100644 --- a/internal/controllers/terraformlayer/controller.go +++ b/internal/controllers/terraformlayer/controller.go @@ -18,6 +18,7 @@ package terraformlayer import ( "context" + e "errors" "time" "github.com/google/go-cmp/cmp" @@ -93,7 +94,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{RequeueAfter: r.Config.Controller.Timers.OnError}, err } if locked { - log.Infof("terraform layer %s is locked, skipping reconciliation.", layer.Name) + log.Infof("TerraformLayer %s is locked, skipping reconciliation.", layer.Name) return ctrl.Result{RequeueAfter: r.Config.Controller.Timers.WaitAction}, nil } repository := &configv1alpha1.TerraformRepository{} @@ -110,6 +111,11 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu log.Errorf("failed to get TerraformRepository linked to layer %s: %s", layer.Name, err) return ctrl.Result{RequeueAfter: r.Config.Controller.Timers.OnError}, err } + err = validateLayerConfig(layer, repository) + if err != nil { + r.Recorder.Event(layer, corev1.EventTypeWarning, "Reconciliation", err.Error()) + return ctrl.Result{}, err + } state, conditions := r.GetState(ctx, layer) lastResult, err := r.Datastore.GetPlan(layer.Namespace, layer.Name, layer.Status.LastRun.Name, "", "short") if err != nil { @@ -226,3 +232,10 @@ func ignorePredicate() predicate.Predicate { }, } } + +func validateLayerConfig(layer *configv1alpha1.TerraformLayer, repository *configv1alpha1.TerraformRepository) error { + if !configv1alpha1.GetTerraformEnabled(repository, layer) && !configv1alpha1.GetOpenTofuEnabled(repository, layer) { + return e.New("TerraformLayer configuration is invalid: Neither Terraform nor OpenTofu is enabled for this layer. Please enable one of them in the TerraformLayer or the TerraformRepository referenced by the layer.") + } + return nil +} diff --git a/internal/controllers/terraformlayer/testdata/cleanup-case.yaml b/internal/controllers/terraformlayer/testdata/cleanup-case.yaml index 1098cc88..8dfe7c8d 100644 --- a/internal/controllers/terraformlayer/testdata/cleanup-case.yaml +++ b/internal/controllers/terraformlayer/testdata/cleanup-case.yaml @@ -6,6 +6,8 @@ metadata: spec: repository: url: git@github.com:padok-team/burrito-examples.git + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer diff --git a/internal/controllers/terraformlayer/testdata/error-case.yaml b/internal/controllers/terraformlayer/testdata/error-case.yaml index f4451013..aa9c1be4 100644 --- a/internal/controllers/terraformlayer/testdata/error-case.yaml +++ b/internal/controllers/terraformlayer/testdata/error-case.yaml @@ -19,10 +19,10 @@ spec: repository: name: burrito namespace: default + terragrunt: + enabled: true + version: 0.45.4 terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 --- apiVersion: config.terraform.padok.cloud/v1alpha1 @@ -49,10 +49,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -75,10 +75,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -105,10 +106,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 status: lastRun: name: run-failed @@ -139,10 +141,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 status: lastRun: name: run-failed diff --git a/internal/controllers/terraformlayer/testdata/merge-case.yaml b/internal/controllers/terraformlayer/testdata/merge-case.yaml index 336c6393..6971bab6 100644 --- a/internal/controllers/terraformlayer/testdata/merge-case.yaml +++ b/internal/controllers/terraformlayer/testdata/merge-case.yaml @@ -12,10 +12,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 overrideRunnerSpec: imagePullSecrets: - name: gh-token diff --git a/internal/controllers/terraformlayer/testdata/nominal-case.yaml b/internal/controllers/terraformlayer/testdata/nominal-case.yaml index 7fb66dff..927044ef 100644 --- a/internal/controllers/terraformlayer/testdata/nominal-case.yaml +++ b/internal/controllers/terraformlayer/testdata/nominal-case.yaml @@ -12,10 +12,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -36,10 +37,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 status: lastRun: name: run-succeeded @@ -64,10 +66,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 status: lastRun: name: run-succeeded @@ -97,10 +100,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -116,10 +120,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -135,10 +140,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: coordination.k8s.io/v1 kind: Lease @@ -172,10 +178,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -193,10 +200,11 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 status: lastRun: name: run-running diff --git a/internal/controllers/terraformlayer/testdata/repository.yaml b/internal/controllers/terraformlayer/testdata/repository.yaml index 26f81d17..eab4432f 100644 --- a/internal/controllers/terraformlayer/testdata/repository.yaml +++ b/internal/controllers/terraformlayer/testdata/repository.yaml @@ -12,3 +12,5 @@ spec: repository: secretName: burrito-repo url: git@github.com:padok-team/burrito-examples.git + terraform: + enabled: true diff --git a/internal/controllers/terraformlayer/testdata/unknown-cases.yaml b/internal/controllers/terraformlayer/testdata/unknown-cases.yaml index b42c1327..3c1035c6 100644 --- a/internal/controllers/terraformlayer/testdata/unknown-cases.yaml +++ b/internal/controllers/terraformlayer/testdata/unknown-cases.yaml @@ -13,7 +13,8 @@ spec: name: non-existent namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 - version: 1.3.1 \ No newline at end of file + enabled: true + version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 diff --git a/internal/controllers/terraformlayer/testdata/webhook-issue-case.yaml b/internal/controllers/terraformlayer/testdata/webhook-issue-case.yaml index 13abe1c8..f431ca57 100644 --- a/internal/controllers/terraformlayer/testdata/webhook-issue-case.yaml +++ b/internal/controllers/terraformlayer/testdata/webhook-issue-case.yaml @@ -26,7 +26,8 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 + enabled: true version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 diff --git a/internal/controllers/terraformpullrequest/testdata/nominal-case.yaml b/internal/controllers/terraformpullrequest/testdata/nominal-case.yaml index 80c8663b..8b283468 100644 --- a/internal/controllers/terraformpullrequest/testdata/nominal-case.yaml +++ b/internal/controllers/terraformpullrequest/testdata/nominal-case.yaml @@ -30,10 +30,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -49,10 +49,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformPullRequest @@ -91,10 +91,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformPullRequest @@ -133,10 +133,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformPullRequest diff --git a/internal/controllers/terraformpullrequest/testdata/repository.yaml b/internal/controllers/terraformpullrequest/testdata/repository.yaml index 929a58cf..fb641c3f 100644 --- a/internal/controllers/terraformpullrequest/testdata/repository.yaml +++ b/internal/controllers/terraformpullrequest/testdata/repository.yaml @@ -12,3 +12,5 @@ spec: repository: secretName: burrito-repo url: git@github.com:padok-team/burrito-examples.git + terraform: + enabled: true diff --git a/internal/controllers/terraformrun/testdata/repository-layers.yaml b/internal/controllers/terraformrun/testdata/repository-layers.yaml index 6410c661..8f712dcb 100644 --- a/internal/controllers/terraformrun/testdata/repository-layers.yaml +++ b/internal/controllers/terraformrun/testdata/repository-layers.yaml @@ -16,6 +16,8 @@ spec: repository: secretName: burrito-repo url: git@github.com:padok-team/burrito-examples.git + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -31,10 +33,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -52,10 +54,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -71,7 +73,7 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 diff --git a/internal/lock/testdata/layer.yaml b/internal/lock/testdata/layer.yaml index a9440a48..cae5bf55 100644 --- a/internal/lock/testdata/layer.yaml +++ b/internal/lock/testdata/layer.yaml @@ -12,10 +12,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformRun diff --git a/internal/runner/actions.go b/internal/runner/actions.go index 69a27dac..f2697fa9 100644 --- a/internal/runner/actions.go +++ b/internal/runner/actions.go @@ -52,24 +52,24 @@ func (r *Runner) ExecAction() error { err := annotations.Add(context.TODO(), r.Client, r.Layer, ann) if err != nil { - log.Errorf("could not update terraform layer annotations: %s", err) + log.Errorf("could not update TerraformLayer annotations: %s", err) return err } - log.Infof("successfully updated terraform layer annotations") + log.Infof("successfully updated TerraformLayer annotations") return nil } // Run the `init` command func (r *Runner) ExecInit() error { - log.Infof("launching terraform init in %s", r.workingDir) + log.Infof("launching %s init in %s", r.exec.TenvName(), r.workingDir) if r.exec == nil { err := errors.New("terraform or terragrunt binary not installed") return err } err := r.exec.Init(r.workingDir) if err != nil { - log.Errorf("error executing terraform init: %s", err) + log.Errorf("error executing %s init: %s", r.exec.TenvName(), err) return err } return nil @@ -78,24 +78,24 @@ func (r *Runner) ExecInit() error { // Run the `plan` command and save the plan artifact in the datastore // Returns the sha256 sum of the plan artifact func (r *Runner) execPlan() (string, error) { - log.Infof("starting terraform plan") + log.Infof("running %s plan", r.exec.TenvName()) if r.exec == nil { err := errors.New("terraform or terragrunt binary not installed") return "", err } err := r.exec.Plan(PlanArtifact) if err != nil { - log.Errorf("error executing terraform plan: %s", err) + log.Errorf("error executing %s plan: %s", r.exec.TenvName(), err) return "", err } planJsonBytes, err := r.exec.Show(PlanArtifact, "json") if err != nil { - log.Errorf("error getting terraform plan json: %s", err) + log.Errorf("error getting %s plan json: %s", r.exec.TenvName(), err) return "", err } prettyPlan, err := r.exec.Show(PlanArtifact, "pretty") if err != nil { - log.Errorf("error getting terraform pretty plan: %s", err) + log.Errorf("error getting %s pretty plan: %s", r.exec.TenvName(), err) return "", err } log.Infof("sending plan to datastore") @@ -106,7 +106,7 @@ func (r *Runner) execPlan() (string, error) { plan := &tfjson.Plan{} err = json.Unmarshal(planJsonBytes, plan) if err != nil { - log.Errorf("error parsing terraform json plan: %s", err) + log.Errorf("error parsing %s json plan: %s", r.exec.TenvName(), err) return "", err } _, shortDiff := runnerutils.GetDiff(plan) @@ -129,19 +129,19 @@ func (r *Runner) execPlan() (string, error) { log.Errorf("could not put plan binary in cache: %s", err) return "", err } - log.Infof("terraform plan ran successfully") + log.Infof("%s plan ran successfully", r.exec.TenvName()) return b64.StdEncoding.EncodeToString(sum[:]), nil } // Run the `apply` command, by default with the plan artifact from the previous plan run // Returns the sha256 sum of the plan artifact used func (r *Runner) execApply() (string, error) { - log.Infof("starting terraform apply") + log.Infof("starting %s apply", r.exec.TenvName()) if r.exec == nil { - err := errors.New("terraform or terragrunt binary not installed") + err := fmt.Errorf("%s binary not installed", r.exec.TenvName()) return "", err } - log.Info("getting plan binary in datastore at key") + log.Infof("getting plan binary in datastore at key %s/%s/%s/%s", r.Layer.Namespace, r.Layer.Name, r.Run.Spec.Artifact.Run, r.Run.Spec.Artifact.Attempt) plan, err := r.Datastore.GetPlan(r.Layer.Namespace, r.Layer.Name, r.Run.Spec.Artifact.Run, r.Run.Spec.Artifact.Attempt, "bin") if err != nil { log.Errorf("could not get plan artifact: %s", err) @@ -153,7 +153,7 @@ func (r *Runner) execApply() (string, error) { log.Errorf("could not write plan artifact to disk: %s", err) return "", err } - log.Print("launching terraform apply") + log.Infof("launching %s apply", r.exec.TenvName()) if configv1alpha1.GetApplyWithoutPlanArtifactEnabled(r.Repository, r.Layer) { log.Infof("applying without reusing plan artifact from previous plan run") err = r.exec.Apply("") @@ -161,13 +161,13 @@ func (r *Runner) execApply() (string, error) { err = r.exec.Apply(PlanArtifact) } if err != nil { - log.Errorf("error executing terraform apply: %s", err) + log.Errorf("error executing %s apply: %s", r.exec.TenvName(), err) return "", err } err = r.Datastore.PutPlan(r.Layer.Namespace, r.Layer.Name, r.Run.Name, strconv.Itoa(r.Run.Status.Retries), "short", []byte("Apply Successful")) if err != nil { log.Errorf("could not put short plan in datastore: %s", err) } - log.Infof("terraform apply ran successfully") + log.Infof("%s apply ran successfully", r.exec.TenvName()) return b64.StdEncoding.EncodeToString(sum[:]), nil } diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 66ccdd6a..add69d12 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -18,7 +18,7 @@ import ( type Runner struct { config *config.Config - exec tools.TerraformExec + exec tools.BaseExec Datastore datastore.Client Client client.Client Layer *configv1alpha1.TerraformLayer diff --git a/internal/runner/testdata/error-case.yaml b/internal/runner/testdata/error-case.yaml index a6f126c7..92de69eb 100644 --- a/internal/runner/testdata/error-case.yaml +++ b/internal/runner/testdata/error-case.yaml @@ -6,6 +6,8 @@ metadata: spec: repository: url: https://github.com/padok-team/burrito-examples-non-existent-repo + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer diff --git a/internal/runner/testdata/nominal-case.yaml b/internal/runner/testdata/nominal-case.yaml index acefe2f1..8f0dc178 100644 --- a/internal/runner/testdata/nominal-case.yaml +++ b/internal/runner/testdata/nominal-case.yaml @@ -11,6 +11,8 @@ spec: namespace: default remediationStrategy: applyWithoutPlanArtifact: true + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformRun @@ -49,9 +51,9 @@ spec: applyWithoutPlanArtifact: true terraform: version: "1.7.5" - terragrunt: - enabled: true - version: "0.66.9" + terragrunt: + enabled: true + version: "0.66.9" --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformRun diff --git a/internal/runner/testdata/repo.yaml b/internal/runner/testdata/repo.yaml index 43053ace..2367b622 100644 --- a/internal/runner/testdata/repo.yaml +++ b/internal/runner/testdata/repo.yaml @@ -6,3 +6,5 @@ metadata: spec: repository: url: https://github.com/padok-team/burrito-examples + terraform: + enabled: true diff --git a/internal/runner/tools/common.go b/internal/runner/tools/common.go index 0770acaf..69003256 100644 --- a/internal/runner/tools/common.go +++ b/internal/runner/tools/common.go @@ -1,8 +1,10 @@ package tools -type TerraformExec interface { +type BaseExec interface { Init(string) error Plan(string) error Apply(string) error Show(string, string) ([]byte, error) + TenvName() string + GetExecPath() string } diff --git a/internal/runner/tools/install.go b/internal/runner/tools/install.go index 2e74cdad..76438084 100644 --- a/internal/runner/tools/install.go +++ b/internal/runner/tools/install.go @@ -1,11 +1,13 @@ package tools import ( + "errors" "os" "path/filepath" "github.com/hashicorp/hcl/v2/hclparse" configv1alpha1 "github.com/padok-team/burrito/api/v1alpha1" + ot "github.com/padok-team/burrito/internal/runner/tools/opentofu" tf "github.com/padok-team/burrito/internal/runner/tools/terraform" tg "github.com/padok-team/burrito/internal/runner/tools/terragrunt" log "github.com/sirupsen/logrus" @@ -70,7 +72,7 @@ func install(binaryPath, toolName, version string) error { } // If not already on the system, install Terraform and, if needed, Terragrunt binaries -func InstallBinaries(layer *configv1alpha1.TerraformLayer, repo *configv1alpha1.TerraformRepository, binaryPath, workingDir string) (TerraformExec, error) { +func InstallBinaries(layer *configv1alpha1.TerraformLayer, repo *configv1alpha1.TerraformRepository, binaryPath, workingDir string) (BaseExec, error) { cwd, err := os.Getwd() if err != nil { log.Errorf("error getting current working directory: %s", err) @@ -88,18 +90,32 @@ func InstallBinaries(layer *configv1alpha1.TerraformLayer, repo *configv1alpha1. } }() - terraformVersion := configv1alpha1.GetTerraformVersion(repo, layer) - terraformVersion, err = detect(binaryPath, "terraform", terraformVersion) - if err != nil { - return nil, err + var baseExec BaseExec + var baseExecVersion string + if configv1alpha1.GetTerraformEnabled(repo, layer) { + baseExecVersion, err = detect(binaryPath, "terraform", configv1alpha1.GetTerraformVersion(repo, layer)) + if err != nil { + return nil, err + } + baseExec = &tf.Terraform{ + ExecPath: filepath.Join(binaryPath, "Terraform", baseExecVersion, "terraform"), + } + } else if configv1alpha1.GetOpenTofuEnabled(repo, layer) { + baseExecVersion, err = detect(binaryPath, "tofu", configv1alpha1.GetOpenTofuVersion(repo, layer)) + if err != nil { + return nil, err + } + baseExec = &ot.OpenTofu{ + ExecPath: filepath.Join(binaryPath, "OpenTofu", baseExecVersion, "tofu"), + } + } else { + return nil, errors.New("Please enable either Terraform or OpenTofu in the repository or layer configuration") } - if err := install(binaryPath, "terraform", terraformVersion); err != nil { + log.Infof("using %s version %s", baseExec.TenvName(), baseExecVersion) + + if err := install(binaryPath, baseExec.TenvName(), baseExecVersion); err != nil { return nil, err } - tf := &tf.Terraform{ - ExecPath: filepath.Join(binaryPath, "Terraform", terraformVersion, "terraform"), - } - if configv1alpha1.GetTerragruntEnabled(repo, layer) { terragruntVersion := configv1alpha1.GetTerragruntVersion(repo, layer) terragruntVersion, err := detect(binaryPath, "terragrunt", terragruntVersion) @@ -109,12 +125,11 @@ func InstallBinaries(layer *configv1alpha1.TerraformLayer, repo *configv1alpha1. if err := install(binaryPath, "terragrunt", terragruntVersion); err != nil { return nil, err } + log.Infof("using Terragrunt version %s as wrapper for %s", terragruntVersion, baseExec.TenvName()) return &tg.Terragrunt{ - ExecPath: filepath.Join(binaryPath, "Terragrunt", terragruntVersion, "terragrunt"), - Terraform: tf, + ExecPath: filepath.Join(binaryPath, "Terragrunt", terragruntVersion, "terragrunt"), + ChildExecPath: baseExec.GetExecPath(), }, nil } - - log.Infof("binaries successfully installed") - return tf, nil + return baseExec, nil } diff --git a/internal/runner/tools/opentofu/opentofu.go b/internal/runner/tools/opentofu/opentofu.go new file mode 100644 index 00000000..6acd4725 --- /dev/null +++ b/internal/runner/tools/opentofu/opentofu.go @@ -0,0 +1,79 @@ +package opentofu + +import ( + "errors" + "os/exec" + + c "github.com/padok-team/burrito/internal/utils/cmd" +) + +// The equivalent of tfexec for OpenTofu is not actively maintained. +// Switch to it when this repo is updated: https://github.com/tofu/tofu-exec + +type OpenTofu struct { + ExecPath string + WorkingDir string +} + +func (t *OpenTofu) TenvName() string { + return "tofu" +} + +func (t *OpenTofu) Init(workingDir string) error { + t.WorkingDir = workingDir + cmd := exec.Command(t.ExecPath, "init", "-upgrade") + c.Verbose(cmd) + cmd.Dir = workingDir + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +func (t *OpenTofu) Plan(planArtifactPath string) error { + cmd := exec.Command(t.ExecPath, "plan", "-out", planArtifactPath) + c.Verbose(cmd) + cmd.Dir = t.WorkingDir + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +func (t *OpenTofu) Apply(planArtifactPath string) error { + var cmd *exec.Cmd + if planArtifactPath != "" { + cmd = exec.Command(t.ExecPath, "apply", "-auto-approve", planArtifactPath) + } else { + cmd = exec.Command(t.ExecPath, "apply", "-auto-approve") + } + c.Verbose(cmd) + cmd.Dir = t.WorkingDir + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +func (t *OpenTofu) Show(planArtifactPath, mode string) ([]byte, error) { + var cmd *exec.Cmd + switch mode { + case "json": + cmd = exec.Command(t.ExecPath, "show", "-json", planArtifactPath) + case "pretty": + cmd = exec.Command(t.ExecPath, "show", planArtifactPath) + default: + return nil, errors.New("invalid mode") + } + + cmd.Dir = t.WorkingDir + out, err := cmd.Output() + if err != nil { + return nil, err + } + return out, nil +} + +func (t *OpenTofu) GetExecPath() string { + return t.ExecPath +} diff --git a/internal/runner/tools/terraform/terraform.go b/internal/runner/tools/terraform/terraform.go index 3884b29b..ae027237 100644 --- a/internal/runner/tools/terraform/terraform.go +++ b/internal/runner/tools/terraform/terraform.go @@ -15,6 +15,10 @@ type Terraform struct { ExecPath string } +func (t *Terraform) TenvName() string { + return "terraform" +} + func (t *Terraform) Init(workingDir string) error { exec, err := tfexec.NewTerraform(workingDir, t.ExecPath) if err != nil { @@ -75,6 +79,10 @@ func (t *Terraform) Show(planArtifactPath, mode string) ([]byte, error) { } } +func (t *Terraform) GetExecPath() string { + return t.ExecPath +} + func (t *Terraform) silent() { t.exec.SetStdout(io.Discard) t.exec.SetStderr(io.Discard) diff --git a/internal/runner/tools/terragrunt/terragrunt.go b/internal/runner/tools/terragrunt/terragrunt.go index 78f4d126..e6f9ae6a 100644 --- a/internal/runner/tools/terragrunt/terragrunt.go +++ b/internal/runner/tools/terragrunt/terragrunt.go @@ -2,38 +2,40 @@ package terragrunt import ( "errors" - "os" "os/exec" - "github.com/padok-team/burrito/internal/runner/tools/terraform" + c "github.com/padok-team/burrito/internal/utils/cmd" ) type Terragrunt struct { - ExecPath string - WorkingDir string - Terraform *terraform.Terraform + ExecPath string + WorkingDir string + ChildExecPath string } -func verbose(cmd *exec.Cmd) { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr +func (t *Terragrunt) TenvName() string { + return "terragrunt" } -func (t *Terragrunt) getDefaultOptions(command string) []string { +func (t *Terragrunt) getDefaultOptions(command string) ([]string, error) { return []string{ command, "--terragrunt-tfpath", - t.Terraform.ExecPath, + t.ChildExecPath, "--terragrunt-working-dir", t.WorkingDir, "-no-color", - } + }, nil } func (t *Terragrunt) Init(workingDir string) error { t.WorkingDir = workingDir - cmd := exec.Command(t.ExecPath, t.getDefaultOptions("init")...) - verbose(cmd) + options, err := t.getDefaultOptions("init") + if err != nil { + return err + } + cmd := exec.Command(t.ExecPath, options...) + c.Verbose(cmd) cmd.Dir = t.WorkingDir if err := cmd.Run(); err != nil { return err @@ -42,9 +44,13 @@ func (t *Terragrunt) Init(workingDir string) error { } func (t *Terragrunt) Plan(planArtifactPath string) error { - options := append(t.getDefaultOptions("plan"), "-out", planArtifactPath) + options, err := t.getDefaultOptions("plan") + if err != nil { + return err + } + options = append(options, "-out", planArtifactPath) cmd := exec.Command(t.ExecPath, options...) - verbose(cmd) + c.Verbose(cmd) cmd.Dir = t.WorkingDir if err := cmd.Run(); err != nil { return err @@ -53,13 +59,17 @@ func (t *Terragrunt) Plan(planArtifactPath string) error { } func (t *Terragrunt) Apply(planArtifactPath string) error { - options := append(t.getDefaultOptions("apply"), "-auto-approve") + options, err := t.getDefaultOptions("apply") + if err != nil { + return err + } + options = append(options, "-auto-approve") if planArtifactPath != "" { options = append(options, planArtifactPath) } cmd := exec.Command(t.ExecPath, options...) - verbose(cmd) + c.Verbose(cmd) cmd.Dir = t.WorkingDir if err := cmd.Run(); err != nil { return err @@ -68,7 +78,10 @@ func (t *Terragrunt) Apply(planArtifactPath string) error { } func (t *Terragrunt) Show(planArtifactPath, mode string) ([]byte, error) { - options := t.getDefaultOptions("show") + options, err := t.getDefaultOptions("show") + if err != nil { + return nil, err + } switch mode { case "json": options = append(options, "-json", planArtifactPath) @@ -86,3 +99,7 @@ func (t *Terragrunt) Show(planArtifactPath, mode string) ([]byte, error) { } return output, nil } + +func (t *Terragrunt) GetExecPath() string { + return t.ExecPath +} diff --git a/internal/server/api/layers.go b/internal/server/api/layers.go index f3d511a0..a6d58c42 100644 --- a/internal/server/api/layers.go +++ b/internal/server/api/layers.go @@ -46,14 +46,14 @@ func (a *API) getLayersAndRuns() ([]configv1alpha1.TerraformLayer, map[string]co layers := &configv1alpha1.TerraformLayerList{} err := a.Client.List(context.Background(), layers) if err != nil { - log.Errorf("could not list terraform layers: %s", err) + log.Errorf("could not list TerraformLayers: %s", err) return nil, nil, err } runs := &configv1alpha1.TerraformRunList{} indexedRuns := map[string]configv1alpha1.TerraformRun{} err = a.Client.List(context.Background(), runs) if err != nil { - log.Errorf("could not list terraform runs: %s", err) + log.Errorf("could not list TerraformRuns: %s", err) return nil, nil, err } for _, run := range runs.Items { diff --git a/internal/server/api/repositories.go b/internal/server/api/repositories.go index c3ee5d98..367e78cb 100644 --- a/internal/server/api/repositories.go +++ b/internal/server/api/repositories.go @@ -22,7 +22,7 @@ func (a *API) RepositoriesHandler(c echo.Context) error { repositories := &configv1alpha1.TerraformRepositoryList{} err := a.Client.List(context.Background(), repositories) if err != nil { - log.Errorf("could not list terraform repositories: %s", err) + log.Errorf("could not list TerraformRepositories: %s", err) return c.String(http.StatusInternalServerError, "could not list terraform repositories") } results := []repository{} diff --git a/internal/utils/cmd/cmd.go b/internal/utils/cmd/cmd.go new file mode 100644 index 00000000..d7e4d078 --- /dev/null +++ b/internal/utils/cmd/cmd.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "os" + "os/exec" +) + +func Verbose(cmd *exec.Cmd) { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr +} diff --git a/internal/webhook/event/pullrequest.go b/internal/webhook/event/pullrequest.go index 6e4b5e23..9215dea3 100644 --- a/internal/webhook/event/pullrequest.go +++ b/internal/webhook/event/pullrequest.go @@ -29,7 +29,7 @@ func (e *PullRequestEvent) Handle(c client.Client) error { repositories := &configv1alpha1.TerraformRepositoryList{} err := c.List(context.Background(), repositories) if err != nil { - log.Errorf("could not list terraform repositories: %s", err) + log.Errorf("could not list TerraformRepositories: %s", err) return err } affectedRepositories := e.getAffectedRepositories(repositories.Items) @@ -52,7 +52,7 @@ func (e *PullRequestEvent) Handle(c client.Client) error { func batchCreatePullRequests(ctx context.Context, c client.Client, prs []configv1alpha1.TerraformPullRequest) error { var errResult error for _, pr := range prs { - log.Infof("creating terraform pull request %s/%s", pr.Namespace, pr.Name) + log.Infof("creating TerraformPullRequest %s/%s", pr.Namespace, pr.Name) err := c.Create(ctx, &pr) if err != nil { errResult = multierror.Append(errResult, err) @@ -64,7 +64,7 @@ func batchCreatePullRequests(ctx context.Context, c client.Client, prs []configv func batchDeletePullRequests(ctx context.Context, c client.Client, prs []configv1alpha1.TerraformPullRequest) error { var errResult error for _, pr := range prs { - log.Infof("deleting terraform pull request %s/%s", pr.Namespace, pr.Name) + log.Infof("deleting TerraformPullRequest %s/%s", pr.Namespace, pr.Name) err := c.Delete(ctx, &pr) if err != nil { errResult = multierror.Append(errResult, err) @@ -104,7 +104,7 @@ func (e *PullRequestEvent) generateTerraformPullRequests(repositories []configv1 func (e *PullRequestEvent) getAffectedRepositories(repositories []configv1alpha1.TerraformRepository) []configv1alpha1.TerraformRepository { affectedRepositories := []configv1alpha1.TerraformRepository{} for _, repo := range repositories { - log.Infof("evaluating terraform repository %s for url %s", repo.Name, repo.Spec.Repository.Url) + log.Infof("evaluating TerraformRepository %s for url %s", repo.Name, repo.Spec.Repository.Url) log.Infof("comparing normalized url %s with received URL from payload %s", utils.NormalizeUrl(repo.Spec.Repository.Url), e.URL) if e.URL == utils.NormalizeUrl(repo.Spec.Repository.Url) { affectedRepositories = append(affectedRepositories, repo) diff --git a/internal/webhook/event/push.go b/internal/webhook/event/push.go index c4332bdc..bf151a27 100644 --- a/internal/webhook/event/push.go +++ b/internal/webhook/event/push.go @@ -25,19 +25,19 @@ func (e *PushEvent) Handle(c client.Client) error { repositories := &configv1alpha1.TerraformRepositoryList{} err := c.List(context.Background(), repositories) if err != nil { - log.Errorf("could not list terraform repositories: %s", err) + log.Errorf("could not list TerraformRepositories: %s", err) return err } layers := &configv1alpha1.TerraformLayerList{} err = c.List(context.Background(), layers) if err != nil { - log.Errorf("could not list terraform layers: %s", err) + log.Errorf("could not list TerraformLayers: %s", err) return err } prs := &configv1alpha1.TerraformPullRequestList{} err = c.List(context.Background(), prs) if err != nil { - log.Errorf("could not list terraform prs: %s", err) + log.Errorf("could not list TerraformPullRequests: %s", err) return err } affectedRepositories := e.getAffectedRepositories(repositories.Items) @@ -47,16 +47,16 @@ func (e *PushEvent) Handle(c client.Client) error { ann[annotations.LastBranchCommitDate] = date err := annotations.Add(context.TODO(), c, &repo, ann) if err != nil { - log.Errorf("could not add annotation to terraform repository %s", err) + log.Errorf("could not add annotation to TerraformRepository %s", err) return err } } for _, layer := range e.getAffectedLayers(layers.Items, affectedRepositories) { ann := map[string]string{} - log.Printf("evaluating terraform layer %s for revision %s", layer.Name, e.Revision) + log.Printf("evaluating TerraformLayer %s for revision %s", layer.Name, e.Revision) if layer.Spec.Branch != e.Revision { - log.Infof("branch %s for terraform layer %s not matching revision %s", layer.Spec.Branch, layer.Name, e.Revision) + log.Infof("branch %s for TerraformLayer %s not matching revision %s", layer.Spec.Branch, layer.Name, e.Revision) continue } ann[annotations.LastBranchCommit] = e.ChangeInfo.ShaAfter @@ -70,7 +70,7 @@ func (e *PushEvent) Handle(c client.Client) error { err := annotations.Add(context.TODO(), c, &layer, ann) if err != nil { - log.Errorf("could not add annotation to terraform layer %s", err) + log.Errorf("could not add annotation to TerraformLayer %s", err) return err } } @@ -81,7 +81,7 @@ func (e *PushEvent) Handle(c client.Client) error { ann[annotations.LastBranchCommitDate] = date err := annotations.Add(context.TODO(), c, &pr, ann) if err != nil { - log.Errorf("could not add annotation to terraform pr %s", err) + log.Errorf("could not add annotation to TerraformPullRequest %s", err) return err } } diff --git a/internal/webhook/event/testdata/layers.yaml b/internal/webhook/event/testdata/layers.yaml index cd6fb72e..5f0878fe 100644 --- a/internal/webhook/event/testdata/layers.yaml +++ b/internal/webhook/event/testdata/layers.yaml @@ -12,10 +12,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -34,10 +34,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -53,10 +53,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -72,10 +72,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -91,10 +91,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -112,10 +112,10 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformLayer @@ -133,7 +133,7 @@ spec: name: burrito namespace: default terraform: - terragrunt: - enabled: true - version: 0.45.4 version: 1.3.1 + terragrunt: + enabled: true + version: 0.45.4 diff --git a/internal/webhook/event/testdata/repositories.yaml b/internal/webhook/event/testdata/repositories.yaml index bf6a78fe..bfcd6439 100644 --- a/internal/webhook/event/testdata/repositories.yaml +++ b/internal/webhook/event/testdata/repositories.yaml @@ -12,6 +12,8 @@ spec: repository: secretName: burrito-repo url: git@github.com:padok-team/burrito-examples.git + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformRepository @@ -27,6 +29,8 @@ spec: repository: secretName: burrito-repo url: git@github.com:example/other-repo.git + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformRepository @@ -42,6 +46,8 @@ spec: repository: secretName: burrito-repo url: git@github.com:example/other-repo.git + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformRepository @@ -57,6 +63,8 @@ spec: repository: secretName: burrito-repo url: git@github.com:padok-team/burrito-closed-single-pr.git + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformRepository @@ -72,6 +80,8 @@ spec: repository: secretName: burrito-repo url: git@github.com:padok-team/burrito-closed-multi-pr.git + terraform: + enabled: true --- apiVersion: config.terraform.padok.cloud/v1alpha1 kind: TerraformRepository @@ -87,3 +97,5 @@ spec: repository: secretName: burrito-repo url: git@github.com:padok-team/burrito-closed-multi-pr.git + terraform: + enabled: true diff --git a/manifests/crds/config.terraform.padok.cloud_terraformlayers.yaml b/manifests/crds/config.terraform.padok.cloud_terraformlayers.yaml index 356aac04..b957f615 100644 --- a/manifests/crds/config.terraform.padok.cloud_terraformlayers.yaml +++ b/manifests/crds/config.terraform.padok.cloud_terraformlayers.yaml @@ -62,6 +62,13 @@ spec: properties: branch: type: string + opentofu: + properties: + enabled: + type: boolean + version: + type: string + type: object overrideRunnerSpec: properties: env: @@ -2231,17 +2238,30 @@ spec: type: object terraform: properties: - terragrunt: - properties: - enabled: - type: boolean - version: - type: string - type: object + enabled: + type: boolean + version: + type: string + type: object + terragrunt: + properties: + enabled: + type: boolean version: type: string type: object type: object + x-kubernetes-validations: + - message: Both terraform.enabled and opentofu.enabled cannot be true + at the same time + rule: '!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) + && has(self.opentofu.enabled) && self.terraform.enabled == true && + self.opentofu.enabled == true)' + - message: Both terraform.enabled and opentofu.enabled cannot be false + at the same time + rule: '!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) + && has(self.opentofu.enabled) && self.terraform.enabled == false && + self.opentofu.enabled == false)' status: description: TerraformLayerStatus defines the observed state of TerraformLayer properties: diff --git a/manifests/crds/config.terraform.padok.cloud_terraformrepositories.yaml b/manifests/crds/config.terraform.padok.cloud_terraformrepositories.yaml index 5e315ed1..aa1a32af 100644 --- a/manifests/crds/config.terraform.padok.cloud_terraformrepositories.yaml +++ b/manifests/crds/config.terraform.padok.cloud_terraformrepositories.yaml @@ -50,6 +50,13 @@ spec: spec: description: TerraformRepositorySpec defines the desired state of TerraformRepository properties: + opentofu: + properties: + enabled: + type: boolean + version: + type: string + type: object overrideRunnerSpec: properties: env: @@ -2217,17 +2224,30 @@ spec: type: object terraform: properties: - terragrunt: - properties: - enabled: - type: boolean - version: - type: string - type: object + enabled: + type: boolean + version: + type: string + type: object + terragrunt: + properties: + enabled: + type: boolean version: type: string type: object type: object + x-kubernetes-validations: + - message: Both terraform.enabled and opentofu.enabled cannot be true + at the same time + rule: '!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) + && has(self.opentofu.enabled) && self.terraform.enabled == true && + self.opentofu.enabled == true)' + - message: Both terraform.enabled and opentofu.enabled cannot be false + at the same time + rule: '!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) + && has(self.opentofu.enabled) && self.terraform.enabled == false && + self.opentofu.enabled == false)' status: description: TerraformRepositoryStatus defines the observed state of TerraformRepository properties: diff --git a/manifests/install.yaml b/manifests/install.yaml index dcd91f3d..b449da3d 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -62,6 +62,13 @@ spec: properties: branch: type: string + opentofu: + properties: + enabled: + type: boolean + version: + type: string + type: object overrideRunnerSpec: properties: env: @@ -2111,17 +2118,24 @@ spec: type: object terraform: properties: - terragrunt: - properties: - enabled: - type: boolean - version: - type: string - type: object + enabled: + type: boolean + version: + type: string + type: object + terragrunt: + properties: + enabled: + type: boolean version: type: string type: object type: object + x-kubernetes-validations: + - message: Both terraform.enabled and opentofu.enabled cannot be true at the same time + rule: '!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) && has(self.opentofu.enabled) && self.terraform.enabled == true && self.opentofu.enabled == true)' + - message: Both terraform.enabled and opentofu.enabled cannot be false at the same time + rule: '!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) && has(self.opentofu.enabled) && self.terraform.enabled == false && self.opentofu.enabled == false)' status: description: TerraformLayerStatus defines the observed state of TerraformLayer properties: @@ -2425,6 +2439,13 @@ spec: spec: description: TerraformRepositorySpec defines the desired state of TerraformRepository properties: + opentofu: + properties: + enabled: + type: boolean + version: + type: string + type: object overrideRunnerSpec: properties: env: @@ -4472,17 +4493,24 @@ spec: type: object terraform: properties: - terragrunt: - properties: - enabled: - type: boolean - version: - type: string - type: object + enabled: + type: boolean + version: + type: string + type: object + terragrunt: + properties: + enabled: + type: boolean version: type: string type: object type: object + x-kubernetes-validations: + - message: Both terraform.enabled and opentofu.enabled cannot be true at the same time + rule: '!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) && has(self.opentofu.enabled) && self.terraform.enabled == true && self.opentofu.enabled == true)' + - message: Both terraform.enabled and opentofu.enabled cannot be false at the same time + rule: '!(has(self.terraform) && has(self.opentofu) && has(self.terraform.enabled) && has(self.opentofu.enabled) && self.terraform.enabled == false && self.opentofu.enabled == false)' status: description: TerraformRepositoryStatus defines the observed state of TerraformRepository properties: