Skip to content

Commit 3f7e1a5

Browse files
remyleoneCodelax
andauthored
feat(function): add support for function (#1109)
Co-authored-by: Jules Castéran <[email protected]>
1 parent 262a71c commit 3f7e1a5

19 files changed

+8783
-382
lines changed

docs/resources/function.md

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
---
2+
page_title: "Scaleway: scaleway_function"
3+
description: |-
4+
Manages Scaleway Functions.
5+
---
6+
7+
# scaleway_function
8+
9+
Creates and manages Scaleway Functions.
10+
For more information see [the documentation](https://developers.scaleway.com/en/products/functions/api/).
11+
12+
## Examples
13+
14+
### Basic
15+
16+
```hcl
17+
resource "scaleway_function_namespace" "main" {
18+
name = "main-function-namespace"
19+
description = "Main function namespace"
20+
}
21+
22+
resource scaleway_function main {
23+
namespace_id = scaleway_function_namespace.main.id
24+
runtime = "go118"
25+
handler = "Handle"
26+
privacy = "private"
27+
}
28+
```
29+
30+
### With sources and deploy
31+
32+
You create a zip of your function (ex: `zip function.zip -r go.mod go.sum handler.go`)
33+
34+
```hcl
35+
resource "scaleway_function_namespace" "main" {
36+
name = "main-function-namespace"
37+
description = "Main function namespace"
38+
}
39+
40+
resource scaleway_function main {
41+
namespace_id = scaleway_function_namespace.main.id
42+
runtime = "go118"
43+
handler = "Handle"
44+
privacy = "private"
45+
zip_file = "function.zip"
46+
zip_hash = filesha256("function.zip")
47+
deploy = true
48+
}
49+
```
50+
51+
52+
## Arguments Reference
53+
54+
The following arguments are supported:
55+
56+
- `name` - (Required) The unique name of the function.
57+
58+
~> **Important** Updates to `name` will recreate the function.
59+
60+
- `description` (Optional) The description of the function.
61+
62+
- `environment_variables` - The environment variables of the function.
63+
64+
- `privacy` - Privacy of the function. Can be either `private` or `public`. Read more on [authentication](https://developers.scaleway.com/en/products/functions/api/#authentication)
65+
66+
- `runtime` - Runtime of the function. Runtimes can be fetched using [specific route](https://developers.scaleway.com/en/products/functions/api/#get-f7de6a)
67+
68+
- `min_scale` - Minimum replicas for your function, defaults to 0, Note that a function is billed when it gets executed, and using a min_scale greater than 0 will cause your function container to run constantly.
69+
70+
- `max_scale` - Maximum replicas for your function (defaults to 20), our system will scale your functions automatically based on incoming workload, but will never scale the number of replicas above the configured max_scale.
71+
72+
- `memory_limit` - Memory limit in MB for your function, defaults to 128MB
73+
74+
- `handler` - Handler of the function. Depends on the runtime ([function guide](https://developers.scaleway.com/en/products/functions/api/#create-a-function))
75+
76+
- `timeout` - Holds the max duration (in seconds) the function is allowed for responding to a request
77+
78+
- `zip_file` - Location of the zip file to upload containing your function sources
79+
80+
- `zip_hash` - The hash of your source zip file, changing it will re-apply function. Can be any string, changing it will just trigger state change. You can use any terraform hash function to trigger a change on your zip change (see examples)
81+
82+
- `deploy` - Define if the function should be deployed, terraform will wait for function to be deployed. Function will get deployed if you change source zip
83+
84+
- `region` - (Defaults to [provider](../index.md#region) `region`). The [region](../guides/regions_and_zones.md#regions) in which the namespace should be created.
85+
86+
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the namespace is associated with.
87+
88+
89+
## Attributes Reference
90+
91+
In addition to all arguments above, the following attributes are exported:
92+
93+
- `id` - The ID of the function
94+
- `organization_id` - The organization ID the function is associated with.
95+
- `cpu_limit` - The CPU limit in mCPU for your function. More infos on resources [here](https://developers.scaleway.com/en/products/functions/api/#functions)
96+
97+
98+
## Import
99+
100+
Functions can be imported using the `{region}/{id}`, e.g.
101+
102+
```bash
103+
$ terraform import scaleway_function.main fr-par/11111111-1111-1111-1111-111111111111
104+
```

go.mod

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ require (
1313
github.com/hashicorp/terraform-plugin-sdk/v2 v2.17.0
1414
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220519084803-2eb97e55dee8
1515
github.com/stretchr/testify v1.7.1
16-
1716
)
1817

1918
require (

scaleway/data_source_function.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package scaleway
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
function "github.com/scaleway/scaleway-sdk-go/api/function/v1beta1"
10+
"github.com/scaleway/scaleway-sdk-go/scw"
11+
)
12+
13+
func dataSourceScalewayFunction() *schema.Resource {
14+
// Generate datasource schema from resource
15+
dsSchema := datasourceSchemaFromResourceSchema(resourceScalewayFunction().Schema)
16+
17+
dsSchema["function_id"] = &schema.Schema{
18+
Type: schema.TypeString,
19+
Description: "The ID of the function",
20+
Computed: true,
21+
}
22+
23+
addOptionalFieldsToSchema(dsSchema, "name", "function_id")
24+
fixDatasourceSchemaFlags(dsSchema, true, "namespace_id")
25+
26+
return &schema.Resource{
27+
ReadContext: dataSourceScalewayFunctionRead,
28+
Schema: dsSchema,
29+
}
30+
}
31+
32+
func dataSourceScalewayFunctionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
33+
api, region, err := functionAPIWithRegion(d, meta)
34+
if err != nil {
35+
return diag.FromErr(err)
36+
}
37+
38+
functionID, ok := d.GetOk("function_id")
39+
if !ok {
40+
res, err := api.ListFunctions(&function.ListFunctionsRequest{
41+
Region: region,
42+
NamespaceID: expandID(d.Get("namespace_id").(string)),
43+
Name: expandStringPtr(d.Get("name")),
44+
}, scw.WithContext(ctx))
45+
if err != nil {
46+
return diag.FromErr(err)
47+
}
48+
if len(res.Functions) == 0 {
49+
return diag.FromErr(fmt.Errorf("no functions found with the name %s", d.Get("name")))
50+
}
51+
if len(res.Functions) > 1 {
52+
return diag.FromErr(fmt.Errorf("%d functions found with the same name %s", len(res.Functions), d.Get("name")))
53+
}
54+
functionID = res.Functions[0].ID
55+
}
56+
57+
regionalID := datasourceNewRegionalizedID(functionID, region)
58+
d.SetId(regionalID)
59+
_ = d.Set("function_id", regionalID)
60+
61+
return resourceScalewayFunctionRead(ctx, d, meta)
62+
}

scaleway/data_source_function_test.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package scaleway
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
7+
)
8+
9+
func TestAccScalewayDataSourceFunction_Basic(t *testing.T) {
10+
tt := NewTestTools(t)
11+
defer tt.Cleanup()
12+
13+
resource.ParallelTest(t, resource.TestCase{
14+
PreCheck: func() { testAccPreCheck(t) },
15+
ProviderFactories: tt.ProviderFactories,
16+
CheckDestroy: testAccCheckScalewayFunctionDestroy(tt),
17+
Steps: []resource.TestStep{
18+
{
19+
Config: `
20+
resource "scaleway_function_namespace" "main" {
21+
name = "tf-ds-function"
22+
}
23+
24+
resource scaleway_function main {
25+
name = "tf-ds-function"
26+
namespace_id = scaleway_function_namespace.main.id
27+
runtime = "node14"
28+
privacy = "private"
29+
handler = "handler.handle"
30+
}
31+
`,
32+
Check: resource.ComposeTestCheckFunc(
33+
testAccCheckScalewayFunctionExists(tt, "scaleway_function.main"),
34+
resource.TestCheckResourceAttr("scaleway_function.main", "name", "tf-ds-function"),
35+
),
36+
},
37+
{
38+
Config: `
39+
resource "scaleway_function_namespace" "main" {
40+
name = "tf-ds-function"
41+
}
42+
43+
resource scaleway_function main {
44+
name = "tf-ds-function"
45+
namespace_id = scaleway_function_namespace.main.id
46+
runtime = "node14"
47+
privacy = "private"
48+
handler = "handler.handle"
49+
}
50+
51+
data "scaleway_function" "by_name" {
52+
name = scaleway_function.main.name
53+
namespace_id = scaleway_function_namespace.main.id
54+
}
55+
56+
data "scaleway_function" "by_id" {
57+
function_id = scaleway_function.main.id
58+
namespace_id = scaleway_function_namespace.main.id
59+
}
60+
`,
61+
Check: resource.ComposeTestCheckFunc(
62+
testAccCheckScalewayFunctionExists(tt, "scaleway_function.main"),
63+
resource.TestCheckResourceAttr("scaleway_function.main", "name", "tf-ds-function"),
64+
resource.TestCheckResourceAttrSet("data.scaleway_function.by_name", "id"),
65+
66+
resource.TestCheckResourceAttr("data.scaleway_function.by_id", "name", "tf-ds-function"),
67+
resource.TestCheckResourceAttrSet("data.scaleway_function.by_id", "id"),
68+
resource.TestCheckResourceAttr("data.scaleway_function.by_id", "name", "tf-ds-function"),
69+
),
70+
},
71+
},
72+
})
73+
}

scaleway/helpers_function.go

+100
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@ package scaleway
22

33
import (
44
"context"
5+
"fmt"
6+
"net/http"
7+
"net/http/httputil"
8+
"os"
59
"time"
610

11+
"github.com/hashicorp/terraform-plugin-log/tflog"
712
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
813
function "github.com/scaleway/scaleway-sdk-go/api/function/v1beta1"
914
"github.com/scaleway/scaleway-sdk-go/scw"
1015
)
1116

1217
const (
1318
defaultFunctionNamespaceTimeout = 5 * time.Minute
19+
defaultFunctionTimeout = 15 * time.Minute
1420
defaultFunctionRetryInterval = 5 * time.Second
21+
defaultFunctionAfterUpdateWait = 1 * time.Second
1522
)
1623

1724
// functionAPIWithRegion returns a new container registry API and the region.
@@ -53,3 +60,96 @@ func waitForFunctionNamespace(ctx context.Context, functionAPI *function.API, re
5360

5461
return ns, err
5562
}
63+
64+
func waitForFunction(ctx context.Context, functionAPI *function.API, region scw.Region, id string, timeout time.Duration) (*function.Function, error) {
65+
retryInterval := defaultFunctionRetryInterval
66+
if DefaultWaitRetryInterval != nil {
67+
retryInterval = *DefaultWaitRetryInterval
68+
}
69+
70+
f, err := functionAPI.WaitForFunction(&function.WaitForFunctionRequest{
71+
Region: region,
72+
FunctionID: id,
73+
RetryInterval: &retryInterval,
74+
Timeout: scw.TimeDurationPtr(timeout),
75+
}, scw.WithContext(ctx))
76+
77+
return f, err
78+
}
79+
80+
func functionUpload(ctx context.Context, m interface{}, functionAPI *function.API, region scw.Region, functionID string, zipFile string) error {
81+
meta := m.(*Meta)
82+
zipStat, err := os.Stat(zipFile)
83+
if err != nil {
84+
return fmt.Errorf("failed to stat zip file: %w", err)
85+
}
86+
87+
uploadURL, err := functionAPI.GetFunctionUploadURL(&function.GetFunctionUploadURLRequest{
88+
Region: region,
89+
FunctionID: functionID,
90+
ContentLength: uint64(zipStat.Size()),
91+
}, scw.WithContext(ctx))
92+
if err != nil {
93+
return fmt.Errorf("failed to fetch upload url: %w", err)
94+
}
95+
96+
zip, err := os.Open(zipFile)
97+
if err != nil {
98+
return fmt.Errorf("failed to read zip file: %w", err)
99+
}
100+
defer zip.Close()
101+
102+
req, err := http.NewRequest(http.MethodPut, uploadURL.URL, zip)
103+
if err != nil {
104+
return fmt.Errorf("failed to init request: %w", err)
105+
}
106+
107+
req = req.WithContext(ctx)
108+
109+
for headerName, headerList := range uploadURL.Headers {
110+
for _, header := range *headerList {
111+
req.Header.Add(headerName, header)
112+
}
113+
}
114+
115+
secretKey, _ := meta.scwClient.GetSecretKey()
116+
req.Header.Add("X-Auth-Token", secretKey)
117+
118+
resp, err := meta.httpClient.Do(req)
119+
if err != nil {
120+
return fmt.Errorf("failed to send request: %w", err)
121+
}
122+
defer resp.Body.Close()
123+
124+
respDump, err := httputil.DumpResponse(resp, true)
125+
if err != nil {
126+
return fmt.Errorf("failed to dump response: %w", err)
127+
}
128+
129+
reqDump, err := httputil.DumpRequest(req, false)
130+
if err != nil {
131+
return fmt.Errorf("failed to dump request: %w", err)
132+
}
133+
134+
tflog.Debug(ctx, "Request dump", map[string]interface{}{
135+
"url": uploadURL.URL,
136+
"response": string(respDump),
137+
"request": string(reqDump),
138+
})
139+
if resp.StatusCode != http.StatusOK {
140+
return fmt.Errorf("failed to upload function (Status: %d)", resp.StatusCode)
141+
}
142+
143+
return nil
144+
}
145+
146+
func functionDeploy(ctx context.Context, functionAPI *function.API, region scw.Region, functionID string) error {
147+
_, err := functionAPI.DeployFunction(&function.DeployFunctionRequest{
148+
Region: region,
149+
FunctionID: functionID,
150+
}, scw.WithContext(ctx))
151+
if err != nil {
152+
return fmt.Errorf("failed to deploy function")
153+
}
154+
return nil
155+
}

scaleway/provider.go

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func Provider(config *ProviderConfig) plugin.ProviderFunc {
6767
"scaleway_container_namespace": resourceScalewayContainerNamespace(),
6868
"scaleway_domain_record": resourceScalewayDomainRecord(),
6969
"scaleway_domain_zone": resourceScalewayDomainZone(),
70+
"scaleway_function": resourceScalewayFunction(),
7071
"scaleway_function_namespace": resourceScalewayFunctionNamespace(),
7172
"scaleway_instance_ip": resourceScalewayInstanceIP(),
7273
"scaleway_instance_ip_reverse_dns": resourceScalewayInstanceIPReverseDNS(),
@@ -116,6 +117,7 @@ func Provider(config *ProviderConfig) plugin.ProviderFunc {
116117
"scaleway_domain_zone": dataSourceScalewayDomainZone(),
117118
"scaleway_container_namespace": dataSourceScalewayContainerNamespace(),
118119
"scaleway_container": dataSourceScalewayContainer(),
120+
"scaleway_function": dataSourceScalewayFunction(),
119121
"scaleway_function_namespace": dataSourceScalewayFunctionNamespace(),
120122
"scaleway_instance_ip": dataSourceScalewayInstanceIP(),
121123
"scaleway_instance_security_group": dataSourceScalewayInstanceSecurityGroup(),

0 commit comments

Comments
 (0)