diff --git a/.changelog/807.txt b/.changelog/807.txt
new file mode 100644
index 000000000..8a79a1151
--- /dev/null
+++ b/.changelog/807.txt
@@ -0,0 +1,4 @@
+```release-note:feature
+ New resource: Add `hcp_waypoint_add_on` resource for managing Waypoint Add-ons.
+ New data-source: Add `data.hcp_waypoint_add_on` data-source for Waypoint Add-ons.
+ ```
\ No newline at end of file
diff --git a/docs/data-sources/waypoint_add_on.md b/docs/data-sources/waypoint_add_on.md
new file mode 100644
index 000000000..c2f015cee
--- /dev/null
+++ b/docs/data-sources/waypoint_add_on.md
@@ -0,0 +1,44 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "hcp_waypoint_add_on Data Source - terraform-provider-hcp"
+subcategory: ""
+description: |-
+ The Waypoint Add-on data source retrieves information on a given Add-on.
+---
+
+# hcp_waypoint_add_on (Data Source)
+
+The Waypoint Add-on data source retrieves information on a given Add-on.
+
+
+
+
+## Schema
+
+### Optional
+
+- `id` (String) The ID of the Add-on.
+- `name` (String) The name of the Add-on.
+
+### Read-Only
+
+- `application_id` (String) The ID of the Application that this Add-on is created for.
+- `created_by` (String) The user who created the Add-on.
+- `definition_id` (String) The ID of the Add-on Definition that this Add-on is created from.
+- `description` (String) A longer description of the Add-on.
+- `install_count` (Number) The number of installed Add-ons for the same Application that share the same Add-on Definition.
+- `labels` (List of String) List of labels attached to this Add-on.
+- `organization_id` (String) The ID of the HCP organization where the Waypoint AddOn is located.
+- `project_id` (String) The ID of the HCP project where the Waypoint AddOn is located.
+- `readme_markdown` (String) Instructions for using the Add-on (markdown format supported).
+- `status` (Number) The status of the Terraform run for the Add-on.
+- `summary` (String) A short summary of the Add-on.
+- `terraform_no_code_module` (Attributes) Terraform Cloud no-code Module details. (see [below for nested schema](#nestedatt--terraform_no_code_module))
+
+
+### Nested Schema for `terraform_no_code_module`
+
+Read-Only:
+
+- `source` (String) Terraform Cloud no-code Module Source
+- `version` (String) Terraform Cloud no-code Module Version
diff --git a/docs/resources/waypoint_add_on.md b/docs/resources/waypoint_add_on.md
new file mode 100644
index 000000000..8e0834181
--- /dev/null
+++ b/docs/resources/waypoint_add_on.md
@@ -0,0 +1,46 @@
+---
+page_title: "hcp_waypoint_add_on Resource - terraform-provider-hcp"
+subcategory: "HCP Waypoint"
+description: |-
+ Waypoint Add-on resource
+---
+
+# hcp_waypoint_add_on `Resource`
+
+-> **Note:** HCP Waypoint is currently in public beta.
+
+Waypoint Add-on resource
+
+
+## Schema
+
+### Required
+
+- `application_id` (String) The ID of the Application that this Add-on is created for.
+- `definition_id` (String) The ID of the Add-on Definition that this Add-on is created from.
+- `name` (String) The name of the Add-on.
+
+### Optional
+
+- `project_id` (String) The ID of the HCP project where the Waypoint AddOn is located.
+
+### Read-Only
+
+- `created_by` (String) The user who created the Add-on.
+- `description` (String) A longer description of the Add-on.
+- `id` (String) The ID of the Add-on.
+- `install_count` (Number) The number of installed Add-ons for the same Application that share the same Add-on Definition.
+- `labels` (List of String) List of labels attached to this Add-on.
+- `organization_id` (String) The ID of the HCP organization where the Waypoint AddOn is located.
+- `readme_markdown` (String) The markdown for the Add-on README.
+- `status` (Number) The status of the Terraform run for the Add-on.
+- `summary` (String) A short summary of the Add-on.
+- `terraform_no_code_module` (Attributes) Terraform Cloud no-code Module details. (see [below for nested schema](#nestedatt--terraform_no_code_module))
+
+
+### Nested Schema for `terraform_no_code_module`
+
+Read-Only:
+
+- `source` (String) Terraform Cloud no-code Module Source
+- `version` (String) Terraform Cloud no-code Module Version
\ No newline at end of file
diff --git a/internal/clients/response.go b/internal/clients/response.go
index 977fa9252..1db4efffb 100644
--- a/internal/clients/response.go
+++ b/internal/clients/response.go
@@ -22,3 +22,12 @@ func IsResponseCodeNotFound(err error) bool {
return strings.Contains(err.Error(), fmt.Sprintf("[%d]", http.StatusNotFound))
}
}
+
+func IsResponseCodeInternalError(erro error) bool {
+ var apiErr *runtime.APIError
+ if errors.As(erro, &apiErr) {
+ return apiErr.Code == http.StatusInternalServerError
+ } else {
+ return strings.Contains(erro.Error(), fmt.Sprintf("[%d]", http.StatusInternalServerError))
+ }
+}
diff --git a/internal/clients/waypoint.go b/internal/clients/waypoint.go
index b63af83c8..70f162c74 100644
--- a/internal/clients/waypoint.go
+++ b/internal/clients/waypoint.go
@@ -139,3 +139,41 @@ func GetApplicationByID(ctx context.Context, client *Client, loc *sharedmodels.H
}
return getResp.GetPayload().Application, nil
}
+
+// GetAddOnByName will retrieve an add-on by name
+func GetAddOnByName(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, defName string) (*waypoint_models.HashicorpCloudWaypointAddOn, error) {
+ ns, err := getNamespaceByLocation(ctx, client, loc)
+ if err != nil {
+ return nil, err
+ }
+
+ params := &waypoint_service.WaypointServiceGetAddOn2Params{
+ AddOnName: defName,
+ NamespaceID: ns.ID,
+ }
+
+ getResp, err := client.Waypoint.WaypointServiceGetAddOn2(params, nil)
+ if err != nil {
+ return nil, err
+ }
+ return getResp.GetPayload().AddOn, nil
+}
+
+// GetAddOnByID will retrieve an add-on by ID
+func GetAddOnByID(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, defID string) (*waypoint_models.HashicorpCloudWaypointAddOn, error) {
+ ns, err := getNamespaceByLocation(ctx, client, loc)
+ if err != nil {
+ return nil, err
+ }
+
+ params := &waypoint_service.WaypointServiceGetAddOnParams{
+ AddOnID: defID,
+ NamespaceID: ns.ID,
+ }
+
+ getResp, err := client.Waypoint.WaypointServiceGetAddOn(params, nil)
+ if err != nil {
+ return nil, err
+ }
+ return getResp.GetPayload().AddOn, nil
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index f26382282..3c42096f6 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -153,6 +153,7 @@ func (p *ProviderFramework) Resources(ctx context.Context) []func() resource.Res
// Waypoint
waypoint.NewApplicationResource,
waypoint.NewApplicationTemplateResource,
+ waypoint.NewAddOnResource,
waypoint.NewAddOnDefinitionResource,
waypoint.NewTfcConfigResource,
}, packer.ResourceSchemaBuilders...)
@@ -174,6 +175,7 @@ func (p *ProviderFramework) DataSources(ctx context.Context) []func() datasource
// Waypoint
waypoint.NewApplicationDataSource,
waypoint.NewApplicationTemplateDataSource,
+ waypoint.NewAddOnDataSource,
waypoint.NewAddOnDefinitionDataSource,
}, packer.DataSourceSchemaBuilders...)
}
diff --git a/internal/provider/waypoint/data_source_waypoint_add_on.go b/internal/provider/waypoint/data_source_waypoint_add_on.go
new file mode 100644
index 000000000..b8dd7281f
--- /dev/null
+++ b/internal/provider/waypoint/data_source_waypoint_add_on.go
@@ -0,0 +1,275 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package waypoint
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+
+ sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models"
+ waypoint_models "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/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-provider-hcp/internal/clients"
+)
+
+var _ datasource.DataSource = &DataSourceAddOn{}
+var _ datasource.DataSourceWithConfigValidators = &DataSourceAddOn{}
+
+func (d DataSourceAddOn) ConfigValidators(ctx context.Context) []datasource.ConfigValidator {
+ return []datasource.ConfigValidator{
+ datasourcevalidator.Conflicting(
+ path.MatchRoot("name"),
+ path.MatchRoot("id"),
+ ),
+ }
+}
+
+type DataSourceAddOn struct {
+ client *clients.Client
+}
+
+func NewAddOnDataSource() datasource.DataSource {
+ return &DataSourceAddOn{}
+}
+
+func (d *DataSourceAddOn) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_waypoint_add_on"
+}
+
+//TODO: Make sure this schema is correct (do we want to include count or output values?)
+
+func (d *DataSourceAddOn) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "The Waypoint Add-on data source retrieves information on a given Add-on.",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ Optional: true,
+ Description: "The ID of the Add-on.",
+ },
+ "name": schema.StringAttribute{
+ Description: "The name of the Add-on.",
+ Computed: true,
+ Optional: true,
+ },
+
+ "organization_id": schema.StringAttribute{
+ Description: "The ID of the HCP organization where the Waypoint AddOn is located.",
+ Computed: true,
+ },
+ "project_id": schema.StringAttribute{
+ Description: "The ID of the HCP project where the Waypoint AddOn is located.",
+ Computed: true,
+ },
+ "summary": schema.StringAttribute{
+ Description: "A short summary of the Add-on.",
+ Computed: true,
+ },
+ "description": schema.StringAttribute{
+ Description: "A longer description of the Add-on.",
+ Computed: true,
+ },
+ "readme_markdown": schema.StringAttribute{
+ Computed: true,
+ Description: "Instructions for using the Add-on (markdown format supported).",
+ },
+ "labels": schema.ListAttribute{
+ Computed: true,
+ Description: "List of labels attached to this Add-on.",
+ ElementType: types.StringType,
+ },
+ "created_by": schema.StringAttribute{
+ Description: "The user who created the Add-on.",
+ Computed: true,
+ },
+ "install_count": schema.Int64Attribute{
+ Description: "The number of installed Add-ons for the same Application that share the same " +
+ "Add-on Definition.",
+ Computed: true,
+ },
+ "definition_id": schema.StringAttribute{
+ Computed: true,
+ Description: "The ID of the Add-on Definition that this Add-on is created from.",
+ },
+ "application_id": schema.StringAttribute{
+ Computed: true,
+ Description: "The ID of the Application that this Add-on is created for.",
+ },
+ "terraform_no_code_module": &schema.SingleNestedAttribute{
+ Computed: true,
+ Description: "Terraform Cloud no-code Module details.",
+ Attributes: map[string]schema.Attribute{
+ "source": &schema.StringAttribute{
+ Computed: true,
+ Description: "Terraform Cloud no-code Module Source",
+ },
+ "version": &schema.StringAttribute{
+ Computed: true,
+ Description: "Terraform Cloud no-code Module Version",
+ },
+ },
+ },
+ "status": schema.Int64Attribute{
+ Computed: true,
+ Description: "The status of the Terraform run for the Add-on.",
+ },
+ /*"output_values": schema.ListNestedAttribute{
+ Computed: true,
+ Description: "The output values of the Terraform run for the Add-on, sensitive values have type " +
+ "and value omitted.",
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Computed: true,
+ Description: "The name of the output value.",
+ },
+ "type": schema.StringAttribute{
+ Computed: true,
+ Description: "The type of the output value.",
+ },
+ "value": schema.StringAttribute{
+ Computed: true,
+ Description: "The value of the output value.",
+ },
+ "sensitive": schema.BoolAttribute{
+ Computed: true,
+ Description: "Whether the output value is sensitive.",
+ },
+ },
+ },
+ },*/
+ },
+ }
+}
+
+func (d *DataSourceAddOn) 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
+}
+
+// TODO: Output values?
+func (d *DataSourceAddOn) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var state AddOnResourceModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
+
+ 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
+ }
+
+ loc := &sharedmodels.HashicorpCloudLocationLocation{
+ OrganizationID: client.Config.OrganizationID,
+ ProjectID: client.Config.ProjectID,
+ }
+
+ var addOn *waypoint_models.HashicorpCloudWaypointAddOn
+ var err error
+
+ if state.ID.IsNull() {
+ addOn, err = clients.GetAddOnByName(ctx, client, loc, state.Name.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(err.Error(), "failed to get add-on by name")
+ return
+ }
+ state.ID = types.StringValue(addOn.ID)
+ } else if state.Name.IsNull() {
+ addOn, err = clients.GetAddOnByID(ctx, client, loc, state.ID.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(err.Error(), "failed to get add-on by ID")
+ return
+ }
+ state.Name = types.StringValue(addOn.Name)
+ }
+
+ state.Summary = types.StringValue(addOn.Summary)
+
+ labels, diags := types.ListValueFrom(ctx, types.StringType, addOn.Labels)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ if len(labels.Elements()) == 0 {
+ labels = types.ListNull(types.StringType)
+ }
+ state.Labels = labels
+
+ if addOn.TerraformNocodeModule != nil {
+ tfcNoCode := &tfcNoCodeModule{
+ Source: types.StringValue(addOn.TerraformNocodeModule.Source),
+ Version: types.StringValue(addOn.TerraformNocodeModule.Version),
+ }
+ state.TerraformNoCodeModule, diags = types.ObjectValueFrom(ctx, tfcNoCode.attrTypes(), tfcNoCode)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
+ // set plan.description if it's not null or addOn.description is not empty
+ state.Description = types.StringValue(addOn.Description)
+ if addOn.Description == "" {
+ state.Description = types.StringNull()
+ }
+ state.ReadmeMarkdown = types.StringValue(addOn.ReadmeMarkdown.String())
+ // set state.readme if it's not null or addOnDefinition.readme is not empty
+ if addOn.ReadmeMarkdown.String() == "" {
+ state.ReadmeMarkdown = types.StringNull()
+ }
+
+ state.CreatedBy = types.StringValue(addOn.CreatedBy)
+
+ // TODO: Add support for outputValues
+
+ // If we can process status as an int64, add it to the plan
+ statusNum, err := strconv.ParseInt(addOn.Count, 10, 64)
+ if err != nil {
+ resp.Diagnostics.AddError("Error parsing installed Add-on status", err.Error())
+ } else {
+ state.Status = types.Int64Value(statusNum)
+ }
+
+ // If we can process count as an int64, add it to the plan
+ installedCount, err := strconv.ParseInt(addOn.Count, 10, 64)
+ if err != nil {
+ resp.Diagnostics.AddError("Error parsing installed Add-ons count", err.Error())
+ } else {
+ state.Count = types.Int64Value(installedCount)
+ }
+
+ // Display the reference to the Definition in the state
+ if addOn.Definition != nil {
+ if addOn.Definition.ID != "" {
+ state.DefinitionID = types.StringValue(addOn.Definition.ID)
+ }
+ }
+
+ // Display the reference to the Application in the state
+ if addOn.Application != nil {
+ if addOn.Application.ID != "" {
+ state.ApplicationID = types.StringValue(addOn.Application.ID)
+ }
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+}
diff --git a/internal/provider/waypoint/data_source_waypoint_add_on_test.go b/internal/provider/waypoint/data_source_waypoint_add_on_test.go
new file mode 100644
index 000000000..654dc2313
--- /dev/null
+++ b/internal/provider/waypoint/data_source_waypoint_add_on_test.go
@@ -0,0 +1,56 @@
+// 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 TestAccWaypointData_Add_On_basic(t *testing.T) {
+ // this is only used to verify the add-on gets cleaned up in the end
+ // of the test, and not used for any other purpose at this time
+ var addOnModel waypoint.AddOnResourceModel
+ resourceName := "hcp_waypoint_add_on.test"
+ dataSourceName := "data." + resourceName
+ addOnName := generateRandomName()
+ templateName := generateRandomName()
+ appName := generateRandomName()
+ defName := generateRandomName()
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckWaypointAddOnDestroy(t, &addOnModel),
+ Steps: []resource.TestStep{
+ {
+ // establish the base add-on
+ // note this reuses the config method from the add-on
+ // resource test
+ Config: testAddOnConfig(templateName, appName, defName, addOnName),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckWaypointAddOnExists(t, resourceName, &addOnModel),
+ ),
+ },
+ {
+ // add a data source config to read the add-on
+ Config: testDataAddOnConfig(templateName, appName, defName, addOnName),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(dataSourceName, "name", addOnName),
+ ),
+ },
+ },
+ })
+}
+
+func testDataAddOnConfig(templateName string, appName string, defName string, addOnName string) string {
+ return fmt.Sprintf(`%s
+data "hcp_waypoint_add_on" "test" {
+ name = hcp_waypoint_add_on.test.name
+}`, testAddOnConfig(templateName, appName, defName, addOnName))
+}
diff --git a/internal/provider/waypoint/resource_waypoint_add_on.go b/internal/provider/waypoint/resource_waypoint_add_on.go
new file mode 100644
index 000000000..faf5c8e76
--- /dev/null
+++ b/internal/provider/waypoint/resource_waypoint_add_on.go
@@ -0,0 +1,671 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package waypoint
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+
+ sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models"
+ "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/client/waypoint_service"
+ waypointmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-waypoint-service/preview/2023-08-18/models"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/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 = &AddOnResource{}
+var _ resource.ResourceWithImportState = &AddOnResource{}
+
+func NewAddOnResource() resource.Resource {
+ return &AddOnResource{}
+}
+
+// AddOnResource defines the resource implementation.
+type AddOnResource struct {
+ client *clients.Client
+}
+
+// AddOnResourceModel describes the resource data model.
+type AddOnResourceModel struct {
+ ID types.String `tfsdk:"id"`
+ Name types.String `tfsdk:"name"`
+ ProjectID types.String `tfsdk:"project_id"`
+ OrgID types.String `tfsdk:"organization_id"`
+ Summary types.String `tfsdk:"summary"`
+ Labels types.List `tfsdk:"labels"`
+ Description types.String `tfsdk:"description"`
+ ReadmeMarkdown types.String `tfsdk:"readme_markdown"`
+ CreatedBy types.String `tfsdk:"created_by"`
+ Count types.Int64 `tfsdk:"install_count"`
+ Status types.Int64 `tfsdk:"status"`
+ ApplicationID types.String `tfsdk:"application_id"`
+ DefinitionID types.String `tfsdk:"definition_id"`
+ // OutputValues types.List `tfsdk:"output_values"`
+
+ TerraformNoCodeModule types.Object `tfsdk:"terraform_no_code_module"`
+}
+
+func (r tfcNoCodeModule) attrTypes() map[string]attr.Type {
+ return map[string]attr.Type{
+ "source": types.StringType,
+ "version": types.StringType,
+ }
+}
+
+func (r *AddOnResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_waypoint_add_on"
+}
+
+// TODO: Make most of these computed because they are not used in the protos (Also add variables later)
+func (r *AddOnResource) 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: "Waypoint Add-on resource",
+
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ Description: "The ID of the Add-on.",
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "name": schema.StringAttribute{
+ Description: "The name of the Add-on.",
+ Required: true,
+ },
+ "organization_id": schema.StringAttribute{
+ Description: "The ID of the HCP organization where the Waypoint AddOn is located.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "project_id": schema.StringAttribute{
+ Description: "The ID of the HCP project where the Waypoint AddOn is located.",
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "summary": schema.StringAttribute{
+ Description: "A short summary of the Add-on.",
+ Computed: true,
+ },
+ "description": schema.StringAttribute{
+ Description: "A longer description of the Add-on.",
+ Computed: true,
+ },
+ "labels": schema.ListAttribute{
+ Computed: true,
+ Description: "List of labels attached to this Add-on.",
+ ElementType: types.StringType,
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "readme_markdown": schema.StringAttribute{
+ Description: "The markdown for the Add-on README.",
+ Computed: true,
+ },
+ "created_by": schema.StringAttribute{
+ Description: "The user who created the Add-on.",
+ Computed: true,
+ },
+ "install_count": schema.Int64Attribute{
+ Description: "The number of installed Add-ons for the same Application that share the same " +
+ "Add-on Definition.",
+ Computed: true,
+ },
+ "terraform_no_code_module": &schema.SingleNestedAttribute{
+ Computed: true,
+ Description: "Terraform Cloud no-code Module details.",
+ Attributes: map[string]schema.Attribute{
+ "source": &schema.StringAttribute{
+ Computed: true,
+ Description: "Terraform Cloud no-code Module Source",
+ },
+ "version": &schema.StringAttribute{
+ Computed: true,
+ Description: "Terraform Cloud no-code Module Version",
+ },
+ },
+ },
+ "definition_id": schema.StringAttribute{
+ Required: true,
+ Description: "The ID of the Add-on Definition that this Add-on is created from.",
+ },
+ "application_id": schema.StringAttribute{
+ Required: true,
+ Description: "The ID of the Application that this Add-on is created for.",
+ },
+ "status": schema.Int64Attribute{
+ Computed: true,
+ Description: "The status of the Terraform run for the Add-on.",
+ },
+ /*"output_values": schema.ListNestedAttribute{
+ Computed: true,
+ Description: "The output values of the Terraform run for the Add-on, sensitive values have type " +
+ "and value omitted.",
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Computed: true,
+ Description: "The name of the output value.",
+ },
+ "type": schema.StringAttribute{
+ Computed: true,
+ Description: "The type of the output value.",
+ },
+ "value": schema.StringAttribute{
+ Computed: true,
+ Description: "The value of the output value.",
+ },
+ "sensitive": schema.BoolAttribute{
+ Computed: true,
+ Description: "Whether the output value is sensitive.",
+ },
+ },
+ },
+ },*/
+ },
+ }
+}
+
+func (r *AddOnResource) 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
+}
+
+// TODO: Add support for new fields
+func (r *AddOnResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan *AddOnResourceModel
+
+ // Read 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() {
+ projectID = plan.ProjectID.ValueString()
+ }
+
+ orgID := r.client.Config.OrganizationID
+ loc := &sharedmodels.HashicorpCloudLocationLocation{
+ OrganizationID: orgID,
+ ProjectID: projectID,
+ }
+
+ client := r.client
+ ns, err := getNamespaceByLocation(ctx, client, loc)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "error getting namespace by location",
+ err.Error(),
+ )
+ return
+ }
+
+ stringLabels := []string{}
+ if !plan.Labels.IsNull() && !plan.Labels.IsUnknown() {
+ diagnostics := plan.Labels.ElementsAs(ctx, &stringLabels, false)
+ if diagnostics.HasError() {
+ resp.Diagnostics.AddError(
+ "error converting labels",
+ "The list of labels was incorrectly formated",
+ )
+ return
+ }
+ }
+
+ // An application ref can only have one of ID or Name set,
+ // we ask for ID, so we will set ID
+ applicationID := plan.ApplicationID.ValueString()
+ applicationRefModel := &waypointmodels.HashicorpCloudWaypointRefApplication{}
+ if applicationID != "" {
+ applicationRefModel.ID = applicationID
+ } else {
+ resp.Diagnostics.AddError(
+ "error reading application ID",
+ "The application ID was missing",
+ )
+ return
+ }
+
+ // Similarly, a definition ref can only have one of ID or Name set,
+ // we ask for ID, so we will set ID
+ definitionID := plan.DefinitionID.ValueString()
+ definitionRefModel := &waypointmodels.HashicorpCloudWaypointRefAddOnDefinition{}
+ if definitionID != "" {
+ definitionRefModel.ID = definitionID
+ } else {
+ resp.Diagnostics.AddError(
+ "error reading definition ID",
+ "The definition ID was missing",
+ )
+ return
+ }
+
+ modelBody := &waypointmodels.HashicorpCloudWaypointWaypointServiceCreateAddOnBody{
+ Name: plan.Name.ValueString(),
+ Application: applicationRefModel,
+ Definition: definitionRefModel,
+ }
+
+ params := &waypoint_service.WaypointServiceCreateAddOnParams{
+ NamespaceID: ns.ID,
+ Body: modelBody,
+ }
+ responseAddOn, err := r.client.Waypoint.WaypointServiceCreateAddOn(params, nil)
+ if err != nil {
+ resp.Diagnostics.AddError("Error creating add-on", err.Error())
+ return
+ }
+
+ var addOn *waypointmodels.HashicorpCloudWaypointAddOn
+ if responseAddOn.Payload != nil {
+ addOn = responseAddOn.Payload.AddOn
+ }
+ if addOn == nil {
+ resp.Diagnostics.AddError("unknown error creating add-on", "empty add-on returned")
+ return
+ }
+
+ plan.ID = types.StringValue(addOn.ID)
+ plan.Name = types.StringValue(addOn.Name)
+ plan.ProjectID = types.StringValue(projectID)
+ plan.OrgID = types.StringValue(orgID)
+ plan.Summary = types.StringValue(addOn.Summary)
+
+ plan.Description = types.StringValue(addOn.Description)
+ // set plan.description if it's not null or addOn.description is not empty
+ if addOn.Description == "" {
+ plan.Description = types.StringNull()
+ }
+ plan.ReadmeMarkdown = types.StringValue(addOn.ReadmeMarkdown.String())
+ // set plan.readme if it's not null or addOn.readme is not empty
+ if addOn.ReadmeMarkdown.String() == "" {
+ plan.ReadmeMarkdown = types.StringNull()
+ }
+
+ labels, diags := types.ListValueFrom(ctx, types.StringType, addOn.Labels)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ plan.Labels = labels
+
+ if addOn.TerraformNocodeModule != nil {
+ tfcNoCode := &tfcNoCodeModule{}
+ if addOn.TerraformNocodeModule.Source != "" {
+ tfcNoCode.Source = types.StringValue(addOn.TerraformNocodeModule.Source)
+ }
+ if addOn.TerraformNocodeModule.Version != "" {
+ tfcNoCode.Version = types.StringValue(addOn.TerraformNocodeModule.Version)
+ }
+ plan.TerraformNoCodeModule, diags = types.ObjectValueFrom(ctx, tfcNoCode.attrTypes(), tfcNoCode)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
+ // Display the reference to the Definition in the plan
+ if addOn.Definition != nil {
+ if addOn.Definition.ID != "" {
+ plan.DefinitionID = types.StringValue(addOn.Definition.ID)
+ }
+ }
+
+ // Display the reference to the Application in the plan
+ if addOn.Application != nil {
+ if addOn.Application.ID != "" {
+ plan.ApplicationID = types.StringValue(addOn.Application.ID)
+ }
+ }
+
+ plan.CreatedBy = types.StringValue(addOn.CreatedBy)
+
+ // If we can process status as an int64, add it to the plan
+ statusNum, err := strconv.ParseInt(addOn.Count, 10, 64)
+ if err != nil {
+ resp.Diagnostics.AddError("Error parsing installed Add-on status", err.Error())
+ } else {
+ plan.Status = types.Int64Value(statusNum)
+ }
+
+ // If we can process count as an int64, add it to the plan
+ installedCount, err := strconv.ParseInt(addOn.Count, 10, 64)
+ if err != nil {
+ resp.Diagnostics.AddError("Error parsing installed Add-ons count", err.Error())
+ } else {
+ plan.Count = types.Int64Value(installedCount)
+ }
+
+ // TODO: Add support for output values
+
+ // Write logs using the tflog package
+ // Documentation: https://terraform.io/plugin/log
+ tflog.Trace(ctx, "created add-on resource")
+
+ // Save plan into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+}
+
+// TODO: Add support for new fields
+func (r *AddOnResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var state *AddOnResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ projectID := r.client.Config.ProjectID
+
+ orgID := r.client.Config.OrganizationID
+ loc := &sharedmodels.HashicorpCloudLocationLocation{
+ OrganizationID: orgID,
+ ProjectID: projectID,
+ }
+
+ client := r.client
+
+ addOn, err := clients.GetAddOnByID(ctx, client, loc, state.ID.ValueString())
+ if err != nil {
+ if clients.IsResponseCodeNotFound(err) {
+ tflog.Info(ctx, "Add-on not found for organization, removing from state.")
+ resp.State.RemoveResource(ctx)
+ return
+ }
+ resp.Diagnostics.AddError("Error reading Add-on", err.Error())
+ return
+ }
+
+ state.ID = types.StringValue(addOn.ID)
+ state.Name = types.StringValue(addOn.Name)
+ state.ProjectID = types.StringValue(projectID)
+ state.OrgID = types.StringValue(orgID)
+ state.Summary = types.StringValue(addOn.Summary)
+
+ state.Description = types.StringValue(addOn.Description)
+ // set plan.description if it's not null or addOn.description is not empty
+ if addOn.Description == "" {
+ state.Description = types.StringNull()
+ }
+ state.ReadmeMarkdown = types.StringValue(addOn.ReadmeMarkdown.String())
+ // set plan.readme if it's not null or addOn.readme is not empty
+ if addOn.ReadmeMarkdown.String() == "" {
+ state.ReadmeMarkdown = types.StringNull()
+ }
+
+ labels, diags := types.ListValueFrom(ctx, types.StringType, addOn.Labels)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ state.Labels = labels
+
+ if addOn.TerraformNocodeModule != nil {
+ tfcNoCode := &tfcNoCodeModule{
+ Source: types.StringValue(addOn.TerraformNocodeModule.Source),
+ Version: types.StringValue(addOn.TerraformNocodeModule.Version),
+ }
+ state.TerraformNoCodeModule, diags = types.ObjectValueFrom(ctx, tfcNoCode.attrTypes(), tfcNoCode)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
+ state.CreatedBy = types.StringValue(addOn.CreatedBy)
+
+ // TODO: Error out here on failure to convert?
+
+ // If we can process status as an int64, add it to the plan
+ statusNum, err := strconv.ParseInt(addOn.Count, 10, 64)
+ if err != nil {
+ resp.Diagnostics.AddError("Error parsing installed Add-on status", err.Error())
+ } else {
+ state.Status = types.Int64Value(statusNum)
+ }
+
+ // If we can process count as an int64, add it to the plan
+ installedCount, err := strconv.ParseInt(addOn.Count, 10, 64)
+ if err != nil {
+ resp.Diagnostics.AddError("Error parsing installed Add-ons count", err.Error())
+ } else {
+ state.Count = types.Int64Value(installedCount)
+ }
+
+ // Display the reference to the Definition in the state
+ if addOn.Definition != nil {
+ if addOn.Definition.ID != "" {
+ state.DefinitionID = types.StringValue(addOn.Definition.ID)
+ }
+ }
+
+ // Display the reference to the Application in the state
+ if addOn.Application != nil {
+ if addOn.Application.ID != "" {
+ state.ApplicationID = types.StringValue(addOn.Application.ID)
+ }
+ }
+
+ // TODO: Add support for output values
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+}
+
+// TODO: Add support for new fields
+func (r *AddOnResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ var plan *AddOnResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ projectID := r.client.Config.ProjectID
+
+ orgID := r.client.Config.OrganizationID
+ loc := &sharedmodels.HashicorpCloudLocationLocation{
+ OrganizationID: orgID,
+ ProjectID: projectID,
+ }
+
+ client := r.client
+ ns, err := getNamespaceByLocation(ctx, client, loc)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "error getting namespace by location",
+ err.Error(),
+ )
+ return
+ }
+
+ modelBody := &waypointmodels.HashicorpCloudWaypointWaypointServiceUpdateAddOnBody{
+ Name: plan.Name.ValueString(),
+ }
+
+ params := &waypoint_service.WaypointServiceUpdateAddOnParams{
+ NamespaceID: ns.ID,
+ Body: modelBody,
+ ExistingAddOnID: plan.ID.ValueString(),
+ }
+ def, err := r.client.Waypoint.WaypointServiceUpdateAddOn(params, nil)
+ if err != nil {
+ resp.Diagnostics.AddError("Error updating Add-on", err.Error())
+ return
+ }
+
+ var addOn *waypointmodels.HashicorpCloudWaypointAddOn
+ if def.Payload != nil {
+ addOn = def.Payload.AddOn
+ }
+ if addOn == nil {
+ resp.Diagnostics.AddError("Unknown error updating Add-on", "Empty Add-on found")
+ return
+ }
+
+ plan.ID = types.StringValue(addOn.ID)
+ plan.Name = types.StringValue(addOn.Name)
+ plan.ProjectID = types.StringValue(projectID)
+ plan.OrgID = types.StringValue(orgID)
+ plan.Summary = types.StringValue(addOn.Summary)
+
+ plan.Description = types.StringValue(addOn.Description)
+ // set plan.description if it's not null or addOn.description is not empty
+ if addOn.Description == "" {
+ plan.Description = types.StringNull()
+ }
+ plan.ReadmeMarkdown = types.StringValue(addOn.ReadmeMarkdown.String())
+ // set plan.readme if it's not null or addOn.readme is not empty
+ if addOn.ReadmeMarkdown.String() == "" {
+ plan.ReadmeMarkdown = types.StringNull()
+ }
+
+ labels, diags := types.ListValueFrom(ctx, types.StringType, addOn.Labels)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ plan.Labels = labels
+
+ if addOn.TerraformNocodeModule != nil {
+ tfcNoCode := &tfcNoCodeModule{
+ Source: types.StringValue(addOn.TerraformNocodeModule.Source),
+ Version: types.StringValue(addOn.TerraformNocodeModule.Version),
+ }
+ plan.TerraformNoCodeModule, diags = types.ObjectValueFrom(ctx, tfcNoCode.attrTypes(), tfcNoCode)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
+ // Display the reference to the Definition in the plan
+ if addOn.Definition != nil {
+ if addOn.Definition.ID != "" {
+ plan.DefinitionID = types.StringValue(addOn.Definition.ID)
+ }
+ }
+
+ // Display the reference to the Application in the plan
+ if addOn.Application != nil {
+ if addOn.Application.ID != "" {
+ plan.ApplicationID = types.StringValue(addOn.Application.ID)
+ }
+ }
+
+ plan.CreatedBy = types.StringValue(addOn.CreatedBy)
+
+ // If we can process status as an int64, add it to the plan
+ statusNum, err := strconv.ParseInt(addOn.Count, 10, 64)
+ if err != nil {
+ resp.Diagnostics.AddError("Error parsing installed Add-on status", err.Error())
+ } else {
+ plan.Status = types.Int64Value(statusNum)
+ }
+
+ // If we can process count as an int64, add it to the plan
+ installedCount, err := strconv.ParseInt(addOn.Count, 10, 64)
+ if err != nil {
+ resp.Diagnostics.AddError("Error parsing installed Add-ons count", err.Error())
+ } else {
+ plan.Count = types.Int64Value(installedCount)
+ }
+
+ // TODO: Add support for output values
+
+ // Write logs using the tflog package
+ // Documentation: https://terraform.io/plugin/log
+ tflog.Trace(ctx, "updated add-on resource")
+
+ // Save updated data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+}
+
+func (r *AddOnResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var state *AddOnResourceModel
+
+ // Read Terraform prior state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ projectID := r.client.Config.ProjectID
+
+ loc := &sharedmodels.HashicorpCloudLocationLocation{
+ OrganizationID: r.client.Config.OrganizationID,
+ ProjectID: projectID,
+ }
+
+ client := r.client
+ ns, err := getNamespaceByLocation(ctx, client, loc)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Deleting TFC Config",
+ err.Error(),
+ )
+ return
+ }
+
+ params := &waypoint_service.WaypointServiceDestroyAddOnParams{
+ NamespaceID: ns.ID,
+ AddOnID: state.ID.ValueString(),
+ }
+
+ _, err = r.client.Waypoint.WaypointServiceDestroyAddOn(params, nil)
+ if err != nil {
+ if clients.IsResponseCodeNotFound(err) {
+ tflog.Info(ctx, "Add-on not found for organization during delete call, ignoring")
+ return
+ }
+ resp.Diagnostics.AddError(
+ "Error Deleting Add-on",
+ err.Error(),
+ )
+ return
+ }
+
+}
+
+func (r *AddOnResource) 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_add_on_definition_test.go b/internal/provider/waypoint/resource_waypoint_add_on_definition_test.go
index e39a6958b..03096bad0 100644
--- a/internal/provider/waypoint/resource_waypoint_add_on_definition_test.go
+++ b/internal/provider/waypoint/resource_waypoint_add_on_definition_test.go
@@ -19,7 +19,7 @@ import (
)
func TestAccWaypoint_Add_On_Definition_basic(t *testing.T) {
- var appTemplateModel waypoint.AddOnDefinitionResourceModel
+ var addOnDefinitionModel waypoint.AddOnDefinitionResourceModel
resourceName := "hcp_waypoint_add_on_definition.test"
name := generateRandomName()
updatedName := generateRandomName()
@@ -27,21 +27,21 @@ func TestAccWaypoint_Add_On_Definition_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
- CheckDestroy: testAccCheckWaypointAddOnDefinitionDestroy(t, &appTemplateModel),
+ CheckDestroy: testAccCheckWaypointAddOnDefinitionDestroy(t, &addOnDefinitionModel),
Steps: []resource.TestStep{
{
Config: testAddOnDefinitionConfig(name),
Check: resource.ComposeTestCheckFunc(
- testAccCheckWaypointAddOnDefinitionExists(t, resourceName, &appTemplateModel),
- testAccCheckWaypointAddOnDefinitionName(t, &appTemplateModel, name),
+ testAccCheckWaypointAddOnDefinitionExists(t, resourceName, &addOnDefinitionModel),
+ testAccCheckWaypointAddOnDefinitionName(t, &addOnDefinitionModel, name),
resource.TestCheckResourceAttr(resourceName, "name", name),
),
},
{
Config: testAddOnDefinitionConfig(updatedName),
Check: resource.ComposeTestCheckFunc(
- testAccCheckWaypointAddOnDefinitionExists(t, resourceName, &appTemplateModel),
- testAccCheckWaypointAddOnDefinitionName(t, &appTemplateModel, updatedName),
+ testAccCheckWaypointAddOnDefinitionExists(t, resourceName, &addOnDefinitionModel),
+ testAccCheckWaypointAddOnDefinitionName(t, &addOnDefinitionModel, updatedName),
resource.TestCheckResourceAttr(resourceName, "name", updatedName),
),
},
diff --git a/internal/provider/waypoint/resource_waypoint_add_on_test.go b/internal/provider/waypoint/resource_waypoint_add_on_test.go
new file mode 100644
index 000000000..d80b5ff28
--- /dev/null
+++ b/internal/provider/waypoint/resource_waypoint_add_on_test.go
@@ -0,0 +1,165 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+
+package waypoint_test
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "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"
+
+ sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models"
+)
+
+func TestAccWaypoint_Add_On_basic(t *testing.T) {
+ var addOnModel waypoint.AddOnResourceModel
+ resourceName := "hcp_waypoint_add_on.test"
+ addOnName := generateRandomName()
+ templateName := generateRandomName()
+ appName := generateRandomName()
+ defName := generateRandomName()
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckWaypointAddOnDestroy(t, &addOnModel),
+ Steps: []resource.TestStep{
+ {
+ Config: testAddOnConfig(templateName, appName, defName, addOnName),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckWaypointAddOnExists(t, resourceName, &addOnModel),
+ testAccCheckWaypointAddOnName(t, &addOnModel, addOnName),
+ resource.TestCheckResourceAttr(resourceName, "name", addOnName),
+ ),
+ },
+ },
+ })
+}
+
+// simple attribute check on the add-on definition received from the API
+func testAccCheckWaypointAddOnName(t *testing.T, addOnModel *waypoint.AddOnResourceModel, nameValue string) resource.TestCheckFunc {
+ return func(s *terraform.State) error {
+ if addOnModel.Name.ValueString() != nameValue {
+ return fmt.Errorf("expected add-on name to be %s, but got %s", nameValue, addOnModel.Name.ValueString())
+ }
+ return nil
+ }
+}
+
+func testAccCheckWaypointAddOnExists(t *testing.T, resourceName string, addOnModel *waypoint.AddOnResourceModel) 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 and ID from state
+ projectID := rs.Primary.Attributes["project_id"]
+ addOnID := rs.Primary.Attributes["id"]
+ orgID := client.Config.OrganizationID
+
+ loc := &sharedmodels.HashicorpCloudLocationLocation{
+ OrganizationID: orgID,
+ ProjectID: projectID,
+ }
+
+ // Fetch the add-on
+ addOn, err := clients.GetAddOnByID(context.Background(), client, loc, addOnID)
+ if err != nil {
+ return err
+ }
+
+ // at this time we're only verifing existence and not checking all the
+ // values, so only set name and ID for now
+ addOnModel.Name = types.StringValue(addOn.Name)
+ addOnModel.ID = types.StringValue(addOn.ID)
+
+ return nil
+ }
+}
+
+func testAccCheckWaypointAddOnDestroy(t *testing.T, addOnModel *waypoint.AddOnResourceModel) resource.TestCheckFunc {
+ return func(_ *terraform.State) error {
+ client := acctest.HCPClients(t)
+ id := addOnModel.ID.ValueString()
+ projectID := client.Config.ProjectID
+ orgID := client.Config.OrganizationID
+
+ loc := &sharedmodels.HashicorpCloudLocationLocation{
+ OrganizationID: orgID,
+ ProjectID: projectID,
+ }
+
+ addOn, err := clients.GetAddOnByID(context.Background(), client, loc, id)
+ if err != nil {
+ // expected (500 because the application is destroyed as well)
+ 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 addOn != nil {
+ return fmt.Errorf("expected add-on to be destroyed, but it still exists")
+ }
+
+ return fmt.Errorf("Both add-on and error were nil in destroy check, this should not happen")
+ }
+}
+
+// These are hardcoded project and no-code module values because they work. The
+// automated tests do not run acceptance tests at this time, so these should be
+// sufficient for now.
+func testAddOnConfig(templateName string, appName string, defName string, addOnName string) string {
+ return fmt.Sprintf(`
+resource "hcp_waypoint_application_template" "test" {
+ name = "%s"
+ summary = "some summary for fun"
+ readme_markdown_template = base64encode("# Some Readme")
+ terraform_no_code_module = {
+ source = "private/waypoint-tfc-testing/waypoint-template-starter/null"
+ version = "0.0.2"
+ }
+ terraform_cloud_workspace_details = {
+ name = "Default Project"
+ terraform_project_id = "prj-gfVyPJ2q2Aurn25o"
+ }
+ labels = ["one", "two"]
+}
+
+resource "hcp_waypoint_application" "test" {
+ name = "%s"
+ application_template_id = hcp_waypoint_application_template.test.id
+}
+
+resource "hcp_waypoint_add_on_definition" "test" {
+ name = "%s"
+ summary = "some summary for fun"
+ description = "some description for fun"
+ terraform_no_code_module = {
+ source = "private/waypoint-tfc-testing/waypoint-template-starter/null"
+ version = "0.0.2"
+ }
+ terraform_cloud_workspace_details = {
+ name = "Default Project"
+ terraform_project_id = "prj-gfVyPJ2q2Aurn25o"
+ }
+}
+
+resource "hcp_waypoint_add_on" "test" {
+ name = "%s"
+ application_id = hcp_waypoint_application.test.id
+ definition_id = hcp_waypoint_add_on_definition.test.id
+}`, templateName, appName, defName, addOnName)
+}
diff --git a/templates/resources/waypoint_add_on.md.tmpl b/templates/resources/waypoint_add_on.md.tmpl
new file mode 100644
index 000000000..8da38ebea
--- /dev/null
+++ b/templates/resources/waypoint_add_on.md.tmpl
@@ -0,0 +1,14 @@
+---
+page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}"
+subcategory: "HCP Waypoint"
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} `{{.Type}}`
+
+-> **Note:** HCP Waypoint is currently in public beta.
+
+{{ .Description | trimspace }}
+
+{{ .SchemaMarkdown | trimspace }}
\ No newline at end of file