Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
version: 2
interactions: []
86 changes: 86 additions & 0 deletions internal/functions/zone_from_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package functions

import (
"context"
"strings"

"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/scaleway/scaleway-sdk-go/scw"
)

var _ function.Function = &ZoneFromID{}

type ZoneFromID struct{}

func NewZoneFromID() function.Function {
return &ZoneFromID{}
}

func (f *ZoneFromID) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) {
resp.Name = "zone_from_id"
}

func (f *ZoneFromID) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) {
resp.Definition = function.Definition{
Summary: "Extract a zone from the ID",
Description: "Given an ID string value, returns the zone contained in the ID.",
Parameters: []function.Parameter{
function.StringParameter{
Name: "id",
Description: "id to extract the zone from",
},
function.BoolParameter{
Name: "skip_zone_validation",
Description: "If true, will skip zone validation with the zone known by the Scaleway SDK.",
AllowNullValue: true,
},
},
Return: function.StringReturn{},
}
}

func (f *ZoneFromID) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) {
var (
input types.String
skipZoneValidation types.Bool
)

resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &input, &skipZoneValidation))

if input.IsNull() || input.IsUnknown() {
resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, input))

return
}

if input.ValueString() == "" {
resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(0, "bad zone format, available zones are: fr-par-1, nl-ams-1, pl-waw-1"))
resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, basetypes.NewStringUnknown()))

return
}

if skipZoneValidation.IsNull() || skipZoneValidation.IsUnknown() {
skipZoneValidation = basetypes.NewBoolValue(false)
}

idParts := strings.Split(input.ValueString(), "/")
if len(idParts) < 2 {
resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(0, "cannot parse ID: invalid format"))
resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, basetypes.NewStringUnknown()))

return
}

zone, err := scw.ParseZone(idParts[0])
if err != nil && !skipZoneValidation.ValueBool() {
resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(0, err.Error()))
resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, basetypes.NewStringUnknown()))

return
}

resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, types.StringValue(zone.String())))
}
154 changes: 154 additions & 0 deletions internal/functions/zone_from_id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package functions_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/functions"
)

func TestZoneFromIDFunctionRun(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
expected function.RunResponse
request function.RunRequest
}{
// The example implementation uses the Go built-in string type, however
// if AllowNullValue was enabled and *string or types.String was used,
// this test case shows how the function would be expected to behave.
"null": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringNull(), types.BoolNull()}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringNull()),
},
},
// The example implementation uses the Go built-in string type, however
// if AllowUnknownValues was enabled and types.String was used,
// this test case shows how the function would be expected to behave.
"unknown": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringUnknown(), types.BoolNull()}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringUnknown()),
},
},
// Test valid ID format - extracts zone from ID
"valid-id-format": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("fr-par-1/1111-1111-1111-1111-1111111111111111"), types.BoolNull()}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringValue("fr-par-1")),
},
},
// Test another valid ID format
"valid-id-format-amsterdam": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("nl-ams-1/1111-1111-1111-1111-1111111111111111"), types.BoolNull()}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringValue("nl-ams-1")),
},
},
"valid-id-multi-part": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("nl-ams-1/foo/bar"), types.BoolNull()}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringValue("nl-ams-1")),
},
},
"unknown-id-valid-format": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("xx-yyy-1/11111111-1111-1111-1111-111111111111"), types.BoolValue(true)}),
},
expected: function.RunResponse{
Result: function.NewResultData(types.StringValue("xx-yyy-1")),
},
},
// Test invalid format - empty string
"empty-string": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue(""), types.BoolNull()}),
},
expected: function.RunResponse{
Error: function.NewArgumentFuncError(0, "bad zone format, available zones are: fr-par-1, nl-ams-1, pl-waw-1"),
Result: function.NewResultData(types.StringUnknown()),
},
},
// Test invalid format - malformed ID
"malformed-id": {
request: function.RunRequest{
Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("invalid-format"), types.BoolNull()}),
},
expected: function.RunResponse{
Error: function.NewArgumentFuncError(0, "cannot parse ID: invalid format"),
Result: function.NewResultData(types.StringUnknown()),
},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

got := function.RunResponse{
Result: function.NewResultData(types.StringUnknown()),
}

functions.NewZoneFromID().Run(context.Background(), testCase.request, &got)

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}

func TestAccProviderFunction_Zone_From_ID(t *testing.T) {
tt := acctest.NewTestTools(t)
defer tt.Cleanup()

resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: tt.ProviderFactories,
Steps: []resource.TestStep{
{
// Can get the zone from a resource's id in one step
Config: `
# terraform block required for provider function to be found
erraform {
required_providers {
scaleway = {
source = "scaleway/scaleway"
}
}
}

resource "scaleway_instance_server" "main" {
name = "terraform_test_zone_from_id"
type = "DEV1-S"
image = "fr-par-1/ubuntu_jammy"
zone = "fr-par-1"
}

output "zone" {
value = provider::scaleway::zone_from_id(scaleway_instance_server.main.id)
}
`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckOutput("zone", "fr-par-1"),
),
},
},
})
}
1 change: 1 addition & 0 deletions provider/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,5 +247,6 @@ func (p *ScalewayProvider) ListResources(_ context.Context) []func() list.ListRe
func (p *ScalewayProvider) Functions(_ context.Context) []func() function.Function {
return []func() function.Function{
functions.NewRegionFromID,
functions.NewZoneFromID,
}
}
Loading