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

Add resource for granting project access to integrations #269

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 5 commits into from
Feb 12, 2025
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
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ jobs:
VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO: "dglsparsons-test/test"
VERCEL_TERRAFORM_TESTING_DOMAIN: "dgls.dev"
VERCEL_TERRAFORM_TESTING_ADDITIONAL_USER: ${{ secrets.VERCEL_TERRAFORM_TESTING_ADDITIONAL_USER }}
VERCEL_TERRAFORM_TESTING_EXISTING_INTEGRATION: ${{ secrets.VERCEL_TERRAFORM_TESTING_EXISTING_INTEGRATION }}
run: |
go test ./...

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ The acceptance tests require a few environment variables to be set:
* `VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO` - a Bitbucket repository in the form 'project/repo' that can be used to trigger deployments
* `VERCEL_TERRAFORM_TESTING_GITLAB_REPO` - a GitLab repository in the form 'project/repo' that can be used to trigger deployments
* `VERCEL_TERRAFORM_TESTING_DOMAIN` - a Vercel testing domain that can be used for testing
* `VERCEL_TERRAFORM_TESTING_EXISTING_INTEGRATION` - a Vercel integration that can be used for testing

```sh
$ task test
Expand Down
86 changes: 86 additions & 0 deletions client/integrations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package client

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-log/tflog"
)

func (c *Client) GetIntegrationProjectAccess(ctx context.Context, integrationID, projectID, teamID string) (bool, error) {
url := fmt.Sprintf("%s/v1/integrations/configuration/%s/project/%s", c.baseURL, integrationID, projectID)
if c.teamID(teamID) != "" {
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID))
}

tflog.Info(ctx, "getting integration project access", map[string]interface{}{
"url": url,
})

type resp struct {
Allowed bool `json:"allowed"`
}

var e resp
if err := c.doRequest(clientRequest{
ctx: ctx,
method: "GET",
url: url,
body: "",
}, &e); err != nil {
return false, err
}
return e.Allowed, nil
}

func (c *Client) GrantIntegrationProjectAccess(ctx context.Context, integrationID, projectID, teamID string) (bool, error) {
url := fmt.Sprintf("%s/v1/integrations/configuration/%s/project/%s", c.baseURL, integrationID, projectID)
if c.teamID(teamID) != "" {
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID))
}

tflog.Info(ctx, "getting integration project access", map[string]interface{}{
"url": url,
})

type resp struct {
Allowed bool `json:"allowed"`
}

var e resp
if err := c.doRequest(clientRequest{
ctx: ctx,
method: "POST",
url: url,
body: `{ "allowed": true }`,
}, &e); err != nil {
return false, err
}
return true, nil
}

func (c *Client) RevokeIntegrationProjectAccess(ctx context.Context, integrationID, projectID, teamID string) (bool, error) {
url := fmt.Sprintf("%s/v1/integrations/configuration/%s/project/%s", c.baseURL, integrationID, projectID)
if c.teamID(teamID) != "" {
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID))
}

tflog.Info(ctx, "getting integration project access", map[string]interface{}{
"url": url,
})

type resp struct {
Allowed bool `json:"allowed"`
}

var e resp
if err := c.doRequest(clientRequest{
ctx: ctx,
method: "POST",
url: url,
body: `{ "allowed": false }`,
}, &e); err != nil {
return false, err
}
return false, nil
}
25 changes: 25 additions & 0 deletions docs/resources/integration_project_access.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "vercel_integration_project_access Resource - terraform-provider-vercel"
subcategory: ""
description: |-
Provides Project access to an existing Integration. This requires the integration already exists and is already configured for Specific Project access.
---

# vercel_integration_project_access (Resource)

Provides Project access to an existing Integration. This requires the integration already exists and is already configured for Specific Project access.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `integration_id` (String) The ID of the integration.
- `project_id` (String) The ID of the Vercel project.

### Optional

- `team_id` (String) The ID of the Vercel team.Required when configuring a team resource if a default team has not been set in the provider.
1 change: 1 addition & 0 deletions vercel/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (p *vercelProvider) Resources(_ context.Context) []func() resource.Resource
newEdgeConfigTokenResource,
newFirewallConfigResource,
newFirewallBypassResource,
newIntegrationProjectAccessResource,
newLogDrainResource,
newProjectDeploymentRetentionResource,
newProjectDomainResource,
Expand Down
5 changes: 5 additions & 0 deletions vercel/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func testAccPreCheck(t *testing.T) {
mustHaveEnv(t, "VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO")
mustHaveEnv(t, "VERCEL_TERRAFORM_TESTING_DOMAIN")
mustHaveEnv(t, "VERCEL_TERRAFORM_TESTING_ADDITIONAL_USER")
mustHaveEnv(t, "VERCEL_TERRAFORM_TESTING_EXISTING_INTEGRATION")
}

var tc *client.Client
Expand Down Expand Up @@ -70,3 +71,7 @@ func testDomain() string {
func testAdditionalUser() string {
return os.Getenv("VERCEL_TERRAFORM_TESTING_ADDITIONAL_USER")
}

func testExistingIntegration() string {
return os.Getenv("VERCEL_TERRAFORM_TESTING_EXISTING_INTEGRATION")
}
225 changes: 225 additions & 0 deletions vercel/resource_integration_project_access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package vercel

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"

"github.com/vercel/terraform-provider-vercel/v2/client"
)

var (
_ resource.Resource = &integrationProjectAccessResource{}
_ resource.ResourceWithConfigure = &integrationProjectAccessResource{}
)

func newIntegrationProjectAccessResource() resource.Resource {
return &integrationProjectAccessResource{}
}

type integrationProjectAccessResource struct {
client *client.Client
}

func (r *integrationProjectAccessResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_integration_project_access"
}

func (r *integrationProjectAccessResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*client.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}

r.client = client
}

func (r *integrationProjectAccessResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: `Provides Project access to an existing Integration. This requires the integration already exists and is already configured for Specific Project access.
`,
Attributes: map[string]schema.Attribute{
"integration_id": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
Description: "The ID of the integration.",
},
"project_id": schema.StringAttribute{
Required: true,
Description: "The ID of the Vercel project.",
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
},
"team_id": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "The ID of the Vercel team.Required when configuring a team resource if a default team has not been set in the provider.",
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplaceIfConfigured(), stringplanmodifier.UseStateForUnknown()},
},
},
}
}

type IntegrationProjectAccess struct {
TeamID types.String `tfsdk:"team_id"`
ProjectID types.String `tfsdk:"project_id"`
IntegrationID types.String `tfsdk:"integration_id"`
}

func (r *integrationProjectAccessResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan IntegrationProjectAccess
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

_, err := r.client.GrantIntegrationProjectAccess(ctx, plan.IntegrationID.ValueString(), plan.ProjectID.ValueString(), plan.TeamID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error granting integration project access",
"Could not grant integration project access, unexpected error: "+err.Error(),
)
return
}

result := IntegrationProjectAccess{
TeamID: plan.TeamID,
IntegrationID: plan.IntegrationID,
ProjectID: plan.ProjectID,
}

tflog.Info(ctx, "granted integration project access", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"integration_id": result.IntegrationID.ValueString(),
"project_id": result.ProjectID.ValueString(),
})

diags = resp.State.Set(ctx, result)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}

func (r *integrationProjectAccessResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state IntegrationProjectAccess
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

allowed, err := r.client.GetIntegrationProjectAccess(ctx, state.IntegrationID.ValueString(), state.ProjectID.ValueString(), state.TeamID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error granting integration project access",
"Could not grant integration project access, unexpected error: "+err.Error(),
)
return
}

result := IntegrationProjectAccess{
TeamID: state.TeamID,
IntegrationID: state.IntegrationID,
ProjectID: state.ProjectID,
}
tflog.Info(ctx, "read integration project access", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"integration_id": result.IntegrationID.ValueString(),
"project_id": result.ProjectID.ValueString(),
"allowed": allowed,
})

if allowed {
diags = resp.State.Set(ctx, result)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
} else {
resp.State.RemoveResource(ctx)
}
}

func (r *integrationProjectAccessResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan IntegrationProjectAccess
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

allowed, err := r.client.GrantIntegrationProjectAccess(ctx, plan.IntegrationID.ValueString(), plan.ProjectID.ValueString(), plan.TeamID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error granting integration project access",
"Could not grant integration project access, unexpected error: "+err.Error(),
)
return
}

result := IntegrationProjectAccess{
TeamID: plan.TeamID,
IntegrationID: plan.IntegrationID,
ProjectID: plan.ProjectID,
}

tflog.Info(ctx, "granted integration project access", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"integration_id": result.IntegrationID.ValueString(),
"project_id": result.ProjectID.ValueString(),
"allowed": allowed,
})

diags = resp.State.Set(ctx, result)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}

func (r *integrationProjectAccessResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var plan IntegrationProjectAccess
diags := req.State.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

allowed, err := r.client.RevokeIntegrationProjectAccess(ctx, plan.IntegrationID.ValueString(), plan.ProjectID.ValueString(), plan.TeamID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error revoking integration project access",
"Could not revoke integration project access, unexpected error: "+err.Error(),
)
return
}

result := IntegrationProjectAccess{
TeamID: plan.TeamID,
IntegrationID: plan.IntegrationID,
ProjectID: plan.ProjectID,
}

tflog.Info(ctx, "revoked integration project access", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"integration_id": result.IntegrationID.ValueString(),
"project_id": result.ProjectID.ValueString(),
"allowed": allowed,
})
}
Loading