Skip to content

Commit

Permalink
[HPR-859] Add HCP Packer bucket channel resource (#435)
Browse files Browse the repository at this point in the history
* Add working create/delete resource

* Add ability to update channel assignment
* Add validation for channel and bucket names
* Add support for resource import
* Update resource schema to use a single iteration block
* Update changelog
  • Loading branch information
nywilken committed Feb 9, 2023
1 parent eeb6e2a commit 99e5cb7
Show file tree
Hide file tree
Showing 12 changed files with 915 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/435.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
New resource `hcp_packer_channel` to create, or update an existing, channel with or without an assigned iteration.
```
118 changes: 118 additions & 0 deletions docs/resources/packer_channel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
page_title: "Resource hcp_packer_channel - terraform-provider-hcp"
subcategory: ""
description: |-
The Packer Channel resource allows you to manage image bucket channels within an active HCP Packer Registry.
---

# hcp_packer_channel (Resource)

The Packer Channel resource allows you to manage image bucket channels within an active HCP Packer Registry.

## Example Usage

To create a channel with no assigned iteration.
```terraform
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
}
```

To create, or update an existing, channel with an assigned iteration.
```terraform
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
id = "iteration-id"
}
}
# Update assigned iteration using an iteration fingerprint
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
fingerprint = "fingerprint-associated-to-iteration"
}
}
# Update assigned iteration using an iteration incremental version
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
// incremental_version is the version number assigned to a completed iteration.
incremental_version = 1
}
}
```

Using the latest channel to create a new channel with an assigned iteration.
```terraform
data "hcp_packer_image_iteration" "latest" {
bucket_name = "alpine"
channel = "latest"
}
resource "hcp_packer_channel" "staging" {
name = staging
bucket_name = alpine
iteration {
id = data.hcp_packer_image_iteration.latest.id
}
}
```


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

### Required

- `bucket_name` (String) The slug of the HCP Packer Registry image bucket where the channel should be managed in.
- `name` (String) The name of the channel being managed.

### Optional

- `iteration` (Block List, Max: 1) The iteration assigned to the channel. (see [below for nested schema](#nestedblock--iteration))
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))

### Read-Only

- `author_id` (String) The author of the channel.
- `created_at` (String) Creation time of this build.
- `id` (String) The ID of this resource.
- `organization_id` (String) The ID of the organization this HCP Packer registry is located in.
- `project_id` (String) The ID of the project this HCP Packer registry is located in.
- `updated_at` (String) The author of the channel.

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

Optional:

- `fingerprint` (String) The fingerprint of the iteration assigned to the channel.
- `id` (String) The ID of the iteration assigned to the channel.
- `incremental_version` (Number) The incremental_version of the iteration assigned to the channel.


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

Optional:

- `create` (String)
- `default` (String)
- `delete` (String)
- `update` (String)

## Import

Import is supported using the following syntax:

```shell
# The import ID requires the bucket and channel name in the following format {bucket_name}:{name}
terraform import hcp_packer_channel.staging alpine:staging
```
2 changes: 2 additions & 0 deletions examples/resources/hcp_packer_channel/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# The import ID requires the bucket and channel name in the following format {bucket_name}:{name}
terraform import hcp_packer_channel.staging alpine:staging
4 changes: 4 additions & 0 deletions examples/resources/hcp_packer_channel/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
}
27 changes: 27 additions & 0 deletions examples/resources/hcp_packer_channel/resource_assignment.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
id = "iteration-id"
}
}

# Update assigned iteration using an iteration fingerprint
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
fingerprint = "fingerprint-associated-to-iteration"
}
}

# Update assigned iteration using an iteration incremental version
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
// incremental_version is the version number assigned to a completed iteration.
incremental_version = 1
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
data "hcp_packer_image_iteration" "latest" {
bucket_name = "alpine"
channel = "latest"
}

resource "hcp_packer_channel" "staging" {
name = staging
bucket_name = alpine
iteration {
id = data.hcp_packer_image_iteration.latest.id
}
}
97 changes: 96 additions & 1 deletion internal/clients/packer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func GetPackerChannelBySlug(ctx context.Context, client *Client, loc *sharedmode
return getResp.Payload.Channel, nil
}

// GetIteration queries the HCP Packer registry for an existing bucket iteration.
// GetIterationFromID queries the HCP Packer registry for an existing bucket iteration.
func GetIterationFromID(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation,
bucketslug string, iterationID string) (*packermodels.HashicorpCloudPackerIteration, error) {
params := packer_service.NewPackerServiceGetIterationParamsWithContext(ctx)
Expand All @@ -48,6 +48,101 @@ func GetIterationFromID(ctx context.Context, client *Client, loc *sharedmodels.H
return it.Payload.Iteration, nil
}

// CreateBucketChannel creates a channel on the named bucket.
func CreateBucketChannel(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, bucketSlug string, channelSlug string,
iteration *packermodels.HashicorpCloudPackerIteration) (*packermodels.HashicorpCloudPackerChannel, error) {
params := packer_service.NewPackerServiceCreateChannelParamsWithContext(ctx)
params.LocationOrganizationID = loc.OrganizationID
params.LocationProjectID = loc.ProjectID
params.BucketSlug = bucketSlug
params.Body.Slug = channelSlug

if iteration != nil {
switch {
case iteration.ID != "":
params.Body.IterationID = iteration.ID
case iteration.Fingerprint != "":
params.Body.Fingerprint = iteration.Fingerprint
case iteration.IncrementalVersion > 0:
params.Body.IncrementalVersion = iteration.IncrementalVersion
}
}

channel, err := client.Packer.PackerServiceCreateChannel(params, nil)
if err != nil {
err := err.(*packer_service.PackerServiceCreateChannelDefault)
return nil, errors.New(err.Payload.Message)
}

return channel.GetPayload().Channel, nil
}

// UpdateBucketChannel updates the named channel.
func UpdateBucketChannel(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, bucketSlug string, channelSlug string,
iteration *packermodels.HashicorpCloudPackerIteration) (*packermodels.HashicorpCloudPackerChannel, error) {
params := packer_service.NewPackerServiceUpdateChannelParamsWithContext(ctx)
params.LocationOrganizationID = loc.OrganizationID
params.LocationProjectID = loc.ProjectID
params.BucketSlug = bucketSlug
params.Slug = channelSlug

if iteration != nil {
switch {
case iteration.ID != "":
params.Body.IterationID = iteration.ID
case iteration.Fingerprint != "":
params.Body.Fingerprint = iteration.Fingerprint
case iteration.IncrementalVersion > 0:
params.Body.IncrementalVersion = iteration.IncrementalVersion
}
}

channel, err := client.Packer.PackerServiceUpdateChannel(params, nil)
if err != nil {
err := err.(*packer_service.PackerServiceUpdateChannelDefault)
return nil, errors.New(err.Payload.Message)
}

return channel.GetPayload().Channel, nil
}

// DeleteBucketChannel deletes a channel from the named bucket.
func DeleteBucketChannel(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, bucketSlug, channelSlug string) (*packermodels.HashicorpCloudPackerChannel, error) {
params := packer_service.NewPackerServiceDeleteChannelParamsWithContext(ctx)
params.LocationOrganizationID = loc.OrganizationID
params.LocationProjectID = loc.ProjectID
params.BucketSlug = bucketSlug
params.Slug = channelSlug

req, err := client.Packer.PackerServiceDeleteChannel(params, nil)
if err != nil {
err := err.(*packer_service.PackerServiceDeleteChannelDefault)
return nil, errors.New(err.Payload.Message)
}

if !req.IsSuccess() {
return nil, errors.New("failed to delete channel")
}

return nil, nil
}

// ListBucketChannels queries the HCP Packer registry for channels associated to the specified bucket.
func ListBucketChannels(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, bucketSlug string) (*packermodels.HashicorpCloudPackerListChannelsResponse, error) {
params := packer_service.NewPackerServiceListChannelsParams()
params.LocationOrganizationID = loc.OrganizationID
params.LocationProjectID = loc.ProjectID
params.BucketSlug = bucketSlug

req, err := client.Packer.PackerServiceListChannels(params, nil)
if err != nil {
err := err.(*packer_service.PackerServiceListChannelsDefault)
return nil, errors.New(err.Payload.Message)
}

return req.Payload, nil
}

// handleGetChannelError returns a formatted error for the GetChannel error.
// The upstream API does a good job of providing detailed error messages so we just display the error message, with no status code.
func handleGetChannelError(err *packer_service.PackerServiceGetChannelDefault) error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
var (
acctestAlpineBucket = fmt.Sprintf("alpine-acc-%s", time.Now().Format("200601021504"))
acctestUbuntuBucket = fmt.Sprintf("ubuntu-acc-%s", time.Now().Format("200601021504"))
acctestProductionChannel = "production"
acctestProductionChannel = fmt.Sprintf("packer-acc-channel-%s", time.Now().Format("200601021504"))
)

var (
Expand Down
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func New() func() *schema.Provider {
"hcp_hvn": resourceHvn(),
"hcp_hvn_peering_connection": resourceHvnPeeringConnection(),
"hcp_hvn_route": resourceHvnRoute(),
"hcp_packer_channel": resourcePackerChannel(),
"hcp_vault_cluster": resourceVaultCluster(),
"hcp_vault_cluster_admin_token": resourceVaultClusterAdminToken(),
},
Expand Down
Loading

0 comments on commit 99e5cb7

Please sign in to comment.