diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fc41ab8..791771b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,14 +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.24' - + go-version: '1.25' + - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v9 with: - version: latest + version: v2.10 args: --timeout=5m - name: Run go fmt diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..b7d964f --- /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/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 > ~/.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/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) } } 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/design/kubectl-oadp-design.md b/docs/design/kubectl-oadp-design.md similarity index 100% rename from design/kubectl-oadp-design.md rename to docs/design/kubectl-oadp-design.md diff --git a/go.mod b/go.mod index b95c980..a106e21 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 @@ -11,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 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=