diff --git a/.gitignore b/.gitignore index 611e48c..4c48cab 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ terraform-provider* autogen TODO.md NOTES.md -.DS_Store \ No newline at end of file +.DS_Store +.env \ No newline at end of file diff --git a/docs/resources/project_artefacts.md b/docs/resources/project_artefacts.md index 17b94ff..c6c4219 100644 --- a/docs/resources/project_artefacts.md +++ b/docs/resources/project_artefacts.md @@ -34,7 +34,7 @@ resource "dbtcloud_project_artefacts" "my_project_artefacts" { ### Read-Only -- `id` (String) The ID of this resource. +- `id` (String) The ID of the project artefacts resource. ## Import diff --git a/pkg/framework/objects/project_artefacts/model.go b/pkg/framework/objects/project_artefacts/model.go new file mode 100644 index 0000000..c950ee3 --- /dev/null +++ b/pkg/framework/objects/project_artefacts/model.go @@ -0,0 +1,12 @@ +package project_artefacts + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type ProjectArtefactsResourceModel struct { + ID types.String `tfsdk:"id"` + ProjectID types.Int64 `tfsdk:"project_id"` + DocsJobID types.Int64 `tfsdk:"docs_job_id"` + FreshnessJobID types.Int64 `tfsdk:"freshness_job_id"` +} diff --git a/pkg/framework/objects/project_artefacts/resource.go b/pkg/framework/objects/project_artefacts/resource.go new file mode 100644 index 0000000..36adbb6 --- /dev/null +++ b/pkg/framework/objects/project_artefacts/resource.go @@ -0,0 +1,238 @@ +package project_artefacts + +import ( + "context" + "strconv" + "strings" + + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/helper" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ resource.Resource = &projectArtefactsResource{} + _ resource.ResourceWithConfigure = &projectArtefactsResource{} + _ resource.ResourceWithImportState = &projectArtefactsResource{} +) + +type projectArtefactsResource struct { + client *dbt_cloud.Client +} + +// ImportState implements resource.ResourceWithImportState. +func (p *projectArtefactsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + id_as_int, err := strconv.Atoi(req.ID) + if err != nil { + resp.Diagnostics.AddError("Invalid ID", "The ID must be an integer") + return + } + resp.State.SetAttribute(ctx, path.Root("id"), req.ID) + resp.State.SetAttribute(ctx, path.Root("project_id"), id_as_int) + +} + +// Configure implements resource.ResourceWithConfigure. +func (p *projectArtefactsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + switch c := req.ProviderData.(type) { + case nil: // do nothing + case *dbt_cloud.Client: + p.client = c + default: + resp.Diagnostics.AddError("Missing client", "A client is required to configure the project artefacts resource") + } +} + +// Create implements resource.Resource. +func (p *projectArtefactsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ProjectArtefactsResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + projectIDString := strconv.FormatInt(plan.ProjectID.ValueInt64(), 10) + + project, err := p.client.GetProject(projectIDString) + if err != nil { + resp.Diagnostics.AddError( + "Unable to get project", + "Error: "+err.Error(), + ) + + return + } + + if plan.DocsJobID.ValueInt64() != 0 { + conv := int(plan.DocsJobID.ValueInt64()) + project.DocsJobId = &conv + } else { + project.DocsJobId = nil + } + + if plan.FreshnessJobID.ValueInt64() != 0 { + conv := int(plan.FreshnessJobID.ValueInt64()) + project.FreshnessJobId = &conv + } else { + project.FreshnessJobId = nil + } + + if _, err := p.client.UpdateProject(projectIDString, *project); err != nil { + resp.Diagnostics.AddError( + "Unable to update project", + "Error: "+err.Error(), + ) + + return + } + + plan.ID = types.StringValue(strconv.Itoa(*project.ID)) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +// Delete implements resource.Resource. +func (p *projectArtefactsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state ProjectArtefactsResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + projectIDString := strconv.FormatInt(state.ProjectID.ValueInt64(), 10) + + project, err := p.client.GetProject(projectIDString) + if err != nil { + resp.Diagnostics.AddError( + "Unable to get project", + "Error: "+err.Error(), + ) + + return + } + + project.FreshnessJobId = nil + project.DocsJobId = nil + + _, err = p.client.UpdateProject(projectIDString, *project) + if err != nil { + resp.Diagnostics.AddError( + "Unable to update project", + "Error: "+err.Error(), + ) + + return + } +} + +// Metadata implements resource.Resource. +func (p *projectArtefactsResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_project_artefacts" +} + +// Read implements resource.Resource. +func (p *projectArtefactsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state ProjectArtefactsResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + projectIDString := strconv.FormatInt(state.ProjectID.ValueInt64(), 10) + + project, err := p.client.GetProject(projectIDString) + if err != nil { + if strings.HasPrefix(err.Error(), "resource-not-found") { + resp.Diagnostics.AddError( + "Project not found", + "The project artefacts resource was not found and has been removed from the state.", + ) + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError( + "Unable to get project", + "Error: "+err.Error(), + ) + + return + } + + state.ID = types.StringValue(strconv.Itoa(*project.ID)) + if project.DocsJobId != nil { + state.DocsJobID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(project.DocsJobId)) + } + + if project.FreshnessJobId != nil { + state.FreshnessJobID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(project.FreshnessJobId)) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) + +} + +// Update implements resource.Resource. +func (p *projectArtefactsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state ProjectArtefactsResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + projectIDString := strconv.FormatInt(plan.ProjectID.ValueInt64(), 10) + + project, err := p.client.GetProject(projectIDString) + if err != nil { + resp.Diagnostics.AddError( + "Unable to get project", + "Error: "+err.Error(), + ) + + return + } + + if !state.DocsJobID.Equal(plan.DocsJobID) { + if plan.DocsJobID.ValueInt64() != 0 { + conv := int(plan.DocsJobID.ValueInt64()) + project.DocsJobId = &conv + } else { + project.DocsJobId = nil + } + } + + if !state.FreshnessJobID.Equal(plan.FreshnessJobID) { + if plan.FreshnessJobID.ValueInt64() != 0 { + conv := int(plan.FreshnessJobID.ValueInt64()) + project.FreshnessJobId = &conv + } else { + project.FreshnessJobId = nil + } + } + + project, err = p.client.UpdateProject(projectIDString, *project) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to update project", + "Error: "+err.Error(), + ) + + return + } + + plan.DocsJobID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(project.DocsJobId)) + plan.FreshnessJobID = types.Int64PointerValue(helper.IntPointerToInt64Pointer(project.FreshnessJobId)) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func ProjectArtefactsResource() resource.Resource { + return &projectArtefactsResource{} +} diff --git a/pkg/sdkv2/resources/project_artefacts_acceptance_test.go b/pkg/framework/objects/project_artefacts/resource_acceptance_test.go similarity index 96% rename from pkg/sdkv2/resources/project_artefacts_acceptance_test.go rename to pkg/framework/objects/project_artefacts/resource_acceptance_test.go index ba475bd..99fcab6 100644 --- a/pkg/sdkv2/resources/project_artefacts_acceptance_test.go +++ b/pkg/framework/objects/project_artefacts/resource_acceptance_test.go @@ -1,4 +1,4 @@ -package resources_test +package project_artefacts_test import ( "fmt" @@ -19,7 +19,7 @@ func TestAccDbtCloudProjectArtefactsResource(t *testing.T) { environmentName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { acctest_helper.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest_helper.TestAccProtoV6ProviderFactories, CheckDestroy: testAccCheckDbtCloudProjectArtefactsDestroy, Steps: []resource.TestStep{ @@ -89,7 +89,7 @@ resource "dbtcloud_project_artefacts" "test_project_artefacts" { docs_job_id = dbtcloud_job.test_job.id freshness_job_id = dbtcloud_job.test_job.id } -`, projectName, environmentName, DBT_CLOUD_VERSION, jobName) +`, projectName, environmentName, acctest_helper.DBT_CLOUD_VERSION, jobName) } func testAccDbtCloudProjectArtefactsResourceEmptyConfig(projectName string) string { diff --git a/pkg/framework/objects/project_artefacts/schema.go b/pkg/framework/objects/project_artefacts/schema.go new file mode 100644 index 0000000..8f6cf63 --- /dev/null +++ b/pkg/framework/objects/project_artefacts/schema.go @@ -0,0 +1,47 @@ +package project_artefacts + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" +) + +// Schema implements resource.Resource. +func (p *projectArtefactsResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "[Deprecated] Resource for mentioning what jobs are the source of truth for the legacy dbt Docs and dbt Source Freshness pages. dbt Explorer doesn't require this config anymore.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "The ID of the project artefacts resource.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "project_id": schema.Int64Attribute{ + Description: "Project ID", + Required: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "docs_job_id": schema.Int64Attribute{ + Description: "Docs Job ID", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "freshness_job_id": schema.Int64Attribute{ + Description: "Freshness Job ID", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + }, + } +} diff --git a/pkg/provider/framework_provider.go b/pkg/provider/framework_provider.go index f39ec80..ba2229d 100644 --- a/pkg/provider/framework_provider.go +++ b/pkg/provider/framework_provider.go @@ -22,6 +22,7 @@ import ( "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/partial_license_map" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/partial_notification" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/project" + "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/project_artefacts" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/service_token" "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/user" @@ -186,33 +187,34 @@ func (p *dbtCloudProvider) DataSources(_ context.Context) []func() datasource.Da return []func() datasource.DataSource{ azure_dev_ops_project.AzureDevOpsProjectDataSource, azure_dev_ops_repository.AzureDevOpsRepositoryDataSource, - user.UserDataSource, - user.UsersDataSource, - notification.NotificationDataSource, environment.EnvironmentDataSource, environment.EnvironmentsDataSource, + global_connection.GlobalConnectionDataSource, + global_connection.GlobalConnectionsDataSource, group.GroupDataSource, job.JobsDataSource, - service_token.ServiceTokenDataSource, + notification.NotificationDataSource, project.ProjectsDataSource, - global_connection.GlobalConnectionDataSource, - global_connection.GlobalConnectionsDataSource, + service_token.ServiceTokenDataSource, + user.UserDataSource, + user.UsersDataSource, } } func (p *dbtCloudProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ - notification.NotificationResource, + account_features.AccountFeaturesResource, + global_connection.GlobalConnectionResource, group_partial_permissions.GroupPartialPermissionsResource, - partial_notification.PartialNotificationResource, - partial_license_map.PartialLicenseMapResource, group.GroupResource, - service_token.ServiceTokenResource, - global_connection.GlobalConnectionResource, - lineage_integration.LineageIntegrationResource, - oauth_configuration.OAuthConfigurationResource, - account_features.AccountFeaturesResource, ip_restrictions_rule.IPRestrictionsRuleResource, license_map.LicenseMapResource, + lineage_integration.LineageIntegrationResource, + notification.NotificationResource, + oauth_configuration.OAuthConfigurationResource, + partial_license_map.PartialLicenseMapResource, + partial_notification.PartialNotificationResource, + project_artefacts.ProjectArtefactsResource, + service_token.ServiceTokenResource, } } diff --git a/pkg/provider/sdk_provider.go b/pkg/provider/sdk_provider.go index 3d2fbfe..b7be42f 100644 --- a/pkg/provider/sdk_provider.go +++ b/pkg/provider/sdk_provider.go @@ -57,7 +57,6 @@ func SDKProvider(version string) func() *schema.Provider { "dbtcloud_project": resources.ResourceProject(), "dbtcloud_project_connection": resources.ResourceProjectConnection(), "dbtcloud_project_repository": resources.ResourceProjectRepository(), - "dbtcloud_project_artefacts": resources.ResourceProjectArtefacts(), "dbtcloud_environment": resources.ResourceEnvironment(), "dbtcloud_environment_variable": resources.ResourceEnvironmentVariable(), "dbtcloud_databricks_credential": resources.ResourceDatabricksCredential(), diff --git a/pkg/sdkv2/resources/project_artefacts.go b/pkg/sdkv2/resources/project_artefacts.go deleted file mode 100644 index a26a7bd..0000000 --- a/pkg/sdkv2/resources/project_artefacts.go +++ /dev/null @@ -1,198 +0,0 @@ -package resources - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/dbt-labs/terraform-provider-dbtcloud/pkg/dbt_cloud" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -var projectArtefactsSchema = map[string]*schema.Schema{ - "project_id": { - Type: schema.TypeInt, - Required: true, - Description: "Project ID", - ForceNew: true, - }, - "docs_job_id": { - Type: schema.TypeInt, - Optional: true, - Description: "Docs Job ID", - }, - "freshness_job_id": { - Type: schema.TypeInt, - Optional: true, - Description: "Freshness Job ID", - }, -} - -func ResourceProjectArtefacts() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceProjectArtefactsCreate, - ReadContext: resourceProjectArtefactsRead, - UpdateContext: resourceProjectArtefactsUpdate, - DeleteContext: resourceProjectArtefactsDelete, - - Schema: projectArtefactsSchema, - DeprecationMessage: "Please remove this deprecated resource from your project. It will be deleted from the provider in a future version. dbt Explorer replaces the need for this config.", - Description: "[Deprecated] Resource for mentioning what jobs are the source of truth for the legacy dbt Docs and dbt Source Freshness pages. dbt Explorer doesn't require this config anymore.", - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - } -} - -func resourceProjectArtefactsCreate( - ctx context.Context, - d *schema.ResourceData, - m interface{}, -) diag.Diagnostics { - c := m.(*dbt_cloud.Client) - - var diags diag.Diagnostics - - projectID := d.Get("project_id").(int) - docsJobID := d.Get("docs_job_id").(int) - freshnessJobID := d.Get("freshness_job_id").(int) - projectIDString := strconv.Itoa(projectID) - - project, err := c.GetProject(projectIDString) - if err != nil { - return diag.FromErr(err) - } - - if docsJobID != 0 { - project.DocsJobId = &docsJobID - } else { - project.DocsJobId = nil - } - - if freshnessJobID != 0 { - project.FreshnessJobId = &freshnessJobID - } else { - project.FreshnessJobId = nil - } - - _, err = c.UpdateProject(projectIDString, *project) - if err != nil { - return diag.FromErr(err) - } - - d.SetId(fmt.Sprintf("%d", *project.ID)) - - resourceProjectArtefactsRead(ctx, d, m) - - return diags -} - -func resourceProjectArtefactsRead( - ctx context.Context, - d *schema.ResourceData, - m interface{}, -) diag.Diagnostics { - c := m.(*dbt_cloud.Client) - - var diags diag.Diagnostics - - projectID, err := strconv.Atoi(d.Id()) - if err != nil { - return diag.FromErr(err) - } - projectIDString := strconv.Itoa(projectID) - - project, err := c.GetProject(projectIDString) - if err != nil { - if strings.HasPrefix(err.Error(), "resource-not-found") { - d.SetId("") - return diags - } - return diag.FromErr(err) - } - - if err := d.Set("project_id", project.ID); err != nil { - return diag.FromErr(err) - } - if err := d.Set("docs_job_id", project.DocsJobId); err != nil { - return diag.FromErr(err) - } - if err := d.Set("freshness_job_id", project.FreshnessJobId); err != nil { - return diag.FromErr(err) - } - - return diags -} - -func resourceProjectArtefactsUpdate( - ctx context.Context, - d *schema.ResourceData, - m interface{}, -) diag.Diagnostics { - - c := m.(*dbt_cloud.Client) - - projectIDString := d.Id() - - if d.HasChange("docs_job_id") || d.HasChange("freshness_job_id") { - project, err := c.GetProject(projectIDString) - if err != nil { - return diag.FromErr(err) - } - - if d.HasChange("docs_job_id") { - docsJobId := d.Get("docs_job_id").(int) - if docsJobId != 0 { - project.DocsJobId = &docsJobId - } else { - project.DocsJobId = nil - } - } - - if d.HasChange("freshness_job_id") { - freshnessJobId := d.Get("freshness_job_id").(int) - if freshnessJobId != 0 { - project.FreshnessJobId = &freshnessJobId - } else { - project.FreshnessJobId = nil - } - } - - _, err = c.UpdateProject(projectIDString, *project) - if err != nil { - return diag.FromErr(err) - } - } - - return resourceProjectArtefactsRead(ctx, d, m) -} - -func resourceProjectArtefactsDelete( - ctx context.Context, - d *schema.ResourceData, - m interface{}, -) diag.Diagnostics { - c := m.(*dbt_cloud.Client) - - var diags diag.Diagnostics - - projectID := d.Get("project_id").(int) - projectIDString := strconv.Itoa(projectID) - - project, err := c.GetProject(projectIDString) - if err != nil { - return diag.FromErr(err) - } - - project.FreshnessJobId = nil - project.DocsJobId = nil - - _, err = c.UpdateProject(projectIDString, *project) - if err != nil { - return diag.FromErr(err) - } - - return diags -}