diff --git a/.changelog/1304.txt b/.changelog/1304.txt new file mode 100644 index 000000000..8c63a7896 --- /dev/null +++ b/.changelog/1304.txt @@ -0,0 +1,3 @@ +```release-note:feature +Add support for agent actions in HCP Waypoint +``` \ No newline at end of file diff --git a/docs/data-sources/waypoint_action.md b/docs/data-sources/waypoint_action.md index e9323e350..d469a4c95 100644 --- a/docs/data-sources/waypoint_action.md +++ b/docs/data-sources/waypoint_action.md @@ -7,8 +7,6 @@ description: |- # hcp_waypoint_action `Data Source` --> **Note:** HCP Waypoint actions is currently in beta. - The Waypoint Action data source retrieves information on a given Action. @@ -29,10 +27,28 @@ The Waypoint Action data source retrieves information on a given Action. ### Nested Schema for `request` +Optional: + +- `agent` (Attributes) Agent mode allows users to define the agent to use for the request. (see [below for nested schema](#nestedatt--request--agent)) + Read-Only: - `custom` (Attributes) Custom mode allows users to define the HTTP method, the request body, etc. (see [below for nested schema](#nestedatt--request--custom)) + +### Nested Schema for `request.agent` + +Required: + +- `group` (String) The name of the group that the operation is in. +- `operation_id` (String) The identifying name of the operation in the agent config file. + +Optional: + +- `action_run_id` (String) An optional action run id. If specified the agent will interact with the actions subsystem. +- `body` (String) Arguments to the operation, specified as JSON. + + ### Nested Schema for `request.custom` diff --git a/docs/data-sources/waypoint_agent_group.md b/docs/data-sources/waypoint_agent_group.md new file mode 100644 index 000000000..30c229502 --- /dev/null +++ b/docs/data-sources/waypoint_agent_group.md @@ -0,0 +1,23 @@ +--- +page_title: "hcp_waypoint_agent_group Data Source - terraform-provider-hcp" +subcategory: "HCP Waypoint" +description: |- + The Waypoint Agent Group resource manages the lifecycle of an Agent Group. +--- + +# hcp_waypoint_agent_group `Data Source` + +The Waypoint Agent Group resource manages the lifecycle of an Agent Group. + + +## Schema + +### Required + +- `name` (String) The name of the Agent Group. + +### Optional + +- `description` (String) A description of the Agent Group. +- `organization_id` (String) The ID of the Waypoint organization to which the Agent Group belongs. +- `project_id` (String) The ID of the Waypoint project to which the Agent Group belongs. \ No newline at end of file diff --git a/docs/resources/waypoint_action.md b/docs/resources/waypoint_action.md index 3af7859b4..8ae595fc1 100644 --- a/docs/resources/waypoint_action.md +++ b/docs/resources/waypoint_action.md @@ -7,8 +7,6 @@ description: |- # hcp_waypoint_action `Resource` --> **Note:** HCP Waypoint actions is currently in beta. - The Waypoint Action resource manages the lifecycle of an Action. @@ -34,8 +32,23 @@ The Waypoint Action resource manages the lifecycle of an Action. Optional: +- `agent` (Attributes) Agent mode allows users to define the agent to use for the request. (see [below for nested schema](#nestedatt--request--agent)) - `custom` (Attributes) Custom mode allows users to define the HTTP method, the request body, etc. (see [below for nested schema](#nestedatt--request--custom)) + +### Nested Schema for `request.agent` + +Required: + +- `group` (String) The name of the group that the operation is in. +- `operation_id` (String) The identifying name of the operation in the agent config file. + +Optional: + +- `action_run_id` (String) An optional action run id. If specified the agent will interact with the actions subsystem. +- `body` (String) Arguments to the operation, specified as JSON. + + ### Nested Schema for `request.custom` diff --git a/docs/resources/waypoint_agent_group.md b/docs/resources/waypoint_agent_group.md new file mode 100644 index 000000000..7d246dbbf --- /dev/null +++ b/docs/resources/waypoint_agent_group.md @@ -0,0 +1,23 @@ +--- +page_title: "hcp_waypoint_agent_group Resource - terraform-provider-hcp" +subcategory: "HCP Waypoint" +description: |- + The Waypoint Agent Group resource manages the lifecycle of an Agent Group. +--- + +# hcp_waypoint_agent_group `Resource` + +The Waypoint Agent Group resource manages the lifecycle of an Agent Group. + + +## Schema + +### Required + +- `name` (String) The name of the Agent Group. + +### Optional + +- `description` (String) A description of the Agent Group. +- `organization_id` (String) The ID of the Waypoint organization to which the Agent Group belongs. +- `project_id` (String) The ID of the Waypoint project to which the Agent Group belongs. \ No newline at end of file diff --git a/internal/clients/waypoint.go b/internal/clients/waypoint.go index b76aa0261..9eca4718a 100644 --- a/internal/clients/waypoint.go +++ b/internal/clients/waypoint.go @@ -28,6 +28,21 @@ func GetAction(ctx context.Context, client *Client, loc *sharedmodels.HashicorpC return getResp.GetPayload().ActionConfig, nil } +// GetAgentGroup will retrieve an Agent Group by name +func GetAgentGroup(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, groupName string) (*waypoint_models.HashicorpCloudWaypointV20241122AgentGroup, error) { + params := &waypoint_service.WaypointServiceGetAgentGroupParams{ + Name: groupName, + NamespaceLocationOrganizationID: loc.OrganizationID, + NamespaceLocationProjectID: loc.ProjectID, + } + + getResp, err := client.Waypoint.WaypointServiceGetAgentGroup(params, nil) + if err != nil { + return nil, err + } + return getResp.GetPayload().Group, nil +} + // GetApplicationTemplateByName will retrieve a template by name func GetApplicationTemplateByName(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName string) (*waypoint_models.HashicorpCloudWaypointV20241122ApplicationTemplate, error) { params := &waypoint_service.WaypointServiceGetApplicationTemplate2Params{ diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e5bcbe272..92d14ab7a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -175,6 +175,7 @@ func (p *ProviderFramework) Resources(ctx context.Context) []func() resource.Res webhook.NewNotificationsWebhookResource, // Waypoint waypoint.NewActionResource, + waypoint.NewAgentGroupResource, waypoint.NewApplicationResource, waypoint.NewTemplateResource, waypoint.NewAddOnResource, @@ -207,6 +208,7 @@ func (p *ProviderFramework) DataSources(ctx context.Context) []func() datasource iam.NewUserPrincipalDataSource, // Waypoint waypoint.NewActionDataSource, + waypoint.NewAgentGroupDataSource, waypoint.NewApplicationDataSource, waypoint.NewTemplateDataSource, waypoint.NewAddOnDataSource, diff --git a/internal/provider/waypoint/data_source_waypoint_action.go b/internal/provider/waypoint/data_source_waypoint_action.go index 27d0383f8..2e11fd707 100644 --- a/internal/provider/waypoint/data_source_waypoint_action.go +++ b/internal/provider/waypoint/data_source_waypoint_action.go @@ -96,6 +96,28 @@ func (d *DataSourceAction) Schema(ctx context.Context, req datasource.SchemaRequ }, }, }, + "agent": schema.SingleNestedAttribute{ + Description: "Agent mode allows users to define the agent to use for the request.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "operation_id": schema.StringAttribute{ + Description: "The identifying name of the operation in the agent config file.", + Required: true, + }, + "body": schema.StringAttribute{ + Description: "Arguments to the operation, specified as JSON.", + Optional: true, + }, + "action_run_id": schema.StringAttribute{ + Description: "An optional action run id. If specified the agent will interact with the actions subsystem.", + Optional: true, + }, + "group": schema.StringAttribute{ + Description: "The name of the group that the operation is in.", + Required: true, + }, + }, + }, }, }, }, @@ -119,7 +141,7 @@ func (d *DataSourceAction) Configure(ctx context.Context, req datasource.Configu } func (d *DataSourceAction) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data ActionResourceModel + var data *ActionResourceModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -159,36 +181,24 @@ func (d *DataSourceAction) Read(ctx context.Context, req datasource.ReadRequest, data.ProjectID = types.StringValue(client.Config.ProjectID) data.Request = &actionRequest{} - headerMap := make(map[string]string) var diags diag.Diagnostics // In the future, expand this to accommodate other types of requests - data.Request.Custom = &customRequest{} - if actionModel.Request.Custom.Method != nil { - data.Request.Custom.Method = types.StringValue(string(*actionModel.Request.Custom.Method)) - } else { - data.Request.Custom.Method = types.StringNull() - } - if actionModel.Request.Custom.Headers != nil { - for _, header := range actionModel.Request.Custom.Headers { - headerMap[header.Key] = header.Value + if actionModel.Request.Custom != nil { + diags = readCustomAction(ctx, data, actionModel) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return } - if len(headerMap) > 0 { - data.Request.Custom.Headers, diags = types.MapValueFrom(ctx, types.StringType, headerMap) + } else if actionModel.Request.Agent != nil { + diags = readAgentAction(ctx, data, actionModel) + if diags.HasError() { resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - } else { - data.Request.Custom.Headers = types.MapNull(types.StringType) + return } - } else { - data.Request.Custom.Headers = types.MapNull(types.StringType) } - data.Request.Custom.URL = types.StringValue(actionModel.Request.Custom.URL) - data.Request.Custom.Body = types.StringValue(actionModel.Request.Custom.Body) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } diff --git a/internal/provider/waypoint/data_source_waypoint_action_test.go b/internal/provider/waypoint/data_source_waypoint_action_test.go index 7a5c4a0db..5fcc9c6e7 100644 --- a/internal/provider/waypoint/data_source_waypoint_action_test.go +++ b/internal/provider/waypoint/data_source_waypoint_action_test.go @@ -14,7 +14,6 @@ import ( ) func TestAcc_Waypoint_Action_DataSource_basic(t *testing.T) { - t.Parallel() // Skip this test unless the appropriate environment variable is set // This is to prevent running this test by default @@ -51,10 +50,53 @@ func TestAcc_Waypoint_Action_DataSource_basic(t *testing.T) { }) } +func TestAcc_Waypoint_Action_DataSource_Agent(t *testing.T) { + + // Skip this test unless the appropriate environment variable is set + // This is to prevent running this test by default + if os.Getenv("HCP_WAYP_ACTION_TEST") == "" { + t.Skipf("Waypoint Action tests skipped unless env '%s' set", + "HCP_WAYP_ACTION_TEST") + return + } + var actionModel waypoint.ActionResourceModel + resourceName := "hcp_waypoint_action.test_agent" + dataSourceName := "data." + resourceName + actionName := generateRandomName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointActionDestroy(t, &actionModel), + Steps: []resource.TestStep{ + { + // establish the base action config with agent + Config: testAgentAction(actionName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointActionExists(t, resourceName, &actionModel), + ), + }, + { + // add a data source config to read the action config with agent + Config: testDataActionConfigAgent(actionName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "name", actionName), + ), + }, + }, + }) +} + func testDataActionConfig(actionName string) string { return fmt.Sprintf(`%s - data "hcp_waypoint_action" "test" { name = hcp_waypoint_action.test.name }`, testAction(actionName)) } + +func testDataActionConfigAgent(actionName string) string { + return fmt.Sprintf(`%s +data "hcp_waypoint_action" "test_agent" { + name = hcp_waypoint_action.test_agent.name +}`, testAgentAction(actionName)) +} diff --git a/internal/provider/waypoint/data_source_waypoint_agent_group.go b/internal/provider/waypoint/data_source_waypoint_agent_group.go new file mode 100644 index 000000000..491f60097 --- /dev/null +++ b/internal/provider/waypoint/data_source_waypoint_agent_group.go @@ -0,0 +1,146 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package waypoint + +import ( + "context" + "fmt" + + sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "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" +) + +var _ datasource.DataSource = &DataSourceAgentGroup{} +var _ datasource.DataSourceWithConfigValidators = &DataSourceAgentGroup{} + +func (d DataSourceAgentGroup) ConfigValidators(ctx context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.AtLeastOneOf( + path.MatchRoot("name"), + ), + } +} + +type DataSourceAgentGroup struct { + client *clients.Client +} + +func NewAgentGroupDataSource() datasource.DataSource { + return &DataSourceAgentGroup{} +} + +func (d *DataSourceAgentGroup) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_waypoint_agent_group" +} + +func (d *DataSourceAgentGroup) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "The Waypoint Agent Group resource manages the lifecycle of an Agent Group.", + + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The name of the Agent Group.", + Required: true, + }, + "project_id": schema.StringAttribute{ + Description: "The ID of the Waypoint project to which the Agent Group belongs.", + Computed: true, + Optional: true, + }, + "organization_id": schema.StringAttribute{ + Description: "The ID of the Waypoint organization to which the Agent Group belongs.", + Computed: true, + Optional: true, + }, + "description": schema.StringAttribute{ + Description: "A description of the Agent Group.", + Optional: true, + }, + }, + } +} + +func (d *DataSourceAgentGroup) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*clients.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *clients.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +func (d *DataSourceAgentGroup) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *AgentGroupResourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + client := d.client + if d.client == nil { + resp.Diagnostics.AddError( + "Unconfigured HCP Client", + "Expected configured HCP client. Please report this issue to the provider developers.", + ) + return + } + + projectID := client.Config.ProjectID + if !data.ProjectID.IsUnknown() && !data.ProjectID.IsNull() { + projectID = data.ProjectID.ValueString() + } + + orgID := client.Config.OrganizationID + if !data.OrgID.IsUnknown() && !data.OrgID.IsNull() { + orgID = data.OrgID.ValueString() + } + + group, err := clients.GetAgentGroup(ctx, client, &sharedmodels.HashicorpCloudLocationLocation{ + OrganizationID: orgID, + ProjectID: projectID, + }, data.Name.ValueString()) + if err != nil { + if clients.IsResponseCodeNotFound(err) { + // If the group does not exist, remove it from state + tflog.Info(ctx, "Waypoint Agent Group not found for organization, removing from state") + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Error Reading Waypoint Agent Group", err.Error()) + return + } + + if group.Description != "" { + data.Description = types.StringValue(group.Description) + } else { + data.Description = types.StringNull() + } + + if group.Name != "" { + data.Name = types.StringValue(group.Name) + } else { + data.Name = types.StringNull() + } + + if data.ProjectID.IsUnknown() { + data.ProjectID = types.StringValue(projectID) + } + if data.OrgID.IsUnknown() { + data.OrgID = types.StringValue(orgID) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/waypoint/data_source_waypoint_agent_group_test.go b/internal/provider/waypoint/data_source_waypoint_agent_group_test.go new file mode 100644 index 000000000..6ad600b4d --- /dev/null +++ b/internal/provider/waypoint/data_source_waypoint_agent_group_test.go @@ -0,0 +1,50 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package waypoint_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-hcp/internal/provider/acctest" + "github.com/hashicorp/terraform-provider-hcp/internal/provider/waypoint" +) + +func TestAcc_Waypoint_Agent_Group_DataSource_basic(t *testing.T) { + t.Parallel() + var agentGroupModel waypoint.AgentGroupResourceModel + resourceName := "hcp_waypoint_agent_group.test" + dataSourceName := "data." + resourceName + agentGroupName := generateRandomName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointAgentGroupDestroy(t, &agentGroupModel), + Steps: []resource.TestStep{ + { + // establish the base agent group + Config: testAgentGroup(agentGroupName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointAgentGroupExists(t, resourceName, &agentGroupModel), + ), + }, + { + // add a data source config to read the agent group + Config: testDataAgentGroup(agentGroupName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "name", agentGroupName), + ), + }, + }, + }) +} + +func testDataAgentGroup(agentGroupName string) string { + return fmt.Sprintf(`%s +data "hcp_waypoint_agent_group" "test" { + name = hcp_waypoint_agent_group.test.name +}`, testAgentGroup(agentGroupName)) +} diff --git a/internal/provider/waypoint/resource_waypoint_action.go b/internal/provider/waypoint/resource_waypoint_action.go index 5d2c2da71..e5f8311c9 100644 --- a/internal/provider/waypoint/resource_waypoint_action.go +++ b/internal/provider/waypoint/resource_waypoint_action.go @@ -5,11 +5,13 @@ package waypoint import ( "context" + "encoding/base64" "fmt" sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2024-11-22/client/waypoint_service" waypoint_models "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2024-11-22/models" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -33,7 +35,7 @@ type ActionResource struct { client *clients.Client } -// ActionModel describes the resource data model. +// ActionResourceModel describes the resource data model. type ActionResourceModel struct { ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` @@ -46,6 +48,7 @@ type ActionResourceModel struct { type actionRequest struct { Custom *customRequest `tfsdk:"custom"` + Agent *agentRequest `tfsdk:"agent"` } type customRequest struct { @@ -55,6 +58,13 @@ type customRequest struct { Body types.String `tfsdk:"body"` } +type agentRequest struct { + OperationID types.String `tfsdk:"operation_id"` + Body types.String `tfsdk:"body"` + ActionRunID types.String `tfsdk:"action_run_id"` + Group types.String `tfsdk:"group"` +} + func (r *ActionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_waypoint_action" } @@ -117,6 +127,28 @@ func (r *ActionResource) Schema(ctx context.Context, req resource.SchemaRequest, }, }, }, + "agent": schema.SingleNestedAttribute{ + Description: "Agent mode allows users to define the agent to use for the request.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "operation_id": schema.StringAttribute{ + Description: "The identifying name of the operation in the agent config file.", + Required: true, + }, + "body": schema.StringAttribute{ + Description: "Arguments to the operation, specified as JSON.", + Optional: true, + }, + "action_run_id": schema.StringAttribute{ + Description: "An optional action run id. If specified the agent will interact with the actions subsystem.", + Optional: true, + }, + "group": schema.StringAttribute{ + Description: "The name of the group that the operation is in.", + Required: true, + }, + }, + }, }, }, }, @@ -147,7 +179,6 @@ func (r *ActionResource) Create(ctx context.Context, req resource.CreateRequest, // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { return } @@ -174,7 +205,7 @@ func (r *ActionResource) Create(ctx context.Context, req resource.CreateRequest, var diags diag.Diagnostics // This is a proxy for the request type, as Custom.Method is required for Custom requests - if !plan.Request.Custom.Method.IsUnknown() && !plan.Request.Custom.Method.IsNull() { + if plan.Request.Custom != nil && !plan.Request.Custom.Method.IsUnknown() && !plan.Request.Custom.Method.IsNull() { modelBody.ActionConfig.Request.Custom = &waypoint_models.HashicorpCloudWaypointV20241122ActionConfigFlavorCustom{} method := waypoint_models.HashicorpCloudWaypointV20241122ActionConfigFlavorCustomMethod(plan.Request.Custom.Method.ValueString()) @@ -199,7 +230,33 @@ func (r *ActionResource) Create(ctx context.Context, req resource.CreateRequest, } if !plan.Request.Custom.Body.IsUnknown() && !plan.Request.Custom.Body.IsNull() { modelBody.ActionConfig.Request.Custom.Body = plan.Request.Custom.Body.ValueString() + } + } else if plan.Request.Agent != nil && !plan.Request.Agent.OperationID.IsUnknown() && !plan.Request.Agent.OperationID.IsNull() { + modelBody.ActionConfig.Request.Agent = &waypoint_models.HashicorpCloudWaypointV20241122ActionConfigFlavorAgent{ + Op: &waypoint_models.HashicorpCloudWaypointV20241122AgentOperation{}, + } + if !plan.Request.Agent.OperationID.IsUnknown() && !plan.Request.Agent.OperationID.IsNull() { + modelBody.ActionConfig.Request.Agent.Op.ID = plan.Request.Agent.OperationID.ValueString() + } + if !plan.Request.Agent.Group.IsUnknown() && !plan.Request.Agent.Group.IsNull() { + modelBody.ActionConfig.Request.Agent.Op.Group = plan.Request.Agent.Group.ValueString() + } + if !plan.Request.Agent.Body.IsUnknown() && !plan.Request.Agent.Body.IsNull() { + // The body is expected to be a base64 encoded string, so we decode it + bodyBytes, err := base64.StdEncoding.DecodeString(plan.Request.Agent.Body.ValueString()) + // If there is an error, we immediately return an error to the user + if err != nil { + resp.Diagnostics.AddError( + "Error decoding Agent Body", + fmt.Sprintf("The Agent Body must be a base64 encoded string, got: %q", plan.Request.Agent.Body.ValueString()), + ) + return + } + modelBody.ActionConfig.Request.Agent.Op.Body = bodyBytes + } + if !plan.Request.Agent.ActionRunID.IsUnknown() && !plan.Request.Agent.ActionRunID.IsNull() { + modelBody.ActionConfig.Request.Agent.Op.ActionRunID = plan.Request.Agent.ActionRunID.ValueString() } } @@ -218,7 +275,11 @@ func (r *ActionResource) Create(ctx context.Context, req resource.CreateRequest, var aCfgModel *waypoint_models.HashicorpCloudWaypointV20241122ActionConfig if aCfg.Payload != nil { aCfgModel = aCfg.Payload.ActionConfig + } else { + resp.Diagnostics.AddError("Error creating Action", "The response payload was nil.") + return } + if aCfgModel == nil { resp.Diagnostics.AddError("Unknown error creating Action", "Empty Action returned") return @@ -247,6 +308,12 @@ func (r *ActionResource) Create(ctx context.Context, req resource.CreateRequest, resp.Diagnostics.Append(diags...) return } + } else if aCfgModel.Request.Agent != nil { + diags = readAgentAction(ctx, plan, aCfgModel) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } } // Write logs using the tflog package @@ -314,6 +381,12 @@ func (r *ActionResource) Read(ctx context.Context, req resource.ReadRequest, res resp.Diagnostics.Append(diags...) return } + } else if actionCfg.Request.Agent != nil { + diags := readAgentAction(ctx, data, actionCfg) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -356,7 +429,7 @@ func (r *ActionResource) Update(ctx context.Context, req resource.UpdateRequest, var diags diag.Diagnostics // This is a proxy for the request type, as Custom.Method is required for Custom requests - if !plan.Request.Custom.Method.IsUnknown() && !plan.Request.Custom.Method.IsNull() { + if plan.Request.Custom != nil && !plan.Request.Custom.Method.IsUnknown() && !plan.Request.Custom.Method.IsNull() { modelBody.ActionConfig.Request.Custom = &waypoint_models.HashicorpCloudWaypointV20241122ActionConfigFlavorCustom{} method := waypoint_models.HashicorpCloudWaypointV20241122ActionConfigFlavorCustomMethod(plan.Request.Custom.Method.ValueString()) @@ -383,6 +456,36 @@ func (r *ActionResource) Update(ctx context.Context, req resource.UpdateRequest, modelBody.ActionConfig.Request.Custom.Body = plan.Request.Custom.Body.ValueString() } + // This is a proxy for the request type, as Agent.OperationID is required for Agent requests + } else if plan.Request.Agent != nil && !plan.Request.Agent.OperationID.IsUnknown() && !plan.Request.Agent.OperationID.IsNull() { + modelBody.ActionConfig.Request.Agent = &waypoint_models.HashicorpCloudWaypointV20241122ActionConfigFlavorAgent{ + Op: &waypoint_models.HashicorpCloudWaypointV20241122AgentOperation{}, + } + + if !plan.Request.Agent.OperationID.IsUnknown() && !plan.Request.Agent.OperationID.IsNull() { + modelBody.ActionConfig.Request.Agent.Op.ID = plan.Request.Agent.OperationID.ValueString() + } + if !plan.Request.Agent.Group.IsUnknown() && !plan.Request.Agent.Group.IsNull() { + modelBody.ActionConfig.Request.Agent.Op.Group = plan.Request.Agent.Group.ValueString() + } + + if !plan.Request.Agent.Body.IsUnknown() && !plan.Request.Agent.Body.IsNull() { + // The body is expected to be a base64 encoded string, so we decode it + bodyBytes, err := base64.StdEncoding.DecodeString(plan.Request.Agent.Body.ValueString()) + // If there is an error, we immediately return an error to the user + if err != nil { + resp.Diagnostics.AddError( + "Error decoding Agent Body", + fmt.Sprintf("The Agent Body must be a base64 encoded string, got: %q", plan.Request.Agent.Body.ValueString()), + ) + return + } else { + modelBody.ActionConfig.Request.Agent.Op.Body = bodyBytes + } + } + if !plan.Request.Agent.ActionRunID.IsUnknown() && !plan.Request.Agent.ActionRunID.IsNull() { + modelBody.ActionConfig.Request.Agent.Op.ActionRunID = plan.Request.Agent.ActionRunID.ValueString() + } } params := &waypoint_service.WaypointServiceUpdateActionConfigParams{ @@ -429,6 +532,12 @@ func (r *ActionResource) Update(ctx context.Context, req resource.UpdateRequest, resp.Diagnostics.Append(diags...) return } + } else if aCfgModel.Request.Agent != nil { + diags = readAgentAction(ctx, plan, aCfgModel) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } } // Write logs using the tflog package @@ -479,6 +588,7 @@ func (r *ActionResource) Delete(ctx context.Context, req resource.DeleteRequest, return } } + func (r *ActionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } @@ -519,5 +629,43 @@ func readCustomAction( } else { data.Request.Custom.Body = types.StringNull() } + + // Ensure Agent is nil if Custom is set + data.Request.Agent = nil + + return diags +} + +func readAgentAction( + ctx context.Context, + data *ActionResourceModel, + actionCfg *waypoint_models.HashicorpCloudWaypointV20241122ActionConfig, +) diag.Diagnostics { + data.Request.Agent = &agentRequest{} + var diags diag.Diagnostics + if actionCfg.Request.Agent.Op.ID != "" { + data.Request.Agent.OperationID = types.StringValue(actionCfg.Request.Agent.Op.ID) + } else { + data.Request.Agent.OperationID = types.StringNull() + } + if actionCfg.Request.Agent.Op.Group != "" { + data.Request.Agent.Group = types.StringValue(actionCfg.Request.Agent.Op.Group) + } else { + data.Request.Agent.Group = types.StringNull() + } + if actionCfg.Request.Agent.Op.Body.String() != "" { + data.Request.Agent.Body = types.StringValue(base64.StdEncoding.EncodeToString(actionCfg.Request.Agent.Op.Body)) + } else { + data.Request.Agent.Body = types.StringNull() + } + if actionCfg.Request.Agent.Op.ActionRunID != "" { + data.Request.Agent.ActionRunID = types.StringValue(actionCfg.Request.Agent.Op.ActionRunID) + } else { + data.Request.Agent.ActionRunID = types.StringNull() + } + + // Ensure Custom is nil if Agent is set + data.Request.Custom = nil + return diags } diff --git a/internal/provider/waypoint/resource_waypoint_action_test.go b/internal/provider/waypoint/resource_waypoint_action_test.go index a030613b9..2138b6790 100644 --- a/internal/provider/waypoint/resource_waypoint_action_test.go +++ b/internal/provider/waypoint/resource_waypoint_action_test.go @@ -19,7 +19,6 @@ import ( ) func TestAcc_Waypoint_Action_basic(t *testing.T) { - t.Parallel() // Skip this test unless the appropriate environment variable is set // This is to prevent running this test by default @@ -30,7 +29,9 @@ func TestAcc_Waypoint_Action_basic(t *testing.T) { } var actionCfgModel waypoint.ActionResourceModel resourceName := "hcp_waypoint_action.test" + resourceNameAgent := "hcp_waypoint_action.test_agent" actionName := generateRandomName() + actionAgentName := generateRandomName() resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -47,6 +48,22 @@ func TestAcc_Waypoint_Action_basic(t *testing.T) { }, }, }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointActionDestroy(t, &actionCfgModel), + Steps: []resource.TestStep{ + { + Config: testAgentAction(actionAgentName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointActionExists(t, resourceNameAgent, &actionCfgModel), + testAccCheckWaypointActionName(t, &actionCfgModel, actionAgentName), + resource.TestCheckResourceAttr(resourceNameAgent, "name", actionAgentName), + ), + }, + }, + }) } // Simple attribute check on the action received from the API @@ -90,6 +107,8 @@ func testAccCheckWaypointActionExists(t *testing.T, resourceName string, actionC if actionCfgModel != nil { actionCfgModel.Name = types.StringValue(actionCfg.Name) actionCfgModel.ID = types.StringValue(actionCfg.ID) + } else { + return fmt.Errorf("actionCfgModel is nil when it should not be") } return nil @@ -146,3 +165,17 @@ resource "hcp_waypoint_action" "test" { } `, actionName) } + +func testAgentAction(actionName string) string { + return fmt.Sprintf(` +resource "hcp_waypoint_action" "test_agent" { + name = "%s" + description = "Test action" + request = { + agent = { + operation_id = "test-operation-id" + group = "test-group" + } + } +}`, actionName) +} diff --git a/internal/provider/waypoint/resource_waypoint_agent_group.go b/internal/provider/waypoint/resource_waypoint_agent_group.go new file mode 100644 index 000000000..91f97f723 --- /dev/null +++ b/internal/provider/waypoint/resource_waypoint_agent_group.go @@ -0,0 +1,354 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package waypoint + +import ( + "context" + "fmt" + + sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" + "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2024-11-22/client/waypoint_service" + waypoint_models "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2024-11-22/models" + + "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/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-hcp/internal/clients" +) + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &AgentGroupResource{} +var _ resource.ResourceWithImportState = &AgentGroupResource{} + +func NewAgentGroupResource() resource.Resource { + return &AgentGroupResource{} +} + +type AgentGroupResource struct { + client *clients.Client +} + +// AgentGroupResourceModel describes the resource data model +type AgentGroupResourceModel struct { + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + ProjectID types.String `tfsdk:"project_id"` + OrgID types.String `tfsdk:"organization_id"` +} + +func (r *AgentGroupResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_waypoint_agent_group" +} + +func (r *AgentGroupResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "The Waypoint Agent Group resource manages the lifecycle of an Agent Group.", + + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "The name of the Agent Group.", + Required: true, + }, + "project_id": schema.StringAttribute{ + Description: "The ID of the Waypoint project to which the Agent Group belongs.", + Computed: true, + Optional: true, + }, + "organization_id": schema.StringAttribute{ + Description: "The ID of the Waypoint organization to which the Agent Group belongs.", + Computed: true, + Optional: true, + }, + "description": schema.StringAttribute{ + Description: "A description of the Agent Group.", + Optional: true, + }, + }, + } +} + +func (r *AgentGroupResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*clients.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *AgentGroupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan *AgentGroupResourceModel + + // Read the Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + projectID := r.client.Config.ProjectID + if !plan.ProjectID.IsUnknown() && !plan.ProjectID.IsNull() { + projectID = plan.ProjectID.ValueString() + } + + orgID := r.client.Config.OrganizationID + if !plan.OrgID.IsUnknown() && !plan.OrgID.IsNull() { + orgID = plan.OrgID.ValueString() + } + + modelBody := &waypoint_models.HashicorpCloudWaypointV20241122WaypointServiceCreateAgentGroupBody{ + Group: &waypoint_models.HashicorpCloudWaypointV20241122AgentGroup{}, + } + + modelBody.Group.Name = plan.Name.ValueString() + + if !plan.Description.IsNull() && !plan.Description.IsUnknown() { + modelBody.Group.Description = plan.Description.ValueString() + } + + params := &waypoint_service.WaypointServiceCreateAgentGroupParams{ + NamespaceLocationOrganizationID: orgID, + NamespaceLocationProjectID: projectID, + Body: modelBody, + } + + _, err := r.client.Waypoint.WaypointServiceCreateAgentGroup(params, nil) + if err != nil { + resp.Diagnostics.AddError("Error Creating Waypoint Agent Group", err.Error()) + return + } + + // We do not get the agent group back from the API, so we need to get it in a separate request + getParams := &waypoint_service.WaypointServiceGetAgentGroupParams{ + Name: params.Body.Group.Name, + NamespaceLocationOrganizationID: orgID, + NamespaceLocationProjectID: projectID, + } + + getAgentGroupResp, err := r.client.Waypoint.WaypointServiceGetAgentGroup(getParams, nil) + if err != nil { + resp.Diagnostics.AddError("Error Getting Waypoint Agent Group after creation", err.Error()) + return + } + + var agentGroupModel *waypoint_models.HashicorpCloudWaypointV20241122AgentGroup + if getAgentGroupResp.Payload != nil { + agentGroupModel = getAgentGroupResp.Payload.Group + } else { + resp.Diagnostics.AddError("Error Getting Waypoint Agent Group after creation", "The response payload was nil.") + return + } + + if agentGroupModel == nil { + resp.Diagnostics.AddError("Error Getting Waypoint Agent Group after creation", "The agent group model was nil.") + return + } + + if agentGroupModel.Description != "" { + plan.Description = types.StringValue(agentGroupModel.Description) + } else { + plan.Description = types.StringNull() + } + if agentGroupModel.Name != "" { + plan.Name = types.StringValue(agentGroupModel.Name) + } else { + plan.Name = types.StringNull() + } + + if plan.ProjectID.IsUnknown() { + plan.ProjectID = types.StringValue(projectID) + } + if plan.OrgID.IsUnknown() { + plan.OrgID = types.StringValue(orgID) + } + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Trace(ctx, "Created Agent group resource") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + +} + +func (r *AgentGroupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *AgentGroupResourceModel + + // Read the Terraform state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + projectID := r.client.Config.ProjectID + if !data.ProjectID.IsUnknown() && !data.ProjectID.IsNull() { + projectID = data.ProjectID.ValueString() + } + + orgID := r.client.Config.OrganizationID + if !data.OrgID.IsUnknown() && !data.OrgID.IsNull() { + orgID = data.OrgID.ValueString() + } + + client := r.client + + group, err := clients.GetAgentGroup(ctx, client, &sharedmodels.HashicorpCloudLocationLocation{ + OrganizationID: orgID, + ProjectID: projectID, + }, data.Name.ValueString()) + if err != nil { + if clients.IsResponseCodeNotFound(err) { + // If the group does not exist, remove it from state + tflog.Info(ctx, "Waypoint Agent Group not found for organization, removing from state") + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("Error Reading Waypoint Agent Group", err.Error()) + return + } + + if group.Description != "" { + data.Description = types.StringValue(group.Description) + } else { + data.Description = types.StringNull() + } + + if group.Name != "" { + data.Name = types.StringValue(group.Name) + } else { + data.Name = types.StringNull() + } + + if data.ProjectID.IsUnknown() { + data.ProjectID = types.StringValue(projectID) + } + if data.OrgID.IsUnknown() { + data.OrgID = types.StringValue(orgID) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *AgentGroupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan *AgentGroupResourceModel + + // Read the Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + // get the current state as well, so we know the current name of the + // agent group for reference during the update + var data *AgentGroupResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + projectID := r.client.Config.ProjectID + if !plan.ProjectID.IsUnknown() && !plan.ProjectID.IsNull() { + projectID = plan.ProjectID.ValueString() + } + + orgID := r.client.Config.OrganizationID + if !plan.OrgID.IsUnknown() && !plan.OrgID.IsNull() { + orgID = plan.OrgID.ValueString() + } + + modelBody := &waypoint_models.HashicorpCloudWaypointV20241122WaypointServiceUpdateAgentGroupBody{} + + modelBody.Description = plan.Description.ValueString() + + params := &waypoint_service.WaypointServiceUpdateAgentGroupParams{ + Body: modelBody, + Name: data.Name.ValueString(), + NamespaceLocationOrganizationID: orgID, + NamespaceLocationProjectID: projectID, + } + + agentGroup, err := r.client.Waypoint.WaypointServiceUpdateAgentGroup(params, nil) + if err != nil { + resp.Diagnostics.AddError("Error Updating Waypoint Agent Group", err.Error()) + return + } + + if agentGroup.Payload == nil || agentGroup.Payload.Group == nil { + resp.Diagnostics.AddError("Error Updating Waypoint Agent Group", "The response payload or group was nil.") + return + } + + // Update the plan with the new value + if agentGroup.Payload.Group.Description != "" { + plan.Description = types.StringValue(agentGroup.Payload.Group.Description) + } else { + plan.Description = types.StringNull() + } + + if plan.ProjectID.IsUnknown() { + plan.ProjectID = types.StringValue(projectID) + } + if plan.OrgID.IsUnknown() { + plan.OrgID = types.StringValue(orgID) + } + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Trace(ctx, "Updated Agent group resource") + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *AgentGroupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *AgentGroupResourceModel + + // Read Terraform state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + projectID := r.client.Config.ProjectID + if !data.ProjectID.IsUnknown() { + projectID = data.ProjectID.ValueString() + } + + loc := &sharedmodels.HashicorpCloudLocationLocation{ + OrganizationID: r.client.Config.OrganizationID, + ProjectID: projectID, + } + + params := &waypoint_service.WaypointServiceDeleteAgentGroupParams{ + Name: data.Name.ValueString(), + NamespaceLocationOrganizationID: loc.OrganizationID, + NamespaceLocationProjectID: loc.ProjectID, + } + + _, err := r.client.Waypoint.WaypointServiceDeleteAgentGroup(params, nil) + if err != nil { + if clients.IsResponseCodeNotFound(err) { + // If the group does not exist, just return + tflog.Info(ctx, "Waypoint Agent Group not found for organization, nothing to delete") + return + } + resp.Diagnostics.AddError("Error Deleting Waypoint Agent Group", err.Error()) + return + } +} + +func (r *AgentGroupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/waypoint/resource_waypoint_agent_group_test.go b/internal/provider/waypoint/resource_waypoint_agent_group_test.go new file mode 100644 index 000000000..56a7f8fea --- /dev/null +++ b/internal/provider/waypoint/resource_waypoint_agent_group_test.go @@ -0,0 +1,129 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package waypoint_test + +import ( + "context" + "fmt" + "testing" + + sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-hcp/internal/clients" + "github.com/hashicorp/terraform-provider-hcp/internal/provider/acctest" + "github.com/hashicorp/terraform-provider-hcp/internal/provider/waypoint" +) + +func TestAcc_Waypoint_Agent_Group_basic(t *testing.T) { + t.Parallel() + + var agentGroupModel waypoint.AgentGroupResourceModel + resourceName := "hcp_waypoint_agent_group.test" + agentGroupName := generateRandomName() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + CheckDestroy: testAccCheckWaypointAgentGroupDestroy(t, &agentGroupModel), + Steps: []resource.TestStep{ + { + Config: testAgentGroup(agentGroupName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWaypointAgentGroupExists(t, resourceName, &agentGroupModel), + testAccCheckWaypointAgentGroupName(t, &agentGroupModel, agentGroupName), + resource.TestCheckResourceAttr(resourceName, "name", agentGroupName), + ), + }, + }, + }) +} + +func testAccCheckWaypointAgentGroupExists(t *testing.T, resourceName string, agentGroupModel *waypoint.AgentGroupResourceModel) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Find the corresponding state object + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + + client := acctest.HCPClients(t) + + // Get the project ID, org ID, and Name from state + projectID := rs.Primary.Attributes["project_id"] + groupName := rs.Primary.Attributes["name"] + orgID := rs.Primary.Attributes["organization_id"] + + loc := &sharedmodels.HashicorpCloudLocationLocation{ + ProjectID: projectID, + OrganizationID: orgID, + } + + // Retrieve the agent group using the client + agentGroup, err := clients.GetAgentGroup(context.Background(), client, loc, groupName) + if err != nil { + return fmt.Errorf("error retrieving agent group %q: %w", groupName, err) + } + + // Verify the agent group exists + if agentGroup == nil { + return fmt.Errorf("agent group %q not found", groupName) + } + + agentGroupModel.Name = types.StringValue(agentGroup.Name) + agentGroupModel.Description = types.StringValue(agentGroup.Description) + + return nil + } +} + +func testAccCheckWaypointAgentGroupName(_ *testing.T, agentGroupModel *waypoint.AgentGroupResourceModel, nameValue string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if agentGroupModel.Name.ValueString() != nameValue { + return fmt.Errorf("expected agent group name to be %q, but got %q", nameValue, agentGroupModel.Name.ValueString()) + } + return nil + } +} + +func testAccCheckWaypointAgentGroupDestroy(t *testing.T, agentGroupModel *waypoint.AgentGroupResourceModel) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := acctest.HCPClients(t) + name := agentGroupModel.Name.ValueString() + projectID := agentGroupModel.ProjectID.ValueString() + orgID := client.Config.OrganizationID + + loc := &sharedmodels.HashicorpCloudLocationLocation{ + OrganizationID: orgID, + ProjectID: projectID, + } + + agentGroupGetResponse, err := clients.GetAgentGroup(context.Background(), client, loc, name) + if err != nil { + // expected + if clients.IsResponseCodeNotFound(err) { + return nil + } + return err + } + + // fall through, we expect a not found above but if we get this far then + // the test should fail + if agentGroupGetResponse != nil { + return fmt.Errorf("expected agent group to be destroyed, but it still exists") + } + + return fmt.Errorf("both agent group and error were nil in destroy check, this should not happen") + } +} + +func testAgentGroup(groupName string) string { + return fmt.Sprintf(` +resource "hcp_waypoint_agent_group" "test" { + name = %q + description = "Test Agent Group" +} +`, groupName) +} diff --git a/templates/data-sources/waypoint_action.md.tmpl b/templates/data-sources/waypoint_action.md.tmpl index 3fcdb9c86..1536bf63e 100644 --- a/templates/data-sources/waypoint_action.md.tmpl +++ b/templates/data-sources/waypoint_action.md.tmpl @@ -7,8 +7,6 @@ description: |- # {{.Name}} `{{.Type}}` --> **Note:** HCP Waypoint actions is currently in beta. - {{ .Description | trimspace }} {{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/data-sources/waypoint_agent_group.md.tmpl b/templates/data-sources/waypoint_agent_group.md.tmpl new file mode 100644 index 000000000..1536bf63e --- /dev/null +++ b/templates/data-sources/waypoint_agent_group.md.tmpl @@ -0,0 +1,12 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "HCP Waypoint" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} `{{.Type}}` + +{{ .Description | trimspace }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/resources/waypoint_action.md.tmpl b/templates/resources/waypoint_action.md.tmpl index 3fcdb9c86..1536bf63e 100644 --- a/templates/resources/waypoint_action.md.tmpl +++ b/templates/resources/waypoint_action.md.tmpl @@ -7,8 +7,6 @@ description: |- # {{.Name}} `{{.Type}}` --> **Note:** HCP Waypoint actions is currently in beta. - {{ .Description | trimspace }} {{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/resources/waypoint_agent_group.md.tmpl b/templates/resources/waypoint_agent_group.md.tmpl new file mode 100644 index 000000000..1536bf63e --- /dev/null +++ b/templates/resources/waypoint_agent_group.md.tmpl @@ -0,0 +1,12 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "HCP Waypoint" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} `{{.Type}}` + +{{ .Description | trimspace }} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file