From 4f26f4aa96d019b4bcc024e5110ec078db7203b7 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 13 Dec 2024 20:37:00 +0000 Subject: [PATCH 01/49] colors default --- pkg/ui/colors.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 pkg/ui/colors.go diff --git a/pkg/ui/colors.go b/pkg/ui/colors.go new file mode 100644 index 000000000..f83fc6615 --- /dev/null +++ b/pkg/ui/colors.go @@ -0,0 +1,14 @@ +package markdown + +import "github.com/charmbracelet/lipgloss" + +// Colors defines the color scheme for markdown rendering +var Colors = struct { + Primary lipgloss.AdaptiveColor + Secondary lipgloss.AdaptiveColor + Success lipgloss.AdaptiveColor +}{ + Primary: lipgloss.AdaptiveColor{Light: "#00A3E0", Dark: "#00A3E0"}, // Atmos blue + Secondary: lipgloss.AdaptiveColor{Light: "#4A5568", Dark: "#A0AEC0"}, // Slate gray + Success: lipgloss.AdaptiveColor{Light: "#48BB78", Dark: "#68D391"}, // Green +} From 9bbad7a9eaeacdca8f201bed35c8a52a36bf5936 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 13 Dec 2024 21:15:24 +0000 Subject: [PATCH 02/49] added more case colors --- pkg/ui/colors.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/pkg/ui/colors.go b/pkg/ui/colors.go index f83fc6615..b98803689 100644 --- a/pkg/ui/colors.go +++ b/pkg/ui/colors.go @@ -4,11 +4,25 @@ import "github.com/charmbracelet/lipgloss" // Colors defines the color scheme for markdown rendering var Colors = struct { - Primary lipgloss.AdaptiveColor - Secondary lipgloss.AdaptiveColor - Success lipgloss.AdaptiveColor + Primary lipgloss.AdaptiveColor + Secondary lipgloss.AdaptiveColor + Success lipgloss.AdaptiveColor + Warning lipgloss.AdaptiveColor + Error lipgloss.AdaptiveColor + Info lipgloss.AdaptiveColor + Subtle lipgloss.AdaptiveColor + HeaderBg lipgloss.AdaptiveColor + Border lipgloss.AdaptiveColor + Background lipgloss.AdaptiveColor }{ - Primary: lipgloss.AdaptiveColor{Light: "#00A3E0", Dark: "#00A3E0"}, // Atmos blue - Secondary: lipgloss.AdaptiveColor{Light: "#4A5568", Dark: "#A0AEC0"}, // Slate gray - Success: lipgloss.AdaptiveColor{Light: "#48BB78", Dark: "#68D391"}, // Green + Primary: lipgloss.AdaptiveColor{Light: "#00A3E0", Dark: "#00A3E0"}, // Atmos blue + Secondary: lipgloss.AdaptiveColor{Light: "#4A5568", Dark: "#A0AEC0"}, // Slate gray + Success: lipgloss.AdaptiveColor{Light: "#48BB78", Dark: "#68D391"}, // Green + Warning: lipgloss.AdaptiveColor{Light: "#ECC94B", Dark: "#F6E05E"}, // Yellow + Error: lipgloss.AdaptiveColor{Light: "#F56565", Dark: "#FC8181"}, // Red + Info: lipgloss.AdaptiveColor{Light: "#4299E1", Dark: "#63B3ED"}, // Light blue + Subtle: lipgloss.AdaptiveColor{Light: "#718096", Dark: "#A0AEC0"}, // Gray + HeaderBg: lipgloss.AdaptiveColor{Light: "#2D3748", Dark: "#4A5568"}, // Dark slate + Border: lipgloss.AdaptiveColor{Light: "#CBD5E0", Dark: "#4A5568"}, // Light gray + Background: lipgloss.AdaptiveColor{Light: "#F7FAFC", Dark: "#1A202C"}, // Off white/dark } From 91093f67eb5a41aa7df69102494d1479fc9c4185 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 13 Dec 2024 21:16:08 +0000 Subject: [PATCH 03/49] reorder ui --- pkg/ui/{ => markdown}/colors.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/ui/{ => markdown}/colors.go (100%) diff --git a/pkg/ui/colors.go b/pkg/ui/markdown/colors.go similarity index 100% rename from pkg/ui/colors.go rename to pkg/ui/markdown/colors.go From c3c2b9e225f8d3bded79ad93aaf3333e87791e7e Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sat, 14 Dec 2024 08:49:52 +0000 Subject: [PATCH 04/49] default renderer --- pkg/ui/markdown/renderer.go | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 pkg/ui/markdown/renderer.go diff --git a/pkg/ui/markdown/renderer.go b/pkg/ui/markdown/renderer.go new file mode 100644 index 000000000..dad3eadd0 --- /dev/null +++ b/pkg/ui/markdown/renderer.go @@ -0,0 +1,44 @@ +package markdown + +import ( + "github.com/charmbracelet/glamour" + "github.com/muesli/termenv" +) + +// Renderer is a markdown renderer using Glamour +type Renderer struct { + renderer *glamour.TermRenderer + width uint + profile termenv.Profile +} + +// NewRenderer creates a new markdown renderer with the given options +func NewRenderer(opts ...Option) (*Renderer, error) { + r := &Renderer{ + width: 80, // default width + profile: termenv.ColorProfile(), // default color profile + } + + // Apply options + for _, opt := range opts { + opt(r) + } + + // Initialize glamour renderer + renderer, err := glamour.NewTermRenderer( + glamour.WithAutoStyle(), + glamour.WithWordWrap(int(r.width)), + glamour.WithStylesFromJSONBytes(DefaultStyle), + glamour.WithColorProfile(r.profile), + glamour.WithEmoji(), + ) + if err != nil { + return nil, err + } + + r.renderer = renderer + return r, nil +} + +// Option is a function that configures the renderer +type Option func(*Renderer) From 0724a6ec1e62a62015f8637c107317af0259d0f2 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sat, 14 Dec 2024 09:11:24 +0000 Subject: [PATCH 05/49] adding styles --- pkg/ui/markdown/renderer.go | 35 +++++++++++++++++++++++++++++++++++ pkg/ui/markdown/styles.go | 8 ++++++++ 2 files changed, 43 insertions(+) create mode 100644 pkg/ui/markdown/styles.go diff --git a/pkg/ui/markdown/renderer.go b/pkg/ui/markdown/renderer.go index dad3eadd0..bfba82782 100644 --- a/pkg/ui/markdown/renderer.go +++ b/pkg/ui/markdown/renderer.go @@ -40,5 +40,40 @@ func NewRenderer(opts ...Option) (*Renderer, error) { return r, nil } +// Render renders markdown content to ANSI styled text +func (r *Renderer) Render(content string) (string, error) { + return r.renderer.Render(content) +} + +// RenderWithStyle renders markdown content with a specific style +func (r *Renderer) RenderWithStyle(content string, style []byte) (string, error) { + renderer, err := glamour.NewTermRenderer( + glamour.WithAutoStyle(), + glamour.WithWordWrap(int(r.width)), + glamour.WithStylesFromJSONBytes(style), + glamour.WithColorProfile(r.profile), + glamour.WithEmoji(), + ) + if err != nil { + return "", err + } + + return renderer.Render(content) +} + // Option is a function that configures the renderer type Option func(*Renderer) + +// WithWidth sets the word wrap width for the renderer +func WithWidth(width uint) Option { + return func(r *Renderer) { + r.width = width + } +} + +// WithColorProfile sets the color profile for the renderer +func WithColorProfile(profile termenv.Profile) Option { + return func(r *Renderer) { + r.profile = profile + } +} diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go new file mode 100644 index 000000000..c2e3b3e75 --- /dev/null +++ b/pkg/ui/markdown/styles.go @@ -0,0 +1,8 @@ +package markdown + +// DefaultStyle defines the default Atmos markdown style +var DefaultStyle = []byte(`{ + "CodeBlock": { + "Color": "240" + } +}`) From 1e179123edae09cf7f1acceba337046cb805b72b Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sun, 15 Dec 2024 23:47:11 +0000 Subject: [PATCH 06/49] general styles --- pkg/ui/markdown/styles.go | 206 +++++++++++++++++++++++++++++++++++++- 1 file changed, 203 insertions(+), 3 deletions(-) diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go index c2e3b3e75..06441d5a3 100644 --- a/pkg/ui/markdown/styles.go +++ b/pkg/ui/markdown/styles.go @@ -2,7 +2,207 @@ package markdown // DefaultStyle defines the default Atmos markdown style var DefaultStyle = []byte(`{ - "CodeBlock": { - "Color": "240" - } + "document": { + "block_prefix": "\n", + "block_suffix": "\n", + "color": "#4A5568", + "margin": 0 + }, + "block_quote": { + "indent": 1, + "indent_token": "│ " + }, + "paragraph": { + "block_suffix": "\n" + }, + "list": { + "level_indent": 2 + }, + "heading": { + "block_suffix": "\n", + "color": "#00A3E0", + "bold": true + }, + "h1": { + "prefix": "# ", + "color": "#00A3E0", + "bold": true + }, + "h2": { + "prefix": "## ", + "color": "#00A3E0", + "bold": true + }, + "h3": { + "prefix": "### ", + "color": "#00A3E0", + "bold": true + }, + "h4": { + "prefix": "#### ", + "color": "#00A3E0", + "bold": true + }, + "h5": { + "prefix": "##### ", + "color": "#00A3E0", + "bold": true + }, + "h6": { + "prefix": "###### ", + "color": "#00A3E0", + "bold": true + }, + "text": {}, + "strikethrough": { + "crossed_out": true + }, + "emph": { + "italic": true + }, + "strong": { + "bold": true + }, + "hr": { + "color": "#CBD5E0", + "format": "\n--------\n" + }, + "item": { + "block_prefix": "• " + }, + "enumeration": { + "block_prefix": ". " + }, + "task": { + "ticked": "[✓] ", + "unticked": "[ ] " + }, + "link": { + "color": "#4299E1", + "underline": true + }, + "link_text": { + "color": "#4299E1" + }, + "image": { + "color": "#4299E1" + }, + "image_text": { + "color": "#4299E1", + "format": "Image: {{.text}} →" + }, + "code": { + "color": "#4A5568", + "background_color": "#F7FAFC" + }, + "code_block": { + "color": "#4A5568", + "background_color": "#F7FAFC", + "margin": 2, + "chroma": { + "text": { + "color": "#4A5568" + }, + "error": { + "color": "#F56565", + "background_color": "#F7FAFC" + }, + "comment": { + "color": "#718096" + }, + "comment_preproc": { + "color": "#4299E1" + }, + "keyword": { + "color": "#00A3E0" + }, + "keyword_reserved": { + "color": "#00A3E0" + }, + "keyword_namespace": { + "color": "#00A3E0" + }, + "keyword_type": { + "color": "#48BB78" + }, + "operator": { + "color": "#4A5568" + }, + "punctuation": { + "color": "#4A5568" + }, + "name": { + "color": "#4A5568" + }, + "name_builtin": { + "color": "#00A3E0" + }, + "name_tag": { + "color": "#00A3E0" + }, + "name_attribute": { + "color": "#48BB78" + }, + "name_class": { + "color": "#48BB78" + }, + "name_constant": { + "color": "#4299E1" + }, + "name_decorator": { + "color": "#4299E1" + }, + "name_exception": { + "color": "#F56565" + }, + "name_function": { + "color": "#4299E1" + }, + "name_other": { + "color": "#4A5568" + }, + "literal": { + "color": "#ECC94B" + }, + "literal_number": { + "color": "#ECC94B" + }, + "literal_date": { + "color": "#ECC94B" + }, + "literal_string": { + "color": "#48BB78" + }, + "literal_string_escape": { + "color": "#4299E1" + }, + "generic_deleted": { + "color": "#F56565" + }, + "generic_emph": { + "italic": true + }, + "generic_inserted": { + "color": "#48BB78" + }, + "generic_strong": { + "bold": true + }, + "generic_subheading": { + "color": "#4299E1" + } + } + }, + "table": { + "center_separator": "┼", + "column_separator": "│", + "row_separator": "─" + }, + "definition_list": {}, + "definition_term": {}, + "definition_description": { + "block_prefix": "\n🠶 " + }, + "html_block": {}, + "html_span": {} }`) From cce868f8660ccd46c852af3ebc343d5fc5b466ed Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sun, 15 Dec 2024 23:48:04 +0000 Subject: [PATCH 07/49] workflow implementation msgs --- cmd/workflow.go | 37 ++- internal/tui/components/code_view/main.go | 26 +- internal/tui/utils/utils.go | 21 ++ internal/tui/workflow/model.go | 311 ++++++++++++---------- 4 files changed, 235 insertions(+), 160 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index cc657a6c9..7061c09ff 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -1,9 +1,12 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" e "github.com/cloudposse/atmos/internal/exec" + "github.com/cloudposse/atmos/internal/tui/utils" "github.com/cloudposse/atmos/pkg/schema" u "github.com/cloudposse/atmos/pkg/utils" ) @@ -12,16 +15,32 @@ import ( var workflowCmd = &cobra.Command{ Use: "workflow", Short: "Execute a workflow", - Long: `This command executes a workflow: atmos workflow -f `, + Long: `This command executes a workflow: atmos workflow --file `, Example: "atmos workflow\n" + - "atmos workflow -f \n" + - "atmos workflow -f -s \n" + - "atmos workflow -f --from-step \n\n" + + "atmos workflow --file \n" + + "atmos workflow --file --stack \n" + + "atmos workflow --file --from-step \n\n" + "To resume the workflow from this step, run:\n" + - "atmos workflow deploy-infra -f workflow1 --from-step deploy-vpc\n\n" + + "atmos workflow deploy-infra --file workflow1 --from-step deploy-vpc\n\n" + "For more details refer to https://atmos.tools/cli/commands/workflow/", FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false}, Run: func(cmd *cobra.Command, args []string) { + // If the first arg is "list", show a clear error message + if len(args) > 0 && args[0] == "list" { + errorMsg := "# Error! Invalid command.\n\n" + + "## Usage\n" + + "`atmos workflow --file `\n\n" + + "Run `atmos workflow --help` for more information." + + rendered, err := utils.RenderMarkdown(errorMsg, "dark") + if err != nil { + fmt.Println(errorMsg) // Fallback to plain text if rendering fails + } else { + fmt.Print(rendered) + } + return + } + err := e.ExecuteWorkflowCmd(cmd, args) if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) @@ -31,10 +50,10 @@ var workflowCmd = &cobra.Command{ func init() { workflowCmd.DisableFlagParsing = false - workflowCmd.PersistentFlags().StringP("file", "f", "", "atmos workflow -f ") - workflowCmd.PersistentFlags().Bool("dry-run", false, "atmos workflow -f --dry-run") - workflowCmd.PersistentFlags().StringP("stack", "s", "", "atmos workflow -f -s ") - workflowCmd.PersistentFlags().String("from-step", "", "atmos workflow -f --from-step ") + workflowCmd.PersistentFlags().StringP("file", "f", "", "atmos workflow --file ") + workflowCmd.PersistentFlags().Bool("dry-run", false, "atmos workflow --file --dry-run") + workflowCmd.PersistentFlags().StringP("stack", "s", "", "atmos workflow --file --stack ") + workflowCmd.PersistentFlags().String("from-step", "", "atmos workflow --file --from-step ") RootCmd.AddCommand(workflowCmd) } diff --git a/internal/tui/components/code_view/main.go b/internal/tui/components/code_view/main.go index 0719eeda4..ebe4d4664 100644 --- a/internal/tui/components/code_view/main.go +++ b/internal/tui/components/code_view/main.go @@ -12,6 +12,7 @@ type Model struct { Viewport viewport.Model HighlightedContent string SyntaxTheme string + IsMarkdown bool } // New creates a new instance of the model @@ -25,6 +26,7 @@ func New(syntaxTheme string) Model { return Model{ Viewport: viewPort, SyntaxTheme: syntaxTheme, + IsMarkdown: false, } } @@ -35,8 +37,26 @@ func (m *Model) Init() tea.Cmd { // SetContent sets content func (m *Model) SetContent(content string, language string) { - highlighted, _ := u.HighlightCode(content, language, m.SyntaxTheme) - m.HighlightedContent = highlighted + var rendered string + var err error + + if language == "markdown" || language == "md" { + m.IsMarkdown = true + rendered, err = u.RenderMarkdown(content, "") + if err != nil { + // Fallback to plain text if markdown rendering fails + rendered = content + } + } else { + m.IsMarkdown = false + rendered, err = u.HighlightCode(content, language, m.SyntaxTheme) + if err != nil { + // Fallback to plain text if syntax highlighting fails + rendered = content + } + } + + m.HighlightedContent = rendered m.Viewport.ViewUp() m.Viewport.MouseWheelEnabled = true @@ -44,7 +64,7 @@ func (m *Model) SetContent(content string, language string) { m.Viewport.SetContent(lipgloss.NewStyle(). Width(m.Viewport.Width). Height(m.Viewport.Height). - Render(highlighted)) + Render(rendered)) } // SetSyntaxTheme sets the syntax theme diff --git a/internal/tui/utils/utils.go b/internal/tui/utils/utils.go index 42bedabb7..ac7d046d0 100644 --- a/internal/tui/utils/utils.go +++ b/internal/tui/utils/utils.go @@ -6,6 +6,7 @@ import ( "github.com/alecthomas/chroma/quick" "github.com/arsham/figurine/figurine" + "github.com/charmbracelet/glamour" "github.com/jwalton/go-supportscolor" ) @@ -27,3 +28,23 @@ func PrintStyledText(text string) error { } return nil } + +// RenderMarkdown renders markdown text with terminal styling +func RenderMarkdown(markdown string, style string) (string, error) { + // If no style is provided, use the default style + if style == "" { + style = "dark" + } + + // Create a new renderer with the specified style + r, err := glamour.NewTermRenderer( + glamour.WithAutoStyle(), + glamour.WithWordWrap(80), + ) + if err != nil { + return "", err + } + + // Render the markdown + return r.Render(markdown) +} diff --git a/internal/tui/workflow/model.go b/internal/tui/workflow/model.go index d7f2cb211..e999b270e 100644 --- a/internal/tui/workflow/model.go +++ b/internal/tui/workflow/model.go @@ -17,15 +17,15 @@ import ( type App struct { help help.Model - loaded bool columnViews []columnView - quit bool - workflows map[string]schema.WorkflowManifest + columnPointer int selectedWorkflowFile string selectedWorkflow string selectedWorkflowStep string - columnPointer int + workflows map[string]schema.WorkflowManifest workflowStepsViewShowWorkflow bool + quit bool + loaded bool } func NewApp(workflows map[string]schema.WorkflowManifest) *App { @@ -47,136 +47,6 @@ func NewApp(workflows map[string]schema.WorkflowManifest) *App { return app } -func (app *App) Init() tea.Cmd { - return nil -} - -func (app *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - // Process messages relevant to the parent view - switch message := msg.(type) { - case tea.WindowSizeMsg: - app.loaded = false - var cmd tea.Cmd - var cmds []tea.Cmd - app.help.Width = message.Width - for i := 0; i < len(app.columnViews); i++ { - var res tea.Model - res, cmd = app.columnViews[i].Update(message) - app.columnViews[i] = *res.(*columnView) - cmds = append(cmds, cmd) - } - app.loaded = true - return app, tea.Batch(cmds...) - - case tea.MouseMsg: - if message.Button == tea.MouseButtonWheelUp { - app.columnViews[app.columnPointer].CursorUp() - app.updateWorkflowFilesAndWorkflowsViews() - return app, nil - } - if message.Button == tea.MouseButtonWheelDown { - app.columnViews[app.columnPointer].CursorDown() - app.updateWorkflowFilesAndWorkflowsViews() - return app, nil - } - if message.Button == tea.MouseButtonLeft { - for i := 0; i < len(app.columnViews); i++ { - zoneInfo := mouseZone.Get(app.columnViews[i].id) - if zoneInfo.InBounds(message) { - app.columnViews[app.columnPointer].Blur() - app.columnPointer = i - app.columnViews[app.columnPointer].Focus() - break - } - } - } - - case tea.KeyMsg: - switch { - case key.Matches(message, keys.CtrlC): - app.quit = true - return app, tea.Quit - case key.Matches(message, keys.Escape): - if app.columnViews[app.columnPointer].viewType == listViewType || app.columnViews[app.columnPointer].viewType == listViewType2 { - res, cmd := app.columnViews[app.columnPointer].Update(msg) - app.columnViews[app.columnPointer] = *res.(*columnView) - if cmd == nil { - return app, nil - } else { - app.quit = true - return app, tea.Quit - } - } - app.quit = true - return app, tea.Quit - case key.Matches(message, keys.Execute): - app.execute() - return app, tea.Quit - case key.Matches(message, keys.Up): - app.columnViews[app.columnPointer].CursorUp() - app.updateWorkflowFilesAndWorkflowsViews() - return app, nil - case key.Matches(message, keys.Down): - app.columnViews[app.columnPointer].CursorDown() - app.updateWorkflowFilesAndWorkflowsViews() - return app, nil - case key.Matches(message, keys.Left): - app.columnViews[app.columnPointer].Blur() - app.columnPointer = app.getPrevViewPointer() - app.columnViews[app.columnPointer].Focus() - return app, nil - case key.Matches(message, keys.Right): - app.columnViews[app.columnPointer].Blur() - app.columnPointer = app.getNextViewPointer() - app.columnViews[app.columnPointer].Focus() - return app, nil - case key.Matches(message, keys.FlipWorkflowStepsView): - app.flipWorkflowStepsView() - return app, nil - } - } - - // Send all other messages to the selected child view - res, cmd := app.columnViews[app.columnPointer].Update(msg) - app.columnViews[app.columnPointer] = *res.(*columnView) - return app, cmd -} - -func (app *App) View() string { - if app.quit { - return "" - } - - if !app.loaded { - return "loading..." - } - - layout := lipgloss.JoinHorizontal( - lipgloss.Left, - app.columnViews[0].View(), - app.columnViews[1].View(), - app.columnViews[2].View(), - ) - - return mouseZone.Scan(lipgloss.JoinVertical(lipgloss.Left, layout, app.help.View(keys))) -} - -func (app *App) GetSelectedWorkflowFile() string { - return app.selectedWorkflowFile -} - -func (app *App) GetSelectedWorkflow() string { - return app.selectedWorkflow -} - -func (app *App) GetSelectedWorkflowStep() string { - return app.selectedWorkflowStep -} - -func (app *App) ExitStatusQuit() bool { - return app.quit -} - func (app *App) initViews(workflows map[string]schema.WorkflowManifest) { app.columnViews = []columnView{ newColumn(0, listViewType), @@ -216,7 +86,12 @@ func (app *App) initViews(workflows map[string]schema.WorkflowManifest) { selectedWorkflowDefinition := workflows[selectedWorkflowFileName].Workflows[selectedWorkflowName] stepItems = lo.Map(selectedWorkflowDefinition.Steps, func(s schema.WorkflowStep, _ int) list.Item { + name := s.Name + if name == "" { + name = s.Command + } return listItem{ + name: name, item: s.Name, } }) @@ -246,20 +121,6 @@ func (app *App) initViews(workflows map[string]schema.WorkflowManifest) { app.columnViews[2].SetContent(selectedWorkflowContent, "yaml") } -func (app *App) getNextViewPointer() int { - if app.columnPointer == 2 { - return 0 - } - return app.columnPointer + 1 -} - -func (app *App) getPrevViewPointer() int { - if app.columnPointer == 0 { - return 2 - } - return app.columnPointer - 1 -} - func (app *App) updateWorkflowFilesAndWorkflowsViews() { if app.columnPointer == 0 { selectedWorkflowFile := app.columnViews[0].list.SelectedItem() @@ -288,7 +149,12 @@ func (app *App) updateWorkflowFilesAndWorkflowsViews() { selectedWorkflowDefinition := app.workflows[selectedWorkflowFileName].Workflows[selectedWorkflowName] stepItems := lo.Map(selectedWorkflowDefinition.Steps, func(s schema.WorkflowStep, _ int) list.Item { + name := s.Name + if name == "" { + name = s.Command + } return listItem{ + name: name, item: s.Name, } }) @@ -315,7 +181,12 @@ func (app *App) updateWorkflowFilesAndWorkflowsViews() { selectedWorkflowDefinition := app.workflows[selectedWorkflowFileName].Workflows[selectedWorkflowName] stepItems := lo.Map(selectedWorkflowDefinition.Steps, func(s schema.WorkflowStep, _ int) list.Item { + name := s.Name + if name == "" { + name = s.Command + } return listItem{ + name: name, item: s.Name, } }) @@ -361,3 +232,147 @@ func (app *App) execute() { app.selectedWorkflowStep = "" } } + +func (app *App) Init() tea.Cmd { + return nil +} + +func (app *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + // Process messages relevant to the parent view + switch message := msg.(type) { + case tea.WindowSizeMsg: + app.loaded = false + var cmd tea.Cmd + var cmds []tea.Cmd + app.help.Width = message.Width + for i := 0; i < len(app.columnViews); i++ { + var res tea.Model + res, cmd = app.columnViews[i].Update(message) + app.columnViews[i] = *res.(*columnView) + cmds = append(cmds, cmd) + } + app.loaded = true + return app, tea.Batch(cmds...) + + case tea.MouseMsg: + if message.Button == tea.MouseButtonWheelUp { + app.columnViews[app.columnPointer].CursorUp() + app.updateWorkflowFilesAndWorkflowsViews() + return app, nil + } + if message.Button == tea.MouseButtonWheelDown { + app.columnViews[app.columnPointer].CursorDown() + app.updateWorkflowFilesAndWorkflowsViews() + return app, nil + } + if message.Button == tea.MouseButtonLeft { + for i := 0; i < len(app.columnViews); i++ { + zoneInfo := mouseZone.Get(app.columnViews[i].id) + if zoneInfo.InBounds(message) { + app.columnViews[app.columnPointer].Blur() + app.columnPointer = i + app.columnViews[app.columnPointer].Focus() + break + } + } + } + + case tea.KeyMsg: + switch { + case key.Matches(message, keys.CtrlC): + app.quit = true + return app, tea.Quit + case key.Matches(message, keys.Escape): + if app.columnViews[app.columnPointer].viewType == listViewType || app.columnViews[app.columnPointer].viewType == listViewType2 { + res, cmd := app.columnViews[app.columnPointer].Update(msg) + app.columnViews[app.columnPointer] = *res.(*columnView) + if cmd == nil { + return app, nil + } else { + app.quit = true + return app, tea.Quit + } + } + app.quit = true + return app, tea.Quit + case key.Matches(message, keys.Execute): + app.execute() + return app, tea.Quit + case key.Matches(message, keys.Up): + app.columnViews[app.columnPointer].CursorUp() + app.updateWorkflowFilesAndWorkflowsViews() + return app, nil + case key.Matches(message, keys.Down): + app.columnViews[app.columnPointer].CursorDown() + app.updateWorkflowFilesAndWorkflowsViews() + return app, nil + case key.Matches(message, keys.Left): + app.columnViews[app.columnPointer].Blur() + app.columnPointer = app.getPrevViewPointer() + app.columnViews[app.columnPointer].Focus() + return app, nil + case key.Matches(message, keys.Right): + app.columnViews[app.columnPointer].Blur() + app.columnPointer = app.getNextViewPointer() + app.columnViews[app.columnPointer].Focus() + return app, nil + case key.Matches(message, keys.FlipWorkflowStepsView): + app.flipWorkflowStepsView() + return app, nil + } + } + + // Send all other messages to the selected child view + res, cmd := app.columnViews[app.columnPointer].Update(msg) + app.columnViews[app.columnPointer] = *res.(*columnView) + return app, cmd +} + +func (app *App) View() string { + if app.quit { + return "" + } + + if !app.loaded { + return "loading..." + } + + layout := lipgloss.JoinHorizontal( + lipgloss.Left, + app.columnViews[0].View(), + app.columnViews[1].View(), + app.columnViews[2].View(), + ) + + return mouseZone.Scan(lipgloss.JoinVertical(lipgloss.Left, layout, app.help.View(keys))) +} + +func (app *App) GetSelectedWorkflowFile() string { + return app.selectedWorkflowFile +} + +func (app *App) GetSelectedWorkflow() string { + return app.selectedWorkflow +} + +func (app *App) GetSelectedWorkflowStep() string { + return app.selectedWorkflowStep +} + +func (app *App) ExitStatusQuit() bool { + return app.quit +} + +func (app *App) getNextViewPointer() int { + if app.columnPointer == 2 { + return 0 + } + return app.columnPointer + 1 +} + +func (app *App) getPrevViewPointer() int { + if app.columnPointer == 0 { + return 2 + } + return app.columnPointer - 1 +} From 73f5a29b48dedab4bc73ef16a2951eb6dbfd44a6 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Mon, 16 Dec 2024 00:02:23 +0000 Subject: [PATCH 08/49] workflow implementation msgs --- cmd/workflow.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index 7061c09ff..ba8bab5bb 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -50,10 +50,10 @@ var workflowCmd = &cobra.Command{ func init() { workflowCmd.DisableFlagParsing = false - workflowCmd.PersistentFlags().StringP("file", "f", "", "atmos workflow --file ") - workflowCmd.PersistentFlags().Bool("dry-run", false, "atmos workflow --file --dry-run") - workflowCmd.PersistentFlags().StringP("stack", "s", "", "atmos workflow --file --stack ") - workflowCmd.PersistentFlags().String("from-step", "", "atmos workflow --file --from-step ") + workflowCmd.PersistentFlags().StringP("file", "f", "", "atmos workflow -f ") + workflowCmd.PersistentFlags().Bool("dry-run", false, "atmos workflow -f --dry-run") + workflowCmd.PersistentFlags().StringP("stack", "s", "", "atmos workflow -f -s ") + workflowCmd.PersistentFlags().String("from-step", "", "atmos workflow -f -from-step ") RootCmd.AddCommand(workflowCmd) } From 5dd2350267aa83d56a9f1165062d98f70eb67f44 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Mon, 16 Dec 2024 00:03:45 +0000 Subject: [PATCH 09/49] workflow implementation msgs --- cmd/workflow.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index ba8bab5bb..a8d98cf63 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -17,11 +17,11 @@ var workflowCmd = &cobra.Command{ Short: "Execute a workflow", Long: `This command executes a workflow: atmos workflow --file `, Example: "atmos workflow\n" + - "atmos workflow --file \n" + - "atmos workflow --file --stack \n" + - "atmos workflow --file --from-step \n\n" + + "atmos workflow -f \n" + + "atmos workflow -f -s \n" + + "atmos workflow -f -from-step \n\n" + "To resume the workflow from this step, run:\n" + - "atmos workflow deploy-infra --file workflow1 --from-step deploy-vpc\n\n" + + "atmos workflow deploy-infra -f workflow1 -from-step deploy-vpc\n\n" + "For more details refer to https://atmos.tools/cli/commands/workflow/", FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false}, Run: func(cmd *cobra.Command, args []string) { From c47c9a141fa675530fcfe9fd31b095b2b24637f1 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Thu, 19 Dec 2024 21:31:48 +0000 Subject: [PATCH 10/49] markdown start point implementation for workflow --- cmd/workflow.go | 127 ++++++-- .../infra/account-map/component.yaml | 40 --- .../infra/vpc-flow-logs-bucket/component.yaml | 52 ---- .../infra/vpc-flow-logs-bucket/context.tf | 279 ------------------ .../infra/vpc-flow-logs-bucket/main.tf | 18 -- .../infra/vpc-flow-logs-bucket/outputs.tf | 9 - .../infra/vpc-flow-logs-bucket/providers.tf | 19 -- .../infra/vpc-flow-logs-bucket/variables.tf | 64 ---- .../infra/vpc-flow-logs-bucket/versions.tf | 10 - .../terraform/infra/vpc/component.yaml | 36 --- .../components/terraform/infra/vpc/context.tf | 279 ------------------ .../components/terraform/infra/vpc/main.tf | 173 ----------- .../components/terraform/infra/vpc/outputs.tf | 109 ------- .../terraform/infra/vpc/providers.tf | 3 - .../terraform/infra/vpc/remote-state.tf | 20 -- .../terraform/infra/vpc/variables.tf | 165 ----------- .../terraform/infra/vpc/versions.tf | 14 - .../terraform/infra/vpc/vpc-flow-logs.tf | 15 - .../terraform/infra/vpc2/component.yaml | 26 -- .../components/terraform/mixins/context.tf | 279 ------------------ .../terraform/mixins/introspection.mixin.tf | 34 --- .../test/template-functions-test/context.tf | 279 ------------------ .../test/template-functions-test/main.tf | 6 - .../test/template-functions-test/outputs.tf | 22 -- .../test/template-functions-test2/context.tf | 279 ------------------ .../test/template-functions-test2/outputs.tf | 14 - .../template-functions-test2/variables.tf | 14 - .../terraform/test/test-component/context.tf | 279 ------------------ .../terraform/test/test-component/main.tf | 17 -- .../terraform/test/test-component/outputs.tf | 9 - .../test/test-component/variables.tf | 38 --- .../terraform/test/test-component/versions.tf | 3 - .../test/test2/test-component-2/context.tf | 279 ------------------ .../test/test2/test-component-2/main.tf | 17 -- .../test/test2/test-component-2/outputs.tf | 9 - .../test/test2/test-component-2/variables.tf | 38 --- .../test/test2/test-component-2/versions.tf | 3 - .../terraform/top-level-component1/context.tf | 279 ------------------ .../terraform/top-level-component1/main.tf | 16 - .../terraform/top-level-component1/outputs.tf | 9 - .../policies/policy1.rego | 5 - .../top-level-component1/variables.tf | 38 --- .../top-level-component1/versions.tf | 3 - go.mod | 4 +- 44 files changed, 109 insertions(+), 3322 deletions(-) delete mode 100644 examples/tests/components/terraform/infra/account-map/component.yaml delete mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/component.yaml delete mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/context.tf delete mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/main.tf delete mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/outputs.tf delete mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/providers.tf delete mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/variables.tf delete mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/versions.tf delete mode 100644 examples/tests/components/terraform/infra/vpc/component.yaml delete mode 100644 examples/tests/components/terraform/infra/vpc/context.tf delete mode 100644 examples/tests/components/terraform/infra/vpc/main.tf delete mode 100644 examples/tests/components/terraform/infra/vpc/outputs.tf delete mode 100644 examples/tests/components/terraform/infra/vpc/providers.tf delete mode 100644 examples/tests/components/terraform/infra/vpc/remote-state.tf delete mode 100644 examples/tests/components/terraform/infra/vpc/variables.tf delete mode 100644 examples/tests/components/terraform/infra/vpc/versions.tf delete mode 100644 examples/tests/components/terraform/infra/vpc/vpc-flow-logs.tf delete mode 100644 examples/tests/components/terraform/infra/vpc2/component.yaml delete mode 100644 examples/tests/components/terraform/mixins/context.tf delete mode 100644 examples/tests/components/terraform/mixins/introspection.mixin.tf delete mode 100644 examples/tests/components/terraform/test/template-functions-test/context.tf delete mode 100644 examples/tests/components/terraform/test/template-functions-test/main.tf delete mode 100644 examples/tests/components/terraform/test/template-functions-test/outputs.tf delete mode 100644 examples/tests/components/terraform/test/template-functions-test2/context.tf delete mode 100644 examples/tests/components/terraform/test/template-functions-test2/outputs.tf delete mode 100644 examples/tests/components/terraform/test/template-functions-test2/variables.tf delete mode 100644 examples/tests/components/terraform/test/test-component/context.tf delete mode 100644 examples/tests/components/terraform/test/test-component/main.tf delete mode 100644 examples/tests/components/terraform/test/test-component/outputs.tf delete mode 100644 examples/tests/components/terraform/test/test-component/variables.tf delete mode 100644 examples/tests/components/terraform/test/test-component/versions.tf delete mode 100644 examples/tests/components/terraform/test/test2/test-component-2/context.tf delete mode 100644 examples/tests/components/terraform/test/test2/test-component-2/main.tf delete mode 100644 examples/tests/components/terraform/test/test2/test-component-2/outputs.tf delete mode 100644 examples/tests/components/terraform/test/test2/test-component-2/variables.tf delete mode 100644 examples/tests/components/terraform/test/test2/test-component-2/versions.tf delete mode 100644 examples/tests/components/terraform/top-level-component1/context.tf delete mode 100644 examples/tests/components/terraform/top-level-component1/main.tf delete mode 100644 examples/tests/components/terraform/top-level-component1/outputs.tf delete mode 100644 examples/tests/components/terraform/top-level-component1/policies/policy1.rego delete mode 100644 examples/tests/components/terraform/top-level-component1/variables.tf delete mode 100644 examples/tests/components/terraform/top-level-component1/versions.tf diff --git a/cmd/workflow.go b/cmd/workflow.go index a8d98cf63..2ef42d847 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -2,58 +2,145 @@ package cmd import ( "fmt" + "strings" "github.com/spf13/cobra" e "github.com/cloudposse/atmos/internal/exec" - "github.com/cloudposse/atmos/internal/tui/utils" "github.com/cloudposse/atmos/pkg/schema" + "github.com/cloudposse/atmos/pkg/ui/markdown" u "github.com/cloudposse/atmos/pkg/utils" ) +// ErrorMessage represents a structured error message +type ErrorMessage struct { + Title string + Details string + Suggestion string +} + +// String returns the markdown representation of the error message +func (e ErrorMessage) String() string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("# ❌ %s\n\n", e.Title)) + + if e.Details != "" { + sb.WriteString(fmt.Sprintf("## Details\n%s\n\n", e.Details)) + } + + if e.Suggestion != "" { + sb.WriteString(fmt.Sprintf("## Suggestion\n%s\n\n", e.Suggestion)) + } + + return sb.String() +} + +// renderError renders an error message using the markdown renderer +func renderError(msg ErrorMessage) error { + renderer, err := markdown.NewRenderer( + markdown.WithWidth(80), + ) + if err != nil { + return fmt.Errorf("failed to create markdown renderer: %w", err) + } + + rendered, err := renderer.Render(msg.String()) + if err != nil { + return fmt.Errorf("failed to render error message: %w", err) + } + + fmt.Print(rendered) + return nil +} + // workflowCmd executes a workflow var workflowCmd = &cobra.Command{ Use: "workflow", Short: "Execute a workflow", Long: `This command executes a workflow: atmos workflow --file `, Example: "atmos workflow\n" + - "atmos workflow -f \n" + - "atmos workflow -f -s \n" + - "atmos workflow -f -from-step \n\n" + + "atmos workflow --file \n" + + "atmos workflow --file --stack \n" + + "atmos workflow --file --from-step \n\n" + "To resume the workflow from this step, run:\n" + - "atmos workflow deploy-infra -f workflow1 -from-step deploy-vpc\n\n" + + "atmos workflow deploy-infra --file workflow1 --from-step deploy-vpc\n\n" + "For more details refer to https://atmos.tools/cli/commands/workflow/", FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false}, Run: func(cmd *cobra.Command, args []string) { - // If the first arg is "list", show a clear error message - if len(args) > 0 && args[0] == "list" { - errorMsg := "# Error! Invalid command.\n\n" + - "## Usage\n" + - "`atmos workflow --file `\n\n" + - "Run `atmos workflow --help` for more information." - - rendered, err := utils.RenderMarkdown(errorMsg, "dark") + // If no arguments are provided, start the workflow UI + if len(args) == 0 { + err := e.ExecuteWorkflowCmd(cmd, args) if err != nil { - fmt.Println(errorMsg) // Fallback to plain text if rendering fails - } else { - fmt.Print(rendered) + u.LogErrorAndExit(schema.AtmosConfiguration{}, err) } return } + // Check if the workflow name is "list" (invalid command) + if args[0] == "list" { + err := renderError(ErrorMessage{ + Title: "Invalid Command", + Details: "The command `atmos workflow list` is not valid.", + Suggestion: "Use `atmos workflow --file ` to execute a workflow, or run `atmos workflow` without arguments to use the interactive UI.", + }) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, err) + } + return + } + + // Get the --file flag value + workflowFile, _ := cmd.Flags().GetString("file") + if workflowFile == "" { + err := renderError(ErrorMessage{ + Title: "Missing Required Flag", + Details: "The `--file` flag is required to specify a workflow manifest.", + Suggestion: "Example:\n`atmos workflow deploy-infra --file workflow1`", + }) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, err) + } + return + } + + // Execute the workflow command err := e.ExecuteWorkflowCmd(cmd, args) if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) + // Format common error messages + if strings.Contains(err.Error(), "does not exist") { + err := renderError(ErrorMessage{ + Title: "Workflow File Not Found", + Details: fmt.Sprintf("The workflow manifest file '%s' could not be found.", workflowFile), + Suggestion: "Check if the file exists in the workflows directory and the path is correct.", + }) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, err) + } + } else if strings.Contains(err.Error(), "does not have the") { + err := renderError(ErrorMessage{ + Title: "Invalid Workflow", + Details: err.Error(), + Suggestion: fmt.Sprintf("Check the available workflows in '%s' and make sure you're using the correct workflow name.", workflowFile), + }) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, err) + } + } else { + // For other errors, use the standard error handler + u.LogErrorAndExit(schema.AtmosConfiguration{}, err) + } + return } }, } func init() { workflowCmd.DisableFlagParsing = false - workflowCmd.PersistentFlags().StringP("file", "f", "", "atmos workflow -f ") - workflowCmd.PersistentFlags().Bool("dry-run", false, "atmos workflow -f --dry-run") - workflowCmd.PersistentFlags().StringP("stack", "s", "", "atmos workflow -f -s ") - workflowCmd.PersistentFlags().String("from-step", "", "atmos workflow -f -from-step ") + workflowCmd.PersistentFlags().StringP("file", "f", "", "atmos workflow --file ") + workflowCmd.PersistentFlags().Bool("dry-run", false, "atmos workflow --file --dry-run") + workflowCmd.PersistentFlags().StringP("stack", "s", "", "atmos workflow --file --stack ") + workflowCmd.PersistentFlags().String("from-step", "", "atmos workflow --file --from-step ") RootCmd.AddCommand(workflowCmd) } diff --git a/examples/tests/components/terraform/infra/account-map/component.yaml b/examples/tests/components/terraform/infra/account-map/component.yaml deleted file mode 100644 index 1240d1d0d..000000000 --- a/examples/tests/components/terraform/infra/account-map/component.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# 'infra/account-map' component vendoring config - -apiVersion: atmos/v1 -kind: ComponentVendorConfig -metadata: - name: account-map-vendor-config - description: Source and mixins config for vendoring of 'vpc-flow-logs-bucket' component -spec: - source: - # Source 'uri' supports the following protocols: Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, - # and all URL and archive formats as described in https://github.com/hashicorp/go-getter - # In 'uri', Golang templates are supported https://pkg.go.dev/text/template - # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'uri' - uri: github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref={{.Version}} - version: 1.372.0 - # Only include the files that match the 'included_paths' patterns - # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths' - # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) - # https://en.wikipedia.org/wiki/Glob_(programming) - # https://github.com/bmatcuk/doublestar#patterns - included_paths: - # include '.tf', '.tfvars' and '.md' files from the root folder - - "**/*.tf" - - "**/*.tfvars" - - "**/*.md" - # include the 'modules' folder and all sub-folders - # note that if you don't include the folders, the files in the folders will not be included - - "**/modules/**" - # include '.tf', '.tfvars' and '.md' files from the 'modules' folder and all sub-folders - - "**/modules/**/*.tf" - - "**/modules/**/*.tfvars" - - "**/modules/**/*.md" - # Exclude the files that match any of the 'excluded_paths' patterns - # Note that we are excluding 'context.tf' since a newer version of it will be downloaded using 'mixins' - # 'excluded_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) - excluded_paths: [] - - # mixins override files from 'source' with the same 'filename' (e.g. 'context.tf' will override 'context.tf' from the 'source') - # mixins are processed in the order they are declared in the list - mixins: [] diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/component.yaml b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/component.yaml deleted file mode 100644 index 72a7df76e..000000000 --- a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/component.yaml +++ /dev/null @@ -1,52 +0,0 @@ -# 'infra/vpc-flow-logs-bucket' component vendoring config - -# 'component.yaml' in the component folder is processed by the 'atmos' commands -# 'atmos vendor pull -c infra/vpc-flow-logs-bucket' or 'atmos vendor pull --component infra/vpc-flow-logs-bucket' - -# > atmos vendor pull -c infra/vpc-flow-logs-bucket -# Pulling sources for the component 'infra/vpc-flow-logs-bucket' from 'github.com/cloudposse/terraform-aws-components.git//modules/vpc-flow-logs-bucket?ref=0.194.0' -# into 'examples/tests/components/terraform/infra/vpc-flow-logs-bucket' -# -# Including the file 'README.md' since it matches the '**/*.md' pattern from 'included_paths' -# Excluding the file 'context.tf' since it matches the '**/context.tf' pattern from 'excluded_paths' -# Including the file 'default.auto.tfvars' since it matches the '**/*.tfvars' pattern from 'included_paths' -# Including the file 'main.tf' since it matches the '**/*.tf' pattern from 'included_paths' -# Including the file 'outputs.tf' since it matches the '**/*.tf' pattern from 'included_paths' -# Including the file 'providers.tf' since it matches the '**/*.tf' pattern from 'included_paths' -# Including the file 'variables.tf' since it matches the '**/*.tf' pattern from 'included_paths' -# Including the file 'versions.tf' since it matches the '**/*.tf' pattern from 'included_paths' -# -# Pulling the mixin 'https://raw.githubusercontent.com/cloudposse/terraform-null-label/0.25.0/exports/context.tf' -# for the component 'infra/vpc-flow-logs-bucket' into 'examples/tests/components/terraform/infra/vpc-flow-logs-bucket' -# Pulling the mixin 'https://raw.githubusercontent.com/cloudposse/terraform-aws-components/0.194.0/modules/datadog-agent/introspection.mixin.tf' -# for the component 'infra/vpc-flow-logs-bucket' into 'examples/tests/components/terraform/infra/vpc-flow-logs-bucket' - -apiVersion: atmos/v1 -kind: ComponentVendorConfig -metadata: - name: vpc-flow-logs-bucket-vendor-config - description: Source and mixins config for vendoring of 'vpc-flow-logs-bucket' component -spec: - source: - # Source 'uri' supports the following protocols: Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, - # and all URL and archive formats as described in https://github.com/hashicorp/go-getter - # In 'uri', Golang templates are supported https://pkg.go.dev/text/template - # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'uri' - uri: github.com/cloudposse/terraform-aws-components.git//modules/vpc-flow-logs-bucket?ref={{.Version}} - version: 1.372.0 - # Only include the files that match the 'included_paths' patterns - # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths' - # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) - # https://en.wikipedia.org/wiki/Glob_(programming) - # https://github.com/bmatcuk/doublestar#patterns - included_paths: - - "**/*.tf" - # Exclude the files that match any of the 'excluded_paths' patterns - # Note that we are excluding 'context.tf' since a newer version of it will be downloaded using 'mixins' - # 'excluded_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) - excluded_paths: - - "**/context.tf" - - # mixins override files from 'source' with the same 'filename' (e.g. 'context.tf' will override 'context.tf' from the 'source') - # mixins are processed in the order they are declared in the list - mixins: [] diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/context.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/context.tf deleted file mode 100644 index 5e0ef8856..000000000 --- a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/main.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/main.tf deleted file mode 100644 index 88eaa98fe..000000000 --- a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/main.tf +++ /dev/null @@ -1,18 +0,0 @@ -module "flow_logs_s3_bucket" { - source = "cloudposse/vpc-flow-logs-s3-bucket/aws" - version = "1.0.1" - - lifecycle_prefix = var.lifecycle_prefix - lifecycle_tags = var.lifecycle_tags - lifecycle_rule_enabled = var.lifecycle_rule_enabled - noncurrent_version_expiration_days = var.noncurrent_version_expiration_days - noncurrent_version_transition_days = var.noncurrent_version_transition_days - standard_transition_days = var.standard_transition_days - glacier_transition_days = var.glacier_transition_days - expiration_days = var.expiration_days - traffic_type = var.traffic_type - force_destroy = var.force_destroy - flow_log_enabled = false - - context = module.this.context -} diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/outputs.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/outputs.tf deleted file mode 100644 index f195f3f81..000000000 --- a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -output "vpc_flow_logs_bucket_id" { - value = module.flow_logs_s3_bucket.bucket_id - description = "VPC Flow Logs bucket ID" -} - -output "vpc_flow_logs_bucket_arn" { - value = module.flow_logs_s3_bucket.bucket_arn - description = "VPC Flow Logs bucket ARN" -} diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/providers.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/providers.tf deleted file mode 100644 index ef923e10a..000000000 --- a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/providers.tf +++ /dev/null @@ -1,19 +0,0 @@ -provider "aws" { - region = var.region - - # Profile is deprecated in favor of terraform_role_arn. When profiles are not in use, terraform_profile_name is null. - profile = module.iam_roles.terraform_profile_name - - dynamic "assume_role" { - # module.iam_roles.terraform_role_arn may be null, in which case do not assume a role. - for_each = compact([module.iam_roles.terraform_role_arn]) - content { - role_arn = assume_role.value - } - } -} - -module "iam_roles" { - source = "../account-map/modules/iam-roles" - context = module.this.context -} diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/variables.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/variables.tf deleted file mode 100644 index 6b87353ed..000000000 --- a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/variables.tf +++ /dev/null @@ -1,64 +0,0 @@ -variable "region" { - type = string - description = "AWS Region" -} - -variable "lifecycle_prefix" { - type = string - description = "Prefix filter. Used to manage object lifecycle events" - default = "" -} - -variable "lifecycle_tags" { - type = map(string) - description = "Tags filter. Used to manage object lifecycle events" - default = {} -} - -variable "force_destroy" { - type = bool - description = "A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable" - default = false -} - -variable "lifecycle_rule_enabled" { - type = bool - description = "Enable lifecycle events on this bucket" - default = true -} - -variable "noncurrent_version_expiration_days" { - type = number - description = "Specifies when noncurrent object versions expire" - default = 90 -} - -variable "noncurrent_version_transition_days" { - type = number - description = "Specifies when noncurrent object versions transitions" - default = 30 -} - -variable "standard_transition_days" { - type = number - description = "Number of days to persist in the standard storage tier before moving to the infrequent access tier" - default = 30 -} - -variable "glacier_transition_days" { - type = number - description = "Number of days after which to move the data to the glacier storage tier" - default = 60 -} - -variable "expiration_days" { - type = number - description = "Number of days after which to expunge the objects" - default = 90 -} - -variable "traffic_type" { - type = string - description = "The type of traffic to capture. Valid values: `ACCEPT`, `REJECT`, `ALL`" - default = "ALL" -} diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/versions.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/versions.tf deleted file mode 100644 index cc73ffd35..000000000 --- a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/versions.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_version = ">= 1.0.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.9.0" - } - } -} diff --git a/examples/tests/components/terraform/infra/vpc/component.yaml b/examples/tests/components/terraform/infra/vpc/component.yaml deleted file mode 100644 index 498f75293..000000000 --- a/examples/tests/components/terraform/infra/vpc/component.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# 'infra/vpc' component vendoring config - -# 'component.yaml' in the component folder is processed by the 'atmos' commands -# 'atmos vendor pull -c infra/vpc' or 'atmos vendor pull --component infra/vpc' - -apiVersion: atmos/v1 -kind: ComponentVendorConfig -metadata: - name: vpc-vendor-config - description: Source and mixins config for vendoring of 'vpc' component -spec: - source: - # Source 'uri' supports the following protocols: Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, - # and all URL and archive formats as described in https://github.com/hashicorp/go-getter - # In 'uri', Golang templates are supported https://pkg.go.dev/text/template - # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'uri' - uri: github.com/cloudposse/terraform-aws-components.git//modules/vpc?ref={{.Version}} - version: 1.372.0 - # Only include the files that match the 'included_paths' patterns - # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths' - # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) - # https://en.wikipedia.org/wiki/Glob_(programming) - # https://github.com/bmatcuk/doublestar#patterns - included_paths: - - "**/*.tf" - - "**/*.tfvars" - - # mixins override files from 'source' with the same 'filename' (e.g. 'context.tf' will override 'context.tf' from the 'source') - # mixins are processed in the order they are declared in the list - mixins: - # https://github.com/hashicorp/go-getter/issues/98 - # Mixins 'uri' supports the following protocols: local files (absolute and relative paths), Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP - # - uri: https://raw.githubusercontent.com/cloudposse/terraform-null-label/0.25.0/exports/context.tf - # This mixin `uri` is relative to the current `vpc` folder - - uri: ../../mixins/context.tf - filename: context.tf diff --git a/examples/tests/components/terraform/infra/vpc/context.tf b/examples/tests/components/terraform/infra/vpc/context.tf deleted file mode 100644 index 5e0ef8856..000000000 --- a/examples/tests/components/terraform/infra/vpc/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/infra/vpc/main.tf b/examples/tests/components/terraform/infra/vpc/main.tf deleted file mode 100644 index dde5622f2..000000000 --- a/examples/tests/components/terraform/infra/vpc/main.tf +++ /dev/null @@ -1,173 +0,0 @@ -locals { - enabled = module.this.enabled - - nat_eip_aws_shield_protection_enabled = local.enabled && var.nat_eip_aws_shield_protection_enabled - vpc_flow_logs_enabled = local.enabled && var.vpc_flow_logs_enabled - - # The usage of specific kubernetes.io/cluster/* resource tags were required before Kubernetes 1.19, - # but are now deprecated. See https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html - - max_subnet_count = ( - var.max_subnet_count > 0 ? var.max_subnet_count : ( - length(var.availability_zone_ids) > 0 ? length(var.availability_zone_ids) : length(var.availability_zones) - ) - ) - - availability_zones = length(var.availability_zones) > 0 ? ( - (substr( - var.availability_zones[0], - 0, - length(var.region) - ) == var.region) ? var.availability_zones : formatlist("${var.region}%s", var.availability_zones) - ) : var.availability_zones - - short_region = module.utils.region_az_alt_code_maps["to_short"][var.region] - - availability_zone_ids = length(var.availability_zone_ids) > 0 ? ( - (substr( - var.availability_zone_ids[0], - 0, - length(local.short_region) - ) == local.short_region) ? var.availability_zone_ids : formatlist("${local.short_region}%s", var.availability_zone_ids) - ) : var.availability_zone_ids - - # required tags to make ALB ingress work https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html - # https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html - public_subnets_additional_tags = { - (var.subnet_type_tag_key) = "public", - "kubernetes.io/role/elb" = 1, - } - - private_subnets_additional_tags = { - (var.subnet_type_tag_key) = "private", - "kubernetes.io/role/internal-elb" = 1, - } - - gateway_endpoint_map = { for v in var.gateway_vpc_endpoints : v => { - name = v - policy = null - route_table_ids = module.subnets.private_route_table_ids - } } - - # If we use a separate security group for each endpoint interface, - # we will use the interface service name as the key: security_group_ids = [module.endpoint_security_groups[v].id] - # If we use a single security group for all endpoint interfaces, - # we will use local.interface_endpoint_security_group_key as the key. - interface_endpoint_security_group_key = "VPC Endpoint interfaces" - - interface_endpoint_map = { for v in var.interface_vpc_endpoints : v => { - name = v - policy = null - private_dns_enabled = true # Allow applications to use normal service DNS names to access the service - security_group_ids = [module.endpoint_security_groups[local.interface_endpoint_security_group_key].id] - subnet_ids = module.subnets.private_subnet_ids - } } -} - -module "utils" { - source = "cloudposse/utils/aws" - version = "1.4.0" -} - -module "vpc" { - source = "cloudposse/vpc/aws" - version = "2.1.1" - - ipv4_primary_cidr_block = var.ipv4_primary_cidr_block - internet_gateway_enabled = var.public_subnets_enabled - assign_generated_ipv6_cidr_block = var.assign_generated_ipv6_cidr_block - - # Required for DNS resolution of VPC Endpoint interfaces, and generally harmless - # See https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html#vpc-dns-support - dns_hostnames_enabled = true - dns_support_enabled = true - - context = module.this.context -} - -# We could create a security group per endpoint, -# but until we are ready to customize them by service, it is just a waste -# of resources. We use a single security group for all endpoints. -# Security groups can be updated without recreating the endpoint or -# interrupting service, so this is an easy change to make later. -module "endpoint_security_groups" { - for_each = local.enabled && try(length(var.interface_vpc_endpoints), 0) > 0 ? toset([local.interface_endpoint_security_group_key]) : [] - - source = "cloudposse/security-group/aws" - version = "2.2.0" - - create_before_destroy = true - preserve_security_group_id = false - attributes = [each.value] - vpc_id = module.vpc.vpc_id - - rules_map = { - ingress = [{ - key = "vpc_ingress" - type = "ingress" - from_port = 0 - to_port = 65535 - protocol = "-1" # allow ping - cidr_blocks = compact(concat([module.vpc.vpc_cidr_block], module.vpc.additional_cidr_blocks)) - ipv6_cidr_blocks = compact(concat([module.vpc.vpc_ipv6_cidr_block], module.vpc.additional_ipv6_cidr_blocks)) - description = "Ingress from VPC to ${each.value}" - }] - } - - allow_all_egress = true - - context = module.this.context -} - -module "vpc_endpoints" { - source = "cloudposse/vpc/aws//modules/vpc-endpoints" - version = "2.1.0" - - enabled = (length(var.interface_vpc_endpoints) + length(var.gateway_vpc_endpoints)) > 0 - - vpc_id = module.vpc.vpc_id - gateway_vpc_endpoints = local.gateway_endpoint_map - interface_vpc_endpoints = local.interface_endpoint_map - - context = module.this.context -} - -module "subnets" { - source = "cloudposse/dynamic-subnets/aws" - version = "2.4.1" - - availability_zones = local.availability_zones - availability_zone_ids = local.availability_zone_ids - ipv4_cidr_block = [module.vpc.vpc_cidr_block] - ipv4_cidrs = var.ipv4_cidrs - ipv6_enabled = false - igw_id = var.public_subnets_enabled ? [module.vpc.igw_id] : [] - map_public_ip_on_launch = var.map_public_ip_on_launch - max_subnet_count = local.max_subnet_count - nat_gateway_enabled = var.nat_gateway_enabled - nat_instance_enabled = var.nat_instance_enabled - nat_instance_type = var.nat_instance_type - public_subnets_enabled = var.public_subnets_enabled - public_subnets_additional_tags = local.public_subnets_additional_tags - private_subnets_additional_tags = local.private_subnets_additional_tags - vpc_id = module.vpc.vpc_id - - context = module.this.context -} - -data "aws_caller_identity" "current" { - count = local.nat_eip_aws_shield_protection_enabled ? 1 : 0 -} - -data "aws_eip" "eip" { - for_each = local.nat_eip_aws_shield_protection_enabled ? toset(module.subnets.nat_ips) : [] - - public_ip = each.key -} - -resource "aws_shield_protection" "nat_eip_shield_protection" { - for_each = local.nat_eip_aws_shield_protection_enabled ? data.aws_eip.eip : {} - - name = data.aws_eip.eip[each.key].id - resource_arn = "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current[0].account_id}:eip-allocation/${data.aws_eip.eip[each.key].id}" -} diff --git a/examples/tests/components/terraform/infra/vpc/outputs.tf b/examples/tests/components/terraform/infra/vpc/outputs.tf deleted file mode 100644 index 3a98630a9..000000000 --- a/examples/tests/components/terraform/infra/vpc/outputs.tf +++ /dev/null @@ -1,109 +0,0 @@ -output "public_subnet_ids" { - value = module.subnets.public_subnet_ids - description = "Public subnet IDs" -} - -output "public_subnet_cidrs" { - value = module.subnets.public_subnet_cidrs - description = "Public subnet CIDRs" -} - -output "private_subnet_ids" { - value = module.subnets.private_subnet_ids - description = "Private subnet IDs" -} - -output "private_subnet_cidrs" { - value = module.subnets.private_subnet_cidrs - description = "Private subnet CIDRs" -} - -output "subnets" { - value = { - public : { - ids : module.subnets.public_subnet_ids - cidr : module.subnets.public_subnet_cidrs - } - private : { - ids : module.subnets.private_subnet_ids - cidr : module.subnets.private_subnet_cidrs - } - } - description = "Subnets info map" -} - -output "vpc_default_network_acl_id" { - value = module.vpc.vpc_default_network_acl_id - description = "The ID of the network ACL created by default on VPC creation" -} - -output "vpc_default_security_group_id" { - value = module.vpc.vpc_default_security_group_id - description = "The ID of the security group created by default on VPC creation" -} - -output "vpc_id" { - value = module.vpc.vpc_id - description = "VPC ID" -} - -output "vpc_cidr" { - value = module.vpc.vpc_cidr_block - description = "VPC CIDR" -} - -output "vpc" { - value = { - id : module.vpc.vpc_id - cidr : module.vpc.vpc_cidr_block - subnet_type_tag_key : var.subnet_type_tag_key - } - description = "VPC info map" -} - -output "private_route_table_ids" { - value = module.subnets.private_route_table_ids - description = "Private subnet route table IDs" -} - -output "public_route_table_ids" { - value = module.subnets.public_route_table_ids - description = "Public subnet route table IDs" -} - -output "route_tables" { - value = { - public : { - ids : module.subnets.public_route_table_ids - } - private : { - ids : module.subnets.private_route_table_ids - } - } - description = "Route tables info map" -} - -output "nat_gateway_ids" { - value = module.subnets.nat_gateway_ids - description = "NAT Gateway IDs" -} - -output "nat_instance_ids" { - value = module.subnets.nat_instance_ids - description = "NAT Instance IDs" -} - -output "nat_gateway_public_ips" { - value = module.subnets.nat_gateway_public_ips - description = "NAT Gateway public IPs" -} - -output "max_subnet_count" { - value = local.max_subnet_count - description = "Maximum allowed number of subnets before all subnet CIDRs need to be recomputed" -} - -output "availability_zones" { - description = "List of Availability Zones where subnets were created" - value = local.availability_zones -} diff --git a/examples/tests/components/terraform/infra/vpc/providers.tf b/examples/tests/components/terraform/infra/vpc/providers.tf deleted file mode 100644 index dc58d9a25..000000000 --- a/examples/tests/components/terraform/infra/vpc/providers.tf +++ /dev/null @@ -1,3 +0,0 @@ -provider "aws" { - region = var.region -} diff --git a/examples/tests/components/terraform/infra/vpc/remote-state.tf b/examples/tests/components/terraform/infra/vpc/remote-state.tf deleted file mode 100644 index e785ec5b1..000000000 --- a/examples/tests/components/terraform/infra/vpc/remote-state.tf +++ /dev/null @@ -1,20 +0,0 @@ -module "vpc_flow_logs_bucket" { - count = var.vpc_flow_logs_enabled ? 1 : 0 - - source = "cloudposse/stack-config/yaml//modules/remote-state" - version = "1.5.0" - - # Specify the Atmos component name (defined in YAML stack config files) - # for which to get the remote state outputs - component = var.vpc_flow_logs_bucket_component_name - - # Override the context variables to point to a different Atmos stack if the - # `vpc-flow-logs-bucket` Atmos component is provisioned in another AWS account, OU or region - stage = try(coalesce(var.vpc_flow_logs_bucket_stage_name, module.this.stage), null) - environment = try(coalesce(var.vpc_flow_logs_bucket_environment_name, module.this.environment), null) - tenant = try(coalesce(var.vpc_flow_logs_bucket_tenant_name, module.this.tenant), null) - - # `context` input is a way to provide the information about the stack (using the context - # variables `namespace`, `tenant`, `environment`, and `stage` defined in the stack config) - context = module.this.context -} diff --git a/examples/tests/components/terraform/infra/vpc/variables.tf b/examples/tests/components/terraform/infra/vpc/variables.tf deleted file mode 100644 index ff1376f6c..000000000 --- a/examples/tests/components/terraform/infra/vpc/variables.tf +++ /dev/null @@ -1,165 +0,0 @@ -variable "region" { - type = string - description = "AWS Region" -} - -variable "availability_zones" { - type = list(string) - description = <<-EOT - List of Availability Zones (AZs) where subnets will be created. Ignored when `availability_zone_ids` is set. - The order of zones in the list ***must be stable*** or else Terraform will continually make changes. - If no AZs are specified, then `max_subnet_count` AZs will be selected in alphabetical order. - If `max_subnet_count > 0` and `length(var.availability_zones) > max_subnet_count`, the list - will be truncated. We recommend setting `availability_zones` and `max_subnet_count` explicitly as constant - (not computed) values for predictability, consistency, and stability. - EOT - default = [] -} - -variable "availability_zone_ids" { - type = list(string) - description = <<-EOT - List of Availability Zones IDs where subnets will be created. Overrides `availability_zones`. - Useful in some regions when using only some AZs and you want to use the same ones across multiple accounts. - EOT - default = [] -} - -variable "ipv4_primary_cidr_block" { - type = string - description = <<-EOT - The primary IPv4 CIDR block for the VPC. - Either `ipv4_primary_cidr_block` or `ipv4_primary_cidr_block_association` must be set, but not both. - EOT - default = null -} - -variable "ipv4_cidrs" { - type = list(object({ - private = list(string) - public = list(string) - })) - description = <<-EOT - Lists of CIDRs to assign to subnets. Order of CIDRs in the lists must not change over time. - Lists may contain more CIDRs than needed. - EOT - default = [] - validation { - condition = length(var.ipv4_cidrs) < 2 - error_message = "Only 1 ipv4_cidrs object can be provided. Lists of CIDRs are passed via the `public` and `private` attributes of the single object." - } -} - -variable "assign_generated_ipv6_cidr_block" { - type = bool - description = "When `true`, assign AWS generated IPv6 CIDR block to the VPC. Conflicts with `ipv6_ipam_pool_id`." - default = false -} - -variable "public_subnets_enabled" { - type = bool - description = <<-EOT - If false, do not create public subnets. - Since NAT gateways and instances must be created in public subnets, these will also not be created when `false`. - EOT - default = true -} - -variable "nat_gateway_enabled" { - type = bool - description = "Flag to enable/disable NAT gateways" - default = true -} - -variable "nat_instance_enabled" { - type = bool - description = "Flag to enable/disable NAT instances" - default = false -} - -variable "nat_instance_type" { - type = string - description = "NAT Instance type" - default = "t3.micro" -} - -variable "map_public_ip_on_launch" { - type = bool - default = true - description = "Instances launched into a public subnet should be assigned a public IP address" -} - -variable "subnet_type_tag_key" { - type = string - description = "Key for subnet type tag to provide information about the type of subnets, e.g. `cpco/subnet/type=private` or `cpcp/subnet/type=public`" -} - -variable "max_subnet_count" { - type = number - default = 0 - description = "Sets the maximum amount of subnets to deploy. 0 will deploy a subnet for every provided availability zone (in `region_availability_zones` variable) within the region" -} - -variable "vpc_flow_logs_enabled" { - type = bool - description = "Enable or disable the VPC Flow Logs" - default = true -} - -variable "vpc_flow_logs_traffic_type" { - type = string - description = "The type of traffic to capture. Valid values: `ACCEPT`, `REJECT`, `ALL`" - default = "ALL" -} - -variable "vpc_flow_logs_log_destination_type" { - type = string - description = "The type of the logging destination. Valid values: `cloud-watch-logs`, `s3`" - default = "s3" -} - -variable "vpc_flow_logs_bucket_component_name" { - type = string - description = "The name of the VPC Flow Logs bucket component" - default = "vpc-flow-logs-bucket" -} - -variable "vpc_flow_logs_bucket_environment_name" { - type = string - description = "The name of the environment where the VPC Flow Logs bucket is provisioned" - default = "" -} - -variable "vpc_flow_logs_bucket_stage_name" { - type = string - description = "The stage (account) name where the VPC Flow Logs bucket is provisioned" - default = "" -} - -variable "vpc_flow_logs_bucket_tenant_name" { - type = string - description = <<-EOT - The name of the tenant where the VPC Flow Logs bucket is provisioned. - - If the `tenant` label is not used, leave this as `null`. - EOT - default = null -} - -variable "nat_eip_aws_shield_protection_enabled" { - type = bool - description = "Enable or disable AWS Shield Advanced protection for NAT EIPs. If set to 'true', a subscription to AWS Shield Advanced must exist in this account." - default = false -} - -variable "gateway_vpc_endpoints" { - type = set(string) - description = "A list of Gateway VPC Endpoints to provision into the VPC. Only valid values are \"dynamodb\" and \"s3\"." - default = [] -} - -variable "interface_vpc_endpoints" { - type = set(string) - description = "A list of Interface VPC Endpoints to provision into the VPC." - default = [] -} diff --git a/examples/tests/components/terraform/infra/vpc/versions.tf b/examples/tests/components/terraform/infra/vpc/versions.tf deleted file mode 100644 index 08c4806a4..000000000 --- a/examples/tests/components/terraform/infra/vpc/versions.tf +++ /dev/null @@ -1,14 +0,0 @@ -terraform { - required_version = ">= 1.3.0" - - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 4.9.0" - } - utils = { - source = "cloudposse/utils" - version = ">= 1.18.0" - } - } -} diff --git a/examples/tests/components/terraform/infra/vpc/vpc-flow-logs.tf b/examples/tests/components/terraform/infra/vpc/vpc-flow-logs.tf deleted file mode 100644 index 51bfe7b37..000000000 --- a/examples/tests/components/terraform/infra/vpc/vpc-flow-logs.tf +++ /dev/null @@ -1,15 +0,0 @@ -# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/flow_log -# https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html - -resource "aws_flow_log" "default" { - count = local.vpc_flow_logs_enabled ? 1 : 0 - - # Use the remote state output `vpc_flow_logs_bucket_arn` of the `vpc_flow_logs_bucket` component - log_destination = module.vpc_flow_logs_bucket[0].outputs.vpc_flow_logs_bucket_arn - - log_destination_type = var.vpc_flow_logs_log_destination_type - traffic_type = var.vpc_flow_logs_traffic_type - vpc_id = module.vpc.vpc_id - - tags = module.this.tags -} diff --git a/examples/tests/components/terraform/infra/vpc2/component.yaml b/examples/tests/components/terraform/infra/vpc2/component.yaml deleted file mode 100644 index 4a46b4ca3..000000000 --- a/examples/tests/components/terraform/infra/vpc2/component.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# This is an example of how to download a Terraform component from an OCI registry (https://opencontainers.org), e.g. AWS Public ECR - -# 'component.yaml' in the component folder is processed by the 'atmos' commands -# 'atmos vendor pull -c infra/vpc2' or 'atmos vendor pull --component infra/vpc2' - -apiVersion: atmos/v1 -kind: ComponentVendorConfig -metadata: - name: stable/aws/vpc - description: Config for vendoring of 'stable/aws/vpc' component -spec: - source: - # Source 'uri' supports the following protocols: OCI (https://opencontainers.org), Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, - # and all URL and archive formats as described in https://github.com/hashicorp/go-getter - # In 'uri', Golang templates are supported https://pkg.go.dev/text/template - # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'uri' - # Download the component from the AWS public ECR registry (https://docs.aws.amazon.com/AmazonECR/latest/public/public-registries.html) - uri: "oci://public.ecr.aws/cloudposse/components/terraform/stable/aws/vpc:{{.Version}}" - version: "latest" - # Only include the files that match the 'included_paths' patterns - # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths' - # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) - # https://en.wikipedia.org/wiki/Glob_(programming) - # https://github.com/bmatcuk/doublestar#patterns - included_paths: - - "**/*.*" diff --git a/examples/tests/components/terraform/mixins/context.tf b/examples/tests/components/terraform/mixins/context.tf deleted file mode 100644 index 5e0ef8856..000000000 --- a/examples/tests/components/terraform/mixins/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/mixins/introspection.mixin.tf b/examples/tests/components/terraform/mixins/introspection.mixin.tf deleted file mode 100644 index 3af29f58d..000000000 --- a/examples/tests/components/terraform/mixins/introspection.mixin.tf +++ /dev/null @@ -1,34 +0,0 @@ -# This mixin is meant to be added to Terraform components in order to append a `Component` tag to all resources in the -# configuration, specifying which component the resources belong to. -# -# It's important to note that all modules and resources within the component then need to use `module.introspection.context` -# and `module.introspection.tags`, respectively, rather than `module.this.context` and `module.this.tags`. -# - -locals { - # Throw an error if lookup fails - check_required_tags = module.this.enabled ? [ - for k in var.required_tags : lookup(module.this.tags, k) - ] : [] -} - -variable "required_tags" { - type = list(string) - description = "List of required tag names" - default = [] -} - -# `introspection` module will contain the additional tags -module "introspection" { - source = "cloudposse/label/null" - version = "0.25.0" - - tags = merge( - var.tags, - { - "Component" = basename(abspath(path.module)) - } - ) - - context = module.this.context -} diff --git a/examples/tests/components/terraform/test/template-functions-test/context.tf b/examples/tests/components/terraform/test/template-functions-test/context.tf deleted file mode 100644 index 5e0ef8856..000000000 --- a/examples/tests/components/terraform/test/template-functions-test/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/test/template-functions-test/main.tf b/examples/tests/components/terraform/test/template-functions-test/main.tf deleted file mode 100644 index 3d0c15f53..000000000 --- a/examples/tests/components/terraform/test/template-functions-test/main.tf +++ /dev/null @@ -1,6 +0,0 @@ -module "test_label" { - source = "cloudposse/label/null" - version = "0.25.0" - - context = module.this.context -} diff --git a/examples/tests/components/terraform/test/template-functions-test/outputs.tf b/examples/tests/components/terraform/test/template-functions-test/outputs.tf deleted file mode 100644 index 2af5e9cef..000000000 --- a/examples/tests/components/terraform/test/template-functions-test/outputs.tf +++ /dev/null @@ -1,22 +0,0 @@ -output "test_label_id" { - value = module.test_label.id - description = "Test label ID" -} - -output "test_list" { - value = [ - "list_item_1", - "list_item_2", - "list_item_3" - ] - description = "Test list" -} - -output "test_map" { - value = { - a = 1, - b = 2, - c = 3 - } - description = "Test map" -} diff --git a/examples/tests/components/terraform/test/template-functions-test2/context.tf b/examples/tests/components/terraform/test/template-functions-test2/context.tf deleted file mode 100644 index 5e0ef8856..000000000 --- a/examples/tests/components/terraform/test/template-functions-test2/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/test/template-functions-test2/outputs.tf b/examples/tests/components/terraform/test/template-functions-test2/outputs.tf deleted file mode 100644 index 76c897196..000000000 --- a/examples/tests/components/terraform/test/template-functions-test2/outputs.tf +++ /dev/null @@ -1,14 +0,0 @@ -output "region" { - value = var.region - description = "Test label ID" -} - -output "test_label_id" { - value = var.test_label_id - description = "Test label ID" -} - -output "test_label_id_2" { - value = var.test_label_id_2 - description = "Test label ID 2" -} diff --git a/examples/tests/components/terraform/test/template-functions-test2/variables.tf b/examples/tests/components/terraform/test/template-functions-test2/variables.tf deleted file mode 100644 index cfbc1e4f9..000000000 --- a/examples/tests/components/terraform/test/template-functions-test2/variables.tf +++ /dev/null @@ -1,14 +0,0 @@ -variable "region" { - type = string - description = "Region" -} - -variable "test_label_id" { - type = string - description = "Test label ID" -} - -variable "test_label_id_2" { - type = string - description = "Test label ID 2" -} diff --git a/examples/tests/components/terraform/test/test-component/context.tf b/examples/tests/components/terraform/test/test-component/context.tf deleted file mode 100644 index 5e0ef8856..000000000 --- a/examples/tests/components/terraform/test/test-component/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/test/test-component/main.tf b/examples/tests/components/terraform/test/test-component/main.tf deleted file mode 100644 index 6ad57234b..000000000 --- a/examples/tests/components/terraform/test/test-component/main.tf +++ /dev/null @@ -1,17 +0,0 @@ -module "service_1_label" { - source = "cloudposse/label/null" - version = "0.25.0" - - name = var.service_1_name - - context = module.this.context -} - -module "service_2_label" { - source = "cloudposse/label/null" - version = "0.25.0" - - name = var.service_2_name - - context = module.this.context -} diff --git a/examples/tests/components/terraform/test/test-component/outputs.tf b/examples/tests/components/terraform/test/test-component/outputs.tf deleted file mode 100644 index d87b49b2d..000000000 --- a/examples/tests/components/terraform/test/test-component/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -output "service_1_id" { - value = module.service_1_label.id - description = "Service 1 ID" -} - -output "service_2_id" { - value = module.service_2_label.id - description = "Service 2 ID" -} diff --git a/examples/tests/components/terraform/test/test-component/variables.tf b/examples/tests/components/terraform/test/test-component/variables.tf deleted file mode 100644 index 90c60dc3a..000000000 --- a/examples/tests/components/terraform/test/test-component/variables.tf +++ /dev/null @@ -1,38 +0,0 @@ -variable "region" { - type = string - description = "Region" -} - -variable "service_1_name" { - type = string - description = "Service 1 name" -} - -variable "service_1_list" { - type = list(string) - description = "Service 1 list" - default = [] -} - -variable "service_1_map" { - type = map(string) - description = "Service 1 map" - default = {} -} - -variable "service_2_name" { - type = string - description = "Service 2 name" -} - -variable "service_2_list" { - type = list(string) - description = "Service 2 list" - default = [] -} - -variable "service_2_map" { - type = map(string) - description = "Service 2 map" - default = {} -} diff --git a/examples/tests/components/terraform/test/test-component/versions.tf b/examples/tests/components/terraform/test/test-component/versions.tf deleted file mode 100644 index 429c0b36d..000000000 --- a/examples/tests/components/terraform/test/test-component/versions.tf +++ /dev/null @@ -1,3 +0,0 @@ -terraform { - required_version = ">= 1.0.0" -} diff --git a/examples/tests/components/terraform/test/test2/test-component-2/context.tf b/examples/tests/components/terraform/test/test2/test-component-2/context.tf deleted file mode 100644 index 5e0ef8856..000000000 --- a/examples/tests/components/terraform/test/test2/test-component-2/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/test/test2/test-component-2/main.tf b/examples/tests/components/terraform/test/test2/test-component-2/main.tf deleted file mode 100644 index 6ad57234b..000000000 --- a/examples/tests/components/terraform/test/test2/test-component-2/main.tf +++ /dev/null @@ -1,17 +0,0 @@ -module "service_1_label" { - source = "cloudposse/label/null" - version = "0.25.0" - - name = var.service_1_name - - context = module.this.context -} - -module "service_2_label" { - source = "cloudposse/label/null" - version = "0.25.0" - - name = var.service_2_name - - context = module.this.context -} diff --git a/examples/tests/components/terraform/test/test2/test-component-2/outputs.tf b/examples/tests/components/terraform/test/test2/test-component-2/outputs.tf deleted file mode 100644 index d87b49b2d..000000000 --- a/examples/tests/components/terraform/test/test2/test-component-2/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -output "service_1_id" { - value = module.service_1_label.id - description = "Service 1 ID" -} - -output "service_2_id" { - value = module.service_2_label.id - description = "Service 2 ID" -} diff --git a/examples/tests/components/terraform/test/test2/test-component-2/variables.tf b/examples/tests/components/terraform/test/test2/test-component-2/variables.tf deleted file mode 100644 index 90c60dc3a..000000000 --- a/examples/tests/components/terraform/test/test2/test-component-2/variables.tf +++ /dev/null @@ -1,38 +0,0 @@ -variable "region" { - type = string - description = "Region" -} - -variable "service_1_name" { - type = string - description = "Service 1 name" -} - -variable "service_1_list" { - type = list(string) - description = "Service 1 list" - default = [] -} - -variable "service_1_map" { - type = map(string) - description = "Service 1 map" - default = {} -} - -variable "service_2_name" { - type = string - description = "Service 2 name" -} - -variable "service_2_list" { - type = list(string) - description = "Service 2 list" - default = [] -} - -variable "service_2_map" { - type = map(string) - description = "Service 2 map" - default = {} -} diff --git a/examples/tests/components/terraform/test/test2/test-component-2/versions.tf b/examples/tests/components/terraform/test/test2/test-component-2/versions.tf deleted file mode 100644 index 429c0b36d..000000000 --- a/examples/tests/components/terraform/test/test2/test-component-2/versions.tf +++ /dev/null @@ -1,3 +0,0 @@ -terraform { - required_version = ">= 1.0.0" -} diff --git a/examples/tests/components/terraform/top-level-component1/context.tf b/examples/tests/components/terraform/top-level-component1/context.tf deleted file mode 100644 index 5e0ef8856..000000000 --- a/examples/tests/components/terraform/top-level-component1/context.tf +++ /dev/null @@ -1,279 +0,0 @@ -# -# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label -# All other instances of this file should be a copy of that one -# -# -# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf -# and then place it in your Terraform module to automatically get -# Cloud Posse's standard configuration inputs suitable for passing -# to Cloud Posse modules. -# -# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf -# -# Modules should access the whole context as `module.this.context` -# to get the input variables with nulls for defaults, -# for example `context = module.this.context`, -# and access individual variables as `module.this.`, -# with final values filled in. -# -# For example, when using defaults, `module.this.context.delimiter` -# will be null, and `module.this.delimiter` will be `-` (hyphen). -# - -module "this" { - source = "cloudposse/label/null" - version = "0.25.0" # requires Terraform >= 0.13.0 - - enabled = var.enabled - namespace = var.namespace - tenant = var.tenant - environment = var.environment - stage = var.stage - name = var.name - delimiter = var.delimiter - attributes = var.attributes - tags = var.tags - additional_tag_map = var.additional_tag_map - label_order = var.label_order - regex_replace_chars = var.regex_replace_chars - id_length_limit = var.id_length_limit - label_key_case = var.label_key_case - label_value_case = var.label_value_case - descriptor_formats = var.descriptor_formats - labels_as_tags = var.labels_as_tags - - context = var.context -} - -# Copy contents of cloudposse/terraform-null-label/variables.tf here - -variable "context" { - type = any - default = { - enabled = true - namespace = null - tenant = null - environment = null - stage = null - name = null - delimiter = null - attributes = [] - tags = {} - additional_tag_map = {} - regex_replace_chars = null - label_order = [] - id_length_limit = null - label_key_case = null - label_value_case = null - descriptor_formats = {} - # Note: we have to use [] instead of null for unset lists due to - # https://github.com/hashicorp/terraform/issues/28137 - # which was not fixed until Terraform 1.0.0, - # but we want the default to be all the labels in `label_order` - # and we want users to be able to prevent all tag generation - # by setting `labels_as_tags` to `[]`, so we need - # a different sentinel to indicate "default" - labels_as_tags = ["unset"] - } - description = <<-EOT - Single object for setting entire context at once. - See description of individual variables for details. - Leave string and numeric variables as `null` to use default value. - Individual variable settings (non-null) override settings in context object, - except for attributes, tags, and additional_tag_map, which are merged. - EOT - - validation { - condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`." - } - - validation { - condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "enabled" { - type = bool - default = null - description = "Set to false to prevent the module from creating any resources" -} - -variable "namespace" { - type = string - default = null - description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" -} - -variable "tenant" { - type = string - default = null - description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" -} - -variable "environment" { - type = string - default = null - description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" -} - -variable "stage" { - type = string - default = null - description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" -} - -variable "name" { - type = string - default = null - description = <<-EOT - ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. - This is the only ID element not also included as a `tag`. - The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. - EOT -} - -variable "delimiter" { - type = string - default = null - description = <<-EOT - Delimiter to be used between ID elements. - Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. - EOT -} - -variable "attributes" { - type = list(string) - default = [] - description = <<-EOT - ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, - in the order they appear in the list. New attributes are appended to the - end of the list. The elements of the list are joined by the `delimiter` - and treated as a single ID element. - EOT -} - -variable "labels_as_tags" { - type = set(string) - default = ["default"] - description = <<-EOT - Set of labels (ID elements) to include as tags in the `tags` output. - Default is to include all labels. - Tags with empty values will not be included in the `tags` output. - Set to `[]` to suppress all generated tags. - **Notes:** - The value of the `name` tag, if included, will be the `id`, not the `name`. - Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be - changed in later chained modules. Attempts to change it will be silently ignored. - EOT -} - -variable "tags" { - type = map(string) - default = {} - description = <<-EOT - Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). - Neither the tag keys nor the tag values will be modified by this module. - EOT -} - -variable "additional_tag_map" { - type = map(string) - default = {} - description = <<-EOT - Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. - This is for some rare cases where resources want additional configuration of tags - and therefore take a list of maps with tag key, value, and additional configuration. - EOT -} - -variable "label_order" { - type = list(string) - default = null - description = <<-EOT - The order in which the labels (ID elements) appear in the `id`. - Defaults to ["namespace", "environment", "stage", "name", "attributes"]. - You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. - EOT -} - -variable "regex_replace_chars" { - type = string - default = null - description = <<-EOT - Terraform regular expression (regex) string. - Characters matching the regex will be removed from the ID elements. - If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. - EOT -} - -variable "id_length_limit" { - type = number - default = null - description = <<-EOT - Limit `id` to this many characters (minimum 6). - Set to `0` for unlimited length. - Set to `null` for keep the existing setting, which defaults to `0`. - Does not affect `id_full`. - EOT - validation { - condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 - error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." - } -} - -variable "label_key_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of the `tags` keys (label names) for tags generated by this module. - Does not affect keys of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper`. - Default value: `title`. - EOT - - validation { - condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) - error_message = "Allowed values: `lower`, `title`, `upper`." - } -} - -variable "label_value_case" { - type = string - default = null - description = <<-EOT - Controls the letter case of ID elements (labels) as included in `id`, - set as tag values, and output by this module individually. - Does not affect values of tags passed in via the `tags` input. - Possible values: `lower`, `title`, `upper` and `none` (no transformation). - Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. - Default value: `lower`. - EOT - - validation { - condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) - error_message = "Allowed values: `lower`, `title`, `upper`, `none`." - } -} - -variable "descriptor_formats" { - type = any - default = {} - description = <<-EOT - Describe additional descriptors to be output in the `descriptors` output map. - Map of maps. Keys are names of descriptors. Values are maps of the form - `{ - format = string - labels = list(string) - }` - (Type is `any` so the map values can later be enhanced to provide additional options.) - `format` is a Terraform format string to be passed to the `format()` function. - `labels` is a list of labels, in order, to pass to `format()` function. - Label values will be normalized before being passed to `format()` so they will be - identical to how they appear in `id`. - Default is `{}` (`descriptors` output will be empty). - EOT -} - -#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/top-level-component1/main.tf b/examples/tests/components/terraform/top-level-component1/main.tf deleted file mode 100644 index 3496a4e33..000000000 --- a/examples/tests/components/terraform/top-level-component1/main.tf +++ /dev/null @@ -1,16 +0,0 @@ -module "service_1_label" { - source = "cloudposse/label/null" - version = "0.25.0" - - name = var.service_1_name - - context = module.this.context -} - -module "service_2_label" { - source = "../../../modules/label" - - name = var.service_2_name - - context = module.this.context -} diff --git a/examples/tests/components/terraform/top-level-component1/outputs.tf b/examples/tests/components/terraform/top-level-component1/outputs.tf deleted file mode 100644 index ae890ae82..000000000 --- a/examples/tests/components/terraform/top-level-component1/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -output "service_1_id" { - value = module.service_1_label.id - description = "Service 1 ID" -} - -output "service_2_id" { - value = module.service_2_label.label.id - description = "Service 2 ID" -} diff --git a/examples/tests/components/terraform/top-level-component1/policies/policy1.rego b/examples/tests/components/terraform/top-level-component1/policies/policy1.rego deleted file mode 100644 index 6fa6fec89..000000000 --- a/examples/tests/components/terraform/top-level-component1/policies/policy1.rego +++ /dev/null @@ -1,5 +0,0 @@ -package atmos - -allow { - true -} diff --git a/examples/tests/components/terraform/top-level-component1/variables.tf b/examples/tests/components/terraform/top-level-component1/variables.tf deleted file mode 100644 index 90c60dc3a..000000000 --- a/examples/tests/components/terraform/top-level-component1/variables.tf +++ /dev/null @@ -1,38 +0,0 @@ -variable "region" { - type = string - description = "Region" -} - -variable "service_1_name" { - type = string - description = "Service 1 name" -} - -variable "service_1_list" { - type = list(string) - description = "Service 1 list" - default = [] -} - -variable "service_1_map" { - type = map(string) - description = "Service 1 map" - default = {} -} - -variable "service_2_name" { - type = string - description = "Service 2 name" -} - -variable "service_2_list" { - type = list(string) - description = "Service 2 list" - default = [] -} - -variable "service_2_map" { - type = map(string) - description = "Service 2 map" - default = {} -} diff --git a/examples/tests/components/terraform/top-level-component1/versions.tf b/examples/tests/components/terraform/top-level-component1/versions.tf deleted file mode 100644 index 429c0b36d..000000000 --- a/examples/tests/components/terraform/top-level-component1/versions.tf +++ /dev/null @@ -1,3 +0,0 @@ -terraform { - required_version = ">= 1.0.0" -} diff --git a/go.mod b/go.mod index c7cc392e5..90bde46cb 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,8 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-wordwrap v1.0.1 github.com/mitchellh/mapstructure v1.5.0 - github.com/open-policy-agent/opa v1.0.0 + github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a + github.com/open-policy-agent/opa v0.70.0 github.com/otiai10/copy v1.14.0 github.com/pkg/errors v0.9.1 github.com/samber/lo v1.47.0 @@ -194,7 +195,6 @@ require ( github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect From d61fe9ec0684d101a4c7f2661dbe6dd09e0f3bdf Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Wed, 18 Dec 2024 22:44:01 +0000 Subject: [PATCH 11/49] markdown implementation and styles improvements --- cmd/workflow.go | 55 +++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index 2ef42d847..61e3e02ce 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -1,6 +1,7 @@ package cmd import ( + _ "embed" "fmt" "strings" @@ -12,6 +13,9 @@ import ( u "github.com/cloudposse/atmos/pkg/utils" ) +//go:embed markdown/workflow.md +var workflowMarkdown string + // ErrorMessage represents a structured error message type ErrorMessage struct { Title string @@ -19,22 +23,6 @@ type ErrorMessage struct { Suggestion string } -// String returns the markdown representation of the error message -func (e ErrorMessage) String() string { - var sb strings.Builder - sb.WriteString(fmt.Sprintf("# ❌ %s\n\n", e.Title)) - - if e.Details != "" { - sb.WriteString(fmt.Sprintf("## Details\n%s\n\n", e.Details)) - } - - if e.Suggestion != "" { - sb.WriteString(fmt.Sprintf("## Suggestion\n%s\n\n", e.Suggestion)) - } - - return sb.String() -} - // renderError renders an error message using the markdown renderer func renderError(msg ErrorMessage) error { renderer, err := markdown.NewRenderer( @@ -44,7 +32,7 @@ func renderError(msg ErrorMessage) error { return fmt.Errorf("failed to create markdown renderer: %w", err) } - rendered, err := renderer.Render(msg.String()) + rendered, err := renderer.RenderError(msg.Title, msg.Details, msg.Suggestion) if err != nil { return fmt.Errorf("failed to render error message: %w", err) } @@ -53,6 +41,19 @@ func renderError(msg ErrorMessage) error { return nil } +// getMarkdownSection returns a section from the markdown file +func getMarkdownSection(title string) (details, suggestion string) { + sections := markdown.ParseMarkdownSections(workflowMarkdown) + if section, ok := sections[title]; ok { + parts := markdown.SplitMarkdownContent(section) + if len(parts) >= 2 { + return parts[0], parts[1] + } + return section, "" + } + return "", "" +} + // workflowCmd executes a workflow var workflowCmd = &cobra.Command{ Use: "workflow", @@ -78,10 +79,11 @@ var workflowCmd = &cobra.Command{ // Check if the workflow name is "list" (invalid command) if args[0] == "list" { + details, suggestion := getMarkdownSection("Invalid Command") err := renderError(ErrorMessage{ Title: "Invalid Command", - Details: "The command `atmos workflow list` is not valid.", - Suggestion: "Use `atmos workflow --file ` to execute a workflow, or run `atmos workflow` without arguments to use the interactive UI.", + Details: details, + Suggestion: suggestion, }) if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) @@ -92,10 +94,11 @@ var workflowCmd = &cobra.Command{ // Get the --file flag value workflowFile, _ := cmd.Flags().GetString("file") if workflowFile == "" { + details, suggestion := getMarkdownSection("Missing Required Flag") err := renderError(ErrorMessage{ Title: "Missing Required Flag", - Details: "The `--file` flag is required to specify a workflow manifest.", - Suggestion: "Example:\n`atmos workflow deploy-infra --file workflow1`", + Details: details, + Suggestion: suggestion, }) if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) @@ -109,19 +112,21 @@ var workflowCmd = &cobra.Command{ u.LogErrorAndExit(schema.AtmosConfiguration{}, err) // Format common error messages if strings.Contains(err.Error(), "does not exist") { + details, suggestion := getMarkdownSection("Workflow File Not Found") err := renderError(ErrorMessage{ Title: "Workflow File Not Found", - Details: fmt.Sprintf("The workflow manifest file '%s' could not be found.", workflowFile), - Suggestion: "Check if the file exists in the workflows directory and the path is correct.", + Details: details, + Suggestion: suggestion, }) if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) } } else if strings.Contains(err.Error(), "does not have the") { + details, suggestion := getMarkdownSection("Invalid Workflow") err := renderError(ErrorMessage{ Title: "Invalid Workflow", - Details: err.Error(), - Suggestion: fmt.Sprintf("Check the available workflows in '%s' and make sure you're using the correct workflow name.", workflowFile), + Details: details, + Suggestion: suggestion, }) if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) From 409d6f6f815fa3f8964f9d18d9b53aa88f71eb5a Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Wed, 18 Dec 2024 22:45:55 +0000 Subject: [PATCH 12/49] parser md implementation --- pkg/ui/markdown/parser.go | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 pkg/ui/markdown/parser.go diff --git a/pkg/ui/markdown/parser.go b/pkg/ui/markdown/parser.go new file mode 100644 index 000000000..9e9176cce --- /dev/null +++ b/pkg/ui/markdown/parser.go @@ -0,0 +1,55 @@ +package markdown + +import ( + "strings" +) + +// ParseMarkdownSections parses a markdown string and returns a map of section titles to their content +func ParseMarkdownSections(content string) map[string]string { + sections := make(map[string]string) + lines := strings.Split(content, "\n") + + var currentTitle string + var currentContent []string + + for _, line := range lines { + if strings.HasPrefix(line, "# ") { + // If we have a previous section, save it + if currentTitle != "" { + sections[currentTitle] = strings.TrimSpace(strings.Join(currentContent, "\n")) + } + // Start new section + currentTitle = strings.TrimPrefix(line, "# ") + currentContent = []string{} + } else if currentTitle != "" { + currentContent = append(currentContent, line) + } + } + + // Save the last section + if currentTitle != "" { + sections[currentTitle] = strings.TrimSpace(strings.Join(currentContent, "\n")) + } + + return sections +} + +// SplitMarkdownContent splits markdown content into details and suggestion parts +func SplitMarkdownContent(content string) []string { + parts := strings.Split(content, "\n\n") + var result []string + + // First non-empty line is details + for i, part := range parts { + if strings.TrimSpace(part) != "" { + result = append(result, strings.TrimSpace(part)) + if i < len(parts)-1 { + // Rest is suggestion + result = append(result, strings.TrimSpace(strings.Join(parts[i+1:], "\n\n"))) + } + break + } + } + + return result +} From 62057b53e26ca28ec88b91bbda74ce18ea934830 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Thu, 19 Dec 2024 21:17:24 +0000 Subject: [PATCH 13/49] workflow file --- cmd/markdown/workflow.md | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 cmd/markdown/workflow.md diff --git a/cmd/markdown/workflow.md b/cmd/markdown/workflow.md new file mode 100644 index 000000000..e59886a5b --- /dev/null +++ b/cmd/markdown/workflow.md @@ -0,0 +1,60 @@ +# Invalid Command +The command `atmos workflow list` is not valid. + +## Examples +Use one of the following commands: +```shell +$ atmos workflow # Use interactive UI +$ atmos workflow --file # Execute workflow +$ atmos workflow --file --stack +$ atmos workflow --file --from-step +``` +For more information, refer to the [Workflow Command Documentation](https://atmos.tools/cli/commands/workflow/). + +# Missing Required Flag +The `--file` flag is required to specify a workflow manifest. + +## Examples +– Deploy a workflow +```shell +$ atmos workflow deploy-infra --file workflow1 +``` +– Deploy with stack configuration +```shell +$ atmos workflow deploy-infra --file workflow1 --stack dev +``` +– Resume from a specific step +```shell +$ atmos workflow deploy-infra --file workflow1 --from-step deploy-vpc +``` +For more information, refer to the [Workflow Command Documentation](https://atmos.tools/cli/commands/workflow/). + +# Workflow File Not Found +The workflow manifest file could not be found. + +## Examples +– List available workflows +```shell +$ ls workflows/ +``` +– Execute a specific workflow +```shell +$ atmos workflow --file +``` +For more information, refer to the [Workflow Command Documentation](https://atmos.tools/cli/commands/workflow/). + +# Invalid Workflow +The specified workflow is not valid or has incorrect configuration. + +## Examples +– Example of a valid workflow configuration: +```yaml +name: deploy-infra +description: Deploy infrastructure components +steps: + - name: deploy-vpc + command: atmos terraform apply vpc + - name: deploy-eks + command: atmos terraform apply eks +``` +For more information, refer to the [Workflow Command Documentation](https://atmos.tools/cli/commands/workflow/). From f2e82b3f0a6c9660c9be656f121bcf0a6a59cf7b Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Thu, 19 Dec 2024 21:25:42 +0000 Subject: [PATCH 14/49] markdown renderer --- pkg/ui/markdown/renderer.go | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/pkg/ui/markdown/renderer.go b/pkg/ui/markdown/renderer.go index bfba82782..bf07a6df5 100644 --- a/pkg/ui/markdown/renderer.go +++ b/pkg/ui/markdown/renderer.go @@ -1,6 +1,9 @@ package markdown import ( + "fmt" + "strings" + "github.com/charmbracelet/glamour" "github.com/muesli/termenv" ) @@ -61,6 +64,43 @@ func (r *Renderer) RenderWithStyle(content string, style []byte) (string, error) return renderer.Render(content) } +// RenderWorkflow renders workflow documentation with specific styling +func (r *Renderer) RenderWorkflow(content string) (string, error) { + // Add workflow header + content = "# Workflow\n\n" + content + return r.Render(content) +} + +// RenderError renders an error message with specific styling +func (r *Renderer) RenderError(title, details, examples string) (string, error) { + var content string + + if details != "" { + content += fmt.Sprintf("%s\n\n", details) + } + + if examples != "" { + if !strings.Contains(examples, "## Examples") { + content += fmt.Sprintf("## Examples\n\n%s", examples) + } else { + content += examples + } + } + + return r.Render(content) +} + +// RenderSuccess renders a success message with specific styling +func (r *Renderer) RenderSuccess(title, details string) (string, error) { + content := fmt.Sprintf("# %s\n\n", title) + + if details != "" { + content += fmt.Sprintf("## Details\n%s\n\n", details) + } + + return r.Render(content) +} + // Option is a function that configures the renderer type Option func(*Renderer) From ed32780c06218854390d2fdfbd1a61d39cb7762e Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 20 Dec 2024 07:48:09 +0000 Subject: [PATCH 15/49] purple and cyan colors back --- pkg/ui/markdown/styles.go | 191 +++++++++++++------------------------- 1 file changed, 63 insertions(+), 128 deletions(-) diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go index 06441d5a3..083ea5188 100644 --- a/pkg/ui/markdown/styles.go +++ b/pkg/ui/markdown/styles.go @@ -3,68 +3,85 @@ package markdown // DefaultStyle defines the default Atmos markdown style var DefaultStyle = []byte(`{ "document": { - "block_prefix": "\n", + "block_prefix": "", "block_suffix": "\n", - "color": "#4A5568", + "color": "#ffffff", "margin": 0 }, "block_quote": { "indent": 1, - "indent_token": "│ " + "indent_token": "│ ", + "color": "#8a2be2" }, "paragraph": { - "block_suffix": "\n" + "block_prefix": "", + "block_suffix": "", + "color": "#ffffff" }, "list": { - "level_indent": 2 + "level_indent": 2, + "color": "#ffffff", + "margin": 0, + "block_suffix": "" + }, + "list_item": { + "block_prefix": "– ", + "color": "#ffffff", + "margin": 0, + "block_suffix": "" }, "heading": { + "block_prefix": "", "block_suffix": "\n", - "color": "#00A3E0", - "bold": true + "color": "#8a2be2", + "bold": true, + "margin": 0 }, "h1": { "prefix": "# ", - "color": "#00A3E0", - "bold": true + "color": "#8a2be2", + "bold": true, + "margin": 1 }, "h2": { "prefix": "## ", - "color": "#00A3E0", - "bold": true + "color": "#8a2be2", + "bold": true, + "margin": 1 }, "h3": { "prefix": "### ", - "color": "#00A3E0", + "color": "#8a2be2", "bold": true }, "h4": { "prefix": "#### ", - "color": "#00A3E0", + "color": "#8a2be2", "bold": true }, "h5": { "prefix": "##### ", - "color": "#00A3E0", + "color": "#8a2be2", "bold": true }, "h6": { "prefix": "###### ", - "color": "#00A3E0", + "color": "#8a2be2", "bold": true }, - "text": {}, - "strikethrough": { - "crossed_out": true - }, - "emph": { - "italic": true + "text": { + "color": "#ffffff" }, "strong": { + "color": "#8a2be2", "bold": true }, + "emph": { + "color": "#8a2be2", + "italic": true + }, "hr": { - "color": "#CBD5E0", + "color": "#8a2be2", "format": "\n--------\n" }, "item": { @@ -73,123 +90,33 @@ var DefaultStyle = []byte(`{ "enumeration": { "block_prefix": ". " }, - "task": { - "ticked": "[✓] ", - "unticked": "[ ] " - }, - "link": { - "color": "#4299E1", - "underline": true - }, - "link_text": { - "color": "#4299E1" - }, - "image": { - "color": "#4299E1" - }, - "image_text": { - "color": "#4299E1", - "format": "Image: {{.text}} →" - }, "code": { - "color": "#4A5568", - "background_color": "#F7FAFC" + "color": "#00ffff" }, "code_block": { - "color": "#4A5568", - "background_color": "#F7FAFC", - "margin": 2, + "margin": 0, + "block_suffix": "", "chroma": { "text": { - "color": "#4A5568" - }, - "error": { - "color": "#F56565", - "background_color": "#F7FAFC" - }, - "comment": { - "color": "#718096" - }, - "comment_preproc": { - "color": "#4299E1" + "color": "#00ffff" }, "keyword": { - "color": "#00A3E0" - }, - "keyword_reserved": { - "color": "#00A3E0" - }, - "keyword_namespace": { - "color": "#00A3E0" - }, - "keyword_type": { - "color": "#48BB78" - }, - "operator": { - "color": "#4A5568" - }, - "punctuation": { - "color": "#4A5568" - }, - "name": { - "color": "#4A5568" - }, - "name_builtin": { - "color": "#00A3E0" - }, - "name_tag": { - "color": "#00A3E0" - }, - "name_attribute": { - "color": "#48BB78" - }, - "name_class": { - "color": "#48BB78" - }, - "name_constant": { - "color": "#4299E1" - }, - "name_decorator": { - "color": "#4299E1" - }, - "name_exception": { - "color": "#F56565" - }, - "name_function": { - "color": "#4299E1" - }, - "name_other": { - "color": "#4A5568" + "color": "#8a2be2" }, "literal": { - "color": "#ECC94B" + "color": "#00ffff" }, - "literal_number": { - "color": "#ECC94B" + "string": { + "color": "#00ffff" }, - "literal_date": { - "color": "#ECC94B" - }, - "literal_string": { - "color": "#48BB78" - }, - "literal_string_escape": { - "color": "#4299E1" - }, - "generic_deleted": { - "color": "#F56565" - }, - "generic_emph": { - "italic": true - }, - "generic_inserted": { - "color": "#48BB78" + "name": { + "color": "#00ffff" }, - "generic_strong": { - "bold": true + "number": { + "color": "#00ffff" }, - "generic_subheading": { - "color": "#4299E1" + "comment": { + "color": "#8a2be2" } } }, @@ -201,8 +128,16 @@ var DefaultStyle = []byte(`{ "definition_list": {}, "definition_term": {}, "definition_description": { - "block_prefix": "\n🠶 " + "block_prefix": "\n" }, "html_block": {}, - "html_span": {} + "html_span": {}, + "link": { + "color": "#00ffff", + "underline": true + }, + "link_text": { + "color": "#00ffff", + "bold": true + } }`) From 00a832598f2e75126cbe23e8a022da0a98e08dc5 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 20 Dec 2024 18:35:04 +0000 Subject: [PATCH 16/49] fixes renderer and markdown --- cmd/markdown/workflow.md | 12 +++--- pkg/ui/markdown/styles.go | 57 +++++++++++++------------- pkg/{ui/markdown => utils}/renderer.go | 0 3 files changed, 35 insertions(+), 34 deletions(-) rename pkg/{ui/markdown => utils}/renderer.go (100%) diff --git a/cmd/markdown/workflow.md b/cmd/markdown/workflow.md index e59886a5b..83633047f 100644 --- a/cmd/markdown/workflow.md +++ b/cmd/markdown/workflow.md @@ -9,7 +9,7 @@ $ atmos workflow --file # Execute workflow $ atmos workflow --file --stack $ atmos workflow --file --from-step ``` -For more information, refer to the [Workflow Command Documentation](https://atmos.tools/cli/commands/workflow/). +For more information, refer to the [docs](https://atmos.tools/cli/commands/workflow/). # Missing Required Flag The `--file` flag is required to specify a workflow manifest. @@ -19,15 +19,15 @@ The `--file` flag is required to specify a workflow manifest. ```shell $ atmos workflow deploy-infra --file workflow1 ``` -– Deploy with stack configuration +– Deploy with stack configuration ```shell $ atmos workflow deploy-infra --file workflow1 --stack dev ``` -– Resume from a specific step +– Resume from a specific step ```shell $ atmos workflow deploy-infra --file workflow1 --from-step deploy-vpc ``` -For more information, refer to the [Workflow Command Documentation](https://atmos.tools/cli/commands/workflow/). +For more information, refer to the [docs](https://atmos.tools/cli/commands/workflow/). # Workflow File Not Found The workflow manifest file could not be found. @@ -41,7 +41,7 @@ $ ls workflows/ ```shell $ atmos workflow --file ``` -For more information, refer to the [Workflow Command Documentation](https://atmos.tools/cli/commands/workflow/). +For more information, refer to the [docs](https://atmos.tools/cli/commands/workflow/). # Invalid Workflow The specified workflow is not valid or has incorrect configuration. @@ -57,4 +57,4 @@ steps: - name: deploy-eks command: atmos terraform apply eks ``` -For more information, refer to the [Workflow Command Documentation](https://atmos.tools/cli/commands/workflow/). +For more information, refer to the [docs](https://atmos.tools/cli/commands/workflow/). diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go index 083ea5188..60b90f9b7 100644 --- a/pkg/ui/markdown/styles.go +++ b/pkg/ui/markdown/styles.go @@ -5,83 +5,83 @@ var DefaultStyle = []byte(`{ "document": { "block_prefix": "", "block_suffix": "\n", - "color": "#ffffff", + "color": "#FFFFFF", "margin": 0 }, "block_quote": { "indent": 1, "indent_token": "│ ", - "color": "#8a2be2" + "color": "#9B51E0" }, "paragraph": { "block_prefix": "", "block_suffix": "", - "color": "#ffffff" + "color": "#FFFFFF" }, "list": { - "level_indent": 2, - "color": "#ffffff", + "level_indent": 4, + "color": "#FFFFFF", "margin": 0, "block_suffix": "" }, "list_item": { "block_prefix": "– ", - "color": "#ffffff", + "color": "#FFFFFF", "margin": 0, "block_suffix": "" }, "heading": { "block_prefix": "", "block_suffix": "\n", - "color": "#8a2be2", + "color": "#00A3E0", "bold": true, "margin": 0 }, "h1": { "prefix": "# ", - "color": "#8a2be2", + "color": "#00A3E0", "bold": true, "margin": 1 }, "h2": { "prefix": "## ", - "color": "#8a2be2", + "color": "#9B51E0", "bold": true, "margin": 1 }, "h3": { "prefix": "### ", - "color": "#8a2be2", + "color": "#00A3E0", "bold": true }, "h4": { "prefix": "#### ", - "color": "#8a2be2", + "color": "#00A3E0", "bold": true }, "h5": { "prefix": "##### ", - "color": "#8a2be2", + "color": "#00A3E0", "bold": true }, "h6": { "prefix": "###### ", - "color": "#8a2be2", + "color": "#00A3E0", "bold": true }, "text": { - "color": "#ffffff" + "color": "#FFFFFF" }, "strong": { - "color": "#8a2be2", + "color": "#9B51E0", "bold": true }, "emph": { - "color": "#8a2be2", + "color": "#9B51E0", "italic": true }, "hr": { - "color": "#8a2be2", + "color": "#9B51E0", "format": "\n--------\n" }, "item": { @@ -91,32 +91,33 @@ var DefaultStyle = []byte(`{ "block_prefix": ". " }, "code": { - "color": "#00ffff" + "color": "#9B51E0" }, "code_block": { - "margin": 0, + "margin": 1, + "indent": 2, "block_suffix": "", "chroma": { "text": { - "color": "#00ffff" + "color": "#00A3E0" }, "keyword": { - "color": "#8a2be2" + "color": "#9B51E0" }, "literal": { - "color": "#00ffff" + "color": "#00A3E0" }, "string": { - "color": "#00ffff" + "color": "#00A3E0" }, "name": { - "color": "#00ffff" + "color": "#00A3E0" }, "number": { - "color": "#00ffff" + "color": "#00A3E0" }, "comment": { - "color": "#8a2be2" + "color": "#9B51E0" } } }, @@ -133,11 +134,11 @@ var DefaultStyle = []byte(`{ "html_block": {}, "html_span": {}, "link": { - "color": "#00ffff", + "color": "#00A3E0", "underline": true }, "link_text": { - "color": "#00ffff", + "color": "#9B51E0", "bold": true } }`) diff --git a/pkg/ui/markdown/renderer.go b/pkg/utils/renderer.go similarity index 100% rename from pkg/ui/markdown/renderer.go rename to pkg/utils/renderer.go From 17c8e8e9b9270baf08ad289f7a50de9dd952ee87 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 20 Dec 2024 19:01:51 +0000 Subject: [PATCH 17/49] fix pkg location --- pkg/{utils => ui/markdown}/renderer.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/{utils => ui/markdown}/renderer.go (100%) diff --git a/pkg/utils/renderer.go b/pkg/ui/markdown/renderer.go similarity index 100% rename from pkg/utils/renderer.go rename to pkg/ui/markdown/renderer.go From 97f7f2f2e6890198f03fd9851cb33291e9dd2316 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 20 Dec 2024 22:09:54 +0000 Subject: [PATCH 18/49] fixes headers --- pkg/ui/markdown/renderer.go | 4 ++++ pkg/ui/markdown/styles.go | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/ui/markdown/renderer.go b/pkg/ui/markdown/renderer.go index bf07a6df5..f00e4059e 100644 --- a/pkg/ui/markdown/renderer.go +++ b/pkg/ui/markdown/renderer.go @@ -75,6 +75,10 @@ func (r *Renderer) RenderWorkflow(content string) (string, error) { func (r *Renderer) RenderError(title, details, examples string) (string, error) { var content string + if title != "" { + content += fmt.Sprintf("# %s\n\n", title) + } + if details != "" { content += fmt.Sprintf("%s\n\n", details) } diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go index 60b90f9b7..ddb905607 100644 --- a/pkg/ui/markdown/styles.go +++ b/pkg/ui/markdown/styles.go @@ -35,13 +35,18 @@ var DefaultStyle = []byte(`{ "block_suffix": "\n", "color": "#00A3E0", "bold": true, - "margin": 0 + "margin": 0, + "style_override": true }, "h1": { - "prefix": "# ", - "color": "#00A3E0", + "prefix": "", + "color": "#FFFFFF", + "background_color": "#9B51E0", "bold": true, - "margin": 1 + "margin": 2, + "block_prefix": "\n", + "block_suffix": "\n", + "padding": 1 }, "h2": { "prefix": "## ", From 9eabbe7900dc5a0c244716c5e102f5ac595fbc15 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 27 Dec 2024 21:38:26 +0000 Subject: [PATCH 19/49] sync main --- .../infra/account-map/component.yaml | 40 +++ .../infra/vpc-flow-logs-bucket/component.yaml | 52 ++++ .../infra/vpc-flow-logs-bucket/context.tf | 279 ++++++++++++++++++ .../infra/vpc-flow-logs-bucket/main.tf | 18 ++ .../infra/vpc-flow-logs-bucket/outputs.tf | 9 + .../infra/vpc-flow-logs-bucket/providers.tf | 19 ++ .../infra/vpc-flow-logs-bucket/variables.tf | 64 ++++ .../infra/vpc-flow-logs-bucket/versions.tf | 10 + .../terraform/infra/vpc/component.yaml | 36 +++ .../components/terraform/infra/vpc/context.tf | 279 ++++++++++++++++++ .../components/terraform/infra/vpc/main.tf | 173 +++++++++++ .../components/terraform/infra/vpc/outputs.tf | 109 +++++++ .../terraform/infra/vpc/providers.tf | 3 + .../terraform/infra/vpc/remote-state.tf | 20 ++ .../terraform/infra/vpc/variables.tf | 165 +++++++++++ .../terraform/infra/vpc/versions.tf | 14 + .../terraform/infra/vpc/vpc-flow-logs.tf | 15 + .../terraform/infra/vpc2/component.yaml | 26 ++ .../components/terraform/mixins/context.tf | 279 ++++++++++++++++++ .../terraform/mixins/introspection.mixin.tf | 34 +++ .../test/template-functions-test/context.tf | 279 ++++++++++++++++++ .../test/template-functions-test/main.tf | 6 + .../test/template-functions-test/outputs.tf | 22 ++ .../test/template-functions-test2/context.tf | 279 ++++++++++++++++++ .../test/template-functions-test2/outputs.tf | 14 + .../template-functions-test2/variables.tf | 14 + .../terraform/test/test-component/context.tf | 279 ++++++++++++++++++ .../terraform/test/test-component/main.tf | 17 ++ .../terraform/test/test-component/outputs.tf | 9 + .../test/test-component/variables.tf | 38 +++ .../terraform/test/test-component/versions.tf | 3 + .../test/test2/test-component-2/context.tf | 279 ++++++++++++++++++ .../test/test2/test-component-2/main.tf | 17 ++ .../test/test2/test-component-2/outputs.tf | 9 + .../test/test2/test-component-2/variables.tf | 38 +++ .../test/test2/test-component-2/versions.tf | 3 + .../terraform/top-level-component1/context.tf | 279 ++++++++++++++++++ .../terraform/top-level-component1/main.tf | 16 + .../terraform/top-level-component1/outputs.tf | 9 + .../policies/policy1.rego | 5 + .../top-level-component1/variables.tf | 38 +++ .../top-level-component1/versions.tf | 3 + 42 files changed, 3300 insertions(+) create mode 100644 examples/tests/components/terraform/infra/account-map/component.yaml create mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/component.yaml create mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/context.tf create mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/main.tf create mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/outputs.tf create mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/providers.tf create mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/variables.tf create mode 100644 examples/tests/components/terraform/infra/vpc-flow-logs-bucket/versions.tf create mode 100644 examples/tests/components/terraform/infra/vpc/component.yaml create mode 100644 examples/tests/components/terraform/infra/vpc/context.tf create mode 100644 examples/tests/components/terraform/infra/vpc/main.tf create mode 100644 examples/tests/components/terraform/infra/vpc/outputs.tf create mode 100644 examples/tests/components/terraform/infra/vpc/providers.tf create mode 100644 examples/tests/components/terraform/infra/vpc/remote-state.tf create mode 100644 examples/tests/components/terraform/infra/vpc/variables.tf create mode 100644 examples/tests/components/terraform/infra/vpc/versions.tf create mode 100644 examples/tests/components/terraform/infra/vpc/vpc-flow-logs.tf create mode 100644 examples/tests/components/terraform/infra/vpc2/component.yaml create mode 100644 examples/tests/components/terraform/mixins/context.tf create mode 100644 examples/tests/components/terraform/mixins/introspection.mixin.tf create mode 100644 examples/tests/components/terraform/test/template-functions-test/context.tf create mode 100644 examples/tests/components/terraform/test/template-functions-test/main.tf create mode 100644 examples/tests/components/terraform/test/template-functions-test/outputs.tf create mode 100644 examples/tests/components/terraform/test/template-functions-test2/context.tf create mode 100644 examples/tests/components/terraform/test/template-functions-test2/outputs.tf create mode 100644 examples/tests/components/terraform/test/template-functions-test2/variables.tf create mode 100644 examples/tests/components/terraform/test/test-component/context.tf create mode 100644 examples/tests/components/terraform/test/test-component/main.tf create mode 100644 examples/tests/components/terraform/test/test-component/outputs.tf create mode 100644 examples/tests/components/terraform/test/test-component/variables.tf create mode 100644 examples/tests/components/terraform/test/test-component/versions.tf create mode 100644 examples/tests/components/terraform/test/test2/test-component-2/context.tf create mode 100644 examples/tests/components/terraform/test/test2/test-component-2/main.tf create mode 100644 examples/tests/components/terraform/test/test2/test-component-2/outputs.tf create mode 100644 examples/tests/components/terraform/test/test2/test-component-2/variables.tf create mode 100644 examples/tests/components/terraform/test/test2/test-component-2/versions.tf create mode 100644 examples/tests/components/terraform/top-level-component1/context.tf create mode 100644 examples/tests/components/terraform/top-level-component1/main.tf create mode 100644 examples/tests/components/terraform/top-level-component1/outputs.tf create mode 100644 examples/tests/components/terraform/top-level-component1/policies/policy1.rego create mode 100644 examples/tests/components/terraform/top-level-component1/variables.tf create mode 100644 examples/tests/components/terraform/top-level-component1/versions.tf diff --git a/examples/tests/components/terraform/infra/account-map/component.yaml b/examples/tests/components/terraform/infra/account-map/component.yaml new file mode 100644 index 000000000..1240d1d0d --- /dev/null +++ b/examples/tests/components/terraform/infra/account-map/component.yaml @@ -0,0 +1,40 @@ +# 'infra/account-map' component vendoring config + +apiVersion: atmos/v1 +kind: ComponentVendorConfig +metadata: + name: account-map-vendor-config + description: Source and mixins config for vendoring of 'vpc-flow-logs-bucket' component +spec: + source: + # Source 'uri' supports the following protocols: Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, + # and all URL and archive formats as described in https://github.com/hashicorp/go-getter + # In 'uri', Golang templates are supported https://pkg.go.dev/text/template + # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'uri' + uri: github.com/cloudposse/terraform-aws-components.git//modules/account-map?ref={{.Version}} + version: 1.372.0 + # Only include the files that match the 'included_paths' patterns + # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths' + # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) + # https://en.wikipedia.org/wiki/Glob_(programming) + # https://github.com/bmatcuk/doublestar#patterns + included_paths: + # include '.tf', '.tfvars' and '.md' files from the root folder + - "**/*.tf" + - "**/*.tfvars" + - "**/*.md" + # include the 'modules' folder and all sub-folders + # note that if you don't include the folders, the files in the folders will not be included + - "**/modules/**" + # include '.tf', '.tfvars' and '.md' files from the 'modules' folder and all sub-folders + - "**/modules/**/*.tf" + - "**/modules/**/*.tfvars" + - "**/modules/**/*.md" + # Exclude the files that match any of the 'excluded_paths' patterns + # Note that we are excluding 'context.tf' since a newer version of it will be downloaded using 'mixins' + # 'excluded_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) + excluded_paths: [] + + # mixins override files from 'source' with the same 'filename' (e.g. 'context.tf' will override 'context.tf' from the 'source') + # mixins are processed in the order they are declared in the list + mixins: [] diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/component.yaml b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/component.yaml new file mode 100644 index 000000000..72a7df76e --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/component.yaml @@ -0,0 +1,52 @@ +# 'infra/vpc-flow-logs-bucket' component vendoring config + +# 'component.yaml' in the component folder is processed by the 'atmos' commands +# 'atmos vendor pull -c infra/vpc-flow-logs-bucket' or 'atmos vendor pull --component infra/vpc-flow-logs-bucket' + +# > atmos vendor pull -c infra/vpc-flow-logs-bucket +# Pulling sources for the component 'infra/vpc-flow-logs-bucket' from 'github.com/cloudposse/terraform-aws-components.git//modules/vpc-flow-logs-bucket?ref=0.194.0' +# into 'examples/tests/components/terraform/infra/vpc-flow-logs-bucket' +# +# Including the file 'README.md' since it matches the '**/*.md' pattern from 'included_paths' +# Excluding the file 'context.tf' since it matches the '**/context.tf' pattern from 'excluded_paths' +# Including the file 'default.auto.tfvars' since it matches the '**/*.tfvars' pattern from 'included_paths' +# Including the file 'main.tf' since it matches the '**/*.tf' pattern from 'included_paths' +# Including the file 'outputs.tf' since it matches the '**/*.tf' pattern from 'included_paths' +# Including the file 'providers.tf' since it matches the '**/*.tf' pattern from 'included_paths' +# Including the file 'variables.tf' since it matches the '**/*.tf' pattern from 'included_paths' +# Including the file 'versions.tf' since it matches the '**/*.tf' pattern from 'included_paths' +# +# Pulling the mixin 'https://raw.githubusercontent.com/cloudposse/terraform-null-label/0.25.0/exports/context.tf' +# for the component 'infra/vpc-flow-logs-bucket' into 'examples/tests/components/terraform/infra/vpc-flow-logs-bucket' +# Pulling the mixin 'https://raw.githubusercontent.com/cloudposse/terraform-aws-components/0.194.0/modules/datadog-agent/introspection.mixin.tf' +# for the component 'infra/vpc-flow-logs-bucket' into 'examples/tests/components/terraform/infra/vpc-flow-logs-bucket' + +apiVersion: atmos/v1 +kind: ComponentVendorConfig +metadata: + name: vpc-flow-logs-bucket-vendor-config + description: Source and mixins config for vendoring of 'vpc-flow-logs-bucket' component +spec: + source: + # Source 'uri' supports the following protocols: Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, + # and all URL and archive formats as described in https://github.com/hashicorp/go-getter + # In 'uri', Golang templates are supported https://pkg.go.dev/text/template + # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'uri' + uri: github.com/cloudposse/terraform-aws-components.git//modules/vpc-flow-logs-bucket?ref={{.Version}} + version: 1.372.0 + # Only include the files that match the 'included_paths' patterns + # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths' + # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) + # https://en.wikipedia.org/wiki/Glob_(programming) + # https://github.com/bmatcuk/doublestar#patterns + included_paths: + - "**/*.tf" + # Exclude the files that match any of the 'excluded_paths' patterns + # Note that we are excluding 'context.tf' since a newer version of it will be downloaded using 'mixins' + # 'excluded_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) + excluded_paths: + - "**/context.tf" + + # mixins override files from 'source' with the same 'filename' (e.g. 'context.tf' will override 'context.tf' from the 'source') + # mixins are processed in the order they are declared in the list + mixins: [] diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/context.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/main.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/main.tf new file mode 100644 index 000000000..88eaa98fe --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/main.tf @@ -0,0 +1,18 @@ +module "flow_logs_s3_bucket" { + source = "cloudposse/vpc-flow-logs-s3-bucket/aws" + version = "1.0.1" + + lifecycle_prefix = var.lifecycle_prefix + lifecycle_tags = var.lifecycle_tags + lifecycle_rule_enabled = var.lifecycle_rule_enabled + noncurrent_version_expiration_days = var.noncurrent_version_expiration_days + noncurrent_version_transition_days = var.noncurrent_version_transition_days + standard_transition_days = var.standard_transition_days + glacier_transition_days = var.glacier_transition_days + expiration_days = var.expiration_days + traffic_type = var.traffic_type + force_destroy = var.force_destroy + flow_log_enabled = false + + context = module.this.context +} diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/outputs.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/outputs.tf new file mode 100644 index 000000000..f195f3f81 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/outputs.tf @@ -0,0 +1,9 @@ +output "vpc_flow_logs_bucket_id" { + value = module.flow_logs_s3_bucket.bucket_id + description = "VPC Flow Logs bucket ID" +} + +output "vpc_flow_logs_bucket_arn" { + value = module.flow_logs_s3_bucket.bucket_arn + description = "VPC Flow Logs bucket ARN" +} diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/providers.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/providers.tf new file mode 100644 index 000000000..ef923e10a --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/providers.tf @@ -0,0 +1,19 @@ +provider "aws" { + region = var.region + + # Profile is deprecated in favor of terraform_role_arn. When profiles are not in use, terraform_profile_name is null. + profile = module.iam_roles.terraform_profile_name + + dynamic "assume_role" { + # module.iam_roles.terraform_role_arn may be null, in which case do not assume a role. + for_each = compact([module.iam_roles.terraform_role_arn]) + content { + role_arn = assume_role.value + } + } +} + +module "iam_roles" { + source = "../account-map/modules/iam-roles" + context = module.this.context +} diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/variables.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/variables.tf new file mode 100644 index 000000000..6b87353ed --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/variables.tf @@ -0,0 +1,64 @@ +variable "region" { + type = string + description = "AWS Region" +} + +variable "lifecycle_prefix" { + type = string + description = "Prefix filter. Used to manage object lifecycle events" + default = "" +} + +variable "lifecycle_tags" { + type = map(string) + description = "Tags filter. Used to manage object lifecycle events" + default = {} +} + +variable "force_destroy" { + type = bool + description = "A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable" + default = false +} + +variable "lifecycle_rule_enabled" { + type = bool + description = "Enable lifecycle events on this bucket" + default = true +} + +variable "noncurrent_version_expiration_days" { + type = number + description = "Specifies when noncurrent object versions expire" + default = 90 +} + +variable "noncurrent_version_transition_days" { + type = number + description = "Specifies when noncurrent object versions transitions" + default = 30 +} + +variable "standard_transition_days" { + type = number + description = "Number of days to persist in the standard storage tier before moving to the infrequent access tier" + default = 30 +} + +variable "glacier_transition_days" { + type = number + description = "Number of days after which to move the data to the glacier storage tier" + default = 60 +} + +variable "expiration_days" { + type = number + description = "Number of days after which to expunge the objects" + default = 90 +} + +variable "traffic_type" { + type = string + description = "The type of traffic to capture. Valid values: `ACCEPT`, `REJECT`, `ALL`" + default = "ALL" +} diff --git a/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/versions.tf b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/versions.tf new file mode 100644 index 000000000..cc73ffd35 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc-flow-logs-bucket/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.9.0" + } + } +} diff --git a/examples/tests/components/terraform/infra/vpc/component.yaml b/examples/tests/components/terraform/infra/vpc/component.yaml new file mode 100644 index 000000000..498f75293 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/component.yaml @@ -0,0 +1,36 @@ +# 'infra/vpc' component vendoring config + +# 'component.yaml' in the component folder is processed by the 'atmos' commands +# 'atmos vendor pull -c infra/vpc' or 'atmos vendor pull --component infra/vpc' + +apiVersion: atmos/v1 +kind: ComponentVendorConfig +metadata: + name: vpc-vendor-config + description: Source and mixins config for vendoring of 'vpc' component +spec: + source: + # Source 'uri' supports the following protocols: Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, + # and all URL and archive formats as described in https://github.com/hashicorp/go-getter + # In 'uri', Golang templates are supported https://pkg.go.dev/text/template + # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'uri' + uri: github.com/cloudposse/terraform-aws-components.git//modules/vpc?ref={{.Version}} + version: 1.372.0 + # Only include the files that match the 'included_paths' patterns + # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths' + # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) + # https://en.wikipedia.org/wiki/Glob_(programming) + # https://github.com/bmatcuk/doublestar#patterns + included_paths: + - "**/*.tf" + - "**/*.tfvars" + + # mixins override files from 'source' with the same 'filename' (e.g. 'context.tf' will override 'context.tf' from the 'source') + # mixins are processed in the order they are declared in the list + mixins: + # https://github.com/hashicorp/go-getter/issues/98 + # Mixins 'uri' supports the following protocols: local files (absolute and relative paths), Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP + # - uri: https://raw.githubusercontent.com/cloudposse/terraform-null-label/0.25.0/exports/context.tf + # This mixin `uri` is relative to the current `vpc` folder + - uri: ../../mixins/context.tf + filename: context.tf diff --git a/examples/tests/components/terraform/infra/vpc/context.tf b/examples/tests/components/terraform/infra/vpc/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/infra/vpc/main.tf b/examples/tests/components/terraform/infra/vpc/main.tf new file mode 100644 index 000000000..dde5622f2 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/main.tf @@ -0,0 +1,173 @@ +locals { + enabled = module.this.enabled + + nat_eip_aws_shield_protection_enabled = local.enabled && var.nat_eip_aws_shield_protection_enabled + vpc_flow_logs_enabled = local.enabled && var.vpc_flow_logs_enabled + + # The usage of specific kubernetes.io/cluster/* resource tags were required before Kubernetes 1.19, + # but are now deprecated. See https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html + + max_subnet_count = ( + var.max_subnet_count > 0 ? var.max_subnet_count : ( + length(var.availability_zone_ids) > 0 ? length(var.availability_zone_ids) : length(var.availability_zones) + ) + ) + + availability_zones = length(var.availability_zones) > 0 ? ( + (substr( + var.availability_zones[0], + 0, + length(var.region) + ) == var.region) ? var.availability_zones : formatlist("${var.region}%s", var.availability_zones) + ) : var.availability_zones + + short_region = module.utils.region_az_alt_code_maps["to_short"][var.region] + + availability_zone_ids = length(var.availability_zone_ids) > 0 ? ( + (substr( + var.availability_zone_ids[0], + 0, + length(local.short_region) + ) == local.short_region) ? var.availability_zone_ids : formatlist("${local.short_region}%s", var.availability_zone_ids) + ) : var.availability_zone_ids + + # required tags to make ALB ingress work https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html + # https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html + public_subnets_additional_tags = { + (var.subnet_type_tag_key) = "public", + "kubernetes.io/role/elb" = 1, + } + + private_subnets_additional_tags = { + (var.subnet_type_tag_key) = "private", + "kubernetes.io/role/internal-elb" = 1, + } + + gateway_endpoint_map = { for v in var.gateway_vpc_endpoints : v => { + name = v + policy = null + route_table_ids = module.subnets.private_route_table_ids + } } + + # If we use a separate security group for each endpoint interface, + # we will use the interface service name as the key: security_group_ids = [module.endpoint_security_groups[v].id] + # If we use a single security group for all endpoint interfaces, + # we will use local.interface_endpoint_security_group_key as the key. + interface_endpoint_security_group_key = "VPC Endpoint interfaces" + + interface_endpoint_map = { for v in var.interface_vpc_endpoints : v => { + name = v + policy = null + private_dns_enabled = true # Allow applications to use normal service DNS names to access the service + security_group_ids = [module.endpoint_security_groups[local.interface_endpoint_security_group_key].id] + subnet_ids = module.subnets.private_subnet_ids + } } +} + +module "utils" { + source = "cloudposse/utils/aws" + version = "1.4.0" +} + +module "vpc" { + source = "cloudposse/vpc/aws" + version = "2.1.1" + + ipv4_primary_cidr_block = var.ipv4_primary_cidr_block + internet_gateway_enabled = var.public_subnets_enabled + assign_generated_ipv6_cidr_block = var.assign_generated_ipv6_cidr_block + + # Required for DNS resolution of VPC Endpoint interfaces, and generally harmless + # See https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html#vpc-dns-support + dns_hostnames_enabled = true + dns_support_enabled = true + + context = module.this.context +} + +# We could create a security group per endpoint, +# but until we are ready to customize them by service, it is just a waste +# of resources. We use a single security group for all endpoints. +# Security groups can be updated without recreating the endpoint or +# interrupting service, so this is an easy change to make later. +module "endpoint_security_groups" { + for_each = local.enabled && try(length(var.interface_vpc_endpoints), 0) > 0 ? toset([local.interface_endpoint_security_group_key]) : [] + + source = "cloudposse/security-group/aws" + version = "2.2.0" + + create_before_destroy = true + preserve_security_group_id = false + attributes = [each.value] + vpc_id = module.vpc.vpc_id + + rules_map = { + ingress = [{ + key = "vpc_ingress" + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "-1" # allow ping + cidr_blocks = compact(concat([module.vpc.vpc_cidr_block], module.vpc.additional_cidr_blocks)) + ipv6_cidr_blocks = compact(concat([module.vpc.vpc_ipv6_cidr_block], module.vpc.additional_ipv6_cidr_blocks)) + description = "Ingress from VPC to ${each.value}" + }] + } + + allow_all_egress = true + + context = module.this.context +} + +module "vpc_endpoints" { + source = "cloudposse/vpc/aws//modules/vpc-endpoints" + version = "2.1.0" + + enabled = (length(var.interface_vpc_endpoints) + length(var.gateway_vpc_endpoints)) > 0 + + vpc_id = module.vpc.vpc_id + gateway_vpc_endpoints = local.gateway_endpoint_map + interface_vpc_endpoints = local.interface_endpoint_map + + context = module.this.context +} + +module "subnets" { + source = "cloudposse/dynamic-subnets/aws" + version = "2.4.1" + + availability_zones = local.availability_zones + availability_zone_ids = local.availability_zone_ids + ipv4_cidr_block = [module.vpc.vpc_cidr_block] + ipv4_cidrs = var.ipv4_cidrs + ipv6_enabled = false + igw_id = var.public_subnets_enabled ? [module.vpc.igw_id] : [] + map_public_ip_on_launch = var.map_public_ip_on_launch + max_subnet_count = local.max_subnet_count + nat_gateway_enabled = var.nat_gateway_enabled + nat_instance_enabled = var.nat_instance_enabled + nat_instance_type = var.nat_instance_type + public_subnets_enabled = var.public_subnets_enabled + public_subnets_additional_tags = local.public_subnets_additional_tags + private_subnets_additional_tags = local.private_subnets_additional_tags + vpc_id = module.vpc.vpc_id + + context = module.this.context +} + +data "aws_caller_identity" "current" { + count = local.nat_eip_aws_shield_protection_enabled ? 1 : 0 +} + +data "aws_eip" "eip" { + for_each = local.nat_eip_aws_shield_protection_enabled ? toset(module.subnets.nat_ips) : [] + + public_ip = each.key +} + +resource "aws_shield_protection" "nat_eip_shield_protection" { + for_each = local.nat_eip_aws_shield_protection_enabled ? data.aws_eip.eip : {} + + name = data.aws_eip.eip[each.key].id + resource_arn = "arn:aws:ec2:${var.region}:${data.aws_caller_identity.current[0].account_id}:eip-allocation/${data.aws_eip.eip[each.key].id}" +} diff --git a/examples/tests/components/terraform/infra/vpc/outputs.tf b/examples/tests/components/terraform/infra/vpc/outputs.tf new file mode 100644 index 000000000..3a98630a9 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/outputs.tf @@ -0,0 +1,109 @@ +output "public_subnet_ids" { + value = module.subnets.public_subnet_ids + description = "Public subnet IDs" +} + +output "public_subnet_cidrs" { + value = module.subnets.public_subnet_cidrs + description = "Public subnet CIDRs" +} + +output "private_subnet_ids" { + value = module.subnets.private_subnet_ids + description = "Private subnet IDs" +} + +output "private_subnet_cidrs" { + value = module.subnets.private_subnet_cidrs + description = "Private subnet CIDRs" +} + +output "subnets" { + value = { + public : { + ids : module.subnets.public_subnet_ids + cidr : module.subnets.public_subnet_cidrs + } + private : { + ids : module.subnets.private_subnet_ids + cidr : module.subnets.private_subnet_cidrs + } + } + description = "Subnets info map" +} + +output "vpc_default_network_acl_id" { + value = module.vpc.vpc_default_network_acl_id + description = "The ID of the network ACL created by default on VPC creation" +} + +output "vpc_default_security_group_id" { + value = module.vpc.vpc_default_security_group_id + description = "The ID of the security group created by default on VPC creation" +} + +output "vpc_id" { + value = module.vpc.vpc_id + description = "VPC ID" +} + +output "vpc_cidr" { + value = module.vpc.vpc_cidr_block + description = "VPC CIDR" +} + +output "vpc" { + value = { + id : module.vpc.vpc_id + cidr : module.vpc.vpc_cidr_block + subnet_type_tag_key : var.subnet_type_tag_key + } + description = "VPC info map" +} + +output "private_route_table_ids" { + value = module.subnets.private_route_table_ids + description = "Private subnet route table IDs" +} + +output "public_route_table_ids" { + value = module.subnets.public_route_table_ids + description = "Public subnet route table IDs" +} + +output "route_tables" { + value = { + public : { + ids : module.subnets.public_route_table_ids + } + private : { + ids : module.subnets.private_route_table_ids + } + } + description = "Route tables info map" +} + +output "nat_gateway_ids" { + value = module.subnets.nat_gateway_ids + description = "NAT Gateway IDs" +} + +output "nat_instance_ids" { + value = module.subnets.nat_instance_ids + description = "NAT Instance IDs" +} + +output "nat_gateway_public_ips" { + value = module.subnets.nat_gateway_public_ips + description = "NAT Gateway public IPs" +} + +output "max_subnet_count" { + value = local.max_subnet_count + description = "Maximum allowed number of subnets before all subnet CIDRs need to be recomputed" +} + +output "availability_zones" { + description = "List of Availability Zones where subnets were created" + value = local.availability_zones +} diff --git a/examples/tests/components/terraform/infra/vpc/providers.tf b/examples/tests/components/terraform/infra/vpc/providers.tf new file mode 100644 index 000000000..dc58d9a25 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/providers.tf @@ -0,0 +1,3 @@ +provider "aws" { + region = var.region +} diff --git a/examples/tests/components/terraform/infra/vpc/remote-state.tf b/examples/tests/components/terraform/infra/vpc/remote-state.tf new file mode 100644 index 000000000..e785ec5b1 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/remote-state.tf @@ -0,0 +1,20 @@ +module "vpc_flow_logs_bucket" { + count = var.vpc_flow_logs_enabled ? 1 : 0 + + source = "cloudposse/stack-config/yaml//modules/remote-state" + version = "1.5.0" + + # Specify the Atmos component name (defined in YAML stack config files) + # for which to get the remote state outputs + component = var.vpc_flow_logs_bucket_component_name + + # Override the context variables to point to a different Atmos stack if the + # `vpc-flow-logs-bucket` Atmos component is provisioned in another AWS account, OU or region + stage = try(coalesce(var.vpc_flow_logs_bucket_stage_name, module.this.stage), null) + environment = try(coalesce(var.vpc_flow_logs_bucket_environment_name, module.this.environment), null) + tenant = try(coalesce(var.vpc_flow_logs_bucket_tenant_name, module.this.tenant), null) + + # `context` input is a way to provide the information about the stack (using the context + # variables `namespace`, `tenant`, `environment`, and `stage` defined in the stack config) + context = module.this.context +} diff --git a/examples/tests/components/terraform/infra/vpc/variables.tf b/examples/tests/components/terraform/infra/vpc/variables.tf new file mode 100644 index 000000000..ff1376f6c --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/variables.tf @@ -0,0 +1,165 @@ +variable "region" { + type = string + description = "AWS Region" +} + +variable "availability_zones" { + type = list(string) + description = <<-EOT + List of Availability Zones (AZs) where subnets will be created. Ignored when `availability_zone_ids` is set. + The order of zones in the list ***must be stable*** or else Terraform will continually make changes. + If no AZs are specified, then `max_subnet_count` AZs will be selected in alphabetical order. + If `max_subnet_count > 0` and `length(var.availability_zones) > max_subnet_count`, the list + will be truncated. We recommend setting `availability_zones` and `max_subnet_count` explicitly as constant + (not computed) values for predictability, consistency, and stability. + EOT + default = [] +} + +variable "availability_zone_ids" { + type = list(string) + description = <<-EOT + List of Availability Zones IDs where subnets will be created. Overrides `availability_zones`. + Useful in some regions when using only some AZs and you want to use the same ones across multiple accounts. + EOT + default = [] +} + +variable "ipv4_primary_cidr_block" { + type = string + description = <<-EOT + The primary IPv4 CIDR block for the VPC. + Either `ipv4_primary_cidr_block` or `ipv4_primary_cidr_block_association` must be set, but not both. + EOT + default = null +} + +variable "ipv4_cidrs" { + type = list(object({ + private = list(string) + public = list(string) + })) + description = <<-EOT + Lists of CIDRs to assign to subnets. Order of CIDRs in the lists must not change over time. + Lists may contain more CIDRs than needed. + EOT + default = [] + validation { + condition = length(var.ipv4_cidrs) < 2 + error_message = "Only 1 ipv4_cidrs object can be provided. Lists of CIDRs are passed via the `public` and `private` attributes of the single object." + } +} + +variable "assign_generated_ipv6_cidr_block" { + type = bool + description = "When `true`, assign AWS generated IPv6 CIDR block to the VPC. Conflicts with `ipv6_ipam_pool_id`." + default = false +} + +variable "public_subnets_enabled" { + type = bool + description = <<-EOT + If false, do not create public subnets. + Since NAT gateways and instances must be created in public subnets, these will also not be created when `false`. + EOT + default = true +} + +variable "nat_gateway_enabled" { + type = bool + description = "Flag to enable/disable NAT gateways" + default = true +} + +variable "nat_instance_enabled" { + type = bool + description = "Flag to enable/disable NAT instances" + default = false +} + +variable "nat_instance_type" { + type = string + description = "NAT Instance type" + default = "t3.micro" +} + +variable "map_public_ip_on_launch" { + type = bool + default = true + description = "Instances launched into a public subnet should be assigned a public IP address" +} + +variable "subnet_type_tag_key" { + type = string + description = "Key for subnet type tag to provide information about the type of subnets, e.g. `cpco/subnet/type=private` or `cpcp/subnet/type=public`" +} + +variable "max_subnet_count" { + type = number + default = 0 + description = "Sets the maximum amount of subnets to deploy. 0 will deploy a subnet for every provided availability zone (in `region_availability_zones` variable) within the region" +} + +variable "vpc_flow_logs_enabled" { + type = bool + description = "Enable or disable the VPC Flow Logs" + default = true +} + +variable "vpc_flow_logs_traffic_type" { + type = string + description = "The type of traffic to capture. Valid values: `ACCEPT`, `REJECT`, `ALL`" + default = "ALL" +} + +variable "vpc_flow_logs_log_destination_type" { + type = string + description = "The type of the logging destination. Valid values: `cloud-watch-logs`, `s3`" + default = "s3" +} + +variable "vpc_flow_logs_bucket_component_name" { + type = string + description = "The name of the VPC Flow Logs bucket component" + default = "vpc-flow-logs-bucket" +} + +variable "vpc_flow_logs_bucket_environment_name" { + type = string + description = "The name of the environment where the VPC Flow Logs bucket is provisioned" + default = "" +} + +variable "vpc_flow_logs_bucket_stage_name" { + type = string + description = "The stage (account) name where the VPC Flow Logs bucket is provisioned" + default = "" +} + +variable "vpc_flow_logs_bucket_tenant_name" { + type = string + description = <<-EOT + The name of the tenant where the VPC Flow Logs bucket is provisioned. + + If the `tenant` label is not used, leave this as `null`. + EOT + default = null +} + +variable "nat_eip_aws_shield_protection_enabled" { + type = bool + description = "Enable or disable AWS Shield Advanced protection for NAT EIPs. If set to 'true', a subscription to AWS Shield Advanced must exist in this account." + default = false +} + +variable "gateway_vpc_endpoints" { + type = set(string) + description = "A list of Gateway VPC Endpoints to provision into the VPC. Only valid values are \"dynamodb\" and \"s3\"." + default = [] +} + +variable "interface_vpc_endpoints" { + type = set(string) + description = "A list of Interface VPC Endpoints to provision into the VPC." + default = [] +} diff --git a/examples/tests/components/terraform/infra/vpc/versions.tf b/examples/tests/components/terraform/infra/vpc/versions.tf new file mode 100644 index 000000000..08c4806a4 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.9.0" + } + utils = { + source = "cloudposse/utils" + version = ">= 1.18.0" + } + } +} diff --git a/examples/tests/components/terraform/infra/vpc/vpc-flow-logs.tf b/examples/tests/components/terraform/infra/vpc/vpc-flow-logs.tf new file mode 100644 index 000000000..51bfe7b37 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc/vpc-flow-logs.tf @@ -0,0 +1,15 @@ +# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/flow_log +# https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html + +resource "aws_flow_log" "default" { + count = local.vpc_flow_logs_enabled ? 1 : 0 + + # Use the remote state output `vpc_flow_logs_bucket_arn` of the `vpc_flow_logs_bucket` component + log_destination = module.vpc_flow_logs_bucket[0].outputs.vpc_flow_logs_bucket_arn + + log_destination_type = var.vpc_flow_logs_log_destination_type + traffic_type = var.vpc_flow_logs_traffic_type + vpc_id = module.vpc.vpc_id + + tags = module.this.tags +} diff --git a/examples/tests/components/terraform/infra/vpc2/component.yaml b/examples/tests/components/terraform/infra/vpc2/component.yaml new file mode 100644 index 000000000..4a46b4ca3 --- /dev/null +++ b/examples/tests/components/terraform/infra/vpc2/component.yaml @@ -0,0 +1,26 @@ +# This is an example of how to download a Terraform component from an OCI registry (https://opencontainers.org), e.g. AWS Public ECR + +# 'component.yaml' in the component folder is processed by the 'atmos' commands +# 'atmos vendor pull -c infra/vpc2' or 'atmos vendor pull --component infra/vpc2' + +apiVersion: atmos/v1 +kind: ComponentVendorConfig +metadata: + name: stable/aws/vpc + description: Config for vendoring of 'stable/aws/vpc' component +spec: + source: + # Source 'uri' supports the following protocols: OCI (https://opencontainers.org), Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, + # and all URL and archive formats as described in https://github.com/hashicorp/go-getter + # In 'uri', Golang templates are supported https://pkg.go.dev/text/template + # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'uri' + # Download the component from the AWS public ECR registry (https://docs.aws.amazon.com/AmazonECR/latest/public/public-registries.html) + uri: "oci://public.ecr.aws/cloudposse/components/terraform/stable/aws/vpc:{{.Version}}" + version: "latest" + # Only include the files that match the 'included_paths' patterns + # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths' + # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported) + # https://en.wikipedia.org/wiki/Glob_(programming) + # https://github.com/bmatcuk/doublestar#patterns + included_paths: + - "**/*.*" diff --git a/examples/tests/components/terraform/mixins/context.tf b/examples/tests/components/terraform/mixins/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/examples/tests/components/terraform/mixins/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/mixins/introspection.mixin.tf b/examples/tests/components/terraform/mixins/introspection.mixin.tf new file mode 100644 index 000000000..3af29f58d --- /dev/null +++ b/examples/tests/components/terraform/mixins/introspection.mixin.tf @@ -0,0 +1,34 @@ +# This mixin is meant to be added to Terraform components in order to append a `Component` tag to all resources in the +# configuration, specifying which component the resources belong to. +# +# It's important to note that all modules and resources within the component then need to use `module.introspection.context` +# and `module.introspection.tags`, respectively, rather than `module.this.context` and `module.this.tags`. +# + +locals { + # Throw an error if lookup fails + check_required_tags = module.this.enabled ? [ + for k in var.required_tags : lookup(module.this.tags, k) + ] : [] +} + +variable "required_tags" { + type = list(string) + description = "List of required tag names" + default = [] +} + +# `introspection` module will contain the additional tags +module "introspection" { + source = "cloudposse/label/null" + version = "0.25.0" + + tags = merge( + var.tags, + { + "Component" = basename(abspath(path.module)) + } + ) + + context = module.this.context +} diff --git a/examples/tests/components/terraform/test/template-functions-test/context.tf b/examples/tests/components/terraform/test/template-functions-test/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/examples/tests/components/terraform/test/template-functions-test/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/test/template-functions-test/main.tf b/examples/tests/components/terraform/test/template-functions-test/main.tf new file mode 100644 index 000000000..3d0c15f53 --- /dev/null +++ b/examples/tests/components/terraform/test/template-functions-test/main.tf @@ -0,0 +1,6 @@ +module "test_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + context = module.this.context +} diff --git a/examples/tests/components/terraform/test/template-functions-test/outputs.tf b/examples/tests/components/terraform/test/template-functions-test/outputs.tf new file mode 100644 index 000000000..2af5e9cef --- /dev/null +++ b/examples/tests/components/terraform/test/template-functions-test/outputs.tf @@ -0,0 +1,22 @@ +output "test_label_id" { + value = module.test_label.id + description = "Test label ID" +} + +output "test_list" { + value = [ + "list_item_1", + "list_item_2", + "list_item_3" + ] + description = "Test list" +} + +output "test_map" { + value = { + a = 1, + b = 2, + c = 3 + } + description = "Test map" +} diff --git a/examples/tests/components/terraform/test/template-functions-test2/context.tf b/examples/tests/components/terraform/test/template-functions-test2/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/examples/tests/components/terraform/test/template-functions-test2/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/test/template-functions-test2/outputs.tf b/examples/tests/components/terraform/test/template-functions-test2/outputs.tf new file mode 100644 index 000000000..76c897196 --- /dev/null +++ b/examples/tests/components/terraform/test/template-functions-test2/outputs.tf @@ -0,0 +1,14 @@ +output "region" { + value = var.region + description = "Test label ID" +} + +output "test_label_id" { + value = var.test_label_id + description = "Test label ID" +} + +output "test_label_id_2" { + value = var.test_label_id_2 + description = "Test label ID 2" +} diff --git a/examples/tests/components/terraform/test/template-functions-test2/variables.tf b/examples/tests/components/terraform/test/template-functions-test2/variables.tf new file mode 100644 index 000000000..cfbc1e4f9 --- /dev/null +++ b/examples/tests/components/terraform/test/template-functions-test2/variables.tf @@ -0,0 +1,14 @@ +variable "region" { + type = string + description = "Region" +} + +variable "test_label_id" { + type = string + description = "Test label ID" +} + +variable "test_label_id_2" { + type = string + description = "Test label ID 2" +} diff --git a/examples/tests/components/terraform/test/test-component/context.tf b/examples/tests/components/terraform/test/test-component/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/examples/tests/components/terraform/test/test-component/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/test/test-component/main.tf b/examples/tests/components/terraform/test/test-component/main.tf new file mode 100644 index 000000000..6ad57234b --- /dev/null +++ b/examples/tests/components/terraform/test/test-component/main.tf @@ -0,0 +1,17 @@ +module "service_1_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + name = var.service_1_name + + context = module.this.context +} + +module "service_2_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + name = var.service_2_name + + context = module.this.context +} diff --git a/examples/tests/components/terraform/test/test-component/outputs.tf b/examples/tests/components/terraform/test/test-component/outputs.tf new file mode 100644 index 000000000..d87b49b2d --- /dev/null +++ b/examples/tests/components/terraform/test/test-component/outputs.tf @@ -0,0 +1,9 @@ +output "service_1_id" { + value = module.service_1_label.id + description = "Service 1 ID" +} + +output "service_2_id" { + value = module.service_2_label.id + description = "Service 2 ID" +} diff --git a/examples/tests/components/terraform/test/test-component/variables.tf b/examples/tests/components/terraform/test/test-component/variables.tf new file mode 100644 index 000000000..90c60dc3a --- /dev/null +++ b/examples/tests/components/terraform/test/test-component/variables.tf @@ -0,0 +1,38 @@ +variable "region" { + type = string + description = "Region" +} + +variable "service_1_name" { + type = string + description = "Service 1 name" +} + +variable "service_1_list" { + type = list(string) + description = "Service 1 list" + default = [] +} + +variable "service_1_map" { + type = map(string) + description = "Service 1 map" + default = {} +} + +variable "service_2_name" { + type = string + description = "Service 2 name" +} + +variable "service_2_list" { + type = list(string) + description = "Service 2 list" + default = [] +} + +variable "service_2_map" { + type = map(string) + description = "Service 2 map" + default = {} +} diff --git a/examples/tests/components/terraform/test/test-component/versions.tf b/examples/tests/components/terraform/test/test-component/versions.tf new file mode 100644 index 000000000..429c0b36d --- /dev/null +++ b/examples/tests/components/terraform/test/test-component/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 1.0.0" +} diff --git a/examples/tests/components/terraform/test/test2/test-component-2/context.tf b/examples/tests/components/terraform/test/test2/test-component-2/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/examples/tests/components/terraform/test/test2/test-component-2/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/test/test2/test-component-2/main.tf b/examples/tests/components/terraform/test/test2/test-component-2/main.tf new file mode 100644 index 000000000..6ad57234b --- /dev/null +++ b/examples/tests/components/terraform/test/test2/test-component-2/main.tf @@ -0,0 +1,17 @@ +module "service_1_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + name = var.service_1_name + + context = module.this.context +} + +module "service_2_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + name = var.service_2_name + + context = module.this.context +} diff --git a/examples/tests/components/terraform/test/test2/test-component-2/outputs.tf b/examples/tests/components/terraform/test/test2/test-component-2/outputs.tf new file mode 100644 index 000000000..d87b49b2d --- /dev/null +++ b/examples/tests/components/terraform/test/test2/test-component-2/outputs.tf @@ -0,0 +1,9 @@ +output "service_1_id" { + value = module.service_1_label.id + description = "Service 1 ID" +} + +output "service_2_id" { + value = module.service_2_label.id + description = "Service 2 ID" +} diff --git a/examples/tests/components/terraform/test/test2/test-component-2/variables.tf b/examples/tests/components/terraform/test/test2/test-component-2/variables.tf new file mode 100644 index 000000000..90c60dc3a --- /dev/null +++ b/examples/tests/components/terraform/test/test2/test-component-2/variables.tf @@ -0,0 +1,38 @@ +variable "region" { + type = string + description = "Region" +} + +variable "service_1_name" { + type = string + description = "Service 1 name" +} + +variable "service_1_list" { + type = list(string) + description = "Service 1 list" + default = [] +} + +variable "service_1_map" { + type = map(string) + description = "Service 1 map" + default = {} +} + +variable "service_2_name" { + type = string + description = "Service 2 name" +} + +variable "service_2_list" { + type = list(string) + description = "Service 2 list" + default = [] +} + +variable "service_2_map" { + type = map(string) + description = "Service 2 map" + default = {} +} diff --git a/examples/tests/components/terraform/test/test2/test-component-2/versions.tf b/examples/tests/components/terraform/test/test2/test-component-2/versions.tf new file mode 100644 index 000000000..429c0b36d --- /dev/null +++ b/examples/tests/components/terraform/test/test2/test-component-2/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 1.0.0" +} diff --git a/examples/tests/components/terraform/top-level-component1/context.tf b/examples/tests/components/terraform/top-level-component1/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/examples/tests/components/terraform/top-level-component1/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/examples/tests/components/terraform/top-level-component1/main.tf b/examples/tests/components/terraform/top-level-component1/main.tf new file mode 100644 index 000000000..3496a4e33 --- /dev/null +++ b/examples/tests/components/terraform/top-level-component1/main.tf @@ -0,0 +1,16 @@ +module "service_1_label" { + source = "cloudposse/label/null" + version = "0.25.0" + + name = var.service_1_name + + context = module.this.context +} + +module "service_2_label" { + source = "../../../modules/label" + + name = var.service_2_name + + context = module.this.context +} diff --git a/examples/tests/components/terraform/top-level-component1/outputs.tf b/examples/tests/components/terraform/top-level-component1/outputs.tf new file mode 100644 index 000000000..ae890ae82 --- /dev/null +++ b/examples/tests/components/terraform/top-level-component1/outputs.tf @@ -0,0 +1,9 @@ +output "service_1_id" { + value = module.service_1_label.id + description = "Service 1 ID" +} + +output "service_2_id" { + value = module.service_2_label.label.id + description = "Service 2 ID" +} diff --git a/examples/tests/components/terraform/top-level-component1/policies/policy1.rego b/examples/tests/components/terraform/top-level-component1/policies/policy1.rego new file mode 100644 index 000000000..6fa6fec89 --- /dev/null +++ b/examples/tests/components/terraform/top-level-component1/policies/policy1.rego @@ -0,0 +1,5 @@ +package atmos + +allow { + true +} diff --git a/examples/tests/components/terraform/top-level-component1/variables.tf b/examples/tests/components/terraform/top-level-component1/variables.tf new file mode 100644 index 000000000..90c60dc3a --- /dev/null +++ b/examples/tests/components/terraform/top-level-component1/variables.tf @@ -0,0 +1,38 @@ +variable "region" { + type = string + description = "Region" +} + +variable "service_1_name" { + type = string + description = "Service 1 name" +} + +variable "service_1_list" { + type = list(string) + description = "Service 1 list" + default = [] +} + +variable "service_1_map" { + type = map(string) + description = "Service 1 map" + default = {} +} + +variable "service_2_name" { + type = string + description = "Service 2 name" +} + +variable "service_2_list" { + type = list(string) + description = "Service 2 list" + default = [] +} + +variable "service_2_map" { + type = map(string) + description = "Service 2 map" + default = {} +} diff --git a/examples/tests/components/terraform/top-level-component1/versions.tf b/examples/tests/components/terraform/top-level-component1/versions.tf new file mode 100644 index 000000000..429c0b36d --- /dev/null +++ b/examples/tests/components/terraform/top-level-component1/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 1.0.0" +} From 016c3cf315c281fc6eb5306f6b2d87f837d74e7f Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Mon, 30 Dec 2024 16:34:33 +0000 Subject: [PATCH 20/49] updates for making styles dynamic set --- pkg/schema/schema.go | 59 ++++- pkg/ui/markdown/renderer.go | 8 +- pkg/ui/markdown/styles.go | 414 +++++++++++++++++++++++------------- 3 files changed, 331 insertions(+), 150 deletions(-) diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 668a39b90..2713a76a4 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -38,8 +38,9 @@ type AtmosConfiguration struct { } type AtmosSettings struct { - ListMergeStrategy string `yaml:"list_merge_strategy" json:"list_merge_strategy" mapstructure:"list_merge_strategy"` - Docs Docs `yaml:"docs,omitempty" json:"docs,omitempty" mapstructure:"docs"` + ListMergeStrategy string `yaml:"list_merge_strategy" json:"list_merge_strategy" mapstructure:"list_merge_strategy"` + Docs Docs `yaml:"docs,omitempty" json:"docs,omitempty" mapstructure:"docs"` + Markdown MarkdownSettings `yaml:"markdown,omitempty" json:"markdown,omitempty" mapstructure:"markdown"` } type Docs struct { @@ -590,3 +591,57 @@ type Vendor struct { // If a directory is specified, all .yaml files in the directory will be processed in lexicographical order BasePath string `yaml:"base_path" json:"base_path" mapstructure:"base_path"` } + +type MarkdownSettings struct { + Document MarkdownStyle `yaml:"document,omitempty" json:"document,omitempty" mapstructure:"document"` + BlockQuote MarkdownStyle `yaml:"block_quote,omitempty" json:"block_quote,omitempty" mapstructure:"block_quote"` + Paragraph MarkdownStyle `yaml:"paragraph,omitempty" json:"paragraph,omitempty" mapstructure:"paragraph"` + List MarkdownStyle `yaml:"list,omitempty" json:"list,omitempty" mapstructure:"list"` + ListItem MarkdownStyle `yaml:"list_item,omitempty" json:"list_item,omitempty" mapstructure:"list_item"` + Heading MarkdownStyle `yaml:"heading,omitempty" json:"heading,omitempty" mapstructure:"heading"` + H1 MarkdownStyle `yaml:"h1,omitempty" json:"h1,omitempty" mapstructure:"h1"` + H2 MarkdownStyle `yaml:"h2,omitempty" json:"h2,omitempty" mapstructure:"h2"` + H3 MarkdownStyle `yaml:"h3,omitempty" json:"h3,omitempty" mapstructure:"h3"` + H4 MarkdownStyle `yaml:"h4,omitempty" json:"h4,omitempty" mapstructure:"h4"` + H5 MarkdownStyle `yaml:"h5,omitempty" json:"h5,omitempty" mapstructure:"h5"` + H6 MarkdownStyle `yaml:"h6,omitempty" json:"h6,omitempty" mapstructure:"h6"` + Text MarkdownStyle `yaml:"text,omitempty" json:"text,omitempty" mapstructure:"text"` + Strong MarkdownStyle `yaml:"strong,omitempty" json:"strong,omitempty" mapstructure:"strong"` + Emph MarkdownStyle `yaml:"emph,omitempty" json:"emph,omitempty" mapstructure:"emph"` + Hr MarkdownStyle `yaml:"hr,omitempty" json:"hr,omitempty" mapstructure:"hr"` + Item MarkdownStyle `yaml:"item,omitempty" json:"item,omitempty" mapstructure:"item"` + Enumeration MarkdownStyle `yaml:"enumeration,omitempty" json:"enumeration,omitempty" mapstructure:"enumeration"` + Code MarkdownStyle `yaml:"code,omitempty" json:"code,omitempty" mapstructure:"code"` + CodeBlock MarkdownStyle `yaml:"code_block,omitempty" json:"code_block,omitempty" mapstructure:"code_block"` + Table MarkdownStyle `yaml:"table,omitempty" json:"table,omitempty" mapstructure:"table"` + DefinitionList MarkdownStyle `yaml:"definition_list,omitempty" json:"definition_list,omitempty" mapstructure:"definition_list"` + DefinitionTerm MarkdownStyle `yaml:"definition_term,omitempty" json:"definition_term,omitempty" mapstructure:"definition_term"` + DefinitionDescription MarkdownStyle `yaml:"definition_description,omitempty" json:"definition_description,omitempty" mapstructure:"definition_description"` + HtmlBlock MarkdownStyle `yaml:"html_block,omitempty" json:"html_block,omitempty" mapstructure:"html_block"` + HtmlSpan MarkdownStyle `yaml:"html_span,omitempty" json:"html_span,omitempty" mapstructure:"html_span"` + Link MarkdownStyle `yaml:"link,omitempty" json:"link,omitempty" mapstructure:"link"` + LinkText MarkdownStyle `yaml:"link_text,omitempty" json:"link_text,omitempty" mapstructure:"link_text"` +} + +type MarkdownStyle struct { + BlockPrefix string `yaml:"block_prefix,omitempty" json:"block_prefix,omitempty" mapstructure:"block_prefix"` + BlockSuffix string `yaml:"block_suffix,omitempty" json:"block_suffix,omitempty" mapstructure:"block_suffix"` + Color string `yaml:"color,omitempty" json:"color,omitempty" mapstructure:"color"` + BackgroundColor string `yaml:"background_color,omitempty" json:"background_color,omitempty" mapstructure:"background_color"` + Bold bool `yaml:"bold,omitempty" json:"bold,omitempty" mapstructure:"bold"` + Italic bool `yaml:"italic,omitempty" json:"italic,omitempty" mapstructure:"italic"` + Underline bool `yaml:"underline,omitempty" json:"underline,omitempty" mapstructure:"underline"` + Margin int `yaml:"margin,omitempty" json:"margin,omitempty" mapstructure:"margin"` + Padding int `yaml:"padding,omitempty" json:"padding,omitempty" mapstructure:"padding"` + Indent int `yaml:"indent,omitempty" json:"indent,omitempty" mapstructure:"indent"` + IndentToken string `yaml:"indent_token,omitempty" json:"indent_token,omitempty" mapstructure:"indent_token"` + LevelIndent int `yaml:"level_indent,omitempty" json:"level_indent,omitempty" mapstructure:"level_indent"` + Format string `yaml:"format,omitempty" json:"format,omitempty" mapstructure:"format"` + Prefix string `yaml:"prefix,omitempty" json:"prefix,omitempty" mapstructure:"prefix"` + StyleOverride bool `yaml:"style_override,omitempty" json:"style_override,omitempty" mapstructure:"style_override"` + Chroma map[string]ChromaStyle `yaml:"chroma,omitempty" json:"chroma,omitempty" mapstructure:"chroma"` +} + +type ChromaStyle struct { + Color string `yaml:"color,omitempty" json:"color,omitempty" mapstructure:"color"` +} diff --git a/pkg/ui/markdown/renderer.go b/pkg/ui/markdown/renderer.go index f00e4059e..1b86e7070 100644 --- a/pkg/ui/markdown/renderer.go +++ b/pkg/ui/markdown/renderer.go @@ -27,11 +27,17 @@ func NewRenderer(opts ...Option) (*Renderer, error) { opt(r) } + // Get default style + style, err := GetDefaultStyle() + if err != nil { + return nil, err + } + // Initialize glamour renderer renderer, err := glamour.NewTermRenderer( glamour.WithAutoStyle(), glamour.WithWordWrap(int(r.width)), - glamour.WithStylesFromJSONBytes(DefaultStyle), + glamour.WithStylesFromJSONBytes(style), glamour.WithColorProfile(r.profile), glamour.WithEmoji(), ) diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go index ddb905607..26750da2a 100644 --- a/pkg/ui/markdown/styles.go +++ b/pkg/ui/markdown/styles.go @@ -1,149 +1,269 @@ package markdown -// DefaultStyle defines the default Atmos markdown style -var DefaultStyle = []byte(`{ - "document": { - "block_prefix": "", - "block_suffix": "\n", - "color": "#FFFFFF", - "margin": 0 - }, - "block_quote": { - "indent": 1, - "indent_token": "│ ", - "color": "#9B51E0" - }, - "paragraph": { - "block_prefix": "", - "block_suffix": "", - "color": "#FFFFFF" - }, - "list": { - "level_indent": 4, - "color": "#FFFFFF", - "margin": 0, - "block_suffix": "" - }, - "list_item": { - "block_prefix": "– ", - "color": "#FFFFFF", - "margin": 0, - "block_suffix": "" - }, - "heading": { - "block_prefix": "", - "block_suffix": "\n", - "color": "#00A3E0", - "bold": true, - "margin": 0, - "style_override": true - }, - "h1": { - "prefix": "", - "color": "#FFFFFF", - "background_color": "#9B51E0", - "bold": true, - "margin": 2, - "block_prefix": "\n", - "block_suffix": "\n", - "padding": 1 - }, - "h2": { - "prefix": "## ", - "color": "#9B51E0", - "bold": true, - "margin": 1 - }, - "h3": { - "prefix": "### ", - "color": "#00A3E0", - "bold": true - }, - "h4": { - "prefix": "#### ", - "color": "#00A3E0", - "bold": true - }, - "h5": { - "prefix": "##### ", - "color": "#00A3E0", - "bold": true - }, - "h6": { - "prefix": "###### ", - "color": "#00A3E0", - "bold": true - }, - "text": { - "color": "#FFFFFF" - }, - "strong": { - "color": "#9B51E0", - "bold": true - }, - "emph": { - "color": "#9B51E0", - "italic": true - }, - "hr": { - "color": "#9B51E0", - "format": "\n--------\n" - }, - "item": { - "block_prefix": "• " - }, - "enumeration": { - "block_prefix": ". " - }, - "code": { - "color": "#9B51E0" - }, - "code_block": { - "margin": 1, - "indent": 2, - "block_suffix": "", - "chroma": { - "text": { - "color": "#00A3E0" - }, - "keyword": { - "color": "#9B51E0" - }, - "literal": { - "color": "#00A3E0" - }, - "string": { - "color": "#00A3E0" - }, - "name": { - "color": "#00A3E0" - }, - "number": { - "color": "#00A3E0" - }, - "comment": { - "color": "#9B51E0" - } - } - }, - "table": { - "center_separator": "┼", - "column_separator": "│", - "row_separator": "─" - }, - "definition_list": {}, - "definition_term": {}, - "definition_description": { - "block_prefix": "\n" - }, - "html_block": {}, - "html_span": {}, - "link": { - "color": "#00A3E0", - "underline": true - }, - "link_text": { - "color": "#9B51E0", - "bold": true - } -}`) +import ( + "encoding/json" + + "github.com/charmbracelet/glamour/ansi" + "github.com/cloudposse/atmos/pkg/config" + "github.com/cloudposse/atmos/pkg/schema" +) + +// GetDefaultStyle returns the markdown style configuration from atmos.yaml settings +// or falls back to built-in defaults if not configured +func GetDefaultStyle() ([]byte, error) { + atmosConfig, err := config.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + if err != nil { + return getBuiltinDefaultStyle() + } + + // Get the built-in default style + defaultBytes, err := getBuiltinDefaultStyle() + if err != nil { + return nil, err + } + + if atmosConfig.Settings.Markdown.Document.Color == "" && + atmosConfig.Settings.Markdown.Heading.Color == "" && + atmosConfig.Settings.Markdown.H1.Color == "" && + atmosConfig.Settings.Markdown.H2.Color == "" && + atmosConfig.Settings.Markdown.H3.Color == "" { + return defaultBytes, nil + } + + var style ansi.StyleConfig + if err := json.Unmarshal(defaultBytes, &style); err != nil { + return nil, err + } + + // Apply custom styles on top of defaults + if atmosConfig.Settings.Markdown.Document.Color != "" { + style.Document.Color = &atmosConfig.Settings.Markdown.Document.Color + } + + if atmosConfig.Settings.Markdown.Heading.Color != "" { + style.Heading.Color = &atmosConfig.Settings.Markdown.Heading.Color + style.Heading.Bold = &atmosConfig.Settings.Markdown.Heading.Bold + } + + if atmosConfig.Settings.Markdown.H1.Color != "" { + style.H1.Color = &atmosConfig.Settings.Markdown.H1.Color + if atmosConfig.Settings.Markdown.H1.BackgroundColor != "" { + style.H1.BackgroundColor = &atmosConfig.Settings.Markdown.H1.BackgroundColor + } + style.H1.Bold = &atmosConfig.Settings.Markdown.H1.Bold + style.H1.Margin = uintPtr(uint(atmosConfig.Settings.Markdown.H1.Margin)) + } + + if atmosConfig.Settings.Markdown.H2.Color != "" { + style.H2.Color = &atmosConfig.Settings.Markdown.H2.Color + style.H2.Bold = &atmosConfig.Settings.Markdown.H2.Bold + } + + if atmosConfig.Settings.Markdown.H3.Color != "" { + style.H3.Color = &atmosConfig.Settings.Markdown.H3.Color + style.H3.Bold = &atmosConfig.Settings.Markdown.H3.Bold + } + + if atmosConfig.Settings.Markdown.CodeBlock.Color != "" { + if style.CodeBlock.StyleBlock.StylePrimitive.Color == nil { + style.CodeBlock.StyleBlock.StylePrimitive.Color = &atmosConfig.Settings.Markdown.CodeBlock.Color + } else { + *style.CodeBlock.StyleBlock.StylePrimitive.Color = atmosConfig.Settings.Markdown.CodeBlock.Color + } + style.CodeBlock.Margin = uintPtr(uint(atmosConfig.Settings.Markdown.CodeBlock.Margin)) + } + + if atmosConfig.Settings.Markdown.Link.Color != "" { + style.Link.Color = &atmosConfig.Settings.Markdown.Link.Color + style.Link.Underline = &atmosConfig.Settings.Markdown.Link.Underline + } + + if atmosConfig.Settings.Markdown.Strong.Color != "" { + style.Strong.Color = &atmosConfig.Settings.Markdown.Strong.Color + style.Strong.Bold = &atmosConfig.Settings.Markdown.Strong.Bold + } + + if atmosConfig.Settings.Markdown.Emph.Color != "" { + style.Emph.Color = &atmosConfig.Settings.Markdown.Emph.Color + style.Emph.Italic = &atmosConfig.Settings.Markdown.Emph.Italic + } + + return json.Marshal(style) +} + +// this only returns the built-in default style configuration +func getBuiltinDefaultStyle() ([]byte, error) { + style := ansi.StyleConfig{ + Document: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockPrefix: "", + BlockSuffix: "\n", + Color: stringPtr("#FFFFFF"), + }, + Margin: uintPtr(0), + }, + BlockQuote: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#9B51E0"), + }, + Indent: uintPtr(1), + IndentToken: stringPtr("│ "), + }, + Paragraph: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockPrefix: "", + BlockSuffix: "", + Color: stringPtr("#FFFFFF"), + }, + }, + List: ansi.StyleList{ + LevelIndent: 4, + }, + Heading: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockPrefix: "", + BlockSuffix: "\n", + Color: stringPtr("#00A3E0"), + Bold: boolPtr(true), + }, + Margin: uintPtr(0), + }, + H1: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "", + Color: stringPtr("#FFFFFF"), + BackgroundColor: stringPtr("#9B51E0"), + Bold: boolPtr(true), + }, + Margin: uintPtr(2), + }, + H2: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "## ", + Color: stringPtr("#9B51E0"), + Bold: boolPtr(true), + }, + Margin: uintPtr(1), + }, + H3: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "### ", + Color: stringPtr("#00A3E0"), + Bold: boolPtr(true), + }, + }, + H4: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "#### ", + Color: stringPtr("#00A3E0"), + Bold: boolPtr(true), + }, + }, + H5: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "##### ", + Color: stringPtr("#00A3E0"), + Bold: boolPtr(true), + }, + }, + H6: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "###### ", + Color: stringPtr("#00A3E0"), + Bold: boolPtr(true), + }, + }, + Text: ansi.StylePrimitive{ + Color: stringPtr("#FFFFFF"), + }, + Strong: ansi.StylePrimitive{ + Color: stringPtr("#9B51E0"), + Bold: boolPtr(true), + }, + Emph: ansi.StylePrimitive{ + Color: stringPtr("#9B51E0"), + Italic: boolPtr(true), + }, + HorizontalRule: ansi.StylePrimitive{ + Color: stringPtr("#9B51E0"), + Format: "\n--------\n", + }, + Item: ansi.StylePrimitive{ + BlockPrefix: "• ", + }, + Enumeration: ansi.StylePrimitive{ + BlockPrefix: ". ", + }, + Code: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#9B51E0"), + }, + }, + CodeBlock: ansi.StyleCodeBlock{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#00A3E0"), + }, + Margin: uintPtr(1), + }, + Chroma: &ansi.Chroma{ + Text: ansi.StylePrimitive{ + Color: stringPtr("#00A3E0"), + }, + Keyword: ansi.StylePrimitive{ + Color: stringPtr("#9B51E0"), + }, + Literal: ansi.StylePrimitive{ + Color: stringPtr("#00A3E0"), + }, + LiteralString: ansi.StylePrimitive{ + Color: stringPtr("#00A3E0"), + }, + Name: ansi.StylePrimitive{ + Color: stringPtr("#00A3E0"), + }, + LiteralNumber: ansi.StylePrimitive{ + Color: stringPtr("#00A3E0"), + }, + Comment: ansi.StylePrimitive{ + Color: stringPtr("#9B51E0"), + }, + }, + }, + Table: ansi.StyleTable{ + StyleBlock: ansi.StyleBlock{}, + CenterSeparator: stringPtr("┼"), + ColumnSeparator: stringPtr("│"), + RowSeparator: stringPtr("─"), + }, + DefinitionList: ansi.StyleBlock{}, + DefinitionTerm: ansi.StylePrimitive{}, + DefinitionDescription: ansi.StylePrimitive{ + BlockPrefix: "\n", + }, + HTMLBlock: ansi.StyleBlock{}, + HTMLSpan: ansi.StyleBlock{}, + Link: ansi.StylePrimitive{ + Color: stringPtr("#00A3E0"), + Underline: boolPtr(true), + }, + LinkText: ansi.StylePrimitive{ + Color: stringPtr("#9B51E0"), + Bold: boolPtr(true), + }, + } + + return json.Marshal(style) +} + +func stringPtr(s string) *string { + return &s +} + +func boolPtr(b bool) *bool { + return &b +} + +func uintPtr(u uint) *uint { + return &u +} From 203f3472213677083725caecb2b510b441f33aa6 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Mon, 30 Dec 2024 16:50:05 +0000 Subject: [PATCH 21/49] markdown styles docs --- .../cli/configuration/markdown-styling.mdx | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 website/docs/cli/configuration/markdown-styling.mdx diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx new file mode 100644 index 000000000..57a45a093 --- /dev/null +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -0,0 +1,204 @@ +--- +title: Markdown Styling +sidebar_label: Markdown Styling +sidebar_position: 7 +description: Configure custom markdown styling for Atmos CLI output +--- + +# Markdown Styling Configuration + +Atmos CLI supports custom markdown styling for its command outputs, allowing you to personalize the appearance of messages, help text, and error outputs. This feature enables you to match your terminal's theme or create a more visually appealing and accessible CLI experience. + +## Configuration + +The markdown styling is configured in your `atmos.yaml` file under the `settings.markdown` section. Here's a complete example: + +```yaml +settings: + markdown: + # Base document styling + document: + color: "#FFFFFF" # Default text color + + # General heading styles + heading: + color: "#FF6B6B" # Color for all headings + bold: true + + # Level 1 heading (H1) + h1: + color: "#FFFFFF" + background_color: "#FF0000" # Background color for H1 + bold: true + margin: 2 + + # Level 2 heading (H2) + h2: + color: "#FFD700" # Golden yellow + bold: true + + # Level 3 heading (H3) + h3: + color: "#00FF00" # Bright green + bold: true + + # Code blocks + code_block: + color: "#00FFFF" # Cyan + margin: 1 + + # Block quotes + block_quote: + color: "#FF69B4" # Hot pink + indent: 2 + + # Links + link: + color: "#FFD700" # Golden yellow + underline: true + + # Bold text + strong: + color: "#00FF00" # Bright green + bold: true + + # Italic text + emph: + color: "#00FFFF" # Cyan + italic: true +``` + +## Style Properties + +Each markdown element supports the following properties: + +### Common Properties + +| Property | Type | Description | +|----------|------|-------------| +| `color` | string | Text color in hex format (e.g., "#FFFFFF") | +| `background_color` | string | Background color in hex format | +| `bold` | boolean | Whether to make the text bold | +| `italic` | boolean | Whether to make the text italic | +| `underline` | boolean | Whether to underline the text | +| `margin` | number | Space around the element | +| `indent` | number | Indentation level | + +### Element-Specific Properties + +#### Document +- Base styling for all text content +- Supports all common properties + +#### Headings (H1-H6) +- Individual styling for each heading level +- H1 supports additional `background_color` property +- All heading levels support `margin` for vertical spacing + +#### Code Blocks +- Styling for multi-line code blocks +- Supports `margin` for visual separation +- Color applies to the entire block + +#### Block Quotes +- Styling for quoted text +- `indent` property controls quote indentation +- Supports all common properties + +#### Links +- Styling for hyperlinks +- `underline` property specifically for links +- Color applies to both link text and underline + +## Default Styles + +If no custom styles are configured, Atmos uses a built-in default theme related to the default atmos brand colors: + +```yaml +# Built-in default theme +document: + color: "#FFFFFF" # White text +heading: + color: "#00A3E0" # Blue headings + bold: true +h1: + color: "#FFFFFF" + background_color: "#9B51E0" # Purple background + bold: true +code_block: + color: "#00A3E0" # Blue code +link: + color: "#00A3E0" + underline: true +``` + +## Terminal Compatibility + +The markdown styling feature requires a terminal that supports: +- 24-bit true color (for hex color codes) +- ANSI escape sequences +- Unicode characters + +Most modern terminals support these features. For terminals with limited color support, Atmos will automatically adjust colors to the closest available match. + +## Examples + +### Error Messages +Custom styling makes error messages more readable: + +```yaml +settings: + markdown: + h1: + color: "#FFFFFF" + background_color: "#FF0000" # Red background for errors + bold: true + code_block: + color: "#00FFFF" # Cyan for code examples +``` + +### Help Text +Enhance help text readability: + +```yaml +settings: + markdown: + heading: + color: "#FFD700" # Golden headings + bold: true + code_block: + color: "#00FFFF" # Cyan code examples + link: + color: "#FFD700" + underline: true +``` + +## Best Practices + +1. **Color Contrast**: Ensure sufficient contrast between text and background colors for readability. +2. **Consistent Styling**: Use a consistent color scheme across different elements. +3. **Terminal Support**: Test your styling in different terminals to ensure compatibility. +4. **Accessibility**: Consider color-blind users when choosing your color scheme. + +## Troubleshooting + +If styles aren't applying correctly: + +1. Verify your terminal supports true color: +```bash +$ echo $COLORTERM +``` +Should output `truecolor` or `24bit`. + +2. Check your `atmos.yaml` is properly formatted: +- Colors must be in hex format +- Boolean values must be `true` or `false` +- Numbers must be integers + +3. Try removing custom styles to see if default styles work. + +## See Also + +- [CLI Configuration](/cli/configuration) +- [Command Reference](/cli/commands) +- [Terminal Support](/cli/terminal-support) \ No newline at end of file From 3cae139695ccfa11170f4b8c039e8d0870ea9b09 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Mon, 30 Dec 2024 23:33:36 +0000 Subject: [PATCH 22/49] fixes markdown and styles --- cmd/markdown/workflow.md | 80 ++++++++++--------------------------- cmd/workflow.go | 73 +++++++++++++++++++++------------ pkg/ui/markdown/renderer.go | 44 ++++++++++++++++---- pkg/ui/markdown/styles.go | 5 ++- 4 files changed, 109 insertions(+), 93 deletions(-) diff --git a/cmd/markdown/workflow.md b/cmd/markdown/workflow.md index 83633047f..9fd74685a 100644 --- a/cmd/markdown/workflow.md +++ b/cmd/markdown/workflow.md @@ -1,60 +1,20 @@ -# Invalid Command -The command `atmos workflow list` is not valid. - -## Examples -Use one of the following commands: -```shell -$ atmos workflow # Use interactive UI -$ atmos workflow --file # Execute workflow -$ atmos workflow --file --stack -$ atmos workflow --file --from-step -``` -For more information, refer to the [docs](https://atmos.tools/cli/commands/workflow/). - -# Missing Required Flag -The `--file` flag is required to specify a workflow manifest. - -## Examples -– Deploy a workflow -```shell -$ atmos workflow deploy-infra --file workflow1 -``` -– Deploy with stack configuration -```shell -$ atmos workflow deploy-infra --file workflow1 --stack dev -``` -– Resume from a specific step -```shell -$ atmos workflow deploy-infra --file workflow1 --from-step deploy-vpc -``` -For more information, refer to the [docs](https://atmos.tools/cli/commands/workflow/). - -# Workflow File Not Found -The workflow manifest file could not be found. - -## Examples -– List available workflows -```shell -$ ls workflows/ -``` -– Execute a specific workflow -```shell -$ atmos workflow --file -``` -For more information, refer to the [docs](https://atmos.tools/cli/commands/workflow/). - -# Invalid Workflow -The specified workflow is not valid or has incorrect configuration. - -## Examples -– Example of a valid workflow configuration: -```yaml -name: deploy-infra -description: Deploy infrastructure components -steps: - - name: deploy-vpc - command: atmos terraform apply vpc - - name: deploy-eks - command: atmos terraform apply eks -``` -For more information, refer to the [docs](https://atmos.tools/cli/commands/workflow/). +Examples: + + – Use interactive UI + + $ atmos workflow + + – Execute a workflow + + $ atmos workflow --file + + – Execute with stack override + + $ atmos workflow --file --stack + + – Resume from specific step + + $ atmos workflow --file --from-step + +For more information, refer to the **docs**: +https://atmos.tools/cli/commands/workflow/ diff --git a/cmd/workflow.go b/cmd/workflow.go index 61e3e02ce..0d77b16f5 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -3,6 +3,7 @@ package cmd import ( _ "embed" "fmt" + "os" "strings" "github.com/spf13/cobra" @@ -77,39 +78,61 @@ var workflowCmd = &cobra.Command{ return } - // Check if the workflow name is "list" (invalid command) - if args[0] == "list" { - details, suggestion := getMarkdownSection("Invalid Command") - err := renderError(ErrorMessage{ - Title: "Invalid Command", - Details: details, - Suggestion: suggestion, - }) - if err != nil { - u.LogErrorAndExit(schema.AtmosConfiguration{}, err) - } - return + // List any known subcommands here + knownSubcommands := map[string]bool{ + "help": true, + "-h": true, + "--help": true, } - // Get the --file flag value - workflowFile, _ := cmd.Flags().GetString("file") - if workflowFile == "" { - details, suggestion := getMarkdownSection("Missing Required Flag") - err := renderError(ErrorMessage{ - Title: "Missing Required Flag", - Details: details, - Suggestion: suggestion, - }) - if err != nil { - u.LogErrorAndExit(schema.AtmosConfiguration{}, err) + if !knownSubcommands[args[0]] { + // Get the --file flag value + workflowFile, _ := cmd.Flags().GetString("file") + + // If no file is provided, show invalid command error with usage information + if workflowFile == "" { + renderer, err := markdown.NewRenderer( + markdown.WithWidth(80), + ) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to create markdown renderer: %w", err)) + } + + // Generate the error message dynamically using H1 styling + errorMsg := fmt.Sprintf("# Invalid Command\n\nThe command `atmos workflow %s` is not valid.\n\n", args[0]) + content := errorMsg + workflowMarkdown + rendered, err := renderer.Render(content) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to render markdown: %w", err)) + } + + // Remove duplicate URLs and format output + lines := strings.Split(rendered, "\n") + var result []string + seenURL := false + + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if strings.Contains(trimmed, "https://") { + if !seenURL { + seenURL = true + result = append(result, line) + } + } else if strings.HasPrefix(trimmed, "$") { + result = append(result, " "+strings.TrimSpace(line)) + } else if trimmed != "" { + result = append(result, line) + } + } + + fmt.Print("\n" + strings.Join(result, "\n") + "\n\n") + os.Exit(1) } - return } // Execute the workflow command err := e.ExecuteWorkflowCmd(cmd, args) if err != nil { - u.LogErrorAndExit(schema.AtmosConfiguration{}, err) // Format common error messages if strings.Contains(err.Error(), "does not exist") { details, suggestion := getMarkdownSection("Workflow File Not Found") diff --git a/pkg/ui/markdown/renderer.go b/pkg/ui/markdown/renderer.go index 1b86e7070..19a148cd8 100644 --- a/pkg/ui/markdown/renderer.go +++ b/pkg/ui/markdown/renderer.go @@ -78,26 +78,56 @@ func (r *Renderer) RenderWorkflow(content string) (string, error) { } // RenderError renders an error message with specific styling -func (r *Renderer) RenderError(title, details, examples string) (string, error) { +func (r *Renderer) RenderError(title, details, suggestion string) (string, error) { var content string if title != "" { - content += fmt.Sprintf("# %s\n\n", title) + content += fmt.Sprintf("\n# %s\n\n", title) } if details != "" { content += fmt.Sprintf("%s\n\n", details) } - if examples != "" { - if !strings.Contains(examples, "## Examples") { - content += fmt.Sprintf("## Examples\n\n%s", examples) + if suggestion != "" { + if strings.HasPrefix(suggestion, "http") { + content += fmt.Sprintf("For more information, refer to the **docs**\n%s\n", suggestion) } else { - content += examples + content += suggestion } } - return r.Render(content) + rendered, err := r.Render(content) + if err != nil { + return "", err + } + + // Remove duplicate URLs and trailing newlines + lines := strings.Split(rendered, "\n") + var result []string + seenURL := false + + // Create a purple style + purpleStyle := termenv.Style{}.Foreground(r.profile.Color("#9B51E0")).Bold() + + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if strings.Contains(trimmed, "https://") { + if !seenURL { + seenURL = true + result = append(result, line) + } + } else if strings.HasPrefix(trimmed, "$") { + // Add custom styling for command examples + styled := purpleStyle.Styled(strings.TrimSpace(line)) + result = append(result, " "+styled) + } else if trimmed != "" { + result = append(result, line) + } + } + + // Add a single newline at the end plus extra spacing + return "\n" + strings.Join(result, "\n") + "\n\n", nil } // RenderSuccess renders a success message with specific styling diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go index 26750da2a..ff2c9dd60 100644 --- a/pkg/ui/markdown/styles.go +++ b/pkg/ui/markdown/styles.go @@ -196,8 +196,11 @@ func getBuiltinDefaultStyle() ([]byte, error) { }, Code: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr("#9B51E0"), + Prefix: " ", + Bold: boolPtr(true), }, + Margin: uintPtr(0), }, CodeBlock: ansi.StyleCodeBlock{ StyleBlock: ansi.StyleBlock{ From 2fae8033d5c0cbc95b90e1ad5c9f03b1c8212527 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 08:29:24 +0000 Subject: [PATCH 23/49] fix help subcommands --- .../stacks/workflows/example.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 examples/demo-workflows/stacks/workflows/example.yaml diff --git a/examples/demo-workflows/stacks/workflows/example.yaml b/examples/demo-workflows/stacks/workflows/example.yaml new file mode 100644 index 000000000..db3dd946c --- /dev/null +++ b/examples/demo-workflows/stacks/workflows/example.yaml @@ -0,0 +1,21 @@ +workflows: + example: + name: "Example Workflow" + description: "This is a sample workflow to test markdown styling" + + steps: + - name: "List Components" + description: "List available components" + command: "list" + args: + - "components" + + - name: "List Stacks" + description: "List available stacks" + command: "list" + args: + - "stacks" + + - name: "Show Version" + description: "Display Atmos version" + command: "version" \ No newline at end of file From b663608285d95b4827d368a2fec5641708695d32 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 08:37:03 +0000 Subject: [PATCH 24/49] dynamic size --- cmd/workflow.go | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index 0d77b16f5..0b15ffff8 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -9,6 +9,8 @@ import ( "github.com/spf13/cobra" e "github.com/cloudposse/atmos/internal/exec" + termwriter "github.com/cloudposse/atmos/internal/tui/templates/term" + cfg "github.com/cloudposse/atmos/pkg/config" "github.com/cloudposse/atmos/pkg/schema" "github.com/cloudposse/atmos/pkg/ui/markdown" u "github.com/cloudposse/atmos/pkg/utils" @@ -26,8 +28,20 @@ type ErrorMessage struct { // renderError renders an error message using the markdown renderer func renderError(msg ErrorMessage) error { + atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + if err != nil { + return fmt.Errorf("failed to initialize atmos config: %w", err) + } + + termWriter := termwriter.NewResponsiveWriter(os.Stdout) + screenWidth := termWriter.(*termwriter.TerminalWriter).GetWidth() + + if atmosConfig.Settings.Docs.MaxWidth > 0 { + screenWidth = uint(min(atmosConfig.Settings.Docs.MaxWidth, int(screenWidth))) + } + renderer, err := markdown.NewRenderer( - markdown.WithWidth(80), + markdown.WithWidth(screenWidth), ) if err != nil { return fmt.Errorf("failed to create markdown renderer: %w", err) @@ -78,21 +92,34 @@ var workflowCmd = &cobra.Command{ return } - // List any known subcommands here - knownSubcommands := map[string]bool{ + helpFlags := map[string]bool{ "help": true, "-h": true, "--help": true, } - if !knownSubcommands[args[0]] { + if !helpFlags[args[0]] { // Get the --file flag value workflowFile, _ := cmd.Flags().GetString("file") // If no file is provided, show invalid command error with usage information if workflowFile == "" { + // Get atmos configuration + atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to initialize atmos config: %w", err)) + } + + // Create a terminal writer to get the optimal width + termWriter := termwriter.NewResponsiveWriter(os.Stdout) + screenWidth := termWriter.(*termwriter.TerminalWriter).GetWidth() + + if atmosConfig.Settings.Docs.MaxWidth > 0 { + screenWidth = uint(min(atmosConfig.Settings.Docs.MaxWidth, int(screenWidth))) + } + renderer, err := markdown.NewRenderer( - markdown.WithWidth(80), + markdown.WithWidth(screenWidth), ) if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to create markdown renderer: %w", err)) From 75a67e2d4293164499033afc265cf58d025e00f7 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 08:39:44 +0000 Subject: [PATCH 25/49] terminal width --- internal/tui/utils/utils.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/tui/utils/utils.go b/internal/tui/utils/utils.go index ac7d046d0..4486555f8 100644 --- a/internal/tui/utils/utils.go +++ b/internal/tui/utils/utils.go @@ -7,6 +7,7 @@ import ( "github.com/alecthomas/chroma/quick" "github.com/arsham/figurine/figurine" "github.com/charmbracelet/glamour" + "github.com/cloudposse/atmos/internal/tui/templates/term" "github.com/jwalton/go-supportscolor" ) @@ -36,15 +37,17 @@ func RenderMarkdown(markdown string, style string) (string, error) { style = "dark" } + termWriter := term.NewResponsiveWriter(os.Stdout) + screenWidth := termWriter.(*term.TerminalWriter).GetWidth() + // Create a new renderer with the specified style r, err := glamour.NewTermRenderer( glamour.WithAutoStyle(), - glamour.WithWordWrap(80), + glamour.WithWordWrap(int(screenWidth)), ) if err != nil { return "", err } - // Render the markdown return r.Render(markdown) } From e072c6045ba7c5db99570f54c67acb05da2fe289 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 11:52:35 +0000 Subject: [PATCH 26/49] fixed colors names --- pkg/ui/markdown/colors.go | 42 +++++++++++++++++++++++-------- pkg/ui/markdown/styles.go | 52 +++++++++++++++++++-------------------- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/pkg/ui/markdown/colors.go b/pkg/ui/markdown/colors.go index b98803689..b8d1bac95 100644 --- a/pkg/ui/markdown/colors.go +++ b/pkg/ui/markdown/colors.go @@ -2,6 +2,28 @@ package markdown import "github.com/charmbracelet/lipgloss" +// Base colors used throughout the application +var ( + White = "#FFFFFF" + Purple = "#9B51E0" + Blue = "#00A3E0" + Gray = "#4A5568" + Green = "#48BB78" + Yellow = "#ECC94B" + Red = "#F56565" + LightBlue = "#4299E1" + BlueLight = "#63B3ED" + GrayLight = "#718096" + GrayMid = "#A0AEC0" + GrayDark = "#2D3748" + GrayBorder = "#CBD5E0" + OffWhite = "#F7FAFC" + DarkSlate = "#1A202C" + GreenLight = "#68D391" + YellowLight = "#F6E05E" + RedLight = "#FC8181" +) + // Colors defines the color scheme for markdown rendering var Colors = struct { Primary lipgloss.AdaptiveColor @@ -15,14 +37,14 @@ var Colors = struct { Border lipgloss.AdaptiveColor Background lipgloss.AdaptiveColor }{ - Primary: lipgloss.AdaptiveColor{Light: "#00A3E0", Dark: "#00A3E0"}, // Atmos blue - Secondary: lipgloss.AdaptiveColor{Light: "#4A5568", Dark: "#A0AEC0"}, // Slate gray - Success: lipgloss.AdaptiveColor{Light: "#48BB78", Dark: "#68D391"}, // Green - Warning: lipgloss.AdaptiveColor{Light: "#ECC94B", Dark: "#F6E05E"}, // Yellow - Error: lipgloss.AdaptiveColor{Light: "#F56565", Dark: "#FC8181"}, // Red - Info: lipgloss.AdaptiveColor{Light: "#4299E1", Dark: "#63B3ED"}, // Light blue - Subtle: lipgloss.AdaptiveColor{Light: "#718096", Dark: "#A0AEC0"}, // Gray - HeaderBg: lipgloss.AdaptiveColor{Light: "#2D3748", Dark: "#4A5568"}, // Dark slate - Border: lipgloss.AdaptiveColor{Light: "#CBD5E0", Dark: "#4A5568"}, // Light gray - Background: lipgloss.AdaptiveColor{Light: "#F7FAFC", Dark: "#1A202C"}, // Off white/dark + Primary: lipgloss.AdaptiveColor{Light: Blue, Dark: Blue}, + Secondary: lipgloss.AdaptiveColor{Light: Gray, Dark: GrayMid}, + Success: lipgloss.AdaptiveColor{Light: Green, Dark: GreenLight}, + Warning: lipgloss.AdaptiveColor{Light: Yellow, Dark: YellowLight}, + Error: lipgloss.AdaptiveColor{Light: Red, Dark: RedLight}, + Info: lipgloss.AdaptiveColor{Light: LightBlue, Dark: BlueLight}, + Subtle: lipgloss.AdaptiveColor{Light: GrayLight, Dark: GrayMid}, + HeaderBg: lipgloss.AdaptiveColor{Light: GrayDark, Dark: Gray}, + Border: lipgloss.AdaptiveColor{Light: GrayBorder, Dark: Gray}, + Background: lipgloss.AdaptiveColor{Light: OffWhite, Dark: DarkSlate}, } diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go index ff2c9dd60..61e957110 100644 --- a/pkg/ui/markdown/styles.go +++ b/pkg/ui/markdown/styles.go @@ -98,13 +98,13 @@ func getBuiltinDefaultStyle() ([]byte, error) { StylePrimitive: ansi.StylePrimitive{ BlockPrefix: "", BlockSuffix: "\n", - Color: stringPtr("#FFFFFF"), + Color: stringPtr(White), }, Margin: uintPtr(0), }, BlockQuote: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), }, Indent: uintPtr(1), IndentToken: stringPtr("│ "), @@ -113,7 +113,7 @@ func getBuiltinDefaultStyle() ([]byte, error) { StylePrimitive: ansi.StylePrimitive{ BlockPrefix: "", BlockSuffix: "", - Color: stringPtr("#FFFFFF"), + Color: stringPtr(White), }, }, List: ansi.StyleList{ @@ -123,7 +123,7 @@ func getBuiltinDefaultStyle() ([]byte, error) { StylePrimitive: ansi.StylePrimitive{ BlockPrefix: "", BlockSuffix: "\n", - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), Bold: boolPtr(true), }, Margin: uintPtr(0), @@ -131,8 +131,8 @@ func getBuiltinDefaultStyle() ([]byte, error) { H1: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ Prefix: "", - Color: stringPtr("#FFFFFF"), - BackgroundColor: stringPtr("#9B51E0"), + Color: stringPtr(White), + BackgroundColor: stringPtr(Purple), Bold: boolPtr(true), }, Margin: uintPtr(2), @@ -140,7 +140,7 @@ func getBuiltinDefaultStyle() ([]byte, error) { H2: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ Prefix: "## ", - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), Bold: boolPtr(true), }, Margin: uintPtr(1), @@ -148,44 +148,44 @@ func getBuiltinDefaultStyle() ([]byte, error) { H3: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ Prefix: "### ", - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), Bold: boolPtr(true), }, }, H4: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ Prefix: "#### ", - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), Bold: boolPtr(true), }, }, H5: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ Prefix: "##### ", - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), Bold: boolPtr(true), }, }, H6: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ Prefix: "###### ", - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), Bold: boolPtr(true), }, }, Text: ansi.StylePrimitive{ - Color: stringPtr("#FFFFFF"), + Color: stringPtr(White), }, Strong: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), Bold: boolPtr(true), }, Emph: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), Italic: boolPtr(true), }, HorizontalRule: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), Format: "\n--------\n", }, Item: ansi.StylePrimitive{ @@ -196,7 +196,7 @@ func getBuiltinDefaultStyle() ([]byte, error) { }, Code: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), Prefix: " ", Bold: boolPtr(true), }, @@ -205,31 +205,31 @@ func getBuiltinDefaultStyle() ([]byte, error) { CodeBlock: ansi.StyleCodeBlock{ StyleBlock: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), }, Margin: uintPtr(1), }, Chroma: &ansi.Chroma{ Text: ansi.StylePrimitive{ - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), }, Keyword: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), }, Literal: ansi.StylePrimitive{ - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), }, LiteralString: ansi.StylePrimitive{ - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), }, Name: ansi.StylePrimitive{ - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), }, LiteralNumber: ansi.StylePrimitive{ - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), }, Comment: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), }, }, }, @@ -247,11 +247,11 @@ func getBuiltinDefaultStyle() ([]byte, error) { HTMLBlock: ansi.StyleBlock{}, HTMLSpan: ansi.StyleBlock{}, Link: ansi.StylePrimitive{ - Color: stringPtr("#00A3E0"), + Color: stringPtr(Blue), Underline: boolPtr(true), }, LinkText: ansi.StylePrimitive{ - Color: stringPtr("#9B51E0"), + Color: stringPtr(Purple), Bold: boolPtr(true), }, } From 6d30d091f239d726d32c32eed819e8c3a2253b6d Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 16:37:14 +0000 Subject: [PATCH 27/49] replace namecolor --- pkg/ui/markdown/renderer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/ui/markdown/renderer.go b/pkg/ui/markdown/renderer.go index 19a148cd8..beac0ca59 100644 --- a/pkg/ui/markdown/renderer.go +++ b/pkg/ui/markdown/renderer.go @@ -108,7 +108,7 @@ func (r *Renderer) RenderError(title, details, suggestion string) (string, error seenURL := false // Create a purple style - purpleStyle := termenv.Style{}.Foreground(r.profile.Color("#9B51E0")).Bold() + purpleStyle := termenv.Style{}.Foreground(r.profile.Color(Purple)).Bold() for _, line := range lines { trimmed := strings.TrimSpace(line) From 0811fbc34b91bbf6422fdfa6d39daf0e02299a9e Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 17:48:00 +0000 Subject: [PATCH 28/49] fix docs markdown --- .../cli/configuration/markdown-styling.mdx | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 57a45a093..91b4175fc 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -116,20 +116,24 @@ If no custom styles are configured, Atmos uses a built-in default theme related ```yaml # Built-in default theme -document: - color: "#FFFFFF" # White text -heading: - color: "#00A3E0" # Blue headings - bold: true -h1: - color: "#FFFFFF" - background_color: "#9B51E0" # Purple background - bold: true -code_block: - color: "#00A3E0" # Blue code -link: - color: "#00A3E0" - underline: true +settings: + markdown: + document: + color: "#FFFFFF" # White text + heading: + color: "#00A3E0" # Blue headings + bold: true + h1: + color: "#FFFFFF" # White text + background_color: "#9B51E0" # Purple background + bold: true + margin: 2 + code_block: + color: "#00A3E0" # Blue code + margin: 1 + link: + color: "#00A3E0" # Blue links + underline: true ``` ## Terminal Compatibility From 38b5af47dfd43e4807f5f9f1da06e432003094e5 Mon Sep 17 00:00:00 2001 From: "Vinicius C." Date: Tue, 31 Dec 2024 17:49:40 +0000 Subject: [PATCH 29/49] Update website/docs/cli/configuration/markdown-styling.mdx Co-authored-by: Erik Osterman (CEO @ Cloud Posse) --- .../cli/configuration/markdown-styling.mdx | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 91b4175fc..88d2a1bb4 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -186,20 +186,39 @@ settings: ## Troubleshooting -If styles aren't applying correctly: +1. **Verify Terminal Supports True Color:** -1. Verify your terminal supports true color: -```bash -$ echo $COLORTERM -``` -Should output `truecolor` or `24bit`. + - **Check `$COLORTERM`:** + ```bash + echo $COLORTERM + ``` + **Expected Output:** `truecolor` or `24bit` + + - **Check `$TERM`:** + ```bash + echo $TERM + ``` + **Recommended Values:** `xterm-256color`, `xterm-direct`, `xterm-truecolor` + +2. **Ensure Your Terminal Emulator Supports True Color:** + + - Use a terminal emulator known for true color support (e.g., Terminal.app, iTerm2, Windows Terminal, etc). + +3. **Configure Environment Variables Correctly:** + + - Set `$TERM` to a value that supports true color: + ```bash + export TERM=xterm-256color + ``` + Add this to your shell’s configuration file (`~/.bashrc`, `~/.zshrc`, etc.) to make it permanent. + +4. **Validate `atmos.yaml` Configuration:** + + - Ensure colors are in hex format, boolean values are `true`/`false` (not quoted strings), and numbers are integers. + - Use a YAML linter to validate the syntax. + - Try removing custom styles to see if default styles work. -2. Check your `atmos.yaml` is properly formatted: -- Colors must be in hex format -- Boolean values must be `true` or `false` -- Numbers must be integers -3. Try removing custom styles to see if default styles work. ## See Also From 567fded6f7d949f12b3593b087307e6d0e93eed3 Mon Sep 17 00:00:00 2001 From: "Vinicius C." Date: Tue, 31 Dec 2024 17:50:06 +0000 Subject: [PATCH 30/49] Update website/docs/cli/configuration/markdown-styling.mdx Co-authored-by: Erik Osterman (CEO @ Cloud Posse) --- .../cli/configuration/markdown-styling.mdx | 54 +++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 88d2a1bb4..e915b980b 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -87,26 +87,62 @@ Each markdown element supports the following properties: ### Element-Specific Properties #### Document -- Base styling for all text content -- Supports all common properties + +Base styling for all text content. + +Supports all common properties. #### Headings (H1-H6) -- Individual styling for each heading level + +Individual styling for each heading level (1-6). + + ```markdown + # Heading 1 + ## Heading 2 + ### Heading 3 + etc... + ``` + +**Supports:** - H1 supports additional `background_color` property - All heading levels support `margin` for vertical spacing #### Code Blocks -- Styling for multi-line code blocks -- Supports `margin` for visual separation + +Styling for multi-line code blocks (aka code fences). + +````markdown +``` +this is a codeblock +``` +```` + +**Supports:** +- `margin` for visual separation - Color applies to the entire block #### Block Quotes -- Styling for quoted text -- `indent` property controls quote indentation -- Supports all common properties + +Styling for quoted text. Supports all common properties. + +```markdown +> +> This is quoted text +> +``` + +**Supports:** +- `indent` property controls quote indentation #### Links -- Styling for hyperlinks + +Styling for hyperlinks. + +``` +[This is a link](https://example.com/) +``` + +**Supports:** - `underline` property specifically for links - Color applies to both link text and underline From 669764ba6ee1f3d6bb74087ca3023c7a4083152a Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 17:56:47 +0000 Subject: [PATCH 31/49] explain color degradation --- .../cli/configuration/markdown-styling.mdx | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index e915b980b..d6cade4aa 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -174,12 +174,29 @@ settings: ## Terminal Compatibility -The markdown styling feature requires a terminal that supports: -- 24-bit true color (for hex color codes) -- ANSI escape sequences -- Unicode characters +Atmos uses [termenv](https://github.com/muesli/termenv) and [glamour](https://github.com/charmbracelet/glamour) to automatically detect and adapt to your terminal's capabilities: -Most modern terminals support these features. For terminals with limited color support, Atmos will automatically adjust colors to the closest available match. +- **Full Color Support (24-bit)** + - Renders exact hex colors as specified in your config + - Detected via `$COLORTERM=truecolor` or `$TERM` containing `24bit`/`truecolor` + - Examples: iTerm2, Terminal.app, Windows Terminal + +- **256 Color Support** + - Automatically maps hex colors to nearest ANSI 256 colors + - Detected via `$TERM` containing `256color` + - Examples: xterm-256color terminals + +- **Basic Color Support (8/16 colors)** + - Automatically maps to basic ANSI colors + - Used when `$TERM` indicates basic terminal + - Examples: xterm, vt100, basic SSH sessions + +- **No Color Support** + - Falls back to plain text with basic formatting + - Used when `$TERM=dumb` or no color support detected + - Examples: Basic terminals, some CI environments + +The color degradation is handled automatically by termenv's color profile detection. You don't need to configure anything - your styles will work everywhere, automatically adjusting to each terminal's capabilities. ## Examples @@ -246,7 +263,7 @@ settings: ```bash export TERM=xterm-256color ``` - Add this to your shell’s configuration file (`~/.bashrc`, `~/.zshrc`, etc.) to make it permanent. + Add this to your shell's configuration file (`~/.bashrc`, `~/.zshrc`, etc.) to make it permanent. 4. **Validate `atmos.yaml` Configuration:** From 530f404c551bd766d6eae4a7a349fcbd6ef11d7b Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 17:59:07 +0000 Subject: [PATCH 32/49] fix h1 headers --- .../cli/configuration/markdown-styling.mdx | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index d6cade4aa..2bc9f3ddb 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -201,17 +201,30 @@ The color degradation is handled automatically by termenv's color profile detect ## Examples ### Error Messages -Custom styling makes error messages more readable: +Custom styling can help distinguish different types of messages: ```yaml settings: markdown: - h1: - color: "#FFFFFF" - background_color: "#FF0000" # Red background for errors + # General heading styles + heading: + color: "#00A3E0" # Blue for standard headings bold: true + + # Code blocks for command examples code_block: color: "#00FFFF" # Cyan for code examples + margin: 1 + + # Emphasized text for warnings/errors + emph: + color: "#FF6B6B" # Red for emphasis in error messages + italic: true + + # Strong text for important messages + strong: + color: "#FF6B6B" # Red for important parts + bold: true ``` ### Help Text From f96e73d7cb75f938b7b8d0cccc304f6d270d4935 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 18:00:16 +0000 Subject: [PATCH 33/49] remove broken link --- website/docs/cli/configuration/markdown-styling.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 2bc9f3ddb..35eedeb00 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -289,5 +289,4 @@ settings: ## See Also - [CLI Configuration](/cli/configuration) -- [Command Reference](/cli/commands) -- [Terminal Support](/cli/terminal-support) \ No newline at end of file +- [Command Reference](/cli/commands) \ No newline at end of file From 1974be194285958efa4501a21985d626580d9e96 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 18:07:17 +0000 Subject: [PATCH 34/49] format workflow file --- cmd/workflow.go | 99 ++++++++++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 51 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index 0b15ffff8..b238c0f5b 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -92,69 +92,66 @@ var workflowCmd = &cobra.Command{ return } - helpFlags := map[string]bool{ - "help": true, - "-h": true, - "--help": true, + if args[0] == "help" { + cmd.Help() + return } - if !helpFlags[args[0]] { - // Get the --file flag value - workflowFile, _ := cmd.Flags().GetString("file") + // Get the --file flag value + workflowFile, _ := cmd.Flags().GetString("file") - // If no file is provided, show invalid command error with usage information - if workflowFile == "" { - // Get atmos configuration - atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) - if err != nil { - u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to initialize atmos config: %w", err)) - } + // If no file is provided, show invalid command error with usage information + if workflowFile == "" { + // Get atmos configuration + atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to initialize atmos config: %w", err)) + } - // Create a terminal writer to get the optimal width - termWriter := termwriter.NewResponsiveWriter(os.Stdout) - screenWidth := termWriter.(*termwriter.TerminalWriter).GetWidth() + // Create a terminal writer to get the optimal width + termWriter := termwriter.NewResponsiveWriter(os.Stdout) + screenWidth := termWriter.(*termwriter.TerminalWriter).GetWidth() - if atmosConfig.Settings.Docs.MaxWidth > 0 { - screenWidth = uint(min(atmosConfig.Settings.Docs.MaxWidth, int(screenWidth))) - } + if atmosConfig.Settings.Docs.MaxWidth > 0 { + screenWidth = uint(min(atmosConfig.Settings.Docs.MaxWidth, int(screenWidth))) + } - renderer, err := markdown.NewRenderer( - markdown.WithWidth(screenWidth), - ) - if err != nil { - u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to create markdown renderer: %w", err)) - } + renderer, err := markdown.NewRenderer( + markdown.WithWidth(screenWidth), + ) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to create markdown renderer: %w", err)) + } - // Generate the error message dynamically using H1 styling - errorMsg := fmt.Sprintf("# Invalid Command\n\nThe command `atmos workflow %s` is not valid.\n\n", args[0]) - content := errorMsg + workflowMarkdown - rendered, err := renderer.Render(content) - if err != nil { - u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to render markdown: %w", err)) - } + // Generate the error message dynamically using H1 styling + errorMsg := fmt.Sprintf("# Invalid Command\n\nThe command `atmos workflow %s` is not valid.\n\n", args[0]) + content := errorMsg + workflowMarkdown + rendered, err := renderer.Render(content) + if err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to render markdown: %w", err)) + } + + // Remove duplicate URLs and format output + lines := strings.Split(rendered, "\n") + var result []string + seenURL := false - // Remove duplicate URLs and format output - lines := strings.Split(rendered, "\n") - var result []string - seenURL := false - - for _, line := range lines { - trimmed := strings.TrimSpace(line) - if strings.Contains(trimmed, "https://") { - if !seenURL { - seenURL = true - result = append(result, line) - } - } else if strings.HasPrefix(trimmed, "$") { - result = append(result, " "+strings.TrimSpace(line)) - } else if trimmed != "" { + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if strings.Contains(trimmed, "https://") { + if !seenURL { + seenURL = true result = append(result, line) } + } else if strings.HasPrefix(trimmed, "$") { + result = append(result, " "+strings.TrimSpace(line)) + } else if trimmed != "" { + result = append(result, line) } - - fmt.Print("\n" + strings.Join(result, "\n") + "\n\n") - os.Exit(1) } + + fmt.Print("\n" + strings.Join(result, "\n") + "\n\n") + os.Exit(1) } // Execute the workflow command From fd2ae69e56aa1ef6f7743783cf667ada32da4b42 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 21:21:51 +0000 Subject: [PATCH 35/49] added safe style --- pkg/ui/markdown/styles.go | 42 ++++--- .../cli/configuration/markdown-styling.mdx | 108 +++++++++--------- 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/pkg/ui/markdown/styles.go b/pkg/ui/markdown/styles.go index 61e957110..549ef3706 100644 --- a/pkg/ui/markdown/styles.go +++ b/pkg/ui/markdown/styles.go @@ -8,6 +8,18 @@ import ( "github.com/cloudposse/atmos/pkg/schema" ) +// applyStyleSafely applies a color to a style primitive safely handling nil pointers +func applyStyleSafely(style *ansi.StylePrimitive, color string) { + if style == nil { + return + } + if style.Color != nil { + *style.Color = color + } else { + style.Color = &color + } +} + // GetDefaultStyle returns the markdown style configuration from atmos.yaml settings // or falls back to built-in defaults if not configured func GetDefaultStyle() ([]byte, error) { @@ -22,14 +34,6 @@ func GetDefaultStyle() ([]byte, error) { return nil, err } - if atmosConfig.Settings.Markdown.Document.Color == "" && - atmosConfig.Settings.Markdown.Heading.Color == "" && - atmosConfig.Settings.Markdown.H1.Color == "" && - atmosConfig.Settings.Markdown.H2.Color == "" && - atmosConfig.Settings.Markdown.H3.Color == "" { - return defaultBytes, nil - } - var style ansi.StyleConfig if err := json.Unmarshal(defaultBytes, &style); err != nil { return nil, err @@ -37,16 +41,16 @@ func GetDefaultStyle() ([]byte, error) { // Apply custom styles on top of defaults if atmosConfig.Settings.Markdown.Document.Color != "" { - style.Document.Color = &atmosConfig.Settings.Markdown.Document.Color + applyStyleSafely(&style.Document.StylePrimitive, atmosConfig.Settings.Markdown.Document.Color) } if atmosConfig.Settings.Markdown.Heading.Color != "" { - style.Heading.Color = &atmosConfig.Settings.Markdown.Heading.Color + applyStyleSafely(&style.Heading.StylePrimitive, atmosConfig.Settings.Markdown.Heading.Color) style.Heading.Bold = &atmosConfig.Settings.Markdown.Heading.Bold } if atmosConfig.Settings.Markdown.H1.Color != "" { - style.H1.Color = &atmosConfig.Settings.Markdown.H1.Color + applyStyleSafely(&style.H1.StylePrimitive, atmosConfig.Settings.Markdown.H1.Color) if atmosConfig.Settings.Markdown.H1.BackgroundColor != "" { style.H1.BackgroundColor = &atmosConfig.Settings.Markdown.H1.BackgroundColor } @@ -55,36 +59,36 @@ func GetDefaultStyle() ([]byte, error) { } if atmosConfig.Settings.Markdown.H2.Color != "" { - style.H2.Color = &atmosConfig.Settings.Markdown.H2.Color + applyStyleSafely(&style.H2.StylePrimitive, atmosConfig.Settings.Markdown.H2.Color) style.H2.Bold = &atmosConfig.Settings.Markdown.H2.Bold } if atmosConfig.Settings.Markdown.H3.Color != "" { - style.H3.Color = &atmosConfig.Settings.Markdown.H3.Color + applyStyleSafely(&style.H3.StylePrimitive, atmosConfig.Settings.Markdown.H3.Color) style.H3.Bold = &atmosConfig.Settings.Markdown.H3.Bold } if atmosConfig.Settings.Markdown.CodeBlock.Color != "" { - if style.CodeBlock.StyleBlock.StylePrimitive.Color == nil { - style.CodeBlock.StyleBlock.StylePrimitive.Color = &atmosConfig.Settings.Markdown.CodeBlock.Color - } else { + if style.CodeBlock.StyleBlock.StylePrimitive.Color != nil { *style.CodeBlock.StyleBlock.StylePrimitive.Color = atmosConfig.Settings.Markdown.CodeBlock.Color + } else { + style.CodeBlock.StyleBlock.StylePrimitive.Color = &atmosConfig.Settings.Markdown.CodeBlock.Color } style.CodeBlock.Margin = uintPtr(uint(atmosConfig.Settings.Markdown.CodeBlock.Margin)) } if atmosConfig.Settings.Markdown.Link.Color != "" { - style.Link.Color = &atmosConfig.Settings.Markdown.Link.Color + applyStyleSafely(&style.Link, atmosConfig.Settings.Markdown.Link.Color) style.Link.Underline = &atmosConfig.Settings.Markdown.Link.Underline } if atmosConfig.Settings.Markdown.Strong.Color != "" { - style.Strong.Color = &atmosConfig.Settings.Markdown.Strong.Color + applyStyleSafely(&style.Strong, atmosConfig.Settings.Markdown.Strong.Color) style.Strong.Bold = &atmosConfig.Settings.Markdown.Strong.Bold } if atmosConfig.Settings.Markdown.Emph.Color != "" { - style.Emph.Color = &atmosConfig.Settings.Markdown.Emph.Color + applyStyleSafely(&style.Emph, atmosConfig.Settings.Markdown.Emph.Color) style.Emph.Italic = &atmosConfig.Settings.Markdown.Emph.Italic } diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 35eedeb00..e42e0fed2 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -5,67 +5,73 @@ sidebar_position: 7 description: Configure custom markdown styling for Atmos CLI output --- -# Markdown Styling Configuration +# Terminal Styling Configuration -Atmos CLI supports custom markdown styling for its command outputs, allowing you to personalize the appearance of messages, help text, and error outputs. This feature enables you to match your terminal's theme or create a more visually appealing and accessible CLI experience. +Atmos provides a unified styling system for all terminal output. This includes documentation, command help text, error messages, and syntax highlighting. ## Configuration -The markdown styling is configured in your `atmos.yaml` file under the `settings.markdown` section. Here's a complete example: +The styling is configured in your `atmos.yaml` file under the `settings.terminal` section: ```yaml settings: - markdown: - # Base document styling - document: - color: "#FFFFFF" # Default text color - - # General heading styles - heading: - color: "#FF6B6B" # Color for all headings - bold: true - - # Level 1 heading (H1) - h1: - color: "#FFFFFF" - background_color: "#FF0000" # Background color for H1 - bold: true - margin: 2 - - # Level 2 heading (H2) - h2: - color: "#FFD700" # Golden yellow - bold: true + terminal: + # Color theme configuration + theme: + # Base colors used throughout the application + colors: + primary: "#00A3E0" # Atmos blue + secondary: "#9B51E0" # Purple + success: "#48BB78" # Green + warning: "#ECC94B" # Yellow + error: "#F56565" # Red + text: "#FFFFFF" # White + muted: "#4A5568" # Gray - # Level 3 heading (H3) - h3: - color: "#00FF00" # Bright green - bold: true - - # Code blocks - code_block: - color: "#00FFFF" # Cyan - margin: 1 - - # Block quotes - block_quote: - color: "#FF69B4" # Hot pink - indent: 2 - - # Links - link: - color: "#FFD700" # Golden yellow - underline: true + # Dark mode variants + dark: + text: "#F7FAFC" # Off white + muted: "#A0AEC0" # Light gray + background: "#1A202C" # Dark slate + + # Documentation and help text styling + docs: + max_width: 120 # Maximum width for documentation + pager: true # Use pager for long output - # Bold text - strong: - color: "#00FF00" # Bright green - bold: true + # Markdown element styling + markdown: + document: + color: "${colors.text}" + heading: + color: "${colors.primary}" + bold: true + code_block: + color: "${colors.secondary}" + margin: 1 + link: + color: "${colors.primary}" + underline: true + strong: + color: "${colors.secondary}" + bold: true + emph: + color: "${colors.muted}" + italic: true + + # Syntax highlighting configuration + syntax: + enabled: true + theme: "dracula" # or use custom colors from theme + line_numbers: true + wrap: false - # Italic text - emph: - color: "#00FFFF" # Cyan - italic: true + # Console output configuration + console: + pager: true # Enable pager for long output + timestamps: false # Show timestamps in logs + colors: true # Enable colored output + unicode: true # Use unicode characters ``` ## Style Properties From 1c5b72670c2150c58f151b1a0c941b4a10d22f6d Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Tue, 31 Dec 2024 21:32:19 +0000 Subject: [PATCH 36/49] error handling for edge cases --- internal/tui/utils/utils.go | 41 +++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/internal/tui/utils/utils.go b/internal/tui/utils/utils.go index 4486555f8..0cbe53380 100644 --- a/internal/tui/utils/utils.go +++ b/internal/tui/utils/utils.go @@ -2,13 +2,15 @@ package utils import ( "bytes" + "fmt" "os" "github.com/alecthomas/chroma/quick" "github.com/arsham/figurine/figurine" "github.com/charmbracelet/glamour" - "github.com/cloudposse/atmos/internal/tui/templates/term" + mdstyle "github.com/cloudposse/atmos/pkg/ui/markdown" "github.com/jwalton/go-supportscolor" + xterm "golang.org/x/term" ) // HighlightCode returns a syntax highlighted code for the specified language @@ -31,23 +33,40 @@ func PrintStyledText(text string) error { } // RenderMarkdown renders markdown text with terminal styling -func RenderMarkdown(markdown string, style string) (string, error) { - // If no style is provided, use the default style - if style == "" { - style = "dark" +func RenderMarkdown(markdownText string, style string) (string, error) { + if markdownText == "" { + return "", fmt.Errorf("empty markdown input") } - termWriter := term.NewResponsiveWriter(os.Stdout) - screenWidth := termWriter.(*term.TerminalWriter).GetWidth() + // Get the custom style from atmos config + customStyle, err := mdstyle.GetDefaultStyle() + if err != nil { + return "", fmt.Errorf("failed to get markdown style: %w", err) + } + + // Get terminal width safely + var screenWidth int + if w, _, err := xterm.GetSize(int(os.Stdout.Fd())); err == nil { + screenWidth = w + } else { + // Fallback to a reasonable default if we can't get the terminal width + screenWidth = 80 + } // Create a new renderer with the specified style r, err := glamour.NewTermRenderer( - glamour.WithAutoStyle(), - glamour.WithWordWrap(int(screenWidth)), + // Use our custom style if available + glamour.WithStylesFromJSONBytes(customStyle), + glamour.WithWordWrap(screenWidth), ) if err != nil { - return "", err + return "", fmt.Errorf("failed to create markdown renderer: %w", err) + } + + out, err := r.Render(markdownText) + if err != nil { + return "", fmt.Errorf("failed to render markdown: %w", err) } - return r.Render(markdown) + return out, nil } From 969fe28587d564e490cf7d89a5a70a990f284005 Mon Sep 17 00:00:00 2001 From: "Vinicius C." Date: Wed, 1 Jan 2025 08:49:15 +0000 Subject: [PATCH 37/49] Update website/docs/cli/configuration/markdown-styling.mdx Co-authored-by: Erik Osterman (CEO @ Cloud Posse) --- website/docs/cli/configuration/markdown-styling.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index e42e0fed2..30d88665c 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -30,8 +30,8 @@ settings: # Dark mode variants dark: - text: "#F7FAFC" # Off white - muted: "#A0AEC0" # Light gray + text: "#F7FAFC" # Off white + muted: "#A0AEC0" # Light gray background: "#1A202C" # Dark slate # Documentation and help text styling From eaea6876bcdb22c6b8b1806773329f905410a8a0 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Wed, 1 Jan 2025 10:47:01 +0000 Subject: [PATCH 38/49] update docs --- .../cli/configuration/markdown-styling.mdx | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 30d88665c..61946bbb4 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -234,20 +234,17 @@ settings: ``` ### Help Text -Enhance help text readability: -```yaml -settings: - markdown: - heading: - color: "#FFD700" # Golden headings - bold: true - code_block: - color: "#00FFFF" # Cyan code examples - link: - color: "#FFD700" - underline: true -``` +Atmos uses the [Glamour](https://github.com/charmbracelet/glamour) library for markdown rendering and styling. The styling is handled automatically based on your terminal's capabilities and color profile. + +Key features of the markdown rendering: + +- **Auto-styling**: Adapts to your terminal's color scheme +- **Word wrapping**: Automatically adjusts to terminal width +- **Emoji support**: Renders emoji characters when available +- **Rich formatting**: Supports headings, code blocks, links, and other markdown elements + +The styling is managed internally by Glamour and does not require manual configuration in your atmos settings. ## Best Practices From 99556f69fd3860427d0ff42cac4d8e9370ecf453 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Wed, 1 Jan 2025 12:02:59 +0000 Subject: [PATCH 39/49] update terminal settings --- cmd/docs.go | 14 +++++- pkg/schema/schema.go | 6 +++ .../docs/cli/configuration/configuration.mdx | 47 ++++++++++++++----- .../cli/configuration/markdown-styling.mdx | 38 ++++++--------- 4 files changed, 67 insertions(+), 38 deletions(-) diff --git a/cmd/docs.go b/cmd/docs.go index 650a74d25..4cc1fc542 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -41,7 +41,11 @@ var docsCmd = &cobra.Command{ // Detect terminal width if not specified in `atmos.yaml` // The default screen width is 120 characters, but uses maxWidth if set and greater than zero - maxWidth := atmosConfig.Settings.Docs.MaxWidth + maxWidth := atmosConfig.Settings.Terminal.MaxWidth + if maxWidth == 0 && atmosConfig.Settings.Docs.MaxWidth > 0 { + maxWidth = atmosConfig.Settings.Docs.MaxWidth + u.LogWarning(atmosConfig, "'settings.docs.max-width' is deprecated and will be removed in a future version. Please use 'settings.terminal.max_width' instead") + } defaultWidth := 120 screenWidth := defaultWidth @@ -97,7 +101,13 @@ var docsCmd = &cobra.Command{ u.LogErrorAndExit(schema.AtmosConfiguration{}, err) } - if err := u.DisplayDocs(componentDocs, atmosConfig.Settings.Docs.Pagination); err != nil { + usePager := atmosConfig.Settings.Terminal.Pager + if !usePager && atmosConfig.Settings.Docs.Pagination { + usePager = atmosConfig.Settings.Docs.Pagination + u.LogWarning(atmosConfig, "'settings.docs.pagination' is deprecated and will be removed in a future version. Please use 'settings.terminal.pager' instead") + } + + if err := u.DisplayDocs(componentDocs, usePager); err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to display documentation: %w", err)) } diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 2713a76a4..c308cf0e3 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -37,8 +37,14 @@ type AtmosConfiguration struct { Stores store.StoreRegistry `yaml:"stores_registry,omitempty" json:"stores_registry,omitempty" mapstructure:"stores_registry"` } +type Terminal struct { + MaxWidth int `yaml:"max_width" json:"max_width" mapstructure:"max_width"` + Pager bool `yaml:"pager" json:"pager" mapstructure:"pager"` +} + type AtmosSettings struct { ListMergeStrategy string `yaml:"list_merge_strategy" json:"list_merge_strategy" mapstructure:"list_merge_strategy"` + Terminal Terminal `yaml:"terminal,omitempty" json:"terminal,omitempty" mapstructure:"terminal"` Docs Docs `yaml:"docs,omitempty" json:"docs,omitempty" mapstructure:"docs"` Markdown MarkdownSettings `yaml:"markdown,omitempty" json:"markdown,omitempty" mapstructure:"markdown"` } diff --git a/website/docs/cli/configuration/configuration.mdx b/website/docs/cli/configuration/configuration.mdx index 9268c0cb3..24f6f948c 100644 --- a/website/docs/cli/configuration/configuration.mdx +++ b/website/docs/cli/configuration/configuration.mdx @@ -105,6 +105,11 @@ templates: # https://docs.gomplate.ca gomplate: enabled: true +settings: + list_merge_strategy: replace + terminal: + max_width: 120 # Maximum width for terminal output + pager: true # Use pager for long output ``` @@ -151,13 +156,16 @@ The `settings` section configures Atmos global settings. # If the source and destination lists have the same length, all items in the destination lists are # deep-merged with all items in the source list. list_merge_strategy: replace - # `docs` specifies how component documentation is displayed in the terminal. - # The following documentation display settings are supported: - # `max-width`: The maximum width for displaying component documentation in the terminal. - # 'pagination`: When enabled, displays component documentation in a pager instead of directly in the terminal. + + # Terminal settings for displaying content + terminal: + max_width: 120 # Maximum width for terminal output + pager: true # Use pager for long output + + # DEPRECATED: Use settings.terminal instead docs: - max-width: 80 - pagination: true + max-width: 120 # DEPRECATED: Use settings.terminal.max_width instead + pagination: true # DEPRECATED: Use settings.terminal.pager instead ``` @@ -179,17 +187,32 @@ The `settings` section configures Atmos global settings. -
`settings.docs`
+
`settings.terminal`
- Specifies how component documentation is displayed in the terminal. + Specifies how content is displayed in the terminal. The following settings are supported:
-
`max-width`
-
The maximum width for displaying component documentation in the terminal.
+
`max_width`
+
The maximum width for displaying content in the terminal.
+ +
`pager`
+
When enabled, displays long content in a pager instead of directly in the terminal.
+
+
+ +
`settings.docs` (Deprecated)
+
+ :::warning Deprecated + The `settings.docs` section is deprecated and will be removed in a future version. Please use `settings.terminal` instead. + ::: + +
+
`max-width` (Deprecated)
+
Use `settings.terminal.max_width` instead.
-
`pagination`
-
When enabled, displays component documentation in a pager instead of directly in the terminal.
+
`pagination` (Deprecated)
+
Use `settings.terminal.pager` instead.
diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 61946bbb4..78237e8fd 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -5,39 +5,28 @@ sidebar_position: 7 description: Configure custom markdown styling for Atmos CLI output --- -# Terminal Styling Configuration +# Markdown Styling -Atmos provides a unified styling system for all terminal output. This includes documentation, command help text, error messages, and syntax highlighting. + +Configure how Atmos displays markdown content in the terminal. + ## Configuration -The styling is configured in your `atmos.yaml` file under the `settings.terminal` section: +Configure markdown styling in your `atmos.yaml` configuration file: + ```yaml settings: - terminal: - # Color theme configuration - theme: - # Base colors used throughout the application - colors: - primary: "#00A3E0" # Atmos blue - secondary: "#9B51E0" # Purple - success: "#48BB78" # Green - warning: "#ECC94B" # Yellow - error: "#F56565" # Red - text: "#FFFFFF" # White - muted: "#4A5568" # Gray - - # Dark mode variants - dark: - text: "#F7FAFC" # Off white - muted: "#A0AEC0" # Light gray - background: "#1A202C" # Dark slate + # Terminal settings for displaying content + terminal: + max_width: 120 # Maximum width for terminal output + pager: true # Use pager for long output - # Documentation and help text styling + # DEPRECATED: Use settings.terminal instead docs: - max_width: 120 # Maximum width for documentation - pager: true # Use pager for long output + max_width: 120 # DEPRECATED: Use settings.terminal.max_width instead + pager: true # DEPRECATED: Use settings.terminal.pager instead # Markdown element styling markdown: @@ -73,6 +62,7 @@ settings: colors: true # Enable colored output unicode: true # Use unicode characters ``` + ## Style Properties From f63e012d3cd77e6640004f38d19b04d19e069b8c Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Wed, 1 Jan 2025 12:10:44 +0000 Subject: [PATCH 40/49] import statement --- website/docs/cli/configuration/markdown-styling.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 78237e8fd..f2cf149b2 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -5,6 +5,9 @@ sidebar_position: 7 description: Configure custom markdown styling for Atmos CLI output --- +import File from '@site/src/components/File' +import Intro from '@site/src/components/Intro' + # Markdown Styling From 6f448037eea0a59b4b1fba4fdd0dab8629f4d51a Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Thu, 2 Jan 2025 20:36:39 +0000 Subject: [PATCH 41/49] unify settings --- atmos.yaml | 45 ++++++++++++++++++++++++++++++++++++++++++++ pkg/schema/schema.go | 7 +++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/atmos.yaml b/atmos.yaml index e9793b62f..7d3f46ca8 100644 --- a/atmos.yaml +++ b/atmos.yaml @@ -321,3 +321,48 @@ version: enabled: true timeout: 1000 # ms frequency: 1h + + # Terminal settings for displaying content + terminal: + max_width: 120 # Maximum width for terminal output + pager: true # Use pager for long output + timestamps: false # Show timestamps in logs + colors: true # Enable colored output + unicode: true # Use unicode characters + + # Markdown element styling + markdown: + document: + color: "${colors.text}" + heading: + color: "${colors.primary}" + bold: true + code_block: + color: "${colors.secondary}" + margin: 1 + link: + color: "${colors.primary}" + underline: true + strong: + color: "${colors.secondary}" + bold: true + emph: + color: "${colors.muted}" + italic: true + + # Syntax highlighting configuration + syntax: + enabled: true + theme: "dracula" # or use custom colors from theme + line_numbers: true + wrap: false + + # DEPRECATED: settings.docs and settings.console are now consolidated under settings.terminal + # docs: + # max_width: 120 # DEPRECATED: Use settings.terminal.max_width instead + # pager: true # DEPRECATED: Use settings.terminal.pager instead + # console: + # pager: true # DEPRECATED: Use settings.terminal.pager instead + # timestamps: false # DEPRECATED: Use settings.terminal.timestamps instead + # colors: true # DEPRECATED: Use settings.terminal.colors instead + # unicode: true # DEPRECATED: Use settings.terminal.unicode instead diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index c308cf0e3..af7e3c5d2 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -38,8 +38,11 @@ type AtmosConfiguration struct { } type Terminal struct { - MaxWidth int `yaml:"max_width" json:"max_width" mapstructure:"max_width"` - Pager bool `yaml:"pager" json:"pager" mapstructure:"pager"` + MaxWidth int `yaml:"max_width" json:"max_width" mapstructure:"max_width"` + Pager bool `yaml:"pager" json:"pager" mapstructure:"pager"` + Timestamps bool `yaml:"timestamps" json:"timestamps" mapstructure:"timestamps"` + Colors bool `yaml:"colors" json:"colors" mapstructure:"colors"` + Unicode bool `yaml:"unicode" json:"unicode" mapstructure:"unicode"` } type AtmosSettings struct { From e94dc90f93d150e208ef32d3446b8bead7229e38 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Thu, 2 Jan 2025 21:02:42 +0000 Subject: [PATCH 42/49] remove example --- .../stacks/workflows/example.yaml | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 examples/demo-workflows/stacks/workflows/example.yaml diff --git a/examples/demo-workflows/stacks/workflows/example.yaml b/examples/demo-workflows/stacks/workflows/example.yaml deleted file mode 100644 index db3dd946c..000000000 --- a/examples/demo-workflows/stacks/workflows/example.yaml +++ /dev/null @@ -1,21 +0,0 @@ -workflows: - example: - name: "Example Workflow" - description: "This is a sample workflow to test markdown styling" - - steps: - - name: "List Components" - description: "List available components" - command: "list" - args: - - "components" - - - name: "List Stacks" - description: "List available stacks" - command: "list" - args: - - "stacks" - - - name: "Show Version" - description: "Display Atmos version" - command: "version" \ No newline at end of file From 940c6f8fc3a2a24f609ba8412daa155ef3cebd3a Mon Sep 17 00:00:00 2001 From: "Vinicius C." Date: Thu, 2 Jan 2025 21:04:22 +0000 Subject: [PATCH 43/49] remove deprecated options atmos.yaml Co-authored-by: Erik Osterman (CEO @ Cloud Posse) --- atmos.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/atmos.yaml b/atmos.yaml index 7d3f46ca8..c2a283efc 100644 --- a/atmos.yaml +++ b/atmos.yaml @@ -357,12 +357,3 @@ version: line_numbers: true wrap: false - # DEPRECATED: settings.docs and settings.console are now consolidated under settings.terminal - # docs: - # max_width: 120 # DEPRECATED: Use settings.terminal.max_width instead - # pager: true # DEPRECATED: Use settings.terminal.pager instead - # console: - # pager: true # DEPRECATED: Use settings.terminal.pager instead - # timestamps: false # DEPRECATED: Use settings.terminal.timestamps instead - # colors: true # DEPRECATED: Use settings.terminal.colors instead - # unicode: true # DEPRECATED: Use settings.terminal.unicode instead From bd275f539af7c4700170e1ef08cc0fe88f4f9a75 Mon Sep 17 00:00:00 2001 From: aknysh Date: Thu, 2 Jan 2025 20:26:30 -0500 Subject: [PATCH 44/49] updates --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 90bde46cb..ba46402a9 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/charmbracelet/log v0.4.0 github.com/elewis787/boa v0.1.2 github.com/fatih/color v1.18.0 - github.com/go-git/go-git/v5 v5.13.0 + github.com/go-git/go-git/v5 v5.13.1 github.com/gofrs/flock v0.12.1 github.com/google/go-containerregistry v0.20.2 github.com/google/go-github/v59 v59.0.0 @@ -40,7 +40,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.1 github.com/mitchellh/mapstructure v1.5.0 github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a - github.com/open-policy-agent/opa v0.70.0 + github.com/open-policy-agent/opa v1.0.0 github.com/otiai10/copy v1.14.0 github.com/pkg/errors v0.9.1 github.com/samber/lo v1.47.0 @@ -114,7 +114,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect - github.com/cyphar/filepath-securejoin v0.2.5 // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dlclark/regexp2 v1.11.0 // indirect github.com/docker/cli v27.1.1+incompatible // indirect @@ -128,7 +128,7 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.0 // indirect + github.com/go-git/go-billy/v5 v5.6.1 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/go.sum b/go.sum index 2e9c307f2..3f6cf9c6c 100644 --- a/go.sum +++ b/go.sum @@ -480,8 +480,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= -github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -513,8 +513,8 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA= github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= -github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug= -github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= +github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/elewis787/boa v0.1.2 h1:xNKWJ9X2MWbLSLLOA31N4l1Jdec9FZSkbTvXy3C8rw4= github.com/elewis787/boa v0.1.2/go.mod h1:EFDKuz/bYgQAKJQBnfHmB9i+bBzsaZJyyoSmOz6eBZI= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -562,12 +562,12 @@ github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1 github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= -github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= -github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= +github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= +github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= From f88bbf7e311dd57dcc076265f9c5988557939b23 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 3 Jan 2025 19:00:21 +0000 Subject: [PATCH 45/49] update yaml and clean up --- atmos.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/atmos.yaml b/atmos.yaml index c2a283efc..aa6e2bba3 100644 --- a/atmos.yaml +++ b/atmos.yaml @@ -316,12 +316,6 @@ settings: # deep-merged with all items in the source list. list_merge_strategy: replace -version: - check: - enabled: true - timeout: 1000 # ms - frequency: 1h - # Terminal settings for displaying content terminal: max_width: 120 # Maximum width for terminal output @@ -357,3 +351,9 @@ version: line_numbers: true wrap: false +version: + check: + enabled: true + timeout: 1000 # ms + frequency: 1h + From 6c38e5ac549ee45ef6f966438772e04247237f06 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 3 Jan 2025 19:40:54 +0000 Subject: [PATCH 46/49] general clean up --- cmd/workflow.go | 4 +++- internal/tui/utils/utils.go | 1 + website/docs/cli/configuration/configuration.mdx | 5 ----- website/docs/cli/configuration/markdown-styling.mdx | 5 ----- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/cmd/workflow.go b/cmd/workflow.go index b238c0f5b..1d9167967 100644 --- a/cmd/workflow.go +++ b/cmd/workflow.go @@ -93,7 +93,9 @@ var workflowCmd = &cobra.Command{ } if args[0] == "help" { - cmd.Help() + if err := cmd.Help(); err != nil { + u.LogErrorAndExit(schema.AtmosConfiguration{}, err) + } return } diff --git a/internal/tui/utils/utils.go b/internal/tui/utils/utils.go index 0cbe53380..e737ba3cf 100644 --- a/internal/tui/utils/utils.go +++ b/internal/tui/utils/utils.go @@ -62,6 +62,7 @@ func RenderMarkdown(markdownText string, style string) (string, error) { if err != nil { return "", fmt.Errorf("failed to create markdown renderer: %w", err) } + defer r.Close() out, err := r.Render(markdownText) if err != nil { diff --git a/website/docs/cli/configuration/configuration.mdx b/website/docs/cli/configuration/configuration.mdx index 24f6f948c..4f8570d02 100644 --- a/website/docs/cli/configuration/configuration.mdx +++ b/website/docs/cli/configuration/configuration.mdx @@ -161,11 +161,6 @@ The `settings` section configures Atmos global settings. terminal: max_width: 120 # Maximum width for terminal output pager: true # Use pager for long output - - # DEPRECATED: Use settings.terminal instead - docs: - max-width: 120 # DEPRECATED: Use settings.terminal.max_width instead - pagination: true # DEPRECATED: Use settings.terminal.pager instead ```
diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index f2cf149b2..a71bc1e37 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -25,11 +25,6 @@ settings: terminal: max_width: 120 # Maximum width for terminal output pager: true # Use pager for long output - - # DEPRECATED: Use settings.terminal instead - docs: - max_width: 120 # DEPRECATED: Use settings.terminal.max_width instead - pager: true # DEPRECATED: Use settings.terminal.pager instead # Markdown element styling markdown: From 448f75b0f3a54fb0c74722484d7f3aadc0c99715 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 3 Jan 2025 19:56:51 +0000 Subject: [PATCH 47/49] update docs --- website/docs/cli/configuration/markdown-styling.mdx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index a71bc1e37..134931039 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -25,6 +25,9 @@ settings: terminal: max_width: 120 # Maximum width for terminal output pager: true # Use pager for long output + timestamps: false + colors: true + unicode: true # Markdown element styling markdown: @@ -53,12 +56,6 @@ settings: line_numbers: true wrap: false - # Console output configuration - console: - pager: true # Enable pager for long output - timestamps: false # Show timestamps in logs - colors: true # Enable colored output - unicode: true # Use unicode characters ```
From 78f216ce7c30b55378e6c4a363a4aaeb6dc94e9c Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 3 Jan 2025 22:05:24 +0000 Subject: [PATCH 48/49] remove syntax --- atmos.yaml | 7 ------- website/docs/cli/configuration/markdown-styling.mdx | 7 ------- 2 files changed, 14 deletions(-) diff --git a/atmos.yaml b/atmos.yaml index aa6e2bba3..5d3492eeb 100644 --- a/atmos.yaml +++ b/atmos.yaml @@ -344,13 +344,6 @@ settings: color: "${colors.muted}" italic: true - # Syntax highlighting configuration - syntax: - enabled: true - theme: "dracula" # or use custom colors from theme - line_numbers: true - wrap: false - version: check: enabled: true diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index 134931039..ead6c5f54 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -48,13 +48,6 @@ settings: emph: color: "${colors.muted}" italic: true - - # Syntax highlighting configuration - syntax: - enabled: true - theme: "dracula" # or use custom colors from theme - line_numbers: true - wrap: false ```
From 1841d5d7bb315f4b3629110293c6cb25d6ee29ac Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Fri, 3 Jan 2025 22:13:08 +0000 Subject: [PATCH 49/49] clean up spaces --- .../cli/configuration/markdown-styling.mdx | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/website/docs/cli/configuration/markdown-styling.mdx b/website/docs/cli/configuration/markdown-styling.mdx index ead6c5f54..5d5bd9746 100644 --- a/website/docs/cli/configuration/markdown-styling.mdx +++ b/website/docs/cli/configuration/markdown-styling.mdx @@ -21,33 +21,33 @@ Configure markdown styling in your `atmos.yaml` configuration file: ```yaml settings: - # Terminal settings for displaying content - terminal: - max_width: 120 # Maximum width for terminal output - pager: true # Use pager for long output - timestamps: false - colors: true - unicode: true - - # Markdown element styling - markdown: - document: - color: "${colors.text}" - heading: - color: "${colors.primary}" - bold: true - code_block: - color: "${colors.secondary}" - margin: 1 - link: - color: "${colors.primary}" - underline: true - strong: - color: "${colors.secondary}" - bold: true - emph: - color: "${colors.muted}" - italic: true + # Terminal settings for displaying content + terminal: + max_width: 120 # Maximum width for terminal output + pager: true # Use pager for long output + timestamps: false + colors: true + unicode: true + + # Markdown element styling + markdown: + document: + color: "${colors.text}" + heading: + color: "${colors.primary}" + bold: true + code_block: + color: "${colors.secondary}" + margin: 1 + link: + color: "${colors.primary}" + underline: true + strong: + color: "${colors.secondary}" + bold: true + emph: + color: "${colors.muted}" + italic: true ``` @@ -270,4 +270,4 @@ The styling is managed internally by Glamour and does not require manual configu ## See Also - [CLI Configuration](/cli/configuration) -- [Command Reference](/cli/commands) \ No newline at end of file +- [Command Reference](/cli/commands) \ No newline at end of file