Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 0 additions & 141 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,6 @@ A kubectl plugin for OpenShift API for Data Protection (OADP) that provides both

> **What it does**: Extends OADP functionality with a unified CLI that supports both cluster-wide Velero operations (admin) and namespace-scoped self-service operations (non-admin users).

## Key Capabilities

- **Admin Operations**: Full Velero backup, restore, and version commands (requires cluster admin permissions)
- **Non-Admin Operations**: Namespace-scoped backup operations using non-admin CRDs (works with regular user permissions)
- **Smart Namespace Handling**: Non-admin commands automatically operate in your current kubectl context namespace
- **Seamless Integration**: Works as a standard kubectl plugin

## Command Structure

```
kubectl oadp
├── backup # Velero cluster-wide backups (admin)
├── restore # Velero cluster-wide restores (admin)
├── version # Version information
├── nabsl-request # Manage NonAdminBackupStorageLocation approval requests
└── nonadmin (na) # Namespace-scoped operations (non-admin)
└── backup
├── create
├── describe
├── logs
└── delete
```

## Installation

Expand All @@ -50,132 +28,13 @@ kubectl oadp --help
make install-system
```

The `make install` command automatically detects your OADP deployment namespace by looking for:
1. **OADP Controller** (`openshift-adp-controller-manager` deployment)
2. **DPA Resources** (`DataProtectionApplication` custom resources)
3. **Velero Deployment** (fallback for vanilla Velero installations)

If no OADP resources are detected, you'll be prompted to specify the namespace manually.

**Installation Options:**
```sh
make install # Smart detection + interactive prompt
make install ASSUME_DEFAULT=true # Use default namespace (no detection)
make install VELERO_NAMESPACE=custom # Use specific namespace (no detection)
```

**💡 Path Setup:** The installer will automatically check your PATH and guide you through any necessary setup. If `kubectl oadp` doesn't work immediately after installation, follow the on-screen instructions to update your PATH for the current session or restart your terminal.

You can set the velero namespace afterwards using the oadp client command



## Usage Guide

### Non-Admin Backup Operations

Non-admin commands work within your current namespace and user permissions:

```sh
# Basic backup of current namespace
kubectl oadp nonadmin backup create my-backup
# Short form
kubectl oadp na backup create my-backup

# Include specific resource types only
kubectl oadp na backup create app-backup --include-resources deployments,services,configmaps

# Exclude sensitive data
kubectl oadp na backup create safe-backup --exclude-resources secrets

# Preview backup configuration without creating
kubectl oadp na backup create test-backup --snapshot-volumes=false -o yaml

# Create backup and wait for completion
kubectl oadp na backup create prod-backup --wait

# Check backup status
kubectl oadp na backup describe my-backup

# View backup logs
kubectl oadp na backup logs my-backup

# Delete a backup
kubectl oadp na backup delete my-backup
```

### Admin Operations

Admin commands require cluster-level permissions and operate across all namespaces:

```sh
# Cluster-wide backup operations
kubectl oadp backup create cluster-backup --include-namespaces namespace1,namespace2

# Restore operations
kubectl oadp restore create --from-backup cluster-backup

# Check OADP/Velero version
kubectl oadp version
```

## How Non-Admin Backups Work

1. **Namespace Detection**: Commands automatically use your current kubectl context namespace
2. **Permission Model**: Works with standard namespace-level RBAC permissions
3. **Resource Creation**: Creates `Non-admin` custom resources that are processed by the OADP operator
4. **Velero Integration**: OADP operator translates NonAdminBackup resources into standard Velero backup jobs

**Example workflow:**
```sh
# Switch to your project namespace
kubectl config set-context --current --namespace=my-project

# Create backup (automatically backs up 'my-project' namespace)
kubectl oadp na backup create project-backup --wait

# Monitor progress
kubectl oadp na backup logs project-backup
```

## Development

### Quick Development Commands

```sh
# Build and test locally
make build
./kubectl-oadp --help

# Run integration tests
make test

# Build release archives
make release

# Generate Krew manifest
make krew-manifest
```

### Project Structure

- **`cmd/`**: Command definitions and CLI logic
- **`cmd/non-admin/`**: Non-admin specific commands
- **`tests/`**: Integration tests and test utilities
- **`Makefile`**: Build automation and common tasks

## Testing

Comprehensive integration tests verify CLI functionality:

```bash
# Run all tests
make test

# For detailed test information
cat tests/README.md
```

## Technical Details

**Built with:**
Expand Down
42 changes: 11 additions & 31 deletions cmd/non-admin/backup/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@ limitations under the License.
*/

import (
"bufio"
"compress/gzip"
"context"
"fmt"
"io"
"net/http"
"time"

"github.com/migtools/oadp-cli/cmd/shared"
Expand Down Expand Up @@ -94,17 +90,21 @@ func NewLogsCommand(f client.Factory, use string) *cobra.Command {
return fmt.Errorf("failed to create NonAdminDownloadRequest: %w", err)
}

// Clean up the download request when done
defer func() {
deleteCtx, cancelDelete := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelDelete()
_ = kbClient.Delete(deleteCtx, req)
}()

var signedURL string
timeout := time.After(120 * time.Second) // Increased timeout to 2 minutes
tick := time.Tick(2 * time.Second) // Check every 2 seconds instead of 1
fmt.Fprintf(cmd.OutOrStdout(), "Waiting for backup logs to be processed...\n")

// Wait for the download request to be processed using shared utility
// Note: We create a custom waiting implementation here to provide user feedback
timeout := time.After(120 * time.Second)
tick := time.Tick(2 * time.Second)

fmt.Fprintf(cmd.OutOrStdout(), "Waiting for backup logs to be processed...")
var signedURL string
Loop:
for {
select {
Expand Down Expand Up @@ -140,29 +140,9 @@ func NewLogsCommand(f client.Factory, use string) *cobra.Command {
}
}

resp, err := http.Get(signedURL)
if err != nil {
return fmt.Errorf("failed to download logs from URL %q: %w", signedURL, err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("failed to download logs: status %s, body: %s", resp.Status, string(bodyBytes))
}

gzr, err := gzip.NewReader(resp.Body)
if err != nil {
return fmt.Errorf("failed to create gzip reader: %w", err)
}
defer gzr.Close()

scanner := bufio.NewScanner(gzr)
for scanner.Scan() {
fmt.Fprintln(cmd.OutOrStdout(), scanner.Text())
}
if err := scanner.Err(); err != nil && err != io.EOF {
return fmt.Errorf("failed to read logs: %w", err)
// Use the shared StreamDownloadContent function to download and stream logs
if err := shared.StreamDownloadContent(signedURL, cmd.OutOrStdout()); err != nil {
return fmt.Errorf("failed to download and stream logs: %w", err)
}

return nil
Expand Down
130 changes: 70 additions & 60 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,93 +17,103 @@ limitations under the License.
package cmd

import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/fatih/color"
"github.com/migtools/oadp-cli/cmd/nabsl-request"
nonadmin "github.com/migtools/oadp-cli/cmd/non-admin"
"github.com/migtools/oadp-cli/cmd/verbs"
"github.com/spf13/cobra"
clientcmd "github.com/vmware-tanzu/velero/pkg/client"

"github.com/vmware-tanzu/velero/pkg/cmd/cli/backup"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/client"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/backuplocation"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/bug"
cliclient "github.com/vmware-tanzu/velero/pkg/cmd/cli/client"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/create"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/datamover"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/debug"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/delete"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/describe"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/get"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/repo"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/repomantenance"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/restore"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/schedule"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/snapshotlocation"
"github.com/vmware-tanzu/velero/pkg/cmd/cli/version"

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"
)

// isRunningAsPlugin detects if the executable is running as a kubectl plugin
func isRunningAsPlugin() bool {
return strings.HasPrefix(filepath.Base(os.Args[0]), "kubectl-")
}
// NewVeleroRootCommand returns a root command with all Velero CLI subcommands attached.
func NewVeleroRootCommand(baseName string) *cobra.Command {

// getUsagePrefix returns the appropriate command prefix for help messages
func getUsagePrefix() string {
if isRunningAsPlugin() {
return "kubectl oadp"
config, err := clientcmd.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error reading config file: %v\n", err)
}
return "oadp"
}

// NewVeleroRootCommand returns a root command with all Velero CLI subcommands attached.
func NewVeleroRootCommand() *cobra.Command {
usagePrefix := getUsagePrefix()
// Declare cmdFeatures and cmdColorzied here so we can access them in the PreRun hooks
// without doing a chain of calls into the command's FlagSet
var cmdFeatures veleroflag.StringArray
var cmdColorzied veleroflag.OptionalBool

rootCmd := &cobra.Command{
Use: "oadp",
c := &cobra.Command{
Use: baseName,
Short: "OADP CLI commands",
Run: func(cmd *cobra.Command, args []string) {
// Default action when no subcommand is provided
if isRunningAsPlugin() {
fmt.Printf("Welcome to the OADP CLI! Use '%s --help' to see available commands.\n", usagePrefix)
} else {
fmt.Println("Welcome to the OADP CLI! Use --help to see available commands.")
fmt.Println("Welcome to the OADP CLI! Use --help to see available commands.")
},
PersistentPreRun: func(cmd *cobra.Command, args []string) {
features.Enable(config.Features()...)
features.Enable(cmdFeatures...)

switch {
case cmdColorzied.Value != nil:
color.NoColor = !*cmdColorzied.Value
default:
color.NoColor = !config.Colorized()
}
},
}

// Create Velero client factory for regular Velero commands
// This factory is used to create clients for interacting with Velero resources.
veleroFactory := newVeleroFactory()

// Create NonAdmin client factory for NonAdminBackup commands
// This factory uses the current kubeconfig context namespace instead of hardcoded openshift-adp
nonAdminFactory := NewNonAdminFactory()

// Create the commands and modify their help text before adding them
backupCmd := backup.NewCommand(veleroFactory)
restoreCmd := restore.NewCommand(veleroFactory)
clientCmd := client.NewCommand()

// Modify help text to replace "velero" with "oadp"
updateCommandHelpText(backupCmd, usagePrefix)
updateCommandHelpText(restoreCmd, usagePrefix)

// Add subcommands to the root command
rootCmd.AddCommand(version.NewCommand(veleroFactory))
rootCmd.AddCommand(backupCmd)
rootCmd.AddCommand(restoreCmd)
rootCmd.AddCommand(clientCmd)

// Add verb-based commands for compatibility with Velero CLI pattern
rootCmd.AddCommand(verbs.NewGetCommand(veleroFactory, nonAdminFactory))
rootCmd.AddCommand(verbs.NewCreateCommand(veleroFactory, nonAdminFactory))
rootCmd.AddCommand(verbs.NewDeleteCommand(veleroFactory, nonAdminFactory))
rootCmd.AddCommand(verbs.NewDescribeCommand(veleroFactory, nonAdminFactory))
rootCmd.AddCommand(verbs.NewLogsCommand(veleroFactory, nonAdminFactory))
f := clientcmd.NewFactory(baseName, config)

c.AddCommand(
backup.NewCommand(f),
schedule.NewCommand(f),
restore.NewCommand(f),
version.NewCommand(f),
get.NewCommand(f),
describe.NewCommand(f),
create.NewCommand(f),
delete.NewCommand(f),
cliclient.NewCommand(),
completion.NewCommand(),
repo.NewCommand(f),
bug.NewCommand(),
backuplocation.NewCommand(f),
snapshotlocation.NewCommand(f),
debug.NewCommand(f),
repomantenance.NewCommand(f),
datamover.NewCommand(f),
)

// Admin NABSL request commands - use Velero factory (admin namespace)
rootCmd.AddCommand(nabsl.NewNABSLRequestCommand(veleroFactory))
c.AddCommand(nabsl.NewNABSLRequestCommand(f))

// Custom subcommands - use NonAdmin factory
rootCmd.AddCommand(nonadmin.NewNonAdminCommand(nonAdminFactory))
c.AddCommand(nonadmin.NewNonAdminCommand(f))

return rootCmd
}

func Execute() {
if err := NewVeleroRootCommand().Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
klog.InitFlags(flag.CommandLine)
c.PersistentFlags().AddGoFlagSet(flag.CommandLine)
return c
}
Loading
Loading