From a443ed054bf0eaf3ed8a522453c11e1d3edeee0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Tue, 16 Apr 2024 18:06:18 +0100 Subject: [PATCH 01/10] Initial implementation --- .../generate-payload/generate_payload.go | 140 ++++++++++++++++++ .../argus/scrape-configs/scrape_configs.go | 26 ++++ internal/pkg/services/argus/utils/utils.go | 98 ++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go create mode 100644 internal/cmd/argus/scrape-configs/scrape_configs.go diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go new file mode 100644 index 000000000..7c1ea1f9b --- /dev/null +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go @@ -0,0 +1,140 @@ +package generatepayload + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/client" + argusUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/argus/utils" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/services/argus" +) + +const ( + jobNameFlag = "job-name" + instanceIdFlag = "instance-id" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + JobName *string + InstanceId string +} + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "generate-payload", + Short: "Generates a payload to create/update Scrape Configurations for an Argus instance ", + Long: fmt.Sprintf("%s\n%s\n%s", + "Generates a JSON payload with values to be used as --payload input for Scrape Configurations creation or update.", + "If --job-name is set, an Update payload will be generated with the current state of the given configuration. If unset, a Create payload will be generated with default values.", + "See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure.", + ), + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Generate a payload with default values, and adapt it with custom values for the different configuration options`, + `$ stackit argus scrape-configs generate-payload > ./payload.json`, + ``, + `$ stackit argus scrape-configs create my-config --payload @./payload.json`), + examples.NewExample( + `Generate a payload with values of an existing configuration, and adapt it with custom values for the different configuration options`, + `$ stackit argus scrape-configs generate-payload --job-name my-config > ./payload.json`, + ``, + `$ stackit argus scrape-configs update my-config --payload @./payload.json`), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(cmd) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(p) + if err != nil { + return err + } + + if model.JobName == nil { + createPayload, err := argusUtils.GetDefaultCreateScrapeConfigPayload(ctx, apiClient) + if err != nil { + return err + } + return outputCreateResult(p, createPayload) + } else { + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("read SKE cluster: %w", err) + } + + payload := argusUtils.MapToUpdateScrapeConfigPayload(resp) + + return outputUpdateResult(p, payload) + } + + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") + cmd.Flags().StringP(jobNameFlag, "n", "", "If set, generates the payload with the current state of the given cluster. If unset, generates the payload with default values") +} + +func parseInput(cmd *cobra.Command) (*inputModel, error) { + globalFlags := globalflags.Parse(cmd) + + jobName := flags.FlagToStringPointer(cmd, jobNameFlag) + // If jobName is provided, projectId and instanceId are needed as well + if jobName != nil { + err := flags.MarkFlagsRequired(cmd, instanceIdFlag) + cobra.CheckErr(err) + + if globalFlags.ProjectId == "" { + return nil, &errors.ProjectIdError{} + } + } + + return &inputModel{ + GlobalFlagModel: globalFlags, + JobName: jobName, + InstanceId: flags.FlagToStringValue(cmd, instanceIdFlag), + }, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *argus.APIClient) argus.ApiGetScrapeConfigRequest { + req := apiClient.GetScrapeConfig(ctx, model.InstanceId, *model.JobName, model.ProjectId) + return req +} + +func outputCreateResult(p *print.Printer, payload *argus.CreateScrapeConfigPayload) error { + payloadBytes, err := json.MarshalIndent(*payload, "", " ") + if err != nil { + return fmt.Errorf("marshal payload: %w", err) + } + p.Outputln(string(payloadBytes)) + + return nil +} + +func outputUpdateResult(p *print.Printer, payload *argus.UpdateScrapeConfigPayload) error { + payloadBytes, err := json.MarshalIndent(*payload, "", " ") + if err != nil { + return fmt.Errorf("marshal payload: %w", err) + } + p.Outputln(string(payloadBytes)) + + return nil +} diff --git a/internal/cmd/argus/scrape-configs/scrape_configs.go b/internal/cmd/argus/scrape-configs/scrape_configs.go new file mode 100644 index 000000000..07ad5ed0b --- /dev/null +++ b/internal/cmd/argus/scrape-configs/scrape_configs.go @@ -0,0 +1,26 @@ +package scrapeconfigs + +import ( + generatepayload "github.com/stackitcloud/stackit-cli/internal/cmd/scrape-configs/generate-payload" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/spf13/cobra" +) + +func NewCmd(p *print.Printer) *cobra.Command { + cmd := &cobra.Command{ + Use: "scrape-configs", + Short: "Provides functionality for scraping configs in Argus.", + Long: "Provides functionality for scraping configurations in Argus.", + Args: args.NoArgs, + Run: utils.CmdHelp, + } + addSubcommands(cmd, p) + return cmd +} + +func addSubcommands(cmd *cobra.Command, p *print.Printer) { + cmd.AddCommand(generatepayload.NewCmd(p)) +} diff --git a/internal/pkg/services/argus/utils/utils.go b/internal/pkg/services/argus/utils/utils.go index e530c187b..57e15c39e 100644 --- a/internal/pkg/services/argus/utils/utils.go +++ b/internal/pkg/services/argus/utils/utils.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/argus" ) @@ -56,6 +57,99 @@ func LoadPlanId(planName string, resp *argus.PlansResponse) (*string, error) { } } +func MapToUpdateScrapeConfigPayload(resp *argus.GetScrapeConfigResponse) *argus.UpdateScrapeConfigPayload { + data := resp.Data + + basicAuth := mapBasicAuth(data.BasicAuth) + + httpSdConfigs := make([]argus.CreateScrapeConfigPayloadHttpSdConfigsInner, 0) + if data.HttpSdConfigs != nil { + for _, config := range *data.HttpSdConfigs { + httpSdConfigs = append(httpSdConfigs, mapHttpSdConfig(config)) + } + } + + staticConfigs := make([]argus.UpdateScrapeConfigPayloadStaticConfigsInner, 0) + if data.StaticConfigs != nil { + for _, config := range *data.StaticConfigs { + staticConfigs = append(staticConfigs, mapStaticConfig(config)) + } + } + + tlsConfig := mapTlsConfig(data.TlsConfig) + + return &argus.UpdateScrapeConfigPayload{ + BasicAuth: basicAuth, + BearerToken: data.BearerToken, + HonorLabels: data.HonorLabels, + HonorTimeStamps: data.HonorTimeStamps, + MetricsPath: data.MetricsPath, + // MetricsRelabelConfigs: metricsRelabelConfigs, + // Params: convertMapStringToInterface(data.Params), + SampleLimit: utils.Ptr(float64(*data.SampleLimit)), + Scheme: data.Scheme, + ScrapeInterval: data.ScrapeInterval, + ScrapeTimeout: data.ScrapeTimeout, + StaticConfigs: &staticConfigs, + TlsConfig: tlsConfig, + } +} + +func mapOAuth2(oauth2 *argus.OAuth2) *argus.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2 { + return nil +} + +func mapHttpSdConfig(httpSdConfig argus.HTTPServiceSD) argus.CreateScrapeConfigPayloadHttpSdConfigsInner { + oauth2 := mapOAuth2(httpSdConfig.Oauth2) + tlsConfig := mapTlsConfig(httpSdConfig.TlsConfig) + return argus.CreateScrapeConfigPayloadHttpSdConfigsInner{ + Oauth2: oauth2, + TlsConfig: tlsConfig, + } + +} + +func mapStaticConfig(staticConfig argus.StaticConfigs) argus.UpdateScrapeConfigPayloadStaticConfigsInner { + labels := convertMapStringToInterface(staticConfig.Labels) + return argus.UpdateScrapeConfigPayloadStaticConfigsInner{ + Labels: labels, + Targets: staticConfig.Targets, + } +} + +func mapBasicAuth(basicAuth *argus.BasicAuth) *argus.CreateScrapeConfigPayloadBasicAuth { + if basicAuth == nil { + return nil + } + + return &argus.CreateScrapeConfigPayloadBasicAuth{ + Password: basicAuth.Password, + Username: basicAuth.Username, + } +} + +func mapTlsConfig(tlsConfig *argus.TLSConfig) *argus.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig { + if tlsConfig == nil { + return nil + } + + return &argus.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{ + InsecureSkipVerify: tlsConfig.InsecureSkipVerify, + } +} + +func convertMapStringToInterface(m *map[string]string) *map[string]interface{} { + if m == nil { + return nil + } + + newMap := make(map[string]interface{}) + for k, v := range *m { + newMap[k] = v + } + return &newMap +} + type ArgusClient interface { GetInstanceExecute(ctx context.Context, instanceId, projectId string) (*argus.GetInstanceResponse, error) } @@ -67,3 +161,7 @@ func GetInstanceName(ctx context.Context, apiClient ArgusClient, instanceId, pro } return *resp.Name, nil } + +func GetDefaultCreateScrapeConfigPayload(ctx context.Context, apiClient ArgusClient) (*argus.CreateScrapeConfigPayload, error) { + return nil, nil +} From 63231b71a117663f85279a5184aca204a8085845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Wed, 17 Apr 2024 20:27:33 +0100 Subject: [PATCH 02/10] add utils, testing and make command work --- internal/cmd/argus/argus.go | 2 + .../generate-payload/generate_payload.go | 38 +-- .../generate-payload/generate_payload_test.go | 217 ++++++++++++++++++ .../argus/scrape-configs/scrape_configs.go | 2 +- internal/pkg/services/argus/utils/utils.go | 135 +++++++---- .../pkg/services/argus/utils/utils_test.go | 165 +++++++++++++ 6 files changed, 496 insertions(+), 63 deletions(-) create mode 100644 internal/cmd/argus/scrape-configs/generate-payload/generate_payload_test.go diff --git a/internal/cmd/argus/argus.go b/internal/cmd/argus/argus.go index 38dc77321..8e93e6c6f 100644 --- a/internal/cmd/argus/argus.go +++ b/internal/cmd/argus/argus.go @@ -3,6 +3,7 @@ package argus import ( "github.com/stackitcloud/stackit-cli/internal/cmd/argus/instance" "github.com/stackitcloud/stackit-cli/internal/cmd/argus/plans" + scrapeconfigs "github.com/stackitcloud/stackit-cli/internal/cmd/argus/scrape-configs" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -25,4 +26,5 @@ func NewCmd(p *print.Printer) *cobra.Command { func addSubcommands(cmd *cobra.Command, p *print.Printer) { cmd.AddCommand(plans.NewCmd(p)) cmd.AddCommand(instance.NewCmd(p)) + cmd.AddCommand(scrapeconfigs.NewCmd(p)) } diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go index 7c1ea1f9b..859448b9c 100644 --- a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" @@ -33,21 +32,23 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "generate-payload", Short: "Generates a payload to create/update Scrape Configurations for an Argus instance ", - Long: fmt.Sprintf("%s\n%s\n%s", + Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n", "Generates a JSON payload with values to be used as --payload input for Scrape Configurations creation or update.", - "If --job-name is set, an Update payload will be generated with the current state of the given configuration. If unset, a Create payload will be generated with default values.", + "This command can be used to generate a payload to update an existing Scrape Config job or to create a new Scrape Config job.", + "To obtain an Update payload, provide the job name and instance ID of the desired Scrape Config Job and respective Argus instance.", + "To obtain a Create payload, run the command with no flags", "See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure.", ), Args: args.NoArgs, Example: examples.Build( examples.NewExample( - `Generate a payload with default values, and adapt it with custom values for the different configuration options`, + `Generate a Create payload with default values, and adapt it with custom values for the different configuration options`, `$ stackit argus scrape-configs generate-payload > ./payload.json`, ``, `$ stackit argus scrape-configs create my-config --payload @./payload.json`), examples.NewExample( - `Generate a payload with values of an existing configuration, and adapt it with custom values for the different configuration options`, - `$ stackit argus scrape-configs generate-payload --job-name my-config > ./payload.json`, + `Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and adapt it with custom values for the different configuration options`, + `$ stackit argus scrape-configs generate-payload --job-name my-config --instance-id xxx > ./payload.json`, ``, `$ stackit argus scrape-configs update my-config --payload @./payload.json`), ), @@ -74,10 +75,13 @@ func NewCmd(p *print.Printer) *cobra.Command { req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() if err != nil { - return fmt.Errorf("read SKE cluster: %w", err) + return fmt.Errorf("read Argus Scrape Config: %w", err) } - payload := argusUtils.MapToUpdateScrapeConfigPayload(resp) + payload, err := argusUtils.MapToUpdateScrapeConfigPayload(resp) + if err != nil { + return fmt.Errorf("map update scrape config payloads: %w", err) + } return outputUpdateResult(p, payload) } @@ -90,21 +94,21 @@ func NewCmd(p *print.Printer) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.UUIDFlag(), instanceIdFlag, "Instance ID") - cmd.Flags().StringP(jobNameFlag, "n", "", "If set, generates the payload with the current state of the given cluster. If unset, generates the payload with default values") + cmd.Flags().StringP(jobNameFlag, "n", "", "If set, generates an update payload with the current state of the given scrape config. If unset, generates a create payload with default values") } func parseInput(cmd *cobra.Command) (*inputModel, error) { globalFlags := globalflags.Parse(cmd) jobName := flags.FlagToStringPointer(cmd, jobNameFlag) - // If jobName is provided, projectId and instanceId are needed as well - if jobName != nil { - err := flags.MarkFlagsRequired(cmd, instanceIdFlag) - cobra.CheckErr(err) - - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } + instanceId := flags.FlagToStringValue(cmd, instanceIdFlag) + + if jobName != nil && (globalFlags.ProjectId == "" || instanceId == "") { + return nil, fmt.Errorf("if a job-name is provided then instance-id and project-id must to be provided") + } + + if jobName == nil && (globalFlags.ProjectId != "" || instanceId != "") { + return nil, fmt.Errorf("if a job-name is not provided then instance-id and project-id can't be provided") } return &inputModel{ diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload_test.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload_test.go new file mode 100644 index 000000000..2e7ccdc6b --- /dev/null +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload_test.go @@ -0,0 +1,217 @@ +package generatepayload + +import ( + "context" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/argus" +) + +var projectIdFlag = globalflags.ProjectIdFlag + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &argus.APIClient{} +var testProjectId = uuid.NewString() +var testInstanceId = uuid.NewString() + +const testJobName = "test-job-name" + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + projectIdFlag: testProjectId, + instanceIdFlag: testInstanceId, + jobNameFlag: testJobName, + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + ProjectId: testProjectId, + Verbosity: globalflags.VerbosityDefault, + }, + InstanceId: testInstanceId, + JobName: utils.Ptr(testJobName), + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *argus.ApiGetScrapeConfigRequest)) argus.ApiGetScrapeConfigRequest { + request := testClient.GetScrapeConfig(testCtx, testInstanceId, testJobName, testProjectId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "no values", + flagValues: map[string]string{}, + isValid: true, + expectedModel: &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{Verbosity: globalflags.VerbosityDefault}, + }, + }, + { + description: "job name missing", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, jobNameFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.JobName = nil + }), + }, + { + description: "instance id missing, job name provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, instanceIdFlag) + }), + isValid: false, + }, + { + description: "instance id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "" + }), + isValid: false, + }, + { + description: "instance id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + { + description: "project id missing, job name provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, projectIdFlag) + }), + isValid: false, + }, + { + description: "project id invalid 1", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "" + }), + isValid: false, + }, + { + description: "project id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[projectIdFlag] = "invalid-uuid" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + cmd := NewCmd(nil) + err := globalflags.Configure(cmd.Flags()) + if err != nil { + t.Fatalf("configure global flags: %v", err) + } + + for flag, value := range tt.flagValues { + err := cmd.Flags().Set(flag, value) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("setting flag --%s=%s: %v", flag, value, err) + } + } + + err = cmd.ValidateRequiredFlags() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + err = cmd.ValidateFlagGroups() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error validating flags: %v", err) + } + + model, err := parseInput(cmd) + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("error parsing flags: %v", err) + } + + if !tt.isValid { + t.Fatalf("did not fail on invalid input") + } + diff := cmp.Diff(model, tt.expectedModel) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest argus.ApiGetScrapeConfigRequest + isValid bool + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + request := buildRequest(testCtx, tt.model, testClient) + + diff := cmp.Diff(request, tt.expectedRequest, + cmp.AllowUnexported(tt.expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} diff --git a/internal/cmd/argus/scrape-configs/scrape_configs.go b/internal/cmd/argus/scrape-configs/scrape_configs.go index 07ad5ed0b..b58b1e6e1 100644 --- a/internal/cmd/argus/scrape-configs/scrape_configs.go +++ b/internal/cmd/argus/scrape-configs/scrape_configs.go @@ -1,7 +1,7 @@ package scrapeconfigs import ( - generatepayload "github.com/stackitcloud/stackit-cli/internal/cmd/scrape-configs/generate-payload" + generatepayload "github.com/stackitcloud/stackit-cli/internal/cmd/argus/scrape-configs/generate-payload" "github.com/stackitcloud/stackit-cli/internal/pkg/args" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" diff --git a/internal/pkg/services/argus/utils/utils.go b/internal/pkg/services/argus/utils/utils.go index 57e15c39e..450f03b63 100644 --- a/internal/pkg/services/argus/utils/utils.go +++ b/internal/pkg/services/argus/utils/utils.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" - "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/argus" ) @@ -57,64 +56,101 @@ func LoadPlanId(planName string, resp *argus.PlansResponse) (*string, error) { } } -func MapToUpdateScrapeConfigPayload(resp *argus.GetScrapeConfigResponse) *argus.UpdateScrapeConfigPayload { +func MapToUpdateScrapeConfigPayload(resp *argus.GetScrapeConfigResponse) (*argus.UpdateScrapeConfigPayload, error) { + if resp == nil || resp.Data == nil { + return nil, fmt.Errorf("no Argus scrape config provided") + } + data := resp.Data basicAuth := mapBasicAuth(data.BasicAuth) - httpSdConfigs := make([]argus.CreateScrapeConfigPayloadHttpSdConfigsInner, 0) - if data.HttpSdConfigs != nil { - for _, config := range *data.HttpSdConfigs { - httpSdConfigs = append(httpSdConfigs, mapHttpSdConfig(config)) - } - } - - staticConfigs := make([]argus.UpdateScrapeConfigPayloadStaticConfigsInner, 0) + var staticConfigs *[]argus.UpdateScrapeConfigPayloadStaticConfigsInner if data.StaticConfigs != nil { + configs := make([]argus.UpdateScrapeConfigPayloadStaticConfigsInner, 0) for _, config := range *data.StaticConfigs { - staticConfigs = append(staticConfigs, mapStaticConfig(config)) + newConfig, err := mapStaticConfig(config) + if err != nil { + return nil, fmt.Errorf("map static config: %w", err) + } + configs = append(configs, newConfig) } + staticConfigs = &configs } tlsConfig := mapTlsConfig(data.TlsConfig) - return &argus.UpdateScrapeConfigPayload{ - BasicAuth: basicAuth, - BearerToken: data.BearerToken, - HonorLabels: data.HonorLabels, - HonorTimeStamps: data.HonorTimeStamps, - MetricsPath: data.MetricsPath, - // MetricsRelabelConfigs: metricsRelabelConfigs, - // Params: convertMapStringToInterface(data.Params), - SampleLimit: utils.Ptr(float64(*data.SampleLimit)), - Scheme: data.Scheme, - ScrapeInterval: data.ScrapeInterval, - ScrapeTimeout: data.ScrapeTimeout, - StaticConfigs: &staticConfigs, - TlsConfig: tlsConfig, + var metricsRelabelConfigs *[]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner + if data.MetricsRelabelConfigs != nil { + configs := make([]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner, 0) + for _, config := range *data.MetricsRelabelConfigs { + configs = append(configs, mapMetricsRelabelConfig(config)) + } + metricsRelabelConfigs = &configs } -} -func mapOAuth2(oauth2 *argus.OAuth2) *argus.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2 { - return nil + var params *map[string]interface{} + var err error + + if data.Params != nil { + params, err = convertMapAnyToInterface(*data.Params) + if err != nil { + return nil, fmt.Errorf("convert params: %w", err) + } + } + + return &argus.UpdateScrapeConfigPayload{ + BasicAuth: basicAuth, + BearerToken: data.BearerToken, + HonorLabels: data.HonorLabels, + HonorTimeStamps: data.HonorTimeStamps, + MetricsPath: data.MetricsPath, + MetricsRelabelConfigs: metricsRelabelConfigs, + Params: params, + SampleLimit: convertIntToFloat64(data.SampleLimit), + Scheme: data.Scheme, + ScrapeInterval: data.ScrapeInterval, + ScrapeTimeout: data.ScrapeTimeout, + StaticConfigs: staticConfigs, + TlsConfig: tlsConfig, + }, nil } -func mapHttpSdConfig(httpSdConfig argus.HTTPServiceSD) argus.CreateScrapeConfigPayloadHttpSdConfigsInner { - oauth2 := mapOAuth2(httpSdConfig.Oauth2) - tlsConfig := mapTlsConfig(httpSdConfig.TlsConfig) - return argus.CreateScrapeConfigPayloadHttpSdConfigsInner{ - Oauth2: oauth2, - TlsConfig: tlsConfig, +func convertIntToFloat64(i *int64) *float64 { + if i == nil { + return nil } + f := float64(*i) + return &f +} +func mapMetricsRelabelConfig(metricsRelabelConfig argus.MetricsRelabelConfig) argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner { + return argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ + Action: metricsRelabelConfig.Action, + Modulus: convertIntToFloat64(metricsRelabelConfig.Modulus), + Regex: metricsRelabelConfig.Regex, + Replacement: metricsRelabelConfig.Replacement, + Separator: metricsRelabelConfig.Separator, + SourceLabels: metricsRelabelConfig.SourceLabels, + TargetLabel: metricsRelabelConfig.TargetLabel, + } } -func mapStaticConfig(staticConfig argus.StaticConfigs) argus.UpdateScrapeConfigPayloadStaticConfigsInner { - labels := convertMapStringToInterface(staticConfig.Labels) +func mapStaticConfig(staticConfig argus.StaticConfigs) (argus.UpdateScrapeConfigPayloadStaticConfigsInner, error) { + var labels *map[string]interface{} + var err error + if staticConfig.Labels != nil { + labels, err = convertMapAnyToInterface(*staticConfig.Labels) + + if err != nil { + return argus.UpdateScrapeConfigPayloadStaticConfigsInner{}, fmt.Errorf("convert labels: %w", err) + } + } + return argus.UpdateScrapeConfigPayloadStaticConfigsInner{ Labels: labels, Targets: staticConfig.Targets, - } + }, nil } func mapBasicAuth(basicAuth *argus.BasicAuth) *argus.CreateScrapeConfigPayloadBasicAuth { @@ -138,16 +174,25 @@ func mapTlsConfig(tlsConfig *argus.TLSConfig) *argus.CreateScrapeConfigPayloadHt } } -func convertMapStringToInterface(m *map[string]string) *map[string]interface{} { - if m == nil { - return nil - } - +func convertMapAnyToInterface(m interface{}) (*map[string]interface{}, error) { newMap := make(map[string]interface{}) - for k, v := range *m { - newMap[k] = v + + switch m.(type) { + case map[string]string: + convertedMap := m.(map[string]string) + for k, v := range convertedMap { + newMap[k] = v + } + case map[string][]string: + convertedMap := m.(map[string][]string) + for k, v := range convertedMap { + newMap[k] = v + } + default: + return nil, fmt.Errorf("unsupported map type") } - return &newMap + + return &newMap, nil } type ArgusClient interface { diff --git a/internal/pkg/services/argus/utils/utils_test.go b/internal/pkg/services/argus/utils/utils_test.go index 8adf5de1b..bd54da4cd 100644 --- a/internal/pkg/services/argus/utils/utils_test.go +++ b/internal/pkg/services/argus/utils/utils_test.go @@ -8,6 +8,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/stackitcloud/stackit-sdk-go/services/argus" ) @@ -32,6 +33,109 @@ var testPlansResponse = argus.PlansResponse{ }, } +func fixtureGetScrapeConfigResponse(mods ...func(*argus.GetScrapeConfigResponse)) *argus.GetScrapeConfigResponse { + number := int64(1) + resp := &argus.GetScrapeConfigResponse{ + Data: &argus.Job{ + BasicAuth: &argus.BasicAuth{ + Username: utils.Ptr("username"), + Password: utils.Ptr("password"), + }, + BearerToken: utils.Ptr("bearerToken"), + HonorLabels: utils.Ptr(true), + HonorTimeStamps: utils.Ptr(true), + MetricsPath: utils.Ptr("/metrics"), + MetricsRelabelConfigs: &[]argus.MetricsRelabelConfig{ + { + Action: utils.Ptr("replace"), + Modulus: &number, + Regex: utils.Ptr("regex"), + Replacement: utils.Ptr("replacement"), + Separator: utils.Ptr("separator"), + SourceLabels: &[]string{"sourceLabel"}, + TargetLabel: utils.Ptr("targetLabel"), + }, + }, + Params: &map[string][]string{ + "key": {"value1", "value2"}, + "key2": {}, + }, + SampleLimit: &number, + Scheme: utils.Ptr("scheme"), + ScrapeInterval: utils.Ptr("interval"), + ScrapeTimeout: utils.Ptr("timeout"), + StaticConfigs: &[]argus.StaticConfigs{ + { + Labels: &map[string]string{ + "label": "value", + "label2": "value2", + }, + Targets: &[]string{"target"}, + }, + }, + TlsConfig: &argus.TLSConfig{ + InsecureSkipVerify: utils.Ptr(true), + }, + }, + } + + for _, mod := range mods { + mod(resp) + } + + return resp +} + +func fixtureUpdateScrapeConfigPayload(mods ...func(*argus.UpdateScrapeConfigPayload)) *argus.UpdateScrapeConfigPayload { + payload := &argus.UpdateScrapeConfigPayload{ + BasicAuth: &argus.CreateScrapeConfigPayloadBasicAuth{ + Username: utils.Ptr("username"), + Password: utils.Ptr("password"), + }, + BearerToken: utils.Ptr("bearerToken"), + HonorLabels: utils.Ptr(true), + HonorTimeStamps: utils.Ptr(true), + MetricsPath: utils.Ptr("/metrics"), + MetricsRelabelConfigs: &[]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ + { + Action: utils.Ptr("replace"), + Modulus: utils.Ptr(1.0), + Regex: utils.Ptr("regex"), + Replacement: utils.Ptr("replacement"), + Separator: utils.Ptr("separator"), + SourceLabels: &[]string{"sourceLabel"}, + TargetLabel: utils.Ptr("targetLabel"), + }, + }, + Params: &map[string]interface{}{ + "key": []string{"value1", "value2"}, + "key2": []string{}, + }, + SampleLimit: utils.Ptr(1.0), + Scheme: utils.Ptr("scheme"), + ScrapeInterval: utils.Ptr("interval"), + ScrapeTimeout: utils.Ptr("timeout"), + StaticConfigs: &[]argus.UpdateScrapeConfigPayloadStaticConfigsInner{ + { + Labels: &map[string]interface{}{ + "label": "value", + "label2": "value2", + }, + Targets: &[]string{"target"}, + }, + }, + TlsConfig: &argus.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{ + InsecureSkipVerify: utils.Ptr(true), + }, + } + + for _, mod := range mods { + mod(payload) + } + + return payload +} + type argusClientMocked struct { getInstanceFails bool getInstanceResp *argus.GetInstanceResponse @@ -220,3 +324,64 @@ func TestValidatePlanId(t *testing.T) { }) } } + +func TestMapToUpdateScrapeConfigPayload(t *testing.T) { + tests := []struct { + description string + resp *argus.GetScrapeConfigResponse + expectedPayload *argus.UpdateScrapeConfigPayload + isValid bool + }{ + { + description: "base case", + resp: fixtureGetScrapeConfigResponse(), + expectedPayload: fixtureUpdateScrapeConfigPayload(), + isValid: true, + }, + { + description: "nil response", + resp: nil, + isValid: false, + }, + { + description: "nil data", + resp: &argus.GetScrapeConfigResponse{ + Data: nil, + }, + isValid: false, + }, + { + description: "empty data", + resp: &argus.GetScrapeConfigResponse{ + Data: &argus.Job{}, + }, + expectedPayload: &argus.UpdateScrapeConfigPayload{}, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + payload, err := MapToUpdateScrapeConfigPayload(tt.resp) + + if tt.isValid && err != nil { + t.Errorf("failed on valid input") + } + if !tt.isValid && err == nil { + t.Errorf("did not fail on invalid input") + } + if !tt.isValid { + return + } + + diff := cmp.Diff(*payload, *tt.expectedPayload, + cmp.AllowUnexported(*tt.expectedPayload), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + + }) + } + +} From 38aecfebacd4185393ab9d6921ca7e9cad5f1825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Wed, 17 Apr 2024 20:46:09 +0100 Subject: [PATCH 03/10] implement function to create default create config --- .../generate-payload/generate_payload.go | 5 +- internal/pkg/services/argus/utils/utils.go | 19 ++++++- .../pkg/services/argus/utils/utils_test.go | 50 +++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go index 859448b9c..8cc40c998 100644 --- a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go @@ -66,10 +66,7 @@ func NewCmd(p *print.Printer) *cobra.Command { } if model.JobName == nil { - createPayload, err := argusUtils.GetDefaultCreateScrapeConfigPayload(ctx, apiClient) - if err != nil { - return err - } + createPayload := argusUtils.GetDefaultCreateScrapeConfigPayload() return outputCreateResult(p, createPayload) } else { req := buildRequest(ctx, model, apiClient) diff --git a/internal/pkg/services/argus/utils/utils.go b/internal/pkg/services/argus/utils/utils.go index 450f03b63..05780274a 100644 --- a/internal/pkg/services/argus/utils/utils.go +++ b/internal/pkg/services/argus/utils/utils.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/stackitcloud/stackit-sdk-go/services/argus" ) @@ -207,6 +208,20 @@ func GetInstanceName(ctx context.Context, apiClient ArgusClient, instanceId, pro return *resp.Name, nil } -func GetDefaultCreateScrapeConfigPayload(ctx context.Context, apiClient ArgusClient) (*argus.CreateScrapeConfigPayload, error) { - return nil, nil +func GetDefaultCreateScrapeConfigPayload() *argus.CreateScrapeConfigPayload { + staticConfigs := []argus.CreateScrapeConfigPayloadStaticConfigsInner{ + { + Targets: utils.Ptr([]string{ + "url-target", + }), + }, + } + return &argus.CreateScrapeConfigPayload{ + JobName: utils.Ptr("default-name"), + MetricsPath: utils.Ptr("/metrics"), + Scheme: utils.Ptr("https"), + ScrapeInterval: utils.Ptr("5m"), + ScrapeTimeout: utils.Ptr("2m"), + StaticConfigs: &staticConfigs, + } } diff --git a/internal/pkg/services/argus/utils/utils_test.go b/internal/pkg/services/argus/utils/utils_test.go index bd54da4cd..fcf54ce84 100644 --- a/internal/pkg/services/argus/utils/utils_test.go +++ b/internal/pkg/services/argus/utils/utils_test.go @@ -136,6 +136,31 @@ func fixtureUpdateScrapeConfigPayload(mods ...func(*argus.UpdateScrapeConfigPayl return payload } +func fixtureCreateScrapeConfigPayload(mods ...func(*argus.CreateScrapeConfigPayload)) *argus.CreateScrapeConfigPayload { + staticConfigs := []argus.CreateScrapeConfigPayloadStaticConfigsInner{ + { + Targets: utils.Ptr([]string{ + "url-target", + }), + }, + } + + payload := &argus.CreateScrapeConfigPayload{ + JobName: utils.Ptr("default-name"), + MetricsPath: utils.Ptr("/metrics"), + Scheme: utils.Ptr("https"), + ScrapeInterval: utils.Ptr("5m"), + ScrapeTimeout: utils.Ptr("2m"), + StaticConfigs: &staticConfigs, + } + + for _, mod := range mods { + mod(payload) + } + + return payload +} + type argusClientMocked struct { getInstanceFails bool getInstanceResp *argus.GetInstanceResponse @@ -383,5 +408,30 @@ func TestMapToUpdateScrapeConfigPayload(t *testing.T) { }) } +} + +func TestGetDefaultCreateScrapeConfigPayload(t *testing.T) { + tests := []struct { + description string + expectedPayload *argus.CreateScrapeConfigPayload + }{ + { + description: "base case", + expectedPayload: fixtureCreateScrapeConfigPayload(), + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + payload := GetDefaultCreateScrapeConfigPayload() + + diff := cmp.Diff(*payload, *tt.expectedPayload, + cmp.AllowUnexported(*tt.expectedPayload), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + + }) + } } From 5a81e8e2c674fcd1298b191ebf2e25b6587e8ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 18 Apr 2024 12:54:58 +0200 Subject: [PATCH 04/10] update help, improve testing and remove verification when job name is not provided --- docs/stackit.md | 13 ----- docs/stackit_argus.md | 1 + docs/stackit_argus_scrape-configs.md | 33 ++++++++++++ ...t_argus_scrape-configs_generate-payload.md | 53 +++++++++++++++++++ .../generate-payload/generate_payload.go | 33 +++++------- internal/pkg/services/argus/utils/utils.go | 16 +++--- .../pkg/services/argus/utils/utils_test.go | 2 - 7 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 docs/stackit_argus_scrape-configs.md create mode 100644 docs/stackit_argus_scrape-configs_generate-payload.md diff --git a/docs/stackit.md b/docs/stackit.md index eaa87cd07..7dacfde65 100644 --- a/docs/stackit.md +++ b/docs/stackit.md @@ -26,35 +26,22 @@ stackit [flags] ### SEE ALSO -* [stackit argus](./stackit_argus.md) - Provides functionality for Argus * [stackit argus](./stackit_argus.md) - Provides functionality for Argus * [stackit auth](./stackit_auth.md) - Provides authentication functionality * [stackit config](./stackit_config.md) - Provides functionality for CLI configuration options * [stackit curl](./stackit_curl.md) - Executes an authenticated HTTP request to an endpoint * [stackit dns](./stackit_dns.md) - Provides functionality for DNS * [stackit logme](./stackit_logme.md) - Provides functionality for LogMe -* [stackit logme](./stackit_logme.md) - Provides functionality for LogMe * [stackit mariadb](./stackit_mariadb.md) - Provides functionality for MariaDB -* [stackit mariadb](./stackit_mariadb.md) - Provides functionality for MariaDB -* [stackit mongodbflex](./stackit_mongodbflex.md) - Provides functionality for MongoDB Flex * [stackit mongodbflex](./stackit_mongodbflex.md) - Provides functionality for MongoDB Flex * [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage -* [stackit object-storage](./stackit_object-storage.md) - Provides functionality regarding Object Storage -* [stackit opensearch](./stackit_opensearch.md) - Provides functionality for OpenSearch * [stackit opensearch](./stackit_opensearch.md) - Provides functionality for OpenSearch * [stackit organization](./stackit_organization.md) - Provides functionality regarding organizations -* [stackit organization](./stackit_organization.md) - Provides functionality regarding organizations -* [stackit postgresflex](./stackit_postgresflex.md) - Provides functionality for PostgreSQL Flex * [stackit postgresflex](./stackit_postgresflex.md) - Provides functionality for PostgreSQL Flex * [stackit project](./stackit_project.md) - Provides functionality regarding projects * [stackit rabbitmq](./stackit_rabbitmq.md) - Provides functionality for RabbitMQ -* [stackit rabbitmq](./stackit_rabbitmq.md) - Provides functionality for RabbitMQ -* [stackit redis](./stackit_redis.md) - Provides functionality for Redis * [stackit redis](./stackit_redis.md) - Provides functionality for Redis * [stackit secrets-manager](./stackit_secrets-manager.md) - Provides functionality for Secrets Manager -* [stackit secrets-manager](./stackit_secrets-manager.md) - Provides functionality for Secrets Manager -* [stackit service-account](./stackit_service-account.md) - Provides functionality for service accounts * [stackit service-account](./stackit_service-account.md) - Provides functionality for service accounts * [stackit ske](./stackit_ske.md) - Provides functionality for SKE -* [stackit ske](./stackit_ske.md) - Provides functionality for SKE diff --git a/docs/stackit_argus.md b/docs/stackit_argus.md index 659b1dc36..fe563e5e4 100644 --- a/docs/stackit_argus.md +++ b/docs/stackit_argus.md @@ -31,4 +31,5 @@ stackit argus [flags] * [stackit](./stackit.md) - Manage STACKIT resources using the command line * [stackit argus instance](./stackit_argus_instance.md) - Provides functionality for Argus instances * [stackit argus plans](./stackit_argus_plans.md) - Lists all Argus service plans +* [stackit argus scrape-configs](./stackit_argus_scrape-configs.md) - Provides functionality for scraping configs in Argus. diff --git a/docs/stackit_argus_scrape-configs.md b/docs/stackit_argus_scrape-configs.md new file mode 100644 index 000000000..f5625f9ed --- /dev/null +++ b/docs/stackit_argus_scrape-configs.md @@ -0,0 +1,33 @@ +## stackit argus scrape-configs + +Provides functionality for scraping configs in Argus. + +### Synopsis + +Provides functionality for scraping configurations in Argus. + +``` +stackit argus scrape-configs [flags] +``` + +### Options + +``` + -h, --help Help for "stackit argus scrape-configs" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit argus](./stackit_argus.md) - Provides functionality for Argus +* [stackit argus scrape-configs generate-payload](./stackit_argus_scrape-configs_generate-payload.md) - Generates a payload to create/update Scrape Configurations for an Argus instance + diff --git a/docs/stackit_argus_scrape-configs_generate-payload.md b/docs/stackit_argus_scrape-configs_generate-payload.md new file mode 100644 index 000000000..e10074d1d --- /dev/null +++ b/docs/stackit_argus_scrape-configs_generate-payload.md @@ -0,0 +1,53 @@ +## stackit argus scrape-configs generate-payload + +Generates a payload to create/update Scrape Configurations for an Argus instance + +### Synopsis + +Generates a JSON payload with values to be used as --payload input for Scrape Configurations creation or update. +This command can be used to generate a payload to update an existing Scrape Config job or to create a new Scrape Config job. +To update an existing Scrape Config job, provide the job name and the instance ID of the Argus instance. +To obtain a default payload to create a new Scrape Config job, run the command with no flags. +See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure. + + +``` +stackit argus scrape-configs generate-payload [flags] +``` + +### Examples + +``` + Generate a Create payload with default values, and adapt it with custom values for the different configuration options + $ stackit argus scrape-configs generate-payload > ./payload.json + + $ stackit argus scrape-configs create my-config --payload @./payload.json + + Generate an Update payload with the values of an existing configuration named "my-config" for Argus instance xxx, and adapt it with custom values for the different configuration options + $ stackit argus scrape-configs generate-payload --job-name my-config --instance-id xxx > ./payload.json + + $ stackit argus scrape-configs update my-config --payload @./payload.json +``` + +### Options + +``` + -h, --help Help for "stackit argus scrape-configs generate-payload" + --instance-id string Instance ID + -n, --job-name string If set, generates an update payload with the current state of the given scrape config. If unset, generates a create payload with default values +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty"] + -p, --project-id string Project ID + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit argus scrape-configs](./stackit_argus_scrape-configs.md) - Provides functionality for scraping configs in Argus. + diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go index 8cc40c998..3aa4318ed 100644 --- a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go @@ -35,8 +35,8 @@ func NewCmd(p *print.Printer) *cobra.Command { Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n", "Generates a JSON payload with values to be used as --payload input for Scrape Configurations creation or update.", "This command can be used to generate a payload to update an existing Scrape Config job or to create a new Scrape Config job.", - "To obtain an Update payload, provide the job name and instance ID of the desired Scrape Config Job and respective Argus instance.", - "To obtain a Create payload, run the command with no flags", + "To update an existing Scrape Config job, provide the job name and the instance ID of the Argus instance.", + "To obtain a default payload to create a new Scrape Config job, run the command with no flags.", "See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure.", ), Args: args.NoArgs, @@ -68,21 +68,20 @@ func NewCmd(p *print.Printer) *cobra.Command { if model.JobName == nil { createPayload := argusUtils.GetDefaultCreateScrapeConfigPayload() return outputCreateResult(p, createPayload) - } else { - req := buildRequest(ctx, model, apiClient) - resp, err := req.Execute() - if err != nil { - return fmt.Errorf("read Argus Scrape Config: %w", err) - } - - payload, err := argusUtils.MapToUpdateScrapeConfigPayload(resp) - if err != nil { - return fmt.Errorf("map update scrape config payloads: %w", err) - } - - return outputUpdateResult(p, payload) } + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("read Argus Scrape Config: %w", err) + } + + payload, err := argusUtils.MapToUpdateScrapeConfigPayload(resp) + if err != nil { + return fmt.Errorf("map update scrape config payloads: %w", err) + } + + return outputUpdateResult(p, payload) }, } configureFlags(cmd) @@ -104,10 +103,6 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { return nil, fmt.Errorf("if a job-name is provided then instance-id and project-id must to be provided") } - if jobName == nil && (globalFlags.ProjectId != "" || instanceId != "") { - return nil, fmt.Errorf("if a job-name is not provided then instance-id and project-id can't be provided") - } - return &inputModel{ GlobalFlagModel: globalFlags, JobName: jobName, diff --git a/internal/pkg/services/argus/utils/utils.go b/internal/pkg/services/argus/utils/utils.go index 05780274a..bec1d23ff 100644 --- a/internal/pkg/services/argus/utils/utils.go +++ b/internal/pkg/services/argus/utils/utils.go @@ -91,13 +91,13 @@ func MapToUpdateScrapeConfigPayload(resp *argus.GetScrapeConfigResponse) (*argus } var params *map[string]interface{} - var err error if data.Params != nil { - params, err = convertMapAnyToInterface(*data.Params) + paramsMap, err := convertMapAnyToInterface(*data.Params) if err != nil { return nil, fmt.Errorf("convert params: %w", err) } + params = ¶msMap } return &argus.UpdateScrapeConfigPayload{ @@ -138,7 +138,7 @@ func mapMetricsRelabelConfig(metricsRelabelConfig argus.MetricsRelabelConfig) ar } func mapStaticConfig(staticConfig argus.StaticConfigs) (argus.UpdateScrapeConfigPayloadStaticConfigsInner, error) { - var labels *map[string]interface{} + var labels map[string]interface{} var err error if staticConfig.Labels != nil { labels, err = convertMapAnyToInterface(*staticConfig.Labels) @@ -149,7 +149,7 @@ func mapStaticConfig(staticConfig argus.StaticConfigs) (argus.UpdateScrapeConfig } return argus.UpdateScrapeConfigPayloadStaticConfigsInner{ - Labels: labels, + Labels: &labels, Targets: staticConfig.Targets, }, nil } @@ -175,17 +175,15 @@ func mapTlsConfig(tlsConfig *argus.TLSConfig) *argus.CreateScrapeConfigPayloadHt } } -func convertMapAnyToInterface(m interface{}) (*map[string]interface{}, error) { +func convertMapAnyToInterface(m interface{}) (map[string]interface{}, error) { newMap := make(map[string]interface{}) - switch m.(type) { + switch convertedMap := m.(type) { case map[string]string: - convertedMap := m.(map[string]string) for k, v := range convertedMap { newMap[k] = v } case map[string][]string: - convertedMap := m.(map[string][]string) for k, v := range convertedMap { newMap[k] = v } @@ -193,7 +191,7 @@ func convertMapAnyToInterface(m interface{}) (*map[string]interface{}, error) { return nil, fmt.Errorf("unsupported map type") } - return &newMap, nil + return newMap, nil } type ArgusClient interface { diff --git a/internal/pkg/services/argus/utils/utils_test.go b/internal/pkg/services/argus/utils/utils_test.go index fcf54ce84..2af7370dd 100644 --- a/internal/pkg/services/argus/utils/utils_test.go +++ b/internal/pkg/services/argus/utils/utils_test.go @@ -405,7 +405,6 @@ func TestMapToUpdateScrapeConfigPayload(t *testing.T) { if diff != "" { t.Fatalf("Data does not match: %s", diff) } - }) } } @@ -431,7 +430,6 @@ func TestGetDefaultCreateScrapeConfigPayload(t *testing.T) { if diff != "" { t.Fatalf("Data does not match: %s", diff) } - }) } } From 3604253b848cc326fc8defe9657f06049f908427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 18 Apr 2024 13:14:36 +0200 Subject: [PATCH 05/10] Improve testing --- .../generate-payload/generate_payload_test.go | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload_test.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload_test.go index 2e7ccdc6b..afb663f08 100644 --- a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload_test.go +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload_test.go @@ -90,6 +90,16 @@ func TestParseInput(t *testing.T) { model.JobName = nil }), }, + { + description: "job name missing, instance id provided", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, jobNameFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.JobName = nil + }), + }, { description: "instance id missing, job name provided", flagValues: fixtureFlagValues(func(flagValues map[string]string) { @@ -98,23 +108,31 @@ func TestParseInput(t *testing.T) { isValid: false, }, { - description: "instance id invalid 1", + description: "project id missing, job name provided", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[instanceIdFlag] = "" + delete(flagValues, projectIdFlag) }), isValid: false, }, { - description: "instance id invalid 2", + description: "project id and instance id missing, job name provided", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - flagValues[instanceIdFlag] = "invalid-uuid" + delete(flagValues, projectIdFlag) + delete(flagValues, instanceIdFlag) }), isValid: false, }, { - description: "project id missing, job name provided", + description: "instance id invalid 1", flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, projectIdFlag) + flagValues[instanceIdFlag] = "" + }), + isValid: false, + }, + { + description: "instance id invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[instanceIdFlag] = "invalid-uuid" }), isValid: false, }, From 918daf86b210b046154286c899b37639aaad9448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 18 Apr 2024 17:52:40 +0200 Subject: [PATCH 06/10] address PR comments --- docs/stackit_argus.md | 2 +- docs/stackit_argus_scrape-configs.md | 4 +- ...t_argus_scrape-configs_generate-payload.md | 5 +- .../generate-payload/generate_payload.go | 9 +- .../argus/scrape-configs/scrape_configs.go | 4 +- internal/pkg/services/argus/utils/utils.go | 164 +++++----- .../pkg/services/argus/utils/utils_test.go | 299 +++++++++++++++--- internal/pkg/utils/utils.go | 22 ++ internal/pkg/utils/utils_test.go | 45 +++ 9 files changed, 415 insertions(+), 139 deletions(-) create mode 100644 internal/pkg/utils/utils_test.go diff --git a/docs/stackit_argus.md b/docs/stackit_argus.md index fe563e5e4..eb22ea0fa 100644 --- a/docs/stackit_argus.md +++ b/docs/stackit_argus.md @@ -31,5 +31,5 @@ stackit argus [flags] * [stackit](./stackit.md) - Manage STACKIT resources using the command line * [stackit argus instance](./stackit_argus_instance.md) - Provides functionality for Argus instances * [stackit argus plans](./stackit_argus_plans.md) - Lists all Argus service plans -* [stackit argus scrape-configs](./stackit_argus_scrape-configs.md) - Provides functionality for scraping configs in Argus. +* [stackit argus scrape-configs](./stackit_argus_scrape-configs.md) - Provides functionality for scrape configs in Argus. diff --git a/docs/stackit_argus_scrape-configs.md b/docs/stackit_argus_scrape-configs.md index f5625f9ed..73d9455d8 100644 --- a/docs/stackit_argus_scrape-configs.md +++ b/docs/stackit_argus_scrape-configs.md @@ -1,10 +1,10 @@ ## stackit argus scrape-configs -Provides functionality for scraping configs in Argus. +Provides functionality for scrape configs in Argus. ### Synopsis -Provides functionality for scraping configurations in Argus. +Provides functionality for scrape configurations in Argus. ``` stackit argus scrape-configs [flags] diff --git a/docs/stackit_argus_scrape-configs_generate-payload.md b/docs/stackit_argus_scrape-configs_generate-payload.md index e10074d1d..b2594750d 100644 --- a/docs/stackit_argus_scrape-configs_generate-payload.md +++ b/docs/stackit_argus_scrape-configs_generate-payload.md @@ -7,7 +7,8 @@ Generates a payload to create/update Scrape Configurations for an Argus instance Generates a JSON payload with values to be used as --payload input for Scrape Configurations creation or update. This command can be used to generate a payload to update an existing Scrape Config job or to create a new Scrape Config job. To update an existing Scrape Config job, provide the job name and the instance ID of the Argus instance. -To obtain a default payload to create a new Scrape Config job, run the command with no flags. +To obtain a default payload to create a new Scrape Config job, run the command with no flags. Note that some fields, like the url of the target, are required and must be provided in the payload. +Note that the default values provided for jobName, metricsPath and targets should be changed to make sense for the use case inteded. See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure. @@ -49,5 +50,5 @@ stackit argus scrape-configs generate-payload [flags] ### SEE ALSO -* [stackit argus scrape-configs](./stackit_argus_scrape-configs.md) - Provides functionality for scraping configs in Argus. +* [stackit argus scrape-configs](./stackit_argus_scrape-configs.md) - Provides functionality for scrape configs in Argus. diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go index 3aa4318ed..68d7f0117 100644 --- a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go @@ -32,11 +32,12 @@ func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "generate-payload", Short: "Generates a payload to create/update Scrape Configurations for an Argus instance ", - Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n", + Long: fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n", "Generates a JSON payload with values to be used as --payload input for Scrape Configurations creation or update.", "This command can be used to generate a payload to update an existing Scrape Config job or to create a new Scrape Config job.", "To update an existing Scrape Config job, provide the job name and the instance ID of the Argus instance.", - "To obtain a default payload to create a new Scrape Config job, run the command with no flags.", + "To obtain a default payload to create a new Scrape Config job, run the command with no flags. Note that some fields, like the url of the target, are required and must be provided in the payload.", + "Note that the default values provided for jobName, metricsPath and targets should be changed to make sense for the use case inteded.", "See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure.", ), Args: args.NoArgs, @@ -66,8 +67,8 @@ func NewCmd(p *print.Printer) *cobra.Command { } if model.JobName == nil { - createPayload := argusUtils.GetDefaultCreateScrapeConfigPayload() - return outputCreateResult(p, createPayload) + createPayload := argusUtils.DefaultCreateScrapeConfigPayload + return outputCreateResult(p, &createPayload) } req := buildRequest(ctx, model, apiClient) diff --git a/internal/cmd/argus/scrape-configs/scrape_configs.go b/internal/cmd/argus/scrape-configs/scrape_configs.go index b58b1e6e1..83c4fc634 100644 --- a/internal/cmd/argus/scrape-configs/scrape_configs.go +++ b/internal/cmd/argus/scrape-configs/scrape_configs.go @@ -12,8 +12,8 @@ import ( func NewCmd(p *print.Printer) *cobra.Command { cmd := &cobra.Command{ Use: "scrape-configs", - Short: "Provides functionality for scraping configs in Argus.", - Long: "Provides functionality for scraping configurations in Argus.", + Short: "Provides functionality for scrape configs in Argus.", + Long: "Provides functionality for scrape configurations in Argus.", Args: args.NoArgs, Run: utils.CmdHelp, } diff --git a/internal/pkg/services/argus/utils/utils.go b/internal/pkg/services/argus/utils/utils.go index bec1d23ff..a9fafdef0 100644 --- a/internal/pkg/services/argus/utils/utils.go +++ b/internal/pkg/services/argus/utils/utils.go @@ -15,6 +15,24 @@ const ( service = "argus" ) +var ( + defaultStaticConfigs = []argus.CreateScrapeConfigPayloadStaticConfigsInner{ + { + Targets: utils.Ptr([]string{ + "url-target", + }), + }, + } + DefaultCreateScrapeConfigPayload = argus.CreateScrapeConfigPayload{ + JobName: utils.Ptr("default-name"), + MetricsPath: utils.Ptr("/metrics"), + Scheme: utils.Ptr("https"), + ScrapeInterval: utils.Ptr("5m"), + ScrapeTimeout: utils.Ptr("2m"), + StaticConfigs: utils.Ptr(defaultStaticConfigs), + } +) + func ValidatePlanId(planId string, resp *argus.PlansResponse) error { if resp == nil { return fmt.Errorf("no Argus plans provided") @@ -65,42 +83,16 @@ func MapToUpdateScrapeConfigPayload(resp *argus.GetScrapeConfigResponse) (*argus data := resp.Data basicAuth := mapBasicAuth(data.BasicAuth) - - var staticConfigs *[]argus.UpdateScrapeConfigPayloadStaticConfigsInner - if data.StaticConfigs != nil { - configs := make([]argus.UpdateScrapeConfigPayloadStaticConfigsInner, 0) - for _, config := range *data.StaticConfigs { - newConfig, err := mapStaticConfig(config) - if err != nil { - return nil, fmt.Errorf("map static config: %w", err) - } - configs = append(configs, newConfig) - } - staticConfigs = &configs - } - + staticConfigs := mapStaticConfig(data.StaticConfigs) tlsConfig := mapTlsConfig(data.TlsConfig) - - var metricsRelabelConfigs *[]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner - if data.MetricsRelabelConfigs != nil { - configs := make([]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner, 0) - for _, config := range *data.MetricsRelabelConfigs { - configs = append(configs, mapMetricsRelabelConfig(config)) - } - metricsRelabelConfigs = &configs - } + metricsRelabelConfigs := mapMetricsRelabelConfig(data.MetricsRelabelConfigs) var params *map[string]interface{} - if data.Params != nil { - paramsMap, err := convertMapAnyToInterface(*data.Params) - if err != nil { - return nil, fmt.Errorf("convert params: %w", err) - } - params = ¶msMap + params = utils.Ptr(mapParams(*data.Params)) } - return &argus.UpdateScrapeConfigPayload{ + payload := argus.UpdateScrapeConfigPayload{ BasicAuth: basicAuth, BearerToken: data.BearerToken, HonorLabels: data.HonorLabels, @@ -108,50 +100,59 @@ func MapToUpdateScrapeConfigPayload(resp *argus.GetScrapeConfigResponse) (*argus MetricsPath: data.MetricsPath, MetricsRelabelConfigs: metricsRelabelConfigs, Params: params, - SampleLimit: convertIntToFloat64(data.SampleLimit), + SampleLimit: utils.ConvertInt64PToFloat64P(data.SampleLimit), Scheme: data.Scheme, ScrapeInterval: data.ScrapeInterval, ScrapeTimeout: data.ScrapeTimeout, StaticConfigs: staticConfigs, TlsConfig: tlsConfig, - }, nil -} + } -func convertIntToFloat64(i *int64) *float64 { - if i == nil { - return nil + if payload == (argus.UpdateScrapeConfigPayload{}) { + return nil, fmt.Errorf("the provided Argus Scrape Sonfig payload is empty") } - f := float64(*i) - return &f + + return &payload, nil } -func mapMetricsRelabelConfig(metricsRelabelConfig argus.MetricsRelabelConfig) argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner { - return argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ - Action: metricsRelabelConfig.Action, - Modulus: convertIntToFloat64(metricsRelabelConfig.Modulus), - Regex: metricsRelabelConfig.Regex, - Replacement: metricsRelabelConfig.Replacement, - Separator: metricsRelabelConfig.Separator, - SourceLabels: metricsRelabelConfig.SourceLabels, - TargetLabel: metricsRelabelConfig.TargetLabel, +func mapMetricsRelabelConfig(metricsRelabelConfigs *[]argus.MetricsRelabelConfig) *[]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner { + if metricsRelabelConfigs == nil { + return nil + } + configs := make([]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner, 0) + for _, config := range *metricsRelabelConfigs { + mappedConfig := argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ + Action: config.Action, + Modulus: utils.ConvertInt64PToFloat64P(config.Modulus), + Regex: config.Regex, + Replacement: config.Replacement, + Separator: config.Separator, + SourceLabels: config.SourceLabels, + TargetLabel: config.TargetLabel, + } + configs = append(configs, mappedConfig) } + return &configs } -func mapStaticConfig(staticConfig argus.StaticConfigs) (argus.UpdateScrapeConfigPayloadStaticConfigsInner, error) { - var labels map[string]interface{} - var err error - if staticConfig.Labels != nil { - labels, err = convertMapAnyToInterface(*staticConfig.Labels) - - if err != nil { - return argus.UpdateScrapeConfigPayloadStaticConfigsInner{}, fmt.Errorf("convert labels: %w", err) +func mapStaticConfig(staticConfigs *[]argus.StaticConfigs) *[]argus.UpdateScrapeConfigPayloadStaticConfigsInner { + if staticConfigs == nil { + return nil + } + configs := make([]argus.UpdateScrapeConfigPayloadStaticConfigsInner, 0) + for _, config := range *staticConfigs { + var labels *map[string]interface{} + if config.Labels != nil { + labels = utils.Ptr(mapStaticConfigLabels(*config.Labels)) + } + mappedConfig := argus.UpdateScrapeConfigPayloadStaticConfigsInner{ + Labels: labels, + Targets: config.Targets, } + configs = append(configs, mappedConfig) } - return argus.UpdateScrapeConfigPayloadStaticConfigsInner{ - Labels: &labels, - Targets: staticConfig.Targets, - }, nil + return &configs } func mapBasicAuth(basicAuth *argus.BasicAuth) *argus.CreateScrapeConfigPayloadBasicAuth { @@ -175,23 +176,20 @@ func mapTlsConfig(tlsConfig *argus.TLSConfig) *argus.CreateScrapeConfigPayloadHt } } -func convertMapAnyToInterface(m interface{}) (map[string]interface{}, error) { - newMap := make(map[string]interface{}) - - switch convertedMap := m.(type) { - case map[string]string: - for k, v := range convertedMap { - newMap[k] = v - } - case map[string][]string: - for k, v := range convertedMap { - newMap[k] = v - } - default: - return nil, fmt.Errorf("unsupported map type") +func mapParams(params map[string][]string) map[string]interface{} { + paramsMap := make(map[string]interface{}) + for k, v := range params { + paramsMap[k] = v } + return paramsMap +} - return newMap, nil +func mapStaticConfigLabels(labels map[string]string) map[string]interface{} { + labelsMap := make(map[string]interface{}) + for k, v := range labels { + labelsMap[k] = v + } + return labelsMap } type ArgusClient interface { @@ -205,21 +203,3 @@ func GetInstanceName(ctx context.Context, apiClient ArgusClient, instanceId, pro } return *resp.Name, nil } - -func GetDefaultCreateScrapeConfigPayload() *argus.CreateScrapeConfigPayload { - staticConfigs := []argus.CreateScrapeConfigPayloadStaticConfigsInner{ - { - Targets: utils.Ptr([]string{ - "url-target", - }), - }, - } - return &argus.CreateScrapeConfigPayload{ - JobName: utils.Ptr("default-name"), - MetricsPath: utils.Ptr("/metrics"), - Scheme: utils.Ptr("https"), - ScrapeInterval: utils.Ptr("5m"), - ScrapeTimeout: utils.Ptr("2m"), - StaticConfigs: &staticConfigs, - } -} diff --git a/internal/pkg/services/argus/utils/utils_test.go b/internal/pkg/services/argus/utils/utils_test.go index 2af7370dd..12d39423e 100644 --- a/internal/pkg/services/argus/utils/utils_test.go +++ b/internal/pkg/services/argus/utils/utils_test.go @@ -136,31 +136,6 @@ func fixtureUpdateScrapeConfigPayload(mods ...func(*argus.UpdateScrapeConfigPayl return payload } -func fixtureCreateScrapeConfigPayload(mods ...func(*argus.CreateScrapeConfigPayload)) *argus.CreateScrapeConfigPayload { - staticConfigs := []argus.CreateScrapeConfigPayloadStaticConfigsInner{ - { - Targets: utils.Ptr([]string{ - "url-target", - }), - }, - } - - payload := &argus.CreateScrapeConfigPayload{ - JobName: utils.Ptr("default-name"), - MetricsPath: utils.Ptr("/metrics"), - Scheme: utils.Ptr("https"), - ScrapeInterval: utils.Ptr("5m"), - ScrapeTimeout: utils.Ptr("2m"), - StaticConfigs: &staticConfigs, - } - - for _, mod := range mods { - mod(payload) - } - - return payload -} - type argusClientMocked struct { getInstanceFails bool getInstanceResp *argus.GetInstanceResponse @@ -380,8 +355,7 @@ func TestMapToUpdateScrapeConfigPayload(t *testing.T) { resp: &argus.GetScrapeConfigResponse{ Data: &argus.Job{}, }, - expectedPayload: &argus.UpdateScrapeConfigPayload{}, - isValid: true, + isValid: false, }, } @@ -409,24 +383,277 @@ func TestMapToUpdateScrapeConfigPayload(t *testing.T) { } } -func TestGetDefaultCreateScrapeConfigPayload(t *testing.T) { +func TestMapMetricsRelabelConfig(t *testing.T) { tests := []struct { - description string - expectedPayload *argus.CreateScrapeConfigPayload + description string + config *[]argus.MetricsRelabelConfig + expected *[]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner }{ { - description: "base case", - expectedPayload: fixtureCreateScrapeConfigPayload(), + description: "base case", + config: &[]argus.MetricsRelabelConfig{ + { + Action: utils.Ptr("replace"), + Modulus: utils.Int64Ptr(1), + Regex: utils.Ptr("regex"), + Replacement: utils.Ptr("replacement"), + Separator: utils.Ptr("separator"), + SourceLabels: utils.Ptr([]string{"sourceLabel", "sourceLabel2"}), + TargetLabel: utils.Ptr("targetLabel"), + }, + }, + expected: &[]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ + { + Action: utils.Ptr("replace"), + Modulus: utils.Float64Ptr(1.0), + Regex: utils.Ptr("regex"), + Replacement: utils.Ptr("replacement"), + Separator: utils.Ptr("separator"), + SourceLabels: utils.Ptr([]string{"sourceLabel", "sourceLabel2"}), + TargetLabel: utils.Ptr("targetLabel"), + }, + }, + }, + { + description: "empty data", + config: &[]argus.MetricsRelabelConfig{}, + expected: &[]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{}, + }, + { + description: "nil", + config: nil, + expected: nil, }, } for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - payload := GetDefaultCreateScrapeConfigPayload() + output := mapMetricsRelabelConfig(tt.config) - diff := cmp.Diff(*payload, *tt.expectedPayload, - cmp.AllowUnexported(*tt.expectedPayload), - ) + if tt.expected == nil && output == nil && tt.config == nil { + return + } + + diff := cmp.Diff(*output, *tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestMapStaticConfig(t *testing.T) { + tests := []struct { + description string + config *[]argus.StaticConfigs + expected *[]argus.UpdateScrapeConfigPayloadStaticConfigsInner + }{ + { + description: "base case", + config: &[]argus.StaticConfigs{ + { + Labels: &map[string]string{ + "label": "value", + "label2": "value2", + }, + Targets: &[]string{"target", "target2"}, + }, + }, + expected: &[]argus.UpdateScrapeConfigPayloadStaticConfigsInner{ + { + Labels: utils.Ptr(map[string]interface{}{ + "label": "value", + "label2": "value2", + }), + Targets: utils.Ptr([]string{"target", "target2"}), + }, + }, + }, + { + description: "empty data", + config: &[]argus.StaticConfigs{}, + expected: &[]argus.UpdateScrapeConfigPayloadStaticConfigsInner{}, + }, + { + description: "nil", + config: nil, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output := mapStaticConfig(tt.config) + + if tt.expected == nil && output == nil && tt.config == nil { + return + } + + diff := cmp.Diff(*output, *tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestMapBasicAuth(t *testing.T) { + tests := []struct { + description string + auth *argus.BasicAuth + expected *argus.CreateScrapeConfigPayloadBasicAuth + }{ + { + description: "base case", + auth: &argus.BasicAuth{ + Username: utils.Ptr("username"), + Password: utils.Ptr("password"), + }, + expected: &argus.CreateScrapeConfigPayloadBasicAuth{ + Username: utils.Ptr("username"), + Password: utils.Ptr("password"), + }, + }, + { + description: "empty data", + auth: &argus.BasicAuth{}, + expected: &argus.CreateScrapeConfigPayloadBasicAuth{}, + }, + { + description: "nil", + auth: nil, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output := mapBasicAuth(tt.auth) + + if tt.expected == nil && output == nil && tt.auth == nil { + return + } + + diff := cmp.Diff(*output, *tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestMapTlsConfig(t *testing.T) { + tests := []struct { + description string + config *argus.TLSConfig + expected *argus.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig + }{ + { + description: "base case", + config: &argus.TLSConfig{ + InsecureSkipVerify: utils.Ptr(true), + }, + expected: &argus.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{ + InsecureSkipVerify: utils.Ptr(true), + }, + }, + { + description: "empty data", + config: &argus.TLSConfig{}, + expected: &argus.CreateScrapeConfigPayloadHttpSdConfigsInnerOauth2TlsConfig{}, + }, + { + description: "nil", + config: nil, + expected: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output := mapTlsConfig(tt.config) + + if tt.expected == nil && output == nil && tt.config == nil { + return + } + + diff := cmp.Diff(*output, *tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestMapParams(t *testing.T) { + tests := []struct { + description string + params map[string][]string + expected map[string]interface{} + }{ + { + description: "base case", + params: map[string][]string{ + "key": {"value1", "value2"}, + "key2": {}, + }, + expected: map[string]interface{}{ + "key": []string{"value1", "value2"}, + "key2": []string{}, + }, + }, + { + description: "empty data", + params: map[string][]string{}, + expected: map[string]interface{}{}, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output := mapParams(tt.params) + + if tt.expected == nil && output == nil && tt.params == nil { + return + } + + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + }) + } +} + +func TestMapStaticConfigLabels(t *testing.T) { + tests := []struct { + description string + labels map[string]string + expected map[string]interface{} + }{ + { + description: "base case", + labels: map[string]string{ + "label": "value", + "label2": "value2", + }, + expected: map[string]interface{}{ + "label": "value", + "label2": "value2", + }, + }, + { + description: "empty data", + labels: map[string]string{}, + expected: map[string]interface{}{}, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output := mapStaticConfigLabels(tt.labels) + + diff := cmp.Diff(output, tt.expected) if diff != "" { t.Fatalf("Data does not match: %s", diff) } diff --git a/internal/pkg/utils/utils.go b/internal/pkg/utils/utils.go index 51678c654..f14ea7214 100644 --- a/internal/pkg/utils/utils.go +++ b/internal/pkg/utils/utils.go @@ -12,6 +12,18 @@ func Ptr[T any](v T) *T { return &v } +// Int64Ptr returns a pointer to an int64 +// Needed because the Ptr function only returns pointer to int +func Int64Ptr(i int64) *int64 { + return &i +} + +// Float64Ptr returns a pointer to a float64 +// Needed because the Ptr function only returns pointer to float +func Float64Ptr(f float64) *float64 { + return &f +} + // CmdHelp is used to explicitly set the Run function for non-leaf commands to the command help function, so that we can catch invalid commands // This is a workaround needed due to the open issue on the Cobra repo: https://github.com/spf13/cobra/issues/706 func CmdHelp(cmd *cobra.Command, _ []string) { @@ -26,3 +38,13 @@ func ValidateUUID(value string) error { } return nil } + +// ConvertInt64PToFloat64P converts an int64 pointer to a float64 pointer +// This function will return nil if the input is nil +func ConvertInt64PToFloat64P(i *int64) *float64 { + if i == nil { + return nil + } + f := float64(*i) + return &f +} diff --git a/internal/pkg/utils/utils_test.go b/internal/pkg/utils/utils_test.go new file mode 100644 index 000000000..3dc7b54a7 --- /dev/null +++ b/internal/pkg/utils/utils_test.go @@ -0,0 +1,45 @@ +package utils + +import "testing" + +func TestConvertInt64PToFloat64P(t *testing.T) { + tests := []struct { + name string + input *int64 + expected *float64 + }{ + { + name: "positive", + input: Int64Ptr(1), + expected: Float64Ptr(1.0), + }, + { + name: "negative", + input: Int64Ptr(-1), + expected: Float64Ptr(-1.0), + }, + { + name: "zero", + input: Int64Ptr(0), + expected: Float64Ptr(0.0), + }, + { + name: "nil", + input: nil, + expected: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + expected := ConvertInt64PToFloat64P(tt.input) + + if expected == nil && tt.expected == nil && tt.input == nil { + return + } + + if *expected != *tt.expected { + t.Errorf("ConvertInt64ToFloat64() = %v, want %v", *expected, *tt.expected) + } + }) + } +} From e0d7101fcd119e343f594867e0cfb1bb2f7628aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Thu, 18 Apr 2024 17:56:04 +0200 Subject: [PATCH 07/10] improve variable names --- internal/pkg/services/argus/utils/utils.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/pkg/services/argus/utils/utils.go b/internal/pkg/services/argus/utils/utils.go index a9fafdef0..d99199df3 100644 --- a/internal/pkg/services/argus/utils/utils.go +++ b/internal/pkg/services/argus/utils/utils.go @@ -119,7 +119,7 @@ func mapMetricsRelabelConfig(metricsRelabelConfigs *[]argus.MetricsRelabelConfig if metricsRelabelConfigs == nil { return nil } - configs := make([]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner, 0) + mappedConfigs := make([]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner, 0) for _, config := range *metricsRelabelConfigs { mappedConfig := argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ Action: config.Action, @@ -130,16 +130,16 @@ func mapMetricsRelabelConfig(metricsRelabelConfigs *[]argus.MetricsRelabelConfig SourceLabels: config.SourceLabels, TargetLabel: config.TargetLabel, } - configs = append(configs, mappedConfig) + mappedConfigs = append(mappedConfigs, mappedConfig) } - return &configs + return &mappedConfigs } func mapStaticConfig(staticConfigs *[]argus.StaticConfigs) *[]argus.UpdateScrapeConfigPayloadStaticConfigsInner { if staticConfigs == nil { return nil } - configs := make([]argus.UpdateScrapeConfigPayloadStaticConfigsInner, 0) + mappedConfigs := make([]argus.UpdateScrapeConfigPayloadStaticConfigsInner, 0) for _, config := range *staticConfigs { var labels *map[string]interface{} if config.Labels != nil { @@ -149,10 +149,10 @@ func mapStaticConfig(staticConfigs *[]argus.StaticConfigs) *[]argus.UpdateScrape Labels: labels, Targets: config.Targets, } - configs = append(configs, mappedConfig) + mappedConfigs = append(mappedConfigs, mappedConfig) } - return &configs + return &mappedConfigs } func mapBasicAuth(basicAuth *argus.BasicAuth) *argus.CreateScrapeConfigPayloadBasicAuth { From b1f35a706525eda6d3f7a7443907ff6dab852a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Fri, 19 Apr 2024 09:39:43 +0200 Subject: [PATCH 08/10] update command help --- docs/stackit_argus_scrape-configs_generate-payload.md | 4 ++-- .../argus/scrape-configs/generate-payload/generate_payload.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/stackit_argus_scrape-configs_generate-payload.md b/docs/stackit_argus_scrape-configs_generate-payload.md index b2594750d..fafe0a559 100644 --- a/docs/stackit_argus_scrape-configs_generate-payload.md +++ b/docs/stackit_argus_scrape-configs_generate-payload.md @@ -7,8 +7,8 @@ Generates a payload to create/update Scrape Configurations for an Argus instance Generates a JSON payload with values to be used as --payload input for Scrape Configurations creation or update. This command can be used to generate a payload to update an existing Scrape Config job or to create a new Scrape Config job. To update an existing Scrape Config job, provide the job name and the instance ID of the Argus instance. -To obtain a default payload to create a new Scrape Config job, run the command with no flags. Note that some fields, like the url of the target, are required and must be provided in the payload. -Note that the default values provided for jobName, metricsPath and targets should be changed to make sense for the use case inteded. +To obtain a default payload to create a new Scrape Config job, run the command with no flags. +Note that the default values provided, particularly for the job name, the metrics path and URL of the targets, should be changed to your use case. See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure. diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go index 68d7f0117..8e04bd681 100644 --- a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go @@ -36,8 +36,8 @@ func NewCmd(p *print.Printer) *cobra.Command { "Generates a JSON payload with values to be used as --payload input for Scrape Configurations creation or update.", "This command can be used to generate a payload to update an existing Scrape Config job or to create a new Scrape Config job.", "To update an existing Scrape Config job, provide the job name and the instance ID of the Argus instance.", - "To obtain a default payload to create a new Scrape Config job, run the command with no flags. Note that some fields, like the url of the target, are required and must be provided in the payload.", - "Note that the default values provided for jobName, metricsPath and targets should be changed to make sense for the use case inteded.", + "To obtain a default payload to create a new Scrape Config job, run the command with no flags.", + "Note that the default values provided, particularly for the job name, the metrics path and URL of the targets, should be changed to your use case.", "See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure.", ), Args: args.NoArgs, From 7613a43675cb162cb228cc3fc95b9859cce506a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Fri, 19 Apr 2024 12:29:48 +0200 Subject: [PATCH 09/10] address PR comments --- .../scrape-configs/generate-payload/generate_payload.go | 2 +- internal/pkg/services/argus/utils/utils.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go index 8e04bd681..d79b27b9b 100644 --- a/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go +++ b/internal/cmd/argus/scrape-configs/generate-payload/generate_payload.go @@ -37,7 +37,7 @@ func NewCmd(p *print.Printer) *cobra.Command { "This command can be used to generate a payload to update an existing Scrape Config job or to create a new Scrape Config job.", "To update an existing Scrape Config job, provide the job name and the instance ID of the Argus instance.", "To obtain a default payload to create a new Scrape Config job, run the command with no flags.", - "Note that the default values provided, particularly for the job name, the metrics path and URL of the targets, should be changed to your use case.", + "Note that some of the default values provided, such as the job name, the metrics path and URL of the targets, should be adapted to your use case.", "See https://docs.api.stackit.cloud/documentation/argus/version/v1#tag/scrape-config/operation/v1_projects_instances_scrapeconfigs_create for information regarding the payload structure.", ), Args: args.NoArgs, diff --git a/internal/pkg/services/argus/utils/utils.go b/internal/pkg/services/argus/utils/utils.go index d99199df3..e26f66488 100644 --- a/internal/pkg/services/argus/utils/utils.go +++ b/internal/pkg/services/argus/utils/utils.go @@ -109,7 +109,7 @@ func MapToUpdateScrapeConfigPayload(resp *argus.GetScrapeConfigResponse) (*argus } if payload == (argus.UpdateScrapeConfigPayload{}) { - return nil, fmt.Errorf("the provided Argus Scrape Sonfig payload is empty") + return nil, fmt.Errorf("the provided Argus scrape config payload is empty") } return &payload, nil @@ -119,7 +119,7 @@ func mapMetricsRelabelConfig(metricsRelabelConfigs *[]argus.MetricsRelabelConfig if metricsRelabelConfigs == nil { return nil } - mappedConfigs := make([]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner, 0) + var mappedConfigs []argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner for _, config := range *metricsRelabelConfigs { mappedConfig := argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{ Action: config.Action, @@ -139,7 +139,7 @@ func mapStaticConfig(staticConfigs *[]argus.StaticConfigs) *[]argus.UpdateScrape if staticConfigs == nil { return nil } - mappedConfigs := make([]argus.UpdateScrapeConfigPayloadStaticConfigsInner, 0) + var mappedConfigs []argus.UpdateScrapeConfigPayloadStaticConfigsInner for _, config := range *staticConfigs { var labels *map[string]interface{} if config.Labels != nil { From 34efc7caa4c3560fcefb65820d1b7942798f7d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diogo=20Ferr=C3=A3o?= Date: Fri, 19 Apr 2024 12:44:16 +0200 Subject: [PATCH 10/10] fix utils testing --- internal/pkg/services/argus/utils/utils_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/pkg/services/argus/utils/utils_test.go b/internal/pkg/services/argus/utils/utils_test.go index 12d39423e..453498669 100644 --- a/internal/pkg/services/argus/utils/utils_test.go +++ b/internal/pkg/services/argus/utils/utils_test.go @@ -417,7 +417,7 @@ func TestMapMetricsRelabelConfig(t *testing.T) { { description: "empty data", config: &[]argus.MetricsRelabelConfig{}, - expected: &[]argus.CreateScrapeConfigPayloadMetricsRelabelConfigsInner{}, + expected: nil, }, { description: "nil", @@ -430,7 +430,7 @@ func TestMapMetricsRelabelConfig(t *testing.T) { t.Run(tt.description, func(t *testing.T) { output := mapMetricsRelabelConfig(tt.config) - if tt.expected == nil && output == nil && tt.config == nil { + if tt.expected == nil && output == nil || *output == nil { return } @@ -472,7 +472,7 @@ func TestMapStaticConfig(t *testing.T) { { description: "empty data", config: &[]argus.StaticConfigs{}, - expected: &[]argus.UpdateScrapeConfigPayloadStaticConfigsInner{}, + expected: nil, }, { description: "nil", @@ -485,7 +485,7 @@ func TestMapStaticConfig(t *testing.T) { t.Run(tt.description, func(t *testing.T) { output := mapStaticConfig(tt.config) - if tt.expected == nil && output == nil && tt.config == nil { + if tt.expected == nil && (output == nil || *output == nil) { return }