From 9703c1aca5ef30733362427cc206ea6e062ac802 Mon Sep 17 00:00:00 2001 From: Jonas Schlecht Date: Mon, 31 Aug 2026 13:47:06 +0200 Subject: [PATCH 1/2] feat(automation): onboard automation templates list datasource Relates to STACKITTPR-790 --- docs/data-sources/automation_templates.md | 49 +++++ docs/index.md | 1 + .../data-source.tf | 4 + go.mod | 2 + go.sum | 4 + stackit/internal/core/core.go | 1 + .../automation/templates/datasource.go | 197 ++++++++++++++++++ .../automation/templates/datasource_test.go | 113 ++++++++++ .../services/automation/utils/util.go | 32 +++ .../services/automation/utils/util_test.go | 95 +++++++++ stackit/provider.go | 9 + 11 files changed, 507 insertions(+) create mode 100644 docs/data-sources/automation_templates.md create mode 100644 examples/data-sources/stackit_automation_templates/data-source.tf create mode 100644 stackit/internal/services/automation/templates/datasource.go create mode 100644 stackit/internal/services/automation/templates/datasource_test.go create mode 100644 stackit/internal/services/automation/utils/util.go create mode 100644 stackit/internal/services/automation/utils/util_test.go diff --git a/docs/data-sources/automation_templates.md b/docs/data-sources/automation_templates.md new file mode 100644 index 000000000..ec49e9773 --- /dev/null +++ b/docs/data-sources/automation_templates.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_automation_templates Data Source - stackit" +subcategory: "" +description: |- + Automation templates datasource schema. + ~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources. +--- + +# stackit_automation_templates (Data Source) + +Automation templates datasource schema. + +~> This datasource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. + +## Example Usage + +```terraform +data "stackit_automation_templates" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + # region is taken from the provider configuration +} +``` + + +## Schema + +### Required + +- `project_id` (String) STACKIT project ID. + +### Optional + +- `region` (String) Automation templates data source region. If undefined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal data source ID, structured as "`project_id`,`region`". +- `templates` (Attributes List) List of available templates. (see [below for nested schema](#nestedatt--templates)) + + +### Nested Schema for `templates` + +Read-Only: + +- `create_time` (String) Create timestamp of the template. +- `description` (String) Description of the template. +- `id` (String) Template ID. +- `name` (String) Name of the template. diff --git a/docs/index.md b/docs/index.md index 970764474..950d9cd9d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -175,6 +175,7 @@ See this [example](https://professional-service.git.onstackit.cloud/professional - `alb_custom_endpoint` (String) Custom endpoint for the Application Load Balancer service - `alb_waf_custom_endpoint` (String) Custom endpoint for the Application Load Balancer Web Application Firewall service - `authorization_custom_endpoint` (String) Custom endpoint for the Membership service +- `automation_custom_endpoint` (String) Custom endpoint for the Automation service - `cdn_custom_endpoint` (String) Custom endpoint for the CDN service - `credentials_path` (String) Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`. - `default_region` (String) Region will be used as the default location for regional services. Not all services require a region, some are global diff --git a/examples/data-sources/stackit_automation_templates/data-source.tf b/examples/data-sources/stackit_automation_templates/data-source.tf new file mode 100644 index 000000000..1d181f13f --- /dev/null +++ b/examples/data-sources/stackit_automation_templates/data-source.tf @@ -0,0 +1,4 @@ +data "stackit_automation_templates" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + # region is taken from the provider configuration +} diff --git a/go.mod b/go.mod index 7a1ceafbc..58d7f25af 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/core v0.26.0 github.com/stackitcloud/stackit-sdk-go/services/alb v0.17.1 github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.13.2 + github.com/stackitcloud/stackit-sdk-go/services/automation v0.1.0 github.com/stackitcloud/stackit-sdk-go/services/cdn v1.20.0 github.com/stackitcloud/stackit-sdk-go/services/certificates v1.9.1 github.com/stackitcloud/stackit-sdk-go/services/dns v0.22.1 @@ -58,6 +59,7 @@ require ( require ( github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/kr/text v0.2.0 // indirect + github.com/stackitcloud/stackit-sdk-go/experimental v0.1.0 // indirect github.com/stretchr/testify v1.11.1 // indirect ) diff --git a/go.sum b/go.sum index aa95d0973..2f1d5cba0 100644 --- a/go.sum +++ b/go.sum @@ -155,12 +155,16 @@ github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnB github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10cz4l0KM2L6hqYBH2QA= github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA= +github.com/stackitcloud/stackit-sdk-go/experimental v0.1.0 h1:VzFEk54aKXTYCxx4P0g5iviGQ+1LJvpnVeHfhunNjLQ= +github.com/stackitcloud/stackit-sdk-go/experimental v0.1.0/go.mod h1:P565RBOFE21j+J2vLxdw4qaJcJC2p0BH+Qqqp4E7i7I= github.com/stackitcloud/stackit-sdk-go/services/alb v0.17.1 h1:YOMgUO/0/FZPxOmkKIbEIjzpzJ2VFhfPn3ErID9qC40= github.com/stackitcloud/stackit-sdk-go/services/alb v0.17.1/go.mod h1:eK6oRB5Tmpt6KbXQ4UYBGg2LgW5bPtVoncL9E8JSRww= github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.13.2 h1:zY0ArhTg++8oOAlytCRck3u41ZakAvU74ZkwBuMnipg= github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.13.2/go.mod h1:4M9G1I64kZwlXO32ZoIpt0GAN4SpZ1SYerwCVVIBGoE= github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.3 h1:YEuymF48gnBNqBsraBN5pycBDEyXcFYy/2bCdF+H3eY= github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.3/go.mod h1:T/JF25XGJ3GqER/1L2N//DgY8x5tY7gA3N+/0nvmOWY= +github.com/stackitcloud/stackit-sdk-go/services/automation v0.1.0 h1:25T6OXF+rahWSw+Hhc97CnIkFPzI4yge0fgR5lx4/pk= +github.com/stackitcloud/stackit-sdk-go/services/automation v0.1.0/go.mod h1:vLus9q8y75L+viMJej92c5gF9BD8IDVzm5Q/ae9XX1c= github.com/stackitcloud/stackit-sdk-go/services/cdn v1.20.0 h1:p/8f/+Z+OtqX0cau2CqkidjfO3FkHPc7ymdrF1zv7UY= github.com/stackitcloud/stackit-sdk-go/services/cdn v1.20.0/go.mod h1:MHB1N3EQ9GuAduAQoNS+gb1MjrWJieszbpOso9TQv5s= github.com/stackitcloud/stackit-sdk-go/services/certificates v1.9.1 h1:tf9+nPZ/1Wq7jRAnOzvgBTAS038GlX4ws5Ndw1LdCsc= diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index 90ea4c0bf..b022cbd41 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -42,6 +42,7 @@ type ProviderData struct { ALBCustomEndpoint string AlbWafCustomEndpoint string AuthorizationCustomEndpoint string + AutomationCustomEndpoint string CdnCustomEndpoint string DnsCustomEndpoint string DremioCustomEndpoint string diff --git a/stackit/internal/services/automation/templates/datasource.go b/stackit/internal/services/automation/templates/datasource.go new file mode 100644 index 000000000..a66e49fe3 --- /dev/null +++ b/stackit/internal/services/automation/templates/datasource.go @@ -0,0 +1,197 @@ +package templates + +import ( + "context" + "fmt" + "slices" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/experimental/paginate" + automation "github.com/stackitcloud/stackit-sdk-go/services/automation/v1betaapi" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" + automationUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/automation/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = new(templatesDataSource) + _ datasource.DataSourceWithConfigure = new(templatesDataSource) +) + +type model struct { + ID types.String `tfsdk:"id"` + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + Templates []template `tfsdk:"templates"` +} + +type template struct { + Id types.String `tfsdk:"id"` + CreateTime types.String `tfsdk:"create_time"` + Description types.String `tfsdk:"description"` + Name types.String `tfsdk:"name"` +} + +type templatesDataSource struct { + client *automation.APIClient + providerData core.ProviderData +} + +func NewAutomationTemplatesDataSource() datasource.DataSource { + return new(templatesDataSource) +} + +func (d *templatesDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_automation_templates" +} + +func (d *templatesDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + var ok bool + d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + features.CheckBetaResourcesEnabled(ctx, &d.providerData, &resp.Diagnostics, "stackit_automation_templates", core.Datasource) + if resp.Diagnostics.HasError() { + return + } + + apiClient := automationUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + d.client = apiClient + tflog.Info(ctx, "Automation templates client configured") +} + +func (d *templatesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: features.AddBetaDescription("Automation templates datasource schema.", core.Datasource), + Description: "Automation templates datasource schema.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Terraform's internal data source ID, structured as \"`project_id`,`region`\".", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "STACKIT project ID.", + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: "Automation templates data source region. If undefined, the provider region is used.", + Optional: true, + Computed: true, + }, + "templates": schema.ListNestedAttribute{ + Description: "List of available templates.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Template ID.", + Computed: true, + }, + "name": schema.StringAttribute{ + Description: "Name of the template.", + Computed: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the template.", + Computed: true, + }, + "create_time": schema.StringAttribute{ + Description: "Create timestamp of the template.", + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *templatesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model model + resp.Diagnostics.Append(req.Config.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + projectId := model.ProjectId.ValueString() + region := d.providerData.GetRegionWithOverride(model.Region) + model.Region = types.StringValue(region) + ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "region": region, + }) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + templatesReq := d.client.DefaultAPI.ListVolumeTemplates(ctx, projectId, region) + templates, err := paginate.All(templatesReq) + + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Reading templates", fmt.Sprintf("Calling ListVolumeTemplates: %v", err)) + return + } + if templates == nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Reading templates", "ListVolumeTemplates returned an empty response") + return + } + + ctx = core.LogResponse(ctx) + + if err := mapFields(templates, &model); err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Reading templates", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, model)...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "Automation templates read") +} + +func mapFields(templates []automation.Template, m *model) error { + if templates == nil { + return fmt.Errorf("nil response") + } + if m == nil { + return fmt.Errorf("nil model") + } + + m.ID = utils.BuildInternalTerraformId(m.ProjectId.ValueString(), m.Region.ValueString()) + m.Templates = make([]template, 0, len(templates)) + + slices.SortFunc(templates, func(a, b automation.Template) int { + return strings.Compare(a.Id, b.Id) + }) + + for _, respTemplate := range templates { + m.Templates = append(m.Templates, template{ + Id: types.StringValue(respTemplate.Id), + Name: types.StringValue(respTemplate.Name), + Description: types.StringValue(respTemplate.Description), + CreateTime: types.StringValue(respTemplate.CreateTime.Format(time.RFC3339)), + }) + } + return nil +} diff --git a/stackit/internal/services/automation/templates/datasource_test.go b/stackit/internal/services/automation/templates/datasource_test.go new file mode 100644 index 000000000..5369e80ef --- /dev/null +++ b/stackit/internal/services/automation/templates/datasource_test.go @@ -0,0 +1,113 @@ +package templates + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types" + automation "github.com/stackitcloud/stackit-sdk-go/services/automation/v1betaapi" +) + +var testTimestampValue = "2006-01-02T15:04:05Z" + +func testTimestamp() time.Time { + timestamp, _ := time.Parse(time.RFC3339, testTimestampValue) + return timestamp +} + +func TestMapFields(t *testing.T) { + tests := []struct { + name string + recievedTemplates []automation.Template + model *model + expected *model + valid bool + }{ + { + name: "maps and sorts templates", + recievedTemplates: []automation.Template{ + { + Id: "template-2", + Description: "second", + Name: "second template", + CreateTime: testTimestamp(), + AdditionalProperties: map[string]interface{}{"test": "key"}, + }, + { + Id: "template-1", + Description: "first", + Name: "first template", + CreateTime: testTimestamp(), + AdditionalProperties: map[string]interface{}{"test": "key"}, + }, + }, + model: &model{ + ProjectId: types.StringValue("project-id"), + Region: types.StringValue("eu01"), + }, + expected: &model{ + ID: types.StringValue("project-id,eu01"), + ProjectId: types.StringValue("project-id"), + Region: types.StringValue("eu01"), + Templates: []template{ + { + Id: types.StringValue("template-1"), + Description: types.StringValue("first"), + CreateTime: types.StringValue(testTimestampValue), + Name: types.StringValue("first template"), + }, + { + Id: types.StringValue("template-2"), + Description: types.StringValue("second"), + CreateTime: types.StringValue(testTimestampValue), + Name: types.StringValue("second template"), + }, + }, + }, + valid: true, + }, + { + name: "maps empty response", + recievedTemplates: []automation.Template{}, + model: &model{ + ProjectId: types.StringValue("project-id"), + Region: types.StringValue("eu01"), + }, + expected: &model{ + ID: types.StringValue("project-id,eu01"), + ProjectId: types.StringValue("project-id"), + Region: types.StringValue("eu01"), + Templates: []template{}, + }, + valid: true, + }, + { + name: "rejects nil response", + model: &model{ + ProjectId: types.StringValue("project-id"), + Region: types.StringValue("eu01"), + }, + valid: false, + }, + { + name: "rejects nil model", + recievedTemplates: []automation.Template{}, + valid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := mapFields(tt.recievedTemplates, tt.model) + if (err == nil) != tt.valid { + t.Fatalf("mapFields() error = %v, valid = %t", err, tt.valid) + } + if tt.valid { + if diff := cmp.Diff(tt.expected, tt.model); diff != "" { + t.Fatalf("mapFields() mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/automation/utils/util.go b/stackit/internal/services/automation/utils/util.go new file mode 100644 index 000000000..aca66c6df --- /dev/null +++ b/stackit/internal/services/automation/utils/util.go @@ -0,0 +1,32 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + + "github.com/stackitcloud/stackit-sdk-go/core/config" + automation "github.com/stackitcloud/stackit-sdk-go/services/automation/v1betaapi" +) + +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *automation.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.AutomationCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.AutomationCustomEndpoint)) + } + + apiClient, err := automation.NewAPIClient(apiClientConfigOptions...) + if err != nil { + core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) + return nil + } + + return apiClient +} diff --git a/stackit/internal/services/automation/utils/util_test.go b/stackit/internal/services/automation/utils/util_test.go new file mode 100644 index 000000000..8dc2a490d --- /dev/null +++ b/stackit/internal/services/automation/utils/util_test.go @@ -0,0 +1,95 @@ +package utils + +import ( + "context" + "os" + "reflect" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" + "github.com/stackitcloud/stackit-sdk-go/core/config" + + automation "github.com/stackitcloud/stackit-sdk-go/services/automation/v1betaapi" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +const ( + testVersion = "1.2.3" + testCustomEndpoint = "https://automation-custom-endpoint.api.stackit.cloud" +) + +func TestConfigureClient(t *testing.T) { + /* mock authentication by setting service account token env variable */ + os.Clearenv() + err := os.Setenv(sdkClients.ServiceAccountToken, "mock-val") + if err != nil { + t.Errorf("error setting env variable: %v", err) + } + + type args struct { + providerData *core.ProviderData + } + tests := []struct { + name string + args args + wantErr bool + expected *automation.APIClient + }{ + { + name: "default endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + }, + }, + expected: func() *automation.APIClient { + apiClient, err := automation.NewAPIClient( + utils.UserAgentConfigOption(testVersion), + ) + if err != nil { + t.Errorf("error configuring client: %v", err) + } + return apiClient + }(), + wantErr: false, + }, + { + name: "custom endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + AutomationCustomEndpoint: testCustomEndpoint, + }, + }, + expected: func() *automation.APIClient { + apiClient, err := automation.NewAPIClient( + utils.UserAgentConfigOption(testVersion), + config.WithEndpoint(testCustomEndpoint), + ) + if err != nil { + t.Errorf("error configuring client: %v", err) + } + return apiClient + }(), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + diags := diag.Diagnostics{} + + actual := ConfigureClient(ctx, tt.args.providerData, &diags) + if diags.HasError() != tt.wantErr { + t.Errorf("ConfigureClient() error = %v, want %v", diags.HasError(), tt.wantErr) + } + + if !reflect.DeepEqual(actual, tt.expected) { + t.Errorf("ConfigureClient() = %v, want %v", actual, tt.expected) + } + }) + } +} diff --git a/stackit/provider.go b/stackit/provider.go index 6e0562557..94ad765c0 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -26,6 +26,7 @@ import ( albWaf "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/waf_configuration" customRole "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/customrole" roleAssignements "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/roleassignments" + automationTemplates "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/automation/templates" cdnCustomDomain "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/customdomain" cdn "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/distribution" dnsRecordSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/recordset" @@ -187,6 +188,7 @@ type providerModel struct { // Custom endpoints ALBCustomEndpoint types.String `tfsdk:"alb_custom_endpoint"` AuthorizationCustomEndpoint types.String `tfsdk:"authorization_custom_endpoint"` + AutomationCustomEndpoint types.String `tfsdk:"automation_custom_endpoint"` CdnCustomEndpoint types.String `tfsdk:"cdn_custom_endpoint"` ALBCertificatesCustomEndpoint types.String `tfsdk:"alb_certificates_custom_endpoint"` AlbWafCustomEndpoint types.String `tfsdk:"alb_waf_custom_endpoint"` @@ -250,6 +252,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "alb_certificates_custom_endpoint": "Custom endpoint for the Application Load Balancer TLS Certificate service", "alb_waf_custom_endpoint": "Custom endpoint for the Application Load Balancer Web Application Firewall service", "alb_custom_endpoint": "Custom endpoint for the Application Load Balancer service", + "automation_custom_endpoint": "Custom endpoint for the Automation service", "cdn_custom_endpoint": "Custom endpoint for the CDN service", "dns_custom_endpoint": "Custom endpoint for the DNS service", "dremio_custom_endpoint": "Custom endpoint for the Dremio service", @@ -373,6 +376,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["alb_waf_custom_endpoint"], }, + "automation_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["automation_custom_endpoint"], + }, "dns_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["dns_custom_endpoint"], @@ -567,6 +574,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.ALBCustomEndpoint, func(v string) { providerData.ALBCustomEndpoint = v }) setStringField(providerConfig.AlbWafCustomEndpoint, func(v string) { providerData.AlbWafCustomEndpoint = v }) setStringField(providerConfig.AuthorizationCustomEndpoint, func(v string) { providerData.AuthorizationCustomEndpoint = v }) + setStringField(providerConfig.AutomationCustomEndpoint, func(v string) { providerData.AutomationCustomEndpoint = v }) setStringField(providerConfig.CdnCustomEndpoint, func(v string) { providerData.CdnCustomEndpoint = v }) setStringField(providerConfig.DnsCustomEndpoint, func(v string) { providerData.DnsCustomEndpoint = v }) setStringField(providerConfig.DremioCustomEndpoint, func(v string) { providerData.DremioCustomEndpoint = v }) @@ -680,6 +688,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource albWaf.NewWafConfigurationDatasource, albWafManagedRuleSet.NewManagedRuleSetDataSource, alertGroup.NewAlertGroupDataSource, + automationTemplates.NewAutomationTemplatesDataSource, cdn.NewDistributionDataSource, cert.NewCertificatesDataSource, cdnCustomDomain.NewCustomDomainDataSource, From 1b03165f58070d9088b83e62f8648c4f64ca6b76 Mon Sep 17 00:00:00 2001 From: Jonas Schlecht Date: Wed, 2 Sep 2026 08:40:55 +0200 Subject: [PATCH 2/2] test(automation): add acc test and refactor mapFields to allow nil list --- .../automation/automation_acc_test.go | 37 +++++++++++++++++++ .../automation/templates/datasource.go | 29 ++++++++------- .../automation/templates/datasource_test.go | 10 ++++- .../testdata/datasource-templates.tf | 5 +++ 4 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 stackit/internal/services/automation/automation_acc_test.go create mode 100644 stackit/internal/services/automation/testdata/datasource-templates.tf diff --git a/stackit/internal/services/automation/automation_acc_test.go b/stackit/internal/services/automation/automation_acc_test.go new file mode 100644 index 000000000..aca29ad50 --- /dev/null +++ b/stackit/internal/services/automation/automation_acc_test.go @@ -0,0 +1,37 @@ +package automation_test + +import ( + _ "embed" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +//go:embed testdata/datasource-templates.tf +var templatesDataSourceConfig string + +func TestAccAutomationTemplatesDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + ConfigVariables: config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + }, + Config: testutil.NewConfigBuilder().Region(testutil.Region).EnableBetaResources(true).BuildProviderConfig() + "\n" + templatesDataSourceConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_automation_templates.templates", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("data.stackit_automation_templates.templates", "region", testutil.Region), + resource.TestCheckResourceAttrSet("data.stackit_automation_templates.templates", "id"), + resource.TestCheckResourceAttrSet("data.stackit_automation_templates.templates", "templates.0.id"), + resource.TestCheckResourceAttrSet("data.stackit_automation_templates.templates", "templates.0.name"), + resource.TestCheckResourceAttrSet("data.stackit_automation_templates.templates", "templates.0.description"), + resource.TestCheckResourceAttrSet("data.stackit_automation_templates.templates", "templates.0.create_time"), + ), + }, + }, + }) +} diff --git a/stackit/internal/services/automation/templates/datasource.go b/stackit/internal/services/automation/templates/datasource.go index a66e49fe3..034615a63 100644 --- a/stackit/internal/services/automation/templates/datasource.go +++ b/stackit/internal/services/automation/templates/datasource.go @@ -171,27 +171,28 @@ func (d *templatesDataSource) Read(ctx context.Context, req datasource.ReadReque } func mapFields(templates []automation.Template, m *model) error { - if templates == nil { - return fmt.Errorf("nil response") - } if m == nil { return fmt.Errorf("nil model") } m.ID = utils.BuildInternalTerraformId(m.ProjectId.ValueString(), m.Region.ValueString()) - m.Templates = make([]template, 0, len(templates)) - - slices.SortFunc(templates, func(a, b automation.Template) int { - return strings.Compare(a.Id, b.Id) - }) - for _, respTemplate := range templates { - m.Templates = append(m.Templates, template{ - Id: types.StringValue(respTemplate.Id), - Name: types.StringValue(respTemplate.Name), - Description: types.StringValue(respTemplate.Description), - CreateTime: types.StringValue(respTemplate.CreateTime.Format(time.RFC3339)), + if templates == nil { + m.Templates = nil + } else { + m.Templates = make([]template, 0, len(templates)) + slices.SortFunc(templates, func(a, b automation.Template) int { + return strings.Compare(a.Id, b.Id) }) + + for _, respTemplate := range templates { + m.Templates = append(m.Templates, template{ + Id: types.StringValue(respTemplate.Id), + Name: types.StringValue(respTemplate.Name), + Description: types.StringValue(respTemplate.Description), + CreateTime: types.StringValue(respTemplate.CreateTime.Format(time.RFC3339)), + }) + } } return nil } diff --git a/stackit/internal/services/automation/templates/datasource_test.go b/stackit/internal/services/automation/templates/datasource_test.go index 5369e80ef..b172fb57e 100644 --- a/stackit/internal/services/automation/templates/datasource_test.go +++ b/stackit/internal/services/automation/templates/datasource_test.go @@ -83,12 +83,18 @@ func TestMapFields(t *testing.T) { valid: true, }, { - name: "rejects nil response", + name: "maps nil response", model: &model{ ProjectId: types.StringValue("project-id"), Region: types.StringValue("eu01"), }, - valid: false, + expected: &model{ + ID: types.StringValue("project-id,eu01"), + ProjectId: types.StringValue("project-id"), + Region: types.StringValue("eu01"), + Templates: nil, + }, + valid: true, }, { name: "rejects nil model", diff --git a/stackit/internal/services/automation/testdata/datasource-templates.tf b/stackit/internal/services/automation/testdata/datasource-templates.tf new file mode 100644 index 000000000..03c681eff --- /dev/null +++ b/stackit/internal/services/automation/testdata/datasource-templates.tf @@ -0,0 +1,5 @@ +variable "project_id" {} + +data "stackit_automation_templates" "templates" { + project_id = var.project_id +}