First commit of golang CLI.
This commit is contained in:
130
wild-cli/internal/external/base.go
vendored
Normal file
130
wild-cli/internal/external/base.go
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Tool represents an external command-line tool
|
||||
type Tool interface {
|
||||
Name() string
|
||||
BinaryName() string
|
||||
IsInstalled() bool
|
||||
Version() (string, error)
|
||||
Execute(ctx context.Context, args ...string) ([]byte, error)
|
||||
ExecuteWithInput(ctx context.Context, input string, args ...string) ([]byte, error)
|
||||
}
|
||||
|
||||
// BaseTool provides common functionality for external tools
|
||||
type BaseTool struct {
|
||||
name string
|
||||
binaryName string
|
||||
binaryPath string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewBaseTool creates a new base tool
|
||||
func NewBaseTool(name, binaryName string) *BaseTool {
|
||||
return &BaseTool{
|
||||
name: name,
|
||||
binaryName: binaryName,
|
||||
timeout: 5 * time.Minute, // Default timeout
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the tool name
|
||||
func (t *BaseTool) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// BinaryName returns the binary name
|
||||
func (t *BaseTool) BinaryName() string {
|
||||
return t.binaryName
|
||||
}
|
||||
|
||||
// IsInstalled checks if the tool is available in PATH
|
||||
func (t *BaseTool) IsInstalled() bool {
|
||||
if t.binaryPath == "" {
|
||||
path, err := exec.LookPath(t.binaryName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
t.binaryPath = path
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Version returns the tool version
|
||||
func (t *BaseTool) Version() (string, error) {
|
||||
if !t.IsInstalled() {
|
||||
return "", fmt.Errorf("tool %s not installed", t.name)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
output, err := t.Execute(ctx, "--version")
|
||||
if err != nil {
|
||||
// Try alternative version flags
|
||||
output, err = t.Execute(ctx, "version")
|
||||
if err != nil {
|
||||
output, err = t.Execute(ctx, "-v")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting version: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// Execute runs the tool with given arguments
|
||||
func (t *BaseTool) Execute(ctx context.Context, args ...string) ([]byte, error) {
|
||||
return t.ExecuteWithInput(ctx, "", args...)
|
||||
}
|
||||
|
||||
// ExecuteWithInput runs the tool with stdin input
|
||||
func (t *BaseTool) ExecuteWithInput(ctx context.Context, input string, args ...string) ([]byte, error) {
|
||||
if !t.IsInstalled() {
|
||||
return nil, fmt.Errorf("tool %s not installed", t.name)
|
||||
}
|
||||
|
||||
// Create command with timeout
|
||||
ctx, cancel := context.WithTimeout(ctx, t.timeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, t.binaryPath, args...)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if input != "" {
|
||||
cmd.Stdin = bytes.NewBufferString(input)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("executing %s %v: %w\nstderr: %s",
|
||||
t.name, args, err, stderr.String())
|
||||
}
|
||||
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
// SetTimeout sets the execution timeout
|
||||
func (t *BaseTool) SetTimeout(timeout time.Duration) {
|
||||
t.timeout = timeout
|
||||
}
|
||||
|
||||
// SetBinaryPath explicitly sets the binary path (useful for testing)
|
||||
func (t *BaseTool) SetBinaryPath(path string) {
|
||||
t.binaryPath = path
|
||||
}
|
226
wild-cli/internal/external/kubectl.go
vendored
Normal file
226
wild-cli/internal/external/kubectl.go
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// KubectlTool wraps kubectl operations
|
||||
type KubectlTool struct {
|
||||
*BaseTool
|
||||
kubeconfig string
|
||||
}
|
||||
|
||||
// NewKubectlTool creates a new kubectl tool wrapper
|
||||
func NewKubectlTool() *KubectlTool {
|
||||
return &KubectlTool{
|
||||
BaseTool: NewBaseTool("kubectl", "kubectl"),
|
||||
}
|
||||
}
|
||||
|
||||
// SetKubeconfig sets the kubeconfig file path
|
||||
func (k *KubectlTool) SetKubeconfig(path string) {
|
||||
k.kubeconfig = path
|
||||
}
|
||||
|
||||
// Apply applies Kubernetes manifests
|
||||
func (k *KubectlTool) Apply(ctx context.Context, manifests []string, namespace string, dryRun bool) error {
|
||||
for _, manifest := range manifests {
|
||||
args := []string{"apply", "-f", "-"}
|
||||
|
||||
if namespace != "" {
|
||||
args = append(args, "--namespace", namespace)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
args = append(args, "--dry-run=client")
|
||||
}
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := k.ExecuteWithInput(ctx, manifest, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("applying manifest: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyKustomize applies using kustomize
|
||||
func (k *KubectlTool) ApplyKustomize(ctx context.Context, path string, namespace string, dryRun bool) error {
|
||||
args := []string{"apply", "-k", path}
|
||||
|
||||
if namespace != "" {
|
||||
args = append(args, "--namespace", namespace)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
args = append(args, "--dry-run=client")
|
||||
}
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := k.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("applying kustomize: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes Kubernetes resources
|
||||
func (k *KubectlTool) Delete(ctx context.Context, resource, name, namespace string, ignoreNotFound bool) error {
|
||||
args := []string{"delete", resource, name}
|
||||
|
||||
if namespace != "" {
|
||||
args = append(args, "--namespace", namespace)
|
||||
}
|
||||
|
||||
if ignoreNotFound {
|
||||
args = append(args, "--ignore-not-found=true")
|
||||
}
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := k.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting resource: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSecret creates a Kubernetes secret
|
||||
func (k *KubectlTool) CreateSecret(ctx context.Context, name, namespace string, data map[string]string) error {
|
||||
// First try to delete existing secret
|
||||
_ = k.Delete(ctx, "secret", name, namespace, true)
|
||||
|
||||
args := []string{"create", "secret", "generic", name}
|
||||
|
||||
for key, value := range data {
|
||||
args = append(args, "--from-literal="+key+"="+value)
|
||||
}
|
||||
|
||||
if namespace != "" {
|
||||
args = append(args, "--namespace", namespace)
|
||||
}
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := k.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating secret: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetResource gets a Kubernetes resource
|
||||
func (k *KubectlTool) GetResource(ctx context.Context, resource, name, namespace string) ([]byte, error) {
|
||||
args := []string{"get", resource}
|
||||
|
||||
if name != "" {
|
||||
args = append(args, name)
|
||||
}
|
||||
|
||||
if namespace != "" {
|
||||
args = append(args, "--namespace", namespace)
|
||||
}
|
||||
|
||||
args = append(args, "-o", "yaml")
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
output, err := k.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting resource: %w", err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// WaitForDeletion waits for a resource to be deleted
|
||||
func (k *KubectlTool) WaitForDeletion(ctx context.Context, resource, name, namespace string, timeout string) error {
|
||||
args := []string{"wait", "--for=delete", resource + "/" + name}
|
||||
|
||||
if namespace != "" {
|
||||
args = append(args, "--namespace", namespace)
|
||||
}
|
||||
|
||||
if timeout != "" {
|
||||
args = append(args, "--timeout="+timeout)
|
||||
}
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := k.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("waiting for deletion: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNodes returns information about cluster nodes
|
||||
func (k *KubectlTool) GetNodes(ctx context.Context) ([]byte, error) {
|
||||
args := []string{"get", "nodes", "-o", "wide"}
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
output, err := k.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting nodes: %w", err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// GetServiceAccount gets a service account token
|
||||
func (k *KubectlTool) GetServiceAccountToken(ctx context.Context, serviceAccount, namespace string) (string, error) {
|
||||
// Get the service account
|
||||
args := []string{"get", "serviceaccount", serviceAccount, "--namespace", namespace, "-o", "jsonpath={.secrets[0].name}"}
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
secretName, err := k.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting service account secret: %w", err)
|
||||
}
|
||||
|
||||
secretNameStr := strings.TrimSpace(string(secretName))
|
||||
if secretNameStr == "" {
|
||||
return "", fmt.Errorf("no secret found for service account %s", serviceAccount)
|
||||
}
|
||||
|
||||
// Get the token from the secret
|
||||
args = []string{"get", "secret", secretNameStr, "--namespace", namespace, "-o", "jsonpath={.data.token}"}
|
||||
|
||||
if k.kubeconfig != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfig}, args...)
|
||||
}
|
||||
|
||||
tokenBytes, err := k.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getting token from secret: %w", err)
|
||||
}
|
||||
|
||||
return string(tokenBytes), nil
|
||||
}
|
93
wild-cli/internal/external/manager.go
vendored
Normal file
93
wild-cli/internal/external/manager.go
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Manager coordinates external tools
|
||||
type Manager struct {
|
||||
kubectl *KubectlTool
|
||||
talosctl *TalosctlTool
|
||||
restic *ResticTool
|
||||
tools map[string]Tool
|
||||
}
|
||||
|
||||
// NewManager creates a new tool manager
|
||||
func NewManager() *Manager {
|
||||
kubectl := NewKubectlTool()
|
||||
talosctl := NewTalosctlTool()
|
||||
restic := NewResticTool()
|
||||
|
||||
tools := map[string]Tool{
|
||||
"kubectl": kubectl,
|
||||
"talosctl": talosctl,
|
||||
"restic": restic,
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
kubectl: kubectl,
|
||||
talosctl: talosctl,
|
||||
restic: restic,
|
||||
tools: tools,
|
||||
}
|
||||
}
|
||||
|
||||
// Kubectl returns the kubectl tool
|
||||
func (m *Manager) Kubectl() *KubectlTool {
|
||||
return m.kubectl
|
||||
}
|
||||
|
||||
// Talosctl returns the talosctl tool
|
||||
func (m *Manager) Talosctl() *TalosctlTool {
|
||||
return m.talosctl
|
||||
}
|
||||
|
||||
// Restic returns the restic tool
|
||||
func (m *Manager) Restic() *ResticTool {
|
||||
return m.restic
|
||||
}
|
||||
|
||||
// CheckTools verifies that required tools are available
|
||||
func (m *Manager) CheckTools(ctx context.Context, required []string) error {
|
||||
missing := make([]string, 0)
|
||||
|
||||
for _, toolName := range required {
|
||||
tool, exists := m.tools[toolName]
|
||||
if !exists {
|
||||
missing = append(missing, toolName)
|
||||
continue
|
||||
}
|
||||
|
||||
if !tool.IsInstalled() {
|
||||
missing = append(missing, toolName)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
return fmt.Errorf("missing required tools: %v", missing)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetToolVersion returns the version of a tool
|
||||
func (m *Manager) GetToolVersion(toolName string) (string, error) {
|
||||
tool, exists := m.tools[toolName]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("tool %s not found", toolName)
|
||||
}
|
||||
|
||||
return tool.Version()
|
||||
}
|
||||
|
||||
// ListTools returns information about all tools
|
||||
func (m *Manager) ListTools() map[string]bool {
|
||||
status := make(map[string]bool)
|
||||
|
||||
for name, tool := range m.tools {
|
||||
status[name] = tool.IsInstalled()
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
288
wild-cli/internal/external/restic.go
vendored
Normal file
288
wild-cli/internal/external/restic.go
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ResticTool wraps restic backup operations
|
||||
type ResticTool struct {
|
||||
*BaseTool
|
||||
repository string
|
||||
password string
|
||||
}
|
||||
|
||||
// NewResticTool creates a new restic tool wrapper
|
||||
func NewResticTool() *ResticTool {
|
||||
return &ResticTool{
|
||||
BaseTool: NewBaseTool("restic", "restic"),
|
||||
}
|
||||
}
|
||||
|
||||
// SetRepository sets the restic repository
|
||||
func (r *ResticTool) SetRepository(repo string) {
|
||||
r.repository = repo
|
||||
}
|
||||
|
||||
// SetPassword sets the restic password
|
||||
func (r *ResticTool) SetPassword(password string) {
|
||||
r.password = password
|
||||
}
|
||||
|
||||
// InitRepository initializes a new restic repository
|
||||
func (r *ResticTool) InitRepository(ctx context.Context) error {
|
||||
env := r.getEnvironment()
|
||||
|
||||
cmd := r.BaseTool
|
||||
originalEnv := os.Environ()
|
||||
defer func() {
|
||||
os.Clearenv()
|
||||
for _, kv := range originalEnv {
|
||||
if k, v, found := strings.Cut(kv, "="); found {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set environment variables
|
||||
for k, v := range env {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
|
||||
args := []string{"init"}
|
||||
if r.repository != "" {
|
||||
args = append(args, "--repo", r.repository)
|
||||
}
|
||||
|
||||
_, err := cmd.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initializing repository: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Backup creates a backup
|
||||
func (r *ResticTool) Backup(ctx context.Context, paths []string, excludes []string, tags []string) error {
|
||||
env := r.getEnvironment()
|
||||
|
||||
cmd := r.BaseTool
|
||||
originalEnv := os.Environ()
|
||||
defer func() {
|
||||
os.Clearenv()
|
||||
for _, kv := range originalEnv {
|
||||
if k, v, found := strings.Cut(kv, "="); found {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set environment variables
|
||||
for k, v := range env {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
|
||||
args := []string{"backup"}
|
||||
|
||||
if r.repository != "" {
|
||||
args = append(args, "--repo", r.repository)
|
||||
}
|
||||
|
||||
// Add paths
|
||||
args = append(args, paths...)
|
||||
|
||||
// Add excludes
|
||||
for _, exclude := range excludes {
|
||||
args = append(args, "--exclude", exclude)
|
||||
}
|
||||
|
||||
// Add tags
|
||||
for _, tag := range tags {
|
||||
args = append(args, "--tag", tag)
|
||||
}
|
||||
|
||||
_, err := cmd.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating backup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore restores from backup
|
||||
func (r *ResticTool) Restore(ctx context.Context, snapshotID, target string) error {
|
||||
env := r.getEnvironment()
|
||||
|
||||
cmd := r.BaseTool
|
||||
originalEnv := os.Environ()
|
||||
defer func() {
|
||||
os.Clearenv()
|
||||
for _, kv := range originalEnv {
|
||||
if k, v, found := strings.Cut(kv, "="); found {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set environment variables
|
||||
for k, v := range env {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
|
||||
args := []string{"restore", snapshotID, "--target", target}
|
||||
|
||||
if r.repository != "" {
|
||||
args = append(args, "--repo", r.repository)
|
||||
}
|
||||
|
||||
_, err := cmd.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("restoring backup: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListSnapshots lists available snapshots
|
||||
func (r *ResticTool) ListSnapshots(ctx context.Context, tags []string) ([]byte, error) {
|
||||
env := r.getEnvironment()
|
||||
|
||||
cmd := r.BaseTool
|
||||
originalEnv := os.Environ()
|
||||
defer func() {
|
||||
os.Clearenv()
|
||||
for _, kv := range originalEnv {
|
||||
if k, v, found := strings.Cut(kv, "="); found {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set environment variables
|
||||
for k, v := range env {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
|
||||
args := []string{"snapshots", "--json"}
|
||||
|
||||
if r.repository != "" {
|
||||
args = append(args, "--repo", r.repository)
|
||||
}
|
||||
|
||||
// Add tag filters
|
||||
for _, tag := range tags {
|
||||
args = append(args, "--tag", tag)
|
||||
}
|
||||
|
||||
output, err := cmd.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing snapshots: %w", err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// Check verifies repository integrity
|
||||
func (r *ResticTool) Check(ctx context.Context) error {
|
||||
env := r.getEnvironment()
|
||||
|
||||
cmd := r.BaseTool
|
||||
originalEnv := os.Environ()
|
||||
defer func() {
|
||||
os.Clearenv()
|
||||
for _, kv := range originalEnv {
|
||||
if k, v, found := strings.Cut(kv, "="); found {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set environment variables
|
||||
for k, v := range env {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
|
||||
args := []string{"check"}
|
||||
|
||||
if r.repository != "" {
|
||||
args = append(args, "--repo", r.repository)
|
||||
}
|
||||
|
||||
_, err := cmd.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking repository: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Forget removes snapshots
|
||||
func (r *ResticTool) Forget(ctx context.Context, keepLast int, keepDaily int, keepWeekly int, keepMonthly int, prune bool) error {
|
||||
env := r.getEnvironment()
|
||||
|
||||
cmd := r.BaseTool
|
||||
originalEnv := os.Environ()
|
||||
defer func() {
|
||||
os.Clearenv()
|
||||
for _, kv := range originalEnv {
|
||||
if k, v, found := strings.Cut(kv, "="); found {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set environment variables
|
||||
for k, v := range env {
|
||||
_ = os.Setenv(k, v)
|
||||
}
|
||||
|
||||
args := []string{"forget"}
|
||||
|
||||
if r.repository != "" {
|
||||
args = append(args, "--repo", r.repository)
|
||||
}
|
||||
|
||||
if keepLast > 0 {
|
||||
args = append(args, "--keep-last", fmt.Sprintf("%d", keepLast))
|
||||
}
|
||||
|
||||
if keepDaily > 0 {
|
||||
args = append(args, "--keep-daily", fmt.Sprintf("%d", keepDaily))
|
||||
}
|
||||
|
||||
if keepWeekly > 0 {
|
||||
args = append(args, "--keep-weekly", fmt.Sprintf("%d", keepWeekly))
|
||||
}
|
||||
|
||||
if keepMonthly > 0 {
|
||||
args = append(args, "--keep-monthly", fmt.Sprintf("%d", keepMonthly))
|
||||
}
|
||||
|
||||
if prune {
|
||||
args = append(args, "--prune")
|
||||
}
|
||||
|
||||
_, err := cmd.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("forgetting snapshots: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getEnvironment returns environment variables for restic
|
||||
func (r *ResticTool) getEnvironment() map[string]string {
|
||||
env := make(map[string]string)
|
||||
|
||||
if r.repository != "" {
|
||||
env["RESTIC_REPOSITORY"] = r.repository
|
||||
}
|
||||
|
||||
if r.password != "" {
|
||||
env["RESTIC_PASSWORD"] = r.password
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
285
wild-cli/internal/external/talosctl.go
vendored
Normal file
285
wild-cli/internal/external/talosctl.go
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TalosctlTool wraps talosctl operations
|
||||
type TalosctlTool struct {
|
||||
*BaseTool
|
||||
endpoints []string
|
||||
nodes []string
|
||||
talosconfig string
|
||||
}
|
||||
|
||||
// NewTalosctlTool creates a new talosctl tool wrapper
|
||||
func NewTalosctlTool() *TalosctlTool {
|
||||
return &TalosctlTool{
|
||||
BaseTool: NewBaseTool("talosctl", "talosctl"),
|
||||
}
|
||||
}
|
||||
|
||||
// SetEndpoints sets the Talos API endpoints
|
||||
func (t *TalosctlTool) SetEndpoints(endpoints []string) {
|
||||
t.endpoints = endpoints
|
||||
}
|
||||
|
||||
// SetNodes sets the target nodes
|
||||
func (t *TalosctlTool) SetNodes(nodes []string) {
|
||||
t.nodes = nodes
|
||||
}
|
||||
|
||||
// SetTalosconfig sets the talosconfig file path
|
||||
func (t *TalosctlTool) SetTalosconfig(path string) {
|
||||
t.talosconfig = path
|
||||
}
|
||||
|
||||
// GenerateConfig generates Talos configuration files
|
||||
func (t *TalosctlTool) GenerateConfig(ctx context.Context, clusterName, clusterEndpoint, outputDir string) error {
|
||||
args := []string{
|
||||
"gen", "config",
|
||||
clusterName,
|
||||
clusterEndpoint,
|
||||
"--output-dir", outputDir,
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyConfig applies configuration to nodes
|
||||
func (t *TalosctlTool) ApplyConfig(ctx context.Context, configFile string, insecure bool) error {
|
||||
args := []string{"apply-config"}
|
||||
|
||||
if insecure {
|
||||
args = append(args, "--insecure")
|
||||
}
|
||||
|
||||
args = append(args, "--file", configFile)
|
||||
|
||||
if len(t.endpoints) > 0 {
|
||||
args = append(args, "--endpoints")
|
||||
args = append(args, t.endpoints...)
|
||||
}
|
||||
|
||||
if len(t.nodes) > 0 {
|
||||
args = append(args, "--nodes")
|
||||
args = append(args, t.nodes...)
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("applying config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bootstrap bootstraps the cluster
|
||||
func (t *TalosctlTool) Bootstrap(ctx context.Context) error {
|
||||
args := []string{"bootstrap"}
|
||||
|
||||
if len(t.endpoints) > 0 {
|
||||
args = append(args, "--endpoints")
|
||||
args = append(args, t.endpoints...)
|
||||
}
|
||||
|
||||
if len(t.nodes) > 0 {
|
||||
args = append(args, "--nodes")
|
||||
args = append(args, t.nodes...)
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bootstrapping cluster: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Kubeconfig retrieves the kubeconfig
|
||||
func (t *TalosctlTool) Kubeconfig(ctx context.Context, outputFile string, force bool) error {
|
||||
args := []string{"kubeconfig"}
|
||||
|
||||
if outputFile != "" {
|
||||
args = append(args, outputFile)
|
||||
}
|
||||
|
||||
if force {
|
||||
args = append(args, "--force")
|
||||
}
|
||||
|
||||
if len(t.endpoints) > 0 {
|
||||
args = append(args, "--endpoints")
|
||||
args = append(args, t.endpoints...)
|
||||
}
|
||||
|
||||
if len(t.nodes) > 0 {
|
||||
args = append(args, "--nodes")
|
||||
args = append(args, t.nodes...)
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting kubeconfig: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Health checks the health of nodes
|
||||
func (t *TalosctlTool) Health(ctx context.Context) ([]byte, error) {
|
||||
args := []string{"health"}
|
||||
|
||||
if len(t.endpoints) > 0 {
|
||||
args = append(args, "--endpoints")
|
||||
args = append(args, t.endpoints...)
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
output, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("checking health: %w", err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// List lists Talos resources
|
||||
func (t *TalosctlTool) List(ctx context.Context, resource string) ([]byte, error) {
|
||||
args := []string{"list", resource}
|
||||
|
||||
if len(t.endpoints) > 0 {
|
||||
args = append(args, "--endpoints")
|
||||
args = append(args, t.endpoints...)
|
||||
}
|
||||
|
||||
if len(t.nodes) > 0 {
|
||||
args = append(args, "--nodes")
|
||||
args = append(args, t.nodes...)
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
output, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing %s: %w", resource, err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// Patch applies patches to node configuration
|
||||
func (t *TalosctlTool) Patch(ctx context.Context, patchFile string, configType string) error {
|
||||
args := []string{"patch", configType, "--patch-file", patchFile}
|
||||
|
||||
if len(t.endpoints) > 0 {
|
||||
args = append(args, "--endpoints")
|
||||
args = append(args, t.endpoints...)
|
||||
}
|
||||
|
||||
if len(t.nodes) > 0 {
|
||||
args = append(args, "--nodes")
|
||||
args = append(args, t.nodes...)
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("patching config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reboot reboots nodes
|
||||
func (t *TalosctlTool) Reboot(ctx context.Context) error {
|
||||
args := []string{"reboot"}
|
||||
|
||||
if len(t.endpoints) > 0 {
|
||||
args = append(args, "--endpoints")
|
||||
args = append(args, t.endpoints...)
|
||||
}
|
||||
|
||||
if len(t.nodes) > 0 {
|
||||
args = append(args, "--nodes")
|
||||
args = append(args, t.nodes...)
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("rebooting nodes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateSecrets generates cluster secrets
|
||||
func (t *TalosctlTool) GenerateSecrets(ctx context.Context) error {
|
||||
args := []string{"gen", "secrets"}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating secrets: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateConfigWithSecrets generates configuration with existing secrets
|
||||
func (t *TalosctlTool) GenerateConfigWithSecrets(ctx context.Context, clusterName, clusterEndpoint, secretsFile string) error {
|
||||
args := []string{
|
||||
"gen", "config",
|
||||
"--with-secrets", secretsFile,
|
||||
clusterName,
|
||||
clusterEndpoint,
|
||||
}
|
||||
|
||||
if t.talosconfig != "" {
|
||||
args = append([]string{"--talosconfig", t.talosconfig}, args...)
|
||||
}
|
||||
|
||||
_, err := t.Execute(ctx, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("generating config with secrets: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user