diff --git a/internal/test/client.go b/internal/test/client.go index 9febf689..8980dd32 100644 --- a/internal/test/client.go +++ b/internal/test/client.go @@ -2,6 +2,7 @@ package test import ( + "context" "os" "testing" @@ -120,6 +121,22 @@ func WithInterceptorFuncs(f interceptor.Funcs) ClientSetupOption { } } +// WithNoFlagsInterceptor returns a ClientSetupOption that resets the +// ResourceVersion after each Update call, causing the updater to detect a +// no-op and return an error. Use this in "no-flags" test cases. +func WithNoFlagsInterceptor() ClientSetupOption { + return WithInterceptorFuncs(interceptor.Funcs{ + Update: func(ctx context.Context, c client.WithWatch, obj client.Object, opts ...client.UpdateOption) error { + oldRV := obj.GetResourceVersion() + if err := c.Update(ctx, obj, opts...); err != nil { + return err + } + obj.SetResourceVersion(oldRV) + return nil + }, + }) +} + func SetupClient(t *testing.T, opts ...ClientSetupOption) *api.Client { t.Helper() diff --git a/update/apiserviceaccount.go b/update/apiserviceaccount.go index 6830f262..3332aae7 100644 --- a/update/apiserviceaccount.go +++ b/update/apiserviceaccount.go @@ -27,13 +27,13 @@ func (cmd *apiServiceAccountCmd) Run(ctx context.Context, client *api.Client) er if !ok { return fmt.Errorf("resource is of type %T, expected %T", current, iam.APIServiceAccount{}) } - cmd.applyUpdates(asa) - return nil + return cmd.applyUpdates(asa) }).Update(ctx) } -func (cmd *apiServiceAccountCmd) applyUpdates(asa *iam.APIServiceAccount) { +func (cmd *apiServiceAccountCmd) applyUpdates(asa *iam.APIServiceAccount) error { if cmd.OrganizationAccess != nil { asa.Spec.ForProvider.OrganizationAccess = *cmd.OrganizationAccess } + return nil } diff --git a/update/apiserviceaccount_test.go b/update/apiserviceaccount_test.go index 41afc228..6588389c 100644 --- a/update/apiserviceaccount_test.go +++ b/update/apiserviceaccount_test.go @@ -25,7 +25,22 @@ func TestAPIServiceAccount(t *testing.T) { orig *iam.APIServiceAccount cmd apiServiceAccountCmd checkAPIServiceAccount func(t *testing.T, cmd apiServiceAccountCmd, orig, updated *iam.APIServiceAccount) + errorExpected bool + clientOpts []test.ClientSetupOption }{ + "no-flags": { + orig: &iam.APIServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: asaName, + Namespace: organization, + }, + }, + cmd: apiServiceAccountCmd{ + resourceCmd: resourceCmd{Name: asaName}, + }, + errorExpected: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, "all fields update": { orig: &iam.APIServiceAccount{ ObjectMeta: metav1.ObjectMeta{ @@ -50,15 +65,17 @@ func TestAPIServiceAccount(t *testing.T) { out := &bytes.Buffer{} tc.cmd.Writer = format.NewWriter(out) - apiClient := test.SetupClient(t, + opts := []test.ClientSetupOption{ test.WithObjects(tc.orig), test.WithOrganization(organization), test.WithDefaultProject(organization), test.WithKubeconfig(), - ) + } + opts = append(opts, tc.clientOpts...) + apiClient := test.SetupClient(t, opts...) - if err := tc.cmd.Run(t.Context(), apiClient); err != nil { - t.Fatal(err) + if err := tc.cmd.Run(t.Context(), apiClient); (err != nil) != tc.errorExpected { + t.Fatalf("apiServiceAccountCmd.Run() error = %v, errorExpected %v", err, tc.errorExpected) } updated := &iam.APIServiceAccount{} @@ -66,15 +83,17 @@ func TestAPIServiceAccount(t *testing.T) { t.Fatal(err) } - if tc.checkAPIServiceAccount != nil { - tc.checkAPIServiceAccount(t, tc.cmd, tc.orig, updated) - } + if !tc.errorExpected { + if tc.checkAPIServiceAccount != nil { + tc.checkAPIServiceAccount(t, tc.cmd, tc.orig, updated) + } - if !strings.Contains(out.String(), "updated") { - t.Errorf("expected output to contain 'updated', got %q", out.String()) - } - if !strings.Contains(out.String(), asaName) { - t.Errorf("expected output to contain %q, got %q", asaName, out.String()) + if !strings.Contains(out.String(), "updated") { + t.Errorf("expected output to contain 'updated', got %q", out.String()) + } + if !strings.Contains(out.String(), asaName) { + t.Errorf("expected output to contain %q, got %q", asaName, out.String()) + } } }) } diff --git a/update/application.go b/update/application.go index 8fe16ff6..cc63ab88 100644 --- a/update/application.go +++ b/update/application.go @@ -48,22 +48,22 @@ type applicationCmd struct { // structs. Due to the usage of kong these pointers will never be `nil`. // So checking for `nil` values can not be used to find out if some of // the struct fields have been set. - DeployJob *deployJob `embed:"" prefix:"deploy-job-"` - WorkerJob *workerJob `embed:"" prefix:"worker-job-"` - ScheduledJob *scheduledJob `embed:"" prefix:"scheduled-job-"` - DeleteWorkerJob *string `help:"Delete a worker job by name."` - DeleteScheduledJob *string `help:"Delete a scheduled job by name."` + DeployJob *deployJob `embed:"" prefix:"deploy-job-"` + WorkerJob *workerJob `embed:"" prefix:"worker-job-"` + ScheduledJob *scheduledJob `embed:"" prefix:"scheduled-job-"` + DeleteWorkerJob *string `help:"Delete a worker job by name."` + DeleteScheduledJob *string `help:"Delete a scheduled job by name."` Service application.ServiceMap `help:"Service reference to add/update in the form name=kind/target-name."` - DeleteService []string `help:"Service reference names to remove."` - RetryRelease *bool `help:"Retries release for the application." placeholder:"false"` - RetryBuild *bool `help:"Retries build for the application if set to true." placeholder:"false"` - Pause *bool `help:"Pauses the application if set to true. Stops all costs." placeholder:"false"` - GitInformationServiceURL string `help:"URL of the git information service." default:"https://git-info.deplo.io" env:"GIT_INFORMATION_SERVICE_URL" hidden:""` - SkipRepoAccessCheck bool `help:"Skip the git repository access check." default:"false"` - Debug bool `help:"Enable debug messages." default:"false"` - Language *string `help:"${app_language_help} Possible values: ${enum}" enum:"ruby,php,python,golang,nodejs,static,"` - DockerfileBuild dockerfileBuild `embed:""` - BuildpackStack *string `help:"${app_buildpack_stack_help} Possible values: ${enum}" enum:"paketo,heroku,"` + DeleteService []string `help:"Service reference names to remove."` + RetryRelease *bool `help:"Retries release for the application." placeholder:"false"` + RetryBuild *bool `help:"Retries build for the application if set to true." placeholder:"false"` + Pause *bool `help:"Pauses the application if set to true. Stops all costs." placeholder:"false"` + GitInformationServiceURL string `help:"URL of the git information service." default:"https://git-info.deplo.io" env:"GIT_INFORMATION_SERVICE_URL" hidden:""` + SkipRepoAccessCheck bool `help:"Skip the git repository access check." default:"false"` + Debug bool `help:"Enable debug messages." default:"false"` + Language *string `help:"${app_language_help} Possible values: ${enum}" enum:"ruby,php,python,golang,nodejs,static,"` + DockerfileBuild dockerfileBuild `embed:""` + BuildpackStack *string `help:"${app_buildpack_stack_help} Possible values: ${enum}" enum:"paketo,heroku,"` } type gitConfig struct { @@ -151,7 +151,9 @@ func (cmd *applicationCmd) Run(ctx context.Context, client *api.Client) error { if !ok { return fmt.Errorf("resource is of type %T, expected %T", current, apps.Application{}) } - cmd.applyUpdates(app) + if err := cmd.applyUpdates(app); err != nil { + return err + } // if there was no change in the git config, we don't have // anything to do anymore @@ -229,7 +231,7 @@ func (cmd *applicationCmd) Run(ctx context.Context, client *api.Client) error { return upd.Update(ctx) } -func (cmd *applicationCmd) applyUpdates(app *apps.Application) { +func (cmd *applicationCmd) applyUpdates(app *apps.Application) error { // rebuildNeeded determines if a rebuild trigger should be added rebuildNeeded := false if cmd.Git != nil { @@ -269,7 +271,9 @@ func (cmd *applicationCmd) applyUpdates(app *apps.Application) { app.Spec.ForProvider.BasicAuthPasswordChange = new(metav1.Now()) } if cmd.DeployJob != nil { - cmd.DeployJob.applyUpdates(&app.Spec.ForProvider.Config) + if err := cmd.DeployJob.applyUpdates(&app.Spec.ForProvider.Config); err != nil { + return err + } } if cmd.WorkerJob != nil && cmd.WorkerJob.changesGiven() { cmd.WorkerJob.applyUpdates(cmd.Writer, &app.Spec.ForProvider.Config) @@ -360,6 +364,7 @@ func (cmd *applicationCmd) applyUpdates(app *apps.Application) { app.Spec.ForProvider.Services, toAdd, cmd.DeleteService, cmd.Writer, ) } + return nil } func triggerTimestamp() string { @@ -390,12 +395,12 @@ func (h healthProbe) applyUpdates(cfg *apps.Config) { application.ApplyProbePatch(cfg, h.ToProbePatch()) } -func (job deployJob) applyUpdates(cfg *apps.Config) { +func (job deployJob) applyUpdates(cfg *apps.Config) error { if job.Enabled != nil && !*job.Enabled { // if enabled is explicitly set to false we set the DeployJob field to // nil on the API, to completely remove the object. cfg.DeployJob = nil - return + return nil } if job.Name != nil && len(*job.Name) != 0 { @@ -410,6 +415,7 @@ func (job deployJob) applyUpdates(cfg *apps.Config) { if job.Timeout != nil { ensureDeployJob(cfg).DeployJob.Timeout = &metav1.Duration{Duration: *job.Timeout} } + return nil } func ensureDeployJob(cfg *apps.Config) *apps.Config { diff --git a/update/application_test.go b/update/application_test.go index c1dac3a6..6f435a07 100644 --- a/update/application_test.go +++ b/update/application_test.go @@ -81,7 +81,18 @@ func TestApplication(t *testing.T) { checkSecret func(t *testing.T, cmd applicationCmd, authSecret *corev1.Secret) gitInformationServiceResponse test.GitInformationServiceResponse errorExpected bool + clientOpts []test.ClientSetupOption }{ + "no-flags": { + orig: existingApp, + cmd: applicationCmd{ + resourceCmd: resourceCmd{ + Name: existingApp.Name, + }, + }, + errorExpected: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, "change port": { orig: existingApp, cmd: applicationCmd{ @@ -761,9 +772,8 @@ func TestApplication(t *testing.T) { tc.gitAuth.ApplyToSecret(secret) objects = append(objects, secret) } - apiClient := test.SetupClient(t, - test.WithObjects(objects...), - ) + opts := append([]test.ClientSetupOption{test.WithObjects(objects...)}, tc.clientOpts...) + apiClient := test.SetupClient(t, opts...) if err := tc.cmd.Run(t.Context(), apiClient); err != nil { if tc.errorExpected { diff --git a/update/bucketuser.go b/update/bucketuser.go index 9581df7d..2e7fb5b9 100644 --- a/update/bucketuser.go +++ b/update/bucketuser.go @@ -29,9 +29,15 @@ func (cmd *bucketUserCmd) Run(ctx context.Context, client *api.Client) error { return fmt.Errorf("resource is of type %T, expected %T", current, storage.BucketUser{}) } - bu.Spec.ForProvider.ResetCredentials = cmd.ResetCredentials - return nil + return cmd.applyUpdates(bu) }) return upd.Update(ctx) } + +func (cmd *bucketUserCmd) applyUpdates(bu *storage.BucketUser) error { + if cmd.ResetCredentials != nil { + bu.Spec.ForProvider.ResetCredentials = cmd.ResetCredentials + } + return nil +} diff --git a/update/bucketuser_test.go b/update/bucketuser_test.go index ce185679..7bb13c63 100644 --- a/update/bucketuser_test.go +++ b/update/bucketuser_test.go @@ -49,6 +49,23 @@ func TestBucketUser(t *testing.T) { } } +func TestBucketUserNoFlags(t *testing.T) { + t.Parallel() + + apiClient := test.SetupClient(t, test.WithNoFlagsInterceptor()) + + created := bucketUser("user-noflags", apiClient.Project, "nine-es34") + if err := apiClient.Create(t.Context(), created); err != nil { + t.Fatalf("bucketuser create error, got: %s", err) + } + + out := &bytes.Buffer{} + cmd := bucketUserCmd{resourceCmd{Writer: format.NewWriter(out), Name: created.Name}, nil} + if err := cmd.Run(t.Context(), apiClient); err == nil { + t.Error("expected error when no flags provided, got nil") + } +} + func bucketUser(name, project, location string) *storage.BucketUser { return &storage.BucketUser{ ObjectMeta: metav1.ObjectMeta{ diff --git a/update/cloudvm_test.go b/update/cloudvm_test.go index 8e24a352..3e0048e6 100644 --- a/update/cloudvm_test.go +++ b/update/cloudvm_test.go @@ -17,12 +17,18 @@ func TestCloudVM(t *testing.T) { t.Parallel() tests := []struct { - name string - create infrastructure.CloudVirtualMachineParameters - update cloudVMCmd - want infrastructure.CloudVirtualMachineParameters - wantErr bool + name string + create infrastructure.CloudVirtualMachineParameters + update cloudVMCmd + want infrastructure.CloudVirtualMachineParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "simple", }, @@ -60,7 +66,7 @@ func TestCloudVM(t *testing.T) { tt.update.Writer = format.NewWriter(out) tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.CloudVirtualMachine(tt.update.Name, apiClient.Project, "nine-es34", tt.create.PowerState) created.Spec.ForProvider = tt.create diff --git a/update/grafana.go b/update/grafana.go index 9f418c4c..31b8482f 100644 --- a/update/grafana.go +++ b/update/grafana.go @@ -30,16 +30,16 @@ func (cmd *grafanaCmd) Run(ctx context.Context, client *api.Client) error { return fmt.Errorf("resource is of type %T, expected %T", current, observability.Grafana{}) } - cmd.applyUpdates(grafana) - return nil + return cmd.applyUpdates(grafana) }).Update(ctx) } -func (cmd *grafanaCmd) applyUpdates(grafana *observability.Grafana) { +func (cmd *grafanaCmd) applyUpdates(grafana *observability.Grafana) error { if cmd.AdminAccess != nil { grafana.Spec.ForProvider.EnableAdminAccess = *cmd.AdminAccess } if cmd.LocalUsers != nil { grafana.Spec.ForProvider.AllowLocalUsers = *cmd.LocalUsers } + return nil } diff --git a/update/grafana_test.go b/update/grafana_test.go index 43a904a1..57bf7f88 100644 --- a/update/grafana_test.go +++ b/update/grafana_test.go @@ -16,12 +16,18 @@ func TestGrafana(t *testing.T) { t.Parallel() tests := []struct { - name string - create observability.GrafanaParameters - update grafanaCmd - want observability.GrafanaParameters - wantErr bool + name string + create observability.GrafanaParameters + update grafanaCmd + want observability.GrafanaParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "simple", }, @@ -61,7 +67,7 @@ func TestGrafana(t *testing.T) { tt.update.Writer = format.NewWriter(out) tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.Grafana(tt.update.Name, apiClient.Project) created.Spec.ForProvider = tt.create diff --git a/update/keyvaluestore_test.go b/update/keyvaluestore_test.go index 3c90c80c..bd6e4aee 100644 --- a/update/keyvaluestore_test.go +++ b/update/keyvaluestore_test.go @@ -16,12 +16,18 @@ func TestKeyValueStore(t *testing.T) { t.Parallel() tests := []struct { - name string - create storage.KeyValueStoreParameters - update keyValueStoreCmd - want storage.KeyValueStoreParameters - wantErr bool + name string + create storage.KeyValueStoreParameters + update keyValueStoreCmd + want storage.KeyValueStoreParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "simple", }, @@ -128,7 +134,7 @@ func TestKeyValueStore(t *testing.T) { tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.KeyValueStore(tt.update.Name, apiClient.Project, meta.LocationNineES34) created.Spec.ForProvider = tt.create diff --git a/update/mysql.go b/update/mysql.go index 466104ed..42e4dfcc 100644 --- a/update/mysql.go +++ b/update/mysql.go @@ -53,14 +53,13 @@ func (cmd *mySQLCmd) Run(ctx context.Context, client *api.Client) error { cmd.SSHKeys = keys } - cmd.applyUpdates(mysql) - return nil + return cmd.applyUpdates(mysql) }) return upd.Update(ctx) } -func (cmd *mySQLCmd) applyUpdates(mysql *storage.MySQL) { +func (cmd *mySQLCmd) applyUpdates(mysql *storage.MySQL) error { if cmd.MachineType != nil { mysql.Spec.ForProvider.MachineType = infra.NewMachineType(*cmd.MachineType) } @@ -91,4 +90,5 @@ func (cmd *mySQLCmd) applyUpdates(mysql *storage.MySQL) { if cmd.KeepDailyBackups != nil { mysql.Spec.ForProvider.KeepDailyBackups = cmd.KeepDailyBackups } + return nil } diff --git a/update/mysql_test.go b/update/mysql_test.go index 36392834..f3e949ab 100644 --- a/update/mysql_test.go +++ b/update/mysql_test.go @@ -16,12 +16,18 @@ func TestMySQL(t *testing.T) { t.Parallel() tests := []struct { - name string - create storage.MySQLParameters - update mySQLCmd - want storage.MySQLParameters - wantErr bool + name string + create storage.MySQLParameters + update mySQLCmd + want storage.MySQLParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "simple", }, @@ -101,7 +107,7 @@ func TestMySQL(t *testing.T) { tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.MySQL(tt.update.Name, apiClient.Project, "nine-es34") created.Spec.ForProvider = tt.create diff --git a/update/mysqldatabase.go b/update/mysqldatabase.go index 2001a5b8..d08ef7e1 100644 --- a/update/mysqldatabase.go +++ b/update/mysqldatabase.go @@ -29,15 +29,15 @@ func (cmd *mysqlDatabaseCmd) Run(ctx context.Context, client *api.Client) error return fmt.Errorf("resource is of type %T, expected %T", current, storage.MySQLDatabase{}) } - cmd.applyUpdates(mysqlDatabase) - return nil + return cmd.applyUpdates(mysqlDatabase) }) return upd.Update(ctx) } -func (cmd *mysqlDatabaseCmd) applyUpdates(db *storage.MySQLDatabase) { +func (cmd *mysqlDatabaseCmd) applyUpdates(db *storage.MySQLDatabase) error { if cmd.BackupSchedule != nil { db.Spec.ForProvider.BackupSchedule = *cmd.BackupSchedule } + return nil } diff --git a/update/mysqldatabase_test.go b/update/mysqldatabase_test.go index 65dbad0b..d1b768a9 100644 --- a/update/mysqldatabase_test.go +++ b/update/mysqldatabase_test.go @@ -17,12 +17,18 @@ func TestMySQLDatabase(t *testing.T) { t.Parallel() tests := []struct { - name string - create storage.MySQLDatabaseParameters - update mysqlDatabaseCmd - want storage.MySQLDatabaseParameters - wantErr bool + name string + create storage.MySQLDatabaseParameters + update mysqlDatabaseCmd + want storage.MySQLDatabaseParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "simple", }, @@ -46,7 +52,7 @@ func TestMySQLDatabase(t *testing.T) { tt.update.Writer = format.NewWriter(out) tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.MySQLDatabase(tt.update.Name, apiClient.Project, "nine-es34") created.Spec.ForProvider = tt.create diff --git a/update/opensearch.go b/update/opensearch.go index 9559f3b8..765f4bb0 100644 --- a/update/opensearch.go +++ b/update/opensearch.go @@ -54,7 +54,6 @@ func (cmd *openSearchCmd) applyUpdates(os *storage.OpenSearch) error { for _, user := range *cmd.BucketUsers { bucketUsers = append(bucketUsers, user.LocalReference) } - os.Spec.ForProvider.BucketUsers = bucketUsers } diff --git a/update/opensearch_test.go b/update/opensearch_test.go index 73657547..bc36c7fb 100644 --- a/update/opensearch_test.go +++ b/update/opensearch_test.go @@ -16,12 +16,18 @@ func TestOpenSearch(t *testing.T) { t.Parallel() tests := []struct { - name string - create storage.OpenSearchParameters - update openSearchCmd - want storage.OpenSearchParameters - wantErr bool + name string + create storage.OpenSearchParameters + update openSearchCmd + want storage.OpenSearchParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "increase-machineType", create: storage.OpenSearchParameters{MachineType: infra.MachineTypeNineSearchS}, @@ -103,7 +109,7 @@ func TestOpenSearch(t *testing.T) { tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.OpenSearch(tt.update.Name, apiClient.Project, meta.LocationNineES34) created.Spec.ForProvider = tt.create diff --git a/update/postgres.go b/update/postgres.go index 8d103ea1..b023be51 100644 --- a/update/postgres.go +++ b/update/postgres.go @@ -47,14 +47,13 @@ func (cmd *postgresCmd) Run(ctx context.Context, client *api.Client) error { cmd.SSHKeys = keys } - cmd.applyUpdates(postgres) - return nil + return cmd.applyUpdates(postgres) }) return upd.Update(ctx) } -func (cmd *postgresCmd) applyUpdates(postgres *storage.Postgres) { +func (cmd *postgresCmd) applyUpdates(postgres *storage.Postgres) error { if cmd.MachineType != nil { postgres.Spec.ForProvider.MachineType = infra.NewMachineType(*cmd.MachineType) } @@ -67,4 +66,5 @@ func (cmd *postgresCmd) applyUpdates(postgres *storage.Postgres) { if cmd.KeepDailyBackups != nil { postgres.Spec.ForProvider.KeepDailyBackups = cmd.KeepDailyBackups } + return nil } diff --git a/update/postgres_test.go b/update/postgres_test.go index d80b0692..455f3221 100644 --- a/update/postgres_test.go +++ b/update/postgres_test.go @@ -15,12 +15,18 @@ func TestPostgres(t *testing.T) { t.Parallel() tests := []struct { - name string - create storage.PostgresParameters - update postgresCmd - want storage.PostgresParameters - wantErr bool + name string + create storage.PostgresParameters + update postgresCmd + want storage.PostgresParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "simple", }, @@ -80,7 +86,7 @@ func TestPostgres(t *testing.T) { tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.Postgres(tt.update.Name, apiClient.Project, "nine-es34") created.Spec.ForProvider = tt.create diff --git a/update/postgresdatabase.go b/update/postgresdatabase.go index da8e916b..29e63700 100644 --- a/update/postgresdatabase.go +++ b/update/postgresdatabase.go @@ -29,15 +29,15 @@ func (cmd *postgresDatabaseCmd) Run(ctx context.Context, client *api.Client) err return fmt.Errorf("resource is of type %T, expected %T", current, storage.PostgresDatabase{}) } - cmd.applyUpdates(postgresDatabase) - return nil + return cmd.applyUpdates(postgresDatabase) }) return upd.Update(ctx) } -func (cmd *postgresDatabaseCmd) applyUpdates(db *storage.PostgresDatabase) { +func (cmd *postgresDatabaseCmd) applyUpdates(db *storage.PostgresDatabase) error { if cmd.BackupSchedule != nil { db.Spec.ForProvider.BackupSchedule = *cmd.BackupSchedule } + return nil } diff --git a/update/postgresdatabase_test.go b/update/postgresdatabase_test.go index 14d23225..ff73edf1 100644 --- a/update/postgresdatabase_test.go +++ b/update/postgresdatabase_test.go @@ -17,12 +17,18 @@ func TestPostgresDatabase(t *testing.T) { t.Parallel() tests := []struct { - name string - create storage.PostgresDatabaseParameters - update postgresDatabaseCmd - want storage.PostgresDatabaseParameters - wantErr bool + name string + create storage.PostgresDatabaseParameters + update postgresDatabaseCmd + want storage.PostgresDatabaseParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "simple", }, @@ -46,7 +52,7 @@ func TestPostgresDatabase(t *testing.T) { tt.update.Writer = format.NewWriter(out) tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.PostgresDatabase(tt.update.Name, apiClient.Project, "nine-es34") created.Spec.ForProvider = tt.create diff --git a/update/project.go b/update/project.go index 66d9af15..89185d6f 100644 --- a/update/project.go +++ b/update/project.go @@ -12,7 +12,7 @@ import ( type projectCmd struct { resourceCmd - DisplayName *string `default:"" help:"Display Name of the project."` + DisplayName *string `help:"Display Name of the project."` } func (cmd *projectCmd) Run(ctx context.Context, client *api.Client) error { @@ -34,16 +34,15 @@ func (cmd *projectCmd) Run(ctx context.Context, client *api.Client) error { return fmt.Errorf("resource is of type %T, expected %T", current, management.Project{}) } - cmd.applyUpdates(project) - - return nil + return cmd.applyUpdates(project) }) return upd.Update(ctx) } -func (cmd *projectCmd) applyUpdates(project *management.Project) { +func (cmd *projectCmd) applyUpdates(project *management.Project) error { if cmd.DisplayName != nil { project.Spec.DisplayName = *cmd.DisplayName } + return nil } diff --git a/update/project_config.go b/update/project_config.go index c0c165e2..7a232e09 100644 --- a/update/project_config.go +++ b/update/project_config.go @@ -48,15 +48,13 @@ func (cmd *configCmd) Run(ctx context.Context, client *api.Client) error { return fmt.Errorf("resource is of type %T, expected %T", current, apps.ProjectConfig{}) } - cmd.applyUpdates(cfg) - - return nil + return cmd.applyUpdates(cfg) }) return upd.Update(ctx) } -func (cmd *configCmd) applyUpdates(cfg *apps.ProjectConfig) { +func (cmd *configCmd) applyUpdates(cfg *apps.ProjectConfig) error { if cmd.Size != nil { cfg.Spec.ForProvider.Config.Size = apps.ApplicationSize(*cmd.Size) } @@ -73,6 +71,9 @@ func (cmd *configCmd) applyUpdates(cfg *apps.ProjectConfig) { cfg.Spec.ForProvider.Config.EnableBasicAuth = cmd.BasicAuth } if cmd.DeployJob != nil { - cmd.DeployJob.applyUpdates(&cfg.Spec.ForProvider.Config) + if err := cmd.DeployJob.applyUpdates(&cfg.Spec.ForProvider.Config); err != nil { + return err + } } + return nil } diff --git a/update/project_config_test.go b/update/project_config_test.go index 28d5c620..8c2f5233 100644 --- a/update/project_config_test.go +++ b/update/project_config_test.go @@ -39,10 +39,18 @@ func TestConfig(t *testing.T) { } cases := map[string]struct { - orig *apps.ProjectConfig - cmd configCmd - checkConfig func(t *testing.T, cmd configCmd, orig, updated *apps.ProjectConfig) + orig *apps.ProjectConfig + cmd configCmd + checkConfig func(t *testing.T, cmd configCmd, orig, updated *apps.ProjectConfig) + errorExpected bool + clientOpts []test.ClientSetupOption }{ + "no-flags": { + orig: existingConfig, + cmd: configCmd{}, + errorExpected: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, "change port": { orig: existingConfig, cmd: configCmd{ @@ -104,12 +112,11 @@ func TestConfig(t *testing.T) { for name, tc := range cases { t.Run(name, func(t *testing.T) { - apiClient := test.SetupClient(t, - test.WithObjects(tc.orig), - ) + opts := append([]test.ClientSetupOption{test.WithObjects(tc.orig)}, tc.clientOpts...) + apiClient := test.SetupClient(t, opts...) - if err := tc.cmd.Run(t.Context(), apiClient); err != nil { - t.Fatal(err) + if err := tc.cmd.Run(t.Context(), apiClient); (err != nil) != tc.errorExpected { + t.Fatalf("configCmd.Run() error = %v, errorExpected %v", err, tc.errorExpected) } updated := &apps.ProjectConfig{} @@ -117,8 +124,10 @@ func TestConfig(t *testing.T) { t.Fatal(err) } - if tc.checkConfig != nil { - tc.checkConfig(t, tc.cmd, tc.orig, updated) + if !tc.errorExpected { + if tc.checkConfig != nil { + tc.checkConfig(t, tc.cmd, tc.orig, updated) + } } }) } diff --git a/update/project_test.go b/update/project_test.go index 146da5ee..ea2c513d 100644 --- a/update/project_test.go +++ b/update/project_test.go @@ -29,10 +29,20 @@ func TestProject(t *testing.T) { } cases := map[string]struct { - orig *management.Project - cmd projectCmd - checkProject func(t *testing.T, cmd projectCmd, orig, updated *management.Project) + orig *management.Project + cmd projectCmd + checkProject func(t *testing.T, cmd projectCmd, orig, updated *management.Project) + errorExpected bool + clientOpts []test.ClientSetupOption }{ + "no-flags": { + orig: existingProject, + cmd: projectCmd{ + resourceCmd: resourceCmd{Name: projectName}, + }, + errorExpected: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, "all fields update": { orig: existingProject, cmd: projectCmd{ @@ -51,15 +61,17 @@ func TestProject(t *testing.T) { out := &bytes.Buffer{} tc.cmd.Writer = format.NewWriter(out) - apiClient := test.SetupClient(t, + opts := []test.ClientSetupOption{ test.WithObjects(tc.orig), test.WithOrganization(organization), test.WithDefaultProject(tc.orig.Name), test.WithKubeconfig(), - ) + } + opts = append(opts, tc.clientOpts...) + apiClient := test.SetupClient(t, opts...) - if err := tc.cmd.Run(t.Context(), apiClient); err != nil { - t.Fatal(err) + if err := tc.cmd.Run(t.Context(), apiClient); (err != nil) != tc.errorExpected { + t.Fatalf("projectCmd.Run() error = %v, errorExpected %v", err, tc.errorExpected) } updated := &management.Project{} @@ -67,15 +79,17 @@ func TestProject(t *testing.T) { t.Fatal(err) } - if tc.checkProject != nil { - tc.checkProject(t, tc.cmd, tc.orig, updated) - } + if !tc.errorExpected { + if tc.checkProject != nil { + tc.checkProject(t, tc.cmd, tc.orig, updated) + } - if !strings.Contains(out.String(), "updated") { - t.Errorf("expected output to contain 'updated', got %q", out.String()) - } - if !strings.Contains(out.String(), projectName) { - t.Errorf("expected output to contain project name %q, got %q", projectName, out.String()) + if !strings.Contains(out.String(), "updated") { + t.Errorf("expected output to contain 'updated', got %q", out.String()) + } + if !strings.Contains(out.String(), projectName) { + t.Errorf("expected output to contain project name %q, got %q", projectName, out.String()) + } } }) } diff --git a/update/serviceconnection.go b/update/serviceconnection.go index a71076f3..33995602 100644 --- a/update/serviceconnection.go +++ b/update/serviceconnection.go @@ -37,6 +37,5 @@ func (cmd *serviceConnectionCmd) Run(ctx context.Context, client *api.Client) er func (cmd *serviceConnectionCmd) applyUpdates(sc *networking.ServiceConnection) error { sc.Spec.ForProvider.Source.KubernetesClusterOptions = cmd.KubernetesClusterOptions.APIType() - return nil } diff --git a/update/serviceconnection_test.go b/update/serviceconnection_test.go index b64d6f36..d38b2b0a 100644 --- a/update/serviceconnection_test.go +++ b/update/serviceconnection_test.go @@ -21,11 +21,17 @@ func TestServiceConnection(t *testing.T) { t.Parallel() tests := []struct { - name string - update serviceConnectionCmd - want networking.ServiceConnectionParameters - wantErr bool + name string + update serviceConnectionCmd + want networking.ServiceConnectionParameters + wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "addClusterOptions", update: serviceConnectionCmd{ @@ -136,7 +142,7 @@ func TestServiceConnection(t *testing.T) { tt.update.Writer = format.NewWriter(out) tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) created := test.ServiceConnection(tt.update.Name, apiClient.Project) if err := apiClient.Create(t.Context(), created); err != nil { @@ -154,8 +160,10 @@ func TestServiceConnection(t *testing.T) { t.Fatalf("expected serviceconnection to exist, got: %s", err) } - if !cmp.Equal(updated.Spec.ForProvider, tt.want) { - t.Fatalf("expected serviceConnection.Spec.ForProvider = %v, got: %v", updated.Spec.ForProvider, tt.want) + if !tt.wantErr { + if !cmp.Equal(updated.Spec.ForProvider, tt.want) { + t.Fatalf("expected serviceConnection.Spec.ForProvider = %v, got: %v", updated.Spec.ForProvider, tt.want) + } } if !tt.wantErr { diff --git a/update/staticegress.go b/update/staticegress.go index 8f223302..8adcb73e 100644 --- a/update/staticegress.go +++ b/update/staticegress.go @@ -29,13 +29,13 @@ func (cmd *staticEgressCmd) Run(ctx context.Context, client *api.Client) error { return fmt.Errorf("resource is of type %T, expected %T", current, networking.StaticEgress{}) } - cmd.applyUpdates(staticEgress) - return nil + return cmd.applyUpdates(staticEgress) }).Update(ctx) } -func (cmd *staticEgressCmd) applyUpdates(staticEgress *networking.StaticEgress) { +func (cmd *staticEgressCmd) applyUpdates(staticEgress *networking.StaticEgress) error { if cmd.Disabled != nil { staticEgress.Spec.ForProvider.Disabled = *cmd.Disabled } + return nil } diff --git a/update/staticegress_test.go b/update/staticegress_test.go index 25cf92c6..1365f205 100644 --- a/update/staticegress_test.go +++ b/update/staticegress_test.go @@ -35,7 +35,20 @@ func TestStaticEgress(t *testing.T) { want networking.StaticEgressParameters targetName string wantErr bool + clientOpts []test.ClientSetupOption }{ + { + name: "no-flags", + create: networking.StaticEgressParameters{ + Target: appTarget, + }, + targetName: "my-app", + want: networking.StaticEgressParameters{ + Target: appTarget, + }, + wantErr: true, + clientOpts: []test.ClientSetupOption{test.WithNoFlagsInterceptor()}, + }, { name: "empty update", create: networking.StaticEgressParameters{ @@ -104,7 +117,7 @@ func TestStaticEgress(t *testing.T) { tt.update.Writer = format.NewWriter(out) tt.update.Name = "test-" + t.Name() - apiClient := test.SetupClient(t) + apiClient := test.SetupClient(t, tt.clientOpts...) targetName := tt.targetName if targetName == "" { diff --git a/update/update.go b/update/update.go index b9f972bc..97938455 100644 --- a/update/update.go +++ b/update/update.go @@ -3,10 +3,12 @@ package update import ( "context" + "fmt" "io" "github.com/crossplane/crossplane-runtime/pkg/resource" "github.com/ninech/nctl/api" + "github.com/ninech/nctl/internal/cli" "github.com/ninech/nctl/internal/format" ) @@ -67,10 +69,17 @@ func (u *updater) Update(ctx context.Context) error { return err } + oldRV := u.mg.GetResourceVersion() + if err := u.client.Update(ctx, u.mg); err != nil { return err } + if u.mg.GetResourceVersion() == oldRV { + return cli.ErrorWithContext(fmt.Errorf("no changes detected")). + WithSuggestions("use --help to see available flags") + } + u.Successf("⬆️", "updated %s %q", u.kind, u.mg.GetName()) return nil }