Skip to content

Commit

Permalink
HPR-1177: Assigned iteration should not be forced to be null when no …
Browse files Browse the repository at this point in the history
…iteration block is present (#521)

* don't force null iteration when iteration block is not present

* Update documentation

* Add changelog

* Minor docs adjustment

* Adjust template wording
  • Loading branch information
aidan-mundy committed Jun 6, 2023
1 parent d5f51c7 commit f42ada2
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 41 deletions.
3 changes: 3 additions & 0 deletions .changelog/521.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
Resolve unintended removal of assigned iteration when `iteration` block is not present on `hcp_packer_channel`
```
29 changes: 13 additions & 16 deletions docs/resources/packer_channel.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,42 @@ The Packer Channel resource allows you to manage image bucket channels within an

## Example Usage

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

To create, or update an existing, channel with an assigned iteration.
To create a channel with iteration assignment managed by Terraform.
```terraform
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
id = "iteration-id"
# Exactly one of `id`, `fingerprint` or `incremental_version` must be passed
id = "01H1SF9NWAK8AP25PAWDBGZ1YD"
# fingerprint = "01H1ZMW0Q2W6FT4FK27FQJCFG7"
# incremental_version = 1
}
}
# Update assigned iteration using an iteration fingerprint
# To configure a channel to have no assigned iteration, use a "zero value".
# The zero value for `id` and `fingerprint` is `""`; for `incremental_version`, it is `0`
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
# Exactly one of `id`, `fingerprint` or `incremental_version` must be passed
id = ""
# fingerprint = ""
# incremental_version = 0
}
}
```

Using the latest channel to create a new channel with an assigned iteration.
Using the latest channel to create a new channel with the latest complete iteration assigned.
```terraform
data "hcp_packer_image_iteration" "latest" {
bucket_name = "alpine"
Expand Down
26 changes: 11 additions & 15 deletions examples/resources/hcp_packer_channel/resource_assignment.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,22 @@ resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
id = "iteration-id"
# Exactly one of `id`, `fingerprint` or `incremental_version` must be passed
id = "01H1SF9NWAK8AP25PAWDBGZ1YD"
# fingerprint = "01H1ZMW0Q2W6FT4FK27FQJCFG7"
# incremental_version = 1
}
}

# Update assigned iteration using an iteration fingerprint
# To configure a channel to have no assigned iteration, use a "zero value".
# The zero value for `id` and `fingerprint` is `""`; for `incremental_version`, it is `0`
resource "hcp_packer_channel" "staging" {
name = "staging"
bucket_name = "alpine"
iteration {
fingerprint = "fingerprint-associated-to-iteration"
# Exactly one of `id`, `fingerprint` or `incremental_version` must be passed
id = ""
# fingerprint = ""
# incremental_version = 0
}
}

# 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
}
}

}
34 changes: 28 additions & 6 deletions internal/clients/packer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,40 @@ func GetPackerChannelBySlug(ctx context.Context, client *Client, loc *sharedmode
return getResp.Payload.Channel, nil
}

// GetIterationFromID queries the HCP Packer registry for an existing bucket iteration.
// GetIterationFromID queries the HCP Packer registry for an existing bucket iteration using its ULID.
func GetIterationFromID(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation,
bucketslug string, iterationID string) (*packermodels.HashicorpCloudPackerIteration, error) {
bucketSlug string, iterationID string) (*packermodels.HashicorpCloudPackerIteration, error) {
params := newGetIterationParams(ctx, loc, bucketSlug)
params.IterationID = &iterationID
return getIteration(client, params)
}

// GetIterationFromVersion queries the HCP Packer registry for an existing bucket iteration using its incremental version.
func GetIterationFromVersion(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation,
bucketSlug string, iterationIncrementalVersion int32) (*packermodels.HashicorpCloudPackerIteration, error) {
params := newGetIterationParams(ctx, loc, bucketSlug)
params.IncrementalVersion = &iterationIncrementalVersion
return getIteration(client, params)
}

// GetIterationFromFingerprint queries the HCP Packer registry for an existing bucket iteration using its fingerprint.
func GetIterationFromFingerprint(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation,
bucketSlug string, iterationFingerprint string) (*packermodels.HashicorpCloudPackerIteration, error) {
params := newGetIterationParams(ctx, loc, bucketSlug)
params.Fingerprint = &iterationFingerprint
return getIteration(client, params)
}

func newGetIterationParams(ctx context.Context, loc *sharedmodels.HashicorpCloudLocationLocation,
bucketslug string) *packer_service.PackerServiceGetIterationParams {
params := packer_service.NewPackerServiceGetIterationParamsWithContext(ctx)
params.LocationOrganizationID = loc.OrganizationID
params.LocationProjectID = loc.ProjectID
params.BucketSlug = bucketslug
return params
}

// The identifier can be either fingerprint, iterationid, or incremental version
// for now, we only care about id so we're hardcoding it.
params.IterationID = &iterationID

func getIteration(client *Client, params *packer_service.PackerServiceGetIterationParams) (*packermodels.HashicorpCloudPackerIteration, error) {
it, err := client.Packer.PackerServiceGetIteration(params, nil)
if err != nil {
return nil, handleGetIterationError(err.(*packer_service.PackerServiceGetIterationDefault))
Expand Down
54 changes: 53 additions & 1 deletion internal/provider/resource_packer_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func resourcePackerChannel() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: resourcePackerChannelImport,
},
CustomizeDiff: resourcePackerChannelCustomizeDiff,

Schema: map[string]*schema.Schema{
// Required inputs
Expand Down Expand Up @@ -65,6 +66,7 @@ func resourcePackerChannel() *schema.Resource {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"fingerprint": {
Expand Down Expand Up @@ -220,7 +222,7 @@ func resourcePackerChannelUpdate(ctx context.Context, d *schema.ResourceData, me

var iteration *packermodels.HashicorpCloudPackerIteration
iterationConfig, ok := d.GetOk("iteration")
if !ok {
if !ok || iterationConfig.([]interface{})[0] == nil {
channel, err := clients.UpdateBucketChannel(ctx, client, loc, bucketName, channelName, iteration)
if err != nil {
return diag.FromErr(err)
Expand Down Expand Up @@ -360,6 +362,56 @@ func resourcePackerChannelImport(ctx context.Context, d *schema.ResourceData, me
return []*schema.ResourceData{d}, nil
}

func resourcePackerChannelCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
client := meta.(*clients.Client)

projectID, err := GetProjectID(d.Get("project_id").(string), client.Config.ProjectID)
if err != nil {
return fmt.Errorf("unable to retrieve project ID: %v", err)
}

loc := &sharedmodels.HashicorpCloudLocationLocation{
OrganizationID: client.Config.OrganizationID,
ProjectID: projectID,
}

bucketNameRaw, ok := d.GetOk("bucket_name")
if !ok {
return fmt.Errorf("unable to retrieve bucket_name")
}
bucketName := bucketNameRaw.(string)

if d.HasChange("iteration.0") {
var iterationResponse *packermodels.HashicorpCloudPackerIteration
var err error
if id, ok := d.GetOk("iteration.0.id"); d.HasChange("iteration.0.id") && ok && id.(string) != "" {
iterationResponse, err = clients.GetIterationFromID(ctx, client, loc, bucketName, id.(string))
} else if fingerprint, ok := d.GetOk("iteration.0.fingerprint"); d.HasChange("iteration.0.fingerprint") && ok && fingerprint.(string) != "" {
iterationResponse, err = clients.GetIterationFromFingerprint(ctx, client, loc, bucketName, fingerprint.(string))
} else if version, ok := d.GetOk("iteration.0.incremental_version"); d.HasChange("iteration.0.incremental_version") && ok && version.(int) > 0 {
iterationResponse, err = clients.GetIterationFromVersion(ctx, client, loc, bucketName, int32(version.(int)))
}
if err != nil {
return err
}

iteration := []map[string]interface{}{}
if iterationResponse != nil {
iteration = append(iteration, map[string]interface{}{
"id": iterationResponse.ID,
"fingerprint": iterationResponse.Fingerprint,
"incremental_version": iterationResponse.IncrementalVersion,
})
}
err = d.SetNew("iteration", iteration)
if err != nil {
return err
}
}

return nil
}

func setPackerChannelResourceData(d *schema.ResourceData, channel *packermodels.HashicorpCloudPackerChannel) diag.Diagnostics {
if channel == nil {
err := errors.New("unexpected empty channel provided when setting state")
Expand Down
6 changes: 3 additions & 3 deletions templates/resources/packer_channel.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ description: |-

## Example Usage

To create a channel with no assigned iteration.
To create a channel.
{{ tffile "examples/resources/hcp_packer_channel/resource.tf" }}

To create, or update an existing, channel with an assigned iteration.
To create a channel with iteration assignment managed by Terraform.
{{ tffile "examples/resources/hcp_packer_channel/resource_assignment.tf" }}

Using the latest channel to create a new channel with an assigned iteration.
Using the latest channel to create a new channel with the latest complete iteration assigned.
{{ tffile "examples/resources/hcp_packer_channel/resource_using_latest_channel.tf" }}


Expand Down

0 comments on commit f42ada2

Please sign in to comment.