diff --git a/docs/data-sources/deployment.md b/docs/data-sources/deployment.md new file mode 100644 index 00000000..7c15b640 --- /dev/null +++ b/docs/data-sources/deployment.md @@ -0,0 +1,41 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vercel_deployment Data Source - terraform-provider-vercel" +subcategory: "" +description: |- + Provides information about an existing Deployment. + A Deployment is the result of building your Project and making it available through a live URL. +--- + +# vercel_deployment (Data Source) + +Provides information about an existing Deployment. + +A Deployment is the result of building your Project and making it available through a live URL. + +## Example Usage + +```terraform +data "vercel_deployment" "example" { + id = "https://my-vercel-project.vercel.app" +} +``` + + +## Schema + +### Required + +- `id` (String) The ID or URL of the Deployment to read. + +### Optional + +- `team_id` (String) The Team ID to the Deployment belong to. Required when reading a team resource if a default team has not been set in the provider. + +### Read-Only + +- `domains` (List of String) A list of all the domains (default domains, staging domains and production domains) that were assigned upon deployment creation. +- `production` (Boolean) true if the deployment is a production deployment, meaning production aliases will be assigned. +- `project_id` (String) The project ID to add the deployment to. +- `ref` (String) The branch or commit hash that has been deployed. Note this will only work if the project is configured to use a Git repository. +- `url` (String) A unique URL that is automatically generated for a deployment. diff --git a/examples/data-sources/vercel_deployment/data-source.tf b/examples/data-sources/vercel_deployment/data-source.tf new file mode 100644 index 00000000..f92bc50d --- /dev/null +++ b/examples/data-sources/vercel_deployment/data-source.tf @@ -0,0 +1,3 @@ +data "vercel_deployment" "example" { + id = "https://my-vercel-project.vercel.app" +} diff --git a/vercel/data_source_deployment.go b/vercel/data_source_deployment.go new file mode 100644 index 00000000..89a53859 --- /dev/null +++ b/vercel/data_source_deployment.go @@ -0,0 +1,164 @@ +package vercel + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/vercel/terraform-provider-vercel/client" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &deploymentDataSource{} + _ datasource.DataSourceWithConfigure = &deploymentDataSource{} +) + +func newDeploymentDataSource() datasource.DataSource { + return &deploymentDataSource{} +} + +type deploymentDataSource struct { + client *client.Client +} + +func (d *deploymentDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_deployment" +} + +func (d *deploymentDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.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 Data Source Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = client +} + +// Schema returns the schema information for an deployment data source +func (r *deploymentDataSource) Schema(_ context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: ` +Provides information about an existing Deployment. + +A Deployment is the result of building your Project and making it available through a live URL. +`, + Attributes: map[string]schema.Attribute{ + "team_id": schema.StringAttribute{ + Description: "The Team ID to the Deployment belong to. Required when reading a team resource if a default team has not been set in the provider.", + Optional: true, + Computed: true, + }, + "id": schema.StringAttribute{ + Required: true, + Description: "The ID or URL of the Deployment to read.", + }, + "domains": schema.ListAttribute{ + Description: "A list of all the domains (default domains, staging domains and production domains) that were assigned upon deployment creation.", + Computed: true, + ElementType: types.StringType, + }, + "project_id": schema.StringAttribute{ + Description: "The project ID to add the deployment to.", + Computed: true, + }, + "url": schema.StringAttribute{ + Description: "A unique URL that is automatically generated for a deployment.", + Computed: true, + }, + "production": schema.BoolAttribute{ + Description: "true if the deployment is a production deployment, meaning production aliases will be assigned.", + Computed: true, + }, + "ref": schema.StringAttribute{ + Description: "The branch or commit hash that has been deployed. Note this will only work if the project is configured to use a Git repository.", + Computed: true, + }, + }, + } +} + +type DeploymentDataSource struct { + Domains types.List `tfsdk:"domains"` + ID types.String `tfsdk:"id"` + Production types.Bool `tfsdk:"production"` + ProjectID types.String `tfsdk:"project_id"` + TeamID types.String `tfsdk:"team_id"` + URL types.String `tfsdk:"url"` + Ref types.String `tfsdk:"ref"` +} + +func convertResponseToDeploymentDataSource(in client.DeploymentResponse) DeploymentDataSource { + ref := types.StringNull() + if in.GitSource.Ref != "" { + ref = types.StringValue(in.GitSource.Ref) + } + + var domains []attr.Value + for _, a := range in.Aliases { + domains = append(domains, types.StringValue(a)) + } + return DeploymentDataSource{ + Domains: types.ListValueMust(types.StringType, domains), + Production: types.BoolValue(in.Target != nil && *in.Target == "production"), + TeamID: toTeamID(in.TeamID), + ProjectID: types.StringValue(in.ProjectID), + ID: types.StringValue(in.ID), + URL: types.StringValue(in.URL), + Ref: ref, + } +} + +// Read will read the deployment information by requesting it from the Vercel API, and will update terraform +// with this information. +// It is called by the provider whenever data source values should be read to update state. +func (d *deploymentDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config DeploymentDataSource + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, err := d.client.GetDeployment(ctx, config.ID.ValueString(), config.TeamID.ValueString()) + if client.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + "Error reading deployment", + fmt.Sprintf("Could not get deployment %s %s, unexpected error: %s", + config.TeamID.ValueString(), + config.ID.ValueString(), + err, + ), + ) + return + } + + result := convertResponseToDeploymentDataSource(out) + tflog.Info(ctx, "read deployment", map[string]interface{}{ + "team_id": result.TeamID.ValueString(), + "project_id": result.ID.ValueString(), + }) + + diags = resp.State.Set(ctx, result) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/vercel/data_source_deployment_test.go b/vercel/data_source_deployment_test.go new file mode 100644 index 00000000..d7bf1e16 --- /dev/null +++ b/vercel/data_source_deployment_test.go @@ -0,0 +1,72 @@ +package vercel_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAcc_DeploymentDataSource(t *testing.T) { + name := acctest.RandString(16) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDeploymentDataSourceConfig(name, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("data.vercel_deployment.by_id", "id"), + resource.TestCheckResourceAttrSet("data.vercel_deployment.by_id", "project_id"), + resource.TestCheckResourceAttrSet("data.vercel_deployment.by_id", "url"), + resource.TestCheckResourceAttr("data.vercel_deployment.by_id", "production", "true"), + resource.TestCheckResourceAttr("data.vercel_deployment.by_id", "domains.#", "2"), + + resource.TestCheckResourceAttrSet("data.vercel_deployment.by_url", "id"), + resource.TestCheckResourceAttrSet("data.vercel_deployment.by_url", "project_id"), + resource.TestCheckResourceAttrSet("data.vercel_deployment.by_url", "url"), + resource.TestCheckResourceAttr("data.vercel_deployment.by_url", "production", "true"), + resource.TestCheckResourceAttr("data.vercel_deployment.by_url", "domains.#", "2"), + ), + }, + }, + }) +} + +func testAccDeploymentDataSourceConfig(name, teamID string) string { + return fmt.Sprintf(` +data "vercel_deployment" "by_id" { + id = vercel_deployment.test.id +} + +data "vercel_deployment" "by_url" { + id = vercel_deployment.test.url +} + +resource "vercel_project" "test" { + name = "test-acc-deployment-%[1]s" + %[2]s + environment = [ + { + key = "bar" + value = "baz" + target = ["preview"] + } + ] +} + +data "vercel_prebuilt_project" "test" { + path = "examples/two" +} + +resource "vercel_deployment" "test" { + project_id = vercel_project.test.id + %[2]s + + production = true + files = data.vercel_prebuilt_project.test.output + path_prefix = data.vercel_prebuilt_project.test.path +} +`, name, teamID) +} diff --git a/vercel/provider.go b/vercel/provider.go index 3988ed6a..9c88bd8e 100644 --- a/vercel/provider.go +++ b/vercel/provider.go @@ -68,6 +68,7 @@ func (p *vercelProvider) Resources(_ context.Context) []func() resource.Resource func (p *vercelProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ newAliasDataSource, + newDeploymentDataSource, newEdgeConfigDataSource, newEdgeConfigSchemaDataSource, newEdgeConfigTokenDataSource,