Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pkg/recipes/terraform/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ func (cfg *TerraformConfig) AddRecipeContext(ctx context.Context, moduleName str
return nil
}

// RemoveRecipeContext removes the recipe context parameter from the module configuration.
// This is used when the downloaded module does not declare a context variable.
func (cfg *TerraformConfig) RemoveRecipeContext(moduleName string) {
if mod, ok := cfg.Module[moduleName]; ok {
delete(mod, recipecontext.RecipeContextParamKey)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What if users were using the context to pass some information? I am a bit doubtful if this could break their recipe. But we were also talking about the context being not important since we now pass the connected resource's properties. We just have not documented the change / made sure our recipes dont still use context + @kachawla for inputs.

// newModuleConfig creates a new TFModuleConfig object with the given module source and version
// and also populates RecipeParams in TF module config. If same parameter key exists across params
// then the last map specified gets precedence.
Expand Down
38 changes: 38 additions & 0 deletions pkg/recipes/terraform/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,44 @@ func Test_AddRecipeContext(t *testing.T) {
}
}

func Test_RemoveRecipeContext(t *testing.T) {
ctx := testcontext.New(t)

envdef := &recipes.EnvironmentDefinition{
Name: testRecipeName,
TemplatePath: testTemplatePath,
TemplateVersion: testTemplateVersion,
Parameters: envParams,
}
metadata := &recipes.ResourceMetadata{
Name: testRecipeName,
Parameters: resourceParams,
}

tfconfig, err := New(context.Background(), testRecipeName, envdef, metadata)
require.NoError(t, err)

// Add recipe context
err = tfconfig.AddRecipeContext(ctx, testRecipeName, getTestRecipeContext())
require.NoError(t, err)

// Verify context is present
mod := tfconfig.Module[testRecipeName]
_, hasContext := mod[recipecontext.RecipeContextParamKey]
require.True(t, hasContext)

// Remove recipe context
tfconfig.RemoveRecipeContext(testRecipeName)

// Verify context is removed
_, hasContext = mod[recipecontext.RecipeContextParamKey]
require.False(t, hasContext)

// Verify other params are still present
_, hasSource := mod[moduleSourceKey]
require.True(t, hasSource)
}

func Test_AddProviders(t *testing.T) {
mProvider, ucpConfiguredProviders, mBackend := setup(t)
envRecipe, resourceRecipe := getTestInputs()
Expand Down
71 changes: 44 additions & 27 deletions pkg/recipes/terraform/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,28 @@ func (e *executor) GetRecipeMetadata(ctx context.Context, options Options) (map[
return nil, err
}

_, err = getTerraformConfig(ctx, tf.WorkingDir(), options)
tfConfig, err := getTerraformConfig(ctx, tf.WorkingDir(), options)
if err != nil {
return nil, err
}

// Pre-compute the recipe context and include it in the module config before downloading.
// Terraform validates required module arguments during "terraform get".
recipectx, err := recipecontext.New(options.ResourceRecipe, options.EnvConfig)
if err != nil {
return nil, err
}
if options.ResourceRecipe != nil {
recipectx.Resource.Connections = options.ResourceRecipe.ConnectedResourcesProperties
}
if err = tfConfig.AddRecipeContext(ctx, options.EnvRecipe.Name, recipectx); err != nil {
return nil, err
}

if err := tfConfig.Save(ctx, tf.WorkingDir()); err != nil {
return nil, err
}

result, err := downloadAndInspect(ctx, tf, options)
if err != nil {
return nil, err
Expand Down Expand Up @@ -260,11 +277,37 @@ func (e *executor) generateConfig(ctx context.Context, tf *tfexec.Terraform, opt
return "", err
}

// Pre-compute the recipe context and include it in the module config before downloading.
// Terraform validates required module arguments during "terraform get", so the context
// must be present in the config before the module is downloaded.
// After download and inspection, the context will be removed if the module does not declare it.
recipectx, err := recipecontext.New(options.ResourceRecipe, options.EnvConfig)
if err != nil {
return "", err
}
if options.ResourceRecipe != nil {
recipectx.Resource.Connections = options.ResourceRecipe.ConnectedResourcesProperties
}
if err = tfConfig.AddRecipeContext(ctx, options.EnvRecipe.Name, recipectx); err != nil {
return "", err
}

// Save the config with recipe context included before downloading the module.
if err := tfConfig.Save(ctx, workingDir); err != nil {
return "", err
}

loadedModule, err := downloadAndInspect(ctx, tf, options)
if err != nil {
return "", err
}

// After inspection, remove the recipe context if the module does not declare the context variable.
// Terraform will reject unexpected arguments during init/apply.
if !loadedModule.ContextVarExists {
tfConfig.RemoveRecipeContext(options.EnvRecipe.Name)
}

// Generate Terraform providers configuration for required providers and add it to the Terraform configuration.
logger.Info(fmt.Sprintf("Adding provider config for required providers %+v", loadedModule.RequiredProviders))
if err := tfConfig.AddProviders(ctx, loadedModule.RequiredProviders, providers.GetUCPConfiguredTerraformProviders(e.ucpConn, e.secretProvider),
Expand Down Expand Up @@ -292,26 +335,6 @@ func (e *executor) generateConfig(ctx context.Context, tf *tfexec.Terraform, opt
}
}

// Add recipe context parameter to the generated Terraform config's module parameters.
// This should only be added if the recipe context variable is declared in the downloaded module.
if loadedModule.ContextVarExists {
logger.Info("Adding recipe context module result")

// Create the recipe context object to be passed to the recipe deployment
recipectx, err := recipecontext.New(options.ResourceRecipe, options.EnvConfig)
if err != nil {
return "", err
}

//update the recipe context with connected resources properties
if options.ResourceRecipe != nil {
recipectx.Resource.Connections = options.ResourceRecipe.ConnectedResourcesProperties
}

if err = tfConfig.AddRecipeContext(ctx, options.EnvRecipe.Name, recipectx); err != nil {
return "", err
}
}
if loadedModule.ResultOutputExists {
if err = tfConfig.AddOutputs(options.EnvRecipe.Name); err != nil {
return "", err
Expand Down Expand Up @@ -346,12 +369,6 @@ func getTerraformConfig(ctx context.Context, workingDir string, options Options)
return nil, err
}

// Before downloading the module, Teraform configuration needs to be persisted in the working directory.
// Terraform Get command uses this config file to download module from the source specified in the config.
if err := tfConfig.Save(ctx, workingDir); err != nil {
return nil, err
}

return tfConfig, nil
}

Expand Down
8 changes: 5 additions & 3 deletions pkg/recipes/terraform/execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ func Test_GetTerraformConfig_EmptyRecipeName(t *testing.T) {
}

func Test_GetTerraformConfig_InvalidDirectory(t *testing.T) {
// getTerraformConfig no longer saves the file; it only creates the in-memory config.
// An invalid directory should not cause an error because saving happens separately.
workingDir := "invalid-directory"
options := Options{
EnvRecipe: &recipes.EnvironmentDefinition{
Expand All @@ -124,9 +126,9 @@ func Test_GetTerraformConfig_InvalidDirectory(t *testing.T) {
ResourceRecipe: &recipes.ResourceMetadata{},
}

_, err := getTerraformConfig(testcontext.New(t), workingDir, options)
require.Error(t, err)
require.Contains(t, err.Error(), "error creating file: open invalid-directory/main.tf.json: no such file or directory")
cfg, err := getTerraformConfig(testcontext.New(t), workingDir, options)
require.NoError(t, err)
require.NotNil(t, cfg)
}

func TestSetEnvironmentVariables(t *testing.T) {
Expand Down
Loading