Development Guide

This guide covers how to contribute to kubectl-multi, set up your development environment, and understand the codebase.

Development Setup

Prerequisites

  • Go 1.21 or later
  • kubectl installed and configured
  • Access to KubeStellar managed clusters for testing
  • Make (for build automation)
  • Git

Building from Source

# Clone repository
git clone <repository-url>
cd kubectl-multi
 
# Download dependencies
make deps
 
# Build binary
make build
 
# Run tests
make test
 
# Format code
make fmt
 
# Run all checks
make check

Setup Kubestellar Demo Environment

This can help developers to set up multi-cluster where they can test their kubectl multi commands.

cd kubectl-multi
# Script for creating demo environment
./scripts/create-kubestellar-demo-env.sh

Follow Get-Started for detailed guide.

Development Workflow

  1. Fork the repository
  2. Create feature branch: git checkout -b feature/new-command
  3. Make changes: Follow Go best practices
  4. Add tests: Test new functionality
  5. Run checks: make check
  6. Submit PR: With detailed description

Project Structure

kubectl-multi/
├── main.go                 # Entry point
├── go.mod                  # Go module definition
├── go.sum                  # Dependency checksums
├── Makefile               # Build automation
├── README.md              # Main documentation
├── docs/                  # Documentation folder
│   ├── installation.md    # Installation guide
│   ├── usage.md           # Usage examples
│   ├── architecture.md    # Architecture details
│   ├── development.md     # This file
│   └── api-reference.md   # Code organization
├── pkg/
│   ├── cmd/               # Command implementations
│   │   ├── root.go        # Root command & CLI setup
│   │   ├── get.go         # Get command (fully implemented)
│   │   ├── describe.go    # Describe command (basic)
│   │   ├── apply.go       # Apply command (placeholder)
│   │   └── delete.go      # Other commands (placeholders)
│   ├── cluster/           # Cluster discovery & management
│   │   └── discovery.go   # KubeStellar cluster discovery
│   └── util/              # Utility functions
│       └── formatting.go  # Resource formatting utilities
└── bin/                   # Build output directory
    └── kubectl-multi      # Compiled binary

Adding New Commands

To add a new kubectl command (e.g., logs):

1. Create Command File

Create pkg/cmd/logs.go:

package cmd
 
import (
	"fmt"
	"github.com/spf13/cobra"
	"kubectl-multi/pkg/cluster"
)
 
func newLogsCommand() *cobra.Command {
	var (
		follow     bool
		previous   bool
		container  string
		since      string
		sinceTime  string
		timestamps bool
		tailLines  int64
	)
 
	cmd := &cobra.Command{
		Use:   "logs [-f] [-p] POD [-c CONTAINER]",
		Short: "Print logs for a container in a pod across managed clusters",
		Long: `Print the logs for a container in a pod across all managed clusters.
		
If the pod has container, the container name is optional.`,
		Args: cobra.ExactArgs(1), // Require exactly argument (pod name)
		RunE: func(cmd *cobra.Command, args []string) error &#123;
			return handleLogsCommand(args[0], container, follow, previous, since, sinceTime, timestamps, tailLines)
		&#125;,
	&#125;
 
	// Add flags
	cmd.Flags().BoolVarP(&follow, "follow", "f", false, "Specify if the logs should be streamed")
	cmd.Flags().BoolVarP(&previous, "previous", "p", false, "Print the logs for the previous instance of the container")
	cmd.Flags().StringVarP(&container, "container", "c", "", "Print the logs of this container")
	cmd.Flags().StringVar(&since, "since", "", "Only return logs newer than a relative duration like 5s, 2m, or 3h")
	cmd.Flags().StringVar(&sinceTime, "since-time", "", "Only return logs after a specific date (RFC3339)")
	cmd.Flags().BoolVar(&timestamps, "timestamps", false, "Include timestamps on each line in the log output")
	cmd.Flags().Int64Var(&tailLines, "tail", -1, "Lines of recent log file to display")
 
	return cmd
&#125;
 
func handleLogsCommand(podName, container string, follow, previous bool, since, sinceTime string, timestamps bool, tailLines int64) error &#123;
	// 1. Discover clusters
	clusters, err := cluster.DiscoverClusters(kubeconfig, remoteContext)
	if err != nil &#123;
		return fmt.Errorf("failed to discover clusters: %v", err)
	&#125;
 
	// 2. Get logs from each cluster
	for _, clusterInfo := range clusters &#123;
		fmt.Printf("=== Cluster: %s ===\n", clusterInfo.Name)
		
		// Build log options
		logOptions := &corev1.PodLogOptions&#123;
			Container:  container,
			Follow:     follow,
			Previous:   previous,
			Timestamps: timestamps,
		&#125;
		
		if tailLines >= 0 &#123;
			logOptions.TailLines = &tailLines
		&#125;
		
		// Handle since options
		if since != "" &#123;
			duration, err := time.ParseDuration(since)
			if err != nil &#123;
				fmt.Printf("Warning: invalid since duration for cluster %s: %v\n", clusterInfo.Name, err)
				continue
			&#125;
			sinceSeconds := int64(duration.Seconds())
			logOptions.SinceSeconds = &sinceSeconds
		&#125;
		
		if sinceTime != "" &#123;
			t, err := time.Parse(time.RFC3339, sinceTime)
			if err != nil &#123;
				fmt.Printf("Warning: invalid since-time for cluster %s: %v\n", clusterInfo.Name, err)
				continue
			&#125;
			sinceTime := metav1.NewTime(t)
			logOptions.SinceTime = &sinceTime
		&#125;
 
		// Get logs
		req := clusterInfo.Client.CoreV1().Pods(namespace).GetLogs(podName, logOptions)
		logs, err := req.Stream(context.TODO())
		if err != nil &#123;
			fmt.Printf("Warning: failed to get logs from cluster %s: %v\n", clusterInfo.Name, err)
			continue
		&#125;
		defer logs.Close()
 
		// Stream logs to output
		scanner := bufio.NewScanner(logs)
		for scanner.Scan() &#123;
			fmt.Printf("[%s] %s\n", clusterInfo.Name, scanner.Text())
		&#125;
		
		if err := scanner.Err(); err != nil &#123;
			fmt.Printf("Warning: error reading logs from cluster %s: %v\n", clusterInfo.Name, err)
		&#125;
		
		fmt.Println() // Add spacing between clusters
	&#125;
	
	return nil
&#125;