diff --git a/docs/data-sources/vpn_gateway_status.md b/docs/data-sources/vpn_gateway_status.md new file mode 100644 index 000000000..fa50f30c7 --- /dev/null +++ b/docs/data-sources/vpn_gateway_status.md @@ -0,0 +1,55 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_vpn_gateway_status Data Source - stackit" +subcategory: "" +description: |- + VPN Gateway Status data source schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on datasource level. +--- + +# stackit_vpn_gateway_status (Data Source) + +VPN Gateway Status data source schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level. + +## Example Usage + +```terraform +data "stackit_vpn_gateway_status" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + gateway_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} +``` + + +## Schema + +### Required + +- `gateway_id` (String) The server-generated UUID of the VPN gateway. +- `project_id` (String) STACKIT project ID associated with the VPN gateway. + +### Read-Only + +- `connections` (Attributes List) List of connections in the VPN gateway. (see [below for nested schema](#nestedatt--connections)) +- `display_name` (String) A user-friendly name for the VPN gateway. +- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`gateway_id`". +- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used. +- `tunnels` (Attributes List) List of the VPN tunnels in the gateway. (see [below for nested schema](#nestedatt--tunnels)) + + +### Nested Schema for `connections` + +Read-Only: + +- `connection_id` (String) ID of the VPN connection. +- `display_name` (String) Display name of the VPN connection. +- `enabled` (Boolean) Wether the VPN connection is enabled or not. + + + +### Nested Schema for `tunnels` + +Read-Only: + +- `internal_next_hop_ip` (String) The IPv4 address of the endpoint in the SNA. +- `name` (String) The name of the VPN tunnel. Possible values are: `tunnel1`, `tunnel2`. +- `public_ip` (String) The public IPv4 address of this endpoint. diff --git a/examples/data-sources/stackit_vpn_gateway_status/data-source.tf b/examples/data-sources/stackit_vpn_gateway_status/data-source.tf new file mode 100644 index 000000000..cd4559e44 --- /dev/null +++ b/examples/data-sources/stackit_vpn_gateway_status/data-source.tf @@ -0,0 +1,4 @@ +data "stackit_vpn_gateway_status" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + gateway_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +} diff --git a/stackit/internal/services/vpn/gateway_status/datasource.go b/stackit/internal/services/vpn/gateway_status/datasource.go new file mode 100644 index 000000000..f56134633 --- /dev/null +++ b/stackit/internal/services/vpn/gateway_status/datasource.go @@ -0,0 +1,338 @@ +package gateway_status + +import ( + "context" + "errors" + "fmt" + "net/http" + + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1api" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/vpn/utils" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &vpnGatewayStatusDataSource{} + _ datasource.DataSourceWithConfigure = &vpnGatewayStatusDataSource{} +) + +type vpnGatewayStatusDataSource struct { + client *vpn.APIClient + providerData core.ProviderData +} + +type Model struct { + Id types.String `tfsdk:"id"` // needed by TF + GatewayId types.String `tfsdk:"gateway_id"` + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + Connections types.List `tfsdk:"connections"` + DisplayName types.String `tfsdk:"display_name"` + Tunnels types.List `tfsdk:"tunnels"` +} + +type Connection struct { + DisplayName types.String `tfsdk:"display_name"` + Enabled types.Bool `tfsdk:"enabled"` + Id types.String `tfsdk:"connection_id"` +} + +type Tunnel struct { + InternalNextHopIP types.String `tfsdk:"internal_next_hop_ip"` + Name types.String `tfsdk:"name"` + PublicIP types.String `tfsdk:"public_ip"` +} + +var connectionType = map[string]attr.Type{ + "display_name": basetypes.StringType{}, + "enabled": basetypes.BoolType{}, + "connection_id": basetypes.StringType{}, +} + +var tunnelType = map[string]attr.Type{ + "internal_next_hop_ip": basetypes.StringType{}, + "name": basetypes.StringType{}, + "public_ip": basetypes.StringType{}, +} + +func NewVPNGatewayStatusDataSource() datasource.DataSource { + return &vpnGatewayStatusDataSource{} +} + +func (d *vpnGatewayStatusDataSource) 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 = utils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "VPN client configured") +} + +func (d *vpnGatewayStatusDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_vpn_gateway_status" +} + +var schemaDescriptions = map[string]string{ + "id": "Terraform's internal resource identifier. Structured as \"`project_id`,`region`,`gateway_id`\".", + "gateway_id": "The server-generated UUID of the VPN gateway.", + "project_id": "STACKIT project ID associated with the VPN gateway.", + "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.", + "connections": "List of connections in the VPN gateway.", + "connection_display_name": "Display name of the VPN connection.", + "connection_enabled": "Wether the VPN connection is enabled or not.", + "connection_id": "ID of the VPN connection.", + "display_name": "A user-friendly name for the VPN gateway.", + "tunnels": "List of the VPN tunnels in the gateway.", + "tunnel_internal_next_hop_ip": "The IPv4 address of the endpoint in the SNA.", + "tunnel_name": fmt.Sprintf("The name of the VPN tunnel. %s", tfutils.FormatPossibleValues(sdkUtils.EnumSliceToStringSlice(vpn.AllowedVPNTunnelsNameEnumValues)...)), + "tunnel_public_ip": "The public IPv4 address of this endpoint.", +} + +func (d *vpnGatewayStatusDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("VPN Gateway Status data source schema. %s", core.DatasourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: schemaDescriptions["id"], + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: schemaDescriptions["project_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: schemaDescriptions["region"], + Computed: true, + }, + "gateway_id": schema.StringAttribute{ + Description: schemaDescriptions["gateway_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "connections": schema.ListNestedAttribute{ + Description: schemaDescriptions["connections"], + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "display_name": schema.StringAttribute{ + Description: schemaDescriptions["connection_display_name"], + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: schemaDescriptions["connection_enabled"], + Computed: true, + }, + "connection_id": schema.StringAttribute{ + Description: schemaDescriptions["connection_id"], + Computed: true, + }, + }, + }, + }, + "display_name": schema.StringAttribute{ + Description: schemaDescriptions["display_name"], + Computed: true, + }, + "tunnels": schema.ListNestedAttribute{ + Description: schemaDescriptions["tunnels"], + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "internal_next_hop_ip": schema.StringAttribute{ + Description: schemaDescriptions["tunnel_internal_next_hop_ip"], + Computed: true, + }, + "name": schema.StringAttribute{ + Description: schemaDescriptions["tunnel_name"], + Computed: true, + }, + "public_ip": schema.StringAttribute{ + Description: schemaDescriptions["tunnel_public_ip"], + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *vpnGatewayStatusDataSource) 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 + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := d.providerData.GetRegionWithOverride(model.Region) + gatewayId := model.GatewayId.ValueString() + + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "gateway_id", gatewayId) + + gatewayResponse, err := d.client.DefaultAPI.GetGatewayStatus(ctx, projectId, region, gatewayId).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + ok := errors.As(err, &oapiErr) + if ok && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading VPN gateway", fmt.Sprintf("Calling API: %v", err)) + return + } + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, gatewayResponse, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading VPN gateway", fmt.Sprintf("Processing response: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "VPN gateway read", map[string]any{ + "gateway_id": gatewayId, + }) +} + +func mapFields(ctx context.Context, gatewayStatus *vpn.GatewayStatusResponse, model *Model, region string) error { + if gatewayStatus == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + var gatewayId string + if model.GatewayId.ValueString() != "" { + gatewayId = model.GatewayId.ValueString() + } else if gatewayStatus.Id != nil { + gatewayId = *gatewayStatus.Id + } else { + return fmt.Errorf("gateway id not present") + } + + model.Id = tfutils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, gatewayId) + model.GatewayId = types.StringValue(gatewayId) + model.Region = types.StringValue(region) + + if gatewayStatus.DisplayName != nil { + model.DisplayName = types.StringValue(*gatewayStatus.DisplayName) + } + + tfConnections, err := mapConnections(ctx, gatewayStatus.Connections) + if err != nil { + return fmt.Errorf("map tunnels: %w", err) + } else if tfConnections != nil { + model.Connections = *tfConnections + } + + tfTunnels, err := mapTunnels(ctx, gatewayStatus.Tunnels) + if err != nil { + return fmt.Errorf("map tunnels: %w", err) + } else if tfTunnels != nil { + model.Tunnels = *tfTunnels + } + + return nil +} + +func mapConnections(ctx context.Context, vpnConnections []vpn.ConnectionStatusResponse) (*basetypes.ListValue, error) { + connections := []attr.Value{} + + for _, connectionItem := range vpnConnections { + connection := Connection{} + + if connectionItem.DisplayName != nil { + connection.DisplayName = types.StringValue(string(*connectionItem.DisplayName)) + } + if connectionItem.Enabled != nil { + connection.Enabled = types.BoolValue(*connectionItem.Enabled) + } + if connectionItem.Id != nil { + connection.Id = types.StringValue(string(*connectionItem.Id)) + } + + connectionValue, diags := types.ObjectValueFrom(ctx, connectionType, connection) + if diags.HasError() { + return nil, fmt.Errorf("mapping connection: %w", core.DiagsToError(diags)) + } + + connections = append(connections, connectionValue) + } + + tfConnections, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: connectionType}, connections) + if diags.HasError() { + return nil, fmt.Errorf("mapping connections: %w", core.DiagsToError(diags)) + } + + return &tfConnections, nil +} + +func mapTunnels(ctx context.Context, vpnTunnels []vpn.VPNTunnels) (*basetypes.ListValue, error) { + tunnels := []attr.Value{} + + for _, tunnelItem := range vpnTunnels { + tunnel := Tunnel{} + + if tunnelItem.InternalNextHopIP != nil { + tunnel.InternalNextHopIP = types.StringValue(string(*tunnelItem.InternalNextHopIP)) + } + if tunnelItem.Name != nil { + tunnel.Name = types.StringValue(string(*tunnelItem.Name)) + } + if tunnelItem.PublicIP != nil { + tunnel.PublicIP = types.StringValue(string(*tunnelItem.PublicIP)) + } + + tunnelValue, diags := types.ObjectValueFrom(ctx, tunnelType, tunnel) + if diags.HasError() { + return nil, fmt.Errorf("mapping tunnel: %w", core.DiagsToError(diags)) + } + + tunnels = append(tunnels, tunnelValue) + } + + tfTunnels, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: tunnelType}, tunnels) + if diags.HasError() { + return nil, fmt.Errorf("mapping tunnels: %w", core.DiagsToError(diags)) + } + + return &tfTunnels, nil +} diff --git a/stackit/internal/services/vpn/gateway_status/datasource_test.go b/stackit/internal/services/vpn/gateway_status/datasource_test.go new file mode 100644 index 000000000..c0a35a1cd --- /dev/null +++ b/stackit/internal/services/vpn/gateway_status/datasource_test.go @@ -0,0 +1,242 @@ +package gateway_status + +import ( + "context" + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1api" +) + +const ( + testRegion = "eu01" + testDisplayName = "Gateway" + testTunnel1InternalNextHopIP = "123.45.67.89" + testTunnel1PublicIP = "98.76.54.32" + testTunnel2InternalNextHopIP = "123.45.67.89" + testTunnel2PublicIP = "98.76.54.32" +) + +var ( + testProjectId = uuid.NewString() + testGatewayId = uuid.NewString() + testId = testProjectId + "," + testRegion + "," + testGatewayId +) + +func fixtureInput(mods ...func(m *vpn.GatewayStatusResponse)) *vpn.GatewayStatusResponse { + resp := &vpn.GatewayStatusResponse{ + Id: new(testGatewayId), + Connections: []vpn.ConnectionStatusResponse{ + { + DisplayName: new("Conn1"), + Enabled: new(true), + Id: new("foo"), + }, + { + DisplayName: new("Conn2"), + Enabled: new(false), + Id: new("bar"), + }, + }, + DisplayName: new(testDisplayName), + Tunnels: []vpn.VPNTunnels{ + { + InternalNextHopIP: new(testTunnel1InternalNextHopIP), + Name: vpn.VPNTUNNELSNAME_TUNNEL1.Ptr(), + PublicIP: new(testTunnel1PublicIP), + }, + { + InternalNextHopIP: new(testTunnel2InternalNextHopIP), + Name: vpn.VPNTUNNELSNAME_TUNNEL2.Ptr(), + PublicIP: new(testTunnel2PublicIP), + }, + }, + } + for _, mod := range mods { + mod(resp) + } + return resp +} + +func fixtureModel(mods ...func(m *Model)) *Model { + resp := &Model{ + ProjectId: types.StringValue(testProjectId), + Region: types.StringValue(testRegion), + Id: types.StringValue(testId), + GatewayId: types.StringValue(testGatewayId), + Connections: types.ListValueMust(types.ObjectType{AttrTypes: connectionType}, []attr.Value{ + types.ObjectValueMust(connectionType, map[string]attr.Value{ + "display_name": types.StringValue("Conn1"), + "enabled": types.BoolValue(true), + "connection_id": types.StringValue("foo"), + }), + types.ObjectValueMust(connectionType, map[string]attr.Value{ + "display_name": types.StringValue("Conn2"), + "enabled": types.BoolValue(false), + "connection_id": types.StringValue("bar"), + }), + }), + DisplayName: types.StringValue(testDisplayName), + Tunnels: types.ListValueMust(types.ObjectType{AttrTypes: tunnelType}, []attr.Value{ + types.ObjectValueMust(tunnelType, map[string]attr.Value{ + "internal_next_hop_ip": types.StringValue(testTunnel1InternalNextHopIP), + "name": types.StringValue(string(vpn.VPNTUNNELSNAME_TUNNEL1)), + "public_ip": types.StringValue(testTunnel1PublicIP), + }), + types.ObjectValueMust(tunnelType, map[string]attr.Value{ + "internal_next_hop_ip": types.StringValue(testTunnel2InternalNextHopIP), + "name": types.StringValue(string(vpn.VPNTUNNELSNAME_TUNNEL2)), + "public_ip": types.StringValue(testTunnel2PublicIP), + }), + }), + } + for _, mod := range mods { + mod(resp) + } + return resp +} + +func TestMapDatasourceFields(t *testing.T) { + tests := []struct { + name string + region string + state *Model + input *vpn.GatewayStatusResponse + expected *Model + isValid bool + }{ + { + name: "default_values", + region: "eu01", + state: &Model{ + ProjectId: types.StringValue(testProjectId), + }, + input: fixtureInput(), + expected: fixtureModel(), + isValid: true, + }, + { + name: "no_input", + region: "eu01", + state: &Model{ + ProjectId: types.StringValue(testProjectId), + GatewayId: types.StringValue(testGatewayId), + }, + input: nil, + expected: nil, + isValid: false, + }, + { + name: "no_model", + region: "eu01", + state: nil, + input: &vpn.GatewayStatusResponse{}, + expected: nil, + isValid: false, + }, + { + name: "no_gateway_id", + region: "eu01", + state: &Model{ + ProjectId: types.StringValue(testProjectId), + }, + input: &vpn.GatewayStatusResponse{}, + expected: nil, + isValid: false, + }, + { + name: "empty_input", + region: "eu01", + state: &Model{ + ProjectId: types.StringValue(testProjectId), + GatewayId: types.StringValue(testGatewayId), + }, + input: &vpn.GatewayStatusResponse{}, + expected: &Model{ + Id: types.StringValue(testId), + ProjectId: types.StringValue(testProjectId), + GatewayId: types.StringValue(testGatewayId), + Region: types.StringValue(testRegion), + Connections: types.ListValueMust(types.ObjectType{AttrTypes: connectionType}, []attr.Value{}), + Tunnels: types.ListValueMust(types.ObjectType{AttrTypes: tunnelType}, []attr.Value{}), + }, + isValid: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + if err := mapFields(ctx, tt.input, tt.state, tt.region); (err == nil) != tt.isValid { + t.Errorf("unexpected error: %s", err) + } + if tt.isValid { + if diff := cmp.Diff(tt.state, tt.expected); diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestMapTunnels(t *testing.T) { + tests := []struct { + name string + input []vpn.VPNTunnels + expected *basetypes.ListValue + isValid bool + }{ + { + name: "default_values", + input: []vpn.VPNTunnels{ + { + InternalNextHopIP: new(testTunnel1InternalNextHopIP), + Name: vpn.VPNTUNNELSNAME_TUNNEL1.Ptr(), + PublicIP: new(testTunnel1PublicIP), + }, + { + InternalNextHopIP: new(testTunnel2InternalNextHopIP), + Name: vpn.VPNTUNNELSNAME_TUNNEL2.Ptr(), + PublicIP: new(testTunnel2PublicIP), + }, + }, + expected: new(types.ListValueMust(types.ObjectType{AttrTypes: tunnelType}, []attr.Value{ + types.ObjectValueMust(tunnelType, map[string]attr.Value{ + "internal_next_hop_ip": types.StringValue(testTunnel1InternalNextHopIP), + "name": types.StringValue(string(vpn.VPNTUNNELSNAME_TUNNEL1)), + "public_ip": types.StringValue(testTunnel1PublicIP), + }), + types.ObjectValueMust(tunnelType, map[string]attr.Value{ + "internal_next_hop_ip": types.StringValue(testTunnel2InternalNextHopIP), + "name": types.StringValue(string(vpn.VPNTUNNELSNAME_TUNNEL2)), + "public_ip": types.StringValue(testTunnel2PublicIP), + }), + })), + isValid: true, + }, + { + name: "empty", + input: []vpn.VPNTunnels{}, + expected: new(types.ListValueMust(types.ObjectType{AttrTypes: tunnelType}, []attr.Value{})), + isValid: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + tfTunnels, err := mapTunnels(ctx, tt.input) + if (err == nil) != tt.isValid { + t.Errorf("unexpected error: %s", err) + } + if tt.isValid { + if !reflect.DeepEqual(tfTunnels, tt.expected) { + t.Errorf("ParseProviderData() got = %v, want %v", tfTunnels, tt.expected) + } + } + }) + } +} diff --git a/stackit/internal/services/vpn/vpn_acc_test.go b/stackit/internal/services/vpn/vpn_acc_test.go index 535e2061e..387a5ed1f 100644 --- a/stackit/internal/services/vpn/vpn_acc_test.go +++ b/stackit/internal/services/vpn/vpn_acc_test.go @@ -127,6 +127,38 @@ func TestAccVpnGatewayResourceMin(t *testing.T) { resource.TestCheckResourceAttrPair("data.stackit_vpn_gateway.gateway", "gateway_id", "stackit_vpn_gateway.gateway", "gateway_id"), ), }, + // Status data source + { + ConfigVariables: gatewayMinVars, + Config: fmt.Sprintf(` + %s + %s + + data "stackit_vpn_gateway_status" "gateway" { + project_id = stackit_vpn_gateway.gateway.project_id + gateway_id = stackit_vpn_gateway.gateway.gateway_id + } + `, + testutil.NewConfigBuilder().BuildProviderConfig(), gatewayMinConfig, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair("data.stackit_vpn_gateway_status.gateway", "gateway_id", "stackit_vpn_gateway.gateway", "gateway_id"), + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "region", testutil.Region), + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "display_name", testutil.ConvertConfigVariable(gatewayMinVars["display_name"])), + + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "tunnels.#", "2"), + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "tunnels.0.name", string(vpn.VPNTUNNELSNAME_TUNNEL1)), + resource.TestCheckResourceAttrSet("data.stackit_vpn_gateway_status.gateway", "tunnels.0.internal_next_hop_ip"), + resource.TestCheckResourceAttrSet("data.stackit_vpn_gateway_status.gateway", "tunnels.0.public_ip"), + + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "tunnels.1.name", string(vpn.VPNTUNNELSNAME_TUNNEL2)), + resource.TestCheckResourceAttrSet("data.stackit_vpn_gateway_status.gateway", "tunnels.1.internal_next_hop_ip"), + resource.TestCheckResourceAttrSet("data.stackit_vpn_gateway_status.gateway", "tunnels.1.public_ip"), + + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "connections.#", "0"), + ), + }, // Update { ConfigVariables: gatewayMinVarsUpdated, @@ -226,6 +258,38 @@ func TestAccVpnGatewayResourceMax(t *testing.T) { resource.TestCheckResourceAttrPair("data.stackit_vpn_gateway.gateway", "gateway_id", "stackit_vpn_gateway.gateway", "gateway_id"), ), }, + // Status data source + { + ConfigVariables: gatewayMaxVars, + Config: fmt.Sprintf(` + %s + %s + + data "stackit_vpn_gateway_status" "gateway" { + project_id = stackit_vpn_gateway.gateway.project_id + gateway_id = stackit_vpn_gateway.gateway.gateway_id + } + `, + testutil.NewConfigBuilder().BuildProviderConfig(), gatewayMaxConfig, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair("data.stackit_vpn_gateway_status.gateway", "gateway_id", "stackit_vpn_gateway.gateway", "gateway_id"), + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "region", testutil.Region), + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "display_name", testutil.ConvertConfigVariable(gatewayMaxVars["display_name"])), + + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "tunnels.#", "2"), + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "tunnels.0.name", string(vpn.VPNTUNNELSNAME_TUNNEL1)), + resource.TestCheckResourceAttrSet("data.stackit_vpn_gateway_status.gateway", "tunnels.0.internal_next_hop_ip"), + resource.TestCheckResourceAttrSet("data.stackit_vpn_gateway_status.gateway", "tunnels.0.public_ip"), + + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "tunnels.1.name", string(vpn.VPNTUNNELSNAME_TUNNEL2)), + resource.TestCheckResourceAttrSet("data.stackit_vpn_gateway_status.gateway", "tunnels.1.internal_next_hop_ip"), + resource.TestCheckResourceAttrSet("data.stackit_vpn_gateway_status.gateway", "tunnels.1.public_ip"), + + resource.TestCheckResourceAttr("data.stackit_vpn_gateway_status.gateway", "connections.#", "0"), + ), + }, // Update { ConfigVariables: gatewayMaxVarsUpdated, diff --git a/stackit/provider.go b/stackit/provider.go index 0470af21a..7c653f474 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -129,6 +129,7 @@ import ( telemetryRouterDestination "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/telemetryrouter/destination" telemetryRouterInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/telemetryrouter/instance" vpnGateway "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/vpn/gateway" + vpnGatewayStatus "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/vpn/gateway_status" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" ) @@ -757,6 +758,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource telemetryRouterDestination.NewTelemetryRouterDestinationDataSource, telemetryLink.NewTelemetryLinkDataSource, vpnGateway.NewVPNGatewayDataSource, + vpnGatewayStatus.NewVPNGatewayStatusDataSource, } dataSources = append(dataSources, customRole.NewCustomRoleDataSources()...) dataSources = append(dataSources, iamRoleBindingsV1.NewRoleBindingsDatasources()...)