Skip to content

Commit

Permalink
HCP Packer Bucket resource
Browse files Browse the repository at this point in the history
  • Loading branch information
JenGoldstrich committed May 31, 2024
1 parent 73f6384 commit 7bcaea3
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 1 deletion.
43 changes: 43 additions & 0 deletions docs/resources/packer_bucket.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "hcp_packer_bucket Resource - terraform-provider-hcp"
subcategory: ""
description: |-
The Packer Bucket resource allows you to manage a bucket within an active HCP Packer Registry.
---

# hcp_packer_bucket (Resource)

The Packer Bucket resource allows you to manage a bucket within an active HCP Packer Registry.



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

### Required

- `name` (String) The name of the bucket being managed.

### Optional

- `project_id` (String) The ID of the HCP project where this bucket is located.
If not specified, the project specified in the HCP Provider config block will be used, if configured.
If a project is not configured in the HCP Provider config block, the oldest project in the organization will be used.
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))

### Read-Only

- `created_at` (String) The creation time of this bucket
- `id` (String) The ID of this resource.
- `organization_id` (String) The ID of the HCP organization where this bucket is located.
- `resource_name` (Boolean) The HCP resource name of the bucket

<a id="nestedblock--timeouts"></a>
### Nested Schema for `timeouts`

Optional:

- `create` (String)
- `default` (String)
- `delete` (String)
46 changes: 45 additions & 1 deletion internal/clients/packerv2/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ func ListBuckets(ctx context.Context, client *clients.Client, loc *sharedmodels.
params := packerservice.NewPackerServiceListBucketsParams()
params.LocationOrganizationID = loc.OrganizationID
params.LocationProjectID = loc.ProjectID
// Sort order is needed for acceptance tests.
params.SortingOrderBy = []string{"name"}
if nextPage != "" {
params.PaginationNextPageToken = &nextPage
Expand All @@ -49,3 +48,48 @@ func ListBuckets(ctx context.Context, client *clients.Client, loc *sharedmodels.
nextPage = pagination.NextPageToken
}
}

// GetBucket queries a HCP Packer bucket using the location and the buckets name
func GetBucket(ctx context.Context, client *clients.Client, loc *sharedmodels.HashicorpCloudLocationLocation, name string) (*Bucket, error) {
params := packerservice.NewPackerServiceGetBucketParams()
params.SetLocationOrganizationID(loc.OrganizationID)
params.SetLocationProjectID(loc.ProjectID)
params.SetBucketName(name)

resp, err := client.PackerV2.PackerServiceGetBucket(params, nil)

if err != nil {
return nil, formatGRPCError[*packerservice.PackerServiceGetBucketDefault](err)
}
return resp.GetPayload().Bucket, nil
}

func CreateBucket(ctx context.Context, client *clients.Client, loc *sharedmodels.HashicorpCloudLocationLocation, name string) (*Bucket, error) {
params := packerservice.NewPackerServiceCreateBucketParams()
params.SetLocationOrganizationID(loc.OrganizationID)
params.SetLocationProjectID(loc.ProjectID)
params.Body = &packermodels.HashicorpCloudPacker20230101CreateBucketBody{
Name: name,
}

resp, err := client.PackerV2.PackerServiceCreateBucket(params, nil)

if err != nil {
return nil, formatGRPCError[*packerservice.PackerServiceGetBucketDefault](err)
}
return resp.GetPayload().Bucket, nil
}

func DeleteBucket(ctx context.Context, client *clients.Client, loc *sharedmodels.HashicorpCloudLocationLocation, name string) error {
params := packerservice.NewPackerServiceDeleteBucketParams()
params.SetLocationOrganizationID(loc.OrganizationID)
params.SetLocationProjectID(loc.ProjectID)
params.SetBucketName(name)

_, err := client.PackerV2.PackerServiceDeleteBucket(params, nil)

if err != nil {
return formatGRPCError[*packerservice.PackerServiceGetBucketDefault](err)
}
return nil
}
1 change: 1 addition & 0 deletions internal/providersdkv2/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func New() func() *schema.Provider {
"hcp_packer_channel": resourcePackerChannel(),
"hcp_packer_channel_assignment": resourcePackerChannelAssignment(),
"hcp_packer_run_task": resourcePackerRunTask(),
"hcp_packer_bucket": resourcePackerBucket(),
"hcp_vault_cluster": resourceVaultCluster(),
"hcp_vault_cluster_admin_token": resourceVaultClusterAdminToken(),
"hcp_vault_plugin": resourceVaultPlugin(),
Expand Down
210 changes: 210 additions & 0 deletions internal/providersdkv2/resource_packer_bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package providersdkv2

import (
"context"
"errors"
"fmt"
"log"
"strings"

packermodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-packer-service/stable/2023-01-01/models"
sharedmodels "github.com/hashicorp/hcp-sdk-go/clients/cloud-shared/v1/models"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-hcp/internal/clients"
"github.com/hashicorp/terraform-provider-hcp/internal/clients/packerv2"
)

func resourcePackerBucket() *schema.Resource {
return &schema.Resource{
Description: "The Packer Bucket resource allows you to manage a bucket within an active HCP Packer Registry.",
CreateContext: resourcePackerBucketCreate,
DeleteContext: resourcePackerBucketDelete,
ReadContext: resourcePackerBucketRead,
Timeouts: &schema.ResourceTimeout{
Create: &defaultPackerTimeout,
Default: &defaultPackerTimeout,
Delete: &defaultPackerTimeout,
},
Importer: &schema.ResourceImporter{
StateContext: resourcePackerBucketImport,
},
Schema: map[string]*schema.Schema{
// Required inputs
"name": {
Description: "The name of the bucket being managed. ",
Type: schema.TypeString,
ForceNew: true,
Required: true,
ValidateDiagFunc: validateStringNotEmpty,
},
// Optional inputs
"project_id": {
Description: `
The ID of the HCP project where this bucket is located.
If not specified, the project specified in the HCP Provider config block will be used, if configured.
If a project is not configured in the HCP Provider config block, the oldest project in the organization will be used.`,
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.IsUUID,
Computed: true,
},
// Computed Values
"created_at": {
Description: "The creation time of this bucket",
Type: schema.TypeString,
Computed: true,
},
"organization_id": {
Description: "The ID of the HCP organization where this bucket is located.",
Type: schema.TypeString,
Computed: true,
},
"resource_name": {
Description: "The HCP resource name of the bucket",
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourcePackerBucketRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client)
loc, err := getAndUpdateLocationResourceData(d, client)
if err != nil {
return diag.FromErr(err)
}

bucketName := d.Get("name").(string)

log.Printf("[INFO] Reading HCP Packer bucket (%s) [project_id=%s, organization_id=%s]", bucketName, loc.ProjectID, loc.OrganizationID)

bucket, err := packerv2.GetBucket(ctx, client, loc, bucketName)
if err != nil {
return diag.FromErr(err)
}

if bucket == nil {
log.Printf(
"[WARN] HCP Packer bucket with (bucket_name %q) (project_id %q) not found, removing from state.",
bucketName, loc.ProjectID,
)
d.SetId("")
return nil
}
return nil
}

func resourcePackerBucketCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client)
loc, err := getAndUpdateLocationResourceData(d, client)
if err != nil {
return diag.FromErr(err)
}

name := d.Get("name").(string)

newBucket, err := packerv2.CreateBucket(ctx, client, loc, name)
if err == nil {
if newBucket == nil {
return diag.Errorf("expected a non-nil bucket from CreateBucket, but got nil")
}

// Handle successfully created bucket
setPackerBucketResourceData(d, newBucket)
}

return diag.FromErr(err)
}

func resourcePackerBucketDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client)
loc, err := getLocationResourceData(d, client)
if err != nil {
return diag.FromErr(err)
}

bucketName := d.Get("name").(string)

err = packerv2.DeleteBucket(ctx, client, loc, bucketName)
if err != nil {
return diag.FromErr(err)
}

return nil
}

func resourcePackerBucketImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
client := meta.(*clients.Client)
bucketName := ""
projectID := ""
var err error

idParts := strings.SplitN(d.Id(), ":", 2)
if len(idParts) == 2 { // {bucket_name}:{channel_name}
if idParts[0] == "" || idParts[1] == "" {
return nil, fmt.Errorf("unexpected format of ID (%q), expected {project_id:bucket_name}", d.Id())
}
projectID, err = GetProjectID(projectID, client.Config.ProjectID)
if err != nil {
return nil, fmt.Errorf("unable to retrieve project ID: %v", err)
}
bucketName = idParts[0]
} else if len(idParts) == 1 {
projectID = client.Config.ProjectID
bucketName = idParts[0]
} else {
return nil, fmt.Errorf("unexpected format of ID (%q), expected {bucket_name} or {project_id}:{bucket_name}", d.Id())
}

loc := &sharedmodels.HashicorpCloudLocationLocation{
OrganizationID: client.Config.OrganizationID,
ProjectID: projectID,
}
if err := setLocationResourceData(d, loc); err != nil {
return nil, err

}
bucket, err := packerv2.GetBucket(ctx, client, loc, bucketName)
if err != nil {
return nil, err
}
d.SetId(bucket.ID)

if err := d.Set("name", bucketName); err != nil {
return nil, err
}

if err := d.Set("resource_name", bucket.ResourceName); err != nil {
return nil, err
}
if err := d.Set("created_at", bucket.CreatedAt.String()); err != nil {
return nil, err
}
return []*schema.ResourceData{d}, nil
}

func setPackerBucketResourceData(d *schema.ResourceData, bucket *packermodels.HashicorpCloudPacker20230101Bucket) diag.Diagnostics {
if bucket == nil {
err := errors.New("unexpected empty bucket provided when setting state")
return diag.FromErr(err)
}

d.SetId(bucket.ID)

if err := d.Set("created_at", bucket.CreatedAt.String()); err != nil {
return diag.FromErr(err)
}

if err := d.Set("resource_name", bucket.ResourceName); err != nil {
return diag.FromErr(err)
}

return nil
}
60 changes: 60 additions & 0 deletions internal/providersdkv2/resource_packer_bucket_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package providersdkv2

import (
"fmt"
"testing"

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

func TestAccPackerBucket(t *testing.T) {
bucketName := testAccCreateSlug("BucketSimple")
bucketConfig := testAccPackerBucketBuilder("whatever", fmt.Sprintf("%q", bucketName))
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t, map[string]bool{"aws": false, "azure": false})
upsertRegistry(t)
},
ProtoV6ProviderFactories: testProtoV6ProviderFactories,
CheckDestroy: func(*terraform.State) error {
return nil
},
Steps: []resource.TestStep{
{
Config: testConfig(testAccConfigBuildersToString(bucketConfig)),
Check: testAccCheckPackerBucket(bucketConfig.BlockName(), bucketName),
},
{
ResourceName: bucketConfig.BlockName(),
ImportState: true,
ImportStateId: bucketName,
ImportStateVerify: true,
},
},
})
}

func testAccCheckPackerBucket(resourceName string, bucketName string) resource.TestCheckFunc {
tests := []resource.TestCheckFunc{
resource.TestCheckResourceAttrSet(resourceName, "created_at"),
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttr(resourceName, "name", bucketName),
resource.TestCheckResourceAttrSet(resourceName, "organization_id"),
resource.TestCheckResourceAttrSet(resourceName, "project_id"),
}
return resource.ComposeAggregateTestCheckFunc(tests...)
}

func testAccPackerBucketBuilder(uniqueName string, bucketName string) testAccConfigBuilderInterface {
return &testAccResourceConfigBuilder{
resourceType: "hcp_packer_bucket",
uniqueName: uniqueName,
attributes: map[string]string{
"name": bucketName,
},
}
}

0 comments on commit 7bcaea3

Please sign in to comment.