diff --git a/client/deploy_hooks.go b/client/deploy_hooks.go
new file mode 100644
index 00000000..cc50a56f
--- /dev/null
+++ b/client/deploy_hooks.go
@@ -0,0 +1,84 @@
+package client
+
+import (
+ "context"
+ "fmt"
+ "slices"
+
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+type DeployHook struct {
+ Name string `json:"name"`
+ Ref string `json:"ref"`
+ URL string `json:"url"`
+ ID string `json:"id"`
+}
+
+type CreateDeployHookRequest struct {
+ ProjectID string `json:"-"`
+ TeamID string `json:"-"`
+ Name string `json:"name"`
+ Ref string `json:"ref"`
+}
+
+func (c *Client) CreateDeployHook(ctx context.Context, request CreateDeployHookRequest) (h DeployHook, err error) {
+ url := fmt.Sprintf("%s/v2/projects/%s/deploy-hooks", c.baseURL, request.ProjectID)
+ if c.teamID(request.TeamID) != "" {
+ url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
+ }
+ payload := string(mustMarshal(request))
+ tflog.Info(ctx, "creating deploy hook", map[string]interface{}{
+ "url": url,
+ "payload": payload,
+ })
+
+ var r ProjectResponse
+ err = c.doRequest(clientRequest{
+ ctx: ctx,
+ method: "POST",
+ url: url,
+ body: payload,
+ }, &r)
+ if err != nil {
+ return h, fmt.Errorf("error creating deploy hook: %w", err)
+ }
+
+ // Reverse the list as newest created are at the end
+ slices.Reverse(r.Link.DeployHooks)
+ for _, hook := range r.Link.DeployHooks {
+ if hook.Name == request.Name && hook.Ref == request.Ref {
+ return hook, nil
+ }
+ }
+
+ return h, fmt.Errorf("deploy hook was created successfully, but could not be found")
+}
+
+type DeleteDeployHookRequest struct {
+ ProjectID string
+ TeamID string
+ ID string
+}
+
+func (c *Client) DeleteDeployHook(ctx context.Context, request DeleteDeployHookRequest) error {
+ url := fmt.Sprintf("%s/v2/projects/%s/deploy-hooks/%s", c.baseURL, request.ProjectID, request.ID)
+ if c.teamID(request.TeamID) != "" {
+ url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
+ }
+ payload := string(mustMarshal(request))
+ tflog.Info(ctx, "creating deploy hook", map[string]interface{}{
+ "url": url,
+ "payload": payload,
+ })
+
+ err := c.doRequest(clientRequest{
+ ctx: ctx,
+ method: "DELETE",
+ url: url,
+ }, nil)
+ if err != nil {
+ return fmt.Errorf("error deleting deploy hook: %w", err)
+ }
+ return nil
+}
diff --git a/client/deployment.go b/client/deployment.go
index bcecd34c..7d82cc3f 100644
--- a/client/deployment.go
+++ b/client/deployment.go
@@ -150,7 +150,7 @@ func (e MissingFilesError) Error() string {
}
func (c *Client) getGitSource(ctx context.Context, projectID, ref, teamID string) (gs gitSource, err error) {
- project, err := c.GetProject(ctx, projectID, teamID, false)
+ project, err := c.GetProject(ctx, projectID, teamID)
if err != nil {
return gs, fmt.Errorf("error getting project: %w", err)
}
diff --git a/client/environment_variable.go b/client/environment_variable.go
index 87cc476a..c71f8029 100644
--- a/client/environment_variable.go
+++ b/client/environment_variable.go
@@ -123,7 +123,7 @@ func (c *Client) DeleteEnvironmentVariable(ctx context.Context, projectID, teamI
}, nil)
}
-func (c *Client) getEnvironmentVariables(ctx context.Context, projectID, teamID string) ([]EnvironmentVariable, error) {
+func (c *Client) GetEnvironmentVariables(ctx context.Context, projectID, teamID string) ([]EnvironmentVariable, error) {
url := fmt.Sprintf("%s/v8/projects/%s/env?decrypt=true", c.baseURL, projectID)
if c.teamID(teamID) != "" {
url = fmt.Sprintf("%s&teamId=%s", url, c.teamID(teamID))
diff --git a/client/project.go b/client/project.go
index b0b70de9..282d6281 100644
--- a/client/project.go
+++ b/client/project.go
@@ -64,11 +64,6 @@ func (c *Client) CreateProject(ctx context.Context, teamID string, request Creat
if err != nil {
return r, err
}
- env, err := c.getEnvironmentVariables(ctx, r.ID, teamID)
- if err != nil {
- return r, fmt.Errorf("error getting environment variables: %w", err)
- }
- r.EnvironmentVariables = env
r.TeamID = c.teamID(teamID)
return r, err
}
@@ -96,6 +91,7 @@ type Repository struct {
Type string
Repo string
ProductionBranch *string
+ DeployHooks []DeployHook
}
// getRepoNameFromURL is a helper method to extract the repo name from a GitLab URL.
@@ -120,18 +116,21 @@ func (r *ProjectResponse) Repository() *Repository {
Type: "github",
Repo: fmt.Sprintf("%s/%s", r.Link.Org, r.Link.Repo),
ProductionBranch: r.Link.ProductionBranch,
+ DeployHooks: r.Link.DeployHooks,
}
case "gitlab":
return &Repository{
Type: "gitlab",
Repo: fmt.Sprintf("%s/%s", r.Link.ProjectNamespace, getRepoNameFromURL(r.Link.ProjectURL)),
ProductionBranch: r.Link.ProductionBranch,
+ DeployHooks: r.Link.DeployHooks,
}
case "bitbucket":
return &Repository{
Type: "bitbucket",
Repo: fmt.Sprintf("%s/%s", r.Link.Owner, r.Link.Slug),
ProductionBranch: r.Link.ProductionBranch,
+ DeployHooks: r.Link.DeployHooks,
}
}
return nil
@@ -139,14 +138,13 @@ func (r *ProjectResponse) Repository() *Repository {
// ProjectResponse defines the information Vercel returns about a project.
type ProjectResponse struct {
- BuildCommand *string `json:"buildCommand"`
- CommandForIgnoringBuildStep *string `json:"commandForIgnoringBuildStep"`
- DevCommand *string `json:"devCommand"`
- EnvironmentVariables []EnvironmentVariable `json:"env"`
- Framework *string `json:"framework"`
- ID string `json:"id"`
- TeamID string `json:"-"`
- InstallCommand *string `json:"installCommand"`
+ BuildCommand *string `json:"buildCommand"`
+ CommandForIgnoringBuildStep *string `json:"commandForIgnoringBuildStep"`
+ DevCommand *string `json:"devCommand"`
+ Framework *string `json:"framework"`
+ ID string `json:"id"`
+ TeamID string `json:"-"`
+ InstallCommand *string `json:"installCommand"`
Link *struct {
Type string `json:"type"`
// github
@@ -160,7 +158,8 @@ type ProjectResponse struct {
ProjectURL string `json:"projectUrl"`
ProjectID int64 `json:"projectId,string"`
// production branch
- ProductionBranch *string `json:"productionBranch"`
+ ProductionBranch *string `json:"productionBranch"`
+ DeployHooks []DeployHook `json:"deployHooks"`
} `json:"link"`
Name string `json:"name"`
OutputDirectory *string `json:"outputDirectory"`
@@ -175,14 +174,13 @@ type ProjectResponse struct {
}
// GetProject retrieves information about an existing project from Vercel.
-func (c *Client) GetProject(ctx context.Context, projectID, teamID string, shouldFetchEnvironmentVariables bool) (r ProjectResponse, err error) {
+func (c *Client) GetProject(ctx context.Context, projectID, teamID string) (r ProjectResponse, err error) {
url := fmt.Sprintf("%s/v10/projects/%s", c.baseURL, projectID)
if c.teamID(teamID) != "" {
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID))
}
tflog.Info(ctx, "getting project", map[string]interface{}{
- "url": url,
- "shouldFetchEnvironment": shouldFetchEnvironmentVariables,
+ "url": url,
})
err = c.doRequest(clientRequest{
ctx: ctx,
@@ -194,16 +192,6 @@ func (c *Client) GetProject(ctx context.Context, projectID, teamID string, shoul
return r, fmt.Errorf("unable to get project: %w", err)
}
- if shouldFetchEnvironmentVariables {
- r.EnvironmentVariables, err = c.getEnvironmentVariables(ctx, projectID, teamID)
- if err != nil {
- return r, fmt.Errorf("error getting environment variables for project: %w", err)
- }
- } else {
- // The get project endpoint returns environment variables, but returns them fully
- // encrypted. This isn't useful, so we just remove them.
- r.EnvironmentVariables = nil
- }
r.TeamID = c.teamID(teamID)
return r, err
}
@@ -257,16 +245,15 @@ type UpdateProjectRequest struct {
}
// UpdateProject updates an existing projects configuration within Vercel.
-func (c *Client) UpdateProject(ctx context.Context, projectID, teamID string, request UpdateProjectRequest, shouldFetchEnvironmentVariables bool) (r ProjectResponse, err error) {
+func (c *Client) UpdateProject(ctx context.Context, projectID, teamID string, request UpdateProjectRequest) (r ProjectResponse, err error) {
url := fmt.Sprintf("%s/v9/projects/%s", c.baseURL, projectID)
if c.teamID(teamID) != "" {
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID))
}
payload := string(mustMarshal(request))
tflog.Info(ctx, "updating project", map[string]interface{}{
- "url": url,
- "payload": payload,
- "shouldFetchEnvironmentVariables": shouldFetchEnvironmentVariables,
+ "url": url,
+ "payload": payload,
})
err = c.doRequest(clientRequest{
ctx: ctx,
@@ -277,14 +264,6 @@ func (c *Client) UpdateProject(ctx context.Context, projectID, teamID string, re
if err != nil {
return r, err
}
- if shouldFetchEnvironmentVariables {
- r.EnvironmentVariables, err = c.getEnvironmentVariables(ctx, r.ID, teamID)
- if err != nil {
- return r, fmt.Errorf("error getting environment variables for project: %w", err)
- }
- } else {
- r.EnvironmentVariables = nil
- }
r.TeamID = c.teamID(teamID)
return r, err
@@ -315,11 +294,6 @@ func (c *Client) UpdateProductionBranch(ctx context.Context, request UpdateProdu
if err != nil {
return r, err
}
- env, err := c.getEnvironmentVariables(ctx, r.ID, request.TeamID)
- if err != nil {
- return r, fmt.Errorf("error getting environment variables: %w", err)
- }
- r.EnvironmentVariables = env
r.TeamID = c.teamID(c.teamID(request.TeamID))
return r, err
}
@@ -340,11 +314,6 @@ func (c *Client) UnlinkGitRepoFromProject(ctx context.Context, projectID, teamID
if err != nil {
return r, fmt.Errorf("error unlinking git repo: %w", err)
}
- env, err := c.getEnvironmentVariables(ctx, r.ID, teamID)
- if err != nil {
- return r, fmt.Errorf("error getting environment variables: %w", err)
- }
- r.EnvironmentVariables = env
r.TeamID = c.teamID(teamID)
return r, err
}
@@ -374,11 +343,6 @@ func (c *Client) LinkGitRepoToProject(ctx context.Context, request LinkGitRepoTo
if err != nil {
return r, fmt.Errorf("error linking git repo: %w", err)
}
- env, err := c.getEnvironmentVariables(ctx, r.ID, request.TeamID)
- if err != nil {
- return r, fmt.Errorf("error getting environment variables: %w", err)
- }
- r.EnvironmentVariables = env
r.TeamID = c.teamID(c.teamID(request.TeamID))
return r, err
}
diff --git a/docs/resources/project.md b/docs/resources/project.md
index b1002c69..05839611 100644
--- a/docs/resources/project.md
+++ b/docs/resources/project.md
@@ -107,8 +107,23 @@ Required:
Optional:
+- `deploy_hooks` (Attributes Set) Deploy hooks are unique URLs that allow you to trigger a deployment of a given branch. See https://vercel.com/docs/deployments/deploy-hooks for full information. (see [below for nested schema](#nestedatt--git_repository--deploy_hooks))
- `production_branch` (String) By default, every commit pushed to the main branch will trigger a Production Deployment instead of the usual Preview Deployment. You can switch to a different branch here.
+
+### Nested Schema for `git_repository.deploy_hooks`
+
+Required:
+
+- `name` (String) The name of the deploy hook.
+- `ref` (String) The branch or commit hash that should be deployed.
+
+Read-Only:
+
+- `id` (String) The ID of the deploy hook.
+- `url` (String, Sensitive) A URL that, when a POST request is made to, will trigger a new deployment.
+
+
### Nested Schema for `password_protection`
diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go
index 9bee75cc..0324609a 100644
--- a/vercel/data_source_project.go
+++ b/vercel/data_source_project.go
@@ -243,8 +243,9 @@ type ProjectDataSource struct {
AutoExposeSystemEnvVars types.Bool `tfsdk:"automatically_expose_system_environment_variables"`
}
-func convertResponseToProjectDataSource(ctx context.Context, response client.ProjectResponse, plan Project) (ProjectDataSource, error) {
- project, err := convertResponseToProject(ctx, response, plan)
+func convertResponseToProjectDataSource(ctx context.Context, response client.ProjectResponse, plan Project, environmentVariables []client.EnvironmentVariable) (ProjectDataSource, error) {
+ plan.Environment = types.SetValueMust(envVariableElemType, []attr.Value{})
+ project, err := convertResponseToProject(ctx, response, plan, environmentVariables)
if err != nil {
return ProjectDataSource{}, err
}
@@ -288,7 +289,7 @@ func (d *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest
return
}
- out, err := d.client.GetProject(ctx, config.Name.ValueString(), config.TeamID.ValueString(), true)
+ out, err := d.client.GetProject(ctx, config.Name.ValueString(), config.TeamID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error reading project",
@@ -301,7 +302,15 @@ func (d *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest
return
}
- result, err := convertResponseToProjectDataSource(ctx, out, nullProject)
+ environmentVariables, err := d.client.GetEnvironmentVariables(ctx, out.ID, out.TeamID)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error reading project environment variables",
+ "Could not read project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ result, err := convertResponseToProjectDataSource(ctx, out, nullProject, environmentVariables)
if err != nil {
resp.Diagnostics.AddError(
"Error converting project response to model",
diff --git a/vercel/resource_deployment.go b/vercel/resource_deployment.go
index 8d7a53de..3483dfad 100644
--- a/vercel/resource_deployment.go
+++ b/vercel/resource_deployment.go
@@ -524,7 +524,7 @@ func (r *deploymentResource) Create(ctx context.Context, req resource.CreateRequ
Ref: plan.Ref.ValueString(),
}
- _, err = r.client.GetProject(ctx, plan.ProjectID.ValueString(), plan.TeamID.ValueString(), false)
+ _, err = r.client.GetProject(ctx, plan.ProjectID.ValueString(), plan.TeamID.ValueString())
if client.NotFound(err) {
resp.Diagnostics.AddError(
"Error creating deployment",
diff --git a/vercel/resource_project.go b/vercel/resource_project.go
index 2600bc70..33197d16 100644
--- a/vercel/resource_project.go
+++ b/vercel/resource_project.go
@@ -6,6 +6,7 @@ import (
"regexp"
"github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
@@ -22,8 +23,9 @@ import (
)
var (
- _ resource.Resource = &projectResource{}
- _ resource.ResourceWithConfigure = &projectResource{}
+ _ resource.Resource = &projectResource{}
+ _ resource.ResourceWithConfigure = &projectResource{}
+ _ resource.ResourceWithImportState = &projectResource{}
)
func newProjectResource() resource.Resource {
@@ -175,6 +177,31 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ
Optional: true,
Computed: true,
},
+ "deploy_hooks": schema.SetNestedAttribute{
+ Description: "Deploy hooks are unique URLs that allow you to trigger a deployment of a given branch. See https://vercel.com/docs/deployments/deploy-hooks for full information.",
+ Optional: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "The ID of the deploy hook.",
+ Computed: true,
+ },
+ "name": schema.StringAttribute{
+ Description: "The name of the deploy hook.",
+ Required: true,
+ },
+ "ref": schema.StringAttribute{
+ Description: "The branch or commit hash that should be deployed.",
+ Required: true,
+ },
+ "url": schema.StringAttribute{
+ Description: "A URL that, when a POST request is made to, will trigger a new deployment.",
+ Computed: true,
+ Sensitive: true,
+ },
+ },
+ },
+ },
},
},
"vercel_authentication": schema.SingleNestedAttribute{
@@ -455,11 +482,19 @@ func (e *EnvironmentItem) toEnvironmentVariableRequest() client.EnvironmentVaria
}
}
+type DeployHook struct {
+ Name types.String `tfsdk:"name"`
+ Ref types.String `tfsdk:"ref"`
+ URL types.String `tfsdk:"url"`
+ ID types.String `tfsdk:"id"`
+}
+
// GitRepository reflects the state terraform stores internally for a nested git_repository block on a project resource.
type GitRepository struct {
Type types.String `tfsdk:"type"`
Repo types.String `tfsdk:"repo"`
ProductionBranch types.String `tfsdk:"production_branch"`
+ DeployHooks types.Set `tfsdk:"deploy_hooks"`
}
func (g *GitRepository) isDifferentRepo(other *GitRepository) bool {
@@ -649,7 +684,23 @@ func hasSameTarget(p EnvironmentItem, target []string) bool {
return true
}
-func convertResponseToProject(ctx context.Context, response client.ProjectResponse, plan Project) (Project, error) {
+var deployHookType = types.ObjectType{
+ AttrTypes: map[string]attr.Type{
+ "name": types.StringType,
+ "ref": types.StringType,
+ "url": types.StringType,
+ "id": types.StringType,
+ },
+}
+
+type deployHook struct {
+ Name string `tfsdk:"name"`
+ Ref string `tfsdk:"ref"`
+ URL string `tfsdk:"url"`
+ ID string `tfsdk:"id"`
+}
+
+func convertResponseToProject(ctx context.Context, response client.ProjectResponse, plan Project, environmentVariables []client.EnvironmentVariable) (Project, error) {
fields := plan.coercedFields()
var gr *GitRepository
@@ -658,10 +709,27 @@ func convertResponseToProject(ctx context.Context, response client.ProjectRespon
Type: types.StringValue(repo.Type),
Repo: types.StringValue(repo.Repo),
ProductionBranch: types.StringNull(),
+ DeployHooks: types.SetNull(deployHookType),
}
if repo.ProductionBranch != nil {
gr.ProductionBranch = types.StringValue(*repo.ProductionBranch)
}
+ if repo.DeployHooks != nil && plan.GitRepository != nil && !plan.GitRepository.DeployHooks.IsNull() {
+ var dh []deployHook
+ for _, h := range repo.DeployHooks {
+ dh = append(dh, deployHook{
+ Name: h.Name,
+ Ref: h.Ref,
+ URL: h.URL,
+ ID: h.ID,
+ })
+ }
+ hooks, diags := types.SetValueFrom(ctx, deployHookType, dh)
+ if diags.HasError() {
+ return Project{}, fmt.Errorf("error reading project deploy hooks: %s - %s", diags[0].Summary(), diags[0].Detail())
+ }
+ gr.DeployHooks = hooks
+ }
}
var pp *PasswordProtectionWithPassword
@@ -702,7 +770,7 @@ func convertResponseToProject(ctx context.Context, response client.ProjectRespon
}
var env []attr.Value
- for _, e := range response.EnvironmentVariables {
+ for _, e := range environmentVariables {
target := []attr.Value{}
for _, t := range e.Target {
target = append(target, types.StringValue(t))
@@ -758,7 +826,7 @@ func convertResponseToProject(ctx context.Context, response client.ProjectRespon
}
environmentEntry := types.SetValueMust(envVariableElemType, env)
- if len(response.EnvironmentVariables) == 0 && plan.Environment.IsNull() {
+ if plan.Environment.IsNull() {
environmentEntry = types.SetNull(envVariableElemType)
}
@@ -814,7 +882,16 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
return
}
- result, err := convertResponseToProject(ctx, out, plan)
+ environmentVariables, err := r.client.GetEnvironmentVariables(ctx, out.ID, out.TeamID)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error reading project environment variables",
+ "Could not create project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+
+ result, err := convertResponseToProject(ctx, out, plan, environmentVariables)
if err != nil {
resp.Diagnostics.AddError(
"Error converting project response to model",
@@ -832,8 +909,46 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
return
}
+ if plan.GitRepository != nil && !plan.GitRepository.DeployHooks.IsNull() && !plan.GitRepository.DeployHooks.IsUnknown() {
+ var hooks []DeployHook
+ diags := plan.GitRepository.DeployHooks.ElementsAs(ctx, &hooks, false)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ for _, hook := range hooks {
+ hook, err := r.client.CreateDeployHook(ctx, client.CreateDeployHookRequest{
+ ProjectID: result.ID.ValueString(),
+ TeamID: result.TeamID.ValueString(),
+ Name: hook.Name.ValueString(),
+ Ref: hook.Ref.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating deploy hook",
+ "Could not create project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ out.Link.DeployHooks = append(out.Link.DeployHooks, hook)
+ }
+ result, err := convertResponseToProject(ctx, out, plan, environmentVariables)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error converting project response to model",
+ "Could not create project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ diags = resp.State.Set(ctx, result)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
if plan.PasswordProtection != nil || plan.VercelAuthentication != nil || plan.TrustedIps != nil || !plan.AutoExposeSystemEnvVars.IsNull() {
- out, err = r.client.UpdateProject(ctx, result.ID.ValueString(), plan.TeamID.ValueString(), plan.toUpdateProjectRequest(plan.Name.ValueString()), !plan.Environment.IsNull())
+ out, err = r.client.UpdateProject(ctx, result.ID.ValueString(), plan.TeamID.ValueString(), plan.toUpdateProjectRequest(plan.Name.ValueString()))
if err != nil {
resp.Diagnostics.AddError(
"Error updating project as part of creating project",
@@ -842,7 +957,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
return
}
- result, err = convertResponseToProject(ctx, out, plan)
+ result, err = convertResponseToProject(ctx, out, plan, environmentVariables)
if err != nil {
resp.Diagnostics.AddError(
"Error converting project response to model",
@@ -900,7 +1015,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
return
}
- result, err = convertResponseToProject(ctx, out, plan)
+ result, err = convertResponseToProject(ctx, out, plan, environmentVariables)
if err != nil {
resp.Diagnostics.AddError(
"Error converting project response to model",
@@ -930,7 +1045,7 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
return
}
- out, err := r.client.GetProject(ctx, state.ID.ValueString(), state.TeamID.ValueString(), !state.Environment.IsNull())
+ out, err := r.client.GetProject(ctx, state.ID.ValueString(), state.TeamID.ValueString())
if client.NotFound(err) {
resp.State.RemoveResource(ctx)
return
@@ -947,7 +1062,15 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
return
}
- result, err := convertResponseToProject(ctx, out, state)
+ environmentVariables, err := r.client.GetEnvironmentVariables(ctx, out.ID, out.TeamID)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error reading project environment variables",
+ "Could not read project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ result, err := convertResponseToProject(ctx, out, state, environmentVariables)
if err != nil {
resp.Diagnostics.AddError(
"Error converting project response to model",
@@ -998,6 +1121,51 @@ func diffEnvVars(oldVars, newVars []EnvironmentItem) (toCreate, toRemove []Envir
return toCreate, toRemove
}
+func containsDeployHook(hooks []DeployHook, h DeployHook) bool {
+ for _, hook := range hooks {
+ if hook.ID == h.ID {
+ return true
+ }
+ }
+ return false
+}
+
+func diffDeployHooks(ctx context.Context, new, old *GitRepository) (toCreate, toRemove []DeployHook, diags diag.Diagnostics) {
+ if new == nil && old == nil {
+ return nil, nil, nil
+ }
+ if new == nil {
+ diags = old.DeployHooks.ElementsAs(ctx, &toRemove, false)
+ return nil, toRemove, diags
+ }
+ if old == nil {
+ diags = new.DeployHooks.ElementsAs(ctx, &toCreate, false)
+ return toCreate, nil, diags
+ }
+ var oldHooks []DeployHook
+ var newHooks []DeployHook
+ diags = old.DeployHooks.ElementsAs(ctx, &oldHooks, false)
+ if diags.HasError() {
+ return nil, nil, diags
+ }
+ diags = new.DeployHooks.ElementsAs(ctx, &newHooks, false)
+ if diags.HasError() {
+ return nil, nil, diags
+ }
+
+ for _, h := range oldHooks {
+ if !containsDeployHook(newHooks, h) {
+ toRemove = append(toRemove, h)
+ }
+ }
+ for _, h := range newHooks {
+ if !containsDeployHook(oldHooks, h) {
+ toCreate = append(toCreate, h)
+ }
+ }
+ return toCreate, toRemove, diags
+}
+
// Update will update a project and it's associated environment variables via the vercel API.
// Environment variables are manually diffed and updated individually. Once the environment
// variables are all updated, the project is updated too.
@@ -1112,7 +1280,7 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest
}
}
- out, err := r.client.UpdateProject(ctx, state.ID.ValueString(), state.TeamID.ValueString(), plan.toUpdateProjectRequest(state.Name.ValueString()), !plan.Environment.IsNull())
+ out, err := r.client.UpdateProject(ctx, state.ID.ValueString(), state.TeamID.ValueString(), plan.toUpdateProjectRequest(state.Name.ValueString()))
if err != nil {
resp.Diagnostics.AddError(
"Error updating project",
@@ -1210,11 +1378,65 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest
}
}
- result, err := convertResponseToProject(ctx, out, plan)
+ hooksToCreate, hooksToRemove, diags := diffDeployHooks(ctx, plan.GitRepository, state.GitRepository)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ for _, h := range hooksToRemove {
+ err := r.client.DeleteDeployHook(ctx, client.DeleteDeployHookRequest{
+ ProjectID: plan.ID.ValueString(),
+ TeamID: plan.TeamID.ValueString(),
+ ID: h.ID.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error deleting deploy hook",
+ "Could not update project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ }
+ for _, h := range hooksToCreate {
+ _, err := r.client.CreateDeployHook(ctx, client.CreateDeployHookRequest{
+ ProjectID: plan.ID.ValueString(),
+ TeamID: plan.TeamID.ValueString(),
+ Name: h.Name.ValueString(),
+ Ref: h.Ref.ValueString(),
+ })
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating deploy hook",
+ "Could not update project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ }
+ if hooksToCreate != nil || hooksToRemove != nil {
+ // Re-fetch the project to ensure the hooks afterwards are all correct
+ out, err = r.client.GetProject(ctx, plan.ID.ValueString(), plan.TeamID.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error reading project",
+ "Could not update project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ }
+
+ environmentVariables, err := r.client.GetEnvironmentVariables(ctx, out.ID, out.TeamID)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error reading project environment variables",
+ "Could not update project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ result, err := convertResponseToProject(ctx, out, plan, environmentVariables)
if err != nil {
resp.Diagnostics.AddError(
"Error converting project response to model",
- "Could not create project, unexpected error: "+err.Error(),
+ "Could not update project, unexpected error: "+err.Error(),
)
return
}
@@ -1274,7 +1496,7 @@ func (r *projectResource) ImportState(ctx context.Context, req resource.ImportSt
)
}
- out, err := r.client.GetProject(ctx, projectID, teamID, false)
+ out, err := r.client.GetProject(ctx, projectID, teamID)
if err != nil {
resp.Diagnostics.AddError(
"Error reading project",
@@ -1287,11 +1509,19 @@ func (r *projectResource) ImportState(ctx context.Context, req resource.ImportSt
return
}
- result, err := convertResponseToProject(ctx, out, nullProject)
+ environmentVariables, err := r.client.GetEnvironmentVariables(ctx, out.ID, out.TeamID)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error reading project environment variables",
+ "Could not import project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ result, err := convertResponseToProject(ctx, out, nullProject, environmentVariables)
if err != nil {
resp.Diagnostics.AddError(
"Error converting project response to model",
- "Could not create project, unexpected error: "+err.Error(),
+ "Could not import project, unexpected error: "+err.Error(),
)
return
}
diff --git a/vercel/resource_project_domain.go b/vercel/resource_project_domain.go
index 488dba83..b4dbf5ba 100644
--- a/vercel/resource_project_domain.go
+++ b/vercel/resource_project_domain.go
@@ -148,7 +148,7 @@ func (r *projectDomainResource) Create(ctx context.Context, req resource.CreateR
return
}
- _, err := r.client.GetProject(ctx, plan.ProjectID.ValueString(), plan.TeamID.ValueString(), false)
+ _, err := r.client.GetProject(ctx, plan.ProjectID.ValueString(), plan.TeamID.ValueString())
if client.NotFound(err) {
resp.Diagnostics.AddError(
"Error creating project domain",
diff --git a/vercel/resource_project_environment_variable.go b/vercel/resource_project_environment_variable.go
index 4a63295a..cbf1e142 100644
--- a/vercel/resource_project_environment_variable.go
+++ b/vercel/resource_project_environment_variable.go
@@ -213,7 +213,7 @@ func (r *projectEnvironmentVariableResource) Create(ctx context.Context, req res
return
}
- _, err := r.client.GetProject(ctx, plan.ProjectID.ValueString(), plan.TeamID.ValueString(), false)
+ _, err := r.client.GetProject(ctx, plan.ProjectID.ValueString(), plan.TeamID.ValueString())
if client.NotFound(err) {
resp.Diagnostics.AddError(
"Error creating project environment variable",
diff --git a/vercel/resource_project_environment_variable_test.go b/vercel/resource_project_environment_variable_test.go
index 4294c988..d52d8eab 100644
--- a/vercel/resource_project_environment_variable_test.go
+++ b/vercel/resource_project_environment_variable_test.go
@@ -37,12 +37,12 @@ func testAccProjectEnvironmentVariablesDoNotExist(n, teamID string) resource.Tes
return fmt.Errorf("no ID is set")
}
- project, err := testClient().GetProject(context.TODO(), rs.Primary.ID, teamID, true)
+ envs, err := testClient().GetEnvironmentVariables(context.TODO(), rs.Primary.ID, teamID)
if err != nil {
return fmt.Errorf("could not fetch the project: %w", err)
}
- if len(project.EnvironmentVariables) != 0 {
+ if len(envs) != 0 {
return fmt.Errorf("project environment variables not deleted, they still exist")
}
diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go
index 39740726..9d23c705 100644
--- a/vercel/resource_project_test.go
+++ b/vercel/resource_project_test.go
@@ -283,7 +283,7 @@ func testAccProjectExists(n, teamID string) resource.TestCheckFunc {
return fmt.Errorf("no projectID is set")
}
- _, err := testClient().GetProject(context.TODO(), rs.Primary.ID, teamID, false)
+ _, err := testClient().GetProject(context.TODO(), rs.Primary.ID, teamID)
return err
}
}
@@ -299,7 +299,7 @@ func testAccProjectDestroy(n, teamID string) resource.TestCheckFunc {
return fmt.Errorf("no projectID is set")
}
- _, err := testClient().GetProject(context.TODO(), rs.Primary.ID, teamID, false)
+ _, err := testClient().GetProject(context.TODO(), rs.Primary.ID, teamID)
if err == nil {
return fmt.Errorf("expected not_found error, but got no error")
}
@@ -487,6 +487,12 @@ resource "vercel_project" "test_git" {
git_repository = {
type = "github"
repo = "%s"
+ deploy_hooks = [
+ {
+ ref = "main"
+ name = "some deploy hook"
+ }
+ ]
}
environment = [
{
@@ -510,6 +516,16 @@ resource "vercel_project" "test_git" {
type = "github"
repo = "%s"
production_branch = "staging"
+ deploy_hooks = [
+ {
+ ref = "main"
+ name = "some deploy hook"
+ },
+ {
+ ref = "main"
+ name = "some other hook"
+ }
+ ]
}
environment = [
{