Skip to content

Commit ce7fa82

Browse files
committed
feat: Moving dag graph to list
1 parent 2414861 commit ce7fa82

File tree

7 files changed

+641
-104
lines changed

7 files changed

+641
-104
lines changed

cli/commands/dag/graph/cli.go

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
// Package graph implements the terragrunt dag graph command which generates a visual
22
// representation of the Terragrunt dependency graph in DOT language format.
3+
//
4+
// Alias for 'list --format=dot --dag --dependencies --external'.
35
package graph
46

57
import (
6-
"github.com/gruntwork-io/terragrunt/cli/commands/common/graph"
7-
"github.com/gruntwork-io/terragrunt/cli/commands/common/runall"
8-
"github.com/gruntwork-io/terragrunt/cli/commands/run"
8+
"github.com/gruntwork-io/terragrunt/cli/commands/list"
99
"github.com/gruntwork-io/terragrunt/cli/flags"
1010
"github.com/gruntwork-io/terragrunt/internal/cli"
11-
"github.com/gruntwork-io/terragrunt/internal/runner"
1211
"github.com/gruntwork-io/terragrunt/options"
1312
"github.com/gruntwork-io/terragrunt/pkg/log"
1413
)
@@ -18,31 +17,23 @@ const (
1817
)
1918

2019
func NewCommand(l log.Logger, opts *options.TerragruntOptions, _ flags.Prefix) *cli.Command {
21-
cmd := &cli.Command{
20+
return &cli.Command{
2221
Name: CommandName,
23-
Usage: "Graph the Directed Acyclic Graph (DAG) in DOT language.",
22+
Usage: "Graph the Directed Acyclic Graph (DAG) in DOT language. Alias for 'list --format=dot --dag --dependencies --external'.",
2423
UsageText: "terragrunt dag graph",
2524
Action: func(ctx *cli.Context) error {
2625
return Run(ctx, l, opts)
2726
},
28-
Flags: run.NewFlags(l, opts, nil),
2927
}
30-
31-
cmd = runall.WrapCommand(l, opts, cmd, run.Run, true)
32-
cmd = graph.WrapCommand(l, opts, cmd, run.Run, true)
33-
34-
return cmd
3528
}
3629

3730
func Run(ctx *cli.Context, l log.Logger, opts *options.TerragruntOptions) error {
38-
stack, err := runner.FindStackInSubfolders(ctx, l, opts)
39-
if err != nil {
40-
return err
41-
}
42-
43-
if err := stack.GetStack().Units.WriteDot(l, opts.Writer, opts); err != nil {
44-
l.Warnf("Failed to graph dot: %v", err)
45-
}
46-
47-
return nil
31+
listOpts := list.NewOptions(opts)
32+
listOpts.Format = list.FormatDot
33+
listOpts.Mode = list.ModeDAG
34+
listOpts.Dependencies = true
35+
listOpts.External = true
36+
listOpts.Hidden = true
37+
38+
return list.Run(ctx, l, listOpts)
4839
}

cli/commands/list/cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func NewFlags(opts *Options, prefix flags.Prefix) cli.Flags {
4141
Name: FormatFlagName,
4242
EnvVars: tgPrefix.EnvVars(FormatFlagName),
4343
Destination: &opts.Format,
44-
Usage: "Output format for list results. Valid values: text, tree, long.",
44+
Usage: "Output format for list results. Valid values: text, tree, long, dot.",
4545
DefaultText: FormatText,
4646
}),
4747
flags.NewFlag(&cli.BoolFlag{

cli/commands/list/list.go

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package list
22

33
import (
44
"context"
5+
"fmt"
56
"os"
67
"path/filepath"
78
"sort"
@@ -104,6 +105,8 @@ func Run(ctx context.Context, l log.Logger, opts *Options) error {
104105
return outputTree(l, opts, listedComponents, opts.Mode)
105106
case FormatLong:
106107
return outputLong(l, opts, listedComponents)
108+
case FormatDot:
109+
return outputDot(l, opts, listedComponents)
107110
default:
108111
// This should never happen, because of validation in the command.
109112
// If it happens, we want to throw so we can fix the validation.
@@ -131,10 +134,10 @@ func shouldDiscoverDependencies(opts *Options) bool {
131134
type ListedComponents []*ListedComponent
132135

133136
type ListedComponent struct {
134-
Type component.Kind
135-
Path string
136-
137+
Type component.Kind
138+
Path string
137139
Dependencies []*ListedComponent
140+
Excluded bool
138141
}
139142

140143
// Contains checks to see if the given path is in the listed components.
@@ -168,11 +171,17 @@ func discoveredToListed(components component.Components, opts *Options) (ListedC
168171
continue
169172
}
170173

174+
excluded := false
175+
171176
if opts.QueueConstructAs != "" {
172177
if unit, ok := c.(*component.Unit); ok {
173178
if cfg := unit.Config(); cfg != nil && cfg.Exclude != nil {
174179
if cfg.Exclude.IsActionListed(opts.QueueConstructAs) {
175-
continue
180+
if opts.Format != FormatDot {
181+
continue
182+
}
183+
184+
excluded = true
176185
}
177186
}
178187
}
@@ -186,8 +195,9 @@ func discoveredToListed(components component.Components, opts *Options) (ListedC
186195
}
187196

188197
listedCfg := &ListedComponent{
189-
Type: c.Kind(),
190-
Path: relPath,
198+
Type: c.Kind(),
199+
Path: relPath,
200+
Excluded: excluded,
191201
}
192202

193203
if len(c.Dependencies()) == 0 {
@@ -206,9 +216,22 @@ func discoveredToListed(components component.Components, opts *Options) (ListedC
206216
continue
207217
}
208218

219+
depExcluded := false
220+
221+
if opts.QueueConstructAs != "" {
222+
if depUnit, ok := dep.(*component.Unit); ok {
223+
if depCfg := depUnit.Config(); depCfg != nil && depCfg.Exclude != nil {
224+
if depCfg.Exclude.IsActionListed(opts.QueueConstructAs) {
225+
depExcluded = true
226+
}
227+
}
228+
}
229+
}
230+
209231
listedCfg.Dependencies[i] = &ListedComponent{
210-
Type: dep.Kind(),
211-
Path: relDepPath,
232+
Type: dep.Kind(),
233+
Path: relDepPath,
234+
Excluded: depExcluded,
212235
}
213236
}
214237

@@ -442,6 +465,11 @@ func outputTree(l log.Logger, opts *Options, components ListedComponents, sort s
442465
return renderTree(opts, components, s, sort)
443466
}
444467

468+
// outputDot outputs the discovered components in GraphViz DOT format.
469+
func outputDot(_ log.Logger, opts *Options, components ListedComponents) error {
470+
return renderDot(opts, components)
471+
}
472+
445473
type TreeStyler struct {
446474
entryStyle lipgloss.Style
447475
rootStyle lipgloss.Style
@@ -674,3 +702,73 @@ func getLongestPathLen(components ListedComponents) int {
674702

675703
return longest
676704
}
705+
706+
// renderDot renders the components in GraphViz DOT format.
707+
func renderDot(opts *Options, components ListedComponents) error {
708+
if _, err := opts.Writer.Write([]byte("digraph {\n")); err != nil {
709+
return errors.New(err)
710+
}
711+
712+
writtenNodes := make(map[string]bool, len(components))
713+
714+
for _, component := range components {
715+
if !writtenNodes[component.Path] {
716+
style := ""
717+
if component.Excluded {
718+
style = "[color=red]"
719+
}
720+
721+
if _, writeErr := opts.Writer.Write(
722+
fmt.Appendf(
723+
nil,
724+
"\t\"%s\" %s;\n",
725+
component.Path,
726+
style,
727+
),
728+
); writeErr != nil {
729+
return errors.New(writeErr)
730+
}
731+
732+
writtenNodes[component.Path] = true
733+
}
734+
735+
for _, dep := range component.Dependencies {
736+
if !writtenNodes[dep.Path] {
737+
style := ""
738+
if dep.Excluded {
739+
style = "[color=red]"
740+
}
741+
742+
if _, writeErr := opts.Writer.Write(
743+
fmt.Appendf(
744+
nil,
745+
"\t\"%s\" %s;\n",
746+
dep.Path,
747+
style,
748+
),
749+
); writeErr != nil {
750+
return errors.New(writeErr)
751+
}
752+
753+
writtenNodes[dep.Path] = true
754+
}
755+
756+
if _, writeErr := opts.Writer.Write(
757+
fmt.Appendf(
758+
nil,
759+
"\t\"%s\" -> \"%s\";\n",
760+
component.Path,
761+
dep.Path,
762+
),
763+
); writeErr != nil {
764+
return errors.New(writeErr)
765+
}
766+
}
767+
}
768+
769+
if _, err := opts.Writer.Write([]byte("}\n")); err != nil {
770+
return errors.New(err)
771+
}
772+
773+
return nil
774+
}

0 commit comments

Comments
 (0)