Skip to content

Commit 3b82cc0

Browse files
Mia-Crossremyleone
andauthored
feat(function): add support for vpc integration (#3160)
Co-authored-by: Rémy Léone <[email protected]>
1 parent e052f9f commit 3b82cc0

File tree

11 files changed

+7467
-4
lines changed

11 files changed

+7467
-4
lines changed

docs/resources/function.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ The following arguments are supported:
9999

100100
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the functions namespace is associated with.
101101

102+
- `private_network_id` (Optional) The ID of the Private Network the function is connected to.
103+
104+
~> **Important** This feature is currently in beta and requires a namespace with VPC integration activated by setting the `activate_vpc_integration` attribute to `true`.
102105

103106
## Attributes Reference
104107

docs/resources/function_namespace.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ The following arguments are supported:
4040

4141
- `secret_environment_variables` - (Optional) The secret environment variables of the namespace.
4242

43+
- `activate_vpc_integration` - (Optional) Activates VPC integration for the namespace. Functions of a namespace with VPC integration activated will be able to connect to a Private Network.
44+
45+
~> **Important** Updates to `activate_vpc_integration` will recreate the namespace.
46+
4347
## Attributes Reference
4448

4549
The `scaleway_function_namespace` resource exports certain attributes once the Functions namespace has been created. These attributes can be referenced in other parts of your Terraform configuration.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ require (
2828
github.com/nats-io/jwt/v2 v2.7.4
2929
github.com/nats-io/nats.go v1.38.0
3030
github.com/robfig/cron/v3 v3.0.1
31-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250604134054-a06406d42247
31+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250613133518-6987cd48643b
3232
github.com/stretchr/testify v1.10.0
3333
golang.org/x/crypto v0.38.0
3434
gopkg.in/dnaeon/go-vcr.v3 v3.2.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
447447
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
448448
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
449449
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
450-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250604134054-a06406d42247 h1:wlIvcSpGl3mGDpQmwrZHnYMIlB7Mwx3bhg151LG22Ws=
451-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250604134054-a06406d42247/go.mod h1:qiGzapFyNPFwBBLJ+hTFykKSnU95n1zL64+o1ubmwf0=
450+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250613133518-6987cd48643b h1:7V7T9XzUl+B/6zHeNxyX0DV3OVjCSedA0stHo0BNKZw=
451+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33.0.20250613133518-6987cd48643b/go.mod h1:zFWiHphneiey3s8HOtAEnGrRlWivNaxW5T6d5Xfco7g=
452452
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
453453
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
454454
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=

internal/acctest/validate_cassettes_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func exceptionsCassettesCases() map[string]struct{} {
2929
"../services/secret/testdata/secret-version-type.cassette.yaml": {},
3030
"../services/file/testdata/file-system-invalid-size-granularity-fails.cassette.yaml": {},
3131
"../services/file/testdata/file-system-size-too-small-fails.cassette.yaml": {},
32+
"../services/function/testdata/function-namespace-vpc-integration.cassette.yaml": {},
3233
}
3334
}
3435

internal/services/function/function.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/scaleway/terraform-provider-scaleway/v2/internal/cdf"
1515
"github.com/scaleway/terraform-provider-scaleway/v2/internal/dsf"
1616
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
17+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
1718
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
1819
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
1920
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
@@ -170,6 +171,11 @@ func ResourceFunction() *schema.Resource {
170171
Computed: true,
171172
Description: "The native function domain name.",
172173
},
174+
"private_network_id": {
175+
Type: schema.TypeString,
176+
Optional: true,
177+
Description: "ID of the Private Network the container is connected to",
178+
},
173179
"region": regional.Schema(),
174180
"organization_id": account.OrganizationIDSchema(),
175181
"project_id": account.ProjectIDSchema(),
@@ -214,6 +220,10 @@ func ResourceFunctionCreate(ctx context.Context, d *schema.ResourceData, m any)
214220
req.Timeout = &scw.Duration{Seconds: int64(timeout.(int))}
215221
}
216222

223+
if pnID, ok := d.GetOk("private_network_id"); ok {
224+
req.PrivateNetworkID = types.ExpandStringPtr(locality.ExpandID(pnID.(string)))
225+
}
226+
217227
f, err := api.CreateFunction(req, scw.WithContext(ctx))
218228
if err != nil {
219229
return diag.FromErr(err)
@@ -324,6 +334,12 @@ func ResourceFunctionRead(ctx context.Context, d *schema.ResourceData, m any) di
324334
_ = d.Set("secret_environment_variables", flattenFunctionSecrets(f.SecretEnvironmentVariables))
325335
_ = d.Set("tags", types.FlattenSliceString(f.Tags))
326336

337+
if f.PrivateNetworkID != nil {
338+
_ = d.Set("private_network_id", regional.NewID(region, types.FlattenStringPtr(f.PrivateNetworkID).(string)).String())
339+
} else {
340+
_ = d.Set("private_network_id", nil)
341+
}
342+
327343
return diags
328344
}
329345

@@ -415,6 +431,11 @@ func ResourceFunctionUpdate(ctx context.Context, d *schema.ResourceData, m any)
415431
updated = true
416432
}
417433

434+
if d.HasChanges("private_network_id") {
435+
req.PrivateNetworkID = types.ExpandUpdatedStringPtr(locality.ExpandID(d.Get("private_network_id")))
436+
updated = true
437+
}
438+
418439
if updated {
419440
_, err = api.UpdateFunction(req, scw.WithContext(ctx))
420441
if err != nil {

internal/services/function/function_test.go

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
1313
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
1414
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/function"
15+
vpcchecks "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/vpc/testfuncs"
1516
)
1617

1718
func TestAccFunction_Basic(t *testing.T) {
@@ -410,6 +411,158 @@ func TestAccFunction_Sandbox(t *testing.T) {
410411
})
411412
}
412413

414+
func TestAccFunction_PrivateNetwork(t *testing.T) {
415+
tt := acctest.NewTestTools(t)
416+
defer tt.Cleanup()
417+
resource.ParallelTest(t, resource.TestCase{
418+
PreCheck: func() { acctest.PreCheck(t) },
419+
ProviderFactories: tt.ProviderFactories,
420+
CheckDestroy: resource.ComposeTestCheckFunc(
421+
testAccCheckFunctionNamespaceDestroy(tt),
422+
testAccCheckFunctionDestroy(tt),
423+
vpcchecks.CheckPrivateNetworkDestroy(tt),
424+
),
425+
Steps: []resource.TestStep{
426+
{
427+
Config: `
428+
resource scaleway_vpc_private_network pn00 {
429+
name = "test-acc-function-pn-pn00"
430+
}
431+
resource scaleway_vpc_private_network pn01 {
432+
name = "test-acc-function-pn-pn01"
433+
}
434+
435+
resource scaleway_function_namespace main {
436+
activate_vpc_integration = true
437+
}
438+
439+
resource scaleway_function f00 {
440+
name = "test-acc-function-pn-00"
441+
namespace_id = scaleway_function_namespace.main.id
442+
privacy = "private"
443+
runtime = "go123"
444+
handler = "Handle"
445+
sandbox = "v1"
446+
private_network_id = scaleway_vpc_private_network.pn00.id
447+
}
448+
`,
449+
Check: resource.ComposeTestCheckFunc(
450+
testAccCheckFunctionExists(tt, "scaleway_function.f00"),
451+
resource.TestCheckResourceAttr("scaleway_function_namespace.main", "activate_vpc_integration", "true"),
452+
resource.TestCheckResourceAttr("scaleway_function.f00", "sandbox", "v1"),
453+
resource.TestCheckResourceAttrPair("scaleway_function.f00", "private_network_id", "scaleway_vpc_private_network.pn00", "id"),
454+
),
455+
},
456+
{
457+
Config: `
458+
resource scaleway_vpc_private_network pn00 {
459+
name = "test-acc-function-pn-pn00"
460+
}
461+
resource scaleway_vpc_private_network pn01 {
462+
name = "test-acc-function-pn-pn01"
463+
}
464+
465+
resource scaleway_function_namespace main {
466+
activate_vpc_integration = true
467+
}
468+
469+
resource scaleway_function f00 {
470+
name = "test-acc-function-pn-f00"
471+
namespace_id = scaleway_function_namespace.main.id
472+
privacy = "private"
473+
runtime = "go123"
474+
handler = "Handle"
475+
sandbox = "v1"
476+
private_network_id = scaleway_vpc_private_network.pn00.id
477+
}
478+
479+
resource scaleway_function f01 {
480+
name = "test-acc-function-pn-f01"
481+
namespace_id = scaleway_function_namespace.main.id
482+
privacy = "private"
483+
runtime = "go123"
484+
handler = "Handle"
485+
sandbox = "v1"
486+
private_network_id = scaleway_vpc_private_network.pn00.id
487+
}
488+
489+
resource scaleway_function f02 {
490+
name = "test-acc-function-pn-f02"
491+
namespace_id = scaleway_function_namespace.main.id
492+
privacy = "private"
493+
runtime = "go123"
494+
handler = "Handle"
495+
sandbox = "v1"
496+
private_network_id = scaleway_vpc_private_network.pn00.id
497+
}
498+
`,
499+
Check: resource.ComposeTestCheckFunc(
500+
testAccCheckFunctionExists(tt, "scaleway_function.f00"),
501+
testAccCheckFunctionExists(tt, "scaleway_function.f01"),
502+
testAccCheckFunctionExists(tt, "scaleway_function.f02"),
503+
resource.TestCheckResourceAttr("scaleway_function.f00", "sandbox", "v1"),
504+
resource.TestCheckResourceAttr("scaleway_function.f01", "sandbox", "v1"),
505+
resource.TestCheckResourceAttr("scaleway_function.f02", "sandbox", "v1"),
506+
resource.TestCheckResourceAttrPair("scaleway_function.f00", "private_network_id", "scaleway_vpc_private_network.pn00", "id"),
507+
resource.TestCheckResourceAttrPair("scaleway_function.f01", "private_network_id", "scaleway_vpc_private_network.pn00", "id"),
508+
resource.TestCheckResourceAttrPair("scaleway_function.f02", "private_network_id", "scaleway_vpc_private_network.pn00", "id"),
509+
),
510+
},
511+
{
512+
Config: `
513+
resource scaleway_vpc_private_network pn00 {
514+
name = "test-acc-function-pn-pn00"
515+
}
516+
resource scaleway_vpc_private_network pn01 {
517+
name = "test-acc-function-pn-pn01"
518+
}
519+
520+
resource scaleway_function_namespace main {
521+
activate_vpc_integration = true
522+
}
523+
524+
resource scaleway_function f00 {
525+
name = "test-acc-function-pn-f00"
526+
namespace_id = scaleway_function_namespace.main.id
527+
privacy = "private"
528+
runtime = "go123"
529+
handler = "Handle"
530+
sandbox = "v1"
531+
}
532+
533+
resource scaleway_function f01 {
534+
name = "test-acc-function-pn-f01"
535+
namespace_id = scaleway_function_namespace.main.id
536+
privacy = "private"
537+
runtime = "go123"
538+
handler = "Handle"
539+
sandbox = "v1"
540+
private_network_id = scaleway_vpc_private_network.pn01.id
541+
}
542+
543+
resource scaleway_function f02 {
544+
name = "test-acc-function-pn-02"
545+
namespace_id = scaleway_function_namespace.main.id
546+
privacy = "private"
547+
runtime = "go123"
548+
handler = "Handle"
549+
sandbox = "v1"
550+
private_network_id = scaleway_vpc_private_network.pn00.id
551+
}
552+
`,
553+
Check: resource.ComposeTestCheckFunc(
554+
testAccCheckFunctionExists(tt, "scaleway_function.f00"),
555+
testAccCheckFunctionExists(tt, "scaleway_function.f01"),
556+
testAccCheckFunctionExists(tt, "scaleway_function.f02"),
557+
resource.TestCheckResourceAttr("scaleway_function.f00", "private_network_id", ""),
558+
resource.TestCheckResourceAttrPair("scaleway_function.f01", "private_network_id", "scaleway_vpc_private_network.pn01", "id"),
559+
resource.TestCheckResourceAttrPair("scaleway_function.f02", "private_network_id", "scaleway_vpc_private_network.pn00", "id"),
560+
),
561+
},
562+
},
563+
})
564+
}
565+
413566
func testAccCheckFunctionExists(tt *acctest.TestTools, n string) resource.TestCheckFunc {
414567
return func(state *terraform.State) error {
415568
rs, ok := state.RootModule().Resources[n]
@@ -468,7 +621,7 @@ func passwordMatchHash(parent string, key string, password string) resource.Test
468621
return func(state *terraform.State) error {
469622
rs, ok := state.RootModule().Resources[parent]
470623
if !ok {
471-
return fmt.Errorf("resource container not found: %s", parent)
624+
return fmt.Errorf("resource not found: %s", parent)
472625
}
473626

474627
match, err := argon2id.ComparePasswordAndHash(password, rs.Primary.Attributes[key])

internal/services/function/namespace.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ func ResourceNamespace() *schema.Resource {
8686
Computed: true,
8787
Description: "The ID of the registry namespace",
8888
},
89+
"activate_vpc_integration": {
90+
Type: schema.TypeBool,
91+
ForceNew: true,
92+
Optional: true,
93+
Default: false,
94+
Description: "Activate VPC integration for the namespace",
95+
},
8996
"region": regional.Schema(),
9097
"organization_id": account.OrganizationIDSchema(),
9198
"project_id": account.ProjectIDSchema(),
@@ -113,6 +120,10 @@ func ResourceFunctionNamespaceCreate(ctx context.Context, d *schema.ResourceData
113120
createReq.Tags = types.ExpandStrings(rawTag)
114121
}
115122

123+
if activateVPC, ok := d.GetOk("activate_vpc_integration"); ok {
124+
createReq.ActivateVpcIntegration = activateVPC.(bool)
125+
}
126+
116127
ns, err := api.CreateNamespace(createReq, scw.WithContext(ctx))
117128
if err != nil {
118129
return diag.FromErr(err)
@@ -155,6 +166,7 @@ func ResourceFunctionNamespaceRead(ctx context.Context, d *schema.ResourceData,
155166
_ = d.Set("registry_endpoint", ns.RegistryEndpoint)
156167
_ = d.Set("registry_namespace_id", ns.RegistryNamespaceID)
157168
_ = d.Set("secret_environment_variables", flattenFunctionSecrets(ns.SecretEnvironmentVariables))
169+
_ = d.Set("activate_vpc_integration", types.FlattenBoolPtr(ns.VpcIntegrationActivated)) //nolint:staticcheck
158170

159171
return nil
160172
}

0 commit comments

Comments
 (0)