From 9f63dc55689619fe3e0b993d9fd771ff59f0f6e7 Mon Sep 17 00:00:00 2001 From: Trent DiBacco Date: Tue, 15 Oct 2024 15:57:57 -0700 Subject: [PATCH 1/3] VAULT-31135: add new hcp_vault_radar_source_github_cloud resource. --- .../vault_radar_source_github_cloud.md | 44 +++ .../resource.tf | 10 + internal/provider/provider.go | 1 + internal/provider/vaultradar/radar_source.go | 191 +++++++++++++ .../resource_radar_source_github_cloud.go | 96 +++++++ ...resource_radar_source_github_cloud_test.go | 47 +++ ...resource_radar_source_github_enterprise.go | 268 +++++------------- .../vault_radar_source_github_cloud.md.tmpl | 19 ++ 8 files changed, 476 insertions(+), 200 deletions(-) create mode 100644 docs/resources/vault_radar_source_github_cloud.md create mode 100644 examples/resources/hcp_vault_radar_source_github_cloud/resource.tf create mode 100644 internal/provider/vaultradar/radar_source.go create mode 100644 internal/provider/vaultradar/resource_radar_source_github_cloud.go create mode 100644 internal/provider/vaultradar/resource_radar_source_github_cloud_test.go create mode 100644 templates/resources/vault_radar_source_github_cloud.md.tmpl diff --git a/docs/resources/vault_radar_source_github_cloud.md b/docs/resources/vault_radar_source_github_cloud.md new file mode 100644 index 000000000..58533c309 --- /dev/null +++ b/docs/resources/vault_radar_source_github_cloud.md @@ -0,0 +1,44 @@ +--- +page_title: "hcp_vault_radar_source_github_cloud Resource - terraform-provider-hcp" +subcategory: "" +description: |- + This terraform resource manages a GitHub Cloud data source lifecycle in Vault Radar. +--- + +# hcp_vault_radar_source_github_cloud (Resource) + +-> **Note:** HCP Vault Radar Terraform resources are in preview. + +This terraform resource manages a GitHub Cloud data source lifecycle in Vault Radar. + +## Example Usage + +```terraform +variable "github_cloud_token" { + type = string + sensitive = true +} + +resource "hcp_vault_radar_source_github_cloud" "example" { + github_organization = "my-github-org" + token = var.github_cloud_token + project_id = "my-project-id" +} +``` + + + +## Schema + +### Required + +- `github_organization` (String) GitHub organization Vault Radar will monitor. Example: type "octocat" for the org https://github.com/octocat +- `token` (String, Sensitive) GitHub personal access token. + +### Optional + +- `project_id` (String) The ID of the HCP project where Vault Radar is located. If not specified, the project specified in the HCP Provider config block will be used, if configured. + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/examples/resources/hcp_vault_radar_source_github_cloud/resource.tf b/examples/resources/hcp_vault_radar_source_github_cloud/resource.tf new file mode 100644 index 000000000..0c5eea1f3 --- /dev/null +++ b/examples/resources/hcp_vault_radar_source_github_cloud/resource.tf @@ -0,0 +1,10 @@ +variable "github_cloud_token" { + type = string + sensitive = true +} + +resource "hcp_vault_radar_source_github_cloud" "example" { + github_organization = "my-github-org" + token = var.github_cloud_token + project_id = "my-project-id" +} \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index db70f0a2a..3137a0666 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -181,6 +181,7 @@ func (p *ProviderFramework) Resources(ctx context.Context) []func() resource.Res waypoint.NewTfcConfigResource, // Radar vaultradar.NewSourceGitHubEnterpriseResource, + vaultradar.NewSourceGitHubCloudResource, vaultradar.NewIntegrationJiraConnectionResource, vaultradar.NewIntegrationJiraSubscriptionResource, vaultradar.NewIntegrationSlackConnectionResource, diff --git a/internal/provider/vaultradar/radar_source.go b/internal/provider/vaultradar/radar_source.go new file mode 100644 index 000000000..26c963553 --- /dev/null +++ b/internal/provider/vaultradar/radar_source.go @@ -0,0 +1,191 @@ +package vaultradar + +import ( + "context" + "fmt" + + "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/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-hcp/internal/clients" + "github.com/hashicorp/terraform-provider-hcp/internal/provider/modifiers" + + service "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-radar/preview/2023-05-01/client/data_source_registration_service" +) + +var ( + _ resource.Resource = &radarSourceResource{} + _ resource.ResourceWithConfigure = &radarSourceResource{} +) + +// radarSourceResource is an implementation for configuring specific types Radar data sources. +// Examples: hcp_vault_radar_source_github_cloud and hcp__vault_radar_source_github_enterprise make use of +// this implementation to define resources with specific schemas, validation, and state details related to their types. +type radarSourceResource struct { + client *clients.Client + TypeName string + SourceType string + ConnectionSchema schema.Schema + GetSourceFromPlan func(ctx context.Context, plan tfsdk.Plan) (radarSource, diag.Diagnostics) + GetSourceFromState func(ctx context.Context, state tfsdk.State) (radarSource, diag.Diagnostics) +} + +func (r *radarSourceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + r.TypeName +} + +func (r *radarSourceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = r.ConnectionSchema +} + +// radarSource is the minimal plan/state that a Radar source must have. +type radarSource interface { + GetProjectID() types.String + SetProjectID(types.String) + GetID() types.String + SetID(types.String) + GetName() types.String + GetConnectionURL() types.String + GetToken() types.String +} + +func (r *radarSourceResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*clients.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *clients.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.client = client +} + +func (r *radarSourceResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + modifiers.ModifyPlanForDefaultProjectChange(ctx, r.client.Config.ProjectID, req.State, req.Config, req.Plan, resp) +} + +func (r *radarSourceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + src, diags := r.GetSourceFromPlan(ctx, req.Plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + projectID := r.client.Config.ProjectID + if !src.GetProjectID().IsUnknown() { + projectID = src.GetProjectID().ValueString() + } + + body := service.OnboardDataSourceBody{ + Type: r.SourceType, + Name: src.GetName().ValueString(), + + Token: src.GetToken().ValueString(), + } + + if !src.GetConnectionURL().IsNull() { + body.ConnectionURL = src.GetConnectionURL().ValueString() + } + + res, err := clients.OnboardRadarSource(ctx, r.client, projectID, body) + if err != nil { + resp.Diagnostics.AddError("Error creating Radar source", err.Error()) + return + } + + src.SetID(types.StringValue(res.GetPayload().ID)) + src.SetProjectID(types.StringValue(projectID)) + resp.Diagnostics.Append(resp.State.Set(ctx, &src)...) +} + +func (r *radarSourceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + src, diags := r.GetSourceFromState(ctx, req.State) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + projectID := r.client.Config.ProjectID + if !src.GetProjectID().IsUnknown() { + projectID = src.GetProjectID().ValueString() + } + + res, err := clients.GetRadarSource(ctx, r.client, projectID, src.GetID().ValueString()) + if err != nil { + if clients.IsResponseCodeNotFound(err) { + // Resource is no longer on the server. + tflog.Info(ctx, "Radar source not found, removing from state.") + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Unable to get Radar source", err.Error()) + return + } + + // Resource is marked as deleted on the server. + if res.GetPayload().Deleted { + // Don't update or remove the state, because its has not been fully deleted server side. + tflog.Warn(ctx, "Radar source marked for deletion.") + return + } + + // The only other state that could change related to this resource is the token, and for obvious reasons we don't + // return that in the read response. So we don't need to update the state here. +} + +func (r *radarSourceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + src, diags := r.GetSourceFromState(ctx, req.State) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + projectID := r.client.Config.ProjectID + if !src.GetProjectID().IsUnknown() { + projectID = src.GetProjectID().ValueString() + } + + // Assert resource still exists. + res, err := clients.GetRadarSource(ctx, r.client, projectID, src.GetID().ValueString()) + if err != nil { + if clients.IsResponseCodeNotFound(err) { + // Resource is no longer on the server. + tflog.Info(ctx, "Radar source not found, removing from state.") + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Unable to get Radar source", err.Error()) + return + } + + // Resource is already marked as being deleted on the server. Wait for it to be fully deleted. + if res.GetPayload().Deleted { + tflog.Info(ctx, "Radar resource already marked for deletion, waiting for full deletion") + if err := clients.WaitOnOffboardRadarSource(ctx, r.client, projectID, src.GetID().ValueString()); err != nil { + resp.Diagnostics.AddError("Unable to delete Radar source", err.Error()) + return + } + + tflog.Trace(ctx, "Deleted Radar resource") + return + } + + // Offboard the Radar source. + if err := clients.OffboardRadarSource(ctx, r.client, projectID, src.GetID().ValueString()); err != nil { + resp.Diagnostics.AddError("Unable to delete Radar source", err.Error()) + return + } +} + +func (r *radarSourceResource) Update(_ context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { + // In-place update is not supported. + // Plans to support updating the token will be in a future iteration. + resp.Diagnostics.AddError("Unexpected provider error", "This is an internal error, please report this issue to the provider developers") +} diff --git a/internal/provider/vaultradar/resource_radar_source_github_cloud.go b/internal/provider/vaultradar/resource_radar_source_github_cloud.go new file mode 100644 index 000000000..457b8fd00 --- /dev/null +++ b/internal/provider/vaultradar/resource_radar_source_github_cloud.go @@ -0,0 +1,96 @@ +package vaultradar + +import ( + "context" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func NewSourceGitHubCloudResource() resource.Resource { + return &radarSourceResource{ + TypeName: "_vault_radar_source_github_cloud", + SourceType: "github_cloud", + ConnectionSchema: githubCloudSourceSchema, + GetSourceFromPlan: func(ctx context.Context, plan tfsdk.Plan) (radarSource, diag.Diagnostics) { + var data githubCloudSourceData + diags := plan.Get(ctx, &data) + return &data, diags + }, + GetSourceFromState: func(ctx context.Context, state tfsdk.State) (radarSource, diag.Diagnostics) { + var data githubCloudSourceData + diags := state.Get(ctx, &data) + return &data, diags + }} +} + +var githubCloudSourceSchema = schema.Schema{ + MarkdownDescription: "This terraform resource manages a GitHub Cloud data source lifecycle in Vault Radar.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "The ID of this resource.", + }, + "github_organization": schema.StringAttribute{ + Description: `GitHub organization Vault Radar will monitor. Example: type "octocat" for the org https://github.com/octocat`, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^[a-zA-Z0-9-_.]+$`), + "must contain only letters, numbers, hyphens, underscores, or periods", + ), + }, + }, + "token": schema.StringAttribute{ + Description: "GitHub personal access token.", + Required: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + + // Optional inputs + "project_id": schema.StringAttribute{ + Description: "The ID of the HCP project where Vault Radar is located. If not specified, the project specified in the HCP Provider config block will be used, if configured.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, +} + +type githubCloudSourceData struct { + ID types.String `tfsdk:"id"` + GitHubOrganization types.String `tfsdk:"github_organization"` + Token types.String `tfsdk:"token"` + ProjectID types.String `tfsdk:"project_id"` +} + +func (d *githubCloudSourceData) GetProjectID() types.String { return d.ProjectID } + +func (d *githubCloudSourceData) SetProjectID(projectID types.String) { d.ProjectID = projectID } + +func (d *githubCloudSourceData) GetID() types.String { return d.ID } + +func (d *githubCloudSourceData) SetID(id types.String) { d.ID = id } + +func (d *githubCloudSourceData) GetName() types.String { return d.GitHubOrganization } + +func (d *githubCloudSourceData) GetConnectionURL() types.String { return basetypes.NewStringNull() } + +func (d *githubCloudSourceData) GetToken() types.String { return d.Token } diff --git a/internal/provider/vaultradar/resource_radar_source_github_cloud_test.go b/internal/provider/vaultradar/resource_radar_source_github_cloud_test.go new file mode 100644 index 000000000..f7d121577 --- /dev/null +++ b/internal/provider/vaultradar/resource_radar_source_github_cloud_test.go @@ -0,0 +1,47 @@ +package vaultradar_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-hcp/internal/provider/acctest" +) + +func TestRadarSourceGitHubCloud(t *testing.T) { + // Requires Project already setup with Radar. + // Requires a Service Account with an Admin role on the Project. + // Requires access to a GitHub Cloud Organization. + // Requires the following environment variables to be set: + projectID := os.Getenv("HCP_PROJECT_ID") + githubOrganization := os.Getenv("RADAR_GITHUB_CLOUD_ORGANIZATION") + token := os.Getenv("RADAR_GITHUB_CLOUD_TOKEN") + + if projectID == "" || githubOrganization == "" || token == "" { + t.Skip("HCP_PROJECT_ID, RADAR_GITHUB_CLOUD_ORGANIZATION, and RADAR_GITHUB_CLOUD_TOKEN must be set for acceptance tests") + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // CREATE + { + Config: fmt.Sprintf(` + resource "hcp_vault_radar_source_github_cloud" "example" { + project_id = %q + github_organization = %q + token = %q + } + `, projectID, githubOrganization, token), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("hcp_vault_radar_source_github_cloud.example", "project_id", projectID), + resource.TestCheckResourceAttr("hcp_vault_radar_source_github_cloud.example", "github_organization", githubOrganization), + resource.TestCheckResourceAttrSet("hcp_vault_radar_source_github_cloud.example", "id"), + ), + }, + // UPDATE not supported at this time. + // DELETE happens automatically. + }, + }) +} diff --git a/internal/provider/vaultradar/resource_radar_source_github_enterprise.go b/internal/provider/vaultradar/resource_radar_source_github_enterprise.go index 3a9fe86c3..7042b3c69 100644 --- a/internal/provider/vaultradar/resource_radar_source_github_enterprise.go +++ b/internal/provider/vaultradar/resource_radar_source_github_enterprise.go @@ -2,95 +2,90 @@ package vaultradar import ( "context" - "fmt" "regexp" - radar_service "github.com/hashicorp/hcp-sdk-go/clients/cloud-vault-radar/preview/2023-05-01/client/data_source_registration_service" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-provider-hcp/internal/clients" - "github.com/hashicorp/terraform-provider-hcp/internal/provider/modifiers" -) - -var ( - _ resource.Resource = &sourceGitHubEnterpriseResource{} - _ resource.ResourceWithConfigure = &sourceGitHubEnterpriseResource{} ) func NewSourceGitHubEnterpriseResource() resource.Resource { - return &sourceGitHubEnterpriseResource{} -} - -type sourceGitHubEnterpriseResource struct { - client *clients.Client -} + return &radarSourceResource{ + TypeName: "_vault_radar_source_github_enterprise", + SourceType: "github_enterprise", + ConnectionSchema: githubEnterpriseSourceSchema, + GetSourceFromPlan: func(ctx context.Context, plan tfsdk.Plan) (radarSource, diag.Diagnostics) { + var data githubEnterpriseSourceData + diags := plan.Get(ctx, &data) + return &data, diags + }, + GetSourceFromState: func(ctx context.Context, state tfsdk.State) (radarSource, diag.Diagnostics) { + var data githubEnterpriseSourceData + diags := state.Get(ctx, &data) + return &data, diags + }} -func (r *sourceGitHubEnterpriseResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_vault_radar_source_github_enterprise" } -func (r *sourceGitHubEnterpriseResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - MarkdownDescription: "This terraform resource manages a GitHub Enterprise Server data source lifecycle in Vault Radar.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Computed: true, - Description: "The ID of this resource.", +var githubEnterpriseSourceSchema = schema.Schema{ + MarkdownDescription: "This terraform resource manages a GitHub Enterprise Server data source lifecycle in Vault Radar.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "The ID of this resource.", + }, + "domain_name": schema.StringAttribute{ + Description: "Fully qualified domain name of the server. (Example: myserver.acme.com)", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), }, - "domain_name": schema.StringAttribute{ - Description: "Fully qualified domain name of the server. (Example: myserver.acme.com)", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(`^(?:[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$`), - "must be a valid domain name", - ), - }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^(?:[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$`), + "must be a valid domain name", + ), }, - "github_organization": schema.StringAttribute{ - Description: `GitHub organization Vault Radar will monitor. Example: "octocat" for the org https://yourcodeserver.com/octocat`, - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile(`^[a-zA-Z0-9-_.]+$`), - "must contain only letters, numbers, hyphens, underscores, or periods", - ), - }, + }, + "github_organization": schema.StringAttribute{ + Description: `GitHub organization Vault Radar will monitor. Example: "octocat" for the org https://yourcodeserver.com/octocat`, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^[a-zA-Z0-9-_.]+$`), + "must contain only letters, numbers, hyphens, underscores, or periods", + ), }, - "token": schema.StringAttribute{ - Description: "GitHub personal access token.", - Required: true, - Sensitive: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, + }, + "token": schema.StringAttribute{ + Description: "GitHub personal access token.", + Required: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), }, - // Optional inputs - "project_id": schema.StringAttribute{ - Description: "The ID of the HCP project where Vault Radar is located. If not specified, the project specified in the HCP Provider config block will be used, if configured.", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - stringplanmodifier.UseStateForUnknown(), - }, + }, + // Optional inputs + "project_id": schema.StringAttribute{ + Description: "The ID of the HCP project where Vault Radar is located. If not specified, the project specified in the HCP Provider config block will be used, if configured.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), }, }, - } + }, } -type gitHubEnterpriseSource struct { +type githubEnterpriseSourceData struct { ID types.String `tfsdk:"id"` DomainName types.String `tfsdk:"domain_name"` GitHubOrganization types.String `tfsdk:"github_organization"` @@ -98,143 +93,16 @@ type gitHubEnterpriseSource struct { ProjectID types.String `tfsdk:"project_id"` } -func (r *sourceGitHubEnterpriseResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - if req.ProviderData == nil { - return - } - client, ok := req.ProviderData.(*clients.Client) - if !ok { - resp.Diagnostics.AddError( - "Unexpected Resource Configure Type", - fmt.Sprintf("Expected *clients.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - return - } - r.client = client -} - -func (r *sourceGitHubEnterpriseResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { - modifiers.ModifyPlanForDefaultProjectChange(ctx, r.client.Config.ProjectID, req.State, req.Config, req.Plan, resp) -} - -func (r *sourceGitHubEnterpriseResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan gitHubEnterpriseSource +func (d *githubEnterpriseSourceData) GetProjectID() types.String { return d.ProjectID } - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } +func (d *githubEnterpriseSourceData) SetProjectID(projectID types.String) { d.ProjectID = projectID } - projectID := r.client.Config.ProjectID - if !plan.ProjectID.IsUnknown() { - projectID = plan.ProjectID.ValueString() - } +func (d *githubEnterpriseSourceData) GetID() types.String { return d.ID } - res, err := clients.OnboardRadarSource(ctx, r.client, projectID, radar_service.OnboardDataSourceBody{ - Type: "github_enterprise", - Name: plan.GitHubOrganization.ValueString(), - ConnectionURL: plan.DomainName.ValueString(), - Token: plan.Token.ValueString(), - }) - if err != nil { - resp.Diagnostics.AddError("Error creating Radar source", err.Error()) - return - } +func (d *githubEnterpriseSourceData) SetID(id types.String) { d.ID = id } - plan.ID = types.StringValue(res.GetPayload().ID) - plan.ProjectID = types.StringValue(projectID) - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +func (d *githubEnterpriseSourceData) GetName() types.String { return d.GitHubOrganization } - tflog.Trace(ctx, "Created Radar resource") -} - -func (r *sourceGitHubEnterpriseResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state gitHubEnterpriseSource - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - projectID := r.client.Config.ProjectID - if !state.ProjectID.IsUnknown() { - projectID = state.ProjectID.ValueString() - } - - res, err := clients.GetRadarSource(ctx, r.client, projectID, state.ID.ValueString()) - if err != nil { - if clients.IsResponseCodeNotFound(err) { - // Resource is no longer on the server. - tflog.Info(ctx, "Radar source not found, removing from state.") - resp.State.RemoveResource(ctx) - return - } - resp.Diagnostics.AddError("Unable to get Radar source", err.Error()) - return - } - - // Resource is marked as deleted on the server. - if res.GetPayload().Deleted { - // Don't update or remove the state, because its has not been fully deleted server side. - tflog.Warn(ctx, "Radar source marked for deletion.") - return - } - - // The only other state that could change related to this resource is the token, and for obvious reasons we don't - // return that in the read response. So we don't need to update the state here. - tflog.Trace(ctx, "Read Radar resource") -} - -func (r *sourceGitHubEnterpriseResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state gitHubEnterpriseSource - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } +func (d *githubEnterpriseSourceData) GetConnectionURL() types.String { return d.DomainName } - projectID := r.client.Config.ProjectID - if !state.ProjectID.IsUnknown() { - projectID = state.ProjectID.ValueString() - } - - // Assert resource still exists. - res, err := clients.GetRadarSource(ctx, r.client, projectID, state.ID.ValueString()) - if err != nil { - if clients.IsResponseCodeNotFound(err) { - // Resource is no longer on the server. - tflog.Info(ctx, "Radar source not found, removing from state.") - resp.State.RemoveResource(ctx) - return - } - resp.Diagnostics.AddError("Unable to get Radar source", err.Error()) - return - } - - // Resource is already marked as being deleted on the server. Wait for it to be fully deleted. - if res.GetPayload().Deleted { - tflog.Info(ctx, "Radar resource already marked for deletion, waiting for full deletion") - if err := clients.WaitOnOffboardRadarSource(ctx, r.client, projectID, state.ID.ValueString()); err != nil { - resp.Diagnostics.AddError("Unable to delete Radar source", err.Error()) - return - } - - tflog.Trace(ctx, "Deleted Radar resource") - return - } - - // Offboard the Radar source. - if err := clients.OffboardRadarSource(ctx, r.client, projectID, state.ID.ValueString()); err != nil { - resp.Diagnostics.AddError("Unable to delete Radar source", err.Error()) - return - } - - tflog.Trace(ctx, "Deleted Radar resource") -} - -func (r *sourceGitHubEnterpriseResource) Update(_ context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { - // In-place update is not supported. - // Plans to support updating the token will be in a future iteration. - resp.Diagnostics.AddError("Unexpected provider error", "This is an internal error, please report this issue to the provider developers") -} +func (d *githubEnterpriseSourceData) GetToken() types.String { return d.Token } diff --git a/templates/resources/vault_radar_source_github_cloud.md.tmpl b/templates/resources/vault_radar_source_github_cloud.md.tmpl new file mode 100644 index 000000000..bf32da140 --- /dev/null +++ b/templates/resources/vault_radar_source_github_cloud.md.tmpl @@ -0,0 +1,19 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +-> **Note:** HCP Vault Radar Terraform resources are in preview. + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/hcp_vault_radar_source_github_cloud/resource.tf" }} + + +{{ .SchemaMarkdown | trimspace }} From 44a351b55279edc4dcb554c597b24d0385a67724 Mon Sep 17 00:00:00 2001 From: Trent DiBacco Date: Tue, 15 Oct 2024 16:27:32 -0700 Subject: [PATCH 2/3] add change log. --- .changelog/1119.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/1119.txt diff --git a/.changelog/1119.txt b/.changelog/1119.txt new file mode 100644 index 000000000..b04fd44d9 --- /dev/null +++ b/.changelog/1119.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add preview of vault_radar_source_github_cloud resource. +``` \ No newline at end of file From 9246ee631b2495a3b32ae6d8e7403c9a84b4f6f7 Mon Sep 17 00:00:00 2001 From: Trent DiBacco Date: Thu, 17 Oct 2024 09:11:42 -0700 Subject: [PATCH 3/3] update comment. --- internal/provider/vaultradar/radar_source.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/vaultradar/radar_source.go b/internal/provider/vaultradar/radar_source.go index 26c963553..de68de9b7 100644 --- a/internal/provider/vaultradar/radar_source.go +++ b/internal/provider/vaultradar/radar_source.go @@ -22,7 +22,7 @@ var ( ) // radarSourceResource is an implementation for configuring specific types Radar data sources. -// Examples: hcp_vault_radar_source_github_cloud and hcp__vault_radar_source_github_enterprise make use of +// Examples: hcp_vault_radar_source_github_cloud and hcp_vault_radar_source_github_enterprise make use of // this implementation to define resources with specific schemas, validation, and state details related to their types. type radarSourceResource struct { client *clients.Client