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

Implementing expect unknown and null output value plan checks #220

Merged
merged 15 commits into from
Nov 28, 2023

Conversation

bendbennett
Copy link
Contributor

Closes: #219

@bendbennett bendbennett added the enhancement New feature or request label Nov 17, 2023
@bendbennett bendbennett added this to the v1.6.0 milestone Nov 17, 2023
@bendbennett bendbennett marked this pull request as ready for review November 20, 2023 14:40
@bendbennett bendbennett requested a review from a team as a code owner November 20, 2023 14:40
@bendbennett bendbennett changed the title Implementing expect unknown output value plan check Implementing expect unknown and null output value plan checks Nov 20, 2023
Copy link
Contributor

@SBGoods SBGoods left a comment

Choose a reason for hiding this comment

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

Overall, this looks great! I've added minor comments mostly around documentation.

More generally, I think we should come up with common language to differentiate between attribute state and attribute output/value. Especially with expectUnknownValue() and expectUnknownOutputValue() where the language is a bit too similar. Maybe we can describe expectUnknownValue() and expectSensitiveValue() as "asserting that an attribute is being marked as unknown/sensitive."

plancheck/expect_null_output_value.go Outdated Show resolved Hide resolved
plancheck/expect_null_output_value.go Outdated Show resolved Hide resolved
plancheck/expect_null_output_value.go Outdated Show resolved Hide resolved
plancheck/expect_null_output_value.go Outdated Show resolved Hide resolved
}

case SliceStep:
steps = append(steps, fmt.Sprint(s))
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a use case for using fmt.Sprint(s) instead of string(s) here?

Copy link
Contributor Author

@bendbennett bendbennett Nov 21, 2023

Choose a reason for hiding this comment

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

fmt.Sprint(s) is used as golangci-lint raises the following issue if string(s) is used:

Error: stringintconv: conversion from SliceStep (int) to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?) (govet)

}
}

// OutputValueParams is used during the creation of a plan check for output values, and specifies
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it better to move this information to the website documentation since it's directly exposed to provider developers?

Copy link
Contributor

Choose a reason for hiding this comment

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

We generally prefer to document terraform-plugin-* functionality that is exposed to developers via both Go package documentation and website documentation. The Go package documentation shows up on https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing as well as code editor integrations, such as the Go extension for VS Code, meaning that we can reduce developers need to context switch to a web browser while writing code.

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've added the documentation for OutputValueParams to the website docs. It now appears in both the website docs and in the Go package docs.

Copy link
Contributor

@bflad bflad left a comment

Choose a reason for hiding this comment

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

In general this is getting there -- just some larger things to potentially discuss/handle before getting into smaller implementation details.

}
}

// OutputValueParams is used during the creation of a plan check for output values, and specifies
Copy link
Contributor

Choose a reason for hiding this comment

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

We generally prefer to document terraform-plugin-* functionality that is exposed to developers via both Go package documentation and website documentation. The Go package documentation shows up on https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing as well as code editor integrations, such as the Go extension for VS Code, meaning that we can reduce developers need to context switch to a web browser while writing code.

Comment on lines 107 to 110
type OutputValueParams struct {
OutputAddress string
AttributePath tfjsonpath.Path
}
Copy link
Contributor

Choose a reason for hiding this comment

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

👋 I think it might be good to discuss whether a separate struct is necessary or if we should expose separate functions instead (one without path, one with path). At the very least we should likely call the path information Path or ValuePath -- attribute paths don't exist within outputs since an output is a value. That value may have came from a resource with attributes, but that's not guaranteed nor the only valid usage for complex output values.

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've updated to ValuePath for now, but let's discuss potentially splitting into separate functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Following out-of-band discussion, the Expect<Null|Unknown>OutputValue funcs have been split into:

  • Expect<Null|Unknown>OutputValue
  • Expect<Null|Unknown>OutputValueAtPath

| [`plancheck.ExpectResourceAction(address, operation)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectResourceAction) | Asserts the given resource has the specified operation for apply. |
| [`plancheck.ExpectUnknownValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectUnknownValue) | Asserts the specified attribute at the given resource has an unknown value. |
| [`plancheck.ExpectSensitiveValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectSensitiveValue) | Asserts the specified attribute at the given resource has a sensitive value. |
| Check | Description |
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be good to split up this table (and maybe the page) to be resource attribute checks versus output value checks to reduce potential confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have split the page into Plan Checks - Overview, Resource, Output, and Custom.

The docs for ExpectEmptyPlan and ExpectNonEmptyPlan currently appear on the Resource page. Perhaps these should be on a separate page or on the Overview page?

Copy link
Contributor

Choose a reason for hiding this comment

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

To answer your question directly -- I think choosing the atypical option of documenting those on the Overview page is okay here since they should theoretically apply to the whole plan. ExpectEmptyPlan/ExpectNonEmptyPlan currently only check resource changes, not output changes, at the moment though. I'm guessing that most developers would also expect them to also account for other/all plan changes given the naming, so I'm going to file a bug report to adjust the existing implementation. I think if developers need more granular "only no resource changes" then we can introduce general resource vs output (non-)empty plan checks when requested. 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Also ❤️ for thinking ahead to just split up the pages.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have added a General Plan Checks section to the Overview page to include plan checks for ExpectEmptyPlan/ExpectNonEmptyPlan.

@bendbennett
Copy link
Contributor Author

More generally, I think we should come up with common language to differentiate between attribute state and attribute output/value. Especially with expectUnknownValue() and expectUnknownOutputValue() where the language is a bit too similar. Maybe we can describe expectUnknownValue() and expectSensitiveValue() as "asserting that an attribute is being marked as unknown/sensitive."

I've modified the wording on ExpectUnknownOutputValue, and ExpectNullOutputValue to emphasise the difference. For instance:

ExpectUnknownOutputValue returns a plan check that asserts that the specified output has an unknown value.

Do you think this provides enough distinction/clarity?

Copy link
Contributor

@bflad bflad left a comment

Choose a reason for hiding this comment

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

Overall, this is wonderful. Looks good to me 🚀

},
Steps: []r.TestStep{
{
Config: `resource "time_static" "one" {}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 If we want, we can skip using a real provider and either implement code-based provider or maybe preferably use the terraform_data resource (with requisite Terraform 1.4+ version checking) as its output attribute is automatically an unknown value of input. I personally think its okay to use terraform_data and skip the test on older Terraform versions just to simplify and speed up this testing. If there happened to be an earlier Terraform version bug with its plan output, it would not be fixed at this point.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, this is fun. We validate things a little too well currently if you fully omit referencing some provider in the test case/step configuration.

Test validation error: TestStep 1/2 validation error: Providers must be specified at the TestCase level, or in all TestStep, or in TestStep.ConfigDirectory or TestStep.ConfigFile

I'll create a separate issue to see if we can make it possible to create testing with just the built-in terraform provider.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah ha -- found a workaround based on a special provider discovery rule:

ExternalProviders: map[string]resource.ExternalProvider{
	"terraform": {Source: "terraform.io/builtin/terraform"},
},

That works as expected although it calls terraform init unnecessarily in this very specific case -- I'll look to see if it makes sense to potentially have the init calling logic skip if terraform.io/builtin/terraform is the only external provider and source.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice! I've replaced the usage of the time provider with the built-in terraform_data resource in all of the ExpectUnknownOutputValue, and ExpectUnknownOutputValueAtPath plan check tests.

- Available plan phases for **Refresh** mode are defined in the [`TestStep.RefreshPlanChecks`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) struct
- **Import** mode currently does not run any plan operations, and therefore does not support plan checks.

There are built-in [resource plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks/resource), and [output plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks/output).
Copy link
Contributor

Choose a reason for hiding this comment

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

Resource is definitely the correct word to use for both managed resources and data resources (colloquially called data sources), however I wonder if we should explicitly mention "managed resources and data resources" when we have textual room so folks aren't confused about how to test data sources?

Copy link
Contributor Author

@bendbennett bendbennett Nov 23, 2023

Choose a reason for hiding this comment

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

I've updated the Overview and Resource pages to explicitly mention "managed resources, and data sources". I went with "data sources" rather than "data resources" as it seems likely that the former might be more familiar terminology given the documentation on the terraform language for Data Sources page. Does that seem ok?

@bendbennett bendbennett merged commit 5f5c3b7 into main Nov 28, 2023
6 checks passed
@bendbennett bendbennett deleted the bendbennett/issues-219 branch November 28, 2023 14:30
Copy link

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add unknown and null plan checks for output values
3 participants