Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/data-sources/networks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackit_networks Data Source - stackit"
subcategory: ""
description: |-
Lists STACKIT networks in a project, optionally filtered by name or labels.
---

# stackit_networks (Data Source)

Lists STACKIT networks in a project, optionally filtered by name or labels.

## Example Usage

```terraform
# List all networks in a project
data "stackit_networks" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

# List all networks in a project with a specific name and label
data "stackit_networks" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name_regex = "example"
labels = {
foo = "bar"
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `project_id` (String) STACKIT project ID to which the networks are associated.

### Optional

- `labels` (Map of String) Optional label selector to filter networks by labels.
- `name_regex` (String) Optional regular expression to filter networks by name.
- `region` (String) The resource region. If not defined, the provider region is used.

### Read-Only

- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`".
- `items` (Attributes List) List of networks matching the filters. (see [below for nested schema](#nestedatt--items))

<a id="nestedatt--items"></a>
### Nested Schema for `items`

Read-Only:

- `name` (String) The name of the network.
- `network_id` (String) The network ID.
13 changes: 13 additions & 0 deletions examples/data-sources/stackit_networks/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# List all networks in a project
data "stackit_networks" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

# List all networks in a project with a specific name and label
data "stackit_networks" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name_regex = "example"
labels = {
foo = "bar"
}
}
84 changes: 84 additions & 0 deletions stackit/internal/services/iaas/iaas_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6374,3 +6374,87 @@ func validateImportedNameservers(expectedIPs []string) func(states []*terraform.
return nil
}
}

func TestAccNetworksDataSource(t *testing.T) {
name2 := fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))
t.Logf("TestAccNetworksDataSource names: %s, %s", testutil.ConvertConfigVariable(testConfigNetworkVarsMin["name"]), name2)

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckDestroy,
Steps: []resource.TestStep{
{
ConfigVariables: testConfigNetworkVarsMin,
Config: fmt.Sprintf(`
%s
%s

resource "stackit_network" "network2" {
project_id = stackit_network.network.project_id
name = "%s"
labels = {
foo = "bar"
baz = "qux"
}
}

# No filter -> all networks returned
data "stackit_networks" "all" {
project_id = stackit_network.network.project_id
depends_on = [stackit_network.network2]
}

# name_regex -> matching returned
data "stackit_networks" "regex" {
project_id = stackit_network.network.project_id
name_regex = "%s"
depends_on = [stackit_network.network2]
}

# name_regex + labels -> both must be true
data "stackit_networks" "regex_and_labels" {
project_id = stackit_network.network.project_id
name_regex = "%s"
labels = {
foo = "bar"
}
depends_on = [stackit_network.network2]
}

# 2 labels -> both must be true
data "stackit_networks" "two_labels" {
project_id = stackit_network.network.project_id
labels = {
foo = "bar"
baz = "qux"
}
depends_on = [stackit_network.network2]
}
`,
testutil.NewConfigBuilder().Experiments(testutil.ExperimentNetwork).BuildProviderConfig(),
resourceNetworkMinConfig,
name2,
name2,
name2,
),
Check: resource.ComposeAggregateTestCheckFunc(
// All networks
resource.TestCheckResourceAttrSet("data.stackit_networks.all", "id"),
resource.TestCheckResourceAttr("data.stackit_networks.all", "items.#", "2"),

// regex filter
resource.TestCheckResourceAttr("data.stackit_networks.regex", "items.#", "1"),
resource.TestCheckResourceAttr("data.stackit_networks.regex", "items.0.name", name2),

// regex + label
resource.TestCheckResourceAttr("data.stackit_networks.regex_and_labels", "items.#", "1"),
resource.TestCheckResourceAttr("data.stackit_networks.regex_and_labels", "items.0.name", name2),

// two labels
resource.TestCheckResourceAttr("data.stackit_networks.two_labels", "items.#", "1"),
resource.TestCheckResourceAttr("data.stackit_networks.two_labels", "items.0.name", name2),
),
},
},
})
}
224 changes: 224 additions & 0 deletions stackit/internal/services/iaas/networks/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package networks

import (
"context"
"fmt"
"net/http"
"regexp"
"sort"
"strings"

"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"
iaas "github.com/stackitcloud/stackit-sdk-go/services/iaas/v2api"

"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
)

var (
_ datasource.DataSource = &networksDataSource{}
)

// NetworkItem represents a single network in the list.
type NetworkItem struct {
NetworkId types.String `tfsdk:"network_id"`
Name types.String `tfsdk:"name"`
}

// NetworksModel represents the model for the plural data source.
type NetworksModel struct {
Id types.String `tfsdk:"id"`
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
NameRegex types.String `tfsdk:"name_regex"`
Labels types.Map `tfsdk:"labels"`
Items []NetworkItem `tfsdk:"items"`
}

// NewNetworksDataSource creates a new instance of plural data source.
func NewNetworksDataSource() datasource.DataSource {
return &networksDataSource{}
}

// networksDataSource is the data source implementation for querying multiple networks.
type networksDataSource struct {
client *iaas.APIClient
providerData core.ProviderData
}

func (d *networksDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_networks"
}

func (d *networksDataSource) 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
}

apiClient := iaasUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
d.client = apiClient
tflog.Info(ctx, "IaaS client configured")
}

func (d *networksDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Lists STACKIT networks in a project, optionally filtered by name or labels.",
Description: "Lists STACKIT networks in a project, optionally filtered by name or labels.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`\".",
Computed: true,
},
"project_id": schema.StringAttribute{
Description: "STACKIT project ID to which the networks are associated.",
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"region": schema.StringAttribute{
Description: "The resource region. If not defined, the provider region is used.",
Optional: true,
},
"name_regex": schema.StringAttribute{
Description: "Optional regular expression to filter networks by name.",
Optional: true,
},
"labels": schema.MapAttribute{
Description: "Optional label selector to filter networks by labels.",
ElementType: types.StringType,
Optional: true,
Validators: validate.LabelValidators(),
},
"items": schema.ListNestedAttribute{
Description: "List of networks matching the filters.",
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"network_id": schema.StringAttribute{
Description: "The network ID.",
Computed: true,
},
"name": schema.StringAttribute{
Description: "The name of the network.",
Computed: true,
},
},
},
},
},
}
}

func (d *networksDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic
var model NetworksModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

ctx = core.InitProviderContext(ctx)

projectId := model.ProjectId.ValueString()
region := d.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)

// Compile the regex if provided
var compiledRegex *regexp.Regexp
var err error
if !model.NameRegex.IsNull() && model.NameRegex.ValueString() != "" {
compiledRegex, err = regexp.Compile(model.NameRegex.ValueString())
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Invalid name_regex", err.Error())
return
}
}

// Fetch all networks for the given project and region, applying label selector if provided
listReq := d.client.DefaultAPI.ListNetworks(ctx, projectId, region)
if !model.Labels.IsNull() && !model.Labels.IsUnknown() {
var labels map[string]string
diags = model.Labels.ElementsAs(ctx, &labels, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
var pairs []string
for k, v := range labels {
if v == "" {
pairs = append(pairs, k)
} else {
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
}
}
sort.Strings(pairs)
labelSelector := strings.Join(pairs, ",")
if labelSelector != "" {
listReq = listReq.LabelSelector(labelSelector)
}
}

networksResp, err := listReq.Execute()
if err != nil {
utils.LogError(
ctx,
&resp.Diagnostics,
err,
"Reading networks",
fmt.Sprintf("Networks could not be listed for project %q.", projectId),
map[int]string{
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
},
)
resp.State.RemoveResource(ctx)
return
}

if networksResp == nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Reading networks", "API response is nil")
return
}
if networksResp.Items == nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Reading networks", "network items in the API response are nil")
return
}

// Filter networks based on the compiled regex and populate the items list
items := make([]NetworkItem, 0)
for _, netResp := range networksResp.Items {
if compiledRegex != nil && !compiledRegex.MatchString(netResp.Name) {
continue
}

item := NetworkItem{
NetworkId: types.StringValue(netResp.Id),
Name: types.StringValue(netResp.Name),
}
items = append(items, item)
}

model.Id = utils.BuildInternalTerraformId(projectId, region)
model.Items = items

diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Networks read")
}
2 changes: 2 additions & 0 deletions stackit/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (
iaasNetworkAreaRoute "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networkarearoute"
iaasNetworkInterface "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networkinterface"
iaasNetworkInterfaceAttach "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networkinterfaceattach"
iaasNetworks "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/networks"
iaasProject "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/project"
iaasPublicIp "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/publicip"
iaasPublicIpAssociate "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/publicipassociate"
Expand Down Expand Up @@ -701,6 +702,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
iaasImage.NewImageDataSource,
iaasImageV2.NewImageV2DataSource,
iaasNetwork.NewNetworkDataSource,
iaasNetworks.NewNetworksDataSource,
iaasNetworkArea.NewNetworkAreaDataSource,
iaasNetworkAreaRegion.NewNetworkAreaRegionDataSource,
iaasNetworkAreaRoute.NewNetworkAreaRouteDataSource,
Expand Down