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 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
33 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
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
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
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
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
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,
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
})
}

return vendors, nil
}

// FilterAndListVendors lists vendor configurations based on the provided configuration
func FilterAndListVendors(listConfig schema.ListConfig, format string, delimiter string) (string, error) {
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"}
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
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,
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
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