diff --git a/docs/data-sources/public_ip_ranges.md b/docs/data-sources/public_ip_ranges.md index 0a1fbb277..5a9da3a05 100644 --- a/docs/data-sources/public_ip_ranges.md +++ b/docs/data-sources/public_ip_ranges.md @@ -14,6 +14,22 @@ A list of all public IP ranges that STACKIT uses. ```terraform data "stackit_public_ip_ranges" "example" {} + +# example usage: allow stackit services and customer vpn cidr to access observability apis +locals { + vpn_cidrs = ["X.X.X.X/32", "X.X.X.X/24"] +} + +resource "stackit_observability_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-instance" + plan_name = "Observability-Monitoring-Medium-EU01" + # Allow all stackit services and customer vpn cidr to access observability apis + acl = concat(data.stackit_public_ip_ranges.example.cidr_list, local.vpn_cidrs) + metrics_retention_days = 90 + metrics_retention_days_5m_downsampling = 90 + metrics_retention_days_1h_downsampling = 90 +} ``` @@ -21,6 +37,7 @@ data "stackit_public_ip_ranges" "example" {} ### Read-Only +- `cidr_list` (List of String) A list of IP range strings (CIDRs) extracted from the public_ip_ranges for easy consumption. - `id` (String) Terraform's internal resource ID. It takes the values of "`public_ip_ranges.*.cidr`". - `public_ip_ranges` (Attributes List) A list of all public IP ranges. (see [below for nested schema](#nestedatt--public_ip_ranges)) diff --git a/examples/data-sources/stackit_public_ip_ranges/data-source.tf b/examples/data-sources/stackit_public_ip_ranges/data-source.tf index 6bdffaf1c..78cc517a5 100644 --- a/examples/data-sources/stackit_public_ip_ranges/data-source.tf +++ b/examples/data-sources/stackit_public_ip_ranges/data-source.tf @@ -1 +1,17 @@ -data "stackit_public_ip_ranges" "example" {} \ No newline at end of file +data "stackit_public_ip_ranges" "example" {} + +# example usage: allow stackit services and customer vpn cidr to access observability apis +locals { + vpn_cidrs = ["X.X.X.X/32", "X.X.X.X/24"] +} + +resource "stackit_observability_instance" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-instance" + plan_name = "Observability-Monitoring-Medium-EU01" + # Allow all stackit services and customer vpn cidr to access observability apis + acl = concat(data.stackit_public_ip_ranges.example.cidr_list, local.vpn_cidrs) + metrics_retention_days = 90 + metrics_retention_days_5m_downsampling = 90 + metrics_retention_days_1h_downsampling = 90 +} \ No newline at end of file diff --git a/stackit/internal/services/iaas/iaas_acc_test.go b/stackit/internal/services/iaas/iaas_acc_test.go index 1ae623840..dda89f70a 100644 --- a/stackit/internal/services/iaas/iaas_acc_test.go +++ b/stackit/internal/services/iaas/iaas_acc_test.go @@ -38,6 +38,9 @@ var ( //go:embed testdata/datasource-image-v2-variants.tf dataSourceImageVariants string + //go:embed testdata/datasource-public-ip-ranges.tf + datasourcePublicIpRanges string + //go:embed testdata/resource-image-min.tf resourceImageMinConfig string @@ -4159,6 +4162,25 @@ func TestAccImageV2DatasourceSearchVariants(t *testing.T) { }) } +func TestAccDatasourcePublicIpRanges(t *testing.T) { + t.Log("TestDataSource STACKIT Public Ip Ranges") + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read + { + ConfigVariables: config.Variables{}, + Config: fmt.Sprintf("%s\n%s", datasourcePublicIpRanges, testutil.IaaSProviderConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("data.stackit_public_ip_ranges.example", "id"), + resource.TestCheckResourceAttrSet("data.stackit_public_ip_ranges.example", "public_ip_ranges.0.cidr"), + resource.TestCheckResourceAttrSet("data.stackit_public_ip_ranges.example", "cidr_list.0"), + ), + }, + }, + }) +} + func TestAccProject(t *testing.T) { projectId := testutil.ProjectId resource.ParallelTest(t, resource.TestCase{ diff --git a/stackit/internal/services/iaas/publicipranges/datasource.go b/stackit/internal/services/iaas/publicipranges/datasource.go index e4b969930..3daebaaa5 100644 --- a/stackit/internal/services/iaas/publicipranges/datasource.go +++ b/stackit/internal/services/iaas/publicipranges/datasource.go @@ -41,6 +41,7 @@ type publicIpRangesDataSource struct { type Model struct { Id types.String `tfsdk:"id"` // needed by TF PublicIpRanges types.List `tfsdk:"public_ip_ranges"` + CidrList types.List `tfsdk:"cidr_list"` } var publicIpRangesTypes = map[string]attr.Type{ @@ -97,6 +98,11 @@ func (d *publicIpRangesDataSource) Schema(_ context.Context, _ datasource.Schema }, }, }, + "cidr_list": schema.ListAttribute{ + Description: "A list of IP range strings (CIDRs) extracted from the public_ip_ranges for easy consumption.", + ElementType: types.StringType, + Computed: true, + }, }, } } @@ -155,18 +161,19 @@ func mapFields(ctx context.Context, publicIpRangeResp *iaas.PublicNetworkListRes } // mapPublicIpRanges map the response publicIpRanges to the model -func mapPublicIpRanges(_ context.Context, publicIpRanges *[]iaas.PublicNetwork, model *Model) error { +func mapPublicIpRanges(ctx context.Context, publicIpRanges *[]iaas.PublicNetwork, model *Model) error { if publicIpRanges == nil { return fmt.Errorf("publicIpRanges input is nil") } if len(*publicIpRanges) == 0 { model.PublicIpRanges = types.ListNull(types.ObjectType{AttrTypes: publicIpRangesTypes}) + model.CidrList = types.ListNull(types.StringType) return nil } var apiIpRanges []string for _, ipRange := range *publicIpRanges { - if ipRange.Cidr != nil || *ipRange.Cidr != "" { + if ipRange.Cidr != nil && *ipRange.Cidr != "" { apiIpRanges = append(apiIpRanges, *ipRange.Cidr) } } @@ -197,5 +204,12 @@ func mapPublicIpRanges(_ context.Context, publicIpRanges *[]iaas.PublicNetwork, } model.PublicIpRanges = ipRangesTF + + cidrListTF, diags := types.ListValueFrom(ctx, types.StringType, apiIpRanges) + if diags.HasError() { + return core.DiagsToError(diags) + } + model.CidrList = cidrListTF + return nil } diff --git a/stackit/internal/services/iaas/publicipranges/datasource_test.go b/stackit/internal/services/iaas/publicipranges/datasource_test.go new file mode 100644 index 000000000..535df5f74 --- /dev/null +++ b/stackit/internal/services/iaas/publicipranges/datasource_test.go @@ -0,0 +1,115 @@ +package publicipranges + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + coreUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/services/iaas" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" +) + +func TestMapPublicIpRanges(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + input *[]iaas.PublicNetwork + expected Model + isValid bool + }{ + { + name: "nil input should return error", + input: nil, + isValid: false, + }, + { + name: "empty input should return nulls", + input: &[]iaas.PublicNetwork{}, + expected: Model{ + PublicIpRanges: types.ListNull(types.ObjectType{AttrTypes: publicIpRangesTypes}), + CidrList: types.ListNull(types.StringType), + }, + isValid: true, + }, + { + name: "valid cidr entries", + input: &[]iaas.PublicNetwork{ + {Cidr: coreUtils.Ptr("192.168.0.0/24")}, + {Cidr: coreUtils.Ptr("192.168.1.0/24")}, + }, + expected: func() Model { + cidrs := []string{"192.168.0.0/24", "192.168.1.0/24"} + ipRangesList := make([]attr.Value, 0, len(cidrs)) + for _, cidr := range cidrs { + ipRange, _ := types.ObjectValue(publicIpRangesTypes, map[string]attr.Value{ + "cidr": types.StringValue(cidr), + }) + ipRangesList = append(ipRangesList, ipRange) + } + ipRangesVal, _ := types.ListValue(types.ObjectType{AttrTypes: publicIpRangesTypes}, ipRangesList) + cidrListVal, _ := types.ListValueFrom(ctx, types.StringType, cidrs) + + return Model{ + PublicIpRanges: ipRangesVal, + CidrList: cidrListVal, + Id: utils.BuildInternalTerraformId(cidrs...), + } + }(), + isValid: true, + }, + { + name: "filter out empty CIDRs", + input: &[]iaas.PublicNetwork{ + {Cidr: coreUtils.Ptr("")}, + {Cidr: nil}, + {Cidr: coreUtils.Ptr("10.0.0.0/8")}, + }, + expected: func() Model { + cidrs := []string{"10.0.0.0/8"} + ipRange, _ := types.ObjectValue(publicIpRangesTypes, map[string]attr.Value{ + "cidr": types.StringValue("10.0.0.0/8"), + }) + ipRangesVal, _ := types.ListValue(types.ObjectType{AttrTypes: publicIpRangesTypes}, []attr.Value{ipRange}) + cidrListVal, _ := types.ListValueFrom(ctx, types.StringType, cidrs) + return Model{ + PublicIpRanges: ipRangesVal, + CidrList: cidrListVal, + Id: utils.BuildInternalTerraformId(cidrs...), + } + }(), + isValid: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var model Model + err := mapPublicIpRanges(ctx, tt.input, &model) + + if !tt.isValid { + if err == nil { + t.Fatalf("Expected error but got nil") + } + return + } else if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + if diff := cmp.Diff(tt.expected.Id, model.Id); diff != "" { + t.Errorf("ID does not match:\n%s", diff) + } + + if diff := cmp.Diff(tt.expected.CidrList, model.CidrList); diff != "" { + t.Errorf("cidr_list does not match:\n%s", diff) + } + + if diff := cmp.Diff(tt.expected.PublicIpRanges, model.PublicIpRanges); diff != "" { + t.Errorf("public_ip_ranges does not match:\n%s", diff) + } + }) + } +} diff --git a/stackit/internal/services/iaas/testdata/datasource-public-ip-ranges.tf b/stackit/internal/services/iaas/testdata/datasource-public-ip-ranges.tf new file mode 100644 index 000000000..6bdffaf1c --- /dev/null +++ b/stackit/internal/services/iaas/testdata/datasource-public-ip-ranges.tf @@ -0,0 +1 @@ +data "stackit_public_ip_ranges" "example" {} \ No newline at end of file