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

Add Option to use Terraform Configuration in TestStep #153

Merged
merged 59 commits into from
Aug 31, 2023

Conversation

bendbennett
Copy link
Contributor

@bendbennett bendbennett commented Jul 17, 2023

Closes: #150

This PR adds the following fields to TestStep:

  • ConfigDirectory
  • ConfigFile
  • ConfigVariable

ConfigDirectory

Allows the specification of a path to a directory containing Terraform configuration files. All files in the directory are copied to the working directory used during the execution of an acceptance test TestStep.

ConfigFile

Allows the specification of a path to a file containing Terraform configuration. The file is copied to the working directory used during the execution of an acceptance test TestStep.

ConfigVariables

Allows defining Terraform variables for use in conjunction with Terraform configuration used during the execution of an acceptance test TestStep.

…s of TestStep.Config or TestStep.Directory can be encapsulated in teststep.config struct (#150)
…instantiating implementations of teststep.Config (#150)
…p.Config interface for applied configuration (#150)
…lock and TestStep.mergedConfig() to configuration.HasProviderBlock(), configuration.hasTerraformBlock and configuration.MergedConfig(), respectively (#150)
…configuration struct to simplify processing during GetRaw() and the equivalent function(s) for processing Directory and File (#150)
…gement of writing test case provider config or test step provider config simpler when handling copying of files from configuration.directory (#150)
…Case or TestStep are being used when TestStep.ConfigDirectory is specified (#150)
…f provider blocks in configuration files within the configuration directory (#150)
…ined within testCaseProviderConfig and testStepProvider config fields) when processing config directory as the expectation is that external providers will be specified directly in the terraform configuration files within the config directory (#150)
… validate that when ConfigDirectory is defined that ExternalProviders cannot be specified for either TestCase or TestStep (#150)
…blocks are only written when using TestStep.Config. The expectation is that when using TestStep.ConfigDirectoy, the terraform files within the configuration directory will specify the terraform and/or provider blocks as necessary (#150)
…aform configuration files in TestStep.ConfigDirectory (#150)
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.

Nice work so far, @bendbennett! I left some initial thoughts here. I really like where you are going with the new configuration handling interface and methods since that will help clean up the testing logic implementation.

config/variable.go Outdated Show resolved Hide resolved
config/variable.go Outdated Show resolved Hide resolved
config/variable.go Outdated Show resolved Hide resolved
config/variable.go Show resolved Hide resolved
Comment on lines 147 to 153
innerErr := err

for errors.Unwrap(innerErr) != nil {
innerErr = errors.Unwrap(err)
}

return nil, innerErr
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add a quick code comment about why this unwrapping is necessary? Thanks!

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 comment. Happy to expand/reduce etc.

}

func Configuration(req ConfigurationRequest) (configuration, error) {
var populatedConfig []string
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Can we just directly check the fields rather than storing them in a separate slice?

if req.Directory != nil && req.Raw != nil {
  // return error
}

if req.Directory != nil {
  return configuration{/* ... */}, nil
}

return configuration{/* ... */}, nil

Could potentially introduce a ConfigurationRequest.Validate() method to simplify further.

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 ConfigurationRequest.Validate() method as per your suggestion.

return nil
}

func copyFile(path string, dstPath 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.

internal/plugintest has some helpers for this sorta thing -- if it makes more sense, maybe that functionality can split into its own internal file handling package to help prevent duplicating logic, but totally optional of course.

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 left this as is for now if that's ok? Once these changes are merged I'll add an issue to consider refactoring.

Comment on lines 94 to 97
switch {
case c.hasRaw(ctx):
return providerConfigBlockRegex.MatchString(c.raw), nil
case c.hasDirectory(ctx):
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 One thing you could do here to avoid checking each case repetitively introduce two separate configuration implementations -- ConfigurationDirectory and ConfigurationString

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 configurationDirectory and configurationString types that implement the Config interface. The Configuration() constructor function now returns Config interface to accommodate the various types we will need (e.g., configurationDirectory, configurationString, configurationFile). Owing to the switch to returning an interface from the Configuration() constructor function, nil checks have been added.

config/directory.go Outdated Show resolved Hide resolved
config/directory.go Outdated Show resolved Hide resolved
Co-authored-by: Brian Flad <bflad417@gmail.com>
@bendbennett bendbennett marked this pull request as ready for review July 28, 2023 15:31
@bendbennett bendbennett requested a review from a team as a code owner July 28, 2023 15:31
Copy link
Member

@austinvalle austinvalle left a comment

Choose a reason for hiding this comment

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

So much great work Ben! 🚀 - Couple notes and one thought here:

  • With the amount of file/path specific tests we have now in plugin-testing, should we update our CI to run on other platforms as well (mac/windows)?

helper/resource/teststep_providers_test.go Show resolved Hide resolved
internal/plugintest/util.go Outdated Show resolved Hide resolved
internal/teststep/directory_test.go Show resolved Hide resolved
@bendbennett
Copy link
Contributor Author

bendbennett commented Jul 31, 2023

  • With the amount of file/path specific tests we have now in plugin-testing, should we update our CI to run on other platforms as well (mac/windows)?

We could certainly consider this. Couple of observations when playing around with this:

  • Updating the OS matrix in ci-go.yml to use windows-latest and macos-latest results in the error below when running go test -coverprofile=coverage.out ./... on Windows. This can be handled by changing the running of the tests to something like go test ./... -v -cover:
no required module provides package .out; to add it: .........
  • Next up, there's an error generated for the TestTest_TestStep_ProviderFactories_CopyWorkingDir_EachTestStep test that's specific to MacOS 12. This can be handled by using macos-13 in the testing matrix, but will require that we circle back round and update to macos-latest once the default MacOS for GitHub actions has been bumped to 13.

  • Finally, the tests that purposely fail to find a directory/file, for instance, TestConfigurationFile_HasProviderBlock "not-file", will need to be updated as the error message on Windows differs from Ubuntu and MacOS (e.g., The system cannot find the file specified versus no such file or directory).

I'm happy to make these changes if everyone is on board with the above or some variant thereof.

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.

Drive-by question (sorry I'm having trouble getting time to review this properly): Is there something handling the removal of configuration files not present when there are multiple ConfigDirectory steps?

e.g. if first test step has ConfigDirectory with a.tf and b.tf.json and second test step has ConfigDirectory has c.tf and d.tf.json, the working directory should only have c.tf and d.tf.json during the second test step.

// TestStepConfigRequest defines the request supplied to types
// implementing TestStepConfigFunc.
type TestStepConfigRequest struct {
StepNumber int
Copy link
Contributor

@bflad bflad Jul 26, 2023

Choose a reason for hiding this comment

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

It might be helpful to document that this number is one-based for humans instead of the underlying Go slice index which is zero-based.

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 updated the docs to this effect.

Comment on lines 69 to 70
// these files within a directory with the same name as the test.
func TestStepDirectory() func(TestStepConfigRequest) string { //nolint:paralleltest //Not a test
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to above 😉

Suggested change
// these files within a directory with the same name as the test.
func TestStepDirectory() func(TestStepConfigRequest) string { //nolint:paralleltest //Not a test
// these files within a directory with the same name as the test.
//
// For example, given test code:
//
// func TestExampleCloudThing_basic(t *testing.T) {
// resource.Test(t, resource.TestCase{
// Steps: []resource.TestStep{
// {
// ConfigDirectory: config.TestStepDirectory(),
// },
// },
// })
// }
//
// The testing configurations will be expected in the
// testdata/TestExampleCloudThing_basic/1/ directory.
func TestStepDirectory() func(TestStepConfigRequest) string {

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 updated the docs to this effect.

Comment on lines 2337 to 2338
// TestTest_ConfigDirectory_TestStepDirectory_StepNotHardcoded uses a multistep test
// to prove that the test step number is not hardcoded
Copy link
Member

Choose a reason for hiding this comment

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

🤣 🤣

@austinvalle
Copy link
Member

austinvalle commented Jul 31, 2023

  • With the amount of file/path specific tests we have now in plugin-testing, should we update our CI to run on other platforms as well (mac/windows)?

We could certainly consider this. Couple of observations when playing around with this:

  • Updating the OS matrix in ci-go.yml to use windows-latest and macos-latest results in the error below when running go test -coverprofile=coverage.out ./... on Windows. This can be handled by changing the running of the tests to something like go test ./... -v -cover:
no required module provides package .out; to add it: .........
  • Next up, there's an error generated for the TestTest_TestStep_ProviderFactories_CopyWorkingDir_EachTestStep test that's specific to MacOS 12. This can be handled by using macos-13 in the testing matrix, but will require that we circle back round and update to macos-latest once the default MacOS for GitHub actions has been bumped to 13.
  • Finally, the tests that purposely fail to find a directory/file, for instance, TestConfigurationFile_HasProviderBlock "not-file", will need to be updated as the error message on Windows differs from Ubuntu and MacOS (e.g., The system cannot find the file specified versus no such file or directory).

I'm happy to make these changes if everyone is on board with the above or some variant thereof.

Hmm 🤔 , I don't want it to be too big of a pain... Maybe we just skip running them for different OS's for now? If we start running into bug reports that are OS specific we can revisit. I'd imagine the amount of users running into Window specific bugs is limited for Go modules like this anyways 🤷🏻 . Thanks for giving it a go!

@bendbennett
Copy link
Contributor Author

Drive-by question (sorry I'm having trouble getting time to review this properly): Is there something handling the removal of configuration files not present when there are multiple ConfigDirectory steps?

e.g. if first test step has ConfigDirectory with a.tf and b.tf.json and second test step has ConfigDirectory has c.tf and d.tf.json, the working directory should only have c.tf and d.tf.json during the second test step.

Good point! Have added something to handle removal of config and variables files from previous test step prior to running the current test step along with some test coverage.

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 looks good to me 🚀

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 torn on whether we should "feature" changelog all the individual config package functionality, since the above changelog entry covers the addition and purpose of the whole config package. Generally "feature" should be reserved for major functionality. Personally I think the above entry and individual helper/resource changes do fit the criteria for major functionality. 👍

.changes/unreleased/FEATURES-20230728-152737.yaml Outdated Show resolved Hide resolved
.changes/unreleased/FEATURES-20230728-152822.yaml Outdated Show resolved Hide resolved
.changes/unreleased/FEATURES-20230728-152917.yaml Outdated Show resolved Hide resolved
config/directory.go Outdated Show resolved Hide resolved
config/file.go Outdated Show resolved Hide resolved
helper/resource/testcase_validate.go Outdated Show resolved Hide resolved
helper/resource/testing_new.go Outdated Show resolved Hide resolved

for _, file := range fi {
if file.Mode().IsRegular() {
if filepath.Ext(file.Name()) == ".tf" || filepath.Ext(file.Name()) == ".json" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Open question: Not sure if we should or shouldn't be concerned about non-Terraform JSON files 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.

I think that the only .json files within the working_dir will be those that are copied when a .json file, or a directory containing .json files is specified for TestStep.ConfigFile, orTestStep.ConfigDirectory, respectively. So I believe removing .json files will be ok from this perspective.

Do you know any way in which a non-Terraform JSON file might end-up in the working directory, and if so, whether removing it would be likely to cause issues?

Copy link
Contributor

@bflad bflad Aug 30, 2023

Choose a reason for hiding this comment

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

Hmm, I think you're right to a certain degree. Maybe I was thinking of the opposite problem then -- let's say we're using ConfigDirectory across multiple steps and I have:

# step 1 directory
|- file.whateverextension
 \ main.tf

# step 2 directory
\ main.tf

We should be preserving things necessary between steps in the working directory such as the terraform.tfstate, but removing the rest of the working directory files/directories. The configuration copying would overwrite any exactly named files between steps, so in that regard its okay, but I think it would issue if the configuration was using something like fileset() instead of referencing thing with file() directly and expecting those lingering files to be removed between steps. Let's treat this as something to follow up with rather than holding this up further though!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the additional context. Have created a follow-up issue - Consider removal of all files copied from ConfigDirectory between sequential test steps


for _, file := range fi {
if file.Mode().IsRegular() {
if filepath.Ext(file.Name()) == ".tf" || filepath.Ext(file.Name()) == ".json" {
Copy link
Contributor

@bflad bflad Aug 30, 2023

Choose a reason for hiding this comment

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

Hmm, I think you're right to a certain degree. Maybe I was thinking of the opposite problem then -- let's say we're using ConfigDirectory across multiple steps and I have:

# step 1 directory
|- file.whateverextension
 \ main.tf

# step 2 directory
\ main.tf

We should be preserving things necessary between steps in the working directory such as the terraform.tfstate, but removing the rest of the working directory files/directories. The configuration copying would overwrite any exactly named files between steps, so in that regard its okay, but I think it would issue if the configuration was using something like fileset() instead of referencing thing with file() directly and expecting those lingering files to be removed between steps. Let's treat this as something to follow up with rather than holding this up further though!

@github-actions
Copy link

github-actions bot commented Oct 1, 2023

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 Oct 1, 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 Option to use Terraform Configuration in TestStep
3 participants