Initial commit.
This commit is contained in:
37
internal/tools/context.go
Normal file
37
internal/tools/context.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// WithTalosconfig sets the TALOSCONFIG environment variable for a command
|
||||
// This allows talosctl commands to use the correct context without global state
|
||||
func WithTalosconfig(cmd *exec.Cmd, talosconfigPath string) *exec.Cmd {
|
||||
if cmd.Env == nil {
|
||||
cmd.Env = os.Environ()
|
||||
}
|
||||
cmd.Env = append(cmd.Env, "TALOSCONFIG="+talosconfigPath)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// WithKubeconfig sets the KUBECONFIG environment variable for a command
|
||||
// This allows kubectl commands to use the correct context without global state
|
||||
func WithKubeconfig(cmd *exec.Cmd, kubeconfigPath string) *exec.Cmd {
|
||||
if cmd.Env == nil {
|
||||
cmd.Env = os.Environ()
|
||||
}
|
||||
cmd.Env = append(cmd.Env, "KUBECONFIG="+kubeconfigPath)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// GetTalosconfigPath returns the path to the talosconfig for an instance
|
||||
func GetTalosconfigPath(dataDir, instanceName string) string {
|
||||
return filepath.Join(dataDir, "instances", instanceName, "talos", "generated", "talosconfig")
|
||||
}
|
||||
|
||||
// GetKubeconfigPath returns the path to the kubeconfig for an instance
|
||||
func GetKubeconfigPath(dataDir, instanceName string) string {
|
||||
return filepath.Join(dataDir, "instances", instanceName, "kubeconfig")
|
||||
}
|
||||
111
internal/tools/gomplate.go
Normal file
111
internal/tools/gomplate.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Gomplate provides a wrapper around the gomplate command-line tool
|
||||
type Gomplate struct {
|
||||
gomplatePath string
|
||||
}
|
||||
|
||||
// NewGomplate creates a new Gomplate wrapper
|
||||
func NewGomplate() *Gomplate {
|
||||
// Find gomplate in PATH
|
||||
path, err := exec.LookPath("gomplate")
|
||||
if err != nil {
|
||||
// Default to "gomplate" and let exec handle the error
|
||||
path = "gomplate"
|
||||
}
|
||||
return &Gomplate{gomplatePath: path}
|
||||
}
|
||||
|
||||
// Render renders a template file with the given data sources
|
||||
func (g *Gomplate) Render(templatePath, outputPath string, dataSources map[string]string) error {
|
||||
args := []string{
|
||||
"-f", templatePath,
|
||||
"-o", outputPath,
|
||||
}
|
||||
|
||||
// Add data sources
|
||||
for name, path := range dataSources {
|
||||
args = append(args, "-d", fmt.Sprintf("%s=%s", name, path))
|
||||
}
|
||||
|
||||
cmd := exec.Command(g.gomplatePath, args...)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("gomplate render failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderString renders a template string with the given data sources
|
||||
func (g *Gomplate) RenderString(template string, dataSources map[string]string) (string, error) {
|
||||
args := []string{
|
||||
"-i", template,
|
||||
}
|
||||
|
||||
// Add data sources
|
||||
for name, path := range dataSources {
|
||||
args = append(args, "-d", fmt.Sprintf("%s=%s", name, path))
|
||||
}
|
||||
|
||||
cmd := exec.Command(g.gomplatePath, args...)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("gomplate render string failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
// RenderWithContext renders a template with context values passed as arguments
|
||||
func (g *Gomplate) RenderWithContext(templatePath, outputPath string, context map[string]string) error {
|
||||
args := []string{
|
||||
"-f", templatePath,
|
||||
"-o", outputPath,
|
||||
}
|
||||
|
||||
// Add context values
|
||||
for key, value := range context {
|
||||
args = append(args, "-c", fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
|
||||
cmd := exec.Command(g.gomplatePath, args...)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("gomplate render with context failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec executes gomplate with arbitrary arguments
|
||||
func (g *Gomplate) Exec(args ...string) (string, error) {
|
||||
cmd := exec.Command(g.gomplatePath, args...)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("gomplate exec failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
33
internal/tools/kubectl.go
Normal file
33
internal/tools/kubectl.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Kubectl provides a thin wrapper around the kubectl command-line tool
|
||||
type Kubectl struct {
|
||||
kubeconfigPath string
|
||||
}
|
||||
|
||||
// NewKubectl creates a new Kubectl wrapper
|
||||
func NewKubectl(kubeconfigPath string) *Kubectl {
|
||||
return &Kubectl{
|
||||
kubeconfigPath: kubeconfigPath,
|
||||
}
|
||||
}
|
||||
|
||||
// DeploymentExists checks if a deployment exists in the specified namespace
|
||||
func (k *Kubectl) DeploymentExists(name, namespace string) bool {
|
||||
args := []string{
|
||||
"get", "deployment", name,
|
||||
"-n", namespace,
|
||||
}
|
||||
|
||||
if k.kubeconfigPath != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfigPath}, args...)
|
||||
}
|
||||
|
||||
cmd := exec.Command("kubectl", args...)
|
||||
err := cmd.Run()
|
||||
return err == nil
|
||||
}
|
||||
362
internal/tools/talosctl.go
Normal file
362
internal/tools/talosctl.go
Normal file
@@ -0,0 +1,362 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Talosctl provides a thin wrapper around the talosctl command-line tool
|
||||
type Talosctl struct {
|
||||
talosconfigPath string
|
||||
}
|
||||
|
||||
// NewTalosctl creates a new Talosctl wrapper
|
||||
func NewTalosctl() *Talosctl {
|
||||
return &Talosctl{}
|
||||
}
|
||||
|
||||
// NewTalosconfigWithConfig creates a new Talosctl wrapper with a specific talosconfig
|
||||
func NewTalosconfigWithConfig(talosconfigPath string) *Talosctl {
|
||||
return &Talosctl{
|
||||
talosconfigPath: talosconfigPath,
|
||||
}
|
||||
}
|
||||
|
||||
// buildArgs adds talosconfig to args if set
|
||||
func (t *Talosctl) buildArgs(baseArgs []string) []string {
|
||||
if t.talosconfigPath != "" {
|
||||
return append([]string{"--talosconfig", t.talosconfigPath}, baseArgs...)
|
||||
}
|
||||
return baseArgs
|
||||
}
|
||||
|
||||
// GenConfig generates Talos configuration files
|
||||
func (t *Talosctl) GenConfig(clusterName, endpoint, outputDir string) error {
|
||||
args := []string{
|
||||
"gen", "config",
|
||||
clusterName,
|
||||
endpoint,
|
||||
"--output-dir", outputDir,
|
||||
}
|
||||
|
||||
cmd := exec.Command("talosctl", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("talosctl gen config failed: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyConfig applies configuration to a node
|
||||
func (t *Talosctl) ApplyConfig(nodeIP, configFile string, insecure bool, talosconfigPath string) error {
|
||||
args := []string{
|
||||
"apply-config",
|
||||
"--nodes", nodeIP,
|
||||
"--file", configFile,
|
||||
}
|
||||
|
||||
if insecure {
|
||||
args = append(args, "--insecure")
|
||||
}
|
||||
|
||||
cmd := exec.Command("talosctl", args...)
|
||||
if talosconfigPath != "" {
|
||||
WithTalosconfig(cmd, talosconfigPath)
|
||||
}
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("talosctl apply-config failed: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiskInfo represents disk information including path and size
|
||||
type DiskInfo struct {
|
||||
Path string `json:"path"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// GetDisks queries available disks from a node (filters to disks > 10GB)
|
||||
func (t *Talosctl) GetDisks(nodeIP string, insecure bool) ([]DiskInfo, error) {
|
||||
args := []string{
|
||||
"get", "disks",
|
||||
"--nodes", nodeIP,
|
||||
"-o", "json",
|
||||
}
|
||||
|
||||
if insecure {
|
||||
args = append(args, "--insecure")
|
||||
}
|
||||
|
||||
// Use jq to slurp the NDJSON into an array (like v.PoC does with jq -s)
|
||||
talosCmd := exec.Command("talosctl", args...)
|
||||
jqCmd := exec.Command("jq", "-s", ".")
|
||||
|
||||
// Pipe talosctl output to jq
|
||||
jqCmd.Stdin, _ = talosCmd.StdoutPipe()
|
||||
|
||||
if err := talosCmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start talosctl: %w", err)
|
||||
}
|
||||
|
||||
output, err := jqCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to process disks JSON: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
if err := talosCmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("talosctl get disks failed: %w", err)
|
||||
}
|
||||
|
||||
var result []map[string]interface{}
|
||||
if err := json.Unmarshal(output, &result); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse disks JSON: %w", err)
|
||||
}
|
||||
|
||||
disks := []DiskInfo{}
|
||||
for _, item := range result {
|
||||
metadata, ok := item["metadata"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
id, ok := metadata["id"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
spec, ok := item["spec"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract size - can be float64 or int
|
||||
var size int64
|
||||
switch v := spec["size"].(type) {
|
||||
case float64:
|
||||
size = int64(v)
|
||||
case int64:
|
||||
size = v
|
||||
case int:
|
||||
size = int64(v)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter to disks > 10GB (like v.PoC does)
|
||||
if size > 10000000000 {
|
||||
disks = append(disks, DiskInfo{
|
||||
Path: "/dev/" + id,
|
||||
Size: size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
// GetLinks queries network interfaces from a node
|
||||
func (t *Talosctl) GetLinks(nodeIP string, insecure bool) ([]map[string]interface{}, error) {
|
||||
args := []string{
|
||||
"get", "links",
|
||||
"--nodes", nodeIP,
|
||||
"-o", "json",
|
||||
}
|
||||
|
||||
if insecure {
|
||||
args = append(args, "--insecure")
|
||||
}
|
||||
|
||||
// Use jq to slurp the NDJSON into an array (like v.PoC does with jq -s)
|
||||
talosCmd := exec.Command("talosctl", args...)
|
||||
jqCmd := exec.Command("jq", "-s", ".")
|
||||
|
||||
// Pipe talosctl output to jq
|
||||
jqCmd.Stdin, _ = talosCmd.StdoutPipe()
|
||||
|
||||
if err := talosCmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start talosctl: %w", err)
|
||||
}
|
||||
|
||||
output, err := jqCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to process links JSON: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
if err := talosCmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("talosctl get links failed: %w", err)
|
||||
}
|
||||
|
||||
var result []map[string]interface{}
|
||||
if err := json.Unmarshal(output, &result); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse links JSON: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetRoutes queries routing table from a node
|
||||
func (t *Talosctl) GetRoutes(nodeIP string, insecure bool) ([]map[string]interface{}, error) {
|
||||
args := []string{
|
||||
"get", "routes",
|
||||
"--nodes", nodeIP,
|
||||
"-o", "json",
|
||||
}
|
||||
|
||||
if insecure {
|
||||
args = append(args, "--insecure")
|
||||
}
|
||||
|
||||
// Use jq to slurp the NDJSON into an array (like v.PoC does with jq -s)
|
||||
talosCmd := exec.Command("talosctl", args...)
|
||||
jqCmd := exec.Command("jq", "-s", ".")
|
||||
|
||||
// Pipe talosctl output to jq
|
||||
jqCmd.Stdin, _ = talosCmd.StdoutPipe()
|
||||
|
||||
if err := talosCmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start talosctl: %w", err)
|
||||
}
|
||||
|
||||
output, err := jqCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to process routes JSON: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
if err := talosCmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("talosctl get routes failed: %w", err)
|
||||
}
|
||||
|
||||
var result []map[string]interface{}
|
||||
if err := json.Unmarshal(output, &result); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse routes JSON: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetDefaultInterface finds the interface with the default route
|
||||
func (t *Talosctl) GetDefaultInterface(nodeIP string, insecure bool) (string, error) {
|
||||
routes, err := t.GetRoutes(nodeIP, insecure)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Find route with destination 0.0.0.0/0 (default route)
|
||||
for _, route := range routes {
|
||||
if spec, ok := route["spec"].(map[string]interface{}); ok {
|
||||
destination, _ := spec["destination"].(string)
|
||||
gateway, _ := spec["gateway"].(string)
|
||||
if destination == "0.0.0.0/0" && gateway != "" {
|
||||
if outLink, ok := spec["outLinkName"].(string); ok {
|
||||
return outLink, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no default route found")
|
||||
}
|
||||
|
||||
// GetPhysicalInterface finds the first physical ethernet interface
|
||||
func (t *Talosctl) GetPhysicalInterface(nodeIP string, insecure bool) (string, error) {
|
||||
links, err := t.GetLinks(nodeIP, insecure)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Look for physical ethernet interfaces (eth*, en*, eno*, ens*, enp*)
|
||||
for _, link := range links {
|
||||
metadata, ok := link["metadata"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
id, ok := metadata["id"].(string)
|
||||
if !ok || id == "lo" {
|
||||
continue
|
||||
}
|
||||
|
||||
spec, ok := link["spec"].(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's ethernet and up
|
||||
linkType, _ := spec["type"].(string)
|
||||
operState, _ := spec["operationalState"].(string)
|
||||
|
||||
if linkType == "ether" && operState == "up" {
|
||||
// Prefer interfaces starting with eth, en
|
||||
if strings.HasPrefix(id, "eth") || strings.HasPrefix(id, "en") {
|
||||
// Skip virtual interfaces (cni, flannel, docker, br-, veth)
|
||||
if !strings.Contains(id, "cni") &&
|
||||
!strings.Contains(id, "flannel") &&
|
||||
!strings.Contains(id, "docker") &&
|
||||
!strings.HasPrefix(id, "br-") &&
|
||||
!strings.HasPrefix(id, "veth") {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no physical ethernet interface found")
|
||||
}
|
||||
|
||||
// GetVersion gets Talos version from a node
|
||||
func (t *Talosctl) GetVersion(nodeIP string, insecure bool) (string, error) {
|
||||
args := t.buildArgs([]string{
|
||||
"version",
|
||||
"--nodes", nodeIP,
|
||||
"--short",
|
||||
})
|
||||
|
||||
if insecure {
|
||||
args = append(args, "--insecure")
|
||||
}
|
||||
|
||||
cmd := exec.Command("talosctl", args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("talosctl version failed: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
// Parse output to extract server version
|
||||
// Output format:
|
||||
// Client:
|
||||
// Talos v1.11.2
|
||||
// Server:
|
||||
// NODE: ...
|
||||
// Tag: v1.11.0
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for i, line := range lines {
|
||||
if strings.Contains(line, "Tag:") {
|
||||
// Extract version from "Tag: v1.11.0" format
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 2 {
|
||||
return parts[len(parts)-1], nil
|
||||
}
|
||||
}
|
||||
// Also check for simple "Talos vX.Y.Z" format
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "Talos v") && i < 3 {
|
||||
return strings.TrimSpace(strings.TrimPrefix(line, "Talos ")), nil
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
// Validate checks if talosctl is available
|
||||
func (t *Talosctl) Validate() error {
|
||||
cmd := exec.Command("talosctl", "version", "--client")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("talosctl not found or not working: %w\nOutput: %s", err, string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
133
internal/tools/yq.go
Normal file
133
internal/tools/yq.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// YQ provides a wrapper around the yq command-line tool
|
||||
type YQ struct {
|
||||
yqPath string
|
||||
}
|
||||
|
||||
// NewYQ creates a new YQ wrapper
|
||||
func NewYQ() *YQ {
|
||||
// Find yq in PATH
|
||||
path, err := exec.LookPath("yq")
|
||||
if err != nil {
|
||||
// Default to "yq" and let exec handle the error
|
||||
path = "yq"
|
||||
}
|
||||
return &YQ{yqPath: path}
|
||||
}
|
||||
|
||||
// Get retrieves a value from a YAML file using a yq expression
|
||||
func (y *YQ) Get(filePath, expression string) (string, error) {
|
||||
cmd := exec.Command(y.yqPath, expression, filePath)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", fmt.Errorf("yq get failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
// Set sets a value in a YAML file using a yq expression
|
||||
func (y *YQ) Set(filePath, expression, value string) error {
|
||||
// yq -i '.path = "value"' file.yaml
|
||||
// Ensure expression starts with '.' for yq v4 syntax
|
||||
if !strings.HasPrefix(expression, ".") {
|
||||
expression = "." + expression
|
||||
}
|
||||
|
||||
// Properly quote the value to handle special characters
|
||||
quotedValue := fmt.Sprintf(`"%s"`, strings.ReplaceAll(value, `"`, `\"`))
|
||||
setExpr := fmt.Sprintf("%s = %s", expression, quotedValue)
|
||||
cmd := exec.Command(y.yqPath, "-i", setExpr, filePath)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("yq set failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge merges two YAML files
|
||||
func (y *YQ) Merge(file1, file2, outputFile string) error {
|
||||
// yq eval-all '. as $item ireduce ({}; . * $item)' file1.yaml file2.yaml > output.yaml
|
||||
cmd := exec.Command(y.yqPath, "eval-all", ". as $item ireduce ({}; . * $item)", file1, file2)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("yq merge failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
// Write output
|
||||
return exec.Command("sh", "-c", fmt.Sprintf("echo '%s' > %s", stdout.String(), outputFile)).Run()
|
||||
}
|
||||
|
||||
// Delete removes a key from a YAML file
|
||||
func (y *YQ) Delete(filePath, expression string) error {
|
||||
// yq -i 'del(.path)' file.yaml
|
||||
delExpr := fmt.Sprintf("del(%s)", expression)
|
||||
cmd := exec.Command(y.yqPath, "-i", delExpr, filePath)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("yq delete failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate checks if a YAML file is valid
|
||||
func (y *YQ) Validate(filePath string) error {
|
||||
cmd := exec.Command(y.yqPath, "eval", ".", filePath)
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("yaml validation failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec executes an arbitrary yq expression on a file
|
||||
func (y *YQ) Exec(args ...string) ([]byte, error) {
|
||||
cmd := exec.Command(y.yqPath, args...)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("yq exec failed: %w, stderr: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
// CleanYQOutput removes trailing newlines and "null" values from yq output
|
||||
func CleanYQOutput(output string) string {
|
||||
output = strings.TrimSpace(output)
|
||||
if output == "null" {
|
||||
return ""
|
||||
}
|
||||
return output
|
||||
}
|
||||
Reference in New Issue
Block a user