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