Skip to content

Orchestrator Pattern

Coordinate multi-step workflows through a single entry point.

Single Entry Point

For complex workflows, expose one command that coordinates subcommands. Users run myctl orchestrate instead of chaining multiple commands.


Implementation

package cmd

import (
    "context"
    "fmt"

    "github.com/spf13/cobra"
)

var orchestrateCmd = &cobra.Command{
    Use:   "orchestrate",
    Short: "Run the full orchestration workflow",
    Long: `Execute the complete workflow: check cache, rebuild if needed,
select deployments, and trigger restarts.

This is the main entry point for automated execution.`,
    RunE: runOrchestrate,
}

func runOrchestrate(cmd *cobra.Command, args []string) error {
    ctx := cmd.Context()

    // Step 1: Check cache
    fmt.Println("Checking cache...")
    valid, err := checkCache(ctx)
    if err != nil {
        return fmt.Errorf("cache check failed: %w", err)
    }

    if valid {
        fmt.Println("Cache valid, nothing to do")
        return nil
    }

    // Step 2: Rebuild cache
    fmt.Println("Rebuilding cache...")
    if err := rebuildCache(ctx); err != nil {
        return fmt.Errorf("cache rebuild failed: %w", err)
    }

    // Step 3: Select deployments
    fmt.Println("Selecting deployments...")
    deployments, err := selectDeployments(ctx)
    if err != nil {
        return fmt.Errorf("deployment selection failed: %w", err)
    }

    if len(deployments) == 0 {
        fmt.Println("No deployments need restart")
        return nil
    }

    // Step 4: Restart deployments
    fmt.Printf("Restarting %d deployments...\n", len(deployments))
    if err := restartDeployments(ctx, deployments); err != nil {
        return fmt.Errorf("restart failed: %w", err)
    }

    fmt.Println("Orchestration complete")
    return nil
}

Rebuild Command

The rebuild command forces a cache rebuild:

package cmd

import (
    "context"
    "fmt"

    "github.com/spf13/cobra"
)

var rebuildCmd = &cobra.Command{
    Use:   "rebuild",
    Short: "Force rebuild of the cache",
    Long: `Rebuild the cache from scratch, ignoring any existing cached data.

Use this when you suspect the cache is corrupted or out of sync.`,
    RunE: runRebuild,
}

var forceRebuild bool

func init() {
    rebuildCmd.Flags().BoolVar(&forceRebuild, "force", false, "Skip confirmation prompt")
    rootCmd.AddCommand(rebuildCmd)
}

func runRebuild(cmd *cobra.Command, args []string) error {
    ctx := cmd.Context()

    if !forceRebuild {
        fmt.Print("This will invalidate all cached data. Continue? [y/N]: ")
        var response string
        fmt.Scanln(&response)
        if response != "y" && response != "Y" {
            fmt.Println("Aborted")
            return nil
        }
    }

    fmt.Println("Rebuilding cache...")
    if err := rebuildCache(ctx); err != nil {
        return fmt.Errorf("rebuild failed: %w", err)
    }

    fmt.Println("Cache rebuilt successfully")
    return nil
}

Error Handling and Exit Codes

Consistent Exit Codes

Code Meaning
0 Success
1 Operation failed or condition not met
2 Invalid usage or configuration error
3 Partial failure (some operations succeeded)

ExitError Type

package cmd

import (
    "fmt"
    "os"
)

// ExitError wraps an error with an exit code
type ExitError struct {
    Err  error
    Code int
}

func (e *ExitError) Error() string {
    return e.Err.Error()
}

// Execute handles ExitError for proper exit codes
func Execute() {
    if err := rootCmd.Execute(); err != nil {
        if exitErr, ok := err.(*ExitError); ok {
            fmt.Fprintln(os.Stderr, exitErr.Error())
            os.Exit(exitErr.Code)
        }
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

// Usage in commands
func runCheck(cmd *cobra.Command, args []string) error {
    valid, err := performCacheCheck(cmd.Context())
    if err != nil {
        return &ExitError{Err: err, Code: 2}
    }
    if !valid {
        return &ExitError{Err: fmt.Errorf("cache needs rebuild"), Code: 1}
    }
    return nil
}

Dry Run Mode

Always Implement Dry Run

Users expect --dry-run to preview changes safely. This builds trust and enables CI integration without side effects.

Add a global --dry-run flag that shows what would happen:

package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

var dryRun bool

func init() {
    rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Show what would be done")
}

func runRestart(cmd *cobra.Command, args []string) error {
    ctx := cmd.Context()

    deployments, err := selectDeployments(ctx)
    if err != nil {
        return err
    }

    for _, d := range deployments {
        if dryRun {
            fmt.Printf("[dry-run] Would restart deployment: %s\n", d)
            continue
        }
        fmt.Printf("Restarting deployment: %s\n", d)
        if err := restartDeployment(ctx, d); err != nil {
            return fmt.Errorf("failed to restart %s: %w", d, err)
        }
    }

    return nil
}

The orchestrator coordinates; individual commands do the work.

Comments