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

Stacks: generate #3627

Merged
merged 72 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
4cb4199
Unit struct update
denis256 Dec 5, 2024
d575b6e
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 6, 2024
b7530ca
Add stack parsing
denis256 Dec 6, 2024
47cd721
Add cty serialization
denis256 Dec 6, 2024
4641420
stock go
denis256 Dec 9, 2024
558f3aa
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 10, 2024
97ec9e5
Stack config path
denis256 Dec 10, 2024
1a966ab
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 11, 2024
78a67b1
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 12, 2024
0a1be85
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 13, 2024
01ecf5d
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 16, 2024
7c9f8bd
unit struct clone
denis256 Dec 16, 2024
8928af0
stack cli
denis256 Dec 16, 2024
9d088fb
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 16, 2024
82dde7a
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 18, 2024
bd61393
stack cli update
denis256 Dec 18, 2024
fd83fb4
Stack cli commands
denis256 Dec 18, 2024
14d2088
stack update
denis256 Dec 19, 2024
472aeb9
action update
denis256 Dec 19, 2024
def9b27
Add cli flags
denis256 Dec 19, 2024
f32fb86
stack command
denis256 Dec 19, 2024
46f57c2
tg generate
denis256 Dec 19, 2024
7e3fd2a
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 20, 2024
65dc02d
Config parse update
denis256 Dec 20, 2024
bf26ab2
stacks parsing
denis256 Dec 20, 2024
231303f
stack config file parsing
denis256 Dec 20, 2024
a532787
update terragrunt path
denis256 Dec 20, 2024
ed16d24
action config update
denis256 Dec 20, 2024
b050d2d
symbol links
denis256 Dec 20, 2024
f76ae52
Units parsing
denis256 Dec 20, 2024
8542090
Symbol link issues
denis256 Dec 20, 2024
a96e5cb
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Dec 31, 2024
49634cc
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Jan 7, 2025
6024cc0
Add basic stack tests
denis256 Jan 7, 2025
04ae059
Stacks file update
denis256 Jan 7, 2025
4c1b80a
Local path detection
denis256 Jan 7, 2025
1fa7a7a
Update setting getters
denis256 Jan 7, 2025
8eb6e12
Add locals evaluation
denis256 Jan 7, 2025
d411aa4
Stack local evaluation
denis256 Jan 7, 2025
c342600
Stacks remote test
denis256 Jan 7, 2025
0da61da
Stack generate
denis256 Jan 7, 2025
cb1800b
Add basic stack generate cli
denis256 Jan 7, 2025
4e9372f
Cleanup
denis256 Jan 7, 2025
a2b2186
Markdown link
denis256 Jan 7, 2025
0d52b1e
tree update
denis256 Jan 7, 2025
fc61aa1
stack generate cleanup
denis256 Jan 7, 2025
3c5e3d9
typo fix
denis256 Jan 8, 2025
0c7e426
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Jan 8, 2025
8519fd4
Merged unit and stack files
denis256 Jan 8, 2025
b6316b6
Add error for no command
denis256 Jan 8, 2025
9cb576a
Add check for / and .
denis256 Jan 8, 2025
a9a8ff4
Stack fetching
denis256 Jan 8, 2025
c5426e4
Locals paths importing
denis256 Jan 8, 2025
48d19ec
hcl and cty formatting
denis256 Jan 8, 2025
acad34e
Markdown update
denis256 Jan 8, 2025
56c1ab0
Added go-getter detector
denis256 Jan 8, 2025
0b2d9d1
Markdown update
denis256 Jan 8, 2025
c63ff05
Test stack update
denis256 Jan 8, 2025
5860985
lint fixes
denis256 Jan 8, 2025
f48243e
Strict lint update
denis256 Jan 8, 2025
1b64473
Lint fixes
denis256 Jan 8, 2025
744c6fe
Updated remote clone
denis256 Jan 8, 2025
3d71d51
Setting stack behind stack flag
denis256 Jan 8, 2025
254e95c
Markdown update
denis256 Jan 8, 2025
b0cc2db
lint update
denis256 Jan 8, 2025
9f6987a
Disable update
denis256 Jan 8, 2025
3288ce9
Markdown cleanup
denis256 Jan 8, 2025
6611b23
stacks experiment update
denis256 Jan 8, 2025
54d95a8
Lint fixes
denis256 Jan 8, 2025
dce5729
Merge remote-tracking branch 'origin/main' into tg-432-stack-support
denis256 Jan 9, 2025
2d526f9
Simplified stack generate CLI
denis256 Jan 9, 2025
4ae51d5
Unit path updated
denis256 Jan 9, 2025
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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ run_markdownlint: &run_markdownlint
name: Run markdownlint
command: |
markdownlint \
--disable 'MD013' \
--disable MD013 MD024 \
-- \
docs

Expand Down
3 changes: 3 additions & 0 deletions cli/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"path/filepath"
"sort"

"github.com/gruntwork-io/terragrunt/cli/commands/stack"

"github.com/gruntwork-io/terragrunt/engine"
"github.com/gruntwork-io/terragrunt/internal/os/exec"
"github.com/gruntwork-io/terragrunt/internal/os/signal"
Expand Down Expand Up @@ -154,6 +156,7 @@ func TerragruntCommands(opts *options.TerragruntOptions) cli.Commands {
outputmodulegroups.NewCommand(opts), // output-module-groups
catalog.NewCommand(opts), // catalog
scaffold.NewCommand(opts), // scaffold
stack.NewCommand(opts), // stack
graph.NewCommand(opts), // graph
hclvalidate.NewCommand(opts), // hclvalidate
}
Expand Down
233 changes: 233 additions & 0 deletions cli/commands/stack/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package stack

import (
"context"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/gruntwork-io/terragrunt/internal/experiment"

"github.com/gruntwork-io/terragrunt/config"
getter "github.com/hashicorp/go-getter"

"github.com/gruntwork-io/terragrunt/internal/errors"
"github.com/gruntwork-io/terragrunt/options"
)

const (
stackCacheDir = ".terragrunt-stack"
defaultStackFile = "terragrunt.stack.hcl"
dirPerm = 0755
)

// RunGenerate runs the stack command.
func RunGenerate(ctx context.Context, opts *options.TerragruntOptions) error {
stacksEnabled := opts.Experiments[experiment.Stacks]
if !stacksEnabled.Enabled {
return errors.New("stacks experiment is not enabled use --experiment stacks to enable it")
}

return generateStack(ctx, opts)
}

func generateStack(ctx context.Context, opts *options.TerragruntOptions) error {
opts.TerragruntStackConfigPath = filepath.Join(opts.WorkingDir, defaultStackFile)
stackFile, err := config.ReadStackConfigFile(ctx, opts)

if err != nil {
return errors.New(err)
}

if err := processStackFile(ctx, opts, stackFile); err != nil {
return errors.New(err)
}

return nil
}
func processStackFile(ctx context.Context, opts *options.TerragruntOptions, stackFile *config.StackConfigFile) error {
baseDir := filepath.Join(opts.WorkingDir, stackCacheDir)
if err := os.MkdirAll(baseDir, dirPerm); err != nil {
return errors.New(fmt.Errorf("failed to create base directory: %w", err))
}

for _, unit := range stackFile.Units {
destPath := filepath.Join(baseDir, unit.Path)
dest, err := filepath.Abs(destPath)

if err != nil {
return errors.New(fmt.Errorf("failed to get absolute path for destination '%s': %w", dest, err))
}

client := &getter.Client{
Dst: dest,
Mode: getter.ClientModeAny,
Dir: true,
DisableSymlinks: true,
Options: []getter.ClientOption{
getter.WithContext(ctx),
},
}

// setting custom getters
client.Getters = map[string]getter.Getter{}

for getterName, getterValue := range getter.Getters {
// setting custom getter for file to not use symlinks
if getterName == "file" {
client.Getters[getterName] = &stacksFileProvider{}
} else {
client.Getters[getterName] = getterValue
}
}

// fetching unit source
src := unit.Source

// set absolute path for source if it's not an absolute path or URL
if !filepath.IsAbs(unit.Source) && !isURL(client, unit.Source) {
src = filepath.Join(opts.WorkingDir, unit.Source)
src, err = filepath.Abs(src)

if err != nil {
opts.Logger.Warnf("failed to get absolute path for source '%s': %v", unit.Source, err)
src = unit.Source
}
}

opts.Logger.Debugf("Processing unit: %s (%s) to %s", unit.Name, src, dest)

client.Src = src

if err := client.Get(); err != nil {
return errors.New(err)
}
}

return nil
}

func isURL(client *getter.Client, str string) bool {
value, err := getter.Detect(str, client.Dst, getter.Detectors)
if err != nil {
return false
}
// check if starts with file://
if strings.HasPrefix(value, "file://") {
return false
}

return true
}

// stacksFileProvider is a custom getter for file:// protocol.
type stacksFileProvider struct {
client *getter.Client
}

// Get implements downloading functionality.
func (p *stacksFileProvider) Get(dst string, u *url.URL) error {
src := u.Path
file, err := os.Stat(src)

if err != nil {
return errors.New(fmt.Errorf("source path error: %w", err))
}

if file.IsDir() {
return p.copyDir(src, dst)
}

return p.copyFile(src, dst)
}

// GetFile implements single file download.
func (p *stacksFileProvider) GetFile(dst string, u *url.URL) error {
return p.copyFile(u.Path, dst)
}

// ClientMode determines if we're getting a directory or single file.
func (p *stacksFileProvider) ClientMode(u *url.URL) (getter.ClientMode, error) {
fi, err := os.Stat(u.Path)
if err != nil {
return getter.ClientModeInvalid, errors.New(err)
}

if fi.IsDir() {
denis256 marked this conversation as resolved.
Show resolved Hide resolved
return getter.ClientModeDir, nil
}

return getter.ClientModeFile, nil
}

// SetClient sets the client for this provider.
func (p *stacksFileProvider) SetClient(c *getter.Client) {
p.client = c
}

func (p *stacksFileProvider) copyFile(src, dst string) error {
if err := os.MkdirAll(filepath.Dir(dst), dirPerm); err != nil {
return errors.New(err)
}

srcFile, err := os.Open(src)
if err != nil {
return errors.New(err)
}
defer srcFile.Close()

srcInfo, err := srcFile.Stat()
if err != nil {
return errors.New(err)
}

dstFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, srcInfo.Mode())
if err != nil {
return errors.New(err)
}
defer dstFile.Close()

if _, err := io.Copy(dstFile, srcFile); err != nil {
return errors.New(err)
}

return nil
}

func (p *stacksFileProvider) copyDir(src, dst string) error {
srcInfo, err := os.Stat(src)
if err != nil {
return errors.New(err)
}

if err := os.MkdirAll(dst, srcInfo.Mode()); err != nil {
return errors.New(err)
}

entries, err := os.ReadDir(src)
if err != nil {
return errors.New(err)
}

for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())

if entry.IsDir() {
if err := p.copyDir(srcPath, dstPath); err != nil {
return errors.New(err)
}

continue
}

if err := p.copyFile(srcPath, dstPath); err != nil {
return errors.New(err)
}
}

return nil
}
41 changes: 41 additions & 0 deletions cli/commands/stack/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Package stack provides the command to stack.
package stack

import (
"github.com/gruntwork-io/terragrunt/options"
"github.com/gruntwork-io/terragrunt/pkg/cli"
)

const (
// CommandName stack command name.
CommandName = "stack"
generate = "generate"
)

// NewFlags builds the flags for stack.
func NewFlags(_ *options.TerragruntOptions) cli.Flags {
return cli.Flags{}
}

// NewCommand builds the command for stack.
func NewCommand(opts *options.TerragruntOptions) *cli.Command {
return &cli.Command{
Name: CommandName,
Usage: "Terragrunt stack commands.",
DisallowUndefinedFlags: true,
Flags: NewFlags(opts).Sort(),
Subcommands: cli.Commands{
&cli.Command{
Name: "generate",
Usage: "Generate the stack file.",
Action: func(ctx *cli.Context) error {
return RunGenerate(ctx.Context, opts.OptionsFromContext(ctx))

},
},
},
Action: func(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, generate)
},
}
}
Loading