这是indexloc提供的服务,不要输入任何密码
Skip to content

Automatically gather serverless_function_regions #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 2, 2022
Merged
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
21 changes: 1 addition & 20 deletions vercel/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,7 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ
Type: types.StringType,
Description: "The region on Vercel's network to which your Serverless Functions are deployed. It should be close to any data source your Serverless Function might depend on. A new Deployment is required for your changes to take effect. Please see [Vercel's documentation](https://vercel.com/docs/concepts/edge-network/regions) for a full list of regions.",
Validators: []tfsdk.AttributeValidator{
stringOneOf(
"arn1",
"bom1",
"cdg1",
"cle1",
"cpt1",
"dub1",
"fra1",
"gru1",
"hkg1",
"hnd1",
"iad1",
"icn1",
"kix1",
"lhr1",
"pdx1",
"sfo1",
"sin1",
"syd1",
),
validateServerlessFunctionRegion(),
},
},
"environment": {
Expand Down
20 changes: 20 additions & 0 deletions vercel/resource_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vercel_test
import (
"context"
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
Expand All @@ -23,6 +24,25 @@ func TestAcc_Project(t *testing.T) {
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
CheckDestroy: testAccProjectDestroy("vercel_project.test", testTeam()),
Steps: []resource.TestStep{
// Ensure we get nice framework / serverless_function_region errors
{
Config: `
resource "vercel_project" "test" {
name = "foo"
serverless_function_region = "notexist"
}
`,
ExpectError: regexp.MustCompile("Invalid Serverless Function Region"),
},
{
Config: `
resource "vercel_project" "test" {
name = "foo"
framework = "notexist"
}
`,
ExpectError: regexp.MustCompile("Invalid Framework"),
},
// Create and Read testing
{
Config: testAccProjectConfig(projectSuffix, teamIDConfig()),
Expand Down
32 changes: 27 additions & 5 deletions vercel/validator_framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,32 @@ import (
"fmt"
"io"
"net/http"
"strings"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func validateFramework() validatorFramework {
return validatorFramework{}
}

type validatorFramework struct {
frameworks []string
frameworks map[string]struct{}
}

func (v validatorFramework) Description(ctx context.Context) string {
if v.frameworks == nil {
return "The framework provided is not supported on Vercel"
}
return stringOneOf(v.frameworks...).Description(ctx)
return fmt.Sprintf("The framework provided is not supported on Vercel. Must be one of %s.", strings.Join(keys(v.frameworks), ", "))
}

func (v validatorFramework) MarkdownDescription(ctx context.Context) string {
if v.frameworks == nil {
return "The framework provided is not supported on Vercel"
}
return stringOneOf(v.frameworks...).MarkdownDescription(ctx)
return fmt.Sprintf("The framework provided is not supported on Vercel. Must be one of `%s`.", strings.Join(keys(v.frameworks), "`, `"))
}

func (v validatorFramework) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) {
Expand Down Expand Up @@ -74,8 +76,28 @@ func (v validatorFramework) Validate(ctx context.Context, req tfsdk.ValidateAttr
return
}
for _, fw := range fwList {
v.frameworks = append(v.frameworks, fw.Slug)
if v.frameworks == nil {
v.frameworks = map[string]struct{}{}
}
v.frameworks[fw.Slug] = struct{}{}
}

stringOneOf(v.frameworks...).Validate(ctx, req, resp)
var item types.String
diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &item)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return
}
if item.Unknown || item.Null {
return
}

if _, ok := v.frameworks[item.Value]; !ok {
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Invalid Framework",
fmt.Sprintf("The framework %s is not supported on Vercel. Must be one of %s.", item.Value, strings.Join(keys(v.frameworks), ", ")),
)
return
}
}
123 changes: 123 additions & 0 deletions vercel/validator_serverless_function_region.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package vercel

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func validateServerlessFunctionRegion() validatorServerlessFunctionRegion {
return validatorServerlessFunctionRegion{}
}

type validatorServerlessFunctionRegion struct {
regions map[string]struct{}
}

func (v validatorServerlessFunctionRegion) Description(ctx context.Context) string {
if v.regions == nil {
return "The serverless function region provided is not supported on Vercel"
}
return fmt.Sprintf("The serverless function region provided is not supported on Vercel. Must be one of %s.", strings.Join(keys(v.regions), ", "))
}

func (v validatorServerlessFunctionRegion) MarkdownDescription(ctx context.Context) string {
if v.regions == nil {
return "The serverless function region provided is not supported on Vercel"
}
return fmt.Sprintf("The serverless function region provided is not supported on Vercel. Must be one of `%s`.", strings.Join(keys(v.regions), "`, `"))
}

func contains(items []string, i string) bool {
for _, j := range items {
if j == i {
return true
}
}
return false
}

func keys(v map[string]struct{}) (out []string) {
for k := range v {
out = append(out, k)
}
return
}

func (v validatorServerlessFunctionRegion) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) {
apires, err := http.Get("https://dcs.vercel-infra.com")
if err != nil {
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Unable to validate attribute",
fmt.Sprintf("Unable to retrieve Vercel serverless function regions: unexpected error: %s", err),
)
return
}
if apires.StatusCode != 200 {
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Unable to validate attribute",
fmt.Sprintf("Unable to retrieve Vercel serverless function regions: unexpected status code: %d", apires.StatusCode),
)
return
}

defer apires.Body.Close()
responseBody, err := io.ReadAll(apires.Body)
if err != nil {
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Unable to validate attribute",
fmt.Sprintf("Unable to retrieve Vercel serverless function regions: error reading response body: %s", err),
)
return
}

var regions map[string]struct {
Caps []string `json:"caps"`
}
err = json.Unmarshal(responseBody, &regions)
if err != nil {
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Unable to validate attribute",
fmt.Sprintf("Unable to retrieve Vercel serverless function regions: error parsing serverless function regions response: %s", err),
)
return
}

for region, regionInfo := range regions {
if contains(regionInfo.Caps, "V2_DEPLOYMENT_CREATE") {
if v.regions == nil {
v.regions = map[string]struct{}{}
}
v.regions[region] = struct{}{}
}
}

var item types.String
diags := tfsdk.ValueAs(ctx, req.AttributeConfig, &item)
resp.Diagnostics.Append(diags...)
if diags.HasError() {
return
}
if item.Unknown || item.Null {
return
}

if _, ok := v.regions[item.Value]; !ok {
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Invalid Serverless Function Region",
fmt.Sprintf("The serverless function region %s is not supported on Vercel. Must be one of %s.", item.Value, strings.Join(keys(v.regions), ", ")),
)
return
}
}
2 changes: 1 addition & 1 deletion vercel/validator_string_one_of.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (v validatorStringOneOf) Validate(ctx context.Context, req tfsdk.ValidateAt
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Invalid value provided",
fmt.Sprintf("Item must be one of %s, got: %s.", strings.Join(v.keys(), " "), item.Value),
fmt.Sprintf("Item must be one of %s, got: %s.", strings.Join(v.keys(), ", "), item.Value),
)
return
}
Expand Down
6 changes: 3 additions & 3 deletions vercel/validator_string_set_items_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ func (v validatorStringSetItemsIn) keys() (out []string) {
}

func (v validatorStringSetItemsIn) Description(ctx context.Context) string {
return fmt.Sprintf("set item must be one of %s", strings.Join(v.keys(), " "))
return fmt.Sprintf("Set item must be one of %s", strings.Join(v.keys(), ", "))
}
func (v validatorStringSetItemsIn) MarkdownDescription(ctx context.Context) string {
return fmt.Sprintf("set item must be one of `%s`", strings.Join(v.keys(), "` `"))
return fmt.Sprintf("Set item must be one of `%s`", strings.Join(v.keys(), ",` `"))
}

func (v validatorStringSetItemsIn) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) {
Expand Down Expand Up @@ -62,7 +62,7 @@ func (v validatorStringSetItemsIn) Validate(ctx context.Context, req tfsdk.Valid
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Invalid value provided",
fmt.Sprintf("Set item must be one of %s, got: %s.", strings.Join(v.keys(), " "), item.Value),
fmt.Sprintf("%s, got %s", v.Description(ctx), item.Value),
)
return
}
Expand Down