From 4069abd07baa31135c929ed73226e269a8f61484 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Wed, 17 Jul 2024 17:09:51 +0100 Subject: [PATCH 1/2] Add team config resource for managing team settings --- client/team.go | 127 ++++-- vercel/examples/avatar.png | Bin 0 -> 2240 bytes vercel/provider.go | 1 + vercel/resource_team_config.go | 522 ++++++++++++++++++++++++ vercel/resource_team_config_test.go | 75 ++++ vercel/validator_map_items_max_count.go | 44 ++ vercel/validator_map_items_min_count.go | 1 - 7 files changed, 735 insertions(+), 35 deletions(-) create mode 100644 vercel/examples/avatar.png create mode 100644 vercel/resource_team_config.go create mode 100644 vercel/resource_team_config_test.go create mode 100644 vercel/validator_map_items_max_count.go diff --git a/client/team.go b/client/team.go index ee0f9db1..15a800e8 100644 --- a/client/team.go +++ b/client/team.go @@ -11,48 +11,69 @@ import ( type TeamCreateRequest struct { Slug string `json:"slug"` Name string `json:"name"` + Plan string `json:"plan"` } -// Team is the information returned by the vercel api when a team is created. -type Team struct { - ID string `json:"id"` - SensitiveEnvironmentVariablePolicy *string `json:"sensitiveEnvironmentVariablePolicy"` +type SamlConnection struct { + Status string `json:"status"` } -// CreateTeam creates a team within vercel. -func (c *Client) CreateTeam(ctx context.Context, request TeamCreateRequest) (r Team, err error) { - url := fmt.Sprintf("%s/v1/teams", c.baseURL) +type SamlDirectory struct { + Type string `json:"type"` + State string `json:"state"` +} - payload := string(mustMarshal(request)) - tflog.Info(ctx, "creating team", map[string]interface{}{ - "url": url, - "payload": payload, - }) - err = c.doRequest(clientRequest{ - ctx: ctx, - method: "POST", - url: url, - body: payload, - }, &r) - return r, err +type SamlConfig struct { + Connection *SamlConnection `json:"connection"` + Directory *SamlDirectory `json:"directory"` + Enforced bool `json:"enforced,omitempty"` + Roles map[string]string `json:"roles,omitempty"` } -// DeleteTeam deletes an existing team within vercel. -func (c *Client) DeleteTeam(ctx context.Context, teamID string) error { - url := fmt.Sprintf("%s/v1/teams/%s", c.baseURL, teamID) - tflog.Info(ctx, "deleting team", map[string]interface{}{ - "url": url, - }) - return c.doRequest(clientRequest{ - ctx: ctx, - method: "DELETE", - url: url, - body: "", - }, nil) +type TaxID struct { + Type string `json:"type"` + ID string `json:"id"` +} + +type Address struct { + Line1 *string `json:"line1"` + Line2 *string `json:"line2"` + PostalCode *string `json:"postalCode"` + City *string `json:"city"` + Country *string `json:"country"` + State *string `json:"state"` +} + +type RemoteCaching struct { + Enabled *bool `json:"enabled"` +} + +type SpacesConfig struct { + Enabled bool `json:"enabled"` +} + +// Team is the information returned by the vercel api when a team is created. +type Team struct { + ID string `json:"id"` + Name string `json:"name"` + Avatar *string `json:"avatar"` // hash of uploaded image + Description *string `json:"description"` + Slug string `json:"slug"` + SensitiveEnvironmentVariablePolicy *string `json:"sensitiveEnvironmentVariablePolicy"` + EmailDomain *string `json:"emailDomain"` + Saml *SamlConfig `json:"saml"` + InviteCode *string `json:"inviteCode"` + PreviewDeploymentSuffix *string `json:"previewDeploymentSuffix"` + RemoteCaching *RemoteCaching `json:"remoteCaching"` + EnablePreviewFeedback *string `json:"enablePreviewFeedback"` + EnableProductionFeedback *string `json:"enableProductionFeedback"` + Spaces *SpacesConfig `json:"spaces"` + HideIPAddresses *bool `json:"hideIpAddresses"` + HideIPAddressesInLogDrains *bool `json:"hideIpAddressesInLogDrains,omitempty"` } // GetTeam returns information about an existing team within vercel. -func (c *Client) GetTeam(ctx context.Context, idOrSlug string) (r Team, err error) { +func (c *Client) GetTeam(ctx context.Context, idOrSlug string) (t Team, err error) { url := fmt.Sprintf("%s/v2/teams/%s", c.baseURL, idOrSlug) tflog.Info(ctx, "getting team", map[string]interface{}{ "url": url, @@ -62,6 +83,44 @@ func (c *Client) GetTeam(ctx context.Context, idOrSlug string) (r Team, err erro method: "GET", url: url, body: "", - }, &r) - return r, err + }, &t) + return t, err +} + +type UpdateSamlConfig struct { + Enforced bool `json:"enforced"` + Roles map[string]string `json:"roles"` +} + +type UpdateTeamRequest struct { + TeamID string `json:"-"` + Avatar string `json:"avatar,omitempty"` + Description string `json:"description,omitempty"` + EmailDomain string `json:"emailDomain,omitempty"` + Name string `json:"name,omitempty"` + PreviewDeploymentSuffix string `json:"previewDeploymentSuffix,omitempty"` + Saml *UpdateSamlConfig `json:"saml,omitempty"` + Slug string `json:"slug,omitempty"` + EnablePreviewFeedback string `json:"enablePreviewFeedback,omitempty"` + EnableProductionFeedback string `json:"enableProductionFeedback,omitempty"` + SensitiveEnvironmentVariablePolicy string `json:"sensitiveEnvironmentVariablePolicy,omitempty"` + RemoteCaching *RemoteCaching `json:"remoteCaching,omitempty"` + HideIPAddresses *bool `json:"hideIpAddresses,omitempty"` + HideIPAddressesInLogDrains *bool `json:"hideIpAddressesInLogDrains,omitempty"` +} + +func (c *Client) UpdateTeam(ctx context.Context, request UpdateTeamRequest) (t Team, err error) { + url := fmt.Sprintf("%s/v2/teams/%s", c.baseURL, request.TeamID) + payload := string(mustMarshal(request)) + tflog.Info(ctx, "updating team", map[string]interface{}{ + "url": url, + "payload": payload, + }) + err = c.doRequest(clientRequest{ + ctx: ctx, + method: "PATCH", + url: url, + body: payload, + }, &t) + return t, err } diff --git a/vercel/examples/avatar.png b/vercel/examples/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..2de31315e77a636c674b4479c122d41a5b115c3b GIT binary patch literal 2240 zcmZ8j3pmqzA78G^Wsz7igtAmNDGno24CT^6hccI(D8!7j*w%3g@0`@~;vm;^N)frN zS6LX#tygSy>S%LmY8a18bE&DN)@C`ESqf_x*f7pYP}U{XTp4+i4GHRi#}@ zAP`8^)y2^Z_)ZYNU@am^6n}tirkv`;`>c}NC0t`MoA)+*YwwOE)gO&HeLc@88HSi-$`=MP zGu24`JX9$+DVVrU1+vC$N+*Bk~%EY)7zVD)8CpUvexhNDA=~(S^>Qz4m zfo^*KH%|BE)QFAtKLTKS@B6POY@3sA0Y=P`@Oqj>2y(PXcfNS-UFv!_K(Vwxet=#| zv_Kx8(d@+AHYTwz`~9QNbb!42Bh2=hw}V&}zE+QsG^t39J-;$V$zxoymih08PW?P( zi7|~)lt8&^s6q}c#!r-goPkN&9hNc8g8tN~p7K|$Tvo@;@{&}EFE^YnvMSnXLwxgmkImgAxZ#`gO(5aVFUonodb~p%+$*R=6sSBjh!P(x1P@tDDUZuP$0|&y0lP36FckkENJ2iBE1aR&3exjN1$F`z)X0VZCU5_nMWXiJ6d#W}fnN>`Le6xYABMvcR> ztgvJ)w=O)S$odcS{|+1bHHi>LPBD#+i;(3KMf&?*PVS5Ol@JHn^1wVqAOq~gw!DgX zwS>dpIFVlf2zONEfSp=55FyYE8rvOf%$R4wd>PgVh9o4jLBF4Bt&G@jpOXR zO0!MOnqcKGGU&=?wWD{(d$eWl_BvGE;4~i_McMsSn!9sn0K_Y_lPeEn&(AM(rb*N1 zXcW&}I9PTA4d$Wj&gvn-HxP>JVkFIoQSWhFp9KC1Q3b&BpPNg_Uo}qxaBn>g?RD}r zu9G%aA0%G)8$ioK>bj!Fr^g-a%*2|e(OKABMTm?<5IxUa0CfdH4Lbufb(oFUR~0?$ zE$7ZZ^}}e5AcSwWsp#jC=}L!iT)~dzDTOOu@#a*T8=5Q7TAq~Ga~0O~)xCSFg;gp?O9{Ok8+2r^e`a4KhbPBT3=YbRZ&5VHDrT0$WxEu!vy#LHt zOZ1uHx(6TMMFvs>Uv{n~VrKH}%?xfn3rsIaW*=5(&*y}&TT-@$t7zqKO*k6ILnWS@ z$wmT*j@CSY@i0Eg{=gNMMnP7$M4_pz3~F;vQK}%fw3He0yn&H+1d`b+(^6Wwy84LY zpeKcE(A$DEhk9i+URxbRD9;i7mY4BV-EReS0K4W+HC@*$6<3@gTp9`~`Xc4in_o-x zy{rqB^5rzNjX?d=Wu<)C!U?|U1T~Z-wdnbJ4ZSF$azcpT-E>cgys6f@-$?9jMP->x zzJhW2Ar1+lF-LsVjbFLP`Ndr9d%cc5v)I9vBvZ?Ipfxb9eEEc(>Oe|^EjLm!OGIUKRk;*t+@@`Dt-8_faJ_gOK&>GY z`UKHCefqr?)I|;MUeR=o zF8H!|;6Oh(U$Bvb*zI5%4l9M6u&jjS^ v.Max { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid value provided", + fmt.Sprintf( + "Map must contain no more than %d items, got: %d.", + v.Max, + count, + ), + ) + return + } +} diff --git a/vercel/validator_map_items_min_count.go b/vercel/validator_map_items_min_count.go index a6c68dd8..1456d0cd 100644 --- a/vercel/validator_map_items_min_count.go +++ b/vercel/validator_map_items_min_count.go @@ -14,7 +14,6 @@ func mapItemsMinCount(minCount int) validatorMapItemsMinCount { } type validatorMapItemsMinCount struct { - Max int Min int } From f3ca36903be55371c3c713c6e8625a7b036d534d Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Fri, 25 Oct 2024 15:56:02 +0100 Subject: [PATCH 2/2] Generate docs --- docs/resources/team_config.md | 68 +++++++++++++++++++ .../resources/vercel_team_config/resource.tf | 19 ++++++ 2 files changed, 87 insertions(+) create mode 100644 docs/resources/team_config.md create mode 100644 examples/resources/vercel_team_config/resource.tf diff --git a/docs/resources/team_config.md b/docs/resources/team_config.md new file mode 100644 index 00000000..53731774 --- /dev/null +++ b/docs/resources/team_config.md @@ -0,0 +1,68 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vercel_team_config Resource - terraform-provider-vercel" +subcategory: "" +description: |- + Manages the configuration of an existing Vercel Team. +--- + +# vercel_team_config (Resource) + +Manages the configuration of an existing Vercel Team. + +## Example Usage + +```terraform +data "vercel_file" "example" { + path = "example/avatar.png" +} + +resource "vercel_team_config" "example" { + id = "team_xxxxxxxxxxxxxxxxxxxxxxxx" + avatar = data.vercel_file.example.file + name = "Vercel terraform example" + slug = "vercel-terraform-example" + description = "Vercel Terraform Example" + sensitive_environment_variable_policy = "off" + remote_caching = { + enabled = true + } + enable_preview_feedback = "off" + enable_production_feedback = "off" + hide_ip_addresses = true + hide_ip_addresses_in_log_drains = true +} +``` + + +## Schema + +### Required + +- `id` (String) The ID of the existing Vercel Team. + +### Optional + +- `avatar` (Map of String) The `avatar` should be a the 'file' attribute from a vercel_file data source. +- `description` (String) A description of the team. +- `email_domain` (String) Hostname that'll be matched with emails on sign-up to automatically join the Team. +- `enable_preview_feedback` (String) +- `enable_production_feedback` (String) +- `hide_ip_addresses` (Boolean) Indicates if ip addresses should be accessible in o11y tooling. +- `hide_ip_addresses_in_log_drains` (Boolean) Indicates if ip addresses should be accessible in log drains. +- `name` (String) The name of the team. +- `preview_deployment_suffix` (String) The hostname that is used as the preview deployment suffix. +- `remote_caching` (Attributes) Configuration for Remote Caching. (see [below for nested schema](#nestedatt--remote_caching)) +- `sensitive_environment_variable_policy` (String) +- `slug` (String) The slug of the team. Will be used in the URL of the team's dashboard. + +### Read-Only + +- `invite_code` (String) A code that can be used to join this team. Only visible to Team owners. + + +### Nested Schema for `remote_caching` + +Optional: + +- `enabled` (Boolean) Indicates if Remote Caching is enabled. diff --git a/examples/resources/vercel_team_config/resource.tf b/examples/resources/vercel_team_config/resource.tf new file mode 100644 index 00000000..97efbd4b --- /dev/null +++ b/examples/resources/vercel_team_config/resource.tf @@ -0,0 +1,19 @@ +data "vercel_file" "example" { + path = "example/avatar.png" +} + +resource "vercel_team_config" "example" { + id = "team_xxxxxxxxxxxxxxxxxxxxxxxx" + avatar = data.vercel_file.example.file + name = "Vercel terraform example" + slug = "vercel-terraform-example" + description = "Vercel Terraform Example" + sensitive_environment_variable_policy = "off" + remote_caching = { + enabled = true + } + enable_preview_feedback = "off" + enable_production_feedback = "off" + hide_ip_addresses = true + hide_ip_addresses_in_log_drains = true +}