From 31c9590d6475dcb39e072723fcfd5aaa46e53182 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:23:55 -0400 Subject: [PATCH 01/32] [WAYP-2173] waypoint: Add input vars to application resource. --- .../waypoint/resource_waypoint_application.go | 8 ++++++++ .../resource_waypoint_application_test.go | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 2ba79a0a1..b7b94f3e7 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -50,6 +50,8 @@ type ApplicationResourceModel struct { // deferred and probably a list or objects, but may possible be a separate // ActionCfgs types.List `tfsdk:"action_cfgs"` + + InputVars types.Map `tfsdk:"input_vars"` } func (r *ApplicationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -123,6 +125,12 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq stringplanmodifier.UseStateForUnknown(), }, }, + "input_vars": schema.MapAttribute{ + Optional: true, + Description: "Input variables for the Application. These are " + + "key-value pairs that are used to configure the Application.", + ElementType: types.StringType, + }, }, } } diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index f16e1d923..5d24273da 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -34,6 +34,8 @@ func TestAccWaypoint_Application_basic(t *testing.T) { testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), + resource.TestCheckResourceAttr(resourceName, "input_vars.#", "1"), + resource.TestCheckResourceAttr(resourceName, "input_vars.string_variable", "a"), ), }, }, @@ -129,17 +131,30 @@ resource "hcp_waypoint_application_template" "test" { readme_markdown_template = base64encode("# Some Readme") terraform_no_code_module = { source = "private/waypoint-tfc-testing/waypoint-template-starter/null" - version = "0.0.2" + version = "0.0.3" } terraform_cloud_workspace_details = { name = "Default Project" terraform_project_id = "prj-gfVyPJ2q2Aurn25o" } labels = ["one", "two"] + variable_options = [ + { + name = "string_variable" + variable_type = "string" + options = [ + "a" + ] + } + ] } resource "hcp_waypoint_application" "test" { name = "%s" application_template_id = hcp_waypoint_application_template.test.id + + input_vars = { + "string_variable" = "a" + } }`, tempName, appName) } From 248c47c234fea75025d7e772e148b6e457f032f8 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 30 Apr 2024 14:51:51 -0500 Subject: [PATCH 02/32] [WAYP-2173] waypoint: Support input variables on template resource and data source. --- internal/clients/waypoint.go | 18 +++++ .../data_source_waypoint_application.go | 15 ++++ .../waypoint/resource_waypoint_application.go | 74 +++++++++++++++++-- 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/internal/clients/waypoint.go b/internal/clients/waypoint.go index 70f162c74..5d055b825 100644 --- a/internal/clients/waypoint.go +++ b/internal/clients/waypoint.go @@ -177,3 +177,21 @@ func GetAddOnByID(ctx context.Context, client *Client, loc *sharedmodels.Hashico } return getResp.GetPayload().AddOn, nil } + +func GetInputVariables(ctx context.Context, client *Client, workspaceName string, loc *sharedmodels.HashicorpCloudLocationLocation) ([]*waypoint_models.HashicorpCloudWaypointInputVariable, error) { + ns, err := getNamespaceByLocation(ctx, client, loc) + if err != nil { + return nil, err + } + + params := &waypoint_service.WaypointServiceGetTFRunStatusParams{ + WorkspaceName: workspaceName, + NamespaceID: ns.ID, + } + + getResp, err := client.Waypoint.WaypointServiceGetTFRunStatus(params, nil) + if err != nil { + return nil, err + } + return getResp.GetPayload().InputVariables, nil +} diff --git a/internal/provider/waypoint/data_source_waypoint_application.go b/internal/provider/waypoint/data_source_waypoint_application.go index 2483d45e8..84854f62f 100644 --- a/internal/provider/waypoint/data_source_waypoint_application.go +++ b/internal/provider/waypoint/data_source_waypoint_application.go @@ -146,5 +146,20 @@ func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadReq data.ReadmeMarkdown = types.StringNull() } + // A second API call is made to get the input vars set on the application + inputVars, err := clients.GetInputVariables(ctx, client, data.Name.String(), loc) + if err != nil { + resp.Diagnostics.AddError(err.Error(), "Failed to fetch application's input variables.") + return + } + + for _, iv := range inputVars { + data.InputVars = append(data.InputVars, &InputVar{ + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), + VariableType: types.StringValue(iv.VariableType), + }) + } + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index b7b94f3e7..edaeaf315 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -51,7 +51,13 @@ type ApplicationResourceModel struct { // deferred and probably a list or objects, but may possible be a separate // ActionCfgs types.List `tfsdk:"action_cfgs"` - InputVars types.Map `tfsdk:"input_vars"` + InputVars []*InputVar `tfsdk:"input_vars"` +} + +type InputVar struct { + Name types.String `tfsdk:"name"` + VariableType types.String `tfsdk:"variable_type"` + Value types.String `tfsdk:"value"` } func (r *ApplicationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -125,11 +131,30 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq stringplanmodifier.UseStateForUnknown(), }, }, - "input_vars": schema.MapAttribute{ - Optional: true, - Description: "Input variables for the Application. These are " + - "key-value pairs that are used to configure the Application.", - ElementType: types.StringType, + "input_vars": schema.ListNestedAttribute{ + Optional: true, + Description: "Input variables for the Application.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": &schema.StringAttribute{ + Required: true, + Description: "Variable name", + }, + "variable_type": &schema.StringAttribute{ + Computed: true, + Required: false, + Optional: false, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + Description: "Variable type", + }, + "value": &schema.StringAttribute{ + Required: true, + Description: "Variable value", + }, + }, + }, }, }, } @@ -185,11 +210,20 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq return } + vars := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) + for _, v := range plan.InputVars { + vars = append(vars, &waypoint_models.HashicorpCloudWaypointInputVariable{ + Name: v.Name.ValueString(), + Value: v.Value.ValueString(), + }) + } + modelBody := &waypoint_models.HashicorpCloudWaypointWaypointServiceCreateApplicationFromTemplateBody{ Name: plan.Name.ValueString(), ApplicationTemplate: &waypoint_models.HashicorpCloudWaypointRefApplicationTemplate{ ID: plan.ApplicationTemplateID.ValueString(), }, + Variables: vars, } params := &waypoint_service.WaypointServiceCreateApplicationFromTemplateParams{ @@ -225,6 +259,20 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq plan.ReadmeMarkdown = types.StringNull() } + inputVars, err := clients.GetInputVariables(ctx, client, plan.Name.String(), loc) + if err != nil { + resp.Diagnostics.AddError(err.Error(), "Failed to fetch application's input variables.") + return + } + + for _, v := range inputVars { + plan.InputVars = append(plan.InputVars, &InputVar{ + Name: types.StringValue(v.Name), + VariableType: types.StringValue(v.VariableType), + Value: types.StringValue(v.Value), + }) + } + // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log tflog.Trace(ctx, "created application from template resource") @@ -280,6 +328,20 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest data.ReadmeMarkdown = types.StringNull() } + inputVars, err := clients.GetInputVariables(ctx, client, data.Name.String(), loc) + if err != nil { + resp.Diagnostics.AddError(err.Error(), "Failed to fetch application's input variables.") + return + } + + for _, v := range inputVars { + data.InputVars = append(data.InputVars, &InputVar{ + Name: types.StringValue(v.Name), + VariableType: types.StringValue(v.VariableType), + Value: types.StringValue(v.Value), + }) + } + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } From 476aacd4135192a21aba4f6de7bf30dbf0f4c899 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 6 May 2024 15:11:51 -0400 Subject: [PATCH 03/32] waypoint: Fix application schema. --- .../data_source_waypoint_application.go | 21 +++++++++++++--- .../waypoint/resource_waypoint_application.go | 24 +++++-------------- .../resource_waypoint_application_template.go | 1 - .../resource_waypoint_application_test.go | 16 +++++++------ 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application.go b/internal/provider/waypoint/data_source_waypoint_application.go index 84854f62f..2df27a149 100644 --- a/internal/provider/waypoint/data_source_waypoint_application.go +++ b/internal/provider/waypoint/data_source_waypoint_application.go @@ -80,6 +80,22 @@ func (d *DataSourceApplication) Schema(ctx context.Context, req datasource.Schem Computed: true, Description: "Internal Namespace ID.", }, + "input_vars": schema.ListNestedAttribute{ + Optional: true, + Description: "Input variables for the Application.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": &schema.StringAttribute{ + Computed: true, + Description: "Variable name", + }, + "value": &schema.StringAttribute{ + Computed: true, + Description: "Variable value", + }, + }, + }, + }, }, } } @@ -155,9 +171,8 @@ func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadReq for _, iv := range inputVars { data.InputVars = append(data.InputVars, &InputVar{ - Name: types.StringValue(iv.Name), - Value: types.StringValue(iv.Value), - VariableType: types.StringValue(iv.VariableType), + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), }) } diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index edaeaf315..f9e84388a 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -55,9 +55,8 @@ type ApplicationResourceModel struct { } type InputVar struct { - Name types.String `tfsdk:"name"` - VariableType types.String `tfsdk:"variable_type"` - Value types.String `tfsdk:"value"` + Name types.String `tfsdk:"name"` + Value types.String `tfsdk:"value"` } func (r *ApplicationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -140,15 +139,6 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq Required: true, Description: "Variable name", }, - "variable_type": &schema.StringAttribute{ - Computed: true, - Required: false, - Optional: false, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - Description: "Variable type", - }, "value": &schema.StringAttribute{ Required: true, Description: "Variable value", @@ -267,9 +257,8 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq for _, v := range inputVars { plan.InputVars = append(plan.InputVars, &InputVar{ - Name: types.StringValue(v.Name), - VariableType: types.StringValue(v.VariableType), - Value: types.StringValue(v.Value), + Name: types.StringValue(v.Name), + Value: types.StringValue(v.Value), }) } @@ -336,9 +325,8 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest for _, v := range inputVars { data.InputVars = append(data.InputVars, &InputVar{ - Name: types.StringValue(v.Name), - VariableType: types.StringValue(v.VariableType), - Value: types.StringValue(v.Value), + Name: types.StringValue(v.Name), + Value: types.StringValue(v.Value), }) } diff --git a/internal/provider/waypoint/resource_waypoint_application_template.go b/internal/provider/waypoint/resource_waypoint_application_template.go index 14bf7c2e8..6458fa7ab 100644 --- a/internal/provider/waypoint/resource_waypoint_application_template.go +++ b/internal/provider/waypoint/resource_waypoint_application_template.go @@ -170,7 +170,6 @@ func (r *ApplicationTemplateResource) Schema(ctx context.Context, req resource.S }, "options": &schema.ListAttribute{ ElementType: types.StringType, - Required: true, Description: "List of options", }, "user_editable": &schema.BoolAttribute{ diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index 5d24273da..ace2cda5a 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -35,7 +35,8 @@ func TestAccWaypoint_Application_basic(t *testing.T) { testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), resource.TestCheckResourceAttr(resourceName, "input_vars.#", "1"), - resource.TestCheckResourceAttr(resourceName, "input_vars.string_variable", "a"), + resource.TestCheckResourceAttr(resourceName, "input_vars.0.name", "string_variable"), + resource.TestCheckResourceAttr(resourceName, "input_vars.0.value", "a"), ), }, }, @@ -142,9 +143,7 @@ resource "hcp_waypoint_application_template" "test" { { name = "string_variable" variable_type = "string" - options = [ - "a" - ] + user_editable = true } ] } @@ -153,8 +152,11 @@ resource "hcp_waypoint_application" "test" { name = "%s" application_template_id = hcp_waypoint_application_template.test.id - input_vars = { - "string_variable" = "a" - } + input_vars = [ + { + name = "string_variable" + value = "a" + } + ] }`, tempName, appName) } From df86f64f9936cd796158d35466ec2d68166655d6 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 6 May 2024 17:55:10 -0400 Subject: [PATCH 04/32] waypoint: Fix template schema and regen docs. --- docs/data-sources/waypoint_application.md | 9 +++++++++ docs/resources/waypoint_application.md | 9 +++++++++ docs/resources/waypoint_application_template.md | 2 +- .../waypoint/resource_waypoint_application_template.go | 1 + 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/data-sources/waypoint_application.md b/docs/data-sources/waypoint_application.md index a1aba69a1..7e05e00c8 100644 --- a/docs/data-sources/waypoint_application.md +++ b/docs/data-sources/waypoint_application.md @@ -17,6 +17,7 @@ The Waypoint Application data source retrieves information on a given Applicatio ### Optional - `id` (String) The ID of the Application. +- `input_vars` (Attributes List) Input variables for the Application. (see [below for nested schema](#nestedatt--input_vars)) - `name` (String) The name of the Application. - `project_id` (String) The ID of the HCP project where the Waypoint Application is located. @@ -27,3 +28,11 @@ The Waypoint Application data source retrieves information on a given Applicatio - `namespace_id` (String) Internal Namespace ID. - `organization_id` (String) The ID of the HCP organization where the Waypoint Application is located. - `readme_markdown` (String) Instructions for using the Application (markdown format supported). + + +### Nested Schema for `input_vars` + +Read-Only: + +- `name` (String) Variable name +- `value` (String) Variable value diff --git a/docs/resources/waypoint_application.md b/docs/resources/waypoint_application.md index f4c60f312..b89a59193 100644 --- a/docs/resources/waypoint_application.md +++ b/docs/resources/waypoint_application.md @@ -21,6 +21,7 @@ The Waypoint Application resource managed the lifecycle of an Application that's ### Optional +- `input_vars` (Attributes List) Input variables for the Application. (see [below for nested schema](#nestedatt--input_vars)) - `project_id` (String) The ID of the HCP project where the Waypoint Application is located. - `readme_markdown` (String) Instructions for using the Application (markdown format supported). Note: this is a base64 encoded string, and can only be set in configuration after initial creation. The initial version of the README is generated from the README Template from source Application Template. @@ -30,3 +31,11 @@ The Waypoint Application resource managed the lifecycle of an Application that's - `id` (String) The ID of the Application. - `namespace_id` (String) Internal Namespace ID. - `organization_id` (String) The ID of the HCP organization where the Waypoint Application is located. + + +### Nested Schema for `input_vars` + +Required: + +- `name` (String) Variable name +- `value` (String) Variable value diff --git a/docs/resources/waypoint_application_template.md b/docs/resources/waypoint_application_template.md index d24fc861f..0d9d9d073 100644 --- a/docs/resources/waypoint_application_template.md +++ b/docs/resources/waypoint_application_template.md @@ -58,9 +58,9 @@ Required: Required: - `name` (String) Variable name -- `options` (List of String) List of options - `variable_type` (String) Variable type Optional: +- `options` (List of String) List of options - `user_editable` (Boolean) Whether the variable is editable by the user creating an application diff --git a/internal/provider/waypoint/resource_waypoint_application_template.go b/internal/provider/waypoint/resource_waypoint_application_template.go index 6458fa7ab..d7252bdce 100644 --- a/internal/provider/waypoint/resource_waypoint_application_template.go +++ b/internal/provider/waypoint/resource_waypoint_application_template.go @@ -169,6 +169,7 @@ func (r *ApplicationTemplateResource) Schema(ctx context.Context, req resource.S Description: "Variable type", }, "options": &schema.ListAttribute{ + Optional: true, ElementType: types.StringType, Description: "List of options", }, From 11730fbf15be800ac7e3f7872daac3e6fba7f2b4 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 6 May 2024 18:09:46 -0400 Subject: [PATCH 05/32] waypoint: Add changelog. --- .changelog/833.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/833.txt diff --git a/.changelog/833.txt b/.changelog/833.txt new file mode 100644 index 000000000..23853fa84 --- /dev/null +++ b/.changelog/833.txt @@ -0,0 +1,4 @@ +```release-note:improvement +Add support for input variables to `hcp_waypoint_application` resource and +data source. +``` \ No newline at end of file From 17ea6fee6a45180bb750ea6c822d56df8acc5deb Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 6 May 2024 22:48:18 -0400 Subject: [PATCH 06/32] waypoint: Fix applications and templates issues. * Add variable type to application input vars * Set variable options on a template to be computed * Enforce uniqueness in a template's variable options, if provided * Add acceptance test for templates where a template is user editable, instead of using variable options --- .../data_source_waypoint_application.go | 7 ++-- .../waypoint/resource_waypoint_application.go | 28 +++++++++----- .../resource_waypoint_application_template.go | 6 +++ ...urce_waypoint_application_template_test.go | 38 +++++++++++++++++-- .../resource_waypoint_application_test.go | 10 +++-- 5 files changed, 69 insertions(+), 20 deletions(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application.go b/internal/provider/waypoint/data_source_waypoint_application.go index 2df27a149..4c0b622b6 100644 --- a/internal/provider/waypoint/data_source_waypoint_application.go +++ b/internal/provider/waypoint/data_source_waypoint_application.go @@ -163,7 +163,7 @@ func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadReq } // A second API call is made to get the input vars set on the application - inputVars, err := clients.GetInputVariables(ctx, client, data.Name.String(), loc) + inputVars, err := clients.GetInputVariables(ctx, client, data.Name.ValueString(), loc) if err != nil { resp.Diagnostics.AddError(err.Error(), "Failed to fetch application's input variables.") return @@ -171,8 +171,9 @@ func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadReq for _, iv := range inputVars { data.InputVars = append(data.InputVars, &InputVar{ - Name: types.StringValue(iv.Name), - Value: types.StringValue(iv.Value), + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), + VariableType: types.StringValue(iv.VariableType), }) } diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index f9e84388a..dc0ced665 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -55,8 +55,9 @@ type ApplicationResourceModel struct { } type InputVar struct { - Name types.String `tfsdk:"name"` - Value types.String `tfsdk:"value"` + Name types.String `tfsdk:"name"` + VariableType types.String `tfsdk:"variable_type"` + Value types.String `tfsdk:"value"` } func (r *ApplicationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -139,6 +140,10 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq Required: true, Description: "Variable name", }, + "variable_type": &schema.StringAttribute{ + Required: true, + Description: "Variable type", + }, "value": &schema.StringAttribute{ Required: true, Description: "Variable value", @@ -203,8 +208,9 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq vars := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) for _, v := range plan.InputVars { vars = append(vars, &waypoint_models.HashicorpCloudWaypointInputVariable{ - Name: v.Name.ValueString(), - Value: v.Value.ValueString(), + Name: v.Name.ValueString(), + Value: v.Value.ValueString(), + VariableType: v.VariableType.ValueString(), }) } @@ -249,7 +255,7 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq plan.ReadmeMarkdown = types.StringNull() } - inputVars, err := clients.GetInputVariables(ctx, client, plan.Name.String(), loc) + inputVars, err := clients.GetInputVariables(ctx, client, plan.Name.ValueString(), loc) if err != nil { resp.Diagnostics.AddError(err.Error(), "Failed to fetch application's input variables.") return @@ -257,8 +263,9 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq for _, v := range inputVars { plan.InputVars = append(plan.InputVars, &InputVar{ - Name: types.StringValue(v.Name), - Value: types.StringValue(v.Value), + Name: types.StringValue(v.Name), + Value: types.StringValue(v.Value), + VariableType: types.StringValue(v.VariableType), }) } @@ -317,7 +324,7 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest data.ReadmeMarkdown = types.StringNull() } - inputVars, err := clients.GetInputVariables(ctx, client, data.Name.String(), loc) + inputVars, err := clients.GetInputVariables(ctx, client, data.Name.ValueString(), loc) if err != nil { resp.Diagnostics.AddError(err.Error(), "Failed to fetch application's input variables.") return @@ -325,8 +332,9 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest for _, v := range inputVars { data.InputVars = append(data.InputVars, &InputVar{ - Name: types.StringValue(v.Name), - Value: types.StringValue(v.Value), + Name: types.StringValue(v.Name), + Value: types.StringValue(v.Value), + VariableType: types.StringValue(v.VariableType), }) } diff --git a/internal/provider/waypoint/resource_waypoint_application_template.go b/internal/provider/waypoint/resource_waypoint_application_template.go index d7252bdce..256b70542 100644 --- a/internal/provider/waypoint/resource_waypoint_application_template.go +++ b/internal/provider/waypoint/resource_waypoint_application_template.go @@ -11,6 +11,7 @@ import ( sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/client/waypoint_service" waypoint_models "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/models" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -18,6 +19,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "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/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-hcp/internal/clients" @@ -170,7 +172,11 @@ func (r *ApplicationTemplateResource) Schema(ctx context.Context, req resource.S }, "options": &schema.ListAttribute{ Optional: true, + Computed: true, ElementType: types.StringType, + Validators: []validator.List{ + listvalidator.UniqueValues(), + }, Description: "List of options", }, "user_editable": &schema.BoolAttribute{ diff --git a/internal/provider/waypoint/resource_waypoint_application_template_test.go b/internal/provider/waypoint/resource_waypoint_application_template_test.go index 29b28d64b..8223252ce 100644 --- a/internal/provider/waypoint/resource_waypoint_application_template_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_template_test.go @@ -21,8 +21,9 @@ import ( ) func TestAccWaypoint_Application_Template_basic(t *testing.T) { - var appTemplateModel waypoint.ApplicationTemplateResourceModel + var appTemplateModel, appTemplateModel2 waypoint.ApplicationTemplateResourceModel resourceName := "hcp_waypoint_application_template.test" + resourceName2 := "hcp_waypoint_application_template.test_2" name := generateRandomName() updatedName := generateRandomName() @@ -41,6 +42,14 @@ func TestAccWaypoint_Application_Template_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "variable_options.0.variable_type", "string"), resource.TestCheckResourceAttr(resourceName, "variable_options.0.options.#", "1"), resource.TestCheckResourceAttr(resourceName, "variable_options.0.options.0", "a"), + + testAccCheckWaypointAppTemplateExists(t, resourceName2, &appTemplateModel2), + testAccCheckWaypointAppTemplateName(t, &appTemplateModel2, name+"-2"), + resource.TestCheckResourceAttr(resourceName2, "name", name+"-2"), + resource.TestCheckResourceAttr(resourceName2, "variable_options.0.name", "string_variable"), + resource.TestCheckResourceAttr(resourceName2, "variable_options.0.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName2, "variable_options.0.options.#", "0"), + resource.TestCheckResourceAttr(resourceName2, "variable_options.0.user_editable", "true"), ), }, { @@ -159,10 +168,33 @@ resource "hcp_waypoint_application_template" "test" { options = [ "a" ] - user_editable = false } ] -}`, name) +} + +resource "hcp_waypoint_application_template" "test_2" { + name = "%s-2" + summary = "some summary for fun" + readme_markdown_template = base64encode("# Some Readme") + terraform_no_code_module = { + source = "private/waypoint-tfc-testing/waypoint-template-starter/null" + version = "0.0.3" + } + terraform_cloud_workspace_details = { + name = "Default Project" + terraform_project_id = "prj-gfVyPJ2q2Aurn25o" + } + labels = ["one", "two"] + variable_options = [ + { + name = "string_variable" + variable_type = "string" + user_editable = true + options = [] + } + ] +} +`, name, name) } // generateRandomName will create a valid randomized name diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index ace2cda5a..a287ab26d 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -36,7 +36,7 @@ func TestAccWaypoint_Application_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "name", applicationName), resource.TestCheckResourceAttr(resourceName, "input_vars.#", "1"), resource.TestCheckResourceAttr(resourceName, "input_vars.0.name", "string_variable"), - resource.TestCheckResourceAttr(resourceName, "input_vars.0.value", "a"), + resource.TestCheckResourceAttr(resourceName, "input_vars.0.value", "x"), ), }, }, @@ -141,9 +141,10 @@ resource "hcp_waypoint_application_template" "test" { labels = ["one", "two"] variable_options = [ { - name = "string_variable" + name = "string_variable" variable_type = "string" user_editable = true + options = [] } ] } @@ -154,8 +155,9 @@ resource "hcp_waypoint_application" "test" { input_vars = [ { - name = "string_variable" - value = "a" + name = "string_variable" + variable_type = "string" + value = "x" } ] }`, tempName, appName) From 93461ed8ca0afb52aadb807f2091ccc8dda32dd7 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 7 May 2024 15:38:13 -0400 Subject: [PATCH 07/32] waypoint: Omit special variable from app input vars. This commit updates Waypoint application creation to omit the `waypoint_application` input variable from the variables returned to the provider from HCP Waypoint. This variable is implicitly set by the service, and is never expected to be in the config. Since it's not in the config, but is returned by the API, if it is added to the state, Terraform errors since the config and state don't match, even though that is expected. Another change in this PR is that the type of the input variable for an application is set in the state based on what is in the config, NOT the API response. This is because the API presently does not return the type of the variable. If and when that changes, this can be undone (as noted in the comment with this change). --- .../waypoint/resource_waypoint_application.go | 72 ++++++++++++++++--- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index dc0ced665..6d9aef8db 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -133,6 +133,7 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq }, "input_vars": schema.ListNestedAttribute{ Optional: true, + Computed: true, Description: "Input variables for the Application.", NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ @@ -205,6 +206,12 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq return } + // varTypes is used to store the variable type for each input variable + // to be used later when fetching the input variables from the API + varTypes := map[string]string{} + + // Prepare the input variables that the user provided to the application + // creation request vars := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) for _, v := range plan.InputVars { vars = append(vars, &waypoint_models.HashicorpCloudWaypointInputVariable{ @@ -212,6 +219,8 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq Value: v.Value.ValueString(), VariableType: v.VariableType.ValueString(), }) + + varTypes[v.Name.ValueString()] = v.VariableType.ValueString() } modelBody := &waypoint_models.HashicorpCloudWaypointWaypointServiceCreateApplicationFromTemplateBody{ @@ -261,13 +270,31 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq return } + planVars := make([]*InputVar, 0) for _, v := range inputVars { - plan.InputVars = append(plan.InputVars, &InputVar{ - Name: types.StringValue(v.Name), - Value: types.StringValue(v.Value), - VariableType: types.StringValue(v.VariableType), - }) + // Omit the waypoint_application input variable from the list of input + // variables, because the TF configuration does not set this, HCP + // Waypoint does, resulting in a plan inconsistent w/config. In future + // use a plan modifier to set this value. + if v.Name != "waypoint_application" { + iv := &InputVar{ + Name: types.StringValue(v.Name), + Value: types.StringValue(v.Value), + } + + // This is a workaround to set the variable type for the input by + // using the type defined in the configuration. This is needed + // because the API does not return the variable type for the input. + // When the API returns the variable type, this workaround can be + // removed. + if vt, ok := varTypes[v.Name]; ok { + iv.VariableType = types.StringValue(vt) + } + + planVars = append(planVars, iv) + } } + plan.InputVars = planVars // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log @@ -300,6 +327,13 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest client := r.client + // varTypes is used to store the variable type for each input variable + // to be used later when fetching the input variables from the API. + varTypes := map[string]string{} + for _, v := range data.InputVars { + varTypes[v.Name.ValueString()] = v.VariableType.ValueString() + } + application, err := clients.GetApplicationByID(ctx, client, loc, data.ID.ValueString()) if err != nil { if clients.IsResponseCodeNotFound(err) { @@ -330,13 +364,31 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest return } + dataVars := make([]*InputVar, 0) for _, v := range inputVars { - data.InputVars = append(data.InputVars, &InputVar{ - Name: types.StringValue(v.Name), - Value: types.StringValue(v.Value), - VariableType: types.StringValue(v.VariableType), - }) + // Omit the waypoint_application input variable from the list of input + // variables, because the TF configuration does not set this, HCP + // Waypoint does, resulting in a plan inconsistent w/config. In future + // use a plan modifier to set this value. + if v.Name != "waypoint_application" { + iv := &InputVar{ + Name: types.StringValue(v.Name), + Value: types.StringValue(v.Value), + } + + // This is a workaround to set the variable type for the input by + // using the type defined in the resource state. This is needed + // because the API does not return the variable type for the input. + // When the API returns the variable type, this workaround can be + // removed. + if vt, ok := varTypes[v.Name]; ok { + iv.VariableType = types.StringValue(vt) + } + + dataVars = append(dataVars, iv) + } } + data.InputVars = dataVars resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } From d9b34d802d2d095504ff8847ae9899d921b584e2 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 7 May 2024 18:02:29 -0400 Subject: [PATCH 08/32] waypoint: Add test case for applications w/variables. --- .../waypoint/resource_waypoint_application.go | 9 +- .../resource_waypoint_application_test.go | 86 +++++++++++++++++-- 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 6d9aef8db..533465570 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -133,7 +133,6 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq }, "input_vars": schema.ListNestedAttribute{ Optional: true, - Computed: true, Description: "Input variables for the Application.", NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ @@ -294,7 +293,9 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq planVars = append(planVars, iv) } } - plan.InputVars = planVars + if len(planVars) > 0 { + plan.InputVars = planVars + } // Write logs using the tflog package // Documentation: https://terraform.io/plugin/log @@ -388,7 +389,9 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest dataVars = append(dataVars, iv) } } - data.InputVars = dataVars + if len(dataVars) > 0 { + data.InputVars = dataVars + } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index a287ab26d..cd580d250 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -34,15 +34,43 @@ func TestAccWaypoint_Application_basic(t *testing.T) { testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), - resource.TestCheckResourceAttr(resourceName, "input_vars.#", "1"), - resource.TestCheckResourceAttr(resourceName, "input_vars.0.name", "string_variable"), - resource.TestCheckResourceAttr(resourceName, "input_vars.0.value", "x"), ), }, }, }) } +func TestAccWaypoint_ApplicationInputVariables(t *testing.T) { + var applicationModel waypoint.ApplicationResourceModel + resourceName := "hcp_waypoint_application.test" + templateName := generateRandomName() + applicationName := generateRandomName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointApplicationDestroy(t, &applicationModel), + Steps: []resource.TestStep{ + { + Config: testApplicationWithInputVarsConfig(templateName, applicationName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), + testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), + resource.TestCheckResourceAttr(resourceName, "name", applicationName), + resource.TestCheckResourceAttr(resourceName, "input_vars.#", "2"), + resource.TestCheckResourceAttr(resourceName, "input_vars.0.name", "vault_dweller_name"), + resource.TestCheckResourceAttr(resourceName, "input_vars.0.value", "paladin-devops"), + resource.TestCheckResourceAttr(resourceName, "input_vars.0.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "input_vars.1.name", "faction"), + resource.TestCheckResourceAttr(resourceName, "input_vars.1.value", "brotherhood-of-steel"), + resource.TestCheckResourceAttr(resourceName, "input_vars.1.variable_type", "string"), + ), + }, + }, + }) + +} + // simple attribute check on the application receved from the API func testAccCheckWaypointApplicationName(_ *testing.T, applicationModel *waypoint.ApplicationResourceModel, nameValue string) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -132,20 +160,55 @@ resource "hcp_waypoint_application_template" "test" { readme_markdown_template = base64encode("# Some Readme") terraform_no_code_module = { source = "private/waypoint-tfc-testing/waypoint-template-starter/null" - version = "0.0.3" + version = "0.0.2" } terraform_cloud_workspace_details = { name = "Default Project" terraform_project_id = "prj-gfVyPJ2q2Aurn25o" } labels = ["one", "two"] +} + +resource "hcp_waypoint_application" "test" { + name = "%s" + application_template_id = hcp_waypoint_application_template.test.id +}`, tempName, appName) +} + +func testApplicationWithInputVarsConfig(tempName, appName string) string { + return fmt.Sprintf(` +resource "hcp_waypoint_application_template" "test" { + name = "%s" + summary = "some summary for fun" + readme_markdown_template = base64encode("# Some Readme") + terraform_no_code_module = { + source = "private/waypoint-tfc-testing/waypoint-vault-dweller/null" + version = "0.0.1" + } + terraform_cloud_workspace_details = { + name = "Default Project" + terraform_project_id = "prj-gfVyPJ2q2Aurn25o" + } + labels = ["fallout", "vault-tec"] variable_options = [ { - name = "string_variable" + name = "vault_dweller_name" variable_type = "string" user_editable = true options = [] - } + }, + { + name = "faction" + variable_type = "string" + user_editable = false + options = [ + "ncr", + "brotherhood-of-steel", + "caesars-legion", + "raiders", + "institute" + ] + }, ] } @@ -154,11 +217,16 @@ resource "hcp_waypoint_application" "test" { application_template_id = hcp_waypoint_application_template.test.id input_vars = [ + { + name = "vault_dweller_name" + variable_type = "string" + value = "paladin-devops" + }, { - name = "string_variable" + name = "faction" variable_type = "string" - value = "x" - } + value = "brotherhood-of-steel" + } ] }`, tempName, appName) } From 70c05663fb8b1826e892146480e08f674277d3e2 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 20 May 2024 15:06:58 -0400 Subject: [PATCH 09/32] waypoint: Split template acc test cases. Basic test case is updated to only test basic aspects of a template, not variables with options. That has been split into a new test case. --- ...urce_waypoint_application_template_test.go | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application_template_test.go b/internal/provider/waypoint/resource_waypoint_application_template_test.go index 8223252ce..b53bb502e 100644 --- a/internal/provider/waypoint/resource_waypoint_application_template_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_template_test.go @@ -21,9 +21,8 @@ import ( ) func TestAccWaypoint_Application_Template_basic(t *testing.T) { - var appTemplateModel, appTemplateModel2 waypoint.ApplicationTemplateResourceModel + var appTemplateModel waypoint.ApplicationTemplateResourceModel resourceName := "hcp_waypoint_application_template.test" - resourceName2 := "hcp_waypoint_application_template.test_2" name := generateRandomName() updatedName := generateRandomName() @@ -38,18 +37,6 @@ func TestAccWaypoint_Application_Template_basic(t *testing.T) { testAccCheckWaypointAppTemplateExists(t, resourceName, &appTemplateModel), testAccCheckWaypointAppTemplateName(t, &appTemplateModel, name), resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "variable_options.0.name", "string_variable"), - resource.TestCheckResourceAttr(resourceName, "variable_options.0.variable_type", "string"), - resource.TestCheckResourceAttr(resourceName, "variable_options.0.options.#", "1"), - resource.TestCheckResourceAttr(resourceName, "variable_options.0.options.0", "a"), - - testAccCheckWaypointAppTemplateExists(t, resourceName2, &appTemplateModel2), - testAccCheckWaypointAppTemplateName(t, &appTemplateModel2, name+"-2"), - resource.TestCheckResourceAttr(resourceName2, "name", name+"-2"), - resource.TestCheckResourceAttr(resourceName2, "variable_options.0.name", "string_variable"), - resource.TestCheckResourceAttr(resourceName2, "variable_options.0.variable_type", "string"), - resource.TestCheckResourceAttr(resourceName2, "variable_options.0.options.#", "0"), - resource.TestCheckResourceAttr(resourceName2, "variable_options.0.user_editable", "true"), ), }, { @@ -58,10 +45,28 @@ func TestAccWaypoint_Application_Template_basic(t *testing.T) { testAccCheckWaypointAppTemplateExists(t, resourceName, &appTemplateModel), testAccCheckWaypointAppTemplateName(t, &appTemplateModel, updatedName), resource.TestCheckResourceAttr(resourceName, "name", updatedName), - resource.TestCheckResourceAttr(resourceName, "variable_options.0.name", "string_variable"), - resource.TestCheckResourceAttr(resourceName, "variable_options.0.variable_type", "string"), - resource.TestCheckResourceAttr(resourceName, "variable_options.0.options.#", "1"), - resource.TestCheckResourceAttr(resourceName, "variable_options.0.options.0", "a"), + ), + }, + }, + }) +} + +func TestAccWaypoint_Application_template_with_variable_options(t *testing.T) { + var appTemplateModel waypoint.ApplicationTemplateResourceModel + resourceName := "hcp_waypoint_application_template.var_opts_test" + name := generateRandomName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointAppTemplateDestroy(t, &appTemplateModel), + Steps: []resource.TestStep{ + { + Config: testAppTemplateConfigWithVarOpts(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointAppTemplateExists(t, resourceName, &appTemplateModel), + testAccCheckWaypointAppTemplateName(t, &appTemplateModel, name), + resource.TestCheckResourceAttr(resourceName, "name", name), ), }, }, @@ -154,47 +159,51 @@ resource "hcp_waypoint_application_template" "test" { readme_markdown_template = base64encode("# Some Readme") terraform_no_code_module = { source = "private/waypoint-tfc-testing/waypoint-template-starter/null" - version = "0.0.3" + version = "0.0.2" } terraform_cloud_workspace_details = { name = "Default Project" terraform_project_id = "prj-gfVyPJ2q2Aurn25o" } labels = ["one", "two"] - variable_options = [ - { - name = "string_variable" - variable_type = "string" - options = [ - "a" - ] - } - ] +}`, name) } -resource "hcp_waypoint_application_template" "test_2" { - name = "%s-2" - summary = "some summary for fun" +func testAppTemplateConfigWithVarOpts(name string) string { + return fmt.Sprintf(` +resource "hcp_waypoint_application_template" "var_opts_test" { + name = "%s" + summary = "A template with a variable with options." readme_markdown_template = base64encode("# Some Readme") terraform_no_code_module = { - source = "private/waypoint-tfc-testing/waypoint-template-starter/null" - version = "0.0.3" + source = "private/waypoint-tfc-testing/waypoint-vault-dweller/null" + version = "0.0.1" } terraform_cloud_workspace_details = { name = "Default Project" terraform_project_id = "prj-gfVyPJ2q2Aurn25o" } - labels = ["one", "two"] variable_options = [ { - name = "string_variable" + name = "vault_dweller_name" + variable_type = "string" + user_editable = true + options = [] + }, + { + name = "faction" variable_type = "string" user_editable = true - options = [] + options = [ + "ncr", + "brotherhood-of-steel", + "caesars-legion", + "raiders", + "institute" + ] } ] -} -`, name, name) +}`, name) } // generateRandomName will create a valid randomized name From 3ab871d34f5282483332074e61b4fce9ce3e9fb1 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 20 May 2024 21:37:33 -0400 Subject: [PATCH 10/32] waypoint: Split template data source acc test cases. --- ...urce_waypoint_application_template_test.go | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application_template_test.go b/internal/provider/waypoint/data_source_waypoint_application_template_test.go index 79c1cb97e..633e685f8 100644 --- a/internal/provider/waypoint/data_source_waypoint_application_template_test.go +++ b/internal/provider/waypoint/data_source_waypoint_application_template_test.go @@ -40,10 +40,6 @@ func TestAccWaypointData_Application_Template_basic(t *testing.T) { Config: testDataAppTemplateConfig(name), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(dataSourceName, "name", name), - resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.name", "string_variable"), - resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.variable_type", "string"), - resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.options.#", "1"), - resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.options.0", "a"), ), }, { @@ -52,19 +48,61 @@ func TestAccWaypointData_Application_Template_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", updatedName), resource.TestCheckResourceAttr(dataSourceName, "name", updatedName), - resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.name", "string_variable"), - resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.variable_type", "string"), - resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.options.#", "1"), - resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.options.0", "a"), ), }, }, }) } +func TestAccWaypointData_Application_template_with_variable_options(t *testing.T) { + // this is only used to verify the app template gets cleaned up in the end + // of the test, and not used for any other purpose at this time + var appTemplateModel waypoint.ApplicationTemplateResourceModel + resourceName := "hcp_waypoint_application_template.var_opts_test" + dataSourceName := "data." + resourceName + name := generateRandomName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointAppTemplateDestroy(t, &appTemplateModel), + Steps: []resource.TestStep{ + { + // establish the base app template + // note this reuses the config method from the app template + // resource test + Config: testAppTemplateConfigWithVarOpts(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointAppTemplateExists(t, resourceName, &appTemplateModel), + ), + }, + { + // add a data source config to read the app template + Config: testDataAppTemplateWithVariablesWithOptionsConfig(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "name", name), + resource.TestCheckResourceAttr(dataSourceName, "variable_options.#", "2"), + resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.name", "faction"), + resource.TestCheckResourceAttr(dataSourceName, "variable_options.0.type", "string"), + resource.TestCheckResourceAttr(dataSourceName, "variable_options.1.name", "vault_dweller_name"), + resource.TestCheckResourceAttr(dataSourceName, "variable_options.1.type", "string"), + ), + }, + }, + }) + +} + func testDataAppTemplateConfig(name string) string { return fmt.Sprintf(`%s data "hcp_waypoint_application_template" "test" { name = hcp_waypoint_application_template.test.name }`, testAppTemplateConfig(name)) } + +func testDataAppTemplateWithVariablesWithOptionsConfig(name string) string { + return fmt.Sprintf(`%s +data "hcp_waypoint_application_template" "var_opts_test" { + name = hcp_waypoint_application_template.var_opts_test.name +}`, testAppTemplateConfigWithVarOpts(name)) +} From 451d6dc626dc27d4073dfebeb12ff87553f6ce8c Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 20 May 2024 23:26:43 -0400 Subject: [PATCH 11/32] waypoint: Change app input vars to list. * Implement value converter interface for input vars, to use "As" for conversion from a TF value to a struct * Refactor code to read input vars into the plan/state into readInputs function * Set list of input vars to a null list if there are no inputs * Update tests to use corrected variable options * Add variable type to data source schema, though it won't be set yet --- .../data_source_waypoint_application.go | 15 +- .../waypoint/resource_waypoint_application.go | 215 ++++++++++++------ .../resource_waypoint_application_test.go | 35 +-- 3 files changed, 179 insertions(+), 86 deletions(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application.go b/internal/provider/waypoint/data_source_waypoint_application.go index 4c0b622b6..3eebdfae2 100644 --- a/internal/provider/waypoint/data_source_waypoint_application.go +++ b/internal/provider/waypoint/data_source_waypoint_application.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-hcp/internal/clients" ) @@ -93,6 +94,10 @@ func (d *DataSourceApplication) Schema(ctx context.Context, req datasource.Schem Computed: true, Description: "Variable value", }, + "variable_type": &schema.StringAttribute{ + Computed: true, + Description: "Variable type", + }, }, }, }, @@ -169,12 +174,10 @@ func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadReq return } - for _, iv := range inputVars { - data.InputVars = append(data.InputVars, &InputVar{ - Name: types.StringValue(iv.Name), - Value: types.StringValue(iv.Value), - VariableType: types.StringValue(iv.VariableType), - }) + resp.Diagnostics.Append(readInputs(ctx, inputVars, &data, nil)...) + if resp.Diagnostics.HasError() { + tflog.Error(ctx, "error reading application input variables") + return } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 533465570..71fa601fd 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -11,12 +11,16 @@ import ( sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/client/waypoint_service" waypoint_models "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/models" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "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/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-hcp/internal/clients" ) @@ -51,7 +55,7 @@ type ApplicationResourceModel struct { // deferred and probably a list or objects, but may possible be a separate // ActionCfgs types.List `tfsdk:"action_cfgs"` - InputVars []*InputVar `tfsdk:"input_vars"` + InputVars types.List `tfsdk:"input_vars"` } type InputVar struct { @@ -60,6 +64,14 @@ type InputVar struct { Value types.String `tfsdk:"value"` } +func (i InputVar) attrTypes() map[string]attr.Type { + return map[string]attr.Type{ + "name": types.StringType, + "variable_type": types.StringType, + "value": types.StringType, + } +} + func (r *ApplicationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_waypoint_application" } @@ -174,6 +186,38 @@ func (r *ApplicationResource) Configure(ctx context.Context, req resource.Config r.client = client } +type varConverter struct { + name string + variableType string + value string +} + +// FromTerraform5Value implements the ValueConverter interface +func (vc *varConverter) FromTerraform5Value(val tftypes.Value) error { + v := map[string]tftypes.Value{} + err := val.As(&v) + if err != nil { + return err + } + + err = v["name"].As(&vc.name) + if err != nil { + return err + } + + err = v["value"].As(&vc.value) + if err != nil { + return err + } + + err = v["variable_type"].As(&vc.variableType) + if err != nil { + return err + } + + return nil +} + func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan *ApplicationResourceModel @@ -211,15 +255,33 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq // Prepare the input variables that the user provided to the application // creation request - vars := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) - for _, v := range plan.InputVars { - vars = append(vars, &waypoint_models.HashicorpCloudWaypointInputVariable{ - Name: v.Name.ValueString(), - Value: v.Value.ValueString(), - VariableType: v.VariableType.ValueString(), + ivs := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) + for _, v := range plan.InputVars.Elements() { + // convert list element to a struct representing an input variable, of + // type varConverter + var iv tftypes.Value + iv, err = v.ToTerraformValue(ctx) + if err != nil { + tflog.Error(ctx, "error reading application input variables") + return + } + vc := varConverter{} + err = iv.As(&vc) + if err != nil { + tflog.Error(ctx, "error reading application input variables") + return + } + + // add the input variable to the list of input variables for the app + // creation API call + ivs = append(ivs, &waypoint_models.HashicorpCloudWaypointInputVariable{ + Name: vc.name, + Value: vc.value, + VariableType: vc.variableType, }) - varTypes[v.Name.ValueString()] = v.VariableType.ValueString() + // store var type for later use when fetching the input variables from the API + varTypes[vc.name] = vc.variableType } modelBody := &waypoint_models.HashicorpCloudWaypointWaypointServiceCreateApplicationFromTemplateBody{ @@ -227,7 +289,7 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq ApplicationTemplate: &waypoint_models.HashicorpCloudWaypointRefApplicationTemplate{ ID: plan.ApplicationTemplateID.ValueString(), }, - Variables: vars, + Variables: ivs, } params := &waypoint_service.WaypointServiceCreateApplicationFromTemplateParams{ @@ -269,32 +331,10 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq return } - planVars := make([]*InputVar, 0) - for _, v := range inputVars { - // Omit the waypoint_application input variable from the list of input - // variables, because the TF configuration does not set this, HCP - // Waypoint does, resulting in a plan inconsistent w/config. In future - // use a plan modifier to set this value. - if v.Name != "waypoint_application" { - iv := &InputVar{ - Name: types.StringValue(v.Name), - Value: types.StringValue(v.Value), - } - - // This is a workaround to set the variable type for the input by - // using the type defined in the configuration. This is needed - // because the API does not return the variable type for the input. - // When the API returns the variable type, this workaround can be - // removed. - if vt, ok := varTypes[v.Name]; ok { - iv.VariableType = types.StringValue(vt) - } - - planVars = append(planVars, iv) - } - } - if len(planVars) > 0 { - plan.InputVars = planVars + resp.Diagnostics.Append(readInputs(ctx, inputVars, plan, varTypes)...) + if resp.Diagnostics.HasError() { + tflog.Error(ctx, "error reading application input variables") + return } // Write logs using the tflog package @@ -326,15 +366,42 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest ProjectID: projectID, } - client := r.client - // varTypes is used to store the variable type for each input variable - // to be used later when fetching the input variables from the API. + // to be used later when fetching the input variables from the API varTypes := map[string]string{} - for _, v := range data.InputVars { - varTypes[v.Name.ValueString()] = v.VariableType.ValueString() + + // Prepare the input variables that the user provided to the application + // creation request + ivs := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) + for _, v := range data.InputVars.Elements() { + // convert list element to a struct representing an input variable, of + // type varConverter + iv, err := v.ToTerraformValue(ctx) + if err != nil { + tflog.Error(ctx, "error reading application input variables") + return + } + vc := varConverter{} + err = iv.As(&vc) + if err != nil { + tflog.Error(ctx, "error reading application input variables") + return + } + + // add the input variable to the list of input variables for the app + // creation API call + ivs = append(ivs, &waypoint_models.HashicorpCloudWaypointInputVariable{ + Name: vc.name, + Value: vc.value, + VariableType: vc.variableType, + }) + + // store var type for later use when fetching the input variables from the API + varTypes[vc.name] = vc.variableType } + client := r.client + application, err := clients.GetApplicationByID(ctx, client, loc, data.ID.ValueString()) if err != nil { if clients.IsResponseCodeNotFound(err) { @@ -365,35 +432,53 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest return } - dataVars := make([]*InputVar, 0) - for _, v := range inputVars { - // Omit the waypoint_application input variable from the list of input - // variables, because the TF configuration does not set this, HCP - // Waypoint does, resulting in a plan inconsistent w/config. In future - // use a plan modifier to set this value. - if v.Name != "waypoint_application" { - iv := &InputVar{ - Name: types.StringValue(v.Name), - Value: types.StringValue(v.Value), - } + resp.Diagnostics.Append(readInputs(ctx, inputVars, data, varTypes)...) + if resp.Diagnostics.HasError() { + tflog.Error(ctx, "error reading application input variables") + return + } - // This is a workaround to set the variable type for the input by - // using the type defined in the resource state. This is needed - // because the API does not return the variable type for the input. - // When the API returns the variable type, this workaround can be - // removed. - if vt, ok := varTypes[v.Name]; ok { - iv.VariableType = types.StringValue(vt) - } + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} - dataVars = append(dataVars, iv) +// readInputs reads the inputVars for an app into the application resource model +func readInputs( + ctx context.Context, + inputVars []*waypoint_models.HashicorpCloudWaypointInputVariable, + plan *ApplicationResourceModel, + varTypes map[string]string, +) diag.Diagnostics { + var diags diag.Diagnostics + if inputVars != nil { + ivl := make([]*InputVar, 0) + for _, iv := range inputVars { + // TODO: Omit not only waypoint_application but also variables + // set on the template. + if iv.Name != "waypoint_application" { + inputVar := &InputVar{ + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), + } + + if _, ok := varTypes[iv.Name]; ok { + inputVar.VariableType = types.StringValue(varTypes[iv.Name]) + } + + ivl = append(ivl, inputVar) + } + } + if len(ivl) > 0 { + var ivs basetypes.ListValue + ivs, diags = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, ivl) + if diags.HasError() { + return diags + } + plan.InputVars = ivs + } else { + plan.InputVars = types.ListNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } } - if len(dataVars) > 0 { - data.InputVars = dataVars - } - - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + return diags } func (r *ApplicationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index cd580d250..fd48d5a6a 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -42,7 +42,7 @@ func TestAccWaypoint_Application_basic(t *testing.T) { func TestAccWaypoint_ApplicationInputVariables(t *testing.T) { var applicationModel waypoint.ApplicationResourceModel - resourceName := "hcp_waypoint_application.test" + resourceName := "hcp_waypoint_application.test_var_opts" templateName := generateRandomName() applicationName := generateRandomName() @@ -58,11 +58,11 @@ func TestAccWaypoint_ApplicationInputVariables(t *testing.T) { testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), resource.TestCheckResourceAttr(resourceName, "input_vars.#", "2"), - resource.TestCheckResourceAttr(resourceName, "input_vars.0.name", "vault_dweller_name"), - resource.TestCheckResourceAttr(resourceName, "input_vars.0.value", "paladin-devops"), + resource.TestCheckResourceAttr(resourceName, "input_vars.0.name", "faction"), + resource.TestCheckResourceAttr(resourceName, "input_vars.0.value", "brotherhood-of-steel"), resource.TestCheckResourceAttr(resourceName, "input_vars.0.variable_type", "string"), - resource.TestCheckResourceAttr(resourceName, "input_vars.1.name", "faction"), - resource.TestCheckResourceAttr(resourceName, "input_vars.1.value", "brotherhood-of-steel"), + resource.TestCheckResourceAttr(resourceName, "input_vars.1.name", "vault_dweller_name"), + resource.TestCheckResourceAttr(resourceName, "input_vars.1.value", "courier"), resource.TestCheckResourceAttr(resourceName, "input_vars.1.variable_type", "string"), ), }, @@ -177,7 +177,7 @@ resource "hcp_waypoint_application" "test" { func testApplicationWithInputVarsConfig(tempName, appName string) string { return fmt.Sprintf(` -resource "hcp_waypoint_application_template" "test" { +resource "hcp_waypoint_application_template" "test_var_opts" { name = "%s" summary = "some summary for fun" readme_markdown_template = base64encode("# Some Readme") @@ -195,12 +195,17 @@ resource "hcp_waypoint_application_template" "test" { name = "vault_dweller_name" variable_type = "string" user_editable = true - options = [] + options = [ + "lucy", + "courier", + "lone-wanderer", + "sole-survivor", + ] }, { name = "faction" variable_type = "string" - user_editable = false + user_editable = true options = [ "ncr", "brotherhood-of-steel", @@ -212,20 +217,20 @@ resource "hcp_waypoint_application_template" "test" { ] } -resource "hcp_waypoint_application" "test" { +resource "hcp_waypoint_application" "test_var_opts" { name = "%s" - application_template_id = hcp_waypoint_application_template.test.id + application_template_id = hcp_waypoint_application_template.test_var_opts.id input_vars = [ - { - name = "vault_dweller_name" - variable_type = "string" - value = "paladin-devops" - }, { name = "faction" variable_type = "string" value = "brotherhood-of-steel" + }, + { + name = "vault_dweller_name" + variable_type = "string" + value = "courier" } ] }`, tempName, appName) From 92cb44c9e493226bc241ae5b94801091ca445617 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 20 May 2024 23:29:48 -0400 Subject: [PATCH 12/32] waypoint: Add test for app data source with input vars. --- .../data_source_waypoint_application_test.go | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/internal/provider/waypoint/data_source_waypoint_application_test.go b/internal/provider/waypoint/data_source_waypoint_application_test.go index c5fdb9916..4d7214a9d 100644 --- a/internal/provider/waypoint/data_source_waypoint_application_test.go +++ b/internal/provider/waypoint/data_source_waypoint_application_test.go @@ -44,6 +44,40 @@ func TestAccWaypoint_Application_DataSource_basic(t *testing.T) { }) } +func TestAccWaypoint_Application_DataSource_WithInputVars(t *testing.T) { + // this is only used to verify the app template gets cleaned up in the end + // of the test, and not used for any other purpose at this time + var applicationModel waypoint.ApplicationResourceModel + resourceName := "hcp_waypoint_application.test_var_opts" + dataSourceName := "data." + resourceName + templateName := generateRandomName() + applicationName := generateRandomName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointApplicationDestroy(t, &applicationModel), + Steps: []resource.TestStep{ + { + // establish the base app template and application + Config: testApplicationWithInputVarsConfig(templateName, applicationName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), + ), + }, + { + // add a data source config to read the app template + Config: testDataApplicationWithInputVarsConfig(templateName, applicationName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "name", applicationName), + resource.TestCheckResourceAttr(dataSourceName, "input_vars.#", "2"), + ), + }, + }, + }) + +} + func testDataApplicationConfig(templateName, applicationName string) string { return fmt.Sprintf(`%s @@ -51,3 +85,11 @@ data "hcp_waypoint_application" "test" { name = hcp_waypoint_application.test.name }`, testApplicationConfig(templateName, applicationName)) } + +func testDataApplicationWithInputVarsConfig(templateName, applicationName string) string { + return fmt.Sprintf(`%s + +data "hcp_waypoint_application" "test_var_opts" { + name = hcp_waypoint_application.test_var_opts.name +}`, testApplicationWithInputVarsConfig(templateName, applicationName)) +} From d795c3a60912f17f27f270922c463d2c335fec6a Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 20 May 2024 23:30:29 -0400 Subject: [PATCH 13/32] waypoint: Docs generation. --- docs/data-sources/waypoint_application.md | 1 + docs/resources/waypoint_application.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/data-sources/waypoint_application.md b/docs/data-sources/waypoint_application.md index 7e05e00c8..b73bb8147 100644 --- a/docs/data-sources/waypoint_application.md +++ b/docs/data-sources/waypoint_application.md @@ -36,3 +36,4 @@ Read-Only: - `name` (String) Variable name - `value` (String) Variable value +- `variable_type` (String) Variable type diff --git a/docs/resources/waypoint_application.md b/docs/resources/waypoint_application.md index b89a59193..02435c463 100644 --- a/docs/resources/waypoint_application.md +++ b/docs/resources/waypoint_application.md @@ -39,3 +39,4 @@ Required: - `name` (String) Variable name - `value` (String) Variable value +- `variable_type` (String) Variable type From 0f7dfb73e4d106d82a135c919fdb08d579081fec Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 20 May 2024 23:45:10 -0400 Subject: [PATCH 14/32] waypoint: Add app test. This test simulates a template which sets two variables and an application created using that template. --- .../resource_waypoint_application_test.go | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index fd48d5a6a..faba2a4ff 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -71,6 +71,30 @@ func TestAccWaypoint_ApplicationInputVariables(t *testing.T) { } +func TestAccWaypoint_ApplicationInputVariables_OnTemplate(t *testing.T) { + var applicationModel waypoint.ApplicationResourceModel + resourceName := "hcp_waypoint_application.test_var_opts" + templateName := generateRandomName() + applicationName := generateRandomName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointApplicationDestroy(t, &applicationModel), + Steps: []resource.TestStep{ + { + Config: testApplicationWithNoInputVarsConfig(templateName, applicationName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), + testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), + resource.TestCheckResourceAttr(resourceName, "name", applicationName), + resource.TestCheckResourceAttr(resourceName, "input_vars.#", "0"), + ), + }, + }, + }) +} + // simple attribute check on the application receved from the API func testAccCheckWaypointApplicationName(_ *testing.T, applicationModel *waypoint.ApplicationResourceModel, nameValue string) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -235,3 +259,46 @@ resource "hcp_waypoint_application" "test_var_opts" { ] }`, tempName, appName) } + +func testApplicationWithNoInputVarsConfig(tempName, appName string) string { + return fmt.Sprintf(` +resource "hcp_waypoint_application_template" "test_var_opts" { + name = "%s" + summary = "some summary for fun" + readme_markdown_template = base64encode("# Some Readme") + terraform_no_code_module = { + source = "private/waypoint-tfc-testing/waypoint-vault-dweller/null" + version = "0.0.1" + } + terraform_cloud_workspace_details = { + name = "Default Project" + terraform_project_id = "prj-gfVyPJ2q2Aurn25o" + } + labels = ["fallout", "vault-tec"] + variable_options = [ + { + name = "vault_dweller_name" + variable_type = "string" + user_editable = false + options = [ + "lone-wanderer", + ] + }, + { + name = "faction" + variable_type = "string" + user_editable = false + options = [ + "brotherhood-of-steel", + ] + }, + ] +} + +resource "hcp_waypoint_application" "test_var_opts" { + name = "%s" + application_template_id = hcp_waypoint_application_template.test_var_opts.id + + input_vars = [] +}`, tempName, appName) +} From 648f3ae8140496218076896eafe31c249e5c197d Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 21 May 2024 09:35:32 -0400 Subject: [PATCH 15/32] waypoint: Remove unnecessary list of input vars. The list of input vars removed by this commit is only needed during creation of an app, not a read. --- .../waypoint/resource_waypoint_application.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 71fa601fd..8c9e11de9 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -370,9 +370,6 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest // to be used later when fetching the input variables from the API varTypes := map[string]string{} - // Prepare the input variables that the user provided to the application - // creation request - ivs := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) for _, v := range data.InputVars.Elements() { // convert list element to a struct representing an input variable, of // type varConverter @@ -388,14 +385,6 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest return } - // add the input variable to the list of input variables for the app - // creation API call - ivs = append(ivs, &waypoint_models.HashicorpCloudWaypointInputVariable{ - Name: vc.name, - Value: vc.value, - VariableType: vc.variableType, - }) - // store var type for later use when fetching the input variables from the API varTypes[vc.name] = vc.variableType } From 3120752d103ad9b2268a44f7dac8c749b71fe35c Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Thu, 23 May 2024 17:21:11 -0400 Subject: [PATCH 16/32] waypoint: Split app and template input vars for an app. This commit splits up the input vars for an application that are set by a template and an app into two separate parts of the schema for the resource. The function readInputs is updated to make the distinction. --- .../waypoint/resource_waypoint_application.go | 63 ++++++++++++++----- .../resource_waypoint_application_test.go | 6 +- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 8c9e11de9..9a46c09e5 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -19,7 +19,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-hcp/internal/clients" @@ -55,7 +54,8 @@ type ApplicationResourceModel struct { // deferred and probably a list or objects, but may possible be a separate // ActionCfgs types.List `tfsdk:"action_cfgs"` - InputVars types.List `tfsdk:"input_vars"` + InputVars types.List `tfsdk:"app_input_vars"` + TemplateInputVars types.List `tfsdk:"template_input_vars"` } type InputVar struct { @@ -143,9 +143,9 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq stringplanmodifier.UseStateForUnknown(), }, }, - "input_vars": schema.ListNestedAttribute{ + "app_input_vars": schema.ListNestedAttribute{ Optional: true, - Description: "Input variables for the Application.", + Description: "Input variables set for the application.", NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "name": &schema.StringAttribute{ @@ -163,6 +163,27 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq }, }, }, + "template_input_vars": schema.ListNestedAttribute{ + Computed: true, + Optional: true, + Description: "Input variables set by the template for the application.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": &schema.StringAttribute{ + Required: true, + Description: "Variable name", + }, + "variable_type": &schema.StringAttribute{ + Required: false, + Description: "Variable type", + }, + "value": &schema.StringAttribute{ + Required: true, + Description: "Variable value", + }, + }, + }, + }, }, } } @@ -430,7 +451,7 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -// readInputs reads the inputVars for an app into the application resource model +// readInputs reads the inputVars for an app into the application resource model. func readInputs( ctx context.Context, inputVars []*waypoint_models.HashicorpCloudWaypointInputVariable, @@ -438,34 +459,48 @@ func readInputs( varTypes map[string]string, ) diag.Diagnostics { var diags diag.Diagnostics + if inputVars != nil { - ivl := make([]*InputVar, 0) + // make app an template input vars lists + aivls := make([]*InputVar, 0) + tivls := make([]*InputVar, 0) for _, iv := range inputVars { - // TODO: Omit not only waypoint_application but also variables - // set on the template. if iv.Name != "waypoint_application" { inputVar := &InputVar{ Name: types.StringValue(iv.Name), Value: types.StringValue(iv.Value), } + // if the variable isn't in the varTypes map, it's an input + // variable set by the template if _, ok := varTypes[iv.Name]; ok { inputVar.VariableType = types.StringValue(varTypes[iv.Name]) + aivls = append(aivls, inputVar) + } else { + inputVar.VariableType = types.StringNull() + tivls = append(tivls, inputVar) } - - ivl = append(ivl, inputVar) } } - if len(ivl) > 0 { - var ivs basetypes.ListValue - ivs, diags = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, ivl) + if len(aivls) > 0 { + aivs, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) if diags.HasError() { return diags } - plan.InputVars = ivs + plan.InputVars = aivs } else { plan.InputVars = types.ListNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } + + if len(tivls) > 0 { + tivs, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, tivls) + if diags.HasError() { + return diags + } + plan.TemplateInputVars = tivs + } else { + plan.TemplateInputVars = types.ListNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) + } } return diags } diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index faba2a4ff..f7c380667 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -88,7 +88,7 @@ func TestAccWaypoint_ApplicationInputVariables_OnTemplate(t *testing.T) { testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), - resource.TestCheckResourceAttr(resourceName, "input_vars.#", "0"), + resource.TestCheckResourceAttr(resourceName, "app_input_vars.#", "0"), ), }, }, @@ -245,7 +245,7 @@ resource "hcp_waypoint_application" "test_var_opts" { name = "%s" application_template_id = hcp_waypoint_application_template.test_var_opts.id - input_vars = [ + app_input_vars = [ { name = "faction" variable_type = "string" @@ -299,6 +299,6 @@ resource "hcp_waypoint_application" "test_var_opts" { name = "%s" application_template_id = hcp_waypoint_application_template.test_var_opts.id - input_vars = [] + app_input_vars = [] }`, tempName, appName) } From 2aba08ac746f45b68f456b066c01e41125fe5502 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Thu, 23 May 2024 18:09:31 -0400 Subject: [PATCH 17/32] waypoint: Change app input vars to a computed set. --- internal/provider/waypoint/resource_waypoint_application.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 9a46c09e5..7d1e410a9 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -143,7 +143,7 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq stringplanmodifier.UseStateForUnknown(), }, }, - "app_input_vars": schema.ListNestedAttribute{ + "app_input_vars": schema.SetNestedAttribute{ Optional: true, Description: "Input variables set for the application.", NestedObject: schema.NestedAttributeObject{ @@ -163,9 +163,8 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq }, }, }, - "template_input_vars": schema.ListNestedAttribute{ + "template_input_vars": schema.SetNestedAttribute{ Computed: true, - Optional: true, Description: "Input variables set by the template for the application.", NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ From 2e1bcb2685b56479dc37471e85dc322d0b23d475 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Thu, 23 May 2024 19:11:26 -0400 Subject: [PATCH 18/32] waypoint: Update app input and tpl vars to set. Update acc test to test that template vars are set for an application that uses a template which configured a value for an input var. --- .../waypoint/resource_waypoint_application.go | 14 +++++------ .../resource_waypoint_application_test.go | 25 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 7d1e410a9..c32e537af 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -54,8 +54,8 @@ type ApplicationResourceModel struct { // deferred and probably a list or objects, but may possible be a separate // ActionCfgs types.List `tfsdk:"action_cfgs"` - InputVars types.List `tfsdk:"app_input_vars"` - TemplateInputVars types.List `tfsdk:"template_input_vars"` + InputVars types.Set `tfsdk:"app_input_vars"` + TemplateInputVars types.Set `tfsdk:"template_input_vars"` } type InputVar struct { @@ -165,7 +165,7 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq }, "template_input_vars": schema.SetNestedAttribute{ Computed: true, - Description: "Input variables set by the template for the application.", + Description: "Input variables set for the application.", NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "name": &schema.StringAttribute{ @@ -482,23 +482,23 @@ func readInputs( } } if len(aivls) > 0 { - aivs, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) + aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) if diags.HasError() { return diags } plan.InputVars = aivs } else { - plan.InputVars = types.ListNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) + plan.InputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } if len(tivls) > 0 { - tivs, diags := types.ListValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, tivls) + tivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, tivls) if diags.HasError() { return diags } plan.TemplateInputVars = tivs } else { - plan.TemplateInputVars = types.ListNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) + plan.TemplateInputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } } return diags diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index f7c380667..0d6020be1 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -57,13 +57,13 @@ func TestAccWaypoint_ApplicationInputVariables(t *testing.T) { testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), - resource.TestCheckResourceAttr(resourceName, "input_vars.#", "2"), - resource.TestCheckResourceAttr(resourceName, "input_vars.0.name", "faction"), - resource.TestCheckResourceAttr(resourceName, "input_vars.0.value", "brotherhood-of-steel"), - resource.TestCheckResourceAttr(resourceName, "input_vars.0.variable_type", "string"), - resource.TestCheckResourceAttr(resourceName, "input_vars.1.name", "vault_dweller_name"), - resource.TestCheckResourceAttr(resourceName, "input_vars.1.value", "courier"), - resource.TestCheckResourceAttr(resourceName, "input_vars.1.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "app_input_vars.#", "2"), + resource.TestCheckResourceAttr(resourceName, "app_input_vars.0.name", "faction"), + resource.TestCheckResourceAttr(resourceName, "app_input_vars.0.value", "brotherhood-of-steel"), + resource.TestCheckResourceAttr(resourceName, "app_input_vars.0.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "app_input_vars.1.name", "vault_dweller_name"), + resource.TestCheckResourceAttr(resourceName, "app_input_vars.1.value", "courier"), + resource.TestCheckResourceAttr(resourceName, "app_input_vars.1.variable_type", "string"), ), }, }, @@ -89,6 +89,13 @@ func TestAccWaypoint_ApplicationInputVariables_OnTemplate(t *testing.T) { testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), resource.TestCheckResourceAttr(resourceName, "app_input_vars.#", "0"), + resource.TestCheckResourceAttr(resourceName, "template_input_vars.#", "2"), + resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.name", "faction"), + resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.value", "brotherhood-of-steel"), + //resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.name", "vault_dweller_name"), + resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.value", "lone-wanderer"), + //resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.variable_type", "string"), ), }, }, @@ -296,9 +303,7 @@ resource "hcp_waypoint_application_template" "test_var_opts" { } resource "hcp_waypoint_application" "test_var_opts" { - name = "%s" + name = "%s" application_template_id = hcp_waypoint_application_template.test_var_opts.id - - app_input_vars = [] }`, tempName, appName) } From ef69d0356de1e47fd9674ee566db20649bd2c223 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Thu, 23 May 2024 19:13:57 -0400 Subject: [PATCH 19/32] waypoint: Update app tpl input var type to optional. The variable type for the template-configured input variables of an application is optional. This will remain true unless the API changes to always return a variable's type. --- internal/provider/waypoint/resource_waypoint_application.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index c32e537af..4f046f3f1 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -173,7 +173,7 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq Description: "Variable name", }, "variable_type": &schema.StringAttribute{ - Required: false, + Optional: true, Description: "Variable type", }, "value": &schema.StringAttribute{ From e7bff6941cf8ab2240f79866b33e9ecf747fdfdd Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Thu, 23 May 2024 20:49:12 -0400 Subject: [PATCH 20/32] waypoint: Fix waypoint_application data source input vars. The waypoint_application data source was updated to no longer use the `readInputs` method created to help with reading input vars for the waypoint_application resource. Instead it's handled in a loop in the data source's read function, because it doesn't split vars between app and tpl input vars. Tests were also updated accordingly here. --- .../data_source_waypoint_application.go | 45 ++++++++++++++++--- .../data_source_waypoint_application_test.go | 2 +- .../waypoint/resource_waypoint_application.go | 26 +++++++---- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application.go b/internal/provider/waypoint/data_source_waypoint_application.go index 3eebdfae2..d226f4ab3 100644 --- a/internal/provider/waypoint/data_source_waypoint_application.go +++ b/internal/provider/waypoint/data_source_waypoint_application.go @@ -14,7 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-hcp/internal/clients" ) @@ -81,7 +80,7 @@ func (d *DataSourceApplication) Schema(ctx context.Context, req datasource.Schem Computed: true, Description: "Internal Namespace ID.", }, - "input_vars": schema.ListNestedAttribute{ + "app_input_vars": schema.SetNestedAttribute{ Optional: true, Description: "Input variables for the Application.", NestedObject: schema.NestedAttributeObject{ @@ -121,8 +120,28 @@ func (d *DataSourceApplication) Configure(ctx context.Context, req datasource.Co d.client = client } +// ApplicationDataSourceModel describes the data source data model +type ApplicationDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ProjectID types.String `tfsdk:"project_id"` + OrgID types.String `tfsdk:"organization_id"` + ReadmeMarkdown types.String `tfsdk:"readme_markdown"` + ApplicationTemplateID types.String `tfsdk:"application_template_id"` + ApplicationTemplateName types.String `tfsdk:"application_template_name"` + NamespaceID types.String `tfsdk:"namespace_id"` + + // deferred for now + // Tags types.List `tfsdk:"tags"` + + // deferred and probably a list or objects, but may possible be a separate + // ActionCfgs types.List `tfsdk:"action_cfgs"` + + InputVars types.Set `tfsdk:"app_input_vars"` +} + func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data ApplicationResourceModel + var data ApplicationDataSourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) client := d.client @@ -174,10 +193,22 @@ func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadReq return } - resp.Diagnostics.Append(readInputs(ctx, inputVars, &data, nil)...) - if resp.Diagnostics.HasError() { - tflog.Error(ctx, "error reading application input variables") - return + if len(inputVars) > 0 { + aivls := make([]*InputVar, 0) + for _, iv := range inputVars { + if iv.Name != "waypoint_application" { + aivls = append(aivls, &InputVar{ + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), + }) + } + } + aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) + resp.Diagnostics.Append(diags...) + if diags.HasError() { + return + } + data.InputVars = aivs } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) diff --git a/internal/provider/waypoint/data_source_waypoint_application_test.go b/internal/provider/waypoint/data_source_waypoint_application_test.go index 4d7214a9d..02e9ed31f 100644 --- a/internal/provider/waypoint/data_source_waypoint_application_test.go +++ b/internal/provider/waypoint/data_source_waypoint_application_test.go @@ -70,7 +70,7 @@ func TestAccWaypoint_Application_DataSource_WithInputVars(t *testing.T) { Config: testDataApplicationWithInputVarsConfig(templateName, applicationName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(dataSourceName, "name", applicationName), - resource.TestCheckResourceAttr(dataSourceName, "input_vars.#", "2"), + resource.TestCheckResourceAttr(dataSourceName, "app_input_vars.#", "2"), ), }, }, diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 4f046f3f1..6c5c04019 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -54,7 +54,13 @@ type ApplicationResourceModel struct { // deferred and probably a list or objects, but may possible be a separate // ActionCfgs types.List `tfsdk:"action_cfgs"` - InputVars types.Set `tfsdk:"app_input_vars"` + InputVars types.Set `tfsdk:"app_input_vars"` + + // NOTE: At the time of writing this comment, TemplateInputVars is the only + // struct field that makes ApplicationResourceModel different from ApplicationDataSourceModel. + // One might see an opportunity here to use an embedded struct to avoid code duplication; + // however, this is not currently possible in the framework. See this issue for more details: + // https://github.com/hashicorp/terraform-plugin-framework/issues/242 TemplateInputVars types.Set `tfsdk:"template_input_vars"` } @@ -470,14 +476,16 @@ func readInputs( Value: types.StringValue(iv.Value), } - // if the variable isn't in the varTypes map, it's an input - // variable set by the template - if _, ok := varTypes[iv.Name]; ok { - inputVar.VariableType = types.StringValue(varTypes[iv.Name]) - aivls = append(aivls, inputVar) - } else { - inputVar.VariableType = types.StringNull() - tivls = append(tivls, inputVar) + if varTypes != nil { + // if the variable isn't in the varTypes map, it's an input + // variable set by the template + if _, ok := varTypes[iv.Name]; ok { + inputVar.VariableType = types.StringValue(varTypes[iv.Name]) + aivls = append(aivls, inputVar) + } else { + inputVar.VariableType = types.StringNull() + tivls = append(tivls, inputVar) + } } } } From a04c4752ddf062ba0317c6eced3636efa76e4603 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Thu, 23 May 2024 20:49:41 -0400 Subject: [PATCH 21/32] waypoint: Generate docs. --- docs/data-sources/waypoint_application.md | 6 +++--- docs/resources/waypoint_application.md | 20 +++++++++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/data-sources/waypoint_application.md b/docs/data-sources/waypoint_application.md index b73bb8147..73700737a 100644 --- a/docs/data-sources/waypoint_application.md +++ b/docs/data-sources/waypoint_application.md @@ -16,8 +16,8 @@ The Waypoint Application data source retrieves information on a given Applicatio ### Optional +- `app_input_vars` (Attributes Set) Input variables for the Application. (see [below for nested schema](#nestedatt--app_input_vars)) - `id` (String) The ID of the Application. -- `input_vars` (Attributes List) Input variables for the Application. (see [below for nested schema](#nestedatt--input_vars)) - `name` (String) The name of the Application. - `project_id` (String) The ID of the HCP project where the Waypoint Application is located. @@ -29,8 +29,8 @@ The Waypoint Application data source retrieves information on a given Applicatio - `organization_id` (String) The ID of the HCP organization where the Waypoint Application is located. - `readme_markdown` (String) Instructions for using the Application (markdown format supported). - -### Nested Schema for `input_vars` + +### Nested Schema for `app_input_vars` Read-Only: diff --git a/docs/resources/waypoint_application.md b/docs/resources/waypoint_application.md index 02435c463..fb333e116 100644 --- a/docs/resources/waypoint_application.md +++ b/docs/resources/waypoint_application.md @@ -21,7 +21,7 @@ The Waypoint Application resource managed the lifecycle of an Application that's ### Optional -- `input_vars` (Attributes List) Input variables for the Application. (see [below for nested schema](#nestedatt--input_vars)) +- `app_input_vars` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--app_input_vars)) - `project_id` (String) The ID of the HCP project where the Waypoint Application is located. - `readme_markdown` (String) Instructions for using the Application (markdown format supported). Note: this is a base64 encoded string, and can only be set in configuration after initial creation. The initial version of the README is generated from the README Template from source Application Template. @@ -31,12 +31,26 @@ The Waypoint Application resource managed the lifecycle of an Application that's - `id` (String) The ID of the Application. - `namespace_id` (String) Internal Namespace ID. - `organization_id` (String) The ID of the HCP organization where the Waypoint Application is located. +- `template_input_vars` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--template_input_vars)) - -### Nested Schema for `input_vars` + +### Nested Schema for `app_input_vars` Required: - `name` (String) Variable name - `value` (String) Variable value - `variable_type` (String) Variable type + + + +### Nested Schema for `template_input_vars` + +Required: + +- `name` (String) Variable name +- `value` (String) Variable value + +Optional: + +- `variable_type` (String) Variable type From 258f32aa40aca488a889b389881c61aac634f760 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Thu, 23 May 2024 20:56:21 -0400 Subject: [PATCH 22/32] fmt. --- .../provider/waypoint/resource_waypoint_application_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index 0d6020be1..de674c18a 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -92,10 +92,10 @@ func TestAccWaypoint_ApplicationInputVariables_OnTemplate(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "template_input_vars.#", "2"), resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.name", "faction"), resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.value", "brotherhood-of-steel"), - //resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.variable_type", "string"), + // resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.variable_type", "string"), resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.name", "vault_dweller_name"), resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.value", "lone-wanderer"), - //resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.variable_type", "string"), + // resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.variable_type", "string"), ), }, }, From da8034cada5b07321d43b50e81c24bf08924cff7 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:03:18 -0400 Subject: [PATCH 23/32] Rename Waypoint app data source input vars field. Co-authored-by: Clint --- internal/provider/waypoint/data_source_waypoint_application.go | 2 +- .../provider/waypoint/data_source_waypoint_application_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application.go b/internal/provider/waypoint/data_source_waypoint_application.go index d226f4ab3..17d8d6087 100644 --- a/internal/provider/waypoint/data_source_waypoint_application.go +++ b/internal/provider/waypoint/data_source_waypoint_application.go @@ -80,7 +80,7 @@ func (d *DataSourceApplication) Schema(ctx context.Context, req datasource.Schem Computed: true, Description: "Internal Namespace ID.", }, - "app_input_vars": schema.SetNestedAttribute{ + "input_vars": schema.SetNestedAttribute{ Optional: true, Description: "Input variables for the Application.", NestedObject: schema.NestedAttributeObject{ diff --git a/internal/provider/waypoint/data_source_waypoint_application_test.go b/internal/provider/waypoint/data_source_waypoint_application_test.go index 02e9ed31f..4d7214a9d 100644 --- a/internal/provider/waypoint/data_source_waypoint_application_test.go +++ b/internal/provider/waypoint/data_source_waypoint_application_test.go @@ -70,7 +70,7 @@ func TestAccWaypoint_Application_DataSource_WithInputVars(t *testing.T) { Config: testDataApplicationWithInputVarsConfig(templateName, applicationName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(dataSourceName, "name", applicationName), - resource.TestCheckResourceAttr(dataSourceName, "app_input_vars.#", "2"), + resource.TestCheckResourceAttr(dataSourceName, "input_vars.#", "2"), ), }, }, From cdca91b82746655864d850da19e6cf2a2989ad46 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:31:26 -0400 Subject: [PATCH 24/32] Set input vars to null set for data source. The set of input vars for the Waypoint application data source should be null if there are not any variables. Co-authored-by: Clint --- .../data_source_waypoint_application.go | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application.go b/internal/provider/waypoint/data_source_waypoint_application.go index 17d8d6087..84637a7fe 100644 --- a/internal/provider/waypoint/data_source_waypoint_application.go +++ b/internal/provider/waypoint/data_source_waypoint_application.go @@ -193,22 +193,24 @@ func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadReq return } - if len(inputVars) > 0 { - aivls := make([]*InputVar, 0) - for _, iv := range inputVars { - if iv.Name != "waypoint_application" { - aivls = append(aivls, &InputVar{ - Name: types.StringValue(iv.Name), - Value: types.StringValue(iv.Value), - }) - } + aivls := make([]*InputVar, 0) + for _, iv := range inputVars { + if iv.Name != "waypoint_application" { + aivls = append(aivls, &InputVar{ + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), + }) } + } + if len(aivls) > 0 { aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) resp.Diagnostics.Append(diags...) if diags.HasError() { return } data.InputVars = aivs + } else { + data.InputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) From 5b80ba7a40d5c0085f29388ae71b030da9ffa300 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:32:56 -0400 Subject: [PATCH 25/32] Regen Waypoint docs. --- docs/data-sources/waypoint_application.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data-sources/waypoint_application.md b/docs/data-sources/waypoint_application.md index 73700737a..1daf472b4 100644 --- a/docs/data-sources/waypoint_application.md +++ b/docs/data-sources/waypoint_application.md @@ -16,8 +16,8 @@ The Waypoint Application data source retrieves information on a given Applicatio ### Optional -- `app_input_vars` (Attributes Set) Input variables for the Application. (see [below for nested schema](#nestedatt--app_input_vars)) - `id` (String) The ID of the Application. +- `input_vars` (Attributes Set) Input variables for the Application. (see [below for nested schema](#nestedatt--input_vars)) - `name` (String) The name of the Application. - `project_id` (String) The ID of the HCP project where the Waypoint Application is located. @@ -29,8 +29,8 @@ The Waypoint Application data source retrieves information on a given Applicatio - `organization_id` (String) The ID of the HCP organization where the Waypoint Application is located. - `readme_markdown` (String) Instructions for using the Application (markdown format supported). - -### Nested Schema for `app_input_vars` + +### Nested Schema for `input_vars` Read-Only: From 8dad09e6db677d8af2bf3df37dfd88e58698cc9c Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:53:30 -0400 Subject: [PATCH 26/32] Update WP app to always check vars. Doing this is necessary for drift detection. Co-authored-by: Clint --- .../waypoint/resource_waypoint_application.go | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 6c5c04019..4bc77243d 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -465,50 +465,49 @@ func readInputs( ) diag.Diagnostics { var diags diag.Diagnostics - if inputVars != nil { - // make app an template input vars lists - aivls := make([]*InputVar, 0) - tivls := make([]*InputVar, 0) - for _, iv := range inputVars { - if iv.Name != "waypoint_application" { - inputVar := &InputVar{ - Name: types.StringValue(iv.Name), - Value: types.StringValue(iv.Value), - } + // make app template input vars lists + aivls := make([]*InputVar, 0) + tivls := make([]*InputVar, 0) + for _, iv := range inputVars { + if iv.Name != "waypoint_application" { + inputVar := &InputVar{ + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), + } - if varTypes != nil { - // if the variable isn't in the varTypes map, it's an input - // variable set by the template - if _, ok := varTypes[iv.Name]; ok { - inputVar.VariableType = types.StringValue(varTypes[iv.Name]) - aivls = append(aivls, inputVar) - } else { - inputVar.VariableType = types.StringNull() - tivls = append(tivls, inputVar) - } + if varTypes != nil { + // if the variable isn't in the varTypes map, it's an input + // variable set by the template + if _, ok := varTypes[iv.Name]; ok { + inputVar.VariableType = types.StringValue(varTypes[iv.Name]) + aivls = append(aivls, inputVar) + } else { + inputVar.VariableType = types.StringNull() + tivls = append(tivls, inputVar) } } } - if len(aivls) > 0 { - aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) - if diags.HasError() { - return diags - } - plan.InputVars = aivs - } else { - plan.InputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) + } + if len(aivls) > 0 { + aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) + if diags.HasError() { + return diags } + plan.InputVars = aivs + } else { + plan.InputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) + } - if len(tivls) > 0 { - tivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, tivls) - if diags.HasError() { - return diags - } - plan.TemplateInputVars = tivs - } else { - plan.TemplateInputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) + if len(tivls) > 0 { + tivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, tivls) + if diags.HasError() { + return diags } + plan.TemplateInputVars = tivs + } else { + plan.TemplateInputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } + return diags } From 3532533cd12747640965d2780fc930125145367e Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:59:14 -0400 Subject: [PATCH 27/32] wp: Apply changes from code review. Co-authored-by: Clint --- .../data_source_waypoint_application.go | 20 ++- .../waypoint/resource_waypoint_application.go | 128 ++++++++++-------- .../resource_waypoint_application_test.go | 32 ++--- 3 files changed, 95 insertions(+), 85 deletions(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application.go b/internal/provider/waypoint/data_source_waypoint_application.go index 84637a7fe..0711a39b3 100644 --- a/internal/provider/waypoint/data_source_waypoint_application.go +++ b/internal/provider/waypoint/data_source_waypoint_application.go @@ -80,7 +80,7 @@ func (d *DataSourceApplication) Schema(ctx context.Context, req datasource.Schem Computed: true, Description: "Internal Namespace ID.", }, - "input_vars": schema.SetNestedAttribute{ + "input_variables": schema.SetNestedAttribute{ Optional: true, Description: "Input variables for the Application.", NestedObject: schema.NestedAttributeObject{ @@ -137,7 +137,7 @@ type ApplicationDataSourceModel struct { // deferred and probably a list or objects, but may possible be a separate // ActionCfgs types.List `tfsdk:"action_cfgs"` - InputVars types.Set `tfsdk:"app_input_vars"` + InputVars types.Set `tfsdk:"input_variables"` } func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { @@ -193,17 +193,15 @@ func (d *DataSourceApplication) Read(ctx context.Context, req datasource.ReadReq return } - aivls := make([]*InputVar, 0) + inputVariables := make([]*InputVar, 0) for _, iv := range inputVars { - if iv.Name != "waypoint_application" { - aivls = append(aivls, &InputVar{ - Name: types.StringValue(iv.Name), - Value: types.StringValue(iv.Value), - }) - } + inputVariables = append(inputVariables, &InputVar{ + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), + }) } - if len(aivls) > 0 { - aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) + if len(inputVariables) > 0 { + aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, inputVariables) resp.Diagnostics.Append(diags...) if diags.HasError() { return diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 4bc77243d..992ca6f37 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/client/waypoint_service" waypoint_models "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/models" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -54,14 +53,14 @@ type ApplicationResourceModel struct { // deferred and probably a list or objects, but may possible be a separate // ActionCfgs types.List `tfsdk:"action_cfgs"` - InputVars types.Set `tfsdk:"app_input_vars"` + InputVars types.Set `tfsdk:"application_input_variables"` // NOTE: At the time of writing this comment, TemplateInputVars is the only // struct field that makes ApplicationResourceModel different from ApplicationDataSourceModel. // One might see an opportunity here to use an embedded struct to avoid code duplication; // however, this is not currently possible in the framework. See this issue for more details: // https://github.com/hashicorp/terraform-plugin-framework/issues/242 - TemplateInputVars types.Set `tfsdk:"template_input_vars"` + TemplateInputVars types.Set `tfsdk:"template_input_variables"` } type InputVar struct { @@ -149,7 +148,7 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq stringplanmodifier.UseStateForUnknown(), }, }, - "app_input_vars": schema.SetNestedAttribute{ + "applicatio_input_variables": schema.SetNestedAttribute{ Optional: true, Description: "Input variables set for the application.", NestedObject: schema.NestedAttributeObject{ @@ -169,7 +168,7 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq }, }, }, - "template_input_vars": schema.SetNestedAttribute{ + "template_input_variables": schema.SetNestedAttribute{ Computed: true, Description: "Input variables set for the application.", NestedObject: schema.NestedAttributeObject{ @@ -279,9 +278,15 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq // to be used later when fetching the input variables from the API varTypes := map[string]string{} + // TODO: Try ElementsAs instead of value conversion // Prepare the input variables that the user provided to the application // creation request ivs := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) + diags := plan.InputVars.ElementsAs(ctx, &InputVar{}, false) + if diags.HasError() { + return + } + for _, v := range plan.InputVars.Elements() { // convert list element to a struct representing an input variable, of // type varConverter @@ -357,10 +362,25 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq return } - resp.Diagnostics.Append(readInputs(ctx, inputVars, plan, varTypes)...) - if resp.Diagnostics.HasError() { - tflog.Error(ctx, "error reading application input variables") - return + applicationInputVars, templateInputVars := splitInputs(inputVars, varTypes) + if len(applicationInputVars) > 0 { + aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, applicationInputVars) + if diags.HasError() { + return + } + plan.InputVars = aivs + } else { + plan.InputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) + } + + if len(templateInputVars) > 0 { + tivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, templateInputVars) + if diags.HasError() { + return + } + plan.TemplateInputVars = tivs + } else { + plan.TemplateInputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } // Write logs using the tflog package @@ -447,68 +467,60 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest return } - resp.Diagnostics.Append(readInputs(ctx, inputVars, data, varTypes)...) - if resp.Diagnostics.HasError() { - tflog.Error(ctx, "error reading application input variables") - return + applicationInputVars, templateInputVars := splitInputs(inputVars, varTypes) + if len(applicationInputVars) > 0 { + aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, applicationInputVars) + if diags.HasError() { + return + } + data.InputVars = aivs + } else { + data.InputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } + if len(templateInputVars) > 0 { + tivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, templateInputVars) + if diags.HasError() { + return + } + data.TemplateInputVars = tivs + } else { + data.TemplateInputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) + } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -// readInputs reads the inputVars for an app into the application resource model. -func readInputs( - ctx context.Context, +// splitInputs separates the input variables into two lists: application input +// variables and template input variables. The application input variables are +// those that are set by the user when creating the application, and the +// template input variables are those that are set by the template or by HCP +// Waypoint. +func splitInputs( inputVars []*waypoint_models.HashicorpCloudWaypointInputVariable, - plan *ApplicationResourceModel, varTypes map[string]string, -) diag.Diagnostics { - var diags diag.Diagnostics - - // make app template input vars lists - aivls := make([]*InputVar, 0) - tivls := make([]*InputVar, 0) +) ([]*InputVar, []*InputVar) { + applicationInputVars := make([]*InputVar, 0) + templateInputVars := make([]*InputVar, 0) for _, iv := range inputVars { - if iv.Name != "waypoint_application" { - inputVar := &InputVar{ - Name: types.StringValue(iv.Name), - Value: types.StringValue(iv.Value), - } - - if varTypes != nil { - // if the variable isn't in the varTypes map, it's an input - // variable set by the template - if _, ok := varTypes[iv.Name]; ok { - inputVar.VariableType = types.StringValue(varTypes[iv.Name]) - aivls = append(aivls, inputVar) - } else { - inputVar.VariableType = types.StringNull() - tivls = append(tivls, inputVar) - } - } + inputVar := &InputVar{ + Name: types.StringValue(iv.Name), + Value: types.StringValue(iv.Value), } - } - if len(aivls) > 0 { - aivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, aivls) - if diags.HasError() { - return diags - } - plan.InputVars = aivs - } else { - plan.InputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) - } - if len(tivls) > 0 { - tivs, diags := types.SetValueFrom(ctx, types.ObjectType{AttrTypes: InputVar{}.attrTypes()}, tivls) - if diags.HasError() { - return diags + if varTypes != nil { + // if the variable isn't in the varTypes map, it's an input + // variable set by the template, or set by HCP Waypoint + if _, ok := varTypes[iv.Name]; ok { + inputVar.VariableType = types.StringValue(varTypes[iv.Name]) + applicationInputVars = append(applicationInputVars, inputVar) + } else { + inputVar.VariableType = types.StringNull() + templateInputVars = append(templateInputVars, inputVar) + } } - plan.TemplateInputVars = tivs - } else { - plan.TemplateInputVars = types.SetNull(types.ObjectType{AttrTypes: InputVar{}.attrTypes()}) } - return diags + return applicationInputVars, templateInputVars } func (r *ApplicationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index de674c18a..cebb89e40 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -57,13 +57,13 @@ func TestAccWaypoint_ApplicationInputVariables(t *testing.T) { testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), - resource.TestCheckResourceAttr(resourceName, "app_input_vars.#", "2"), - resource.TestCheckResourceAttr(resourceName, "app_input_vars.0.name", "faction"), - resource.TestCheckResourceAttr(resourceName, "app_input_vars.0.value", "brotherhood-of-steel"), - resource.TestCheckResourceAttr(resourceName, "app_input_vars.0.variable_type", "string"), - resource.TestCheckResourceAttr(resourceName, "app_input_vars.1.name", "vault_dweller_name"), - resource.TestCheckResourceAttr(resourceName, "app_input_vars.1.value", "courier"), - resource.TestCheckResourceAttr(resourceName, "app_input_vars.1.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "application_input_variables.#", "2"), + resource.TestCheckResourceAttr(resourceName, "application_input_variables.0.name", "faction"), + resource.TestCheckResourceAttr(resourceName, "application_input_variables.0.value", "brotherhood-of-steel"), + resource.TestCheckResourceAttr(resourceName, "application_input_variables.0.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "application_input_variables.1.name", "vault_dweller_name"), + resource.TestCheckResourceAttr(resourceName, "application_input_variables.1.value", "courier"), + resource.TestCheckResourceAttr(resourceName, "application_input_variables.1.variable_type", "string"), ), }, }, @@ -88,14 +88,14 @@ func TestAccWaypoint_ApplicationInputVariables_OnTemplate(t *testing.T) { testAccCheckWaypointApplicationExists(t, resourceName, &applicationModel), testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), - resource.TestCheckResourceAttr(resourceName, "app_input_vars.#", "0"), - resource.TestCheckResourceAttr(resourceName, "template_input_vars.#", "2"), - resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.name", "faction"), - resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.value", "brotherhood-of-steel"), - // resource.TestCheckResourceAttr(resourceName, "template_input_vars.0.variable_type", "string"), - resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.name", "vault_dweller_name"), - resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.value", "lone-wanderer"), - // resource.TestCheckResourceAttr(resourceName, "template_input_vars.1.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "application_input_variables.#", "0"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.#", "2"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.0.name", "faction"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.0.value", "brotherhood-of-steel"), + // resource.TestCheckResourceAttr(resourceName, "template_input_variables.0.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.1.name", "vault_dweller_name"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.1.value", "lone-wanderer"), + // resource.TestCheckResourceAttr(resourceName, "template_input_variables.1.variable_type", "string"), ), }, }, @@ -252,7 +252,7 @@ resource "hcp_waypoint_application" "test_var_opts" { name = "%s" application_template_id = hcp_waypoint_application_template.test_var_opts.id - app_input_vars = [ + application_input_variables = [ { name = "faction" variable_type = "string" From da641554c572094cfc45fb42dbeb36327cc92b60 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:00:11 -0400 Subject: [PATCH 28/32] wp: Regen docs. --- docs/data-sources/waypoint_application.md | 6 +++--- docs/resources/waypoint_application.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/data-sources/waypoint_application.md b/docs/data-sources/waypoint_application.md index 1daf472b4..948af6718 100644 --- a/docs/data-sources/waypoint_application.md +++ b/docs/data-sources/waypoint_application.md @@ -17,7 +17,7 @@ The Waypoint Application data source retrieves information on a given Applicatio ### Optional - `id` (String) The ID of the Application. -- `input_vars` (Attributes Set) Input variables for the Application. (see [below for nested schema](#nestedatt--input_vars)) +- `input_variables` (Attributes Set) Input variables for the Application. (see [below for nested schema](#nestedatt--input_variables)) - `name` (String) The name of the Application. - `project_id` (String) The ID of the HCP project where the Waypoint Application is located. @@ -29,8 +29,8 @@ The Waypoint Application data source retrieves information on a given Applicatio - `organization_id` (String) The ID of the HCP organization where the Waypoint Application is located. - `readme_markdown` (String) Instructions for using the Application (markdown format supported). - -### Nested Schema for `input_vars` + +### Nested Schema for `input_variables` Read-Only: diff --git a/docs/resources/waypoint_application.md b/docs/resources/waypoint_application.md index fb333e116..8d3a3c7a7 100644 --- a/docs/resources/waypoint_application.md +++ b/docs/resources/waypoint_application.md @@ -21,7 +21,7 @@ The Waypoint Application resource managed the lifecycle of an Application that's ### Optional -- `app_input_vars` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--app_input_vars)) +- `applicatio_input_variables` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--applicatio_input_variables)) - `project_id` (String) The ID of the HCP project where the Waypoint Application is located. - `readme_markdown` (String) Instructions for using the Application (markdown format supported). Note: this is a base64 encoded string, and can only be set in configuration after initial creation. The initial version of the README is generated from the README Template from source Application Template. @@ -31,10 +31,10 @@ The Waypoint Application resource managed the lifecycle of an Application that's - `id` (String) The ID of the Application. - `namespace_id` (String) Internal Namespace ID. - `organization_id` (String) The ID of the HCP organization where the Waypoint Application is located. -- `template_input_vars` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--template_input_vars)) +- `template_input_variables` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--template_input_variables)) - -### Nested Schema for `app_input_vars` + +### Nested Schema for `applicatio_input_variables` Required: @@ -43,8 +43,8 @@ Required: - `variable_type` (String) Variable type - -### Nested Schema for `template_input_vars` + +### Nested Schema for `template_input_variables` Required: From a13be2edc7e2d1aff33e2d9542245e0b99f67723 Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:01:14 -0400 Subject: [PATCH 29/32] wp: Use ElementsAs for value conversion. This is simpler than implementing the value converter interface. --- .../waypoint/resource_waypoint_application.go | 90 ++++--------------- 1 file changed, 16 insertions(+), 74 deletions(-) diff --git a/internal/provider/waypoint/resource_waypoint_application.go b/internal/provider/waypoint/resource_waypoint_application.go index 992ca6f37..45e44c1a1 100644 --- a/internal/provider/waypoint/resource_waypoint_application.go +++ b/internal/provider/waypoint/resource_waypoint_application.go @@ -18,7 +18,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-provider-hcp/internal/clients" ) @@ -148,7 +147,7 @@ func (r *ApplicationResource) Schema(ctx context.Context, req resource.SchemaReq stringplanmodifier.UseStateForUnknown(), }, }, - "applicatio_input_variables": schema.SetNestedAttribute{ + "application_input_variables": schema.SetNestedAttribute{ Optional: true, Description: "Input variables set for the application.", NestedObject: schema.NestedAttributeObject{ @@ -211,38 +210,6 @@ func (r *ApplicationResource) Configure(ctx context.Context, req resource.Config r.client = client } -type varConverter struct { - name string - variableType string - value string -} - -// FromTerraform5Value implements the ValueConverter interface -func (vc *varConverter) FromTerraform5Value(val tftypes.Value) error { - v := map[string]tftypes.Value{} - err := val.As(&v) - if err != nil { - return err - } - - err = v["name"].As(&vc.name) - if err != nil { - return err - } - - err = v["value"].As(&vc.value) - if err != nil { - return err - } - - err = v["variable_type"].As(&vc.variableType) - if err != nil { - return err - } - - return nil -} - func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan *ApplicationResourceModel @@ -278,41 +245,26 @@ func (r *ApplicationResource) Create(ctx context.Context, req resource.CreateReq // to be used later when fetching the input variables from the API varTypes := map[string]string{} - // TODO: Try ElementsAs instead of value conversion // Prepare the input variables that the user provided to the application // creation request ivs := make([]*waypoint_models.HashicorpCloudWaypointInputVariable, 0) - diags := plan.InputVars.ElementsAs(ctx, &InputVar{}, false) + + inputVarsSlice := []InputVar{} + diags := plan.InputVars.ElementsAs(ctx, &inputVarsSlice, false) if diags.HasError() { return } - - for _, v := range plan.InputVars.Elements() { - // convert list element to a struct representing an input variable, of - // type varConverter - var iv tftypes.Value - iv, err = v.ToTerraformValue(ctx) - if err != nil { - tflog.Error(ctx, "error reading application input variables") - return - } - vc := varConverter{} - err = iv.As(&vc) - if err != nil { - tflog.Error(ctx, "error reading application input variables") - return - } - + for _, v := range inputVarsSlice { // add the input variable to the list of input variables for the app // creation API call ivs = append(ivs, &waypoint_models.HashicorpCloudWaypointInputVariable{ - Name: vc.name, - Value: vc.value, - VariableType: vc.variableType, + Name: v.Name.ValueString(), + Value: v.Value.ValueString(), + VariableType: v.VariableType.ValueString(), }) // store var type for later use when fetching the input variables from the API - varTypes[vc.name] = vc.variableType + varTypes[v.Name.ValueString()] = v.VariableType.ValueString() } modelBody := &waypoint_models.HashicorpCloudWaypointWaypointServiceCreateApplicationFromTemplateBody{ @@ -415,24 +367,14 @@ func (r *ApplicationResource) Read(ctx context.Context, req resource.ReadRequest // varTypes is used to store the variable type for each input variable // to be used later when fetching the input variables from the API varTypes := map[string]string{} - - for _, v := range data.InputVars.Elements() { - // convert list element to a struct representing an input variable, of - // type varConverter - iv, err := v.ToTerraformValue(ctx) - if err != nil { - tflog.Error(ctx, "error reading application input variables") - return - } - vc := varConverter{} - err = iv.As(&vc) - if err != nil { - tflog.Error(ctx, "error reading application input variables") - return - } - + inputVarsSlice := []InputVar{} + diags := data.InputVars.ElementsAs(ctx, &inputVarsSlice, false) + if diags.HasError() { + return + } + for _, v := range inputVarsSlice { // store var type for later use when fetching the input variables from the API - varTypes[vc.name] = vc.variableType + varTypes[v.Name.ValueString()] = v.VariableType.ValueString() } client := r.client From 440b5179806e65e08fe70acf943790d07dc2363e Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:14:00 -0400 Subject: [PATCH 30/32] wp: Fix docs typo. --- docs/resources/waypoint_application.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/resources/waypoint_application.md b/docs/resources/waypoint_application.md index 8d3a3c7a7..22b6345e9 100644 --- a/docs/resources/waypoint_application.md +++ b/docs/resources/waypoint_application.md @@ -21,7 +21,7 @@ The Waypoint Application resource managed the lifecycle of an Application that's ### Optional -- `applicatio_input_variables` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--applicatio_input_variables)) +- `application_input_variables` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--application_input_variables)) - `project_id` (String) The ID of the HCP project where the Waypoint Application is located. - `readme_markdown` (String) Instructions for using the Application (markdown format supported). Note: this is a base64 encoded string, and can only be set in configuration after initial creation. The initial version of the README is generated from the README Template from source Application Template. @@ -33,8 +33,8 @@ The Waypoint Application resource managed the lifecycle of an Application that's - `organization_id` (String) The ID of the HCP organization where the Waypoint Application is located. - `template_input_variables` (Attributes Set) Input variables set for the application. (see [below for nested schema](#nestedatt--template_input_variables)) - -### Nested Schema for `applicatio_input_variables` + +### Nested Schema for `application_input_variables` Required: From 6d823c5465c4ed9ab0f8073f7ebdd5d9649ec6fa Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:24:17 -0400 Subject: [PATCH 31/32] wp: Fix data source vars test. Typo in new field name "input_variables" was fixed. The # of variables also has changed since `waypoint_application` is counted among the variables. --- .../provider/waypoint/data_source_waypoint_application_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/waypoint/data_source_waypoint_application_test.go b/internal/provider/waypoint/data_source_waypoint_application_test.go index 4d7214a9d..0cae3723e 100644 --- a/internal/provider/waypoint/data_source_waypoint_application_test.go +++ b/internal/provider/waypoint/data_source_waypoint_application_test.go @@ -70,7 +70,7 @@ func TestAccWaypoint_Application_DataSource_WithInputVars(t *testing.T) { Config: testDataApplicationWithInputVarsConfig(templateName, applicationName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(dataSourceName, "name", applicationName), - resource.TestCheckResourceAttr(dataSourceName, "input_vars.#", "2"), + resource.TestCheckResourceAttr(dataSourceName, "input_variables.#", "3"), ), }, }, From 470d0789023c1d4d5619a0e4d19a79668c592fdc Mon Sep 17 00:00:00 2001 From: paladin-devops <83741749+paladin-devops@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:28:28 -0400 Subject: [PATCH 32/32] wp: Fix app vars test. The # of variables has changed since `waypoint_application` is counted among the variables. --- .../provider/waypoint/resource_waypoint_application_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/provider/waypoint/resource_waypoint_application_test.go b/internal/provider/waypoint/resource_waypoint_application_test.go index cebb89e40..76bfb3c4c 100644 --- a/internal/provider/waypoint/resource_waypoint_application_test.go +++ b/internal/provider/waypoint/resource_waypoint_application_test.go @@ -64,6 +64,8 @@ func TestAccWaypoint_ApplicationInputVariables(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "application_input_variables.1.name", "vault_dweller_name"), resource.TestCheckResourceAttr(resourceName, "application_input_variables.1.value", "courier"), resource.TestCheckResourceAttr(resourceName, "application_input_variables.1.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.#", "1"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.0.name", "waypoint_application"), ), }, }, @@ -89,13 +91,15 @@ func TestAccWaypoint_ApplicationInputVariables_OnTemplate(t *testing.T) { testAccCheckWaypointApplicationName(t, &applicationModel, applicationName), resource.TestCheckResourceAttr(resourceName, "name", applicationName), resource.TestCheckResourceAttr(resourceName, "application_input_variables.#", "0"), - resource.TestCheckResourceAttr(resourceName, "template_input_variables.#", "2"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.#", "3"), resource.TestCheckResourceAttr(resourceName, "template_input_variables.0.name", "faction"), resource.TestCheckResourceAttr(resourceName, "template_input_variables.0.value", "brotherhood-of-steel"), // resource.TestCheckResourceAttr(resourceName, "template_input_variables.0.variable_type", "string"), resource.TestCheckResourceAttr(resourceName, "template_input_variables.1.name", "vault_dweller_name"), resource.TestCheckResourceAttr(resourceName, "template_input_variables.1.value", "lone-wanderer"), // resource.TestCheckResourceAttr(resourceName, "template_input_variables.1.variable_type", "string"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.2.name", "waypoint_application"), + resource.TestCheckResourceAttr(resourceName, "template_input_variables.2.value", applicationName), ), }, },