diff --git a/example/sharedsecret/main.tf b/example/sharedsecret/main.tf new file mode 100644 index 0000000..7acabb5 --- /dev/null +++ b/example/sharedsecret/main.tf @@ -0,0 +1,35 @@ +terraform { + required_providers { + plural = { + source = "pluralsh/plural" + version = "0.2.1" + } + } +} + +provider "plural" { + use_cli = true +} + +data "plural_user" "user" { + email = "sebastian@plural.sh" +} + +resource "plural_shared_secret" "mysecret" { + name = "mysecret" + secret = "password" + notification_bindings = [ + { user_id = data.plural_user.user.id } + ] +} + +resource "null_resource" "default" { + provisioner "local-exec" { + command = "echo name:${plural_shared_secret.mysecret.name}" + } +} + +output "secretoutput" { + value = plural_shared_secret.mysecret.secret + sensitive = true +} diff --git a/go.mod b/go.mod index 29aeea4..1965dd1 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/pluralsh/console/go/client v1.10.0 + github.com/pluralsh/console/go/client v1.15.0 github.com/pluralsh/plural-cli v0.9.14-0.20240730152129-7ce540b144fd github.com/pluralsh/polly v0.1.10 github.com/samber/lo v1.46.0 diff --git a/go.sum b/go.sum index 3b90860..281cebc 100644 --- a/go.sum +++ b/go.sum @@ -657,8 +657,8 @@ github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFz github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pluralsh/console/go/client v1.10.0 h1:oY8ZeTcoyP5WhrAheQm0PwONJQ/O65sqeWRfgQknMtI= -github.com/pluralsh/console/go/client v1.10.0/go.mod h1:lpoWASYsM9keNePS3dpFiEisUHEfObIVlSL3tzpKn8k= +github.com/pluralsh/console/go/client v1.15.0 h1:dFbTGs6648o1VfaS5ZwBcXOKfQUtjIDCs9/8l13PsXU= +github.com/pluralsh/console/go/client v1.15.0/go.mod h1:lpoWASYsM9keNePS3dpFiEisUHEfObIVlSL3tzpKn8k= github.com/pluralsh/gqlclient v1.12.1 h1:JDOkP9jjqkPdTYdpH5hooG4F8T6FDH90XfipeXJmJFY= github.com/pluralsh/gqlclient v1.12.1/go.mod h1:OEjN9L63x8m3A3eQBv5kVkFgiY9fp2aZ0cgOF0uII58= github.com/pluralsh/plural-cli v0.9.14-0.20240730152129-7ce540b144fd h1:gg+5AUbiip8WzAQE+avozXLBdP5eS19J6x6+BhtkGNY= diff --git a/internal/common/bindings.go b/internal/common/bindings.go index 7decd48..73264bf 100644 --- a/internal/common/bindings.go +++ b/internal/common/bindings.go @@ -91,6 +91,10 @@ func bindingsFrom(bindings []*console.PolicyBindingFragment, config types.Set, c return setValue } +func SetToPolicyBindingAttributes(set types.Set, ctx context.Context, d diag.Diagnostics) []*console.PolicyBindingAttributes { + return policyBindingAttributes(set, ctx, d) +} + type PolicyBinding struct { GroupID types.String `tfsdk:"group_id"` ID types.String `tfsdk:"id"` diff --git a/internal/model/shared_secret.go b/internal/model/shared_secret.go new file mode 100644 index 0000000..f928892 --- /dev/null +++ b/internal/model/shared_secret.go @@ -0,0 +1,25 @@ +package model + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + console "github.com/pluralsh/console/go/client" + + "terraform-provider-plural/internal/common" +) + +type SharedSecret struct { + Name types.String `tfsdk:"name"` + Secret types.String `tfsdk:"secret"` + NotificationBindings types.Set `tfsdk:"notification_bindings"` +} + +func (in *SharedSecret) Attributes(ctx context.Context, d diag.Diagnostics) console.SharedSecretAttributes { + return console.SharedSecretAttributes{ + Name: in.Name.ValueString(), + Secret: in.Secret.ValueString(), + NotificationBindings: common.SetToPolicyBindingAttributes(in.NotificationBindings, ctx, d), + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 11ed3db..22c2dc2 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -193,6 +193,7 @@ func (p *PluralProvider) Resources(_ context.Context) []func() resource.Resource r.NewGroupResource, r.NewPrAutomationTriggerResource, r.NewStackRunTriggerResource, + r.NewSharedSecretResource, } } diff --git a/internal/resource/shared_secret.go b/internal/resource/shared_secret.go new file mode 100644 index 0000000..e83e959 --- /dev/null +++ b/internal/resource/shared_secret.go @@ -0,0 +1,130 @@ +package resource + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "terraform-provider-plural/internal/client" + "terraform-provider-plural/internal/common" + "terraform-provider-plural/internal/model" +) + +var _ resource.ResourceWithConfigure = &sharedSecretResource{} + +func NewSharedSecretResource() resource.Resource { + return &sharedSecretResource{} +} + +type sharedSecretResource struct { + client *client.Client +} + +func (in *sharedSecretResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_shared_secret" +} + +func (in *sharedSecretResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "A one-time-viewable secret shared with a list of eligible users.", + MarkdownDescription: "A one-time-viewable secret shared with a list of eligible users.", + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The name of this shared secret.", + MarkdownDescription: "The name of this shared secret.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "secret": schema.StringAttribute{ + Description: "Content of this shared secret.", + MarkdownDescription: "Content of this shared secret.", + Required: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "notification_bindings": schema.SetNestedAttribute{ + Description: "The users/groups you want this secret to be delivered to.", + MarkdownDescription: "The users/groups you want this secret to be delivered to.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "group_id": schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "id": schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "user_id": schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + }, + }, + PlanModifiers: []planmodifier.Set{setplanmodifier.RequiresReplace()}, + }, + }, + } +} + +func (in *sharedSecretResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + if request.ProviderData == nil { + return + } + + data, ok := request.ProviderData.(*common.ProviderData) + if !ok { + response.Diagnostics.AddError( + "Unexpected Project Resource Configure Type", + fmt.Sprintf("Expected *common.ProviderData, got: %T. Please report this issue to the provider developers.", request.ProviderData), + ) + return + } + + in.client = data.Client +} + +func (in *sharedSecretResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + data := new(model.SharedSecret) + response.Diagnostics.Append(request.Plan.Get(ctx, data)...) + if response.Diagnostics.HasError() { + return + } + + _, err := in.client.ShareSecret(ctx, data.Attributes(ctx, response.Diagnostics)) + if err != nil { + response.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to share a secret, got error: %s", err)) + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (in *sharedSecretResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + data := new(model.SharedSecret) + response.Diagnostics.Append(request.State.Get(ctx, data)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, data)...) +} + +func (in *sharedSecretResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) { + // Ignore. +} + +func (in *sharedSecretResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) { + // Ignore. +}