From a958011abdfe128d9bd7805f3a50e969eaf03df1 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 12:42:19 -0400 Subject: [PATCH 01/11] Refactor how design documentation is organized Signed-off-by: Joseph --- COMPLETION.md | 259 ------------------ .../design}/kubectl-oadp-design.md | 0 2 files changed, 259 deletions(-) delete mode 100644 COMPLETION.md rename {design => docs/design}/kubectl-oadp-design.md (100%) diff --git a/COMPLETION.md b/COMPLETION.md deleted file mode 100644 index 1a90757..0000000 --- a/COMPLETION.md +++ /dev/null @@ -1,259 +0,0 @@ -# OADP CLI Tab Completion Setup - -This document explains how to set up tab completion for the `oadp` cli plugin. - -## Overview - -Setting up completion requires 3 steps: - -1. **Prerequisites**: Ensure kubectl/oc completion is working -2. **Create wrapper script**: `kubectl_complete-oadp` (and optionally `oc_complete-oadp`) - these names follow kubectl/oc plugin completion conventions for automatic discovery - -3. **Configure shell**: Add completion config to `.zshrc` or `.bashrc` - -**Quick test**: After setup, `kubectl oadp ` should show available commands. - -## Prerequisites - -Before setting up kubectl-oadp completion, you need: - -1. **kubectl completion** configured in your shell -2. **oc completion** configured in your shell (if using OpenShift) - -### Check if kubectl completion is working: -```bash -kubectl get -# Should show: pods, services, deployments, etc. -``` - -### Set up kubectl completion if missing: - -**For zsh** (add to `~/.zshrc`): -```bash -if command -v kubectl >/dev/null 2>&1; then - source <(kubectl completion zsh) - compdef _kubectl kubectl -fi -``` - -**For bash** (add to `~/.bashrc`): -```bash -if command -v kubectl >/dev/null 2>&1; then - source <(kubectl completion bash) -fi -``` - -### Set up oc completion if using OpenShift: - -**For zsh** (add to `~/.zshrc`): -```bash -if command -v oc >/dev/null 2>&1; then - source <(oc completion zsh) - compdef _oc oc -fi -``` - -**For bash** (add to `~/.bashrc`): -```bash -if command -v oc >/dev/null 2>&1; then - source <(oc completion bash) -fi -``` - -## What You Need - -Tab completion requires two components: -1. **Completion wrapper script** (`kubectl_complete-oadp`) -2. **Shell configuration** (in `.zshrc` or `.bashrc`) - -## Quick Setup - -### 1. Install the Completion Wrapper - -Create the wrapper script in the same directory as your `kubectl-oadp` binary: - -#### For kubectl plugin completion: - -```bash -# Auto-detect kubectl-oadp location and create wrapper -OADP_PATH=$(which kubectl-oadp) -OADP_DIR=$(dirname "$OADP_PATH") - -cat > "${OADP_DIR}/kubectl_complete-oadp" << EOF -#!/bin/bash -# Wrapper script for kubectl plugin completion -exec ${OADP_PATH} __complete "\$@" -EOF -chmod +x "${OADP_DIR}/kubectl_complete-oadp" -``` - -#### For oc plugin completion: - -If you also want `oc oadp` completion (using the same binary as an oc plugin): - -```bash -# Create oc completion wrapper (uses same auto-detected path) -OADP_PATH=$(which kubectl-oadp) -OADP_DIR=$(dirname "$OADP_PATH") - -cat > "${OADP_DIR}/oc_complete-oadp" << EOF -#!/bin/bash -# Wrapper script for oc plugin completion -exec ${OADP_PATH} __complete "\$@" -EOF -chmod +x "${OADP_DIR}/oc_complete-oadp" -``` - -### 2. Configure Your Shell - -Add completion configuration to your shell's rc file: - -**For zsh** (add to `~/.zshrc`): -```bash -# kubectl-oadp completion -if command -v kubectl-oadp >/dev/null 2>&1; then - source <(kubectl-oadp completion zsh) - compdef _oadp kubectl-oadp -fi -``` - -**For bash** (add to `~/.bashrc`): -```bash -# kubectl-oadp completion -if command -v kubectl-oadp >/dev/null 2>&1; then - source <(kubectl-oadp completion bash) -fi -``` - -**Note for minimal environments (containers, etc.):** If you get `_get_comp_words_by_ref: command not found` errors, the bash-completion framework needs to be loaded first. Add this before the kubectl-oadp completion: -```bash -# Load bash-completion framework (if not auto-loaded) -if [ -f /usr/share/bash-completion/bash_completion ] && ! type _get_comp_words_by_ref >/dev/null 2>&1; then - source /usr/share/bash-completion/bash_completion -fi -``` - -### 3. Reload Your Shell - -```bash -# For zsh -source ~/.zshrc - -# For bash -source ~/.bashrc -``` - -## Test It Works - -Try typing and pressing TAB twice: -```bash -kubectl oadp -``` - -You should see available commands like `backup`, `nonadmin`, `nabsl`, etc. - -## How It Works - -1. **You type**: `kubectl oadp backup ` -2. **Shell detects**: Tab completion request for kubectl plugin -3. **Shell calls**: `kubectl_complete-oadp __complete kubectl oadp backup` -4. **Wrapper forwards to**: `kubectl-oadp __complete kubectl oadp backup` -5. **Plugin returns**: Available completions (create, delete, get, etc.) -6. **Shell shows**: The completion options - -## Troubleshooting - -### Completion Not Working? - -**Check if wrapper exists:** -```bash -# Find where kubectl-oadp is installed -OADP_DIR=$(dirname $(which kubectl-oadp)) -ls -la "${OADP_DIR}/kubectl_complete-oadp" -``` - -**Check if it's executable:** -```bash -chmod +x "${OADP_DIR}/kubectl_complete-oadp" -``` - -**Test wrapper directly:** -```bash -kubectl_complete-oadp __complete kubectl oadp -``` - -**Check shell configuration:** -```bash -# For zsh -grep -A5 "kubectl-oadp completion" ~/.zshrc - -# For bash -grep -A5 "kubectl-oadp completion" ~/.bashrc -``` - -### Path Issues? - -Make sure both files are in the same directory and that directory is in your PATH: -```bash -which kubectl-oadp -which kubectl_complete-oadp -which oc_complete-oadp # if using oc completion - -# Check if the directory is in PATH -OADP_DIR=$(dirname $(which kubectl-oadp)) -echo $PATH | grep -q "$OADP_DIR" && echo "✓ In PATH" || echo "✗ Not in PATH" -``` - -## Uninstalling Completion - -### Remove the Wrapper Scripts -```bash -# Find and remove the wrapper scripts -OADP_DIR=$(dirname $(which kubectl-oadp)) -rm "${OADP_DIR}/kubectl_complete-oadp" -rm "${OADP_DIR}/oc_complete-oadp" # if you created this -``` - -### Remove Shell Configuration - -**For zsh:** -1. Edit `~/.zshrc` -2. Remove the block starting with `# kubectl-oadp completion` through the `fi` line -3. Run `source ~/.zshrc` - -**For bash:** -1. Edit `~/.bashrc` -2. Remove the block starting with `# kubectl-oadp completion` through the `fi` line -3. Run `source ~/.bashrc` - -## Advanced: Custom Locations - -If your `kubectl-oadp` binary is in a non-standard location, the dynamic detection will automatically handle it as long as the binary is in your `$PATH`. If it's not in your PATH, you can either: - -1. **Add it to PATH** (recommended): -```bash -export PATH="/opt/oadp/bin:$PATH" -# Then use the standard setup commands above -``` - -2. **Use explicit paths**: -```bash -# Example for custom location /opt/oadp/bin -cat > /opt/oadp/bin/kubectl_complete-oadp << 'EOF' -#!/bin/bash -exec /opt/oadp/bin/kubectl-oadp __complete "$@" -EOF -chmod +x /opt/oadp/bin/kubectl_complete-oadp - -# Update shell config accordingly -if [ -f "/opt/oadp/bin/kubectl-oadp" ]; then - source Date: Mon, 9 Mar 2026 14:17:14 -0400 Subject: [PATCH 02/11] Add completion install command for shell autocompletion Implements 'kubectl-oadp completion install' subcommand that automatically sets up shell completions for both oc and kubectl-oadp commands. Features: - Auto-detects shell type (bash/zsh/fish) from $SHELL - Generates completion files in parallel using errgroup - Validates bash-completion package presence for bash shells - Provides shell-specific setup instructions with smart RC file detection - Supports --shell flag to override detection - Supports --force flag to reinstall existing completions - Idempotent (safe to run multiple times) Implementation details: - Skips output wrapping for completion command to preserve stdout - Uses shellConfig map to eliminate duplicate shell-specific logic - Adds timeout protection for external commands (brew, completion generation) - Includes comprehensive design documentation Resolves: #139 Signed-off-by: Joseph --- cmd/completion/completion.go | 31 ++ cmd/completion/install.go | 400 +++++++++++++++++++ cmd/root.go | 5 +- docs/design/139-COMPLETION_INSTALL_DESIGN.md | 267 +++++++++++++ go.mod | 5 +- go.sum | 2 + 6 files changed, 705 insertions(+), 5 deletions(-) create mode 100644 cmd/completion/completion.go create mode 100644 cmd/completion/install.go create mode 100644 docs/design/139-COMPLETION_INSTALL_DESIGN.md diff --git a/cmd/completion/completion.go b/cmd/completion/completion.go new file mode 100644 index 0000000..bbdfe7d --- /dev/null +++ b/cmd/completion/completion.go @@ -0,0 +1,31 @@ +/* +Copyright 2025 The OADP CLI Contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package completion + +import ( + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/cmd/config/completion" +) + +// NewCommand creates the completion command with install subcommand +func NewCommand() *cobra.Command { + cmd := completion.NewCommand() + + cmd.AddCommand(NewInstallCommand()) + + return cmd +} diff --git a/cmd/completion/install.go b/cmd/completion/install.go new file mode 100644 index 0000000..1638222 --- /dev/null +++ b/cmd/completion/install.go @@ -0,0 +1,400 @@ +/* +Copyright 2025 The OADP CLI Contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package completion + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "golang.org/x/sync/errgroup" +) + +const ( + shellBash = "bash" + shellZsh = "zsh" + shellFish = "fish" +) + +type shellConfig struct { + completionSubdir string + rcFile string + filePrefix string + fileExtension string + completionCheck func(content string) bool +} + +var shellConfigs = map[string]shellConfig{ + shellBash: { + completionSubdir: filepath.Join(".local", "share", "bash-completion", "completions"), + rcFile: ".bashrc", + filePrefix: "", + fileExtension: "", + completionCheck: func(content string) bool { + return strings.Contains(content, "bash_completion") + }, + }, + shellZsh: { + completionSubdir: filepath.Join(".zsh", "completions"), + rcFile: ".zshrc", + filePrefix: "_", + fileExtension: "", + completionCheck: func(content string) bool { + return strings.Contains(content, "fpath=") && strings.Contains(content, "compinit") + }, + }, + shellFish: { + completionSubdir: filepath.Join(".config", "fish", "completions"), + rcFile: "", + filePrefix: "", + fileExtension: ".fish", + completionCheck: func(content string) bool { + return true // fish auto-loads, no setup needed + }, + }, +} + +type InstallOptions struct { + Shell string + Force bool // Force regeneration of completion files (helpful after updates to OADP CLI) +} + +// BindFlags binds flags to the command +func (o *InstallOptions) BindFlags(flags *pflag.FlagSet) { + flags.StringVar(&o.Shell, "shell", "", "Shell to install completions for (bash, zsh, fish)") + flags.BoolVar(&o.Force, "force", false, "Reinstall completions even if already installed") +} + +// Complete completes the options +func (o *InstallOptions) Complete(args []string) error { + // Auto-detect shell if not specified + if o.Shell == "" { + shell := os.Getenv("SHELL") + if shell != "" { + o.Shell = filepath.Base(shell) + } + } + return nil +} + +// Validate validates the options +func (o *InstallOptions) Validate() error { + if _, ok := shellConfigs[o.Shell]; !ok { + return fmt.Errorf("unsupported shell: %s (supported: %s, %s, %s)", o.Shell, shellBash, shellZsh, shellFish) + } + return nil +} + +// Run executes the install command +func (o *InstallOptions) Run(c *cobra.Command) error { + //c.SilenceUsage = true + + fmt.Printf("Installing completions for %s...\n", o.Shell) + + // Check for bash-completion if shell is bash + if o.Shell == shellBash { + if !o.isBashCompletionInstalled() { + return o.printBashCompletionError() + } + } + + // Check if already installed (unless --force) + if !o.Force && o.isAlreadyInstalled() { + fmt.Println("✓ Completions files are already installed") + fmt.Println("Use --force to reinstall") + fmt.Println() + + // Still print setup instructions in case user hasn't configured their shell + o.printSetupInstructions() + return nil + } + + // Create completion directory + dir := o.getCompletionDir() + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create completion directory: %w", err) + } + + // Generate completion files + if err := o.generateCompletionFiles(dir); err != nil { + return fmt.Errorf("failed to generate completion files: %w", err) + } + + // Print success message with setup instructions + o.printSuccessMessage() + return nil +} + +// isBashCompletionInstalled checks if bash-completion is available +func (o *InstallOptions) isBashCompletionInstalled() bool { + // Check common bash-completion paths + paths := []string{ + "/opt/homebrew/etc/bash_completion", + "/usr/local/etc/bash_completion", + "/etc/bash_completion", + } + + for _, path := range paths { + if _, err := os.Stat(path); err == nil { + return true + } + } + + // Try brew --prefix with timeout + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, "brew", "--prefix") + if output, err := cmd.Output(); err == nil { + brewPrefix := strings.TrimSpace(string(output)) + path := filepath.Join(brewPrefix, "etc", "bash_completion") + if _, err := os.Stat(path); err == nil { + return true + } + } + + return false +} + +// printBashCompletionError prints error message for missing bash-completion +func (o *InstallOptions) printBashCompletionError() error { + fmt.Println("Error: bash-completion package is required for bash shell completions.") + fmt.Println() + fmt.Println("To install:") + fmt.Println(" macOS: brew install bash-completion") + fmt.Println(" Ubuntu: sudo apt-get install bash-completion") + fmt.Println(" RHEL/Fedora: sudo dnf install bash-completion") + fmt.Println() + fmt.Println("After installing, run: kubectl-oadp completion install") + return fmt.Errorf("bash-completion not installed") +} + +// isAlreadyInstalled checks if completions are already installed +func (o *InstallOptions) isAlreadyInstalled() bool { + dir := o.getCompletionDir() + config := shellConfigs[o.Shell] + + // Build file names using shell config + ocFile := filepath.Join(dir, config.filePrefix+"oc"+config.fileExtension) + oadpFile := filepath.Join(dir, config.filePrefix+"kubectl-oadp"+config.fileExtension) + + // Check if both completion files exist + _, ocErr := os.Stat(ocFile) + _, oadpErr := os.Stat(oadpFile) + return ocErr == nil && oadpErr == nil +} + +// getCompletionDir returns the completion directory for the shell +func (o *InstallOptions) getCompletionDir() string { + home, err := os.UserHomeDir() + if err != nil { + // Fallback to HOME env var if UserHomeDir fails + home = os.Getenv("HOME") + } + config := shellConfigs[o.Shell] + return filepath.Join(home, config.completionSubdir) +} + +// getRCFile returns the RC file path for the shell +func (o *InstallOptions) getRCFile() string { + config := shellConfigs[o.Shell] + if config.rcFile == "" { + return "" + } + home, err := os.UserHomeDir() + if err != nil { + // Fallback to HOME env var if UserHomeDir fails + home = os.Getenv("HOME") + } + return filepath.Join(home, config.rcFile) +} + +// generateCompletionFiles generates completion files for oc and kubectl-oadp +func (o *InstallOptions) generateCompletionFiles(dir string) error { + config := shellConfigs[o.Shell] + + ocFile := filepath.Join(dir, config.filePrefix+"oc"+config.fileExtension) + oadpFile := filepath.Join(dir, config.filePrefix+"kubectl-oadp"+config.fileExtension) + + // Generate completions in parallel + var g errgroup.Group + g.Go(func() error { + return o.generateCompletion("oc", ocFile) + }) + g.Go(func() error { + return o.generateCompletion("kubectl-oadp", oadpFile) + }) + + if err := g.Wait(); err != nil { + return fmt.Errorf("failed to generate completion files: %w", err) + } + + fmt.Printf("✓ Generated completion files in %s\n", dir) + return nil +} + +// generateCompletion generates completion for a command +func (o *InstallOptions) generateCompletion(command, outputFile string) error { + fmt.Printf(" Generating %s completion...\n", command) + + // Use timeout to prevent indefinite hangs + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + cmd := exec.CommandContext(ctx, command, "completion", o.Shell) + + // Capture both stdout and stderr for debugging + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run '%s completion %s': %w\nOutput: %s", command, o.Shell, err, string(output)) + } + + return os.WriteFile(outputFile, output, 0644) +} + +// printSuccessMessage prints success message with setup instructions +func (o *InstallOptions) printSuccessMessage() { + fmt.Println() + fmt.Println("✓ Completion files generated successfully!") + fmt.Println() + + o.printSetupInstructions() +} + +// printSetupInstructions prints shell-specific setup instructions +func (o *InstallOptions) printSetupInstructions() { + // Check if user already has completion setup in RC file + hasCompletionSetup := o.hasExistingCompletionSetup() + + switch o.Shell { + case shellFish: + fmt.Println("✓ Setup complete! Fish auto-loads completions.") + fmt.Println() + fmt.Println("Restart fish (or run: exec fish) to activate completions.") + + case shellZsh: + if hasCompletionSetup { + fmt.Println("✓ Setup complete! Your ~/.zshrc has completion infrastructure.") + fmt.Println() + fmt.Println("Completions will work after restarting your shell:") + fmt.Println(" source ~/.zshrc") + } else { + fmt.Println("⚠ Additional setup required!") + fmt.Println() + fmt.Println("Your ~/.zshrc is missing completion setup. Run:") + fmt.Println() + fmt.Println("cat >> ~/.zshrc << 'EOF'") + fmt.Println("fpath=(~/.zsh/completions $fpath)") + fmt.Println("autoload -Uz compinit && compinit") + fmt.Println("EOF") + fmt.Println() + fmt.Println("Then restart your shell:\n source ~/.zshrc") + } + + case shellBash: + if hasCompletionSetup { + fmt.Println("✓ Setup complete! Your ~/.bashrc has bash-completion loaded.") + fmt.Println() + fmt.Println("Completions will work after restarting your shell:") + fmt.Println(" source ~/.bashrc") + } else { + fmt.Println("⚠ Additional setup required!") + fmt.Println() + fmt.Println("Your ~/.bashrc is missing completion setup. Run:") + fmt.Println() + fmt.Println("cat >> ~/.bashrc << 'EOF'") + fmt.Println("# Load bash-completion framework") + fmt.Println("if [ -f $(brew --prefix)/etc/bash_completion ]; then") + fmt.Println(" . $(brew --prefix)/etc/bash_completion") + fmt.Println("elif [ -f /etc/bash_completion ]; then") + fmt.Println(" . /etc/bash_completion") + fmt.Println("fi") + fmt.Println() + fmt.Println("# Load custom completions") + fmt.Println("if [ -d ~/.local/share/bash-completion/completions ]; then") + fmt.Println(" for f in ~/.local/share/bash-completion/completions/*; do") + fmt.Println(" [ -r \"$f\" ] && . \"$f\"") + fmt.Println(" done") + fmt.Println("fi") + fmt.Println("EOF") + fmt.Println() + fmt.Println("Then restart your shell:\n source ~/.bashrc") + } + } +} + +// hasExistingCompletionSetup checks if RC file already has completion infrastructure +func (o *InstallOptions) hasExistingCompletionSetup() bool { + config := shellConfigs[o.Shell] + + // Fish auto-loads, no RC file check needed + if config.rcFile == "" { + return config.completionCheck("") + } + + rcFile := o.getRCFile() + content, err := os.ReadFile(rcFile) + if err != nil { + return false + } + + return config.completionCheck(string(content)) +} + +// NewInstallCommand creates the install subcommand +func NewInstallCommand() *cobra.Command { + o := &InstallOptions{} + + c := &cobra.Command{ + Use: "install", + Short: "Install shell completions for oc and kubectl-oadp", + Long: `Install shell completions for oc and kubectl-oadp. + +Automatically detects your shell and sets up completions in the appropriate +directory and configuration file. + +Supported shells: bash, zsh, fish + +For bash, the bash-completion package must be installed first.`, + Example: ` # Install completions for current shell + kubectl-oadp completion install + + # Install completions for specific shell + kubectl-oadp completion install --shell zsh + + # Reinstall/overwrite existing completions + kubectl-oadp completion install --force`, + Args: cobra.NoArgs, + RunE: func(c *cobra.Command, args []string) error { + if err := o.Complete(args); err != nil { + return err + } + if err := o.Validate(); err != nil { + return err + } + return o.Run(c) + }, + } + + o.BindFlags(c.Flags()) + return c +} diff --git a/cmd/root.go b/cmd/root.go index bbba4d1..d30d0a0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,6 +29,7 @@ import ( "time" "github.com/fatih/color" + "github.com/migtools/oadp-cli/cmd/completion" mustgather "github.com/migtools/oadp-cli/cmd/must-gather" "github.com/migtools/oadp-cli/cmd/nabsl-request" nonadmin "github.com/migtools/oadp-cli/cmd/non-admin" @@ -59,7 +60,6 @@ import ( veleroflag "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" "github.com/vmware-tanzu/velero/pkg/features" "k8s.io/klog/v2" - "sigs.k8s.io/kustomize/cmd/config/completion" ) // globalRequestTimeout holds the request timeout value set by --request-timeout flag. @@ -458,7 +458,8 @@ func NewVeleroRootCommand(baseName string) *cobra.Command { // Skip nonadmin commands since we have full control over their output for _, cmd := range c.Commands() { // Don't wrap nonadmin commands - we control them and they already use correct terminology - if cmd.Use == "nonadmin" || cmd.Use == "nabsl-request" || cmd.Use == "must-gather" || cmd.Use == "setup" { + // Don't wrap completion - it needs direct stdout access for shell completion generation + if cmd.Use == "nonadmin" || cmd.Use == "nabsl-request" || cmd.Use == "must-gather" || cmd.Use == "setup" || strings.HasPrefix(cmd.Use, "completion") { continue } replaceVeleroWithOADP(cmd) diff --git a/docs/design/139-COMPLETION_INSTALL_DESIGN.md b/docs/design/139-COMPLETION_INSTALL_DESIGN.md new file mode 100644 index 0000000..56911ea --- /dev/null +++ b/docs/design/139-COMPLETION_INSTALL_DESIGN.md @@ -0,0 +1,267 @@ +# Completion Install Command Design + +## Overview +New subcommand: `kubectl-oadp completion install` + +Automatically sets up shell completions for both `oc` and `kubectl-oadp` commands. + +## Requirements + +### 1. Shell Detection +- Auto-detect user's current shell from `$SHELL` environment variable +- Support override with `--shell` flag +- Supported shells: bash, zsh, fish + +### 2. Idempotency +- Check if completions are already installed +- Only modify RC files if setup is missing +- Use `--force` flag to reinstall/overwrite existing setup + +### 3. Bash-Specific Requirement ⚠️ +**CRITICAL**: Bash completions require the `bash-completion` package to be installed. + +#### Detection Logic for Bash: +```bash +# Check if bash-completion is available +if [ -f /usr/local/etc/bash_completion ]; then + # Homebrew install (macOS) +elif [ -f $(brew --prefix)/etc/bash_completion ]; then + # Homebrew with dynamic prefix +elif [ -f /etc/bash_completion ]; then + # Linux system install +else + # NOT INSTALLED - prompt user +fi +``` + +#### Error Message When Missing: +``` +Error: bash-completion package is required for bash shell completions. + +To install: + macOS: brew install bash-completion + Ubuntu: sudo apt-get install bash-completion + RHEL/Fedora: sudo dnf install bash-completion + +After installing, run: kubectl-oadp completion install +``` + +**Do not proceed with bash installation if bash-completion is missing.** + +### 4. Shell-Specific Setup + +#### Quick Comparison + +| Shell | Directory | RC File | External Deps | Complexity | +|-------|-----------|---------|---------------|------------| +| **Zsh** | `~/.zsh/completions/` | `~/.zshrc` | None ✅ | Medium | +| **Bash** | `~/.local/share/bash-completion/completions/` | `~/.bashrc` | bash-completion ⚠️ | High | +| **Fish** | `~/.config/fish/completions/` | None ✅ | None ✅ | Low | + +**Key takeaway**: Fish is simplest (no RC changes), Bash is most complex (requires external package + careful ordering). + +--- + +#### Zsh +- **Directory**: `~/.zsh/completions/` +- **RC File**: `~/.zshrc` +- **Setup Code**: + ```zsh + # OADP CLI completions + fpath=(~/.zsh/completions $fpath) + autoload -Uz compinit && compinit + ``` +- **No external dependencies** (compinit is built-in) + +#### Bash +- **Directory**: `~/.local/share/bash-completion/completions/` +- **RC File**: `~/.bashrc` (or `~/.bash_profile` on macOS) +- **Requires**: `bash-completion` package ⚠️ + +**CRITICAL ORDERING**: The bash-completion framework **MUST** be loaded **BEFORE** sourcing individual completion files. + +- **Setup Code**: + ```bash + # BEGIN OADP CLI COMPLETIONS - Managed by kubectl-oadp + # Load bash-completion framework FIRST (provides _get_comp_words_by_ref, etc.) + if [ -f $(brew --prefix)/etc/bash_completion ]; then + . $(brew --prefix)/etc/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi + + # Load custom completions from user directory + # Note: bash-completion v2+ may auto-load from ~/.local/share/bash-completion/completions/ + # but we explicitly source them to ensure compatibility with v1 + if [ -d ~/.local/share/bash-completion/completions ]; then + for completion_file in ~/.local/share/bash-completion/completions/*; do + [ -r "$completion_file" ] && . "$completion_file" + done + fi + # END OADP CLI COMPLETIONS + ``` + +**Why this order matters**: Completion scripts generated by `oc completion bash` and `kubectl-oadp completion bash` use functions like `_get_comp_words_by_ref` provided by the bash-completion framework. If you source the completion files before loading the framework, you'll get `_get_comp_words_by_ref: command not found` errors. + +#### Fish +- **Directory**: `~/.config/fish/completions/` +- **RC File**: None needed (auto-loads `*.fish` files) +- **No external dependencies** (built-in completion system) + +**Fish is the simplest**: No RC file modification needed! Fish automatically loads any `*.fish` files from `~/.config/fish/completions/` on shell startup. + +- **Setup Steps**: + ```bash + # 1. Create completions directory + mkdir -p ~/.config/fish/completions + + # 2. Generate completion files (note: .fish extension required) + oc completion fish > ~/.config/fish/completions/oc.fish + kubectl-oadp completion fish > ~/.config/fish/completions/kubectl-oadp.fish + + # 3. Done! No RC file changes needed - fish auto-loads on next shell start + ``` + +**Testing**: +```bash +# Launch fish shell +fish + +# Test completions (should work immediately) +oc +oc oadp +kubectl-oadp +``` + +**Idempotency for Fish**: Only check if completion files exist: +- `~/.config/fish/completions/oc.fish` +- `~/.config/fish/completions/kubectl-oadp.fish` +- No RC file marker needed + +**Important**: File naming matters for fish! +- ✅ Correct: `oc.fish`, `kubectl-oadp.fish` (`.fish` extension) +- ❌ Wrong: `_oc`, `_kubectl-oadp` (zsh convention doesn't work in fish) + +### 5. Idempotency Strategy + +#### RC File Modification +Use marker comments to detect if setup already exists: + +```bash +# BEGIN OADP CLI COMPLETIONS - Managed by kubectl-oadp + +# END OADP CLI COMPLETIONS +``` + +**Before adding**: +1. Read RC file +2. Search for `# BEGIN OADP CLI COMPLETIONS` +3. If found → already installed (skip unless `--force`) +4. If not found → safe to add + +#### Force Flag Behavior +With `--force`: +1. Remove existing marker block (if present) +2. Regenerate completion files +3. Add fresh setup block to RC file + +### 6. Command Structure + +``` +kubectl-oadp completion +├── [bash|zsh|fish|powershell] # Generate script (existing) +└── install # Auto-install (NEW) + ├── --shell # Override auto-detection + └── --force # Reinstall/overwrite +``` + +### 7. Install Flow + +``` +1. Detect shell (or use --shell flag) + ↓ +2. If bash: Check for bash-completion package + ├─ Found → Continue + └─ Not found → Error with install instructions + ↓ +3. Check if already installed (unless --force) + ├─ Already installed → Exit with message + └─ Not installed → Continue + ↓ +4. Create completion directory + ↓ +5. Generate completion files: + - Zsh: oc completion zsh > ~/.zsh/completions/_oc + - Bash: oc completion bash > ~/.local/share/bash-completion/completions/oc + - Fish: oc completion fish > ~/.config/fish/completions/oc.fish + - Same for kubectl-oadp + ↓ +6. Add setup to RC file (with markers) + - Zsh: Add fpath and compinit to ~/.zshrc + - Bash: Add bash-completion loader to ~/.bashrc + - Fish: SKIP (no RC file changes needed - auto-loads) + ↓ +7. Success message: + "✓ Completions installed successfully + Restart your shell or run: source ~/.rc" +``` + +## Success Criteria +- ✅ Works for zsh, bash, fish +- ✅ Detects bash-completion missing and prompts user +- ✅ Idempotent (can run multiple times safely) +- ✅ Uses markers for RC file management +- ✅ Supports --force to reinstall +- ✅ Supports --shell to override detection +- ✅ Clear error messages and next steps + +## Testing Checklist +- [x] Zsh: Fresh install works ✅ (Tested 2025-03-06) +- [ ] Zsh: Idempotency (second run detects existing) +- [x] Bash: Detects missing bash-completion ✅ (Tested 2025-03-06) +- [x] Bash: Fresh install works (with bash-completion present) ✅ (Tested 2025-03-06) +- [ ] Bash: Idempotency works +- [x] Fish: Fresh install works ✅ (Tested 2025-03-06) +- [ ] Fish: Idempotency works +- [ ] --force flag regenerates everything +- [ ] --shell flag overrides detection + +## Common Issues & Troubleshooting + +### Bash: `_get_comp_words_by_ref: command not found` + +**Cause**: The bash-completion framework is not loaded, or it's being loaded AFTER the completion files are sourced. + +**Solution**: Ensure bash-completion framework is loaded FIRST in `.bashrc`: + +```bash +# ✅ CORRECT ORDER: +# 1. Load bash-completion framework first +if [ -f $(brew --prefix)/etc/bash_completion ]; then + . $(brew --prefix)/etc/bash_completion +fi + +# 2. Then source completion files +. ~/.local/share/bash-completion/completions/oc +. ~/.local/share/bash-completion/completions/kubectl-oadp + +# ❌ WRONG ORDER (will fail): +# 1. Source completion files first +. ~/.local/share/bash-completion/completions/oc # ERROR: _get_comp_words_by_ref not found! + +# 2. Load framework after +. $(brew --prefix)/etc/bash_completion +``` + +### Bash: bash-completion installed but still errors + +**Check installation paths**: +```bash +# macOS (Homebrew) +ls -la $(brew --prefix)/etc/bash_completion + +# Linux +ls -la /etc/bash_completion +``` + +If file exists but still not working, verify it's actually being sourced by adding debug output to `.bashrc`. diff --git a/go.mod b/go.mod index b95c980..c9f5d5a 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/migtools/oadp-cli -go 1.24.0 - -toolchain go1.24.3 +go 1.25.0 require ( github.com/fatih/color v1.18.0 @@ -92,6 +90,7 @@ require ( golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect diff --git a/go.sum b/go.sum index 40e4eeb..235d56d 100644 --- a/go.sum +++ b/go.sum @@ -721,6 +721,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 7cc41fdfae33ad7105ba59382825e24261336684 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:09:17 -0400 Subject: [PATCH 03/11] Upgrade golangci-lint to v2.11.0 for Go 1.25 support Updates golangci-lint from v1.63.4 to v2.11.0 which is built with Go 1.26 and supports Go 1.25 projects. Changes installation method from 'go install' to official binary download script as v2.x requires. Adds .golangci.yml config to disable errcheck for CLI tool where output errors are typically unrecoverable (if writing to stdout fails, the program is fundamentally broken). Applies auto-fixes from new linter: - Use metav1.Time.Format() directly instead of .Time.Format() - Simplify variable declarations with type inference - Use fmt.Fprintf for string builder formatting Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: Joseph --- .golangci.yml | 9 +++++++++ Makefile | 17 ++--------------- cmd/nabsl-request/describe.go | 2 +- cmd/nabsl-request/get.go | 2 +- cmd/non-admin/backup/describe.go | 8 ++++---- cmd/non-admin/backup/get.go | 2 +- cmd/non-admin/restore/describe.go | 8 ++++---- cmd/non-admin/restore/get.go | 2 +- 8 files changed, 23 insertions(+), 27 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d4ac01b --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,9 @@ +version: 2 + +linters: + disable: + - errcheck # Disabled for CLI tool - output errors are typically unrecoverable + +run: + timeout: 5m + go: "1.25" diff --git a/Makefile b/Makefile index 5e672c4..517e265 100644 --- a/Makefile +++ b/Makefile @@ -358,24 +358,11 @@ $(LOCALBIN): mkdir -p $(LOCALBIN) # Tool versions -GOLANGCI_LINT_VERSION ?= v1.63.4 +GOLANGCI_LINT_VERSION ?= v2.11.0 # Tool binaries GOLANGCI_LINT = $(LOCALBIN)/golangci-lint -# go-install-tool will 'go install' any package $2 and install it to $1. -define go-install-tool -[ -f $(1) ] || { \ -set -e ;\ -TMP_DIR=$$(mktemp -d) ;\ -cd $$TMP_DIR ;\ -go mod init tmp ;\ -echo "Downloading $(2)" ;\ -GOBIN=$(LOCALBIN) go install $(2) ;\ -rm -rf $$TMP_DIR ;\ -} -endef - # golangci-lint installation .PHONY: golangci-lint golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary @@ -384,7 +371,7 @@ $(GOLANGCI_LINT): $(LOCALBIN) echo "golangci-lint $(GOLANGCI_LINT_VERSION) is already installed"; \ else \ echo "Installing golangci-lint $(GOLANGCI_LINT_VERSION)"; \ - $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION)); \ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LOCALBIN) $(GOLANGCI_LINT_VERSION); \ fi # Testing targets diff --git a/cmd/nabsl-request/describe.go b/cmd/nabsl-request/describe.go index bd600ae..461aeea 100644 --- a/cmd/nabsl-request/describe.go +++ b/cmd/nabsl-request/describe.go @@ -181,7 +181,7 @@ func describeRequest(request *nacv1alpha1.NonAdminBackupStorageLocationRequest) } // Creation Timestamp - fmt.Printf("Creation Timestamp: %s\n", request.CreationTimestamp.Time.Format("2006-01-02 15:04:05 -0700 MST")) + fmt.Printf("Creation Timestamp: %s\n", request.CreationTimestamp.Format("2006-01-02 15:04:05 -0700 MST")) return nil } diff --git a/cmd/nabsl-request/get.go b/cmd/nabsl-request/get.go index 9004c0a..483c067 100644 --- a/cmd/nabsl-request/get.go +++ b/cmd/nabsl-request/get.go @@ -123,7 +123,7 @@ func (o *GetOptions) Run(c *cobra.Command, f client.Factory) error { // List all requests in admin namespace var requestList nacv1alpha1.NonAdminBackupStorageLocationRequestList - var err error = o.client.List(context.Background(), &requestList, &kbclient.ListOptions{ + var err = o.client.List(context.Background(), &requestList, &kbclient.ListOptions{ Namespace: adminNS, }) diff --git a/cmd/non-admin/backup/describe.go b/cmd/non-admin/backup/describe.go index 8c37b67..68cebae 100644 --- a/cmd/non-admin/backup/describe.go +++ b/cmd/non-admin/backup/describe.go @@ -299,10 +299,10 @@ func printNonAdminBackupDetails(cmd *cobra.Command, nab *nacv1alpha1.NonAdminBac // Started and Completed times if !status.StartTimestamp.IsZero() { - fmt.Fprintf(out, "Started: %s\n", status.StartTimestamp.Time.Format("2006-01-02 15:04:05 -0700 MST")) + fmt.Fprintf(out, "Started: %s\n", status.StartTimestamp.Format("2006-01-02 15:04:05 -0700 MST")) } if !status.CompletionTimestamp.IsZero() { - fmt.Fprintf(out, "Completed: %s\n", status.CompletionTimestamp.Time.Format("2006-01-02 15:04:05 -0700 MST")) + fmt.Fprintf(out, "Completed: %s\n", status.CompletionTimestamp.Format("2006-01-02 15:04:05 -0700 MST")) } fmt.Fprintf(out, "\n") @@ -511,9 +511,9 @@ func formatResourceList(resourceList string) string { var output strings.Builder for _, gvk := range keys { items := resources[gvk] - output.WriteString(fmt.Sprintf(" %s:\n", gvk)) + fmt.Fprintf(&output, " %s:\n", gvk) for _, item := range items { - output.WriteString(fmt.Sprintf(" - %s\n", item)) + fmt.Fprintf(&output, " - %s\n", item) } } diff --git a/cmd/non-admin/backup/get.go b/cmd/non-admin/backup/get.go index 9e9064c..1ef9e04 100644 --- a/cmd/non-admin/backup/get.go +++ b/cmd/non-admin/backup/get.go @@ -149,7 +149,7 @@ func getBackupDuration(nab *nacv1alpha1.NonAdminBackup) string { if nab.Status.VeleroBackup != nil && nab.Status.VeleroBackup.Status != nil { if !nab.Status.VeleroBackup.Status.CompletionTimestamp.IsZero() { // Calculate duration from request creation to completion - duration := nab.Status.VeleroBackup.Status.CompletionTimestamp.Time.Sub(nab.CreationTimestamp.Time) + duration := nab.Status.VeleroBackup.Status.CompletionTimestamp.Sub(nab.CreationTimestamp.Time) return formatDuration(duration) } } diff --git a/cmd/non-admin/restore/describe.go b/cmd/non-admin/restore/describe.go index de13b8a..9c7604d 100644 --- a/cmd/non-admin/restore/describe.go +++ b/cmd/non-admin/restore/describe.go @@ -290,10 +290,10 @@ func printNonAdminRestoreDetails(cmd *cobra.Command, nar *nacv1alpha1.NonAdminRe // Started and Completed times if !status.StartTimestamp.IsZero() { - fmt.Fprintf(out, "Started: %s\n", status.StartTimestamp.Time.Format("2006-01-02 15:04:05 -0700 MST")) + fmt.Fprintf(out, "Started: %s\n", status.StartTimestamp.Format("2006-01-02 15:04:05 -0700 MST")) } if !status.CompletionTimestamp.IsZero() { - fmt.Fprintf(out, "Completed: %s\n", status.CompletionTimestamp.Time.Format("2006-01-02 15:04:05 -0700 MST")) + fmt.Fprintf(out, "Completed: %s\n", status.CompletionTimestamp.Format("2006-01-02 15:04:05 -0700 MST")) } fmt.Fprintf(out, "\n") @@ -430,9 +430,9 @@ func formatRestoreResourceList(resourceList string) string { var output strings.Builder for _, gvk := range keys { items := resources[gvk] - output.WriteString(fmt.Sprintf(" %s:\n", gvk)) + fmt.Fprintf(&output, " %s:\n", gvk) for _, item := range items { - output.WriteString(fmt.Sprintf(" - %s\n", item)) + fmt.Fprintf(&output, " - %s\n", item) } } diff --git a/cmd/non-admin/restore/get.go b/cmd/non-admin/restore/get.go index 4f34df2..ebc8542 100644 --- a/cmd/non-admin/restore/get.go +++ b/cmd/non-admin/restore/get.go @@ -149,7 +149,7 @@ func getRestoreDuration(nar *nacv1alpha1.NonAdminRestore) string { if nar.Status.VeleroRestore != nil && nar.Status.VeleroRestore.Status != nil { if !nar.Status.VeleroRestore.Status.CompletionTimestamp.IsZero() { // Calculate duration from request creation to completion - duration := nar.Status.VeleroRestore.Status.CompletionTimestamp.Time.Sub(nar.CreationTimestamp.Time) + duration := nar.Status.VeleroRestore.Status.CompletionTimestamp.Sub(nar.CreationTimestamp.Time) return formatDuration(duration) } } From b15568ad68918896c503884ddbf47e29ed42783c Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:19:29 -0400 Subject: [PATCH 04/11] Lint workflow update to go 1.25 Signed-off-by: Joseph --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fc41ab8..e1396ef 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.24' + go-version: '1.25' - name: golangci-lint uses: golangci/golangci-lint-action@v3 From bec41cace3608316cc1a9f00e088a47efc4e8ffe Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:25:20 -0400 Subject: [PATCH 05/11] Update golangci-lint version to v2.11.0 Signed-off-by: Joseph --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e1396ef..5eefcf3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: latest + version: v2.11.0 args: --timeout=5m - name: Run go fmt From 939997d30c8ec4fb770c149f207da3b7c133458c Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:30:03 -0400 Subject: [PATCH 06/11] Fix golangci-lint action version Signed-off-by: Joseph --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5eefcf3..dcb4f6d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,9 +17,9 @@ jobs: go-version: '1.25' - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v6 with: - version: v2.11.0 + version: v1.61.0 args: --timeout=5m - name: Run go fmt From e84d7a727395cc3afbcb27221a9ea7f537586ae6 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:31:47 -0400 Subject: [PATCH 07/11] Remove version from golangci-lint action Signed-off-by: Joseph --- .github/workflows/lint.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dcb4f6d..34a3679 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,6 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.61.0 args: --timeout=5m - name: Run go fmt From b11ec47c5c694f60db89f83c055c4e34b97c3767 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:34:05 -0400 Subject: [PATCH 08/11] Update version of golangci-lint action Signed-off-by: Joseph --- .github/workflows/lint.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 34a3679..791771b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,13 +12,14 @@ jobs: - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: go-version: '1.25' - + - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v9 with: + version: v2.10 args: --timeout=5m - name: Run go fmt From cdd94f9895272d394699d91f8e0dc2b758241d73 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:36:15 -0400 Subject: [PATCH 09/11] String version Signed-off-by: Joseph --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index d4ac01b..b7d964f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,4 @@ -version: 2 +version: "2" linters: disable: From dfdac43e6f1686226bf50db048af0e2f397fac80 Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:39:21 -0400 Subject: [PATCH 10/11] Go version 1.25 in Containerfile.download Signed-off-by: Joseph --- Containerfile.download | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Containerfile.download b/Containerfile.download index 2e19c96..d350b84 100644 --- a/Containerfile.download +++ b/Containerfile.download @@ -1,7 +1,7 @@ # This Dockerfile is used to cross-build the kubectl-oadp binaries for all platforms # It also builds a Go server that serves the built binaries -FROM --platform=$BUILDPLATFORM golang:1.24 AS builder +FROM --platform=$BUILDPLATFORM golang:1.25 AS builder ARG TARGETOS ARG TARGETARCH From 14aff5b04b4bc3195620e46a6a9d1dc8c1fb37ab Mon Sep 17 00:00:00 2001 From: Joseph Date: Mon, 9 Mar 2026 15:42:11 -0400 Subject: [PATCH 11/11] Go mod tidy Signed-off-by: Joseph --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c9f5d5a..a106e21 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/vmware-tanzu/velero v1.14.0 + golang.org/x/sync v0.20.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.33.1 k8s.io/apimachinery v0.33.1 @@ -90,7 +91,6 @@ require ( golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect