diff --git a/cmd/generate/main.go b/cmd/generate/main.go index 3647c60f2..946d324bf 100644 --- a/cmd/generate/main.go +++ b/cmd/generate/main.go @@ -82,6 +82,30 @@ This supports a glob format. Examples: Category: "Grafana", EnvVars: []string{"TFGEN_GRAFANA_AUTH"}, }, + &cli.StringFlag{ + Name: "synthetic-monitoring-url", + Usage: "URL of the Synthetic Monitoring instance to generate resources from", + Category: "Grafana", + EnvVars: []string{"TFGEN_SYNTHETIC_MONITORING_URL"}, + }, + &cli.StringFlag{ + Name: "synthetic-monitoring-access-token", + Usage: "API token for the Synthetic Monitoring instance", + Category: "Grafana", + EnvVars: []string{"TFGEN_SYNTHETIC_MONITORING_ACCESS_TOKEN"}, + }, + &cli.StringFlag{ + Name: "oncall-url", + Usage: "URL of the OnCall instance to generate resources from", + Category: "Grafana", + EnvVars: []string{"TFGEN_ONCALL_URL"}, + }, + &cli.StringFlag{ + Name: "oncall-access-token", + Usage: "API token for the OnCall instance", + Category: "Grafana", + EnvVars: []string{"TFGEN_ONCALL_ACCESS_TOKEN"}, + }, // Grafana Cloud flags &cli.StringFlag{ @@ -132,8 +156,12 @@ func parseFlags(ctx *cli.Context) (*generate.Config, error) { Format: generate.OutputFormat(ctx.String("output-format")), ProviderVersion: ctx.String("terraform-provider-version"), Grafana: &generate.GrafanaConfig{ - URL: ctx.String("grafana-url"), - Auth: ctx.String("grafana-auth"), + URL: ctx.String("grafana-url"), + Auth: ctx.String("grafana-auth"), + SMURL: ctx.String("synthetic-monitoring-url"), + SMAccessToken: ctx.String("synthetic-monitoring-access-token"), + OnCallURL: ctx.String("oncall-url"), + OnCallAccessToken: ctx.String("oncall-access-token"), }, Cloud: &generate.CloudConfig{ AccessPolicyToken: ctx.String("cloud-access-policy-token"), @@ -152,7 +180,7 @@ func parseFlags(ctx *cli.Context) (*generate.Config, error) { err := newFlagValidations(). atLeastOne("grafana-url", "cloud-access-policy-token"). conflicting( - []string{"grafana-url", "grafana-auth"}, + []string{"grafana-url", "grafana-auth", "synthetic-monitoring-url", "synthetic-monitoring-access-token", "oncall-url", "oncall-access-token"}, []string{"cloud-access-policy-token", "cloud-org", "cloud-create-stack-service-account", "cloud-stack-service-account-name"}, ). requiredWhenSet("grafana-url", "grafana-auth"). diff --git a/internal/resources/oncall/common_lister.go b/internal/resources/oncall/common_lister.go new file mode 100644 index 000000000..edf5734c0 --- /dev/null +++ b/internal/resources/oncall/common_lister.go @@ -0,0 +1,34 @@ +package oncall + +import ( + "context" + "fmt" + + onCallAPI "github.com/grafana/amixr-api-go-client" + "github.com/grafana/terraform-provider-grafana/v3/internal/common" +) + +type listerFunc func(client *onCallAPI.Client, listOptions onCallAPI.ListOptions) (ids []string, nextPage *string, err error) + +// oncallListerFunction is a helper function that wraps a lister function be used more easily in oncall resources. +func oncallListerFunction(listerFunc listerFunc) common.ResourceListIDsFunc { + return func(ctx context.Context, client *common.Client, data any) ([]string, error) { + if client.OnCallClient == nil { + return nil, fmt.Errorf("client not configured for Grafana OnCall API") + } + ids := []string{} + page := 1 + for { + newIDs, nextPage, err := listerFunc(client.OnCallClient, onCallAPI.ListOptions{Page: page}) + if err != nil { + return nil, err + } + ids = append(ids, newIDs...) + if nextPage == nil { + break + } + page++ + } + return ids, nil + } +} diff --git a/internal/resources/oncall/resource_escalation.go b/internal/resources/oncall/resource_escalation.go index 911e9fa12..56a541ba2 100644 --- a/internal/resources/oncall/resource_escalation.go +++ b/internal/resources/oncall/resource_escalation.go @@ -225,7 +225,18 @@ func resourceEscalation() *common.Resource { "grafana_oncall_escalation", resourceID, schema, - ) + ).WithLister(oncallListerFunction(listEscalations)) +} + +func listEscalations(client *onCallAPI.Client, listOptions onCallAPI.ListOptions) (ids []string, nextPage *string, err error) { + resp, _, err := client.Escalations.ListEscalations(&onCallAPI.ListEscalationOptions{ListOptions: listOptions}) + if err != nil { + return nil, nil, err + } + for _, i := range resp.Escalations { + ids = append(ids, i.ID) + } + return ids, resp.Next, nil } func resourceEscalationCreate(ctx context.Context, d *schema.ResourceData, client *onCallAPI.Client) diag.Diagnostics { diff --git a/internal/resources/oncall/resource_escalation_chain.go b/internal/resources/oncall/resource_escalation_chain.go index 8f33fb215..86cf4593a 100644 --- a/internal/resources/oncall/resource_escalation_chain.go +++ b/internal/resources/oncall/resource_escalation_chain.go @@ -43,7 +43,18 @@ func resourceEscalationChain() *common.Resource { "grafana_oncall_escalation_chain", resourceID, schema, - ) + ).WithLister(oncallListerFunction(listEscalationChains)) +} + +func listEscalationChains(client *onCallAPI.Client, listOptions onCallAPI.ListOptions) (ids []string, nextPage *string, err error) { + resp, _, err := client.EscalationChains.ListEscalationChains(&onCallAPI.ListEscalationChainOptions{ListOptions: listOptions}) + if err != nil { + return nil, nil, err + } + for _, i := range resp.EscalationChains { + ids = append(ids, i.ID) + } + return ids, resp.Next, nil } func resourceEscalationChainCreate(ctx context.Context, d *schema.ResourceData, client *onCallAPI.Client) diag.Diagnostics { diff --git a/internal/resources/oncall/resource_integration.go b/internal/resources/oncall/resource_integration.go index cb025ab64..c6c54d581 100644 --- a/internal/resources/oncall/resource_integration.go +++ b/internal/resources/oncall/resource_integration.go @@ -240,7 +240,18 @@ func resourceIntegration() *common.Resource { "grafana_oncall_integration", resourceID, schema, - ) + ).WithLister(oncallListerFunction(listIntegrations)) +} + +func listIntegrations(client *onCallAPI.Client, listOptions onCallAPI.ListOptions) (ids []string, nextPage *string, err error) { + resp, _, err := client.Integrations.ListIntegrations(&onCallAPI.ListIntegrationOptions{ListOptions: listOptions}) + if err != nil { + return nil, nil, err + } + for _, i := range resp.Integrations { + ids = append(ids, i.ID) + } + return ids, resp.Next, nil } func onCallTemplate(description string, hasMessage, hasImage bool) *schema.Schema { diff --git a/internal/resources/oncall/resource_outgoing_webhook.go b/internal/resources/oncall/resource_outgoing_webhook.go index abb292cfe..a3eb7ae21 100644 --- a/internal/resources/oncall/resource_outgoing_webhook.go +++ b/internal/resources/oncall/resource_outgoing_webhook.go @@ -108,7 +108,18 @@ func resourceOutgoingWebhook() *common.Resource { "grafana_oncall_outgoing_webhook", resourceID, schema, - ) + ).WithLister(oncallListerFunction(listWebhooks)) +} + +func listWebhooks(client *onCallAPI.Client, listOptions onCallAPI.ListOptions) (ids []string, nextPage *string, err error) { + resp, _, err := client.Webhooks.ListWebhooks(&onCallAPI.ListWebhookOptions{ListOptions: listOptions}) + if err != nil { + return nil, nil, err + } + for _, i := range resp.Webhooks { + ids = append(ids, i.ID) + } + return ids, resp.Next, nil } func resourceOutgoingWebhookCreate(ctx context.Context, d *schema.ResourceData, client *onCallAPI.Client) diag.Diagnostics { diff --git a/internal/resources/oncall/resource_route.go b/internal/resources/oncall/resource_route.go index f2e6eeef7..e2e99d1e1 100644 --- a/internal/resources/oncall/resource_route.go +++ b/internal/resources/oncall/resource_route.go @@ -135,7 +135,18 @@ func resourceRoute() *common.Resource { "grafana_oncall_route", resourceID, schema, - ) + ).WithLister(oncallListerFunction(listRoutes)) +} + +func listRoutes(client *onCallAPI.Client, listOptions onCallAPI.ListOptions) (ids []string, nextPage *string, err error) { + resp, _, err := client.Routes.ListRoutes(&onCallAPI.ListRouteOptions{ListOptions: listOptions}) + if err != nil { + return nil, nil, err + } + for _, i := range resp.Routes { + ids = append(ids, i.ID) + } + return ids, resp.Next, nil } func resourceRouteCreate(ctx context.Context, d *schema.ResourceData, client *onCallAPI.Client) diag.Diagnostics { diff --git a/internal/resources/oncall/resource_schedule.go b/internal/resources/oncall/resource_schedule.go index 30d18b23d..4e4f75c43 100644 --- a/internal/resources/oncall/resource_schedule.go +++ b/internal/resources/oncall/resource_schedule.go @@ -106,7 +106,18 @@ func resourceSchedule() *common.Resource { "grafana_oncall_schedule", resourceID, schema, - ) + ).WithLister(oncallListerFunction(listSchedules)) +} + +func listSchedules(client *onCallAPI.Client, listOptions onCallAPI.ListOptions) (ids []string, nextPage *string, err error) { + resp, _, err := client.Schedules.ListSchedules(&onCallAPI.ListScheduleOptions{ListOptions: listOptions}) + if err != nil { + return nil, nil, err + } + for _, i := range resp.Schedules { + ids = append(ids, i.ID) + } + return ids, resp.Next, nil } func resourceScheduleCreate(ctx context.Context, d *schema.ResourceData, client *onCallAPI.Client) diag.Diagnostics { diff --git a/internal/resources/oncall/resource_shift.go b/internal/resources/oncall/resource_shift.go index 9918083e0..49525c1b6 100644 --- a/internal/resources/oncall/resource_shift.go +++ b/internal/resources/oncall/resource_shift.go @@ -187,7 +187,18 @@ func resourceOnCallShift() *common.Resource { "grafana_oncall_on_call_shift", resourceID, schema, - ) + ).WithLister(oncallListerFunction(listShifts)) +} + +func listShifts(client *onCallAPI.Client, listOptions onCallAPI.ListOptions) (ids []string, nextPage *string, err error) { + resp, _, err := client.OnCallShifts.ListOnCallShifts(&onCallAPI.ListOnCallShiftOptions{ListOptions: listOptions}) + if err != nil { + return nil, nil, err + } + for _, i := range resp.OnCallShifts { + ids = append(ids, i.ID) + } + return ids, resp.Next, nil } func resourceOnCallShiftCreate(ctx context.Context, d *schema.ResourceData, client *onCallAPI.Client) diag.Diagnostics { diff --git a/pkg/generate/cloud.go b/pkg/generate/cloud.go index 958a90975..11ad91820 100644 --- a/pkg/generate/cloud.go +++ b/pkg/generate/cloud.go @@ -26,6 +26,9 @@ type stack struct { managementKey string smURL string smToken string + + onCallURL string + onCallToken string } func generateCloudResources(ctx context.Context, cfg *Config) ([]stack, error) { diff --git a/pkg/generate/config.go b/pkg/generate/config.go index bec505bb5..0bc11f810 100644 --- a/pkg/generate/config.go +++ b/pkg/generate/config.go @@ -13,8 +13,12 @@ const ( var OutputFormats = []OutputFormat{OutputFormatJSON, OutputFormatHCL, OutputFormatCrossplane} type GrafanaConfig struct { - URL string - Auth string + URL string + Auth string + SMURL string + SMAccessToken string + OnCallURL string + OnCallAccessToken string } type CloudConfig struct { diff --git a/pkg/generate/generate.go b/pkg/generate/generate.go index 070e36a8a..a80a8155d 100644 --- a/pkg/generate/generate.go +++ b/pkg/generate/generate.go @@ -80,8 +80,10 @@ func Generate(ctx context.Context, cfg *Config) error { stack := stack{ managementKey: cfg.Grafana.Auth, url: cfg.Grafana.URL, - smToken: "", - smURL: "", + smToken: cfg.Grafana.SMAccessToken, + smURL: cfg.Grafana.SMURL, + onCallToken: cfg.Grafana.OnCallAccessToken, + onCallURL: cfg.Grafana.OnCallURL, } if err := generateGrafanaResources(ctx, cfg, stack, true); err != nil { return err diff --git a/pkg/generate/grafana.go b/pkg/generate/grafana.go index 6a23fd158..770a13aa6 100644 --- a/pkg/generate/grafana.go +++ b/pkg/generate/grafana.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/terraform-provider-grafana/v3/internal/resources/grafana" "github.com/grafana/terraform-provider-grafana/v3/internal/resources/machinelearning" + "github.com/grafana/terraform-provider-grafana/v3/internal/resources/oncall" "github.com/grafana/terraform-provider-grafana/v3/internal/resources/slo" "github.com/grafana/terraform-provider-grafana/v3/internal/resources/syntheticmonitoring" "github.com/grafana/terraform-provider-grafana/v3/pkg/provider" @@ -28,6 +29,14 @@ func generateGrafanaResources(ctx context.Context, cfg *Config, stack stack, gen providerBlock := hclwrite.NewBlock("provider", []string{"grafana"}) providerBlock.Body().SetAttributeValue("url", cty.StringVal(stack.url)) providerBlock.Body().SetAttributeValue("auth", cty.StringVal(stack.managementKey)) + if stack.smToken != "" && stack.smURL != "" { + providerBlock.Body().SetAttributeValue("sm_url", cty.StringVal(stack.smURL)) + providerBlock.Body().SetAttributeValue("sm_access_token", cty.StringVal(stack.smToken)) + } + if stack.onCallToken != "" && stack.onCallURL != "" { + providerBlock.Body().SetAttributeValue("oncall_url", cty.StringVal(stack.onCallURL)) + providerBlock.Body().SetAttributeValue("oncall_access_token", cty.StringVal(stack.onCallToken)) + } if stack.name != "" { providerBlock.Body().SetAttributeValue("alias", cty.StringVal(stack.name)) } @@ -44,11 +53,16 @@ func generateGrafanaResources(ctx context.Context, cfg *Config, stack stack, gen URL: types.StringValue(stack.url), Auth: types.StringValue(stack.managementKey), } - if stack.smToken != "" { + resources := grafana.Resources + if stack.smToken != "" && stack.smURL != "" { + resources = append(resources, syntheticmonitoring.Resources...) + config.SMURL = types.StringValue(stack.smURL) config.SMAccessToken = types.StringValue(stack.smToken) } - if stack.smURL != "" { - config.SMURL = types.StringValue(stack.smURL) + if stack.onCallToken != "" && stack.onCallURL != "" { + resources = append(resources, oncall.Resources...) + config.OncallAccessToken = types.StringValue(stack.onCallToken) + config.OncallURL = types.StringValue(stack.onCallURL) } if err := config.SetDefaults(); err != nil { return err @@ -59,11 +73,9 @@ func generateGrafanaResources(ctx context.Context, cfg *Config, stack stack, gen return err } - resources := grafana.Resources if strings.HasPrefix(stack.name, "stack-") { // TODO: is cloud. Find a better way to detect this resources = append(resources, slo.Resources...) resources = append(resources, machinelearning.Resources...) - resources = append(resources, syntheticmonitoring.Resources...) } if err := generateImportBlocks(ctx, client, listerData, resources, cfg, stack.name); err != nil { return err