From 789b107426cd9acbb798914ae8ed94c9a1766ecc Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Mon, 3 Jun 2024 09:02:05 +0200 Subject: [PATCH] extend stacks for hooks and actor --- go.mod | 2 +- go.sum | 6 +- internal/resource/infrastructure_stack.go | 14 ++- .../resource/infrastructure_stack_model.go | 98 +++++++++++++++++-- .../resource/infrastructure_stack_schema.go | 26 +++++ 5 files changed, 132 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 55d6ebec..19d9330f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/pluralsh/console-client-go v0.5.3 + github.com/pluralsh/console-client-go v0.5.14 github.com/pluralsh/plural-cli v0.8.5-0.20240216094552-efc34ee6de37 github.com/pluralsh/polly v0.1.7 github.com/samber/lo v1.38.1 diff --git a/go.sum b/go.sum index 481a4bf0..ffb0cbc2 100644 --- a/go.sum +++ b/go.sum @@ -856,10 +856,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pluralsh/console-client-go v0.4.0 h1:lgKaVGi8jB7S8wFF6L3P6H/4Xc88e4FozhyW58O1w3Q= -github.com/pluralsh/console-client-go v0.4.0/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= -github.com/pluralsh/console-client-go v0.5.3 h1:RB4XtKlvh8+BM5o1o0h+W6zHculBGbL6q5lI/yRnqJE= -github.com/pluralsh/console-client-go v0.5.3/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= +github.com/pluralsh/console-client-go v0.5.14 h1:JgpFSx481O4Od63iIbj3+hKBn+JW/WyAZrJOofFBPpk= +github.com/pluralsh/console-client-go v0.5.14/go.mod h1:eyCiLA44YbXiYyJh8303jk5JdPkt9McgCo5kBjk4lKo= github.com/pluralsh/gqlclient v1.11.0 h1:FfXW7FiEJLHOfTAa7NxDb8jb3aMZNIpCAcG+bg8uHYA= github.com/pluralsh/gqlclient v1.11.0/go.mod h1:qSXKUlio1F2DRPy8el4oFYsmpKbkUYspgPB87T4it5I= github.com/pluralsh/plural-cli v0.8.5-0.20240216094552-efc34ee6de37 h1:DBnaKvKmbTbKwbkrh/2gJBwyHYfaXdxeT3UGh+94K4g= diff --git a/internal/resource/infrastructure_stack.go b/internal/resource/infrastructure_stack.go index 5bee9aed..9fe3cd3e 100644 --- a/internal/resource/infrastructure_stack.go +++ b/internal/resource/infrastructure_stack.go @@ -58,7 +58,12 @@ func (r *InfrastructureStackResource) Create(ctx context.Context, req resource.C return } - sd, err := r.client.CreateStack(ctx, data.Attributes(ctx, resp.Diagnostics)) + attr, err := data.Attributes(ctx, resp.Diagnostics, r.client) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get attributes, got error: %s", err)) + return + } + sd, err := r.client.CreateStack(ctx, *attr) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create infrastructure stack, got error: %s", err)) return @@ -92,7 +97,12 @@ func (r *InfrastructureStackResource) Update(ctx context.Context, req resource.U return } - _, err := r.client.UpdateStack(ctx, data.Id.ValueString(), data.Attributes(ctx, resp.Diagnostics)) + attr, err := data.Attributes(ctx, resp.Diagnostics, r.client) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get attributes, got error: %s", err)) + return + } + _, err = r.client.UpdateStack(ctx, data.Id.ValueString(), *attr) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update infrastructure stack, got error: %s", err)) return diff --git a/internal/resource/infrastructure_stack_model.go b/internal/resource/infrastructure_stack_model.go index 2c166516..83422ad8 100644 --- a/internal/resource/infrastructure_stack_model.go +++ b/internal/resource/infrastructure_stack_model.go @@ -3,6 +3,7 @@ package resource import ( "context" + "terraform-provider-plural/internal/client" "terraform-provider-plural/internal/common" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -17,6 +18,7 @@ type infrastructureStack struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Type types.String `tfsdk:"type"` + Actor types.String `tfsdk:"actor"` Approval types.Bool `tfsdk:"approval"` Detach types.Bool `tfsdk:"detach"` ClusterId types.String `tfsdk:"cluster_id"` @@ -28,21 +30,30 @@ type infrastructureStack struct { Bindings *common.ClusterBindings `tfsdk:"bindings"` } -func (is *infrastructureStack) Attributes(ctx context.Context, d diag.Diagnostics) gqlclient.StackAttributes { - return gqlclient.StackAttributes{ +func (is *infrastructureStack) Attributes(ctx context.Context, d diag.Diagnostics, client *client.Client) (*gqlclient.StackAttributes, error) { + attr := &gqlclient.StackAttributes{ Name: is.Name.ValueString(), Type: gqlclient.StackType(is.Type.ValueString()), RepositoryID: is.Repository.Id.ValueString(), ClusterID: is.ClusterId.ValueString(), Git: is.Repository.Attributes(), JobSpec: is.JobSpec.Attributes(ctx, d), - Configuration: is.Configuration.Attributes(), + Configuration: is.Configuration.Attributes(ctx, d), Approval: is.Approval.ValueBoolPointer(), ReadBindings: is.Bindings.ReadAttributes(ctx, d), WriteBindings: is.Bindings.WriteAttributes(ctx, d), Files: is.FilesAttributes(ctx, d), Environment: is.EnvironmentAttributes(ctx, d), } + if !is.Actor.IsNull() { + user, err := client.GetUser(ctx, is.Actor.ValueString()) + if err != nil { + return nil, err + } + attr.ActorID = &user.User.ID + } + + return attr, nil } func (is *infrastructureStack) FilesAttributes(ctx context.Context, d diag.Diagnostics) []*gqlclient.StackFileAttributes { @@ -88,7 +99,7 @@ func (is *infrastructureStack) From(stack *gqlclient.InfrastructureStackFragment is.Approval = types.BoolPointerValue(stack.Approval) is.ClusterId = types.StringValue(stack.Cluster.ID) is.Repository.From(stack.Repository, stack.Git) - is.Configuration.From(stack.Configuration) + is.Configuration.From(ctx, stack.Configuration, d) is.Files = infrastructureStackFilesFrom(stack.Files, is.Files, d) is.Environment = infrastructureStackEnvironmentsFrom(stack.Environment, is.Environment, ctx, d) is.Bindings.From(stack.ReadBindings, stack.WriteBindings, ctx, d) @@ -168,29 +179,102 @@ func (isr *InfrastructureStackRepository) From(repository *gqlclient.GitReposito isr.Folder = types.StringValue(ref.Folder) } +type InfrastructureStackHookSpec struct { + // the command this hook will execute + Cmd types.String `tfsdk:"cmd"` + + // optional arguments to pass to the command + Args types.List `tfsdk:"args"` + + AfterStage types.String `tfsdk:"after_stage"` +} + +var InfrastructureStackHookTypes = map[string]attr.Type{ + "cmd": types.StringType, + "args": types.ListType{ElemType: types.StringType}, + "after_stage": types.StringType, +} + +func infrastructureStackHooksFrom(ctx context.Context, hooks []*gqlclient.StackHookFragment, configHooks types.Set, d diag.Diagnostics) types.Set { + if len(hooks) == 0 { + return configHooks + } + + values := make([]attr.Value, len(hooks)) + + for i, h := range hooks { + objValue, diags := types.ObjectValueFrom(ctx, InfrastructureStackHookTypes, InfrastructureStackHookSpec{ + Cmd: types.StringValue(h.Cmd), + Args: infrastructureStackContainerSpecArgsFrom(h.Args, ctx, d), + AfterStage: types.StringValue(h.AfterStage.String()), + }) + values[i] = objValue + d.Append(diags...) + } + + setValue, diags := types.SetValue(basetypes.ObjectType{AttrTypes: InfrastructureStackHookTypes}, values) + d.Append(diags...) + return setValue +} + +func (sh *InfrastructureStackHookSpec) Attributes(ctx context.Context, d diag.Diagnostics) *gqlclient.StackHookAttributes { + if sh == nil { + return &gqlclient.StackHookAttributes{} + } + + args := make([]types.String, len(sh.Args.Elements())) + d.Append(sh.Args.ElementsAs(ctx, &args, false)...) + return &gqlclient.StackHookAttributes{ + Cmd: sh.Cmd.ValueString(), + Args: algorithms.Map(args, func(v types.String) *string { + return v.ValueStringPointer() + }), + AfterStage: gqlclient.StepStage(sh.AfterStage.ValueString()), + } +} + type InfrastructureStackConfiguration struct { Image types.String `tfsdk:"image"` Version types.String `tfsdk:"version"` + Hooks types.Set `tfsdk:"hooks"` +} + +func (isc *InfrastructureStackConfiguration) HooksAttributes(ctx context.Context, d diag.Diagnostics) []*gqlclient.StackHookAttributes { + if isc.Hooks.IsNull() { + return nil + } + + result := make([]*gqlclient.StackHookAttributes, 0, len(isc.Hooks.Elements())) + elements := make([]InfrastructureStackHookSpec, len(isc.Hooks.Elements())) + d.Append(isc.Hooks.ElementsAs(ctx, &elements, false)...) + + for _, hook := range elements { + result = append(result, hook.Attributes(ctx, d)) + } + + return result } -func (isc *InfrastructureStackConfiguration) Attributes() gqlclient.StackConfigurationAttributes { +func (isc *InfrastructureStackConfiguration) Attributes(ctx context.Context, d diag.Diagnostics) gqlclient.StackConfigurationAttributes { if isc == nil { return gqlclient.StackConfigurationAttributes{} } return gqlclient.StackConfigurationAttributes{ Image: isc.Image.ValueStringPointer(), - Version: isc.Version.ValueString(), + Version: isc.Version.ValueStringPointer(), + Hooks: isc.HooksAttributes(ctx, d), } } -func (isc *InfrastructureStackConfiguration) From(configuration *gqlclient.StackConfigurationFragment) { +func (isc *InfrastructureStackConfiguration) From(ctx context.Context, configuration *gqlclient.StackConfigurationFragment, d diag.Diagnostics) { if isc == nil || configuration == nil { return } isc.Image = types.StringPointerValue(configuration.Image) isc.Version = types.StringValue(configuration.Version) + isc.Hooks = infrastructureStackHooksFrom(ctx, configuration.Hooks, isc.Hooks, d) } type InfrastructureStackEnvironment struct { diff --git a/internal/resource/infrastructure_stack_schema.go b/internal/resource/infrastructure_stack_schema.go index 49577bca..2517b5b1 100644 --- a/internal/resource/infrastructure_stack_schema.go +++ b/internal/resource/infrastructure_stack_schema.go @@ -51,6 +51,11 @@ func (r *InfrastructureStackResource) schema() schema.Schema { Computed: true, Default: booldefault.StaticBool(false), }, + "actor": schema.StringAttribute{ + Description: "The User email to use for default Plural authentication in this stack.", + MarkdownDescription: "The User email to use for default Plural authentication in this stack.", + Optional: true, + }, "cluster_id": schema.StringAttribute{ Description: "The cluster on which the stack will be applied.", MarkdownDescription: "The cluster on which the stack will be applied.", @@ -94,6 +99,27 @@ func (r *InfrastructureStackResource) schema() schema.Schema { MarkdownDescription: "The semver of the tool you wish to use.", Required: true, }, + "hooks": schema.SetNestedAttribute{ + Description: "The hooks to customize execution for this stack.", + MarkdownDescription: "The hooks to customize execution for this stack.", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "cmd": schema.StringAttribute{ + Required: true, + }, + "args": schema.ListAttribute{ + Description: "Arguments to pass to the command when executing it.", + MarkdownDescription: "Arguments to pass to the command when executing it.", + Optional: true, + ElementType: types.StringType, + }, + "after_stage": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, }, }, "files": schema.MapAttribute{