Skip to content

Commit 98dc6e9

Browse files
Merge branch 'main' into fix-terragrunt-dir-in-stack
2 parents 90126c1 + 4b15e0d commit 98dc6e9

40 files changed

+774
-511
lines changed

.circleci/config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ env: &env
1212

1313
defaults: &defaults
1414
docker:
15-
- image: 087285199408.dkr.ecr.us-east-1.amazonaws.com/circle-ci-test-image-base:go1.24.3-tf1.5-tg58.8-pck1.8-ci58.2
15+
- image: 087285199408.dkr.ecr.us-east-1.amazonaws.com/circle-ci-test-image-base:go1.24.4-tf1.5-tg58.8-pck1.8-ci58.2
1616

1717
version: 2.1
1818
jobs:
@@ -36,7 +36,7 @@ jobs:
3636
- attach_workspace:
3737
at: .
3838
- go/install:
39-
version: "1.24.3"
39+
version: "1.24.4"
4040
- run:
4141
name: Install sign-binary-helpers
4242
command: |
@@ -73,7 +73,7 @@ jobs:
7373
- attach_workspace:
7474
at: .
7575
- go/install:
76-
version: "1.24.3"
76+
version: "1.24.4"
7777
- run:
7878
name: Install sign-binary-helpers
7979
command: |

.github/workflows/pages.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- name: Checkout
2929
uses: actions/checkout@v4
3030
- name: Setup Ruby
31-
uses: ruby/setup-ruby@cb0fda56a307b8c78d38320cd40d9eb22a3bf04e # v1.242.0
31+
uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 # v1.245.0
3232
with:
3333
ruby-version: '3.3'
3434
bundler-cache: true

cli/commands/scaffold/scaffold.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/url"
77
"os"
8+
"path/filepath"
89
"regexp"
910
"strings"
1011

@@ -236,33 +237,50 @@ func generateDefaultTemplate(boilerplateDir string) (string, error) {
236237
return boilerplateDir, nil
237238
}
238239

239-
// downloadTemplate - parse URL and download files
240+
// downloadTemplate - parse URL, download files, and handle subfolders
240241
func downloadTemplate(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, templateURL string, tempDir string) (string, error) {
241242
parsedTemplateURL, err := tf.ToSourceURL(templateURL, tempDir)
242243
if err != nil {
243244
return "", errors.New(err)
244245
}
245246

246-
parsedTemplateURL, err = rewriteTemplateURL(ctx, l, opts, parsedTemplateURL)
247+
// Split the processed URL to get the base URL and subfolder
248+
baseURL, subFolder, err := tf.SplitSourceURL(l, parsedTemplateURL)
247249
if err != nil {
248250
return "", errors.New(err)
249251
}
250-
// regenerate template url with all changes
251-
templateURL = parsedTemplateURL.String()
252252

253-
// prepare temporary directory for template
254-
templateDir, err := os.MkdirTemp("", "template")
253+
// Go-getter expects a pathspec or . for file paths
254+
if baseURL.Scheme == "" || baseURL.Scheme == "file" {
255+
baseURL.Path = filepath.ToSlash(strings.TrimSuffix(baseURL.Path, "/")) + "//."
256+
}
257+
258+
baseURL, err = rewriteTemplateURL(ctx, l, opts, baseURL)
255259
if err != nil {
256260
return "", errors.New(err)
257261
}
258262

259-
// downloading templateURL
260-
l.Infof("Using template from %s", templateURL)
263+
templateDir, err := os.MkdirTemp(tempDir, "template")
264+
if err != nil {
265+
return "", errors.New(err)
266+
}
261267

262-
if _, err := getter.GetAny(ctx, templateDir, templateURL); err != nil {
268+
l.Infof("Downloading template from %s into %s", baseURL.String(), templateDir)
269+
// Downloading baseURL to support boilerplate dependencies and partials. Go-getter discards all but specified folder if one is provided.
270+
if _, err := getter.GetAny(ctx, templateDir, baseURL.String()); err != nil {
263271
return "", errors.New(err)
264272
}
265273

274+
// Add subfolder to templateDir if provided, as scaffold needs path to boilerplate.yml file
275+
if subFolder != "" {
276+
subFolder = strings.TrimPrefix(subFolder, "/")
277+
templateDir = filepath.Join(templateDir, subFolder)
278+
// Verify that subfolder exists
279+
if _, err := os.Stat(templateDir); os.IsNotExist(err) {
280+
return "", errors.Errorf("subfolder \"//%s\" not found in downloaded template from %s", subFolder, templateURL)
281+
}
282+
}
283+
266284
return templateDir, nil
267285
}
268286

@@ -298,7 +316,7 @@ func prepareBoilerplateFiles(ctx context.Context, l log.Logger, opts *options.Te
298316

299317
boilerplateDir = tempTemplateDir
300318
} else {
301-
defaultTempDir, err := os.MkdirTemp("", "boilerplate")
319+
defaultTempDir, err := os.MkdirTemp(tempDir, "boilerplate")
302320
if err != nil {
303321
return "", errors.New(err)
304322
}

cli/commands/stack/cli.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const (
1515
OutputFormatFlagName = "format"
1616
JSONFormatFlagName = "json"
1717
RawFormatFlagName = "raw"
18-
NoStackGenerate = "no-stack-generate"
1918
NoStackValidate = "no-stack-validate"
2019

2120
generateCommandName = "generate"

cli/commands/stack/stack.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ func RunGenerate(ctx context.Context, l log.Logger, opts *options.TerragruntOpti
3030
return nil
3131
}
3232

33+
opts.StackAction = "generate"
34+
3335
return telemetry.TelemeterFromContext(ctx).Collect(ctx, "stack_generate", map[string]any{
3436
"stack_config_path": opts.TerragruntStackConfigPath,
3537
"working_dir": opts.WorkingDir,
@@ -40,6 +42,8 @@ func RunGenerate(ctx context.Context, l log.Logger, opts *options.TerragruntOpti
4042

4143
// Run execute stack command.
4244
func Run(ctx context.Context, l log.Logger, opts *options.TerragruntOptions) error {
45+
opts.StackAction = "run"
46+
4347
err := telemetry.TelemeterFromContext(ctx).Collect(ctx, "stack_run", map[string]any{
4448
"stack_config_path": opts.TerragruntStackConfigPath,
4549
"working_dir": opts.WorkingDir,
@@ -52,12 +56,17 @@ func Run(ctx context.Context, l log.Logger, opts *options.TerragruntOptions) err
5256
}
5357

5458
opts.WorkingDir = filepath.Join(opts.WorkingDir, stackDir)
59+
if _, err := os.Stat(opts.WorkingDir); os.IsNotExist(err) {
60+
return errors.Errorf("Stack directory does not exist or is not accessible: %s", opts.WorkingDir)
61+
}
5562

5663
return runall.Run(ctx, l, opts)
5764
}
5865

5966
// RunOutput stack output.
6067
func RunOutput(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, index string) error {
68+
opts.StackAction = "output"
69+
6170
var outputs cty.Value
6271

6372
// collect outputs

config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,6 +1189,10 @@ func ParseConfigFile(ctx *ParsingContext, l log.Logger, configPath string, inclu
11891189

11901190
fileInfo, err := os.Stat(configPath)
11911191
if err != nil {
1192+
if os.IsNotExist(err) {
1193+
return TerragruntConfigNotFoundError{Path: configPath}
1194+
}
1195+
11921196
return errors.Errorf("failed to get file info: %w", err)
11931197
}
11941198

config/config_partial.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ func PartialParseConfigFile(ctx *ParsingContext, l log.Logger, configPath string
284284

285285
fileInfo, err := os.Stat(configPath)
286286
if err != nil {
287+
if os.IsNotExist(err) {
288+
return nil, TerragruntConfigNotFoundError{Path: configPath}
289+
}
290+
287291
return nil, errors.New(err)
288292
}
289293

config/errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ type TerragruntConfigNotFoundError struct {
142142
}
143143

144144
func (err TerragruntConfigNotFoundError) Error() string {
145-
return fmt.Sprintf("Terragrunt config %s not found", err.Path)
145+
return fmt.Sprintf("You attempted to run terragrunt in a folder that does not contain a terragrunt.hcl file. Please add a terragrunt.hcl file and try again.\n\nPath: %q", err.Path)
146146
}
147147

148148
type InvalidSourceURLError struct {

config/stack.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ func GenerateStacks(ctx context.Context, l log.Logger, opts *options.TerragruntO
8989
return errors.Errorf("Failed to list stack files in %s %w", opts.WorkingDir, err)
9090
}
9191

92+
if len(foundFiles) == 0 {
93+
if opts.StackAction == "generate" {
94+
l.Warnf("No stack files found in %s Nothing to generate.", opts.WorkingDir)
95+
}
96+
97+
return nil
98+
}
99+
92100
for {
93101
// check if we have already processed the files
94102
processedNewFiles := false
@@ -169,6 +177,11 @@ func StackOutput(ctx context.Context, l log.Logger, opts *options.TerragruntOpti
169177
return cty.NilVal, errors.Errorf("Failed to list stack files in %s: %w", opts.WorkingDir, err)
170178
}
171179

180+
if len(foundFiles) == 0 {
181+
l.Warnf("No stack files found in %s Nothing to generate.", opts.WorkingDir)
182+
return cty.NilVal, nil
183+
}
184+
172185
outputs := make(map[string]map[string]cty.Value)
173186
declaredStacks := make(map[string]string)
174187
declaredUnits := make(map[string]*Unit)

configstack/running_module.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,34 @@ func (module *RunningModule) moduleFinished(moduleErr error, r *report.Report, r
221221

222222
if reportExperiment {
223223
if err := r.EndRun(module.Module.Path); err != nil {
224-
module.Logger.Errorf("Error ending run for module %s: %v", module.Module.Path, err)
224+
// If the run is not found in the report, it likely means this module was an external dependency
225+
// that was excluded from the queue (e.g., with --queue-exclude-external).
226+
if !errors.Is(err, report.ErrRunNotFound) {
227+
module.Logger.Errorf("Error ending run for unit %s: %v", module.Module.Path, err)
228+
229+
return
230+
}
231+
232+
if module.Module.AssumeAlreadyApplied {
233+
run, err := report.NewRun(module.Module.Path)
234+
if err != nil {
235+
module.Logger.Errorf("Error creating run for unit %s: %v", module.Module.Path, err)
236+
return
237+
}
238+
239+
if err := r.AddRun(run); err != nil {
240+
module.Logger.Errorf("Error adding run for unit %s: %v", module.Module.Path, err)
241+
return
242+
}
243+
244+
if err := r.EndRun(
245+
run.Path,
246+
report.WithResult(report.ResultExcluded),
247+
report.WithReason(report.ReasonExcludeExternal),
248+
); err != nil {
249+
module.Logger.Errorf("Error ending run for unit %s: %v", module.Module.Path, err)
250+
}
251+
}
225252
}
226253
}
227254
} else {
@@ -234,10 +261,6 @@ func (module *RunningModule) moduleFinished(moduleErr error, r *report.Report, r
234261
report.WithReason(report.ReasonRunError),
235262
report.WithCauseRunError(moduleErr.Error()),
236263
); err != nil {
237-
// If we can't find the run, then it never started,
238-
// So we should start it and then end it as a failed run.
239-
//
240-
// Early exit runs should already be ended at this point.
241264
if errors.Is(err, report.ErrRunNotFound) {
242265
run, err := report.NewRun(module.Module.Path)
243266
if err != nil {

0 commit comments

Comments
 (0)