diff --git a/cmd/monaco/delete/delete.go b/cmd/monaco/delete/delete.go index 31754e13c..49a8bbe39 100644 --- a/cmd/monaco/delete/delete.go +++ b/cmd/monaco/delete/delete.go @@ -17,12 +17,11 @@ package delete import ( "context" "fmt" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "strings" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/delete" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" @@ -45,29 +44,14 @@ func Delete(environments manifest.Environments, entriesToDelete delete.DeleteEnt log.WithCtxFields(ctx).Warn("Delete file contains Dynatrace Platform specific types, but no oAuth credentials are defined for environment %q - Dynatrace Platform configurations won't be deleted.", env.Name) } - clientSet, err := client.CreateClientSet(env.URL.Value, env.Auth, client.ClientOptions{SupportArchive: support.SupportArchive}) + clientSet, err := client.CreateClientSet(ctx, env.URL.Value, env.Auth, client.ClientOptions{SupportArchive: support.SupportArchive}) if err != nil { return fmt.Errorf("failed to create API client for environment %q due to the following error: %w", env.Name, err) } log.WithCtxFields(ctx).Info("Deleting configs for environment %q...", env.Name) - classicAPIs := api.NewAPIs() - automationAPIs := map[string]config.AutomationResource{ - string(config.Workflow): config.Workflow, - string(config.BusinessCalendar): config.BusinessCalendar, - string(config.SchedulingRule): config.SchedulingRule, - } - - deleteClients := delete.ClientSet{ - Classic: clientSet.Classic(), - Settings: clientSet.Settings(), - Automation: clientSet.Automation(), - Buckets: clientSet.Bucket(), - Documents: clientSet.Document(), - } - - if err := delete.Configs(ctx, deleteClients, classicAPIs, automationAPIs, entriesToDelete); err != nil { + if err := delete.Configs(ctx, *clientSet, entriesToDelete); err != nil { log.Error("Failed to delete all configurations from environment %q - check log for details", env.Name) envsWithDeleteErrs = append(envsWithDeleteErrs, env.Name) } diff --git a/cmd/monaco/deploy/deploy.go b/cmd/monaco/deploy/deploy.go index 71b88be68..972cad4fe 100644 --- a/cmd/monaco/deploy/deploy.go +++ b/cmd/monaco/deploy/deploy.go @@ -87,7 +87,7 @@ func deployConfigsWithContext(ctx context.Context, fs afero.Fs, manifestPath str return fmt.Errorf("manifest auth field misconfigured: %w", err) } - clientSets, err := dynatrace.CreateEnvironmentClients(loadedManifest.Environments) + clientSets, err := dynatrace.CreateEnvironmentClients(ctx, loadedManifest.Environments, dryRun) if err != nil { return fmt.Errorf("failed to create API clients: %w", err) } @@ -228,7 +228,7 @@ func configRequiresPlatform(c config.Config) bool { func collectUndefinedEnvironmentErrors(undefinedEnvironments map[string]struct{}) []error { errs := []error{} - for envName, _ := range undefinedEnvironments { + for envName := range undefinedEnvironments { errs = append(errs, fmt.Errorf("undefined environment %q", envName)) } return errs diff --git a/cmd/monaco/download/download_configs.go b/cmd/monaco/download/download_configs.go index 8de48675f..cba090e72 100644 --- a/cmd/monaco/download/download_configs.go +++ b/cmd/monaco/download/download_configs.go @@ -15,13 +15,15 @@ package download import ( + "context" "errors" "fmt" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/openpipeline" "os" + "github.com/spf13/afero" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/dynatrace" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/secret" @@ -34,12 +36,12 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/dependency_resolution" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/document" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/id_extraction" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/openpipeline" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/settings" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" manifestloader "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest/loader" project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" projectv2 "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" - "github.com/spf13/afero" ) type downloadCmdOptions struct { @@ -148,7 +150,7 @@ func (d DefaultCommand) DownloadConfigsBasedOnManifest(fs afero.Fs, cmdOptions d return err } - clientSet, err := client.CreateClientSet(options.environmentURL, options.auth, client.ClientOptions{SupportArchive: support.SupportArchive}) + clientSet, err := client.CreateClientSet(context.TODO(), options.environmentURL, options.auth, client.ClientOptions{SupportArchive: support.SupportArchive}) if err != nil { return err } @@ -186,7 +188,7 @@ func (d DefaultCommand) DownloadConfigs(fs afero.Fs, cmdOptions downloadCmdOptio return err } - clientSet, err := client.CreateClientSet(options.environmentURL, options.auth, client.ClientOptions{SupportArchive: support.SupportArchive}) + clientSet, err := client.CreateClientSet(context.TODO(), options.environmentURL, options.auth, client.ClientOptions{SupportArchive: support.SupportArchive}) if err != nil { return err } diff --git a/cmd/monaco/download/download_configs_test.go b/cmd/monaco/download/download_configs_test.go index 59b29229b..88d1fbfb8 100644 --- a/cmd/monaco/download/download_configs_test.go +++ b/cmd/monaco/download/download_configs_test.go @@ -20,6 +20,12 @@ package download import ( "errors" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/testutils" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" @@ -30,17 +36,14 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/settings" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" projectv2 "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - "testing" ) func TestDownloadConfigsBehaviour(t *testing.T) { tests := []struct { - name string - givenOpts downloadConfigsOptions - expectedBehaviour func(client *client.MockDynatraceClient) + name string + givenOpts downloadConfigsOptions + expectedConfigBehaviour func(client *client.MockConfigClient) + expectedSettingsBehaviour func(client *client.MockSettingsClient) }{ { name: "Default opts: downloads Configs and Settings", @@ -50,9 +53,11 @@ func TestDownloadConfigsBehaviour(t *testing.T) { onlyAPIs: false, onlySettings: false, }, - expectedBehaviour: func(c *client.MockDynatraceClient) { + expectedConfigBehaviour: func(c *client.MockConfigClient) { c.EXPECT().ListConfigs(gomock.Any(), gomock.Any()).AnyTimes().Return([]dtclient.Value{}, nil) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]byte("{}"), nil) // singleton configs are always attempted + }, + expectedSettingsBehaviour: func(c *client.MockSettingsClient) { c.EXPECT().ListSchemas(gomock.Any()).Return(dtclient.SchemaList{}, nil) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]dtclient.DownloadSettingsObject{}, nil) }, @@ -65,9 +70,11 @@ func TestDownloadConfigsBehaviour(t *testing.T) { onlyAPIs: false, onlySettings: false, }, - expectedBehaviour: func(c *client.MockDynatraceClient) { + expectedConfigBehaviour: func(c *client.MockConfigClient) { c.EXPECT().ListConfigs(gomock.Any(), gomock.Any()).Times(0) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + }, + expectedSettingsBehaviour: func(c *client.MockSettingsClient) { c.EXPECT().ListSchemas(gomock.Any()).AnyTimes().Return(dtclient.SchemaList{{SchemaId: "builtin:magic.secret"}}, nil) c.EXPECT().GetSchemaById(gomock.Any(), gomock.Any()).AnyTimes().Return(dtclient.Schema{SchemaId: "builtin:magic.secret"}, nil) c.EXPECT().ListSettings(gomock.Any(), "builtin:magic.secret", gomock.Any()).AnyTimes().Return([]dtclient.DownloadSettingsObject{}, nil) @@ -81,9 +88,11 @@ func TestDownloadConfigsBehaviour(t *testing.T) { onlyAPIs: false, onlySettings: false, }, - expectedBehaviour: func(c *client.MockDynatraceClient) { + expectedConfigBehaviour: func(c *client.MockConfigClient) { c.EXPECT().ListConfigs(gomock.Any(), api.NewAPIs()["alerting-profile"]).Return([]dtclient.Value{{Id: "42", Name: "profile"}}, nil) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), "42").AnyTimes().Return([]byte("{}"), nil) + }, + expectedSettingsBehaviour: func(c *client.MockSettingsClient) { c.EXPECT().ListSchemas(gomock.Any()).Times(0) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) }, @@ -96,13 +105,14 @@ func TestDownloadConfigsBehaviour(t *testing.T) { onlyAPIs: false, onlySettings: false, }, - expectedBehaviour: func(c *client.MockDynatraceClient) { + expectedConfigBehaviour: func(c *client.MockConfigClient) { c.EXPECT().ListConfigs(gomock.Any(), api.NewAPIs()["alerting-profile"]).Return([]dtclient.Value{{Id: "42", Name: "profile"}}, nil) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), "42").AnyTimes().Return([]byte("{}"), nil) + }, + expectedSettingsBehaviour: func(c *client.MockSettingsClient) { c.EXPECT().ListSchemas(gomock.Any()).AnyTimes().Return(dtclient.SchemaList{{SchemaId: "builtin:magic.secret"}}, nil) c.EXPECT().GetSchemaById(gomock.Any(), gomock.Any()).AnyTimes().Return(dtclient.Schema{SchemaId: "builtin:magic.secret"}, nil) c.EXPECT().ListSettings(gomock.Any(), "builtin:magic.secret", gomock.Any()).AnyTimes().Return([]dtclient.DownloadSettingsObject{}, nil) - }, }, { @@ -113,9 +123,11 @@ func TestDownloadConfigsBehaviour(t *testing.T) { onlyAPIs: true, onlySettings: false, }, - expectedBehaviour: func(c *client.MockDynatraceClient) { + expectedConfigBehaviour: func(c *client.MockConfigClient) { c.EXPECT().ListConfigs(gomock.Any(), gomock.Any()).AnyTimes().Return([]dtclient.Value{}, nil) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]byte("{}"), nil) // singleton configs are always attempted + }, + expectedSettingsBehaviour: func(c *client.MockSettingsClient) { c.EXPECT().ListSchemas(gomock.Any()).Times(0) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) }, @@ -128,9 +140,11 @@ func TestDownloadConfigsBehaviour(t *testing.T) { onlyAPIs: false, onlySettings: true, }, - expectedBehaviour: func(c *client.MockDynatraceClient) { + expectedConfigBehaviour: func(c *client.MockConfigClient) { c.EXPECT().ListConfigs(gomock.Any(), gomock.Any()).Times(0) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) + }, + expectedSettingsBehaviour: func(c *client.MockSettingsClient) { c.EXPECT().ListSchemas(gomock.Any()).Return(dtclient.SchemaList{}, nil) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return([]dtclient.DownloadSettingsObject{}, nil) }, @@ -138,7 +152,6 @@ func TestDownloadConfigsBehaviour(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) tt.givenOpts.downloadOptionsShared = downloadOptionsShared{ environmentURL: "testurl.com", @@ -153,9 +166,13 @@ func TestDownloadConfigsBehaviour(t *testing.T) { forceOverwriteManifest: false, } - tt.expectedBehaviour(c) + configClient := client.NewMockConfigClient(gomock.NewController(t)) + tt.expectedConfigBehaviour(configClient) + + settingsClient := client.NewMockSettingsClient(gomock.NewController(t)) + tt.expectedSettingsBehaviour(settingsClient) - _, err := downloadConfigs(&client.ClientSet{DTClient: c}, api.NewAPIs(), tt.givenOpts, defaultDownloadFn) + _, err := downloadConfigs(&client.ClientSet{ClassicClient: configClient, SettingsClient: settingsClient}, api.NewAPIs(), tt.givenOpts, defaultDownloadFn) assert.NoError(t, err) }) } @@ -303,7 +320,8 @@ func TestDownload_Options(t *testing.T) { }, } - _, err := downloadConfigs(&client.ClientSet{DTClient: client.NewMockDynatraceClient(gomock.NewController(t))}, api.NewAPIs(), tt.given, fn) + c := client.NewMockConfigClient(gomock.NewController(t)) + _, err := downloadConfigs(&client.ClientSet{ClassicClient: c}, api.NewAPIs(), tt.given, fn) assert.NoError(t, err) }) } @@ -390,10 +408,9 @@ func Test_shouldDownloadSettings(t *testing.T) { } func TestDownloadConfigsExitsEarlyForUnknownSettingsSchema(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) givenOpts := downloadConfigsOptions{ - specificSchemas: []string{"UNKOWN SCHEMA"}, + specificSchemas: []string{"UNKNOWN SCHEMA"}, onlySettings: false, downloadOptionsShared: downloadOptionsShared{ environmentURL: "testurl.com", @@ -409,9 +426,10 @@ func TestDownloadConfigsExitsEarlyForUnknownSettingsSchema(t *testing.T) { }, } + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSchemas(gomock.Any()).Return(dtclient.SchemaList{{SchemaId: "builtin:some.schema"}}, nil) - err := doDownloadConfigs(afero.NewMemMapFs(), &client.ClientSet{DTClient: c}, nil, givenOpts) + err := doDownloadConfigs(afero.NewMemMapFs(), &client.ClientSet{SettingsClient: c}, nil, givenOpts) assert.ErrorContains(t, err, "not known", "expected download to fail for unkown Settings Schema") c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) // no downloads should even be attempted for unknown schema } diff --git a/cmd/monaco/download/download_integration_test.go b/cmd/monaco/download/download_integration_test.go index 02fbd0d65..052e9d5ff 100644 --- a/cmd/monaco/download/download_integration_test.go +++ b/cmd/monaco/download/download_integration_test.go @@ -18,6 +18,18 @@ package download import ( "encoding/json" + "net/http/httptest" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/environment" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" @@ -33,15 +45,6 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" manifestloader "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest/loader" projectLoader "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "net/http/httptest" - "path/filepath" - "reflect" - "strings" - "testing" ) // compareOptions holds all options we require for the tests to not be flaky. @@ -83,11 +86,11 @@ func TestDownloadIntegrationSimple(t *testing.T) { fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) - + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -154,11 +157,11 @@ func TestDownloadIntegrationWithReference(t *testing.T) { fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) - + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -245,10 +248,11 @@ func TestDownloadIntegrationWithMultipleApisAndReferences(t *testing.T) { server := dtclient.NewIntegrationTestServer(t, testBasePath, responses) fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) assert.NoError(t, err) @@ -363,10 +367,11 @@ func TestDownloadIntegrationSingletonConfig(t *testing.T) { fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) assert.NoError(t, err) @@ -431,10 +436,11 @@ func TestDownloadIntegrationSyntheticLocations(t *testing.T) { fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) assert.NoError(t, err) @@ -502,11 +508,11 @@ func TestDownloadIntegrationDashboards(t *testing.T) { fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) - + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -600,12 +606,13 @@ func TestDownloadIntegrationAllDashboardsAreDownloadedIfFilterFFTurnedOff(t *tes fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) + t.Setenv(featureflags.Permanent[featureflags.DownloadFilterClassicConfigs].EnvName(), "false") // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) - + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -718,11 +725,11 @@ func TestDownloadIntegrationAnomalyDetectionMetrics(t *testing.T) { fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) - + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, projectName)) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -859,11 +866,11 @@ func TestDownloadIntegrationHostAutoUpdate(t *testing.T) { fs := afero.NewMemMapFs() - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // WHEN we download everything - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, testcase.projectName)) - + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apiMap, setupTestingDownloadOptions(t, server, testcase.projectName)) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -933,10 +940,10 @@ func TestDownloadIntegrationOverwritesFolderAndManifestIfForced(t *testing.T) { options.forceOverwriteManifest = true options.outputFolder = testBasePath - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) - - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apis, options) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apis, options) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -1026,10 +1033,10 @@ func TestDownloadIntegrationDownloadsAPIsAndSettings(t *testing.T) { opts.onlySettings = false opts.onlyAPIs = false - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) - - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apis, opts) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apis, opts) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -1086,10 +1093,11 @@ func TestDownloadIntegrationDownloadsOnlyAPIsIfConfigured(t *testing.T) { opts := setupTestingDownloadOptions(t, server, projectName) opts.onlySettings = false opts.onlyAPIs = true - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, apis, opts) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, apis, opts) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -1137,10 +1145,10 @@ func TestDownloadIntegrationDoesNotDownloadUnmodifiableSettings(t *testing.T) { opts.onlySettings = true opts.onlyAPIs = false - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) - - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, nil, opts) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, nil, opts) assert.NoError(t, err) // THEN we can load the project again and verify its content @@ -1191,13 +1199,13 @@ func TestDownloadIntegrationDownloadsUnmodifiableSettingsIfFFTurnedOff(t *testin opts.onlySettings = true opts.onlyAPIs = false - dtClient, _ := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + dtClient, err := dtclient.NewDynatraceClientForTesting(server.URL, server.Client()) + require.NoError(t, err) // GIVEN filter feature flag is turned OFF t.Setenv(featureflags.Permanent[featureflags.DownloadFilterSettingsUnmodifiable].EnvName(), "false") - err := doDownloadConfigs(fs, &client.ClientSet{DTClient: dtClient}, nil, opts) - + err = doDownloadConfigs(fs, &client.ClientSet{ClassicClient: dtClient, SettingsClient: dtClient}, nil, opts) assert.NoError(t, err) // THEN we can load the project again and verify its content diff --git a/cmd/monaco/dynatrace/dynatrace.go b/cmd/monaco/dynatrace/dynatrace.go index 91ebba667..646100c24 100644 --- a/cmd/monaco/dynatrace/dynatrace.go +++ b/cmd/monaco/dynatrace/dynatrace.go @@ -178,14 +178,22 @@ func (e EnvironmentClients) Names() []string { } // CreateEnvironmentClients gives back clients to use for specific environments -func CreateEnvironmentClients(environments manifest.Environments) (EnvironmentClients, error) { +func CreateEnvironmentClients(ctx context.Context, environments manifest.Environments, dryRun bool) (EnvironmentClients, error) { clients := make(EnvironmentClients, len(environments)) for _, env := range environments { + if dryRun { + clients[EnvironmentInfo{ + Name: env.Name, + Group: env.Group, + }] = &client.DummyClientSet + continue + } - clientSet, err := client.CreateClientSet(env.URL.Value, env.Auth, client.ClientOptions{SupportArchive: support.SupportArchive}) + clientSet, err := client.CreateClientSet(ctx, env.URL.Value, env.Auth, client.ClientOptions{SupportArchive: support.SupportArchive}) if err != nil { return EnvironmentClients{}, err } + clients[EnvironmentInfo{ Name: env.Name, Group: env.Group, diff --git a/cmd/monaco/integrationtest/integration_test_utils.go b/cmd/monaco/integrationtest/integration_test_utils.go index ae0c980e8..51a82d5f8 100644 --- a/cmd/monaco/integrationtest/integration_test_utils.go +++ b/cmd/monaco/integrationtest/integration_test_utils.go @@ -19,6 +19,15 @@ package integrationtest import ( + "context" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "path/filepath" + "testing" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/testutils" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api" @@ -27,12 +36,6 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" manifestloader "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest/loader" project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "path/filepath" - "testing" ) // CreateDynatraceClients creates a client set used in e2e tests. @@ -45,6 +48,7 @@ func CreateDynatraceClients(t *testing.T, environment manifest.EnvironmentDefini err error ) clients, err = client.CreateClientSet( + context.TODO(), environment.URL.Value, environment.Auth, client.ClientOptions{ diff --git a/cmd/monaco/integrationtest/v2/diff_project_diff_ext_id_test.go b/cmd/monaco/integrationtest/v2/diff_project_diff_ext_id_test.go index 98dd91458..a49910782 100644 --- a/cmd/monaco/integrationtest/v2/diff_project_diff_ext_id_test.go +++ b/cmd/monaco/integrationtest/v2/diff_project_diff_ext_id_test.go @@ -20,17 +20,18 @@ package v2 import ( "context" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "testing" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/integrationtest" "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/integrationtest/utils/monaco" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/idutils" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/dtclient" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2/sort" - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" ) var diffProjectDiffExtIDFolder = "test-resources/integration-different-projects-different-extid/" @@ -54,7 +55,7 @@ func TestSettingsInDifferentProjectsGetDifferentExternalIDs(t *testing.T) { extIDProject1, _ := idutils.GenerateExternalIDForSettingsObject(sortedConfigs["platform_env"][0].Coordinate) extIDProject2, _ := idutils.GenerateExternalIDForSettingsObject(sortedConfigs["platform_env"][1].Coordinate) - clientSet, err := client.CreateClientSet(environment.URL.Value, environment.Auth, client.ClientOptions{SupportArchive: support.SupportArchive}) + clientSet, err := client.CreateClientSet(context.TODO(), environment.URL.Value, environment.Auth, client.ClientOptions{SupportArchive: support.SupportArchive}) assert.NoError(t, err) c := clientSet.Settings() settings, _ := c.ListSettings(context.TODO(), "builtin:anomaly-detection.metric-events", dtclient.ListSettingsOptions{DiscardValue: true, Filter: func(object dtclient.DownloadSettingsObject) bool { diff --git a/cmd/monaco/integrationtest/v2/dry-run_test.go b/cmd/monaco/integrationtest/v2/dry-run_test.go index 212f19e82..348a3f330 100644 --- a/cmd/monaco/integrationtest/v2/dry-run_test.go +++ b/cmd/monaco/integrationtest/v2/dry-run_test.go @@ -19,12 +19,15 @@ package v2 import ( + "net/http" + "net/http/httptest" "testing" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/integrationtest/utils/monaco" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags" "github.com/spf13/afero" "github.com/stretchr/testify/assert" + + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/integrationtest/utils/monaco" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags" ) func TestDryRun(t *testing.T) { @@ -38,6 +41,14 @@ func TestDryRun(t *testing.T) { } RunIntegrationWithCleanupGivenEnvs(t, configFolder, manifest, specificEnvironment, "AllConfigs", envVars, func(fs afero.Fs, _ TestContext) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + t.Fatalf("unexpected HTTP request made during dry run: %s", req.RequestURI) + })) + defer server.Close() + + // ensure all URLs used in the manifest point at the test server + setAllURLEnvironmentVariables(t, server.URL) + // This causes a POST for all configs: err := monaco.RunWithFSf(fs, "monaco deploy %s --environment=%s --verbose --dry-run", manifest, specificEnvironment) assert.NoError(t, err) @@ -47,3 +58,11 @@ func TestDryRun(t *testing.T) { assert.NoError(t, err) }) } + +func setAllURLEnvironmentVariables(t *testing.T, url string) { + t.Setenv("URL_ENVIRONMENT_1", url) + t.Setenv("URL_ENVIRONMENT_2", url) + t.Setenv("PLATFORM_URL_ENVIRONMENT_1", url) + t.Setenv("PLATFORM_URL_ENVIRONMENT_2", url) + t.Setenv("OAUTH_TOKEN_ENDPOINT", url) +} diff --git a/cmd/monaco/purge/purge.go b/cmd/monaco/purge/purge.go index 378a1dab5..a32fa60d1 100644 --- a/cmd/monaco/purge/purge.go +++ b/cmd/monaco/purge/purge.go @@ -20,18 +20,19 @@ import ( "context" "errors" "fmt" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "path/filepath" + "github.com/spf13/afero" + "golang.org/x/exp/maps" + + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/errutils" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/delete" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" manifestloader "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest/loader" - "github.com/spf13/afero" - "golang.org/x/exp/maps" ) func purge(fs afero.Fs, deploymentManifestPath string, environmentNames []string, apiNames []string) error { @@ -73,33 +74,17 @@ func purgeConfigs(environments []manifest.EnvironmentDefinition, apis api.APIs) } func purgeForEnvironment(env manifest.EnvironmentDefinition, apis api.APIs) error { + ctx := context.WithValue(context.TODO(), log.CtxKeyEnv{}, log.CtxValEnv{Name: env.Name, Group: env.Group}) - deleteClients, err := getClientSet(env) + clients, err := client.CreateClientSet(ctx, env.URL.Value, env.Auth, client.ClientOptions{SupportArchive: support.SupportArchive}) if err != nil { - return err + return fmt.Errorf("failed to create a client for env `%s`: %w", env.Name, err) } - ctx := context.WithValue(context.TODO(), log.CtxKeyEnv{}, log.CtxValEnv{Name: env.Name, Group: env.Group}) - log.WithCtxFields(ctx).Info("Deleting configs for environment `%s`", env.Name) - if err := delete.All(ctx, deleteClients, apis); err != nil { + if err := delete.All(ctx, *clients, apis); err != nil { log.Error("Encountered errors while puring configurations from environment %s, further manual cleanup may be needed - check logs for details.", env.Name) } return nil } - -func getClientSet(env manifest.EnvironmentDefinition) (delete.ClientSet, error) { - clients, err := client.CreateClientSet(env.URL.Value, env.Auth, client.ClientOptions{SupportArchive: support.SupportArchive}) - if err != nil { - return delete.ClientSet{}, fmt.Errorf("failed to create a client for env `%s` due to the following error: %w", env.Name, err) - } - - return delete.ClientSet{ - Classic: clients.Classic(), - Settings: clients.Settings(), - Automation: clients.Automation(), - Buckets: clients.Bucket(), - Documents: clients.Document(), - }, nil -} diff --git a/pkg/client/clientset.go b/pkg/client/clientset.go index 18a316de7..077dc3175 100644 --- a/pkg/client/clientset.go +++ b/pkg/client/clientset.go @@ -23,6 +23,8 @@ import ( "runtime" "time" + "golang.org/x/oauth2/clientcredentials" + coreapi "github.com/dynatrace/dynatrace-configuration-as-code-core/api" automationApi "github.com/dynatrace/dynatrace-configuration-as-code-core/api/clients/automation" corerest "github.com/dynatrace/dynatrace-configuration-as-code-core/api/rest" @@ -40,16 +42,17 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/metadata" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/version" - "golang.org/x/oauth2/clientcredentials" ) var ( - _ SettingsClient = (*dtclient.DynatraceClient)(nil) - _ ConfigClient = (*dtclient.DynatraceClient)(nil) - _ DynatraceClient = (*dtclient.DynatraceClient)(nil) - _ DynatraceClient = (*dtclient.DummyClient)(nil) + _ SettingsClient = (*dtclient.DynatraceClient)(nil) + _ ConfigClient = (*dtclient.DynatraceClient)(nil) + _ SettingsClient = (*dtclient.DummyClient)(nil) + _ ConfigClient = (*dtclient.DummyClient)(nil) ) +//go:generate mockgen -source=clientset.go -destination=client_mock.go -package=client ConfigClient + // ConfigClient is responsible for the classic Dynatrace configs. For settings objects, the [SettingsClient] is responsible. // Each config endpoint is described by an [API] object to describe endpoints, structure, and behavior. type ConfigClient interface { @@ -94,6 +97,8 @@ type ConfigClient interface { ConfigExistsByName(ctx context.Context, a api.API, name string) (exists bool, id string, err error) } +//go:generate mockgen -source=clientset.go -destination=client_mock.go -package=client SettingsClient + // SettingsClient is the abstraction layer for CRUD operations on the Dynatrace Settings API. // Its design is intentionally not dependent on Monaco objects. // @@ -130,21 +135,6 @@ type SettingsClient interface { DeleteSettings(context.Context, string) error } -//go:generate mockgen -source=clientset.go -destination=client_mock.go -package=client DynatraceClient - -// DynatraceClient provides the functionality for performing basic CRUD operations on any Dynatrace API -// supported by monaco. -// It encapsulates the configuration-specific inconsistencies of certain APIs in one place to provide -// a common interface to work with. After all: A user of Client shouldn't care about the -// implementation details of each individual Dynatrace API. -// Its design is intentionally not dependent on the Config and Environment interfaces included in monaco. -// This makes sure, that Client can be used as a base for future tooling, which relies on -// a standardized way to access Dynatrace APIs. -type DynatraceClient interface { - ConfigClient - SettingsClient -} - type AutomationClient interface { Get(ctx context.Context, resourceType automationApi.ResourceType, id string) (automation.Response, error) Create(ctx context.Context, resourceType automationApi.ResourceType, data []byte) (result automation.Response, err error) @@ -173,7 +163,6 @@ type DocumentClient interface { type OpenPipelineClient interface { GetAll(ctx context.Context) ([]openpipeline.Response, error) - Update(ctx context.Context, id string, data []byte) (openpipeline.Response, error) } @@ -185,24 +174,31 @@ var DefaultRetryOptions = corerest.RetryOptions{MaxRetries: 10, ShouldRetryFunc: // Each field may be nil, if the ClientSet is partially initialized - e.g. no autClient will be part of a ClientSet // created for a 'classic' Dynatrace environment, as Automations are a Platform feature type ClientSet struct { - // dtClient is the client capable of updating or creating settings and classic configs - DTClient DynatraceClient + // ClassicClient is the client capable of updating or creating classic configs + ClassicClient ConfigClient + + // SettingsClient is the client capable of updating or creating settings + SettingsClient SettingsClient + // autClient is the client capable of updating or creating automation API configs AutClient AutomationClient + // bucketClient is the client capable of updating or creating Grail Bucket configs BucketClient BucketClient + // DocumentClient is a client capable of manipulating documents DocumentClient DocumentClient + // OpenPipelineClient is a client capable of manipulating openPipeline configs OpenPipelineClient OpenPipelineClient } func (s ClientSet) Classic() ConfigClient { - return s.DTClient + return s.ClassicClient } func (s ClientSet) Settings() SettingsClient { - return s.DTClient + return s.SettingsClient } func (s ClientSet) Automation() AutomationClient { @@ -255,13 +251,13 @@ func validateURL(dtURL string) error { return nil } -func CreateClientSet(url string, auth manifest.Auth, opts ClientOptions) (*ClientSet, error) { +func CreateClientSet(ctx context.Context, url string, auth manifest.Auth, opts ClientOptions) (*ClientSet, error) { var ( classicClient, client *corerest.Client - bucketClient *buckets.Client - autClient *automation.Client - documentClient *documents.Client - openPipelineClient *openpipeline.Client + bucketClient BucketClient + autClient AutomationClient + documentClient DocumentClient + openPipelineClient OpenPipelineClient err error classicUrl string ) @@ -314,7 +310,7 @@ func CreateClientSet(url string, auth manifest.Auth, opts ClientOptions) (*Clien } if auth.Token != nil { - classicUrl, err = transformPlatformUrlToClassic(url, auth.OAuth, client) + classicUrl, err = transformPlatformUrlToClassic(ctx, url, auth.OAuth, client) if err != nil { return nil, err } @@ -332,7 +328,8 @@ func CreateClientSet(url string, auth manifest.Auth, opts ClientOptions) (*Clien } return &ClientSet{ - DTClient: dtClient, + ClassicClient: dtClient, + SettingsClient: dtClient, AutClient: autClient, BucketClient: bucketClient, DocumentClient: documentClient, @@ -359,10 +356,10 @@ func createDTClient(classicClient *corerest.Client, client *corerest.Client, opt ) } -func transformPlatformUrlToClassic(url string, auth *manifest.OAuth, client *corerest.Client) (string, error) { +func transformPlatformUrlToClassic(ctx context.Context, url string, auth *manifest.OAuth, client *corerest.Client) (string, error) { classicUrl := url if auth != nil && client != nil { - return metadata.GetDynatraceClassicURL(context.TODO(), *client) + return metadata.GetDynatraceClassicURL(ctx, *client) } return classicUrl, nil diff --git a/pkg/client/clientset_test.go b/pkg/client/clientset_test.go index 30d823bc0..70ead453a 100644 --- a/pkg/client/clientset_test.go +++ b/pkg/client/clientset_test.go @@ -17,17 +17,20 @@ package client import ( + "context" "encoding/json" "fmt" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" - "github.com/stretchr/testify/assert" - "golang.org/x/oauth2" "net/http" "net/http/httptest" "strings" "testing" "time" + + "github.com/stretchr/testify/assert" + "golang.org/x/oauth2" + + "github.com/dynatrace/dynatrace-configuration-as-code/v2/cmd/monaco/support" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/manifest" ) func TestCreateClientSet(t *testing.T) { @@ -107,7 +110,7 @@ func TestCreateClientSet(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := CreateClientSet(tt.url, tt.auth, ClientOptions{SupportArchive: support.SupportArchive}) + _, err := CreateClientSet(context.TODO(), tt.url, tt.auth, ClientOptions{SupportArchive: support.SupportArchive}) assert.NoError(t, err) }) } diff --git a/pkg/client/dummy_clientset.go b/pkg/client/dummy_clientset.go new file mode 100644 index 000000000..0eadfd2c5 --- /dev/null +++ b/pkg/client/dummy_clientset.go @@ -0,0 +1,157 @@ +/* + * @license + * Copyright 2024 Dynatrace LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package client + +import ( + context "context" + "fmt" + "net/http" + + coreapi "github.com/dynatrace/dynatrace-configuration-as-code-core/api" + automationApi "github.com/dynatrace/dynatrace-configuration-as-code-core/api/clients/automation" + "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/automation" + buckets "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/buckets" + documents "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/documents" + openpipeline "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/openpipeline" + dtclient "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/dtclient" +) + +var DummyClientSet = ClientSet{ + ClassicClient: &dtclient.DummyClient{}, + SettingsClient: &dtclient.DummyClient{}, + AutClient: &DummyAutomationClient{}, + BucketClient: &DummyBucketClient{}, + DocumentClient: &DummyDocumentClient{}, + OpenPipelineClient: &DummyOpenPipelineClient{}, +} + +var _ AutomationClient = (*DummyAutomationClient)(nil) + +type DummyAutomationClient struct { +} + +// Create implements AutomationClient. +func (d *DummyAutomationClient) Create(ctx context.Context, resourceType automationApi.ResourceType, data []byte) (result coreapi.Response, err error) { + panic("unimplemented") +} + +// Delete implements AutomationClient. +func (d *DummyAutomationClient) Delete(ctx context.Context, resourceType automationApi.ResourceType, id string) (coreapi.Response, error) { + panic("unimplemented") +} + +// Get implements AutomationClient. +func (d *DummyAutomationClient) Get(ctx context.Context, resourceType automationApi.ResourceType, id string) (coreapi.Response, error) { + panic("unimplemented") +} + +// List implements AutomationClient. +func (d *DummyAutomationClient) List(ctx context.Context, resourceType automationApi.ResourceType) (coreapi.PagedListResponse, error) { + panic("unimplemented") +} + +// Update implements AutomationClient. +func (d *DummyAutomationClient) Update(ctx context.Context, resourceType automationApi.ResourceType, id string, data []byte) (coreapi.Response, error) { + panic("unimplemented") +} + +// Upsert implements AutomationClient. +func (d *DummyAutomationClient) Upsert(ctx context.Context, resourceType automationApi.ResourceType, id string, data []byte) (result coreapi.Response, err error) { + return automation.Response{ + StatusCode: 200, + Data: []byte(fmt.Sprintf(`{"id" : "%s"}`, id)), + }, nil +} + +var _ BucketClient = (*DummyBucketClient)(nil) + +type DummyBucketClient struct{} + +// Create implements BucketClient. +func (d *DummyBucketClient) Create(ctx context.Context, bucketName string, data []byte) (coreapi.Response, error) { + panic("unimplemented") +} + +// Delete implements BucketClient. +func (d *DummyBucketClient) Delete(ctx context.Context, bucketName string) (coreapi.Response, error) { + panic("unimplemented") +} + +// Get implements BucketClient. +func (d *DummyBucketClient) Get(ctx context.Context, bucketName string) (coreapi.Response, error) { + panic("unimplemented") +} + +// List implements BucketClient. +func (d *DummyBucketClient) List(ctx context.Context) (coreapi.PagedListResponse, error) { + panic("unimplemented") +} + +// Update implements BucketClient. +func (d *DummyBucketClient) Update(ctx context.Context, bucketName string, data []byte) (coreapi.Response, error) { + panic("unimplemented") +} + +// Upsert implements BucketClient. +func (d *DummyBucketClient) Upsert(ctx context.Context, bucketName string, data []byte) (coreapi.Response, error) { + return buckets.Response{ + StatusCode: http.StatusOK, + Data: data, + }, nil +} + +var _ DocumentClient = (*DummyDocumentClient)(nil) + +type DummyDocumentClient struct{} + +// Create implements Client. +func (c *DummyDocumentClient) Create(ctx context.Context, name string, isPrivate bool, externalId string, data []byte, documentType documents.DocumentType) (coreapi.Response, error) { + return coreapi.Response{Data: []byte(`{}`)}, nil +} + +// Get implements Client. +func (c *DummyDocumentClient) Get(ctx context.Context, id string) (documents.Response, error) { + return documents.Response{}, nil +} + +// List implements Client. +func (c *DummyDocumentClient) List(ctx context.Context, filter string) (documents.ListResponse, error) { + return documents.ListResponse{}, nil +} + +// Update implements Client. +func (c *DummyDocumentClient) Update(ctx context.Context, id string, name string, isPrivate bool, data []byte, documentType documents.DocumentType) (coreapi.Response, error) { + return coreapi.Response{Data: []byte(`{}`)}, nil +} + +// Delete implements DocumentClient. +func (c *DummyDocumentClient) Delete(ctx context.Context, id string) (coreapi.Response, error) { + panic("unimplemented") +} + +var _ OpenPipelineClient = (*DummyOpenPipelineClient)(nil) + +type DummyOpenPipelineClient struct{} + +// GetAll implements OpenPipelineClient. +func (c *DummyOpenPipelineClient) GetAll(ctx context.Context) ([]coreapi.Response, error) { + panic("unimplemented") +} + +func (c *DummyOpenPipelineClient) Update(_ context.Context, _ string, _ []byte) (openpipeline.Response, error) { + return openpipeline.Response{}, nil +} diff --git a/pkg/delete/delete.go b/pkg/delete/delete.go index 1e37be9a4..d15dac44d 100644 --- a/pkg/delete/delete.go +++ b/pkg/delete/delete.go @@ -19,10 +19,7 @@ package delete import ( "context" "fmt" - coreAutomation "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/automation" - "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/buckets" - "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/documents" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/dtclient" + "maps" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" @@ -38,91 +35,78 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/delete/pointer" ) -type ClientSet struct { - Classic client.ConfigClient - Settings client.SettingsClient - Automation client.AutomationClient - Buckets client.BucketClient - Documents client.DocumentClient -} - type configurationType = string // DeleteEntries is a map of configuration type to slice of delete pointers type DeleteEntries = map[configurationType][]pointer.DeletePointer // Configs removes all given entriesToDelete from the Dynatrace environment the given client connects to -func Configs(ctx context.Context, clients ClientSet, _ api.APIs, automationResources map[string]config.AutomationResource, entriesToDelete DeleteEntries) error { - copiedDeleteEntries := make(DeleteEntries) - for k, v := range entriesToDelete { - copiedDeleteEntries[k] = v +func Configs(ctx context.Context, clients client.ClientSet, entriesToDelete DeleteEntries) error { + remainingEntriesToDelete, errCount := deleteAutomationConfigs(ctx, clients.AutClient, entriesToDelete) + + // Dashboard share settings cannot be deleted + if _, ok := remainingEntriesToDelete[api.DashboardShareSettings]; ok { + log.Warn("Classic config of type %s cannot be deleted. Note, that they can be removed by deleting the associated dashboard.", api.DashboardShareSettings) + delete(remainingEntriesToDelete, api.DashboardShareSettings) } - var deleteErrors int + // Delete rest of config types + for t, entries := range remainingEntriesToDelete { + if err := deleteConfig(ctx, clients, t, entries); err != nil { + log.WithFields(field.Error(err)).Error("Error during deletion: %v", err) + errCount += 1 + } + } - // Delete automation resources (in the specified order) + if errCount > 0 { + return fmt.Errorf("encountered %d errors", errCount) + } + return nil +} + +func deleteAutomationConfigs(ctx context.Context, autClient client.AutomationClient, allEntries DeleteEntries) (DeleteEntries, int) { + remainingDeleteEntries := maps.Clone(allEntries) + errCount := 0 automationTypeOrder := []config.AutomationResource{config.Workflow, config.SchedulingRule, config.BusinessCalendar} for _, key := range automationTypeOrder { - entries := copiedDeleteEntries[string(key)] - if clients.Automation == (*coreAutomation.Client)(nil) { + entries := allEntries[string(key)] + delete(remainingDeleteEntries, string(key)) + if autClient == nil { log.WithCtxFields(ctx).WithFields(field.Type(key)).Warn("Skipped deletion of %d Automation configuration(s) of type %q as API client was unavailable.", len(entries), key) - delete(copiedDeleteEntries, string(key)) continue } - err := automation.Delete(ctx, clients.Automation, automationResources[string(key)], entries) + err := automation.Delete(ctx, autClient, key, entries) if err != nil { log.WithFields(field.Error(err)).Error("Error during deletion: %v", err) - deleteErrors += 1 + errCount += 1 } - delete(copiedDeleteEntries, string(key)) - } - - // Dashboard share settings cannot be deleted - if _, ok := copiedDeleteEntries[api.DashboardShareSettings]; ok { - log.Warn("Classic config of type %s cannot be deleted. Note, that they can be removed by deleting the associated dashboard.", api.DashboardShareSettings) - delete(copiedDeleteEntries, api.DashboardShareSettings) - } + return remainingDeleteEntries, errCount +} - // Delete rest of config types - for t, entries := range copiedDeleteEntries { - var err error - if _, ok := api.NewAPIs()[t]; ok { - if clients.Classic == (*dtclient.DynatraceClient)(nil) { - log.WithCtxFields(ctx).WithFields(field.Type(t)).Warn("Skipped deletion of %d Classic configuration(s) as API client was unavailable.", len(entries)) - continue - } - err = classic.Delete(ctx, clients.Classic, entries) - } else if t == "bucket" { - if clients.Buckets == (*buckets.Client)(nil) { - log.WithCtxFields(ctx).WithFields(field.Type(t)).Warn("Skipped deletion of %d Grail Bucket configuration(s) as API client was unavailable.", len(entries)) - continue - } - err = bucket.Delete(ctx, clients.Buckets, entries) - } else if t == "document" { - if featureflags.Temporary[featureflags.Documents].Enabled() && featureflags.Temporary[featureflags.DeleteDocuments].Enabled() { - if clients.Documents == (*documents.Client)(nil) { - log.WithCtxFields(ctx).WithFields(field.Type(t)).Warn("Skipped deletion of %d Document configuration(s) as API client was unavailable.", len(entries)) - continue - } - err = document.Delete(ctx, clients.Documents, entries) - } - } else { - if clients.Settings == (*dtclient.DynatraceClient)(nil) { - log.WithCtxFields(ctx).WithFields(field.Type(t)).Warn("Skipped deletion of %d Settings configuration(s) as API client was unavailable.", len(entries)) - continue +func deleteConfig(ctx context.Context, clients client.ClientSet, t string, entries []pointer.DeletePointer) error { + if _, ok := api.NewAPIs()[t]; ok { + if clients.ClassicClient != nil { + return classic.Delete(ctx, clients.ClassicClient, entries) + } + log.WithCtxFields(ctx).WithFields(field.Type(t)).Warn("Skipped deletion of %d Classic configuration(s) as API client was unavailable.", len(entries)) + } else if t == "bucket" { + if clients.BucketClient != nil { + return bucket.Delete(ctx, clients.BucketClient, entries) + } + log.WithCtxFields(ctx).WithFields(field.Type(t)).Warn("Skipped deletion of %d Grail Bucket configuration(s) as API client was unavailable.", len(entries)) + } else if t == "document" { + if featureflags.Temporary[featureflags.Documents].Enabled() && featureflags.Temporary[featureflags.DeleteDocuments].Enabled() { + if clients.DocumentClient != nil { + return document.Delete(ctx, clients.DocumentClient, entries) } - err = setting.Delete(ctx, clients.Settings, entries) + log.WithCtxFields(ctx).WithFields(field.Type(t)).Warn("Skipped deletion of %d Document configuration(s) as API client was unavailable.", len(entries)) } - - if err != nil { - log.WithFields(field.Error(err)).Error("Error during deletion: %v", err) - deleteErrors += 1 + } else { + if clients.SettingsClient != nil { + return setting.Delete(ctx, clients.SettingsClient, entries) } - } - - if deleteErrors > 0 { - return fmt.Errorf("encountered %d errors", deleteErrors) + log.WithCtxFields(ctx).WithFields(field.Type(t)).Warn("Skipped deletion of %d Settings configuration(s) as API client was unavailable.", len(entries)) } return nil } @@ -133,48 +117,48 @@ func Configs(ctx context.Context, clients ClientSet, _ api.APIs, automationResou // Parameters: // - ctx (context.Context): The context in which the function operates. // - clients (ClientSet): A set of API clients used to collect and delete configurations from an environment. -func All(ctx context.Context, clients ClientSet, apis api.APIs) error { - errs := 0 +func All(ctx context.Context, clients client.ClientSet, apis api.APIs) error { + errCount := 0 - if clients.Classic == (*dtclient.DynatraceClient)(nil) { + if clients.ClassicClient == nil { log.Warn("Skipped deletion of classic configurations as API client was unavailable.") - } else if err := classic.DeleteAll(ctx, clients.Classic, apis); err != nil { + } else if err := classic.DeleteAll(ctx, clients.ClassicClient, apis); err != nil { log.Error("Failed to delete all classic API configurations: %v", err) - errs++ + errCount++ } - if clients.Settings == (*dtclient.DynatraceClient)(nil) { + if clients.SettingsClient == nil { log.Warn("Skipped deletion of settings configurations as API client was unavailable.") - } else if err := setting.DeleteAll(ctx, clients.Settings); err != nil { + } else if err := setting.DeleteAll(ctx, clients.SettingsClient); err != nil { log.Error("Failed to delete all Settings 2.0 objects: %v", err) - errs++ + errCount++ } - if clients.Automation == (*coreAutomation.Client)(nil) { + if clients.AutClient == nil { log.Warn("Skipped deletion of Automation configurations as API client was unavailable.") - } else if err := automation.DeleteAll(ctx, clients.Automation); err != nil { + } else if err := automation.DeleteAll(ctx, clients.AutClient); err != nil { log.Error("Failed to delete all Automation configurations: %v", err) - errs++ + errCount++ } - if clients.Buckets == (*buckets.Client)(nil) { + if clients.BucketClient == nil { log.Warn("Skipped deletion of Grail Bucket configurations as API client was unavailable.") - } else if err := bucket.DeleteAll(ctx, clients.Buckets); err != nil { + } else if err := bucket.DeleteAll(ctx, clients.BucketClient); err != nil { log.Error("Failed to delete all Grail Bucket configurations: %v", err) - errs++ + errCount++ } if featureflags.Temporary[featureflags.Documents].Enabled() && featureflags.Temporary[featureflags.DeleteDocuments].Enabled() { - if clients.Documents == (*documents.Client)(nil) { + if clients.DocumentClient == nil { log.Warn("Skipped deletion of Documents configurations as appropriate client was unavailable.") - } else if err := document.DeleteAll(ctx, clients.Documents); err != nil { + } else if err := document.DeleteAll(ctx, clients.DocumentClient); err != nil { log.Error("Failed to delete all Document configurations: %v", err) - errs++ + errCount++ } } - if errs > 0 { - return fmt.Errorf("failed to delete all configurations for %d types", errs) + if errCount > 0 { + return fmt.Errorf("failed to delete all configurations for %d types", errCount) } return nil } diff --git a/pkg/delete/delete_test.go b/pkg/delete/delete_test.go index 69398951a..0ad136c96 100644 --- a/pkg/delete/delete_test.go +++ b/pkg/delete/delete_test.go @@ -27,6 +27,10 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + coreapi "github.com/dynatrace/dynatrace-configuration-as-code-core/api" libAPI "github.com/dynatrace/dynatrace-configuration-as-code-core/api" "github.com/dynatrace/dynatrace-configuration-as-code-core/api/rest" @@ -39,23 +43,13 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/dtclient" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/delete" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/delete/pointer" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) -var automationTypes = map[string]config.AutomationResource{ - string(config.Workflow): config.Workflow, - string(config.BusinessCalendar): config.BusinessCalendar, - string(config.SchedulingRule): config.SchedulingRule, -} - func TestDeleteSettings_LegacyExternalID(t *testing.T) { t.Run("TestDeleteSettings_LegacyExternalID", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, schemaID string, listOpts dtclient.ListSettingsOptions) ([]dtclient.DownloadSettingsObject, error) { assert.True(t, listOpts.Filter(dtclient.DownloadSettingsObject{ExternalId: "monaco:YnVpbHRpbjphbGVydGluZy5wcm9maWxlJGlkMQ=="})) return []dtclient.DownloadSettingsObject{ @@ -78,12 +72,12 @@ func TestDeleteSettings_LegacyExternalID(t *testing.T) { }, }, } - errs := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + errs := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.Empty(t, errs, "errors should be empty") }) t.Run("TestDeleteSettings_LegacyExternalID - List settings with external ID fails", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Return([]dtclient.DownloadSettingsObject{}, coreapi.APIError{StatusCode: 0}) entriesToDelete := delete.DeleteEntries{ "builtin:alerting.profile": { @@ -93,12 +87,12 @@ func TestDeleteSettings_LegacyExternalID(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.Error(t, err) }) t.Run("TestDeleteSettings_LegacyExternalID - List settings returns no objects", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Return([]dtclient.DownloadSettingsObject{}, nil) entriesToDelete := delete.DeleteEntries{ "builtin:alerting.profile": { @@ -108,12 +102,12 @@ func TestDeleteSettings_LegacyExternalID(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.NoError(t, err) }) t.Run("TestDeleteSettings_LegacyExternalID - Delete settings based on object ID fails", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Return([]dtclient.DownloadSettingsObject{ { ExternalId: "externalID", @@ -133,14 +127,14 @@ func TestDeleteSettings_LegacyExternalID(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.Error(t, err) }) } func TestDeleteSettings(t *testing.T) { t.Run("TestDeleteSettings", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Eq("builtin:alerting.profile"), gomock.Any()).DoAndReturn(func(ctx context.Context, schemaID string, listOpts dtclient.ListSettingsOptions) ([]dtclient.DownloadSettingsObject, error) { expectedExtID := "monaco:cHJvamVjdCRidWlsdGluOmFsZXJ0aW5nLnByb2ZpbGUkaWQx" assert.True(t, listOpts.Filter(dtclient.DownloadSettingsObject{ExternalId: expectedExtID}), "Expected request filtering for externalID %q", expectedExtID) @@ -166,12 +160,12 @@ func TestDeleteSettings(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.NoError(t, err) }) t.Run("TestDeleteSettings - List settings with external ID fails", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Return([]dtclient.DownloadSettingsObject{}, coreapi.APIError{StatusCode: 0}) entriesToDelete := delete.DeleteEntries{ "builtin:alerting.profile": { @@ -182,12 +176,12 @@ func TestDeleteSettings(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.Error(t, err) }) t.Run("TestDeleteSettings - List settings returns no objects", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Return([]dtclient.DownloadSettingsObject{}, nil) entriesToDelete := delete.DeleteEntries{ "builtin:alerting.profile": { @@ -198,12 +192,12 @@ func TestDeleteSettings(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.NoError(t, err) }) t.Run("TestDeleteSettings - Delete settings based on object ID fails", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).Return([]dtclient.DownloadSettingsObject{ { ExternalId: "externalID", @@ -224,12 +218,12 @@ func TestDeleteSettings(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.Error(t, err) }) t.Run("TestDeleteSettings - Skips non-deletable Objects", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, schemaID string, listOpts dtclient.ListSettingsOptions) ([]dtclient.DownloadSettingsObject, error) { expectedExtID := "monaco:cHJvamVjdCRidWlsdGluOmFsZXJ0aW5nLnByb2ZpbGUkaWQx" assert.True(t, listOpts.Filter(dtclient.DownloadSettingsObject{ExternalId: expectedExtID}), "Expected request filtering for externalID %q", expectedExtID) @@ -259,12 +253,12 @@ func TestDeleteSettings(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.NoError(t, err) }) t.Run("identification via 'objectId'", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().ListSettings(gomock.Any(), gomock.Eq("builtin:alerting.profile"), gomock.Any()).DoAndReturn(func(ctx context.Context, schemaID string, listOpts dtclient.ListSettingsOptions) ([]dtclient.DownloadSettingsObject, error) { assert.True(t, listOpts.Filter(dtclient.DownloadSettingsObject{ObjectId: "DT-original-object-ID"}), "Expected request filtering for objectId %q", "DT-original-object-ID") return []dtclient.DownloadSettingsObject{ @@ -288,7 +282,7 @@ func TestDeleteSettings(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Settings: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{SettingsClient: c}, entriesToDelete) assert.NoError(t, err) }) @@ -319,7 +313,7 @@ func TestDelete_Automations(t *testing.T) { }, }, } - errs := delete.Configs(context.TODO(), delete.ClientSet{Automation: c}, api.NewAPIs(), automationTypes, entriesToDelete) + errs := delete.Configs(context.TODO(), client.ClientSet{AutClient: c}, entriesToDelete) assert.Empty(t, errs, "errors should be empty") }) @@ -377,7 +371,7 @@ func TestDelete_Automations(t *testing.T) { }, }, } - errs := delete.Configs(context.TODO(), delete.ClientSet{Automation: c}, api.NewAPIs(), automationTypes, entriesToDelete) + errs := delete.Configs(context.TODO(), client.ClientSet{AutClient: c}, entriesToDelete) assert.Empty(t, errs, "errors should be empty") assert.True(t, workflowDeleted, "expected workflow to be deleted but it was not") assert.True(t, calendarDeleted, "expected business-calendar to be deleted but it was not") @@ -407,7 +401,7 @@ func TestDelete_Automations(t *testing.T) { }, }, } - errs := delete.Configs(context.TODO(), delete.ClientSet{Automation: c}, api.NewAPIs(), automationTypes, entriesToDelete) + errs := delete.Configs(context.TODO(), client.ClientSet{AutClient: c}, entriesToDelete) assert.Empty(t, errs, "errors should be empty") }) @@ -434,7 +428,7 @@ func TestDelete_Automations(t *testing.T) { }, }, } - err = delete.Configs(context.TODO(), delete.ClientSet{Automation: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err = delete.Configs(context.TODO(), client.ClientSet{AutClient: c}, entriesToDelete) assert.Error(t, err) }) @@ -461,7 +455,7 @@ func TestDelete_Automations(t *testing.T) { }, }, } - errs := delete.Configs(context.TODO(), delete.ClientSet{Automation: c}, api.NewAPIs(), automationTypes, entriesToDelete) + errs := delete.Configs(context.TODO(), client.ClientSet{AutClient: c}, entriesToDelete) assert.Empty(t, errs, "errors should be empty") }) } @@ -512,7 +506,7 @@ func TestDeleteBuckets(t *testing.T) { }, }, } - errs := delete.Configs(context.TODO(), delete.ClientSet{Buckets: c}, api.NewAPIs(), automationTypes, entriesToDelete) + errs := delete.Configs(context.TODO(), client.ClientSet{BucketClient: c}, entriesToDelete) assert.Empty(t, errs, "errors should be empty") }) @@ -538,7 +532,7 @@ func TestDeleteBuckets(t *testing.T) { }, }, } - errs := delete.Configs(context.TODO(), delete.ClientSet{Buckets: c}, api.NewAPIs(), automationTypes, entriesToDelete) + errs := delete.Configs(context.TODO(), client.ClientSet{BucketClient: c}, entriesToDelete) assert.Empty(t, errs, "errors should be empty") }) @@ -564,7 +558,7 @@ func TestDeleteBuckets(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Buckets: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{BucketClient: c}, entriesToDelete) assert.Error(t, err, "there should be one delete error") }) @@ -612,7 +606,7 @@ func TestDeleteBuckets(t *testing.T) { }, }, } - errs := delete.Configs(context.TODO(), delete.ClientSet{Buckets: c}, api.NewAPIs(), automationTypes, entriesToDelete) + errs := delete.Configs(context.TODO(), client.ClientSet{BucketClient: c}, entriesToDelete) assert.Empty(t, errs, "errors should be empty") }) @@ -716,10 +710,10 @@ func TestSplitConfigsForDeletion(t *testing.T) { } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - apiMap := api.APIs{a.ID: a} + entriesToDelete := delete.DeleteEntries{a.ID: tc.args.entries} - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) if len(tc.args.entries) > 0 { c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(a)).Return(tc.args.values, nil).Times(len(tc.args.entries)) } @@ -727,7 +721,7 @@ func TestSplitConfigsForDeletion(t *testing.T) { c.EXPECT().DeleteConfigById(gomock.Any(), matcher.EqAPI(a), id).Times(1) } - err := delete.Configs(context.TODO(), delete.ClientSet{Classic: c}, apiMap, automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{ClassicClient: c}, entriesToDelete) if tc.expect.err { assert.Error(t, err) } else { @@ -928,7 +922,7 @@ func TestConfigsWithParent(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) if tc.mock.parentList != nil { c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(tc.mock.parentList.api)).Return(tc.mock.parentList.response, tc.mock.parentList.err).Times(1) } @@ -939,7 +933,7 @@ func TestConfigsWithParent(t *testing.T) { c.EXPECT().DeleteConfigById(gomock.Any(), matcher.EqAPI(tc.mock.del.api), tc.mock.del.id).Return(tc.mock.del.err).Times(1) } - err := delete.Configs(context.TODO(), delete.ClientSet{Classic: c}, api.NewAPIs(), automationTypes, tc.forDelete) + err := delete.Configs(context.TODO(), client.ClientSet{ClassicClient: c}, tc.forDelete) if !tc.wantErr { assert.NoError(t, err) } else { @@ -951,7 +945,7 @@ func TestConfigsWithParent(t *testing.T) { func TestDelete_Classic(t *testing.T) { t.Run("identification via 'name'", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) theAPI := api.NewAPIs()[api.ApplicationWeb] c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(theAPI)).Return([]dtclient.Value{{Id: "DT-id-of-app", Name: "application name"}}, nil).Times(1) c.EXPECT().DeleteConfigById(gomock.Any(), matcher.EqAPI(theAPI.ApplyParentObjectID("APP-ID")), "DT-id-of-app").Return(nil).Times(1) @@ -965,12 +959,12 @@ func TestDelete_Classic(t *testing.T) { }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Classic: c}, api.NewAPIs(), automationTypes, given) + err := delete.Configs(context.TODO(), client.ClientSet{ClassicClient: c}, given) require.NoError(t, err) }) t.Run("identification via 'objectId'", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) c.EXPECT().DeleteConfigById(gomock.Any(), matcher.EqAPI(api.NewAPIs()["application-web"]), "DT-id-of-app").Return(nil).Times(1) given := delete.DeleteEntries{ @@ -982,12 +976,12 @@ func TestDelete_Classic(t *testing.T) { }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Classic: c}, api.NewAPIs(), automationTypes, given) + err := delete.Configs(context.TODO(), client.ClientSet{ClassicClient: c}, given) require.NoError(t, err) }) t.Run("skip delete of DashboardShareSettings", func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) given := delete.DeleteEntries{ "dashboard-share-settings": { { @@ -997,7 +991,7 @@ func TestDelete_Classic(t *testing.T) { }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Classic: c}, api.NewAPIs(), automationTypes, given) + err := delete.Configs(context.TODO(), client.ClientSet{ClassicClient: c}, given) require.NoError(t, err) }) } @@ -1005,7 +999,7 @@ func TestDelete_Classic(t *testing.T) { func TestDeleteClassicKeyUserActionsWeb(t *testing.T) { t.Run("uniqueness", func(t *testing.T) { theAPI := api.NewAPIs()[api.KeyUserActionsWeb] - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(*theAPI.Parent)).Return([]dtclient.Value{{Id: "APP-ID", Name: "application name"}}, nil).Times(1) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(theAPI.ApplyParentObjectID("APP-ID"))).Return([]dtclient.Value{ @@ -1026,13 +1020,13 @@ func TestDeleteClassicKeyUserActionsWeb(t *testing.T) { }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Classic: c}, api.NewAPIs(), automationTypes, de) + err := delete.Configs(context.TODO(), client.ClientSet{ClassicClient: c}, de) assert.NoError(t, err) }) t.Run("identification via 'objectId'", func(t *testing.T) { theAPI := api.NewAPIs()[api.KeyUserActionsWeb] - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(*theAPI.Parent)).Return([]dtclient.Value{{Id: "APP-ID", Name: "application name"}}, nil).Times(1) c.EXPECT().DeleteConfigById(gomock.Any(), matcher.EqAPI(theAPI.ApplyParentObjectID("APP-ID")), "DT-id-of-app").Return(nil).Times(1) @@ -1049,7 +1043,7 @@ func TestDeleteClassicKeyUserActionsWeb(t *testing.T) { }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Classic: c}, api.NewAPIs(), automationTypes, de) + err := delete.Configs(context.TODO(), client.ClientSet{ClassicClient: c}, de) assert.NoError(t, err) }) } @@ -1072,7 +1066,7 @@ func TestDelete_Documents(t *testing.T) { c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1) entriesToDelete := delete.DeleteEntries{given.Type: {given}} - err := delete.Configs(context.TODO(), delete.ClientSet{Documents: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{DocumentClient: c}, entriesToDelete) assert.NoError(t, err) }) @@ -1090,7 +1084,7 @@ func TestDelete_Documents(t *testing.T) { Return(documents.ListResponse{Responses: []documents.Response{}}, nil) entriesToDelete := delete.DeleteEntries{given.Type: {given}} - err := delete.Configs(context.TODO(), delete.ClientSet{Documents: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{DocumentClient: c}, entriesToDelete) assert.NoError(t, err) }) @@ -1108,7 +1102,7 @@ func TestDelete_Documents(t *testing.T) { Return(documents.ListResponse{Responses: []documents.Response{{Metadata: documents.Metadata{ID: "originObjectID_1"}}, {Metadata: documents.Metadata{ID: "originObjectID_2"}}}}, nil) entriesToDelete := delete.DeleteEntries{given.Type: {given}} - err := delete.Configs(context.TODO(), delete.ClientSet{Documents: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{DocumentClient: c}, entriesToDelete) assert.Error(t, err) }) @@ -1124,7 +1118,7 @@ func TestDelete_Documents(t *testing.T) { }, }, } - err := delete.Configs(context.TODO(), delete.ClientSet{Documents: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{DocumentClient: c}, entriesToDelete) assert.NoError(t, err) }) @@ -1138,7 +1132,7 @@ func TestDelete_Documents(t *testing.T) { c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, coreapi.APIError{StatusCode: http.StatusNotFound}) entriesToDelete := delete.DeleteEntries{given.Type: {given}} - err := delete.Configs(context.TODO(), delete.ClientSet{Documents: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{DocumentClient: c}, entriesToDelete) assert.NoError(t, err) }) @@ -1151,7 +1145,7 @@ func TestDelete_Documents(t *testing.T) { c.EXPECT().Delete(gomock.Any(), gomock.Eq("originObjectID")).Times(1).Return(libAPI.Response{}, coreapi.APIError{StatusCode: http.StatusInternalServerError}) // the error can be any kind except 404 entriesToDelete := delete.DeleteEntries{given.Type: {given}} - err := delete.Configs(context.TODO(), delete.ClientSet{Documents: c}, api.NewAPIs(), automationTypes, entriesToDelete) + err := delete.Configs(context.TODO(), client.ClientSet{DocumentClient: c}, entriesToDelete) assert.Error(t, err) }) } diff --git a/pkg/deploy/deploy.go b/pkg/deploy/deploy.go index 518532f3c..74dc9e8aa 100644 --- a/pkg/deploy/deploy.go +++ b/pkg/deploy/deploy.go @@ -33,7 +33,6 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/multierror" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/dtclient" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/entities" deployErrors "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/errors" @@ -59,24 +58,6 @@ type DeployConfigsOptions struct { DryRun bool } -type ClientSet struct { - Classic client.ConfigClient - Settings client.SettingsClient - Automation automation.Client - Bucket bucket.Client - Document document.Client - OpenPipeline openpipeline.Client -} - -var DummyClientSet = ClientSet{ - Classic: &dtclient.DummyClient{}, - Settings: &dtclient.DummyClient{}, - Automation: &automation.DummyClient{}, - Bucket: &bucket.DummyClient{}, - Document: &document.DummyClient{}, - OpenPipeline: &openpipeline.DummyClient{}, -} - var ( lock sync.Mutex @@ -95,7 +76,7 @@ func Deploy(ctx context.Context, projects []project.Project, environmentClients errors.As(validationErrs, &deploymentErrors) } - for env, clients := range environmentClients { + for env, clientset := range environmentClients { ctx := newContextWithEnvironment(ctx, env) log.WithCtxFields(ctx).Info("Deploying configurations to environment %q...", env.Name) @@ -104,21 +85,7 @@ func Deploy(ctx context.Context, projects []project.Project, environmentClients return fmt.Errorf("failed to get independently sorted configs for environment %q: %w", env.Name, err) } - var clientSet ClientSet - if opts.DryRun { - clientSet = DummyClientSet - } else { - clientSet = ClientSet{ - Classic: clients.DTClient, - Settings: clients.DTClient, - Automation: clients.AutClient, - Bucket: clients.BucketClient, - Document: clients.DocumentClient, - OpenPipeline: clients.OpenPipelineClient, - } - } - - if err = deployComponents(ctx, sortedConfigs, clientSet); err != nil { + if err = deployComponents(ctx, sortedConfigs, clientset); err != nil { log.WithFields(field.Environment(env.Name, env.Group), field.Error(err)).Error("Deployment failed for environment %q: %v", env.Name, err) deploymentErrors = deploymentErrors.Append(env.Name, err) if !opts.ContinueOnErr && !opts.DryRun { @@ -136,7 +103,7 @@ func Deploy(ctx context.Context, projects []project.Project, environmentClients return nil } -func deployComponents(ctx context.Context, components []graph.SortedComponent, clients ClientSet) error { +func deployComponents(ctx context.Context, components []graph.SortedComponent, clientset *client.ClientSet) error { log.WithCtxFields(ctx).Info("Deploying %d independent configuration sets in parallel...", len(components)) errCount := 0 errChan := make(chan error, len(components)) @@ -145,7 +112,7 @@ func deployComponents(ctx context.Context, components []graph.SortedComponent, c // Iterate over components and launch a goroutine for each component deployment. for i := range components { go func(ctx context.Context, component graph.SortedComponent) { - errChan <- deployGraph(ctx, component.Graph, clients, resolvedEntities) + errChan <- deployGraph(ctx, component.Graph, clientset, resolvedEntities) }(context.WithValue(ctx, log.CtxGraphComponentId{}, log.CtxValGraphComponentId(i)), components[i]) } @@ -168,7 +135,7 @@ func deployComponents(ctx context.Context, components []graph.SortedComponent, c return nil } -func deployGraph(ctx context.Context, configGraph *simple.DirectedGraph, clients ClientSet, resolvedEntities *entities.EntityMap) error { +func deployGraph(ctx context.Context, configGraph *simple.DirectedGraph, clientset *client.ClientSet, resolvedEntities *entities.EntityMap) error { g := simple.NewDirectedGraph() gonum.Copy(g, configGraph) @@ -183,7 +150,7 @@ func deployGraph(ctx context.Context, configGraph *simple.DirectedGraph, clients time.Sleep(api.NewAPIs()[node.Config.Coordinate.Type].DeployWaitDuration) go func(ctx context.Context, node graph.ConfigNode) { - errChan <- deployNode(ctx, node, configGraph, clients, resolvedEntities) + errChan <- deployNode(ctx, node, configGraph, clientset, resolvedEntities) }(context.WithValue(ctx, log.CtxKeyCoord{}, node.Config.Coordinate), node) } @@ -209,9 +176,9 @@ func deployGraph(ctx context.Context, configGraph *simple.DirectedGraph, clients return nil } -func deployNode(ctx context.Context, n graph.ConfigNode, configGraph graph.ConfigGraph, clients ClientSet, resolvedEntities *entities.EntityMap) error { +func deployNode(ctx context.Context, n graph.ConfigNode, configGraph graph.ConfigGraph, clientset *client.ClientSet, resolvedEntities *entities.EntityMap) error { ctx = report.NewContextWithDetailer(ctx, report.NewDefaultDetailer()) - resolvedEntity, err := deployConfig(ctx, n.Config, clients, resolvedEntities) + resolvedEntity, err := deployConfig(ctx, n.Config, clientset, resolvedEntities) details := report.GetDetailerFromContextOrDiscard(ctx).GetAll() if err != nil { @@ -276,7 +243,7 @@ func removeChildren(ctx context.Context, parent, root graph.ConfigNode, configGr } } -func deployConfig(ctx context.Context, c *config.Config, clients ClientSet, resolvedEntities config.EntityLookup) (entities.ResolvedEntity, error) { +func deployConfig(ctx context.Context, c *config.Config, clientset *client.ClientSet, resolvedEntities config.EntityLookup) (entities.ResolvedEntity, error) { if c.Skip { log.WithCtxFields(ctx).WithFields(field.StatusDeploymentSkipped()).Info("Skipping deployment of config") return entities.ResolvedEntity{}, skipError //fake resolved entity that "old" deploy creates is never needed, as we don't even try to deploy dependencies of skipped configs (so no reference will ever be attempted to resolve) @@ -306,27 +273,27 @@ func deployConfig(ctx context.Context, c *config.Config, clients ClientSet, reso if ia, ok := properties[config.InsertAfterParameter]; ok { insertAfter = ia.(string) } - resolvedEntity, deployErr = setting.Deploy(ctx, clients.Settings, properties, renderedConfig, c, insertAfter) + resolvedEntity, deployErr = setting.Deploy(ctx, clientset.SettingsClient, properties, renderedConfig, c, insertAfter) case config.ClassicApiType: - resolvedEntity, deployErr = classic.Deploy(ctx, clients.Classic, api.NewAPIs(), properties, renderedConfig, c) + resolvedEntity, deployErr = classic.Deploy(ctx, clientset.ClassicClient, api.NewAPIs(), properties, renderedConfig, c) case config.AutomationType: - resolvedEntity, deployErr = automation.Deploy(ctx, clients.Automation, properties, renderedConfig, c) + resolvedEntity, deployErr = automation.Deploy(ctx, clientset.AutClient, properties, renderedConfig, c) case config.BucketType: - resolvedEntity, deployErr = bucket.Deploy(ctx, clients.Bucket, properties, renderedConfig, c) + resolvedEntity, deployErr = bucket.Deploy(ctx, clientset.BucketClient, properties, renderedConfig, c) case config.DocumentType: if featureflags.Temporary[featureflags.Documents].Enabled() { - resolvedEntity, deployErr = document.Deploy(ctx, clients.Document, properties, renderedConfig, c) + resolvedEntity, deployErr = document.Deploy(ctx, clientset.DocumentClient, properties, renderedConfig, c) } else { deployErr = fmt.Errorf("unknown config-type (ID: %q)", c.Type.ID()) } case config.OpenPipelineType: if featureflags.Temporary[featureflags.OpenPipeline].Enabled() { - resolvedEntity, deployErr = openpipeline.Deploy(ctx, clients.OpenPipeline, properties, renderedConfig, c) + resolvedEntity, deployErr = openpipeline.Deploy(ctx, clientset.OpenPipelineClient, properties, renderedConfig, c) } else { deployErr = fmt.Errorf("unknown config-type (ID: %q)", c.Type.ID()) } diff --git a/pkg/deploy/deploy_test.go b/pkg/deploy/deploy_test.go index 2278b3616..d1c2a7a75 100644 --- a/pkg/deploy/deploy_test.go +++ b/pkg/deploy/deploy_test.go @@ -96,7 +96,7 @@ func TestDeployConfigGraph_SingleConfig(t *testing.T) { } dummyClient := dtclient.DummyClient{} - clientSet := &client.ClientSet{DTClient: &dummyClient} + clientSet := &client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} c := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: clientSet, @@ -139,7 +139,7 @@ func TestDeployConfigGraph_SettingShouldFailUpsert(t *testing.T) { }, } - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().CacheSettings(gomock.Any(), gomock.Eq("builtin:test")).Times(1) c.EXPECT().UpsertSettings(gomock.Any(), gomock.Any(), gomock.Any()).Return(dtclient.DynatraceEntity{}, fmt.Errorf("upsert failed")) @@ -163,7 +163,7 @@ func TestDeployConfigGraph_SettingShouldFailUpsert(t *testing.T) { } clients := dynatrace.EnvironmentClients{ - dynatrace.EnvironmentInfo{Name: "env"}: &client.ClientSet{DTClient: c}, + dynatrace.EnvironmentInfo{Name: "env"}: &client.ClientSet{SettingsClient: c}, } errors := deploy.Deploy(context.TODO(), p, clients, deploy.DeployConfigsOptions{}) @@ -184,7 +184,7 @@ func TestDeployConfigGraph_DoesNotFailOnEmptyConfigs(t *testing.T) { } dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} c := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: &clientSet, @@ -199,7 +199,7 @@ func TestDeployConfigGraph_DoesNotFailOnEmptyProject(t *testing.T) { var p []project.Project dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} c := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: &clientSet, @@ -212,7 +212,7 @@ func TestDeployConfigGraph_DoesNotFailOnEmptyProject(t *testing.T) { func TestDeployConfigGraph_DoesNotFailNilProject(t *testing.T) { dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} c := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: &clientSet, } @@ -237,7 +237,7 @@ func TestDeployConfigGraph_DoesNotDeploySkippedConfig(t *testing.T) { } dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} c := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: &clientSet, @@ -251,7 +251,7 @@ func TestDeployConfigGraph_DoesNotDeploySkippedConfig(t *testing.T) { } func TestDeployConfigGraph_DeploysSetting(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) configs := []config.Config{ { @@ -287,7 +287,7 @@ func TestDeployConfigGraph_DeploysSetting(t *testing.T) { }, } - clientSet := client.ClientSet{DTClient: c} + clientSet := client.ClientSet{SettingsClient: c} clients := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: &clientSet, @@ -301,7 +301,7 @@ func TestDeployConfigsTargetingClassicConfigUnique(t *testing.T) { theConfigName := "theConfigName" theApi := api.NewAPIs()["management-zone"] - cl := client.NewMockDynatraceClient(gomock.NewController(t)) + cl := client.NewMockConfigClient(gomock.NewController(t)) cl.EXPECT().CacheConfigs(gomock.Any(), gomock.Eq(theApi)).Times(1) cl.EXPECT().UpsertConfigByName(gomock.Any(), gomock.Any(), theConfigName, gomock.Any()).Times(1) @@ -335,7 +335,7 @@ func TestDeployConfigsTargetingClassicConfigUnique(t *testing.T) { }, } - clientSet := client.ClientSet{DTClient: cl} + clientSet := client.ClientSet{ClassicClient: cl} clients := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: &clientSet, } @@ -348,7 +348,7 @@ func TestDeployConfigsTargetingClassicConfigNonUniqueWithExistingCfgsOfSameName( theConfigName := "theConfigName" theApiName := "alerting-profile" - cl := client.NewMockDynatraceClient(gomock.NewController(t)) + cl := client.NewMockConfigClient(gomock.NewController(t)) cl.EXPECT().CacheConfigs(gomock.Any(), gomock.Eq(api.NewAPIs()[theApiName])).Times(1) cl.EXPECT().UpsertConfigByNonUniqueNameAndId(gomock.Any(), gomock.Any(), gomock.Any(), theConfigName, gomock.Any(), false) @@ -382,7 +382,7 @@ func TestDeployConfigsTargetingClassicConfigNonUniqueWithExistingCfgsOfSameName( }, } - clientSet := client.ClientSet{DTClient: cl} + clientSet := client.ClientSet{ClassicClient: cl} clients := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: &clientSet, } @@ -434,7 +434,7 @@ func TestDeployConfigsWithDeploymentErrors(t *testing.T) { } dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} c := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: env}: &clientSet, @@ -561,7 +561,7 @@ func TestDeployConfigGraph_DoesNotDeployConfigsDependingOnSkippedConfigs(t *test dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} clients := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: environmentName}: &clientSet, @@ -676,7 +676,7 @@ func TestDeployConfigGraph_DeploysIndependentConfigurations(t *testing.T) { assert.Len(t, components, 2) dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} clients := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: environmentName}: &clientSet, } @@ -794,7 +794,7 @@ func TestDeployConfigGraph_DeploysIndependentConfigurations_IfContinuingAfterFai assert.Len(t, components, 2) dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} clients := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: environmentName}: &clientSet, @@ -1179,7 +1179,7 @@ func TestDeployConfigsValidatesClassicAPINames(t *testing.T) { t.Run(tc.name, func(t *testing.T) { dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} c := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env1"}: &clientSet, @@ -1270,7 +1270,7 @@ func TestDeployConfigGraph_CollectsAllErrors(t *testing.T) { } dummyClient := dtclient.DummyClient{} - clientSet := client.ClientSet{DTClient: &dummyClient} + clientSet := client.ClientSet{ClassicClient: &dummyClient, SettingsClient: &dummyClient} c := dynatrace.EnvironmentClients{ dynatrace.EnvironmentInfo{Name: "env"}: &clientSet, diff --git a/pkg/deploy/internal/automation/automation.go b/pkg/deploy/internal/automation/automation.go index a80b0ad24..931954b76 100644 --- a/pkg/deploy/internal/automation/automation.go +++ b/pkg/deploy/internal/automation/automation.go @@ -19,6 +19,8 @@ package automation import ( "context" "fmt" + "time" + automationAPI "github.com/dynatrace/dynatrace-configuration-as-code-core/api/clients/automation" "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/automation" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/automationutils" @@ -29,7 +31,6 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/errors" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/internal/extract" - "time" ) //go:generate mockgen -source=automation.go -destination=automation_mock.go -package=automation automationClient @@ -37,18 +38,6 @@ type Client interface { Upsert(ctx context.Context, resourceType automationAPI.ResourceType, id string, data []byte) (result automation.Response, err error) } -var _ Client = (*DummyClient)(nil) - -type DummyClient struct { -} - -func (c *DummyClient) Upsert(_ context.Context, _ automationAPI.ResourceType, id string, _ []byte) (automation.Response, error) { - return automation.Response{ - StatusCode: 200, - Data: []byte(fmt.Sprintf(`{"id" : "%s"}`, id)), - }, nil -} - func Deploy(ctx context.Context, client Client, properties parameter.Properties, renderedConfig string, c *config.Config) (entities.ResolvedEntity, error) { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() diff --git a/pkg/deploy/internal/automation/automation_test.go b/pkg/deploy/internal/automation/automation_test.go index 621148d2c..cd64fbb8a 100644 --- a/pkg/deploy/internal/automation/automation_test.go +++ b/pkg/deploy/internal/automation/automation_test.go @@ -21,19 +21,22 @@ package automation import ( "context" "errors" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "github.com/dynatrace/dynatrace-configuration-as-code-core/api" "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/automation" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/internal/testutils" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - "testing" ) func TestDeployAutomation_WrongType(t *testing.T) { - client := &DummyClient{} + client := &client.DummyAutomationClient{} conf := &config.Config{ Type: config.ClassicApiType{}, @@ -45,7 +48,7 @@ func TestDeployAutomation_WrongType(t *testing.T) { } func TestDeployAutomation_UnknownResourceType(t *testing.T) { - client := &DummyClient{} + client := &client.DummyAutomationClient{} conf := &config.Config{ Type: config.AutomationType{ Resource: config.AutomationResource("unkown"), diff --git a/pkg/deploy/internal/bucket/bucket.go b/pkg/deploy/internal/bucket/bucket.go index c9a196880..53d128ed3 100644 --- a/pkg/deploy/internal/bucket/bucket.go +++ b/pkg/deploy/internal/bucket/bucket.go @@ -20,6 +20,9 @@ import ( "context" "errors" "fmt" + + "github.com/go-logr/logr" + "github.com/dynatrace/dynatrace-configuration-as-code-core/api" "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/buckets" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/idutils" @@ -28,25 +31,12 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/entities" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" deployErrors "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/errors" - "github.com/go-logr/logr" - "net/http" ) type Client interface { Upsert(ctx context.Context, bucketName string, data []byte) (buckets.Response, error) } -var _ Client = (*DummyClient)(nil) - -type DummyClient struct{} - -func (c DummyClient) Upsert(_ context.Context, id string, data []byte) (response buckets.Response, err error) { - return buckets.Response{ - StatusCode: http.StatusOK, - Data: data, - }, nil -} - func Deploy(ctx context.Context, client Client, properties parameter.Properties, renderedConfig string, c *config.Config) (entities.ResolvedEntity, error) { var bucketName string diff --git a/pkg/deploy/internal/document/document.go b/pkg/deploy/internal/document/document.go index 096ea1f34..bcddb1ad3 100644 --- a/pkg/deploy/internal/document/document.go +++ b/pkg/deploy/internal/document/document.go @@ -22,6 +22,8 @@ import ( "fmt" "net/http" + "github.com/go-logr/logr" + "github.com/dynatrace/dynatrace-configuration-as-code-core/api" libAPI "github.com/dynatrace/dynatrace-configuration-as-code-core/api" "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/documents" @@ -32,7 +34,6 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/entities" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" deployErrors "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/errors" - "github.com/go-logr/logr" ) //go:generate mockgen -source=document.go -destination=document_mock.go -package=document documentClient @@ -43,30 +44,6 @@ type Client interface { Update(ctx context.Context, id string, name string, isPrivate bool, data []byte, documentType documents.DocumentType) (libAPI.Response, error) } -var _ Client = (*DummyClient)(nil) - -type DummyClient struct{} - -// Create implements Client. -func (c *DummyClient) Create(ctx context.Context, name string, isPrivate bool, externalId string, data []byte, documentType documents.DocumentType) (libAPI.Response, error) { - return libAPI.Response{Data: []byte(`{}`)}, nil -} - -// Get implements Client. -func (c *DummyClient) Get(ctx context.Context, id string) (documents.Response, error) { - return documents.Response{}, nil -} - -// List implements Client. -func (c *DummyClient) List(ctx context.Context, filter string) (documents.ListResponse, error) { - return documents.ListResponse{}, nil -} - -// Update implements Client. -func (c *DummyClient) Update(ctx context.Context, id string, name string, isPrivate bool, data []byte, documentType documents.DocumentType) (libAPI.Response, error) { - return libAPI.Response{Data: []byte(`{}`)}, nil -} - func Deploy(ctx context.Context, client Client, properties parameter.Properties, renderedConfig string, c *config.Config) (entities.ResolvedEntity, error) { // create new context to carry logger ctx = logr.NewContext(ctx, log.WithCtxFields(ctx).GetLogr()) diff --git a/pkg/deploy/internal/document/document_test.go b/pkg/deploy/internal/document/document_test.go index 04d5b6b07..150398dde 100644 --- a/pkg/deploy/internal/document/document_test.go +++ b/pkg/deploy/internal/document/document_test.go @@ -25,18 +25,20 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" + "github.com/dynatrace/dynatrace-configuration-as-code-core/api" libAPI "github.com/dynatrace/dynatrace-configuration-as-code-core/api" "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/documents" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/idutils" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/coordinate" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/entities" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/internal/testutils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - gomock "go.uber.org/mock/gomock" ) const documentName = "my dashboard" @@ -50,7 +52,7 @@ var documentConfigCoordinate = coordinate.Coordinate{ } func TestDeployDocumentWrongType(t *testing.T) { - client := &DummyClient{} + client := &client.DummyDocumentClient{} conf := &config.Config{ Type: config.ClassicApiType{}, diff --git a/pkg/deploy/internal/openpipeline/openpipeline.go b/pkg/deploy/internal/openpipeline/openpipeline.go index 043be2911..f96bbe85e 100644 --- a/pkg/deploy/internal/openpipeline/openpipeline.go +++ b/pkg/deploy/internal/openpipeline/openpipeline.go @@ -19,6 +19,10 @@ package openpipeline import ( "context" "fmt" + "time" + + "github.com/go-logr/logr" + "github.com/dynatrace/dynatrace-configuration-as-code-core/clients/openpipeline" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" @@ -26,19 +30,9 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/entities" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" deployErrors "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/errors" - "github.com/go-logr/logr" - "time" ) -var _ Client = (*DummyClient)(nil) - -type DummyClient struct{} - //go:generate mockgen -source=openpipeline.go -destination=openpipeline_mock.go -package=openpipeline openpipelineClient -func (c *DummyClient) Update(_ context.Context, _ string, _ []byte) (openpipeline.Response, error) { - return openpipeline.Response{}, nil -} - type Client interface { Update(ctx context.Context, id string, data []byte) (openpipeline.Response, error) } diff --git a/pkg/deploy/internal/setting/setting_test.go b/pkg/deploy/internal/setting/setting_test.go index a65ed5a18..183e5e373 100644 --- a/pkg/deploy/internal/setting/setting_test.go +++ b/pkg/deploy/internal/setting/setting_test.go @@ -20,6 +20,11 @@ package setting import ( "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client/dtclient" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config" @@ -27,9 +32,6 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/entities" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/deploy/internal/testutils" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" - "testing" ) func TestDeploySettingShouldFailCyclicParameterDependencies(t *testing.T) { @@ -85,7 +87,7 @@ func TestDeploySettingShouldFailRenderTemplate(t *testing.T) { } func TestDeploySetting_ManagementZone_MZoneIDGetsEncoded(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().UpsertSettings(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(dtclient.DynatraceEntity{ Id: "vu9U3hXa3q0AAAABABhidWlsdGluOm1hbmFnZW1lbnQtem9uZXMABnRlbmFudAAGdGVuYW50ACRjNDZlNDZiMy02ZDk2LTMyYTctOGI1Yi1mNjExNzcyZDAxNjW-71TeFdrerQ", Name: "mzname"}, nil) @@ -110,7 +112,7 @@ func TestDeploySetting_ManagementZone_MZoneIDGetsEncoded(t *testing.T) { } func TestDeploySetting_ManagementZone_NameGetsExtracted_ifPresent(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().UpsertSettings(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(dtclient.DynatraceEntity{ Id: "abcdefghijk", Name: "mzname"}, nil) @@ -135,7 +137,7 @@ func TestDeploySetting_ManagementZone_NameGetsExtracted_ifPresent(t *testing.T) } func TestDeploySetting_ManagementZone_FailToDecodeMZoneID(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) c.EXPECT().UpsertSettings(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(dtclient.DynatraceEntity{ Id: "INVALID MANAGEMENT ZONE ID", Name: "mzanme"}, nil) diff --git a/pkg/deploy/preload.go b/pkg/deploy/preload.go index ae939b392..3cb4d2180 100644 --- a/pkg/deploy/preload.go +++ b/pkg/deploy/preload.go @@ -30,7 +30,7 @@ import ( type preloadConfigTypeEntry struct { configType config.Type - client client.DynatraceClient + clientset *client.ClientSet } // preloadCaches fills the caches of the specified clients for the config types used in the given projects. @@ -43,10 +43,10 @@ func preloadCaches(ctx context.Context, projects []project.Project, environmentC switch t := p.configType.(type) { case config.SettingsType: - preloadSettingsValuesForSchemaId(ctx, p.client, t.SchemaId) + preloadSettingsValuesForSchemaId(ctx, p.clientset.SettingsClient, t.SchemaId) case config.ClassicApiType: - preloadValuesForApi(ctx, p.client, t.Api) + preloadValuesForApi(ctx, p.clientset.ClassicClient, t.Api) } }(p) @@ -54,7 +54,7 @@ func preloadCaches(ctx context.Context, projects []project.Project, environmentC wg.Wait() } -func preloadSettingsValuesForSchemaId(ctx context.Context, client client.DynatraceClient, schemaId string) { +func preloadSettingsValuesForSchemaId(ctx context.Context, client client.SettingsClient, schemaId string) { if err := client.CacheSettings(ctx, schemaId); err != nil { log.Warn("Could not cache settings values for schema %s: %s", schemaId, err) return @@ -62,7 +62,7 @@ func preloadSettingsValuesForSchemaId(ctx context.Context, client client.Dynatra log.Debug("Cached settings values for schema %s", schemaId) } -func preloadValuesForApi(ctx context.Context, client client.DynatraceClient, theApi string) { +func preloadValuesForApi(ctx context.Context, client client.ConfigClient, theApi string) { a, ok := api.NewAPIs()[theApi] if !ok { return @@ -82,11 +82,6 @@ func preloadValuesForApi(ctx context.Context, client client.DynatraceClient, the func gatherPreloadConfigTypeEntries(projects []project.Project, environmentClients dynatrace.EnvironmentClients) []preloadConfigTypeEntry { preloads := []preloadConfigTypeEntry{} for environmentInfo, environmentClientSet := range environmentClients { - client := environmentClientSet.DTClient - if client == nil { - continue - } - seenConfigTypes := map[string]struct{}{} for _, project := range projects { @@ -101,8 +96,15 @@ func gatherPreloadConfigTypeEntries(projects []project.Project, environmentClien seenConfigTypes[c.Coordinate.Type] = struct{}{} switch t := c.Type.(type) { - case config.SettingsType, config.ClassicApiType: - preloads = append(preloads, preloadConfigTypeEntry{configType: t, client: client}) + case config.ClassicApiType: + if environmentClientSet.ClassicClient != nil { + preloads = append(preloads, preloadConfigTypeEntry{configType: t, clientset: environmentClientSet}) + } + + case config.SettingsType: + if environmentClientSet.SettingsClient != nil { + preloads = append(preloads, preloadConfigTypeEntry{configType: t, clientset: environmentClientSet}) + } } }) } diff --git a/pkg/deploy/preload_test.go b/pkg/deploy/preload_test.go index 64bcd593f..6d31c4d1d 100644 --- a/pkg/deploy/preload_test.go +++ b/pkg/deploy/preload_test.go @@ -33,8 +33,9 @@ import ( project "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/project/v2" ) -var dtClientEnv1 = &dtclient.DummyClient{} -var dtClientEnv2 = &dtclient.DummyClient{} +var dtDummyClient = &dtclient.DummyClient{} +var clientsetEnv1 = &client.ClientSet{ClassicClient: dtDummyClient, SettingsClient: dtDummyClient} +var clientsetEnv2 = &client.ClientSet{ClassicClient: dtDummyClient, SettingsClient: dtDummyClient} func Test_gatherPreloadConfigTypeEntries_OneEntryPerConfigType(t *testing.T) { entries := gatherPreloadConfigTypeEntries( @@ -62,11 +63,11 @@ func Test_gatherPreloadConfigTypeEntries_OneEntryPerConfigType(t *testing.T) { }, }, }, - dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: dtClientEnv1}}, + dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: clientsetEnv1}, ) require.Len(t, entries, 1) - assert.Equal(t, entries[0].client, dtClientEnv1) + assert.Equal(t, entries[0].clientset, clientsetEnv1) assert.Equal(t, entries[0].configType, config.SettingsType{ SchemaId: "builtin:alerting.profile", }) @@ -99,14 +100,14 @@ func Test_gatherPreloadConfigTypeEntries_OneEntryForEachConfigType(t *testing.T) }, }, }, - dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: dtClientEnv1}}, + dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: clientsetEnv1}, ) require.Len(t, entries, 2) - assert.Contains(t, entries, preloadConfigTypeEntry{client: dtClientEnv1, configType: config.SettingsType{ + assert.Contains(t, entries, preloadConfigTypeEntry{clientset: clientsetEnv1, configType: config.SettingsType{ SchemaId: "builtin:alerting.profile", }}) - assert.Contains(t, entries, preloadConfigTypeEntry{client: dtClientEnv1, configType: config.ClassicApiType{ + assert.Contains(t, entries, preloadConfigTypeEntry{clientset: clientsetEnv1, configType: config.ClassicApiType{ Api: "management-zone", }}) } @@ -138,11 +139,11 @@ func Test_gatherPreloadConfigTypeEntries_EntriesOnlyForSupportedConfigTypes(t *t }, }, }, - dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: dtClientEnv1}}, + dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: clientsetEnv1}, ) require.Len(t, entries, 1) - assert.Contains(t, entries, preloadConfigTypeEntry{client: dtClientEnv1, configType: config.SettingsType{ + assert.Contains(t, entries, preloadConfigTypeEntry{clientset: clientsetEnv1, configType: config.SettingsType{ SchemaId: "builtin:alerting.profile", }}) } @@ -178,16 +179,16 @@ func Test_gatherPreloadConfigTypeEntries_OneEntryForEachEnvironmentInSameProject }, }, dynatrace.EnvironmentClients{ - dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: dtClientEnv1}, - dynatrace.EnvironmentInfo{Name: "env2"}: &client.ClientSet{DTClient: dtClientEnv2}, + dynatrace.EnvironmentInfo{Name: "env1"}: clientsetEnv1, + dynatrace.EnvironmentInfo{Name: "env2"}: clientsetEnv2, }, ) require.Len(t, entries, 2) - assert.Contains(t, entries, preloadConfigTypeEntry{client: dtClientEnv1, configType: config.SettingsType{ + assert.Contains(t, entries, preloadConfigTypeEntry{clientset: clientsetEnv1, configType: config.SettingsType{ SchemaId: "builtin:alerting.profile", }}) - assert.Contains(t, entries, preloadConfigTypeEntry{client: dtClientEnv2, configType: config.SettingsType{ + assert.Contains(t, entries, preloadConfigTypeEntry{clientset: clientsetEnv2, configType: config.SettingsType{ SchemaId: "builtin:alerting.profile", }}) } @@ -229,16 +230,16 @@ func Test_gatherPreloadConfigTypeEntries_OneEntryForEachEnvironmentInDifferentPr }, }, dynatrace.EnvironmentClients{ - dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: dtClientEnv1}, - dynatrace.EnvironmentInfo{Name: "env2"}: &client.ClientSet{DTClient: dtClientEnv2}, + dynatrace.EnvironmentInfo{Name: "env1"}: clientsetEnv1, + dynatrace.EnvironmentInfo{Name: "env2"}: clientsetEnv2, }, ) require.Len(t, entries, 2) - assert.Contains(t, entries, preloadConfigTypeEntry{client: dtClientEnv1, configType: config.SettingsType{ + assert.Contains(t, entries, preloadConfigTypeEntry{clientset: clientsetEnv1, configType: config.SettingsType{ SchemaId: "builtin:alerting.profile", }}) - assert.Contains(t, entries, preloadConfigTypeEntry{client: dtClientEnv2, configType: config.SettingsType{ + assert.Contains(t, entries, preloadConfigTypeEntry{clientset: clientsetEnv2, configType: config.SettingsType{ SchemaId: "builtin:alerting.profile", }}) } @@ -280,12 +281,12 @@ func Test_gatherPreloadConfigTypeEntries_OneEntryForSameEnvironmentInDifferentPr }, }, dynatrace.EnvironmentClients{ - dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: dtClientEnv1}, + dynatrace.EnvironmentInfo{Name: "env1"}: clientsetEnv1, }, ) require.Len(t, entries, 1) - assert.Contains(t, entries, preloadConfigTypeEntry{client: dtClientEnv1, configType: config.SettingsType{ + assert.Contains(t, entries, preloadConfigTypeEntry{clientset: clientsetEnv1, configType: config.SettingsType{ SchemaId: "builtin:alerting.profile", }}) } @@ -310,14 +311,14 @@ func Test_gatherPreloadConfigTypeEntries_NoEntryIfEnvironmentMissingClient(t *te }, }, }, - dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: nil}}, + dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{}}, ) assert.Len(t, entries, 0) } func Test_ScopedConfigsAreNotCached(t *testing.T) { - dtClient := client.NewMockDynatraceClient(gomock.NewController(t)) //<- dont expect any call(s) on the mocked client + dtClient := client.NewMockConfigClient(gomock.NewController(t)) //<- dont expect any call(s) on the mocked client type args struct { projects []project.Project environmentClients dynatrace.EnvironmentClients @@ -349,7 +350,7 @@ func Test_ScopedConfigsAreNotCached(t *testing.T) { }, }, }, - environmentClients: dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: dtClient}}, + environmentClients: dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{ClassicClient: dtClient}}, }, }, } @@ -401,17 +402,17 @@ func Test_gatherPreloadConfigTypeEntries_WithSkipParam(t *testing.T) { }, }, }, - dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: &client.ClientSet{DTClient: dtClientEnv1}}, + dynatrace.EnvironmentClients{dynatrace.EnvironmentInfo{Name: "env1"}: clientsetEnv1}, ) expectedEntries := []preloadConfigTypeEntry{ { configType: config.ClassicApiType{Api: "dashboard-share-settings"}, - client: dtClientEnv1, + clientset: clientsetEnv1, }, { configType: config.ClassicApiType{Api: "management-zone"}, - client: dtClientEnv1, + clientset: clientsetEnv1, }, } diff --git a/pkg/download/classic/download_test.go b/pkg/download/classic/download_test.go index a6aa276d5..083cebda0 100644 --- a/pkg/download/classic/download_test.go +++ b/pkg/download/classic/download_test.go @@ -19,10 +19,15 @@ package classic_test import ( "context" "fmt" - "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" "strconv" "testing" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/testutils/matcher" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/api" @@ -32,9 +37,6 @@ import ( "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/reference" valueParam "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/config/parameter/value" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/download/classic" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestDownload_KeyUserActionMobile(t *testing.T) { @@ -45,7 +47,7 @@ func TestDownload_KeyUserActionMobile(t *testing.T) { applicationId := "some-application-id" - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) c.EXPECT().ListConfigs(context.TODO(), apiMap[api.ApplicationMobile]).Return([]dtclient.Value{{Id: applicationId, Name: "some-application-name"}}, nil).Times(2) c.EXPECT().ListConfigs(context.TODO(), apiMap[api.KeyUserActionsMobile].ApplyParentObjectID(applicationId)).Return([]dtclient.Value{{Id: "abc", Name: "abc"}}, nil).Times(1) c.EXPECT().ReadConfigById(context.TODO(), apiMap[api.ApplicationMobile], applicationId).Return([]byte(`{"keyUserActions": [{"name": "abc"}]}`), nil).Times(1) @@ -79,7 +81,7 @@ func toAPIs(apis ...api.API) api.APIs { } func TestDownload_KeyUserActionWeb(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) ctx := context.TODO() c.EXPECT().ListConfigs(ctx, matcher.EqAPI(apiGet(api.ApplicationWeb))).Return([]dtclient.Value{{Id: "applicationID", Name: "web application name"}}, nil) c.EXPECT().ListConfigs(ctx, matcher.EqAPI((apiGet(api.KeyUserActionsWeb).ApplyParentObjectID("applicationID")))).Return([]dtclient.Value{{Id: "APPLICATION_METHOD-ID", Name: "the_name"}}, nil) @@ -101,7 +103,7 @@ func TestDownload_KeyUserActionWeb(t *testing.T) { } func TestDownload_KeyUserActionWeb_Uniqnes(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) ctx := context.TODO() c.EXPECT().ListConfigs(ctx, matcher.EqAPI(apiGet(api.ApplicationWeb))).Return([]dtclient.Value{{Id: "applicationID", Name: "web application name"}}, nil) c.EXPECT().ListConfigs(ctx, matcher.EqAPI((apiGet(api.KeyUserActionsWeb).ApplyParentObjectID("applicationID")))).Return([]dtclient.Value{{Id: "APPLICATION_METHOD-ID", Name: "the_name"}, {Id: "APPLICATION_METHOD-ID2", Name: "the_name"}, {Id: "APPLICATION_METHOD-ID3", Name: "the_name"}}, nil) @@ -124,7 +126,7 @@ func TestDownload_SkipConfigThatShouldNotBePersisted(t *testing.T) { api1 := api.API{ID: "API_ID_1", URLPath: "API_PATH_1", NonUniqueName: true} api2 := api.API{ID: "API_ID_2", URLPath: "API_PATH_2", NonUniqueName: false} - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(api1)).Return([]dtclient.Value{{Id: "API_ID_1", Name: "API_NAME_1"}}, nil) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(api2)).Return([]dtclient.Value{{Id: "API_ID_2", Name: "API_NAME_2"}}, nil) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte("{}"), nil).Times(2) @@ -176,7 +178,7 @@ func TestDownload_SkipConfigBeforeDownload(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(api1)).Return([]dtclient.Value{{Id: "API_ID_1", Name: "API_NAME_1"}}, nil) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(api2)).Return([]dtclient.Value{{Id: "API_ID_2", Name: "API_NAME_2"}}, nil) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte("{}"), nil).AnyTimes() @@ -195,7 +197,7 @@ func TestDownload_FilteringCanBeTurnedOffViaFeatureFlags(t *testing.T) { api1 := api.API{ID: "API_ID_1", URLPath: "API_PATH_1", NonUniqueName: true} api2 := api.API{ID: "API_ID_2", URLPath: "API_PATH_2", NonUniqueName: false} - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(api1)).Return([]dtclient.Value{{Id: "API_ID_1", Name: "API_NAME_1"}}, nil) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(api2)).Return([]dtclient.Value{{Id: "API_ID_2", Name: "API_NAME_2"}}, nil) c.EXPECT().ReadConfigById(gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte("{}"), nil) @@ -292,7 +294,7 @@ func Test_generalCases(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) for _, m := range tc.mockList { c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(m.api)).Return(m.response, m.err) } @@ -342,7 +344,7 @@ func TestDownload_SkippedParentsSkipChildren(t *testing.T) { }, } - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockConfigClient(gomock.NewController(t)) c.EXPECT().ListConfigs(gomock.Any(), matcher.EqAPI(parentAPI)).Return([]dtclient.Value{{Id: "PARENT_ID_1", Name: "PARENT_NAME_1"}}, nil).Times(2) configurations, err := classic.Download(c, "project", apiMap, contentFilters) diff --git a/pkg/download/settings/download_test.go b/pkg/download/settings/download_test.go index 25936cfd6..5216f9997 100644 --- a/pkg/download/settings/download_test.go +++ b/pkg/download/settings/download_test.go @@ -24,10 +24,11 @@ import ( "strconv" "testing" - coreapi "github.com/dynatrace/dynatrace-configuration-as-code-core/api" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" + coreapi "github.com/dynatrace/dynatrace-configuration-as-code-core/api" + "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/featureflags" "github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/idutils" "github.com/dynatrace/dynatrace-configuration-as-code/v2/pkg/client" @@ -517,7 +518,7 @@ func TestDownloadAll(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) schemas, err := tt.mockValues.Schemas() c.EXPECT().ListSchemas(gomock.Any()).Times(tt.mockValues.ListSchemasCalls).Return(schemas, err) //c.EXPECT().GetSchemaById(gomock.Any()).Times(tt.mockValues.GetSchemaCalls).Return(tt.mockValues.GetSchema("")) @@ -692,7 +693,7 @@ func TestDownload(t *testing.T) { t.Setenv(k, v) } - c := client.NewMockDynatraceClient(gomock.NewController(t)) + c := client.NewMockSettingsClient(gomock.NewController(t)) schemas, err1 := tt.mockValues.Schemas() settings, err2 := tt.mockValues.Settings() c.EXPECT().ListSchemas(gomock.Any()).Times(tt.mockValues.ListSchemasCalls).Return(schemas, err1)