Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hcp_packer_image channel attribute support #339

Merged
merged 2 commits into from
Jul 5, 2022

Conversation

lbajolet-hashicorp
Copy link
Contributor

@lbajolet-hashicorp lbajolet-hashicorp commented Jun 28, 2022

🛠️ Description

Add a new channel attribute to the hcp_packer_image data source, so a user can fetch the latest image directly, without having to specify a companion hcp_packer_iteration data source to fetch the build ID from.

🚢 Release Note

* datasource/hcp_packer_image: allow `channel` attribute to get an image [GH-339]

🏗️ Acceptance tests

  • Are there any feature flags that are required to use this functionality?
  • Have you added an acceptance test for the functionality being added?
  • Have you run the acceptance tests on this branch?

Output from acceptance testing:

$ make testacc TESTARGS='-run=TestAcc_dataSourcePackerImage_chan'
==> Checking that code complies with gofmt requirements...
./scripts/gofmtcheck.sh: line 5: gofmt: command not found
TF_ACC=1 go test ./internal/... -v -run=TestAcc_dataSourcePackerImage_chan -timeout 210m
?   	github.com/hashicorp/terraform-provider-hcp/internal/clients	[no test files]
testing: warning: no tests to run
PASS
ok  	github.com/hashicorp/terraform-provider-hcp/internal/consul	(cached) [no tests to run]
testing: warning: no tests to run
PASS
ok  	github.com/hashicorp/terraform-provider-hcp/internal/input	(cached) [no tests to run]
=== RUN   TestAcc_dataSourcePackerImage_channelAndIterationIDReject
--- PASS: TestAcc_dataSourcePackerImage_channelAndIterationIDReject (0.08s)
=== RUN   TestAcc_dataSourcePackerImage_channelAccept
--- PASS: TestAcc_dataSourcePackerImage_channelAccept (3.94s)
PASS
ok  	github.com/hashicorp/terraform-provider-hcp/internal/provider	4.026s

@lbajolet-hashicorp lbajolet-hashicorp requested a review from a team as a code owner June 28, 2022 20:46
@lbajolet-hashicorp lbajolet-hashicorp requested a review from a team June 28, 2022 20:46
@lbajolet-hashicorp lbajolet-hashicorp force-pushed the packer_image_channel_support branch from f81915f to c7460a6 Compare June 28, 2022 20:54
@@ -251,6 +251,35 @@ func getIterationIDFromFingerPrint(t *testing.T, bucketSlug string, fingerprint
return ok.Payload.Iteration.ID, nil
}

func getBuildIDFromIteration(t *testing.T, bucketSlug, iterID, cloudProvider string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this function called from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@sylviamoss sylviamoss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little concerned about the optional fields in the docs that are not really optional and also the fact that terraform validate can validate those properly.

In regards to hcp_packer_iteration I think we should keep it valid for now while we observe people migrating to use just hcp_packer_image. The iteration data source returns information about the iteration that the image doesn't, I can't tell right now how useful those information are for the user, so I definitely not deprecate right now 🤔

Comment on lines 45 to 46
- `channel` (String) Channel that promotes the latest iteration of the image. Either this or `iteration_id` must be specified.
- `iteration_id` (String) HCP ID of this image. Either this or `channel' must be specified.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about these being optional here. If they are not set this data source won't work and the user will figure out about this requirement with the error message. Is it possible to write this file manually and skip the generation of these two?

This is the plugin used to generate the docs https://github.com/hashicorp/terraform-plugin-docs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not certain this is possible, the CI enforces the generated docs being the same as the ones committed :/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is written based on this template https://github.com/hashicorp/terraform-provider-hcp/blob/main/templates/data-sources/packer_image.md.tmpl.
On top, there's a Description which is populated with the schema description. So we need to update that description as well and mention that this data source needs an iteration id or a channel to query the propper image.

internal/provider/data_source_packer_image.go Outdated Show resolved Hide resolved
Description: "HCP ID of this image. Either this or `channel' must be specified.",
Type: schema.TypeString,
Optional: true,
ExactlyOneOf: []string{"channel"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: on terraform validate this one works when none is set but it doesn't when both are set.

➜  terraform validate
Success! The configuration is valid.

➜ terraform validate
╷
│ Error: Invalid combination of arguments
│ 
│   with data.hcp_packer_image.source_ami,
│   on main.tf line 16, in data "hcp_packer_image" "source_ami":
│   16: data "hcp_packer_image" "source_ami" {
│ 
│ "iteration_id": one of `channel,iteration_id` must be specified
╵
➜  tf-modules-local git:(tfc_run_task_example) ✗ 

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I would expect that the provider would fail if both fields are unset. I think we can have both fields optional and possibly computed (need to think through this a bit more) and fail in the read if neither is set. If both are set then you would fail as well since the fields need to be mutually exclusive.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is slightly more complex here, it fails at plan/apply time (there's a test for the literals-thing, I'm adding one for the static + reference) but not necessarily at validation-time currently, as it only works when none of the fields are computed.
According to @bflad the terraform-plugin-framework will let us write custom validators that we can use for this kind of use-case, but as it stands we cannot do better I'm afraid.

internal/provider/data_source_packer_image.go Outdated Show resolved Hide resolved
}
}

var diags diag.Diagnostics
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable is never being set

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, that's an artifact from when both were allowed so long as there wasn't a conflict.
I'll remove this

internal/provider/data_source_packer_image_test.go Outdated Show resolved Hide resolved
internal/provider/data_source_packer_image_test.go Outdated Show resolved Hide resolved
"iteration_id": {
Description: "HCP ID of this image. Either this or `channel' must be specified.",
Type: schema.TypeString,
Optional: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about making this value Compute and Optionaland setting this as an output of this data source as well? This way, users are able to know what's the iteration being queried

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually already set but in the output from the build response, in this case, it makes sense to make this one compute.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this one can be promoted to both Optional and Compute since it's either copied from the input, or derived from the channel, I'll do this if the Schema lets me

- `region` (String) Region this image is stored in, if any.

### Optional

- `channel` (String) Channel that promotes the latest iteration of the image. Either this or `iteration_id` must be specified.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This data source is not actually promoting an image. So this description is misleading

Suggested change
- `channel` (String) Channel that promotes the latest iteration of the image. Either this or `iteration_id` must be specified.
- `channel` (String) The channel associated to this image.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already corrected by @sylviamoss in a previous review, the latest reads as "The channel that points to the version of the image being retrieved.", are you OK with their phrasing?

"region": {
Description: "Region this image is stored in, if any.",
Type: schema.TypeString,
Required: true,
},
// Optional inputs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this comment as optional inputs have the Optional field set to true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is just here for the sake of consistency as we already have some at lines 25 (// Required inputs), and 54 (// computed outputs), I think if we remove this one, the rest should go too for the same consistency reasons, what do you think?

Description: "HCP ID of this image. Either this or `channel' must be specified.",
Type: schema.TypeString,
Optional: true,
ExactlyOneOf: []string{"channel"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we not use ConflictsWith here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this case as ConflictsWith would not raise an error if both are missing

Description: "HCP ID of this image. Either this or `channel' must be specified.",
Type: schema.TypeString,
Optional: true,
ExactlyOneOf: []string{"channel"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I would expect that the provider would fail if both fields are unset. I think we can have both fields optional and possibly computed (need to think through this a bit more) and fail in the read if neither is set. If both are set then you would fail as well since the fields need to be mutually exclusive.

internal/provider/data_source_packer_image.go Outdated Show resolved Hide resolved
internal/provider/data_source_packer_image.go Outdated Show resolved Hide resolved
Comment on lines 109 to 110
channelName := d.Get("channel")
iterationID := d.Get("iteration_id")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these need to be type asserted as they are stored as interface{} values.

Suggested change
channelName := d.Get("channel")
iterationID := d.Get("iteration_id")
channelName := d.Get("channel").(string)
iterationID := d.Get("iteration_id").(string)

That said I would recommend creating a new helper function
The following is a mixture of psuedo and Go code.

func getIteration(ctx context.Context, d *schema.ResourceData) (iteration, error) {
// validate values for channel and iteration_id
// only one can be set. 
/// if both are unset fail, 
// if both are set fail. 

if iterationID is not empty {
return clients.GetIterationFromId(
			ctx,
			client,
			loc,
			bucketName,
			iterID)
}

channel, err = clients.GetPackerChannelBySlug(
			ctx,
			client,
			loc,
			bucketName,
			chanSlug)
   if err != nil {
     return nil, err
  }
 channel.Iteration, nil

}

Comment on lines 155 to 161
if channel != nil && iteration != nil {
return diag.FromErr(fmt.Errorf(
"iteration mismatch: channel %s's iteration (%s) is different from the explicitely specified iteration: %s",
channel.Slug,
channel.Iteration.ID,
iteration.ID))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused by this as channel and iteration appear to be mutually exclusive. Is this check needed?

Copy link
Contributor Author

@lbajolet-hashicorp lbajolet-hashicorp Jun 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it used to be before we had the ExactlyOneOf in the Schema, now you're right it's not needed as the configuration attributes cannot be both set to something other than the empty string.

Comment on lines +163 to 157
// Assuming we passed the above check, the rest of the channel is not
// used after that,
if channel != nil {
iteration = channel.Iteration
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By using the helper function you can get rid of this as we will already have the iteration information we are looking for.

@lbajolet-hashicorp lbajolet-hashicorp force-pushed the packer_image_channel_support branch 3 times, most recently from 39e4f15 to 1e6ba48 Compare June 29, 2022 20:28
Comment on lines +177 to +178
testAccPackerImageBothChanAndIter,
testAccPackerImageBothChanAndIterRef,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

A hcp_packer_image used to rely on the iteration_id, which it could get
from a second resource in the plan.

However, this information is redundant, as the image's ID can be
inferred directly by the image block, if we specify its channel.

Therefore, we let a user specify either an iteration_id for
compatibility, or a channel so they won't need both information in a
single plan.
@lbajolet-hashicorp lbajolet-hashicorp force-pushed the packer_image_channel_support branch from 1e6ba48 to 1809ea9 Compare June 30, 2022 15:07
```

~> **Note:** This data source only returns the first found image's metadata filtered by the given schema values, from the returned list of images associated with the specified iteration. Therefore, if multiple images exist in the same region, it will only pick one of them. If that's the case, you may consider separating your builds into different buckets.

~> **Note:** The `channel` attribute in this data source will incur a billable request to HCP Packer. This attribute is intended for convenience when using a single image. When sourcing multiple images from a single iteration, the separate data source object for the iteration alternative is preferred for saving costs.
Copy link
Contributor

@sylviamoss sylviamoss Jul 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think? I changed to may because there are limited free requests in the standard tier 🤔

Suggested change
~> **Note:** The `channel` attribute in this data source will incur a billable request to HCP Packer. This attribute is intended for convenience when using a single image. When sourcing multiple images from a single iteration, the separate data source object for the iteration alternative is preferred for saving costs.
~> **Note:** The `channel` attribute in this data source may incur a billable request to HCP Packer. This attribute is intended for convenience when using a single image. When sourcing multiple images from a single iteration, the `hcp_packer_iteration` data source
is the alternative for querying a channel just once.

Comment on lines 15 to 32
# If sourcing multiple images

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# If sourcing multiple images

Comment on lines 44 to 55
# Alternatively if sourcing a single image

data "hcp_packer_image" "baz" {
bucket_name = "hardened-ubuntu-16-04"
cloud_provider = "aws"
channel = "production"
region = "us-east-1"
}

output "packer-registry-ubuntu-east-1-alt" {
value = data.hcp_packer_image.baz.cloud_image_id
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Alternatively if sourcing a single image
data "hcp_packer_image" "baz" {
bucket_name = "hardened-ubuntu-16-04"
cloud_provider = "aws"
channel = "production"
region = "us-east-1"
}
output "packer-registry-ubuntu-east-1-alt" {
value = data.hcp_packer_image.baz.cloud_image_id
}
### Alternatively, if sourcing a single image
data "hcp_packer_image" "baz" {
bucket_name = "hardened-ubuntu-16-04"
cloud_provider = "aws"
channel = "production"
region = "us-east-1"
}
output "packer-registry-ubuntu-east-1-alt" {
value = data.hcp_packer_image.baz.cloud_image_id
}

I'm having a hard time adding the code block because the markdown is getting confused with this comment markdown. I'm suggesting moving the alternative into a separated sub-section and keeping both examples separated because all together looks big and hard to quickly see how it works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separating both source examples is a valid alternative, I'll do this

Copy link
Contributor

@sylviamoss sylviamoss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggested some changes to the doc, otherwise LGTM 👍🏼
Great work!

With the introduction of `channel' in hcp_packer_image, we add some more
examples in the documentation for the data_source.
@lbajolet-hashicorp lbajolet-hashicorp force-pushed the packer_image_channel_support branch from 1809ea9 to 83dac3e Compare July 4, 2022 13:02
@lbajolet-hashicorp lbajolet-hashicorp merged commit b96faaf into main Jul 5, 2022
@lbajolet-hashicorp lbajolet-hashicorp deleted the packer_image_channel_support branch July 5, 2022 20:20
aidan-mundy pushed a commit that referenced this pull request Sep 8, 2023
A hcp_packer_image used to rely on the iteration_id, which it could get
from a second resource in the plan.

However, this information is redundant, as the image's ID can be
inferred directly by the image block, if we specify its channel.

Therefore, we let a user specify either an iteration_id for
compatibility, or a channel so they won't need both information in a
single plan.

This does not make the hcp_iteration entity redundant however, as
one can reuse its data for several images, hence why we keep it for
now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants