Skip to content

Commit 737d868

Browse files
committed
Merge remote-tracking branch 'origin/main' into osterman/stack-source-list. Add tests
2 parents 80ace24 + 7c24836 commit 737d868

File tree

120 files changed

+9652
-1706
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+9652
-1706
lines changed

NOTICE

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ APACHE 2.0 LICENSED DEPENDENCIES
2323

2424
- cloud.google.com/go/auth
2525
License: Apache-2.0
26-
URL: https://github.com/googleapis/google-cloud-go/blob/auth/v0.17.0/auth/LICENSE
26+
URL: https://github.com/googleapis/google-cloud-go/blob/auth/v0.18.0/auth/LICENSE
2727

2828
- cloud.google.com/go/auth/oauth2adapt
2929
License: Apache-2.0
@@ -307,7 +307,7 @@ APACHE 2.0 LICENSED DEPENDENCIES
307307

308308
- github.com/googleapis/enterprise-certificate-proxy/client
309309
License: Apache-2.0
310-
URL: https://github.com/googleapis/enterprise-certificate-proxy/blob/v0.3.7/LICENSE
310+
URL: https://github.com/googleapis/enterprise-certificate-proxy/blob/v0.3.9/LICENSE
311311

312312
- github.com/gosimple/unidecode
313313
License: Apache-2.0
@@ -507,15 +507,15 @@ APACHE 2.0 LICENSED DEPENDENCIES
507507

508508
- google.golang.org/genproto/googleapis/rpc
509509
License: Apache-2.0
510-
URL: https://github.com/googleapis/go-genproto/blob/97cd9d5aeac2/googleapis/rpc/LICENSE
510+
URL: https://github.com/googleapis/go-genproto/blob/0a764e51fe1b/googleapis/rpc/LICENSE
511511

512512
- google.golang.org/grpc
513513
License: Apache-2.0
514514
URL: https://github.com/grpc/grpc-go/blob/v1.78.0/LICENSE
515515

516516
- gopkg.in/ini.v1
517517
License: Apache-2.0
518-
URL: https://github.com/go-ini/ini/blob/v1.67.0/LICENSE
518+
URL: https://github.com/go-ini/ini/blob/v1.67.1/LICENSE
519519

520520
- gopkg.in/yaml.v2
521521
License: Apache-2.0
@@ -788,11 +788,11 @@ BSD LICENSED DEPENDENCIES
788788

789789
- google.golang.org/api
790790
License: BSD-3-Clause
791-
URL: https://github.com/googleapis/google-api-go-client/blob/v0.258.0/LICENSE
791+
URL: https://github.com/googleapis/google-api-go-client/blob/v0.260.0/LICENSE
792792

793793
- google.golang.org/api/internal/third_party/uritemplates
794794
License: BSD-3-Clause
795-
URL: https://github.com/googleapis/google-api-go-client/blob/v0.258.0/internal/third_party/uritemplates/LICENSE
795+
URL: https://github.com/googleapis/google-api-go-client/blob/v0.260.0/internal/third_party/uritemplates/LICENSE
796796

797797
- google.golang.org/protobuf
798798
License: BSD-3-Clause
@@ -948,6 +948,10 @@ MOZILLA PUBLIC LICENSE (MPL) 2.0 DEPENDENCIES
948948

949949
MIT LICENSED DEPENDENCIES
950950

951+
- al.essio.dev/pkg/shellescape
952+
License: MIT
953+
URL: https://github.com/alessio/shellescape/blob/v1.5.1/LICENSE
954+
951955
- github.com/99designs/keyring
952956
License: MIT
953957
URL: https://github.com/99designs/keyring/blob/v1.2.2/LICENSE
@@ -1074,7 +1078,7 @@ MIT LICENSED DEPENDENCIES
10741078

10751079
- github.com/bmatcuk/doublestar/v4
10761080
License: MIT
1077-
URL: https://github.com/bmatcuk/doublestar/blob/v4.9.1/LICENSE
1081+
URL: https://github.com/bmatcuk/doublestar/blob/v4.9.2/LICENSE
10781082

10791083
- github.com/catppuccin/go
10801084
License: MIT

cmd/about/about.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import (
1111
)
1212

1313
// aboutCmd represents the about command.
14+
// Args validator is auto-applied by the command registry for commands without PositionalArgsBuilder.
1415
var aboutCmd = &cobra.Command{
1516
Use: "about",
1617
Short: "Learn about Atmos",
1718
Long: `Display information about Atmos, its features, and benefits.`,
18-
Args: cobra.NoArgs,
1919
RunE: func(cmd *cobra.Command, args []string) error {
2020
ui.Markdown(markdown.AboutMarkdown)
2121
return nil

cmd/env/env.go

Lines changed: 60 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package env
22

33
import (
4-
"fmt"
5-
"os"
64
"slices"
7-
"sort"
85
"strings"
96

107
"github.com/spf13/cobra"
@@ -14,29 +11,26 @@ import (
1411
errUtils "github.com/cloudposse/atmos/errors"
1512
cfg "github.com/cloudposse/atmos/pkg/config"
1613
"github.com/cloudposse/atmos/pkg/data"
14+
envfmt "github.com/cloudposse/atmos/pkg/env"
1715
"github.com/cloudposse/atmos/pkg/flags"
1816
"github.com/cloudposse/atmos/pkg/flags/compat"
17+
ghactions "github.com/cloudposse/atmos/pkg/github/actions"
1918
"github.com/cloudposse/atmos/pkg/schema"
2019
u "github.com/cloudposse/atmos/pkg/utils"
2120
)
2221

23-
const (
24-
// DefaultFileMode is the file mode for output files.
25-
defaultFileMode = 0o644
26-
)
27-
2822
// SupportedFormats lists all supported output formats.
2923
var SupportedFormats = []string{"bash", "json", "dotenv", "github"}
3024

3125
// envParser handles flag parsing with Viper precedence.
3226
var envParser *flags.StandardParser
3327

3428
// envCmd outputs environment variables from atmos.yaml.
29+
// Args validator is auto-applied by the command registry for commands without PositionalArgsBuilder.
3530
var envCmd = &cobra.Command{
3631
Use: "env",
3732
Short: "Output environment variables configured in atmos.yaml",
3833
Long: `Outputs environment variables from the 'env' section of atmos.yaml in various formats suitable for shell evaluation, .env files, JSON consumption, or GitHub Actions workflows.`,
39-
Args: cobra.NoArgs,
4034
RunE: func(cmd *cobra.Command, args []string) error {
4135
// Parse flags using Viper (respects precedence: flags > env > config > defaults).
4236
v := viper.GetViper()
@@ -45,16 +39,17 @@ var envCmd = &cobra.Command{
4539
}
4640

4741
// Get output format.
48-
format := v.GetString("format")
49-
if !slices.Contains(SupportedFormats, format) {
42+
formatStr := v.GetString("format")
43+
if !slices.Contains(SupportedFormats, formatStr) {
5044
return errUtils.Build(errUtils.ErrInvalidArgumentError).
51-
WithExplanationf("Invalid --format value %q.", format).
45+
WithExplanationf("Invalid --format value %q.", formatStr).
5246
WithHintf("Supported formats: %s.", strings.Join(SupportedFormats, ", ")).
5347
Err()
5448
}
5549

56-
// Get output file path.
50+
// Get output file path and export flag.
5751
output := v.GetString("output")
52+
exportPrefix := v.GetBool("export")
5853

5954
// Build ConfigAndStacksInfo with CLI overrides (--config, --config-path, --base-path).
6055
// These are persistent flags inherited from the root command.
@@ -85,50 +80,60 @@ var envCmd = &cobra.Command{
8580
envVars = make(map[string]string)
8681
}
8782

88-
// Handle GitHub format special case.
89-
if format == "github" {
90-
if output == "" {
91-
// GITHUB_ENV is an external CI environment variable set by GitHub Actions,
92-
// not an Atmos configuration variable, so os.Getenv is appropriate here.
93-
//nolint:forbidigo // GITHUB_ENV is an external CI env var, not Atmos config
94-
output = os.Getenv("GITHUB_ENV")
95-
if output == "" {
83+
// Handle JSON format separately (not supported by pkg/env).
84+
if formatStr == "json" {
85+
if output != "" {
86+
return u.WriteToFileAsJSON(output, envVars, 0o644)
87+
}
88+
return outputEnvAsJSON(&atmosConfig, envVars)
89+
}
90+
91+
// Handle GitHub format special case (requires output path).
92+
if formatStr == "github" {
93+
path := output
94+
if path == "" {
95+
path = ghactions.GetEnvPath()
96+
if path == "" {
9697
return errUtils.Build(errUtils.ErrRequiredFlagNotProvided).
9798
WithExplanation("--format=github requires GITHUB_ENV environment variable to be set, or use --output to specify a file path.").
9899
Err()
99100
}
100101
}
101-
return writeEnvToFile(envVars, output, formatGitHub)
102+
dataMap := convertToAnyMap(envVars)
103+
formatted, err := envfmt.FormatData(dataMap, envfmt.FormatGitHub)
104+
if err != nil {
105+
return errUtils.Build(errUtils.ErrInvalidArgumentError).
106+
WithCause(err).
107+
WithExplanation("Failed to format environment variables for GitHub output.").
108+
Err()
109+
}
110+
return envfmt.WriteToFile(path, formatted)
102111
}
103112

104-
// Handle file output for other formats.
105-
if output != "" {
106-
var formatter func(map[string]string) string
107-
switch format {
108-
case "bash":
109-
formatter = formatBash
110-
case "dotenv":
111-
formatter = formatDotenv
112-
case "json":
113-
// For JSON file output, use the utility function.
114-
return u.WriteToFileAsJSON(output, envVars, defaultFileMode)
115-
default:
116-
formatter = formatBash
117-
}
118-
return writeEnvToFile(envVars, output, formatter)
113+
// Parse format string to Format type.
114+
format, err := envfmt.ParseFormat(formatStr)
115+
if err != nil {
116+
return errUtils.Build(errUtils.ErrInvalidArgumentError).
117+
WithCause(err).
118+
WithExplanationf("Invalid --format value %q.", formatStr).
119+
Err()
119120
}
120121

121-
// Output to stdout.
122-
switch format {
123-
case "json":
124-
return outputEnvAsJSON(&atmosConfig, envVars)
125-
case "bash":
126-
return outputEnvAsBash(envVars)
127-
case "dotenv":
128-
return outputEnvAsDotenv(envVars)
129-
default:
130-
return outputEnvAsBash(envVars)
122+
// Format the environment variables with export option.
123+
dataMap := convertToAnyMap(envVars)
124+
formatted, err := envfmt.FormatData(dataMap, format, envfmt.WithExport(exportPrefix))
125+
if err != nil {
126+
return errUtils.Build(errUtils.ErrInvalidArgumentError).
127+
WithCause(err).
128+
WithExplanation("Failed to format environment variables.").
129+
Err()
130+
}
131+
132+
// Output to file or stdout.
133+
if output != "" {
134+
return envfmt.WriteToFile(output, formatted)
131135
}
136+
return data.Write(formatted)
132137
},
133138
}
134139

@@ -137,93 +142,24 @@ func outputEnvAsJSON(atmosConfig *schema.AtmosConfiguration, envVars map[string]
137142
return u.PrintAsJSON(atmosConfig, envVars)
138143
}
139144

140-
// outputEnvAsBash outputs environment variables as shell export statements.
141-
func outputEnvAsBash(envVars map[string]string) error {
142-
return data.Write(formatBash(envVars))
143-
}
144-
145-
// outputEnvAsDotenv outputs environment variables in .env format.
146-
func outputEnvAsDotenv(envVars map[string]string) error {
147-
return data.Write(formatDotenv(envVars))
148-
}
149-
150-
// formatBash formats environment variables as shell export statements.
151-
func formatBash(envVars map[string]string) string {
152-
keys := sortedKeys(envVars)
153-
var sb strings.Builder
154-
for _, key := range keys {
155-
value := envVars[key]
156-
// Escape single quotes for safe single-quoted shell literals: ' -> '\''.
157-
safe := strings.ReplaceAll(value, "'", "'\\''")
158-
sb.WriteString(fmt.Sprintf("export %s='%s'\n", key, safe))
159-
}
160-
return sb.String()
161-
}
162-
163-
// formatDotenv formats environment variables in .env format.
164-
func formatDotenv(envVars map[string]string) string {
165-
keys := sortedKeys(envVars)
166-
var sb strings.Builder
167-
for _, key := range keys {
168-
value := envVars[key]
169-
// Use the same safe single-quoted escaping as bash output.
170-
safe := strings.ReplaceAll(value, "'", "'\\''")
171-
sb.WriteString(fmt.Sprintf("%s='%s'\n", key, safe))
172-
}
173-
return sb.String()
174-
}
175-
176-
// formatGitHub formats environment variables for GitHub Actions $GITHUB_ENV file.
177-
// Uses KEY=value format without quoting. For multiline values, GitHub uses heredoc syntax.
178-
func formatGitHub(envVars map[string]string) string {
179-
keys := sortedKeys(envVars)
180-
var sb strings.Builder
181-
for _, key := range keys {
182-
value := envVars[key]
183-
// Check if value contains newlines - use heredoc syntax.
184-
// Use ATMOS_EOF_ prefix to avoid collision with values containing "EOF".
185-
if strings.Contains(value, "\n") {
186-
sb.WriteString(fmt.Sprintf("%s<<ATMOS_EOF_%s\n%s\nATMOS_EOF_%s\n", key, key, value, key))
187-
} else {
188-
sb.WriteString(fmt.Sprintf("%s=%s\n", key, value))
189-
}
190-
}
191-
return sb.String()
192-
}
193-
194-
// writeEnvToFile writes formatted environment variables to a file (append mode).
195-
func writeEnvToFile(envVars map[string]string, filePath string, formatter func(map[string]string) string) error {
196-
// Open file in append mode, create if doesn't exist.
197-
f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, defaultFileMode)
198-
if err != nil {
199-
return fmt.Errorf("failed to open file '%s': %w", filePath, err)
200-
}
201-
defer f.Close()
202-
203-
content := formatter(envVars)
204-
if _, err := f.WriteString(content); err != nil {
205-
return fmt.Errorf("failed to write to file '%s': %w", filePath, err)
206-
}
207-
return nil
208-
}
209-
210-
// sortedKeys returns the keys of a map sorted alphabetically.
211-
func sortedKeys(m map[string]string) []string {
212-
keys := make([]string, 0, len(m))
213-
for k := range m {
214-
keys = append(keys, k)
145+
// convertToAnyMap converts a map[string]string to map[string]any for use with env formatters.
146+
func convertToAnyMap(envVars map[string]string) map[string]any {
147+
result := make(map[string]any, len(envVars))
148+
for k, v := range envVars {
149+
result[k] = v
215150
}
216-
sort.Strings(keys)
217-
return keys
151+
return result
218152
}
219153

220154
func init() {
221155
// Create parser with env-specific flags using functional options.
222156
envParser = flags.NewStandardParser(
223157
flags.WithStringFlag("format", "f", "bash", "Output format: bash, json, dotenv, github"),
224158
flags.WithStringFlag("output", "o", "", "Output file path (default: stdout, or $GITHUB_ENV for github format)"),
159+
flags.WithBoolFlag("export", "", true, "Include 'export' prefix in bash format (default: true)"),
225160
flags.WithEnvVars("format", "ATMOS_ENV_FORMAT"),
226161
flags.WithEnvVars("output", "ATMOS_ENV_OUTPUT"),
162+
flags.WithEnvVars("export", "ATMOS_ENV_EXPORT"),
227163
)
228164

229165
// Register flags using the standard RegisterFlags method.

0 commit comments

Comments
 (0)