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

Implement: atmos list vendor #994

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8a849f7
feat: add vendor list command and functionality
Cerebrovinny Feb 1, 2025
13a0665
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 1, 2025
0a99336
update tests
Cerebrovinny Feb 1, 2025
968ee5c
refactor: update vendor manifest structure and file permissions format
Cerebrovinny Feb 1, 2025
c7c97a2
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 1, 2025
7b2e9dc
feat: add version field to vendor list output
Cerebrovinny Feb 2, 2025
359802a
fix: handle error when restoring working directory in vendor test
Cerebrovinny Feb 2, 2025
32eb1e8
feat: add TSV format support and default delimiters for list vendor
Cerebrovinny Feb 2, 2025
9524836
vendor update test fixes
Cerebrovinny Feb 2, 2025
97561cb
refactor: improve vendor list functionality and template processing
Cerebrovinny Feb 2, 2025
e8bf78d
feat: set relative file path for vendor sources
Cerebrovinny Feb 2, 2025
d0721ab
fix: handle absolute vendor base paths in list vendor command
Cerebrovinny Feb 2, 2025
e58b895
Merge branch 'main' into DEV-2806
Cerebrovinny Feb 4, 2025
0f83c6b
Add documentation for 'atmos list vendor' command
Cerebrovinny Feb 4, 2025
162ee0a
docs: update vendor command output format options and examples
Cerebrovinny Feb 4, 2025
44141e6
fix: correct row index condition in vendor list styling
Cerebrovinny Feb 4, 2025
0555127
Update website/docs/cli/commands/list/list-vendor.mdx
Cerebrovinny Feb 4, 2025
118effd
refactor(list-vendor): simplify output columns and update documentation
Cerebrovinny Feb 4, 2025
38c6ad9
fix: update vendor command links in documentation
Cerebrovinny Feb 4, 2025
26ded60
docs: add vendor list columns configuration documentation
Cerebrovinny Feb 4, 2025
9484970
Add recursion depth limit to vendor imports processing
Cerebrovinny Feb 4, 2025
cf6adf5
Update website/docs/cli/commands/list/list-vendor.mdx
Cerebrovinny Feb 4, 2025
e4be660
Update website/docs/cli/commands/list/list-vendor.mdx
Cerebrovinny Feb 4, 2025
705f5dc
docs: add format option to vendor list command configuration
Cerebrovinny Feb 4, 2025
8eb7dae
Fix broken link to list-components command documentation
Cerebrovinny Feb 4, 2025
aaf323c
Merge branch 'main' into DEV-2806
Cerebrovinny Feb 4, 2025
65f697d
Fix broken link to 'list components' command documentation
Cerebrovinny Feb 4, 2025
a572d0a
feat: add template-based column configuration for vendor list command
Cerebrovinny Feb 5, 2025
cccede3
docs: add template variable documentation for vendor list columns
Cerebrovinny Feb 5, 2025
4c5e0ef
feat: add atmos-specific fields to vendor manifest data
Cerebrovinny Feb 5, 2025
f98652b
Merge branch 'main' into DEV-2806
osterman Feb 6, 2025
59f0048
Remove unused vendor list configuration
Cerebrovinny Feb 7, 2025
9cd9f28
Merge branch 'main' into DEV-2806
Cerebrovinny Feb 7, 2025
02b5a7b
update snapshots
Cerebrovinny Feb 8, 2025
0d2c9cc
Merge branch 'main' into DEV-2806
Cerebrovinny Feb 9, 2025
dd1354e
Merge branch 'main' into DEV-2806
aknysh Feb 10, 2025
ea36308
Merge branch 'main' into DEV-2806
aknysh Feb 11, 2025
c9ccf94
Merge branch 'main' into DEV-2806
aknysh Feb 12, 2025
40bcc6e
Merge branch 'main' into DEV-2806
Cerebrovinny Feb 14, 2025
b8cfbbf
added pkg csv
Cerebrovinny Feb 14, 2025
a21737a
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 14, 2025
b0933a7
fix windows version
Cerebrovinny Feb 15, 2025
6921198
more fixes windows
Cerebrovinny Feb 15, 2025
686cc5c
Merge remote-tracking branch 'origin/main' into DEV-2806
Cerebrovinny Feb 17, 2025
c457166
Merge branch 'main' into DEV-2806
aknysh Feb 17, 2025
91ae874
Merge branch 'main' into DEV-2806
Cerebrovinny Feb 19, 2025
c1a53f3
Merge branch 'main' into DEV-2806
osterman Feb 20, 2025
b4e7b71
Merge branch 'main' into DEV-2806
osterman Feb 20, 2025
de59ca6
Merge branch 'main' into DEV-2806
osterman Feb 28, 2025
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
59 changes: 59 additions & 0 deletions cmd/list_vendor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"

"github.com/cloudposse/atmos/pkg/config"
l "github.com/cloudposse/atmos/pkg/list"
"github.com/cloudposse/atmos/pkg/schema"
"github.com/cloudposse/atmos/pkg/ui/theme"
u "github.com/cloudposse/atmos/pkg/utils"
)

// listVendorCmd lists atmos vendor configurations
var listVendorCmd = &cobra.Command{
Use: "vendor",
Short: "List all vendor configurations",
Long: "List vendor configurations in a tabular way, including component and vendor manifests",
Example: "atmos list vendor\n" +
"atmos list vendor --format json\n" +
"atmos list vendor --format csv --delimiter ','",
Run: func(cmd *cobra.Command, args []string) {
flags := cmd.Flags()

formatFlag, err := flags.GetString("format")
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error getting the 'format' flag: %v", err), theme.Colors.Error)
return
}

delimiterFlag, err := flags.GetString("delimiter")
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error getting the 'delimiter' flag: %v", err), theme.Colors.Error)
return
}

configAndStacksInfo := schema.ConfigAndStacksInfo{}
atmosConfig, err := config.InitCliConfig(configAndStacksInfo, true)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error initializing CLI config: %v", err), theme.Colors.Error)
return
}

output, err := l.FilterAndListVendors(atmosConfig.Vendor.List, formatFlag, delimiterFlag)
if err != nil {
u.PrintMessageInColor(fmt.Sprintf("Error: %v"+"\n", err), theme.Colors.Warning)
return
}

u.PrintMessageInColor(output, theme.Colors.Success)
},
}

func init() {
listVendorCmd.PersistentFlags().String("format", "", "Output format (table, json, csv)")
listVendorCmd.PersistentFlags().String("delimiter", "\t", "Delimiter for csv output")
listCmd.AddCommand(listVendorCmd)
}
195 changes: 195 additions & 0 deletions pkg/list/list_vendor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package list

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"

"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
"github.com/cloudposse/atmos/internal/exec"
"github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/schema"
"github.com/cloudposse/atmos/pkg/ui/theme"
"github.com/cloudposse/atmos/pkg/utils"
"gopkg.in/yaml.v3"
)

// VendorInfo represents a vendor configuration entry
type VendorInfo struct {
Component string `json:"component"`
Type string `json:"type"`
Manifest string `json:"manifest"`
Folder string `json:"folder"`
}

// processVendorFile processes a vendor configuration file and returns vendor information
func processVendorFile(filePath string, atmosConfig schema.AtmosConfiguration) ([]VendorInfo, error) {
var vendors []VendorInfo

data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("error reading vendor file %s: %w", filePath, err)
}

var vendorConfig schema.AtmosVendorConfig
if err := yaml.Unmarshal(data, &vendorConfig); err != nil {
return nil, fmt.Errorf("error parsing vendor file %s: %w", filePath, err)
}

// Process vendor configuration
for _, source := range vendorConfig.Spec.Sources {
vendorType := "Vendor Manifest"
manifest := filepath.Clean(filePath)
folder := filepath.Join(atmosConfig.BasePath, "components", "terraform", source.Component)

vendors = append(vendors, VendorInfo{
Component: source.Component,
Type: vendorType,
Manifest: manifest,
Folder: folder,
})
}

return vendors, nil
}

// FilterAndListVendors lists vendor configurations based on the provided configuration
func FilterAndListVendors(listConfig schema.ListConfig, format string, delimiter string) (string, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor into smaller functions. Under 50 lines long, with 10-30 ideal.

if err := ValidateFormat(format); err != nil {
return "", err
}

if format == "" && listConfig.Format != "" {
if err := ValidateFormat(listConfig.Format); err != nil {
return "", err
}
format = listConfig.Format
}

// Initialize Atmos config
configAndStacksInfo := schema.ConfigAndStacksInfo{}
atmosConfig, err := config.InitCliConfig(configAndStacksInfo, true)
if err != nil {
return "", fmt.Errorf("error initializing CLI config: %w", err)
}

// Define default columns if not specified in config
header := []string{"Component", "Type", "Manifest", "Folder"}
if len(listConfig.Columns) > 0 {
header = make([]string, len(listConfig.Columns))
for i, col := range listConfig.Columns {
header[i] = col.Name
}
}

// Get vendor path
var vendorPath string
if utils.IsPathAbsolute(atmosConfig.Vendor.BasePath) {
vendorPath = atmosConfig.Vendor.BasePath
} else {
vendorPath = filepath.Join(atmosConfig.BasePath, atmosConfig.Vendor.BasePath)
}

// Check if vendor path exists
fileInfo, err := os.Stat(vendorPath)
if err != nil {
return "", fmt.Errorf("the vendor path '%s' does not exist. Review 'vendor.base_path' in 'atmos.yaml'", vendorPath)
}

var files []string
if fileInfo.IsDir() {
// If it's a directory, get all YAML files
files, err = utils.GetAllYamlFilesInDir(vendorPath)
if err != nil {
return "", fmt.Errorf("error reading the directory '%s' defined in 'vendor.base_path' in 'atmos.yaml': %v",
atmosConfig.Vendor.BasePath, err)
}
// Convert relative paths to absolute paths
for i, f := range files {
files[i] = filepath.Join(vendorPath, f)
}
} else {
// If it's a file, just use that file
files = []string{vendorPath}
}

// Process all vendor files
var allVendors []VendorInfo
for _, f := range files {
vendors, err := processVendorFile(f, atmosConfig)
if err != nil {
return "", err
}
allVendors = append(allVendors, vendors...)
}

// Convert vendor info to rows
var rows [][]string
for _, vendor := range allVendors {
rows = append(rows, []string{
vendor.Component,
vendor.Type,
vendor.Manifest,
vendor.Folder,
})
}

// Sort rows for consistent output
sort.Slice(rows, func(i, j int) bool {
return strings.Join(rows[i], delimiter) < strings.Join(rows[j], delimiter)
})

if len(rows) == 0 {
return "No vendor configurations found", nil
}

// Handle different output formats
switch format {
case "json":
jsonBytes, err := json.MarshalIndent(allVendors, "", " ")
if err != nil {
return "", fmt.Errorf("error formatting JSON output: %w", err)
}
return string(jsonBytes), nil

case "csv":
var output strings.Builder
output.WriteString(strings.Join(header, delimiter) + utils.GetLineEnding())
for _, row := range rows {
output.WriteString(strings.Join(row, delimiter) + utils.GetLineEnding())
}
return output.String(), nil

default:
// If format is empty or "table", use table format
if format == "" && exec.CheckTTYSupport() {
// Create a styled table for TTY
t := table.New().
Border(lipgloss.ThickBorder()).
BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color(theme.ColorBorder))).
StyleFunc(func(row, col int) lipgloss.Style {
style := lipgloss.NewStyle().PaddingLeft(1).PaddingRight(1)
if row == 0 {
return style.Inherit(theme.Styles.CommandName).Align(lipgloss.Center)
}
return style.Inherit(theme.Styles.Description)
}).
Headers(header...).
Rows(rows...)

return t.String() + utils.GetLineEnding(), nil
}

// Default to simple tabular format for non-TTY or when format is explicitly "table"
var output strings.Builder
output.WriteString(strings.Join(header, delimiter) + utils.GetLineEnding())
for _, row := range rows {
output.WriteString(strings.Join(row, delimiter) + utils.GetLineEnding())
}
return output.String(), nil
}
}
Loading