Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve atmos validate stacks and atmos describe affected commands #608

Merged
merged 12 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/describe_affected.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func init() {
describeAffectedCmd.PersistentFlags().String("repo-path", "", "Filesystem path to the already cloned target repository with which to compare the current branch: atmos describe affected --repo-path <path_to_already_cloned_repo>")
describeAffectedCmd.PersistentFlags().String("ref", "", "Git reference with which to compare the current branch: atmos describe affected --ref refs/heads/main. Refer to https://git-scm.com/book/en/v2/Git-Internals-Git-References for more details")
describeAffectedCmd.PersistentFlags().String("sha", "", "Git commit SHA with which to compare the current branch: atmos describe affected --sha 3a5eafeab90426bd82bf5899896b28cc0bab3073")
describeAffectedCmd.PersistentFlags().String("file", "", "Write the result to the file: atmos describe affected --ref refs/tags/v1.71.0 --file affected.json")
describeAffectedCmd.PersistentFlags().String("file", "", "Write the result to the file: atmos describe affected --ref refs/tags/v1.75.0 --file affected.json")
describeAffectedCmd.PersistentFlags().String("format", "json", "The output format: atmos describe affected --format=json|yaml ('json' is default)")
describeAffectedCmd.PersistentFlags().Bool("verbose", false, "Print more detailed output when cloning and checking out the Git repository: atmos describe affected --verbose=true")
describeAffectedCmd.PersistentFlags().String("ssh-key", "", "Path to PEM-encoded private key to clone private repos using SSH: atmos describe affected --ssh-key <path_to_ssh_key>")
Expand Down
6 changes: 3 additions & 3 deletions examples/quick-start/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Geodesic: https://github.com/cloudposse/geodesic/
ARG GEODESIC_VERSION=2.11.2
ARG GEODESIC_VERSION=2.11.3
ARG GEODESIC_OS=debian

# Atmos
# https://atmos.tools/
# https://github.com/cloudposse/atmos
# https://github.com/cloudposse/atmos/releases
ARG ATMOS_VERSION=1.74.0
ARG ATMOS_VERSION=1.75.0

# Terraform: https://github.com/hashicorp/terraform/releases
ARG TF_VERSION=1.8.1
ARG TF_VERSION=1.8.4

FROM cloudposse/geodesic:${GEODESIC_VERSION}-${GEODESIC_OS}

Expand Down
9 changes: 7 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ require (
github.com/arsham/figurine v1.3.0
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.26.2
github.com/charmbracelet/lipgloss v0.10.0
github.com/charmbracelet/bubbletea v0.26.3
github.com/charmbracelet/lipgloss v0.11.0
github.com/elewis787/boa v0.1.2
github.com/fatih/color v1.17.0
github.com/go-git/go-git/v5 v5.12.0
Expand Down Expand Up @@ -90,6 +90,10 @@ require (
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/charmbracelet/x/ansi v0.1.1 // indirect
github.com/charmbracelet/x/input v0.1.0 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/containerd v1.7.15 // indirect
Expand Down Expand Up @@ -207,6 +211,7 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
github.com/zealic/xignore v0.3.3 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
Expand Down
18 changes: 14 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -386,10 +386,18 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.26.2 h1:Eeb+n75Om9gQ+I6YpbCXQRKHt5Pn4vMwusQpwLiEgJQ=
github.com/charmbracelet/bubbletea v0.26.2/go.mod h1:6I0nZ3YHUrQj7YHIHlM8RySX4ZIthTliMY+W8X8b+Gs=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/charmbracelet/bubbletea v0.26.3 h1:iXyGvI+FfOWqkB2V07m1DF3xxQijxjY2j8PqiXYqasg=
github.com/charmbracelet/bubbletea v0.26.3/go.mod h1:bpZHfDHTYJC5g+FBK+ptJRCQotRC+Dhh3AoMxa/2+3Q=
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk=
github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ=
github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28=
github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw=
github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4=
github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
Expand Down Expand Up @@ -1160,6 +1168,8 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMc
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg=
github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
9 changes: 9 additions & 0 deletions internal/exec/describe_affected.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ func ExecuteDescribeAffectedCmd(cmd *cobra.Command, args []string) error {
return err
}

err = ValidateStacks(cliConfig)
if err != nil {
return err
}

// Process flags
flags := cmd.Flags()

Expand Down Expand Up @@ -102,6 +107,10 @@ func ExecuteDescribeAffectedCmd(cmd *cobra.Command, args []string) error {
return err
}

if verbose {
cliConfig.Logs.Level = u.LogLevelTrace
}

u.LogTrace(cliConfig, fmt.Sprintf("\nAffected components and stacks: \n"))

err = printOrWriteToFile(format, file, affected)
Expand Down
20 changes: 13 additions & 7 deletions internal/exec/describe_affected_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,21 +492,27 @@ func executeDescribeAffected(
}

u.LogTrace(cliConfig, fmt.Sprintf("Got remote repo commit tree"))
u.LogTrace(cliConfig, fmt.Sprintf("Finding diff between the current working branch and remote target branch ..."))
u.LogTrace(cliConfig, fmt.Sprintf("Finding difference between the current working branch and remote target branch ..."))

// Find a slice of Patch objects with all the changes between the current working and remote trees
patch, err := localTree.Patch(remoteTree)
if err != nil {
return nil, err
}

u.LogTrace(cliConfig, fmt.Sprintf("Found diff between the current working branch and remote target branch"))
u.LogTrace(cliConfig, "\nChanged files:\n")

var changedFiles []string
for _, fileStat := range patch.Stats() {
u.LogTrace(cliConfig, fileStat.Name)
changedFiles = append(changedFiles, fileStat.Name)

if len(patch.Stats()) > 0 {
u.LogTrace(cliConfig, fmt.Sprintf("Found difference between the current working branch and remote target branch"))
u.LogTrace(cliConfig, "\nChanged files:\n")

for _, fileStat := range patch.Stats() {
u.LogTrace(cliConfig, fileStat.Name)
changedFiles = append(changedFiles, fileStat.Name)
}
u.LogTrace(cliConfig, "")
} else {
u.LogTrace(cliConfig, fmt.Sprintf("The current working branch and remote target branch are the same"))
}

affected, err := findAffected(currentStacks, remoteStacks, cliConfig, changedFiles, includeSpaceliftAdminStacks)
Expand Down
183 changes: 174 additions & 9 deletions internal/exec/validate_stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra"

cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/schema"
s "github.com/cloudposse/atmos/pkg/stack"
u "github.com/cloudposse/atmos/pkg/utils"
)
Expand Down Expand Up @@ -36,6 +37,44 @@ func ExecuteValidateStacksCmd(cmd *cobra.Command, args []string) error {
cliConfig.Schemas.Atmos.Manifest = schemasAtmosManifestFlag
}

return ValidateStacks(cliConfig)
}

// ValidateStacks validates Atmos stack configuration
func ValidateStacks(cliConfig schema.CliConfiguration) error {
var validationErrorMessages []string

// 1. Process top-level stack manifests and detect duplicate components in the same stack
stacksMap, _, err := FindStacksMap(cliConfig, false)
if err != nil {
return err
}

terraformComponentStackMap, err := createComponentStackMap(cliConfig, stacksMap, cfg.TerraformSectionName)
if err != nil {
return err
}

errorList, err := checkComponentStackMap(terraformComponentStackMap)
if err != nil {
return err
}
validationErrorMessages = append(validationErrorMessages, errorList...)

helmfileComponentStackMap, err := createComponentStackMap(cliConfig, stacksMap, cfg.HelmfileSectionName)
if err != nil {
return err
}

errorList, err = checkComponentStackMap(helmfileComponentStackMap)
if err != nil {
return err
}
validationErrorMessages = append(validationErrorMessages, errorList...)

// 2. Check all YAML stack manifests defined in the infrastructure
// It will check YAML syntax and all the Atmos sections defined in the manifests

// Check if the Atmos manifest JSON Schema is configured and the file exists
// The path to the Atmos manifest JSON Schema can be absolute path or a path relative to the `base_path` setting in `atmos.yaml`
var atmosManifestJsonSchemaFilePath string
Expand Down Expand Up @@ -73,8 +112,6 @@ func ExecuteValidateStacksCmd(cmd *cobra.Command, args []string) error {
u.LogDebug(cliConfig, fmt.Sprintf("Validating all YAML files in the '%s' folder and all subfolders\n",
path.Join(cliConfig.BasePath, cliConfig.Stacks.BasePath)))

var errorMessages []string

for _, filePath := range stackConfigFilesAbsolutePaths {
stackConfig, importsConfig, _, err := s.ProcessYAMLConfigFile(
cliConfig,
Expand All @@ -91,11 +128,10 @@ func ExecuteValidateStacksCmd(cmd *cobra.Command, args []string) error {
atmosManifestJsonSchemaFilePath,
)
if err != nil {
errorMessages = append(errorMessages, err.Error())
validationErrorMessages = append(validationErrorMessages, err.Error())
}

// Process and validate the stack manifest
componentStackMap := map[string]map[string][]string{}
_, err = s.ProcessStackConfig(
cliConfig,
cliConfig.StacksBaseAbsolutePath,
Expand All @@ -106,17 +142,146 @@ func ExecuteValidateStacksCmd(cmd *cobra.Command, args []string) error {
false,
true,
"",
componentStackMap,
map[string]map[string][]string{},
importsConfig,
false)
false,
)
if err != nil {
errorMessages = append(errorMessages, err.Error())
validationErrorMessages = append(validationErrorMessages, err.Error())
}
}

if len(errorMessages) > 0 {
return errors.New(strings.Join(errorMessages, "\n\n"))
if len(validationErrorMessages) > 0 {
return errors.New(strings.Join(validationErrorMessages, "\n\n"))
}

return nil
}

func createComponentStackMap(
cliConfig schema.CliConfiguration,
stacksMap map[string]any,
componentType string,
) (map[string]map[string][]string, error) {
var varsSection map[any]any
var metadataSection map[any]any
var settingsSection map[any]any
var envSection map[any]any
var providersSection map[any]any
var overridesSection map[any]any
var backendSection map[any]any
var backendTypeSection string
var stackName string
var err error
terraformComponentStackMap := make(map[string]map[string][]string)

for stackManifest, stackSection := range stacksMap {
if componentsSection, ok := stackSection.(map[any]any)[cfg.ComponentsSectionName].(map[string]any); ok {

// Terraform components
if terraformSection, ok := componentsSection[componentType].(map[string]any); ok {
for componentName, compSection := range terraformSection {
componentSection, ok := compSection.(map[string]any)

if varsSection, ok = componentSection[cfg.VarsSectionName].(map[any]any); !ok {
varsSection = map[any]any{}
}

if metadataSection, ok = componentSection[cfg.MetadataSectionName].(map[any]any); !ok {
metadataSection = map[any]any{}
}

if settingsSection, ok = componentSection[cfg.SettingsSectionName].(map[any]any); !ok {
settingsSection = map[any]any{}
}

if envSection, ok = componentSection[cfg.EnvSectionName].(map[any]any); !ok {
envSection = map[any]any{}
}

if providersSection, ok = componentSection[cfg.ProvidersSectionName].(map[any]any); !ok {
providersSection = map[any]any{}
}

if overridesSection, ok = componentSection[cfg.OverridesSectionName].(map[any]any); !ok {
overridesSection = map[any]any{}
}

if backendSection, ok = componentSection[cfg.BackendSectionName].(map[any]any); !ok {
backendSection = map[any]any{}
}

if backendTypeSection, ok = componentSection[cfg.BackendTypeSectionName].(string); !ok {
backendTypeSection = ""
}

configAndStacksInfo := schema.ConfigAndStacksInfo{
ComponentFromArg: componentName,
Stack: stackName,
ComponentMetadataSection: metadataSection,
ComponentVarsSection: varsSection,
ComponentSettingsSection: settingsSection,
ComponentEnvSection: envSection,
ComponentProvidersSection: providersSection,
ComponentOverridesSection: overridesSection,
ComponentBackendSection: backendSection,
ComponentBackendType: backendTypeSection,
ComponentSection: map[string]any{
cfg.VarsSectionName: varsSection,
cfg.MetadataSectionName: metadataSection,
cfg.SettingsSectionName: settingsSection,
cfg.EnvSectionName: envSection,
cfg.ProvidersSectionName: providersSection,
cfg.OverridesSectionName: overridesSection,
cfg.BackendSectionName: backendSection,
cfg.BackendTypeSectionName: backendTypeSection,
},
}

// Find Atmos stack name
if cliConfig.Stacks.NameTemplate != "" {
stackName, err = u.ProcessTmpl("validate-stacks-name-template", cliConfig.Stacks.NameTemplate, configAndStacksInfo.ComponentSection, false)
if err != nil {
return nil, err
}
} else {
context := cfg.GetContextFromVars(varsSection)
configAndStacksInfo.Context = context
stackName, err = cfg.GetContextPrefix(stackManifest, context, GetStackNamePattern(cliConfig), stackManifest)
if err != nil {
return nil, err
}
}

_, ok = terraformComponentStackMap[componentName]
if !ok {
terraformComponentStackMap[componentName] = make(map[string][]string)
}
terraformComponentStackMap[componentName][stackName] = append(terraformComponentStackMap[componentName][stackName], stackManifest)
}
}
}
}

return terraformComponentStackMap, nil
}

func checkComponentStackMap(componentStackMap map[string]map[string][]string) ([]string, error) {
var res []string

for componentName, componentSection := range componentStackMap {
for stackName, stackManifests := range componentSection {
if len(stackManifests) > 1 {
m := fmt.Sprintf("the Atmos component '%s' in the stack '%s' is defined in more than one top-level stack manifest files: %s.\n"+
aknysh marked this conversation as resolved.
Show resolved Hide resolved
"Atmos can't decide which stack manifest to use to get configuration for the component in the stack.\n"+
"This is a stack misconfiguration.",
componentName,
stackName,
strings.Join(stackManifests, ", "))
res = append(res, m)
}
}
}

return res, nil
}
3 changes: 3 additions & 0 deletions pkg/config/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ const (
BackendTypeSectionName = "backend_type"
MetadataSectionName = "metadata"
ComponentSectionName = "component"
ComponentsSectionName = "components"
CommandSectionName = "command"
TerraformSectionName = "terraform"
HelmfileSectionName = "helmfile"

LogsLevelFlag = "--logs-level"
LogsFileFlag = "--logs-file"
Expand Down
Loading
Loading