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

restore hooks functionality #1010

Merged
merged 7 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
30 changes: 21 additions & 9 deletions cmd/terraform.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"

e "github.com/cloudposse/atmos/internal/exec"
"github.com/cloudposse/atmos/pkg/hooks"
cfg "github.com/cloudposse/atmos/pkg/config"
h "github.com/cloudposse/atmos/pkg/hooks"
u "github.com/cloudposse/atmos/pkg/utils"
)

type contextKey string

const atmosInfoKey contextKey = "atmos_info"

// terraformCmd represents the base command for all terraform sub-commands
var terraformCmd = &cobra.Command{
Use: "terraform",
Aliases: []string{"tf"},
Short: "Execute Terraform commands (e.g., plan, apply, destroy) using Atmos stack configurations",
Long: `This command allows you to execute Terraform commands, such as plan, apply, and destroy, using Atmos stack configurations for consistent infrastructure management.`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true},
PostRunE: func(cmd *cobra.Command, args []string) error {
info := getConfigAndStacksInfo("terraform", cmd, args)
return hooks.RunE(cmd, args, &info)
},
}

// Contains checks if a slice of strings contains an exact match for the target string.
Expand All @@ -47,6 +42,23 @@ func terraformRun(cmd *cobra.Command, actualCmd *cobra.Command, args []string) {
}
}

func runHooks(event h.HookEvent, cmd *cobra.Command, args []string) error {
info := getConfigAndStacksInfo("terraform", cmd, append([]string{cmd.Name()}, args...))

// Initialize the CLI config
atmosConfig, err := cfg.InitCliConfig(info, true)
if err != nil {
return fmt.Errorf("error initializing CLI config: %w", err)
}

hooks, err := h.GetHooks(&atmosConfig, &info)
if err != nil {
return fmt.Errorf("error getting hooks: %w", err)
}

return hooks.RunAll(event, &atmosConfig, &info, cmd, args)
}

func init() {
// https://github.com/spf13/cobra/issues/739
terraformCmd.DisableFlagParsing = true
Expand Down
18 changes: 14 additions & 4 deletions cmd/terraform_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"os"

l "github.com/charmbracelet/log"
h "github.com/cloudposse/atmos/pkg/hooks"
"github.com/spf13/cobra"
)

Expand All @@ -27,12 +29,16 @@ func getTerraformCommands() []*cobra.Command {
Annotations: map[string]string{
"nativeCommand": "true",
},
PostRunE: func(cmd *cobra.Command, args []string) error {
l.Info("running hooks", "event", h.AfterTerraformApply)
return runHooks(h.AfterTerraformApply, cmd, args)
},
},
{
Use: "workspace",
Short: "Manage Terraform workspaces",
Long: `The 'atmos terraform workspace' command initializes Terraform for the current configuration, selects the specified workspace, and creates it if it does not already exist.

It runs the following sequence of Terraform commands:
1. 'terraform init -reconfigure' to initialize the working directory.
2. 'terraform workspace select' to switch to the specified workspace.
Expand All @@ -57,6 +63,10 @@ Common use cases:
Use: "deploy",
Short: "Deploy the specified infrastructure using Terraform",
Long: `Deploys infrastructure by running the Terraform apply command with automatic approval. This ensures that the changes defined in your Terraform configuration are applied without requiring manual confirmation, streamlining the deployment process.`,
PostRunE: func(cmd *cobra.Command, args []string) error {
l.Info("running hooks", "event", h.AfterTerraformApply)
return runHooks(h.AfterTerraformApply, cmd, args)
},
},
{
Use: "shell",
Expand Down Expand Up @@ -151,12 +161,12 @@ Common use cases:
Use: "import",
Short: "Import existing infrastructure into Terraform state.",
Long: `The 'atmos terraform import' command imports existing infrastructure resources into Terraform's state.

Before executing the command, it searches for the 'region' variable in the specified component and stack configuration.
If the 'region' variable is found, it sets the 'AWS_REGION' environment variable with the corresponding value before executing the import command.

The import command runs: 'terraform import [ADDRESS] [ID]'

Arguments:
- ADDRESS: The Terraform address of the resource to import.
- ID: The ID of the resource to import.`,
Expand Down
2 changes: 2 additions & 0 deletions go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"fmt"
"os"
"path"
"testing"

"github.com/alicebob/miniredis/v2"
)

func TestMainHooksAndStoreIntegration(t *testing.T) {
// Run the miniredis server so we can store values across calls to main()
s := miniredis.RunT(t)
defer s.Close()

redisUrl := fmt.Sprintf("redis://%s", s.Addr())
os.Setenv("ATMOS_REDIS_URL", redisUrl)

origDir, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get current working directory: %v", err)
}
defer os.RemoveAll(path.Join(origDir, "testdata", "fixtures", "hooks-test", ".terraform"))
defer os.Chdir(origDir)

os.Chdir("testdata/fixtures/hooks-test")
mcalhoun marked this conversation as resolved.
Show resolved Hide resolved

// Capture the original arguments
origArgs := os.Args
defer func() { os.Args = origArgs }()

// Set the arguments for the first call to main() to deeploy the `random1` component, which uses a `hook` to set a
// value in Redis
os.Args = []string{"atmos", "terraform", "deploy", "random1", "-s", "test"}
main()

// Set the arguments for the second call to main() to deeploy the `random2` component, which uses a `store` to read a
// value that was set in the first apply.
os.Args = []string{"atmos", "terraform", "deploy", "random2", "-s", "test"}
main()
}
91 changes: 0 additions & 91 deletions pkg/hooks/cmd.go

This file was deleted.

9 changes: 9 additions & 0 deletions pkg/hooks/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package hooks

import "github.com/spf13/cobra"

// Command is the interface for all commands that can be run by hooks
type Command interface {
GetName() string
RunE(hook *Hook, event HookEvent, cmd *cobra.Command, args []string) error
}
10 changes: 10 additions & 0 deletions pkg/hooks/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package hooks

type HookEvent string

const (
AfterTerraformApply HookEvent = "after.terraform.apply"
BeforeTerraformApply HookEvent = "before.terraform.apply"
AfterTerraformPlan HookEvent = "after.terraform.plan"
BeforeTerraformPlan HookEvent = "before.terraform.plan"
)
14 changes: 14 additions & 0 deletions pkg/hooks/hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package hooks

// Hook is the structure for a hook and is using in the stack config to define
// a command that should be run when a specific event occurs.
type Hook struct {
Events []string `yaml:"events"`
Command string `yaml:"command"`

// Dynamic command-specific properties

// store command
Name string `yaml:"name,omitempty"` // for store command
Outputs map[string]string `yaml:"outputs,omitempty"` // for store command
}
Loading
Loading