diff --git a/internal/controller/atlasschema_controller.go b/internal/controller/atlasschema_controller.go index 9241716..e55bfc6 100644 --- a/internal/controller/atlasschema_controller.go +++ b/internal/controller/atlasschema_controller.go @@ -692,7 +692,7 @@ func (d *managedData) hasLint() bool { if env == nil { return false } - return env.Body().GetAttribute("lint") != nil + return searchBlock(env.Body(), hclwrite.NewBlock("lint", nil)) != nil } // hasLintDestructive returns true if the environment has a lint destructive policy. @@ -813,30 +813,42 @@ func (d *managedData) render(w io.Writer) error { // enableDestructive enables the linting policy for destructive changes. // If the force is set to true, it will override the existing value. func (d *managedData) enableDestructive(force bool) { - check := &dbv1alpha1.CheckConfig{Error: true} - destructive := &dbv1alpha1.Lint{Destructive: check} - switch { - case d.Policy == nil && !d.hasLint(): - d.Policy = &dbv1alpha1.Policy{Lint: destructive} - case !d.Policy.HasLint() && !d.hasLint(): - d.Policy.Lint = destructive - case !d.Policy.HasLintDestructive() && !d.hasLintDestructive(), force: + enable := func() { + check := &dbv1alpha1.CheckConfig{Error: true} + destructive := &dbv1alpha1.Lint{Destructive: check} + if d.Policy == nil { + d.Policy = &dbv1alpha1.Policy{Lint: destructive} + return + } + if d.Policy.Lint == nil { + d.Policy.Lint = destructive + return + } d.Policy.Lint.Destructive = check } + if !d.hasLint() || !d.hasLintDestructive() || force { + enable() + } } // setLintReview sets the lint review policy. // If the force is set to true, it will override the existing value. func (d *managedData) setLintReview(v dbv1alpha1.LintReview, force bool) { - lint := &dbv1alpha1.Lint{Review: v} - switch { - case d.Policy == nil && !d.hasLint(): - d.Policy = &dbv1alpha1.Policy{Lint: lint} - case !d.Policy.HasLint() && !d.hasLint(): - d.Policy.Lint = lint - case !d.Policy.HasLintReview() && !d.hasLintReview(), force: + enable := func() { + lint := &dbv1alpha1.Lint{Review: v} + if d.Policy == nil { + d.Policy = &dbv1alpha1.Policy{Lint: lint} + return + } + if d.Policy.Lint == nil { + d.Policy.Lint = lint + return + } d.Policy.Lint.Review = v } + if !d.hasLint() || !d.hasLintReview() || force { + enable() + } } // asBlocks returns the HCL block for the environment configuration. diff --git a/internal/controller/atlasschema_controller_test.go b/internal/controller/atlasschema_controller_test.go index bea073e..475ef0b 100644 --- a/internal/controller/atlasschema_controller_test.go +++ b/internal/controller/atlasschema_controller_test.go @@ -27,6 +27,8 @@ import ( "time" "ariga.io/atlas-go-sdk/atlasexec" + dbv1alpha1 "github.com/ariga/atlas-operator/api/v1alpha1" + "github.com/ariga/atlas-operator/internal/controller/watch" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -39,9 +41,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - - dbv1alpha1 "github.com/ariga/atlas-operator/api/v1alpha1" - "github.com/ariga/atlas-operator/internal/controller/watch" ) const ( @@ -886,3 +885,439 @@ func Test_truncateSQL(t *testing.T) { "CREATE TABLE BAR(id INT PRIMARY KEY);", }, 37)) } + +func Test_managedData_enableDestructive(t *testing.T) { + type fields struct { + data *managedData + } + type args struct { + force bool + } + tests := []struct { + name string + fields fields + args args + expected *bool + }{ + { + name: "empty managedData", + fields: fields{ + data: &managedData{}, + }, + expected: boolPtr(true), + }, + { + name: "destructive = false", + fields: fields{ + data: &managedData{ + Policy: &dbv1alpha1.Policy{ + Lint: &dbv1alpha1.Lint{ + Destructive: &dbv1alpha1.CheckConfig{Error: false}, + }, + }, + }, + }, + expected: boolPtr(false), + }, + { + name: "destructive = false, but \"force\" is enabled", + fields: fields{ + data: &managedData{ + Policy: &dbv1alpha1.Policy{ + Lint: &dbv1alpha1.Lint{ + Destructive: &dbv1alpha1.CheckConfig{Error: false}, + }, + }, + }, + }, + args: args{force: true}, + expected: boolPtr(true), + }, + { + name: "policy = nil", + fields: fields{ + data: &managedData{ + Policy: nil, + }, + }, + args: args{force: true}, + expected: boolPtr(true), + }, + { + name: "policy.lint = nil", + fields: fields{ + data: &managedData{ + Policy: &dbv1alpha1.Policy{ + Lint: nil, + }, + }, + }, + args: args{force: true}, + expected: boolPtr(true), + }, + { + name: "policy.lint.destructive = nil", + fields: fields{ + data: &managedData{ + Policy: &dbv1alpha1.Policy{ + Lint: &dbv1alpha1.Lint{ + Destructive: nil, + }, + }, + }, + }, + args: args{force: true}, + expected: boolPtr(true), + }, + { + name: "[Custom Config] destructive = false", + fields: fields{ + data: &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint { + destructive { + error = false + } + } + }`), + }, + }, + expected: nil, + }, + { + name: "[Custom Config] destructive = true", + fields: fields{ + data: &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint { + destructive { + error = true + } + } + }`), + }, + }, + expected: nil, + }, + { + name: "[Custom Config] destructive = false, but force", + fields: fields{ + data: &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint { + destructive { + error = false + } + } + }`), + }, + }, + args: args{force: true}, + expected: boolPtr(true), + }, + { + name: "[Custom Config] destructive = nil", + fields: fields{ + data: &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint {} + }`), + }, + }, + args: args{force: true}, + expected: boolPtr(true), + }, + { + name: "[Custom Config] lint = nil", + fields: fields{ + data: &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + }`), + }, + }, + args: args{force: true}, + expected: boolPtr(true), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fields.data.enableDestructive(tt.args.force) + if tt.fields.data.Policy == nil && tt.expected != nil { + t.Errorf("expected Policy to null but got %v", tt.fields.data.Policy) + } + if !tt.fields.data.Policy.HasLint() && tt.expected != nil { + t.Errorf("expected lint to null but got %v", tt.fields.data.Policy.Lint) + } + if !tt.fields.data.Policy.HasLintDestructive() && tt.expected != nil { + t.Errorf("expected destructive to null but got %v", tt.fields.data.Policy.Lint.Destructive) + } + if tt.fields.data.Policy.HasLintDestructive() && + tt.fields.data.Policy.Lint.Destructive.Error != *tt.expected { + t.Errorf("expected %v but got %v", *tt.expected, tt.fields.data.Policy.Lint.Destructive.Error) + } + }) + } +} + +func Test_managedData_setLintReview(t *testing.T) { + type fields struct { + data *managedData + } + type args struct { + review dbv1alpha1.LintReview + force bool + } + tests := []struct { + name string + fields fields + args args + expected *dbv1alpha1.LintReview + }{ + { + name: "empty managedData", + fields: fields{ + data: &managedData{}, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + }, + expected: reviewPtr(dbv1alpha1.LintReviewError), + }, + { + name: "lint_review = warning", + fields: fields{ + data: &managedData{ + Policy: &dbv1alpha1.Policy{ + Lint: &dbv1alpha1.Lint{ + Review: dbv1alpha1.LintReviewWarning, + }, + }, + }, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + }, + expected: reviewPtr(dbv1alpha1.LintReviewWarning), + }, + { + name: "lint_review = warning, but \"force\" is enabled", + fields: fields{ + data: &managedData{ + Policy: &dbv1alpha1.Policy{ + Lint: &dbv1alpha1.Lint{ + Review: dbv1alpha1.LintReviewWarning, + }, + }, + }, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + force: true, + }, + expected: reviewPtr(dbv1alpha1.LintReviewError), + }, + { + name: "policy = nil", + fields: fields{ + data: &managedData{ + Policy: nil, + }, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + }, + expected: reviewPtr(dbv1alpha1.LintReviewError), + }, + { + name: "policy.lint = nil", + fields: fields{ + data: &managedData{ + Policy: &dbv1alpha1.Policy{ + Lint: nil, + }, + }, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + }, + expected: reviewPtr(dbv1alpha1.LintReviewError), + }, + { + name: "policy.lint = nil", + fields: fields{ + data: &managedData{ + Policy: &dbv1alpha1.Policy{ + Lint: nil, + }, + }, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + }, + expected: reviewPtr(dbv1alpha1.LintReviewError), + }, + { + name: "[Custom Config] lint_review = warning", + fields: fields{ + data: &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint { + review = "warning" + } + }`), + }, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + }, + expected: nil, + }, + { + name: "[Custom Config] lint_review = warning, but \"force\" is enabled", + fields: fields{ + data: &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint { + review = "warning" + } + }`), + }, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + force: true, + }, + expected: reviewPtr(dbv1alpha1.LintReviewError), + }, + { + name: "[Custom Config] lint = nil", + fields: fields{ + data: &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint {} + }`), + }, + }, + args: args{ + review: dbv1alpha1.LintReviewError, + }, + expected: reviewPtr(dbv1alpha1.LintReviewError), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.fields.data.setLintReview(tt.args.review, tt.args.force) + if tt.fields.data.Policy == nil && tt.expected != nil { + t.Errorf("expected Policy to null but got %v", tt.fields.data.Policy) + } + if !tt.fields.data.Policy.HasLint() && tt.expected != nil { + t.Errorf("expected lint to null but got %v", tt.fields.data.Policy.Lint) + } + if !tt.fields.data.Policy.HasLintReview() && tt.expected != nil { + t.Errorf("expected lint_review to null but got %v", tt.fields.data.Policy.Lint.Review) + } + if tt.fields.data.Policy.HasLintReview() && + tt.fields.data.Policy.Lint.Review != *tt.expected { + t.Errorf("expected %v but got %v", *tt.expected, tt.fields.data.Policy.Lint.Review) + } + }) + } +} + +// This test ensures that lint policy is set correctly +func Test_managedData_destructive_with_review(t *testing.T) { + // Destructive and LintReview must be set when both are enabled. + data := &managedData{} + data.enableDestructive(true) + data.setLintReview(dbv1alpha1.LintReviewError, false) + require.True(t, data.Policy.Lint.Destructive.Error) + require.EqualValues(t, dbv1alpha1.LintReviewError, data.Policy.Lint.Review) + // Destructive must nil when only LintReview is set. + data = &managedData{} + data.setLintReview(dbv1alpha1.LintReviewError, false) + require.Nil(t, data.Policy.Lint.Destructive) + require.EqualValues(t, dbv1alpha1.LintReviewError, data.Policy.Lint.Review) + // LintReview must nil when only Destructive is set. + data = &managedData{} + data.enableDestructive(true) + require.True(t, data.Policy.Lint.Destructive.Error) + require.Empty(t, data.Policy.Lint.Review) + // [Custom Config] Destructive and LintReview must be set when both are enabled. + data = &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint { + destructive { + error = false + } + review = "warning" + } + } + `), + } + data.enableDestructive(true) + data.setLintReview(dbv1alpha1.LintReviewError, true) + require.True(t, data.Policy.Lint.Destructive.Error) + require.EqualValues(t, dbv1alpha1.LintReviewError, data.Policy.Lint.Review) + // [Custom Config] LintReview must nil when only Destructive is set. + data = &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint { + destructive { + error = false + } + review = "warning" + } + } + `), + } + data.enableDestructive(true) + data.setLintReview(dbv1alpha1.LintReviewError, false) + require.True(t, data.Policy.Lint.Destructive.Error) + require.Empty(t, data.Policy.Lint.Review) + // [Custom Config] destructive must nil when only LintReview is set. + data = &managedData{ + EnvName: "kubernetes", + Config: mustParseHCL(` + env "kubernetes" { + lint { + destructive { + error = false + } + review = "warning" + } + } + `), + } + data.enableDestructive(false) + data.setLintReview(dbv1alpha1.LintReviewError, true) + require.Nil(t, data.Policy.Lint.Destructive) + require.EqualValues(t, dbv1alpha1.LintReviewError, data.Policy.Lint.Review) +} + +func boolPtr(b bool) *bool { + return &b +} + +func reviewPtr(r dbv1alpha1.LintReview) *dbv1alpha1.LintReview { + return &r +}