-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
VAULT-31135: add new hcp_vault_radar_source_github_cloud resource. #1119
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:feature | ||
Add preview of vault_radar_source_github_cloud resource. | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 generated by tfplugindocs --> | ||
## 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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation for most part was taken from the previous implementation of |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Should be one underscore instead of the two |
||
// 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") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious where
1119
comes from, I see1116
from your last PR but don't see1117
or1118
on main, are they from other PRs?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1119 the PR number