diff --git a/docs/data-sources/ske_provider_options.md b/docs/data-sources/ske_provider_options.md new file mode 100644 index 000000000..e5840961e --- /dev/null +++ b/docs/data-sources/ske_provider_options.md @@ -0,0 +1,101 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_ske_provider_options Data Source - stackit" +subcategory: "" +description: |- + Returns a list of supported Kubernetes versions and a list of supported machine types for the cluster nodes. +--- + +# stackit_ske_provider_options (Data Source) + +Returns a list of supported Kubernetes versions and a list of supported machine types for the cluster nodes. + +## Example Usage + +```terraform +data "stackit_ske_provider_options" "default" {} + +data "stackit_ske_provider_options" "eu02" { + region = "eu02" +} + +locals { + k8s_versions = [ + for v in data.stackit_ske_provider_options.default.kubernetes_versions : + v.version if v.state == "supported" + ] + first_k8s_version = length(local.k8s_versions) > 0 ? local.k8s_versions[0] : "" + last_k8s_version = length(local.k8s_versions) > 0 ? local.k8s_versions[length(local.k8s_versions) - 1] : "" + + + flatcar_supported_versions = flatten([ + for mi in data.stackit_ske_provider_options.default.machine_images : [ + for v in mi.versions : + v.version if mi.name == "flatcar" && v.state == "supported" + ] + ]) + + ubuntu_supported_versions = flatten([ + for mi in data.stackit_ske_provider_options.default.machine_images : [ + for v in mi.versions : + v.version if mi.name == "ubuntu" && v.state == "supported" + ] + ]) +} +``` + + +## Schema + +### Optional + +- `region` (String) Region override. If omitted, the provider’s region will be used. + +### Read-Only + +- `availability_zones` (List of String) List of availability zones in the selected region. +- `kubernetes_versions` (Attributes List) Supported Kubernetes versions. (see [below for nested schema](#nestedatt--kubernetes_versions)) +- `machine_images` (Attributes List) Supported machine image types and software versions. (see [below for nested schema](#nestedatt--machine_images)) +- `machine_types` (Attributes List) List of machine types (node sizes) available in the region. (see [below for nested schema](#nestedatt--machine_types)) +- `volume_types` (List of String) Supported root volume types (e.g., `storage_premium_perf1`). + + +### Nested Schema for `kubernetes_versions` + +Read-Only: + +- `expiration_date` (String) Expiration date of the version in RFC3339 format. +- `state` (String) Version state, such as `supported`, `preview`, or `deprecated`. +- `version` (String) Kubernetes version string (e.g., `1.33`). + + + +### Nested Schema for `machine_images` + +Read-Only: + +- `name` (String) Name of the OS image (e.g., `ubuntu`). +- `versions` (Attributes List) Supported versions of the image. (see [below for nested schema](#nestedatt--machine_images--versions)) + + +### Nested Schema for `machine_images.versions` + +Read-Only: + +- `cri` (List of String) Container runtimes supported (e.g., `containerd`). +- `expiration_date` (String) Expiration date of the version in RFC3339 format. +- `state` (String) State of the image version (e.g., `supported`, `preview`, `deprecated`). +- `version` (String) Machine image version string. + + + + +### Nested Schema for `machine_types` + +Read-Only: + +- `architecture` (String) CPU architecture (e.g., `x86_64`, `arm64`). +- `cpu` (Number) Number of virtual CPUs. +- `gpu` (Number) Number of GPUs included. +- `memory` (Number) Memory size in GB. +- `name` (String) Machine type name (e.g., `c2i.2`). diff --git a/examples/data-sources/stackit_ske_provider_options/data-source.tf b/examples/data-sources/stackit_ske_provider_options/data-source.tf new file mode 100644 index 000000000..8160eec1d --- /dev/null +++ b/examples/data-sources/stackit_ske_provider_options/data-source.tf @@ -0,0 +1,29 @@ +data "stackit_ske_provider_options" "default" {} + +data "stackit_ske_provider_options" "eu02" { + region = "eu02" +} + +locals { + k8s_versions = [ + for v in data.stackit_ske_provider_options.default.kubernetes_versions : + v.version if v.state == "supported" + ] + first_k8s_version = length(local.k8s_versions) > 0 ? local.k8s_versions[0] : "" + last_k8s_version = length(local.k8s_versions) > 0 ? local.k8s_versions[length(local.k8s_versions) - 1] : "" + + + flatcar_supported_versions = flatten([ + for mi in data.stackit_ske_provider_options.default.machine_images : [ + for v in mi.versions : + v.version if mi.name == "flatcar" && v.state == "supported" + ] + ]) + + ubuntu_supported_versions = flatten([ + for mi in data.stackit_ske_provider_options.default.machine_images : [ + for v in mi.versions : + v.version if mi.name == "ubuntu" && v.state == "supported" + ] + ]) +} \ No newline at end of file diff --git a/stackit/internal/services/ske/provideroptions/datasource.go b/stackit/internal/services/ske/provideroptions/datasource.go new file mode 100644 index 000000000..f157364cc --- /dev/null +++ b/stackit/internal/services/ske/provideroptions/datasource.go @@ -0,0 +1,384 @@ +package providerOptions + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/services/ske" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + skeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +// Model types for nested structures +type Model struct { + Region types.String `tfsdk:"region"` + AvailabilityZones types.List `tfsdk:"availability_zones"` + KubernetesVersions types.List `tfsdk:"kubernetes_versions"` + MachineTypes types.List `tfsdk:"machine_types"` + MachineImages types.List `tfsdk:"machine_images"` + VolumeTypes types.List `tfsdk:"volume_types"` +} + +var ( + kubernetesVersionType = map[string]attr.Type{ + "version": types.StringType, + "expiration_date": types.StringType, + "state": types.StringType, + } + + machineTypeAttributeType = map[string]attr.Type{ + "name": types.StringType, + "architecture": types.StringType, + "cpu": types.Int64Type, + "gpu": types.Int64Type, + "memory": types.Int64Type, + } + + machineImageVersionType = map[string]attr.Type{ + "version": types.StringType, + "state": types.StringType, + "expiration_date": types.StringType, + "cri": types.ListType{ElemType: types.StringType}, + } + + machineImageType = map[string]attr.Type{ + "name": types.StringType, + "versions": types.ListType{ElemType: types.ObjectType{AttrTypes: machineImageVersionType}}, + } +) + +// Ensure implementation satisfies interface +var _ datasource.DataSource = &optionsDataSource{} + +// NewOptionsDataSource creates the data source instance +func NewOptionsDataSource() datasource.DataSource { + return &optionsDataSource{} +} + +type optionsDataSource struct { + client *ske.APIClient + providerData core.ProviderData +} + +// Metadata sets the data source type name +func (d *optionsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_ske_provider_options" +} + +func (d *optionsDataSource) 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 + } + d.client = skeUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics) + tflog.Info(ctx, "SKE options client configured") +} + +func (d *optionsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + description := "Returns a list of supported Kubernetes versions and a list of supported machine types for the cluster nodes." + + resp.Schema = schema.Schema{ + Description: description, + Attributes: map[string]schema.Attribute{ + "region": schema.StringAttribute{ + Optional: true, + Description: "Region override. If omitted, the provider’s region will be used.", + }, + "availability_zones": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "List of availability zones in the selected region.", + }, + "kubernetes_versions": schema.ListNestedAttribute{ + Computed: true, + Description: "Supported Kubernetes versions.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "version": schema.StringAttribute{ + Computed: true, + Description: "Kubernetes version string (e.g., `1.33`).", + }, + "expiration_date": schema.StringAttribute{ + Computed: true, + Description: "Expiration date of the version in RFC3339 format.", + }, + "state": schema.StringAttribute{ + Computed: true, + Description: "Version state, such as `supported`, `preview`, or `deprecated`.", + }, + }, + }, + }, + "machine_types": schema.ListNestedAttribute{ + Computed: true, + Description: "List of machine types (node sizes) available in the region.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + Description: "Machine type name (e.g., `c2i.2`).", + }, + "architecture": schema.StringAttribute{ + Computed: true, + Description: "CPU architecture (e.g., `x86_64`, `arm64`).", + }, + "cpu": schema.Int64Attribute{ + Computed: true, + Description: "Number of virtual CPUs.", + }, + "gpu": schema.Int64Attribute{ + Computed: true, + Description: "Number of GPUs included.", + }, + "memory": schema.Int64Attribute{ + Computed: true, + Description: "Memory size in GB.", + }, + }, + }, + }, + "machine_images": schema.ListNestedAttribute{ + Computed: true, + Description: "Supported machine image types and software versions.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + Description: "Name of the OS image (e.g., `ubuntu`).", + }, + "versions": schema.ListNestedAttribute{ + Computed: true, + Description: "Supported versions of the image.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "version": schema.StringAttribute{ + Computed: true, + Description: "Machine image version string.", + }, + "state": schema.StringAttribute{ + Computed: true, + Description: "State of the image version (e.g., `supported`, `preview`, `deprecated`).", + }, + "expiration_date": schema.StringAttribute{ + Computed: true, + Description: "Expiration date of the version in RFC3339 format.", + }, + "cri": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "Container runtimes supported (e.g., `containerd`).", + }, + }, + }, + }, + }, + }, + }, + "volume_types": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "Supported root volume types (e.g., `storage_premium_perf1`).", + }, + }, + } +} + +// Read refreshes the Terraform state with the latest data. +func (d *optionsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.Config.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + region := d.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "region", region) + + optionsResp, err := d.client.ListProviderOptions(ctx, region).Execute() + if err != nil { + utils.LogError( + ctx, + &resp.Diagnostics, + err, + "Reading SKE provider options failed", + "Unable to read SKE provider options", + map[int]string{ + http.StatusForbidden: "Forbidden access", + }, + ) + resp.State.RemoveResource(ctx) + return + } + + err = mapFields(ctx, optionsResp, &model) + if err != nil { + core.LogAndAddError(ctx, &diags, "Error reading provider options", fmt.Sprintf("Mapping API Payload: %v", err)) + return + } + + // Set final state + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + tflog.Info(ctx, "Read SKE provider options successfully") +} + +func mapFields(ctx context.Context, optionsResp *ske.ProviderOptions, model *Model) error { + if optionsResp == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + // Availability Zones + azList := make([]types.String, 0) + if optionsResp.AvailabilityZones != nil { + for _, az := range *optionsResp.AvailabilityZones { + if az.Name != nil { + azList = append(azList, types.StringValue(*az.Name)) + } + } + } + avZones, diags := types.ListValueFrom(ctx, types.StringType, azList) + if diags.HasError() { + return core.DiagsToError(diags) + } + model.AvailabilityZones = avZones + + // Volume Types + volList := make([]types.String, 0) + if optionsResp.VolumeTypes != nil { + for _, vt := range *optionsResp.VolumeTypes { + if vt.Name != nil { + volList = append(volList, types.StringValue(*vt.Name)) + } + } + } + volTypes, diags := types.ListValueFrom(ctx, types.StringType, volList) + if diags.HasError() { + return core.DiagsToError(diags) + } + model.VolumeTypes = volTypes + + // Kubernetes Versions + kvList := make([]attr.Value, 0) + if optionsResp.KubernetesVersions != nil { + for _, kv := range *optionsResp.KubernetesVersions { + expDate := types.StringNull() + if kv.ExpirationDate != nil { + expDate = types.StringValue(kv.ExpirationDate.Format(time.RFC3339)) + } + + obj, diags := types.ObjectValue(kubernetesVersionType, map[string]attr.Value{ + "version": types.StringPointerValue(kv.Version), + "state": types.StringPointerValue(kv.State), + "expiration_date": expDate, + }) + if diags.HasError() { + return core.DiagsToError(diags) + } + kvList = append(kvList, obj) + } + } + kvs, diags := types.ListValue(types.ObjectType{AttrTypes: kubernetesVersionType}, kvList) + if diags.HasError() { + return core.DiagsToError(diags) + } + model.KubernetesVersions = kvs + + // Machine Types + mtList := make([]attr.Value, 0) + if optionsResp.MachineTypes != nil { + for _, mt := range *optionsResp.MachineTypes { + vals := map[string]attr.Value{ + "name": types.StringPointerValue(mt.Name), + "architecture": types.StringPointerValue(mt.Architecture), + "cpu": types.Int64PointerValue(mt.Cpu), + "gpu": types.Int64PointerValue(mt.Gpu), + "memory": types.Int64PointerValue(mt.Memory), + } + obj, diags := types.ObjectValue(machineTypeAttributeType, vals) + if diags.HasError() { + return core.DiagsToError(diags) + } + mtList = append(mtList, obj) + } + } + mts, diags := types.ListValue(types.ObjectType{AttrTypes: machineTypeAttributeType}, mtList) + if diags.HasError() { + return core.DiagsToError(diags) + } + model.MachineTypes = mts + + // Machine Images + miList := make([]attr.Value, 0) + if optionsResp.MachineImages != nil { + for _, img := range *optionsResp.MachineImages { + versionsList := make([]attr.Value, 0) + if img.Versions != nil { + for _, ver := range *img.Versions { + criList := make([]types.String, 0) + if ver.Cri != nil { + for _, cri := range *ver.Cri { + if cri.Name != nil { + criList = append(criList, types.StringValue(string(*cri.Name.Ptr()))) + } + } + } + criVal, diags := types.ListValueFrom(ctx, types.StringType, criList) + if diags.HasError() { + return core.DiagsToError(diags) + } + + expDate := types.StringNull() + if ver.ExpirationDate != nil { + expDate = types.StringValue(ver.ExpirationDate.Format(time.RFC3339)) + } + + versionObj, diags := types.ObjectValue(machineImageVersionType, map[string]attr.Value{ + "version": types.StringPointerValue(ver.Version), + "state": types.StringPointerValue(ver.State), + "expiration_date": expDate, + "cri": criVal, + }) + if diags.HasError() { + return core.DiagsToError(diags) + } + versionsList = append(versionsList, versionObj) + } + } + + versions, diags := types.ListValue(types.ObjectType{AttrTypes: machineImageVersionType}, versionsList) + if diags.HasError() { + return core.DiagsToError(diags) + } + + imgObj, diags := types.ObjectValue(machineImageType, map[string]attr.Value{ + "name": types.StringPointerValue(img.Name), + "versions": versions, + }) + if diags.HasError() { + return core.DiagsToError(diags) + } + miList = append(miList, imgObj) + } + } + mis, diags := types.ListValue(types.ObjectType{AttrTypes: machineImageType}, miList) + if diags.HasError() { + return core.DiagsToError(diags) + } + model.MachineImages = mis + + return nil +} diff --git a/stackit/internal/services/ske/provideroptions/datasource_test.go b/stackit/internal/services/ske/provideroptions/datasource_test.go new file mode 100644 index 000000000..de6a845ba --- /dev/null +++ b/stackit/internal/services/ske/provideroptions/datasource_test.go @@ -0,0 +1,337 @@ +package providerOptions + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/ske" +) + +func TestMapFields(t *testing.T) { + timestamp := time.Date(2025, 2, 5, 10, 20, 30, 0, time.UTC) + expDate := timestamp.Format(time.RFC3339) + + tests := []struct { + name string + input *ske.ProviderOptions + expected *Model + isValid bool + }{ + { + name: "normal_case", + input: &ske.ProviderOptions{ + AvailabilityZones: &[]ske.AvailabilityZone{ + {Name: utils.Ptr("eu01-01")}, + {Name: utils.Ptr("eu01-02")}, + }, + VolumeTypes: &[]ske.VolumeType{ + {Name: utils.Ptr("storage_premium_perf1")}, + {Name: utils.Ptr("storage_premium_perf2")}, + }, + KubernetesVersions: &[]ske.KubernetesVersion{ + { + Version: utils.Ptr("1.33.5"), + State: utils.Ptr("supported"), + ExpirationDate: ×tamp, + }, + }, + MachineTypes: &[]ske.MachineType{ + { + Name: utils.Ptr("n2.56d.g4"), + Architecture: utils.Ptr("amd64"), + Cpu: utils.Ptr(int64(4)), + Gpu: utils.Ptr(int64(1)), + Memory: utils.Ptr(int64(16)), + }, + }, + MachineImages: &[]ske.MachineImage{ + { + Name: utils.Ptr("ubuntu"), + Versions: &[]ske.MachineImageVersion{ + { + Version: utils.Ptr("2204.20250620.0"), + State: utils.Ptr("supported"), + ExpirationDate: ×tamp, + Cri: &[]ske.CRI{ + {Name: utils.Ptr(ske.CRINAME_CONTAINERD)}, + }, + }, + }, + }, + }, + }, + expected: &Model{ + AvailabilityZones: types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("eu01-01"), + types.StringValue("eu01-02"), + }, + ), + VolumeTypes: types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("storage_premium_perf1"), + types.StringValue("storage_premium_perf2"), + }, + ), + KubernetesVersions: types.ListValueMust( + types.ObjectType{AttrTypes: kubernetesVersionType}, + []attr.Value{ + types.ObjectValueMust(kubernetesVersionType, map[string]attr.Value{ + "version": types.StringValue("1.33.5"), + "state": types.StringValue("supported"), + "expiration_date": types.StringValue(expDate), + }), + }, + ), + MachineTypes: types.ListValueMust( + types.ObjectType{AttrTypes: machineTypeAttributeType}, + []attr.Value{ + types.ObjectValueMust(machineTypeAttributeType, map[string]attr.Value{ + "name": types.StringValue("n2.56d.g4"), + "architecture": types.StringValue("amd64"), + "cpu": types.Int64Value(4), + "gpu": types.Int64Value(1), + "memory": types.Int64Value(16), + }), + }, + ), + MachineImages: types.ListValueMust( + types.ObjectType{AttrTypes: machineImageType}, + []attr.Value{ + types.ObjectValueMust(machineImageType, map[string]attr.Value{ + "name": types.StringValue("ubuntu"), + "versions": types.ListValueMust( + types.ObjectType{AttrTypes: machineImageVersionType}, + []attr.Value{ + types.ObjectValueMust(machineImageVersionType, map[string]attr.Value{ + "version": types.StringValue("2204.20250620.0"), + "state": types.StringValue("supported"), + "expiration_date": types.StringValue(expDate), + "cri": types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("containerd"), + }, + ), + }), + }, + ), + }), + }, + ), + }, + isValid: true, + }, + { + name: "partial_fields", + input: &ske.ProviderOptions{ + AvailabilityZones: &[]ske.AvailabilityZone{ + {Name: utils.Ptr("eu01-01")}, + }, + MachineTypes: &[]ske.MachineType{ + { + Name: utils.Ptr("g1a.16d"), + Cpu: utils.Ptr(int64(2)), + }, + }, + }, + expected: &Model{ + AvailabilityZones: types.ListValueMust(types.StringType, + []attr.Value{types.StringValue("eu01-01")}, + ), + VolumeTypes: types.ListValueMust(types.StringType, []attr.Value{}), + KubernetesVersions: types.ListValueMust(types.ObjectType{AttrTypes: kubernetesVersionType}, []attr.Value{}), + MachineImages: types.ListValueMust(types.ObjectType{AttrTypes: machineImageType}, []attr.Value{}), + MachineTypes: types.ListValueMust( + types.ObjectType{AttrTypes: machineTypeAttributeType}, + []attr.Value{ + types.ObjectValueMust(machineTypeAttributeType, map[string]attr.Value{ + "name": types.StringValue("g1a.16d"), + "architecture": types.StringNull(), + "cpu": types.Int64Value(2), + "gpu": types.Int64Null(), + "memory": types.Int64Null(), + }), + }, + ), + }, + isValid: true, + }, + { + name: "az_with_nil_name", + input: &ske.ProviderOptions{ + AvailabilityZones: &[]ske.AvailabilityZone{ + {Name: nil}, + {Name: utils.Ptr("eu01-01")}, + }, + }, + expected: &Model{ + AvailabilityZones: types.ListValueMust( + types.StringType, + []attr.Value{types.StringValue("eu01-01")}, + ), + VolumeTypes: types.ListValueMust(types.StringType, []attr.Value{}), + KubernetesVersions: types.ListValueMust(types.ObjectType{AttrTypes: kubernetesVersionType}, []attr.Value{}), + MachineTypes: types.ListValueMust(types.ObjectType{AttrTypes: machineTypeAttributeType}, []attr.Value{}), + MachineImages: types.ListValueMust(types.ObjectType{AttrTypes: machineImageType}, []attr.Value{}), + }, + isValid: true, + }, + { + name: "machine_image_with_nil_versions", + input: &ske.ProviderOptions{ + MachineImages: &[]ske.MachineImage{ + { + Name: utils.Ptr("ubuntu"), + Versions: nil, + }, + }, + }, + expected: &Model{ + AvailabilityZones: types.ListValueMust(types.StringType, []attr.Value{}), + VolumeTypes: types.ListValueMust(types.StringType, []attr.Value{}), + KubernetesVersions: types.ListValueMust(types.ObjectType{AttrTypes: kubernetesVersionType}, []attr.Value{}), + MachineTypes: types.ListValueMust(types.ObjectType{AttrTypes: machineTypeAttributeType}, []attr.Value{}), + MachineImages: types.ListValueMust(types.ObjectType{AttrTypes: machineImageType}, + []attr.Value{ + types.ObjectValueMust(machineImageType, map[string]attr.Value{ + "name": types.StringValue("ubuntu"), + "versions": types.ListValueMust(types.ObjectType{AttrTypes: machineImageVersionType}, []attr.Value{}), + }), + }, + ), + }, + isValid: true, + }, + { + name: "image_version_with_nil_cri", + input: &ske.ProviderOptions{ + MachineImages: &[]ske.MachineImage{ + { + Name: utils.Ptr("ubuntu"), + Versions: &[]ske.MachineImageVersion{ + { + Version: utils.Ptr("1.1"), + State: utils.Ptr("deprecated"), + ExpirationDate: ×tamp, + Cri: nil, + }, + }, + }, + }, + }, + expected: &Model{ + AvailabilityZones: types.ListValueMust(types.StringType, []attr.Value{}), + VolumeTypes: types.ListValueMust(types.StringType, []attr.Value{}), + KubernetesVersions: types.ListValueMust(types.ObjectType{AttrTypes: kubernetesVersionType}, []attr.Value{}), + MachineTypes: types.ListValueMust(types.ObjectType{AttrTypes: machineTypeAttributeType}, []attr.Value{}), + MachineImages: types.ListValueMust( + types.ObjectType{AttrTypes: machineImageType}, + []attr.Value{ + types.ObjectValueMust(machineImageType, map[string]attr.Value{ + "name": types.StringValue("ubuntu"), + "versions": types.ListValueMust( + types.ObjectType{AttrTypes: machineImageVersionType}, + []attr.Value{ + types.ObjectValueMust(machineImageVersionType, map[string]attr.Value{ + "version": types.StringValue("1.1"), + "state": types.StringValue("deprecated"), + "expiration_date": types.StringValue(expDate), + "cri": types.ListValueMust(types.StringType, []attr.Value{}), + }), + }), + }), + }), + }, + isValid: true, + }, + { + name: "machine_type_null_fields", + input: &ske.ProviderOptions{ + MachineTypes: &[]ske.MachineType{ + {}, // all pointer fields are nil + }, + }, + expected: &Model{ + AvailabilityZones: types.ListValueMust(types.StringType, []attr.Value{}), + VolumeTypes: types.ListValueMust(types.StringType, []attr.Value{}), + KubernetesVersions: types.ListValueMust(types.ObjectType{AttrTypes: kubernetesVersionType}, []attr.Value{}), + MachineImages: types.ListValueMust(types.ObjectType{AttrTypes: machineImageType}, []attr.Value{}), + MachineTypes: types.ListValueMust(types.ObjectType{AttrTypes: machineTypeAttributeType}, []attr.Value{ + types.ObjectValueMust(machineTypeAttributeType, map[string]attr.Value{ + "name": types.StringNull(), + "architecture": types.StringNull(), + "cpu": types.Int64Null(), + "gpu": types.Int64Null(), + "memory": types.Int64Null(), + }), + }), + }, + isValid: true, + }, + { + name: "all_nil_fields", + input: &ske.ProviderOptions{ + AvailabilityZones: nil, + VolumeTypes: nil, + KubernetesVersions: nil, + MachineImages: nil, + MachineTypes: nil, + }, + expected: &Model{ + AvailabilityZones: types.ListValueMust(types.StringType, []attr.Value{}), + VolumeTypes: types.ListValueMust(types.StringType, []attr.Value{}), + KubernetesVersions: types.ListValueMust(types.ObjectType{AttrTypes: kubernetesVersionType}, []attr.Value{}), + MachineTypes: types.ListValueMust(types.ObjectType{AttrTypes: machineTypeAttributeType}, []attr.Value{}), + MachineImages: types.ListValueMust(types.ObjectType{AttrTypes: machineImageType}, []attr.Value{}), + }, + isValid: true, + }, + { + name: "all_empty_fields", + input: &ske.ProviderOptions{ + AvailabilityZones: &[]ske.AvailabilityZone{}, + VolumeTypes: &[]ske.VolumeType{}, + KubernetesVersions: &[]ske.KubernetesVersion{}, + MachineImages: &[]ske.MachineImage{}, + MachineTypes: &[]ske.MachineType{}, + }, + expected: &Model{ + AvailabilityZones: types.ListValueMust(types.StringType, []attr.Value{}), + VolumeTypes: types.ListValueMust(types.StringType, []attr.Value{}), + KubernetesVersions: types.ListValueMust(types.ObjectType{AttrTypes: kubernetesVersionType}, []attr.Value{}), + MachineTypes: types.ListValueMust(types.ObjectType{AttrTypes: machineTypeAttributeType}, []attr.Value{}), + MachineImages: types.ListValueMust(types.ObjectType{AttrTypes: machineImageType}, []attr.Value{}), + }, + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + model := &Model{} + err := mapFields(context.Background(), tt.input, model) + + if tt.isValid && err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if !tt.isValid && err == nil { + t.Fatal("expected error but got none") + } + + if tt.isValid { + if diff := cmp.Diff(tt.expected, model); diff != "" { + t.Fatalf("Mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/ske/ske_acc_test.go b/stackit/internal/services/ske/ske_acc_test.go index b5a2d1796..9408de0e8 100644 --- a/stackit/internal/services/ske/ske_acc_test.go +++ b/stackit/internal/services/ske/ske_acc_test.go @@ -31,6 +31,9 @@ var ( //go:embed testdata/resource-max.tf resourceMax string + + //go:embed testdata/provider-options.tf + dataSourceProviderOptions string ) var skeProviderOptions = NewSkeProviderOptions("flatcar") @@ -91,6 +94,10 @@ var testConfigVarsMax = config.Variables{ "dns_name": config.StringVariable("acc-" + acctest.RandStringFromCharSet(6, acctest.CharSetAlpha) + ".runs.onstackit.cloud"), } +var testConfigDatasource = config.Variables{ + "region": config.StringVariable(testutil.Region), +} + func configVarsMinUpdated() config.Variables { updatedConfig := maps.Clone(testConfigVarsMin) updatedConfig["kubernetes_version_min"] = config.StringVariable(skeProviderOptions.GetUpdateK8sVersion()) @@ -455,6 +462,41 @@ func TestAccSKEMax(t *testing.T) { }) } +func TestAccProviderOption(t *testing.T) { + t.Logf("TestAccProviderOption") + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + ConfigVariables: testConfigDatasource, + Config: testutil.SKEProviderConfig() + "\n" + dataSourceProviderOptions, + Check: resource.ComposeTestCheckFunc( + // Availability Zones + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "availability_zones.0"), + + // Volume Types + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "volume_types.0"), + + // Kubernetes Versions + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "kubernetes_versions.0.version"), + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "kubernetes_versions.0.state"), + + // Machine Images + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "machine_images.0.name"), + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "machine_images.0.versions.0.state"), + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "machine_images.0.versions.0.version"), + + // Machine Types + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "machine_types.0.name"), + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "machine_types.0.cpu"), + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "machine_types.0.gpu"), + resource.TestCheckResourceAttrSet("data.stackit_ske_provider_options.this", "machine_types.0.memory"), + ), + }, + }, + }) +} + func testAccCheckSKEDestroy(s *terraform.State) error { ctx := context.Background() var client *ske.APIClient diff --git a/stackit/internal/services/ske/testdata/provider-options.tf b/stackit/internal/services/ske/testdata/provider-options.tf new file mode 100644 index 000000000..f359b0457 --- /dev/null +++ b/stackit/internal/services/ske/testdata/provider-options.tf @@ -0,0 +1 @@ +data "stackit_ske_provider_options" "this" {} \ No newline at end of file diff --git a/stackit/provider.go b/stackit/provider.go index 5f51fc33c..ab2e90040 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -88,6 +88,7 @@ import ( serviceAccountToken "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/serviceaccount/token" skeCluster "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/cluster" skeKubeconfig "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/kubeconfig" + skeProviderOptions "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/provideroptions" sqlServerFlexInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sqlserverflex/instance" sqlServerFlexUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sqlserverflex/user" ) @@ -524,6 +525,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource serverUpdateSchedule.NewSchedulesDataSource, serviceAccount.NewServiceAccountDataSource, skeCluster.NewClusterDataSource, + skeProviderOptions.NewOptionsDataSource, } }