Skip to content

Commit

Permalink
feat: add resource class resource
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuszjenek committed May 27, 2024
1 parent 34c5b09 commit 189882c
Show file tree
Hide file tree
Showing 10 changed files with 361 additions and 29 deletions.
41 changes: 41 additions & 0 deletions docs/resources/resource_class.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "humanitec_resource_class Resource - terraform-provider-humanitec"
subcategory: ""
description: |-
Resource Classes provide a way of specializing Resource Types. Developers can set the class of a Resource alongside the type in their Score File. Platform teams can match the class of a Resource via Matching Criteria.
---

# humanitec_resource_class (Resource)

Resource Classes provide a way of specializing Resource Types. Developers can set the class of a Resource alongside the type in their Score File. Platform teams can match the class of a Resource via Matching Criteria.

## Example Usage

```terraform
resource "humanitec_resource_class" "resource_class" {
id = "example"
resource_type = "mysql"
description = "An example resource class"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `id` (String) Reflects the class string.
- `resource_type` (String) Defines the resource type this class is applicable for.

### Optional

- `description` (String) A human readable description when this class should be used.

## Import

Import is supported using the following syntax:

```shell
terraform import humanitec_resource_class.example resource_type/class_id
```
1 change: 1 addition & 0 deletions examples/resources/humanitec_resource_class/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import humanitec_resource_class.example resource_type/class_id
5 changes: 5 additions & 0 deletions examples/resources/humanitec_resource_class/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resource "humanitec_resource_class" "resource_class" {
id = "example"
resource_type = "mysql"
description = "An example resource class"
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/hashicorp/terraform-plugin-go v0.22.2
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0
github.com/humanitec/humanitec-go-autogen v0.0.0-20240516095603-93b078e55cd9
github.com/humanitec/humanitec-go-autogen v0.0.0-20240523102513-6cf639116144
github.com/stretchr/testify v1.9.0
sigs.k8s.io/yaml v1.4.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/humanitec/humanitec-go-autogen v0.0.0-20240516095603-93b078e55cd9 h1:rW4rt9sN3+4JbltxMkrCkHtqFm7ZInNkjCaY9YhD/u0=
github.com/humanitec/humanitec-go-autogen v0.0.0-20240516095603-93b078e55cd9/go.mod h1:WqItJ/MhAHcjP7LIhIt2/NrgXeXRbLuxvXlin7qY0j4=
github.com/humanitec/humanitec-go-autogen v0.0.0-20240523102513-6cf639116144 h1:h1HURx3DvUaYFxGEFF+iK4eQ4mvKFJtRVnDBHeEZNBM=
github.com/humanitec/humanitec-go-autogen v0.0.0-20240523102513-6cf639116144/go.mod h1:WqItJ/MhAHcjP7LIhIt2/NrgXeXRbLuxvXlin7qY0j4=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ func (p *HumanitecProvider) Resources(ctx context.Context) []func() resource.Res
NewResourcePipeline,
NewResourcePipelineCriteria,
NewResourceRegistry,
NewResourceResourceClass,
NewResourceResourceDriver,
NewResourceRule,
NewResourceSecretStore,
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/resource_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ func parseEnvironmentResponse(appID string, res *client.EnvironmentResponse, dat
if res.FromDeploy != nil {
fromDeployId = &res.FromDeploy.Id
}

data.FromDeployID = types.StringPointerValue(fromDeployId)
data.AppID = types.StringValue(appID)
data.ID = types.StringValue(res.Id)
Expand Down
242 changes: 242 additions & 0 deletions internal/provider/resource_resource_class.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package provider

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/humanitec/humanitec-go-autogen"
"github.com/humanitec/humanitec-go-autogen/client"
)

// Ensure provider defined types fully satisfy framework interfaces
var _ resource.Resource = &ResourceResourceClass{}
var _ resource.ResourceWithImportState = &ResourceResourceClass{}

func NewResourceResourceClass() resource.Resource {
return &ResourceResourceClass{}
}

// ResourceResourceClass defines the resource implementation.
type ResourceResourceClass struct {
client *humanitec.Client
orgId string
}

// ResourceClassModel describes the app data model.
type ResourceClassModel struct {
ID types.String `tfsdk:"id"`
ResourceType types.String `tfsdk:"resource_type"`
Description types.String `tfsdk:"description"`
}

func (r *ResourceResourceClass) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_resource_class"
}

func (r *ResourceResourceClass) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Resource Classes provide a way of specializing Resource Types. Developers can set the class of a Resource alongside the type in their Score File. Platform teams can match the class of a Resource via Matching Criteria.",

Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
MarkdownDescription: "Reflects the class string.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"resource_type": schema.StringAttribute{
MarkdownDescription: "Defines the resource type this class is applicable for.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"description": schema.StringAttribute{
MarkdownDescription: "A human readable description when this class should be used.",
Optional: true,
},
},
}
}

func (r *ResourceResourceClass) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

resdata, ok := req.ProviderData.(*HumanitecData)

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 = resdata.Client
r.orgId = resdata.OrgID
}

func (r *ResourceResourceClass) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data *ResourceClassModel

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

id := data.ID.ValueString()
resourceType := data.ResourceType.ValueString()
description := data.Description.ValueString()

httpResp, err := r.client.CreateResourceClassWithResponse(ctx, r.orgId, resourceType, client.ResourceClassRequest{
Id: id,
Description: description,
})
if err != nil {
resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to create resource class, got error: %s", err))
return
}

if httpResp.StatusCode() != 200 {
resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to create resource class, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body))
return
}

parseResourceClassResponse(httpResp.JSON200, data)

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *ResourceResourceClass) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data *ResourceClassModel

// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

id := data.ID.ValueString()
resourceType := data.ResourceType.ValueString()

httpResp, err := r.client.GetResourceClassWithResponse(ctx, r.orgId, resourceType, id)
if err != nil {
resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to read resource class, got error: %s", err))
return
}

if httpResp.StatusCode() != 200 {
resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to read resource class, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body))
return
}

parseResourceClassResponse(httpResp.JSON200, data)

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *ResourceResourceClass) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data *ResourceClassModel

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

id := data.ID.ValueString()
resourceType := data.ResourceType.ValueString()
description := data.Description.ValueString()

httpResp, err := r.client.UpdateResourceClassWithResponse(ctx, r.orgId, resourceType, id, client.UpdateResourceClassRequest{
Description: description,
})
if err != nil {
resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to update resource class, got error: %s", err))
return
}

if httpResp.StatusCode() != 200 {
resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to update resource class, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body))
return
}

parseResourceClassResponse(httpResp.JSON200, data)

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *ResourceResourceClass) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data *ResourceClassModel

// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

id := data.ID.ValueString()
resourceType := data.ResourceType.ValueString()

httpResp, err := r.client.DeleteResourceClassWithResponse(ctx, r.orgId, resourceType, id)
if err != nil {
resp.Diagnostics.AddError(HUM_CLIENT_ERR, fmt.Sprintf("Unable to delete resource class, got error: %s", err))
return
}

if httpResp.StatusCode() != 204 {
resp.Diagnostics.AddError(HUM_API_ERR, fmt.Sprintf("Unable to delete resource class, unexpected status code: %d, body: %s", httpResp.StatusCode(), httpResp.Body))
return
}
}

func (r *ResourceResourceClass) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, "/")

// ensure idParts elements are not empty
for _, idPart := range idParts {
if idPart == "" {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: resource_type/class_id. Got: %q", req.ID),
)
return
}
}

if len(idParts) == 2 {
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("resource_type"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[1])...)
} else {
resp.Diagnostics.AddError(
"Unexpected Import Identifier",
fmt.Sprintf("Expected import identifier with format: resource_type/class_id. Got: %q", req.ID),
)
return
}
}

func parseResourceClassResponse(resp *client.ResourceClassResponse, data *ResourceClassModel) {
data.ID = types.StringValue(resp.Id)
data.ResourceType = types.StringValue(resp.ResourceType)
data.Description = types.StringValue(resp.Description)
}
62 changes: 62 additions & 0 deletions internal/provider/resource_resource_class_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package provider

import (
"fmt"
"testing"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccResourceClass(t *testing.T) {
id := fmt.Sprintf("test-%d", time.Now().UnixNano())
description := "test-description"
updatedDescription := "test-updated-description"
resourceType := "mysql"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: testAccResourceClass(id, description, resourceType),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("humanitec_resource_class.class_test", "id", id),
resource.TestCheckResourceAttr("humanitec_resource_class.class_test", "description", description),
resource.TestCheckResourceAttr("humanitec_resource_class.class_test", "resource_type", resourceType),
),
},
// Update testing
{
Config: testAccResourceClass(id, updatedDescription, resourceType),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("humanitec_resource_class.class_test", "id", id),
resource.TestCheckResourceAttr("humanitec_resource_class.class_test", "description", updatedDescription),
resource.TestCheckResourceAttr("humanitec_resource_class.class_test", "resource_type", resourceType),
),
},
// ImportState testing
{
ResourceName: "humanitec_resource_class.class_test",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
return fmt.Sprintf("%s/%s", resourceType, id), nil
},
ImportState: true,
ImportStateVerify: true,
},
// Delete testing automatically occurs in TestCase
},
})
}

func testAccResourceClass(id, description, resourceType string) string {
return fmt.Sprintf(`
resource "humanitec_resource_class" "class_test" {
id = "%s"
description = "%s"
resource_type = "%s"
}
`, id, description, resourceType)
}
Loading

0 comments on commit 189882c

Please sign in to comment.