hooks

package
v1.1.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2025 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package hooks provides hook execution and management for Hitch operations.

Overview

This package implements a flexible hook system that allows users to execute custom commands at specific points in the Hitch workflow. Hooks enable integration with CI/CD systems, testing frameworks, notification services, and other automation tools.

Hook Types

Four hook types are supported, corresponding to major Hitch operations:

  • pre-promote: Runs before a feature is promoted to an environment
  • post-promote: Runs after a feature is successfully promoted
  • pre-release: Runs before releasing an environment to base branch
  • post-release: Runs after a successful release

Hook Configuration

Hooks are defined in hitch.json:

{
  "hooks": {
    "pre-promote": [
      {
        "name": "run-tests",
        "type": "command",
        "command": "./scripts/test.sh",
        "args": ["{{.Branch}}"],
        "required": true
      }
    ],
    "post-promote": [
      {
        "name": "notify-slack",
        "type": "command",
        "command": "curl",
        "args": ["-X", "POST", "https://hooks.slack.com/...",
                 "-d", "{\"text\": \"{{.Branch}} promoted to {{.Environment}}\"}"],
        "required": false
      }
    ]
  }
}

Hook Execution

Execute hooks programmatically:

import (
    "context"
    "github.com/DoomedRamen/hitch/internal/hooks"
)

executor := hooks.NewHookExecutor(hooksConfig)

// Create hook context
ctx := hooks.HookContext{
    Branch:      "feature/login",
    Environment: "dev",
    UserEmail:   "user@example.com",
    UserName:    "John Doe",
    BaseBranch:  "main",
}

// Execute pre-promote hooks
results := executor.ExecuteHooks(context.Background(), hooks.PrePromote, ctx)

// Check results
for _, result := range results {
    if !result.Success && result.Hook.Required {
        return fmt.Errorf("required hook '%s' failed: %v", result.Hook.Name, result.Error)
    }
}

Hook Configuration Fields

Each hook supports these fields:

  • name: Descriptive name for the hook (e.g., "run-tests")
  • type: Must be "command" (builtin hooks are deprecated)
  • command: The executable to run (e.g., "./scripts/test.sh", "curl", "npm")
  • args: Array of arguments passed to the command
  • working_dir: Optional working directory for execution
  • required: If true, hook failure blocks the operation

Template Variables

Hook commands and arguments support template substitution:

  • {{.Branch}}: The feature branch name (e.g., "feature/login")
  • {{.Environment}}: The environment name (e.g., "dev", "qa")
  • {{.UserEmail}}: Git user email (e.g., "user@example.com")
  • {{.UserName}}: Git user name (e.g., "John Doe")
  • {{.BaseBranch}}: The base branch (e.g., "main")

Example usage in hook configuration:

{
  "name": "deploy",
  "command": "./deploy.sh",
  "args": ["{{.Environment}}", "{{.Branch}}"],
  "working_dir": "/opt/deployment"
}

Required vs Optional Hooks

Hook behavior depends on the required field:

Required hooks (required: true):

  • Must succeed for the operation to continue
  • Failure blocks the promote/release operation
  • Used for critical validations (tests, security scans)

Optional hooks (required: false):

  • Failure is logged but doesn't block operation
  • Used for notifications, metrics, non-critical tasks

Hook Results

Each hook execution returns a HookResult:

type HookResult struct {
    Hook    HookConfig  // The hook configuration
    Success bool        // Whether execution succeeded
    Output  string      // Combined stdout/stderr
    Error   error       // Error if execution failed
}

Execution Flow

Hook execution follows this pattern:

  1. Load hook configuration from hitch.json
  2. Create HookExecutor with configuration
  3. Build HookContext with operation details
  4. Execute hooks of specific type (pre-promote, post-promote, etc.)
  5. For each hook: - Replace template variables in command/args - Execute command with timeout - Capture output and exit status - If required hook fails, stop execution
  6. Return array of HookResults

Example Hooks

Run tests before promoting:

{
  "name": "unit-tests",
  "type": "command",
  "command": "npm",
  "args": ["test"],
  "required": true
}

Notify Slack after promotion:

{
  "name": "slack-notify",
  "type": "command",
  "command": "./scripts/notify.sh",
  "args": ["{{.Branch}}", "{{.Environment}}"],
  "required": false
}

Deploy to environment after promotion:

{
  "name": "auto-deploy",
  "type": "command",
  "command": "kubectl",
  "args": ["apply", "-f", "k8s/{{.Environment}}.yaml"],
  "working_dir": "/path/to/repo",
  "required": true
}

Context and Cancellation

Hooks respect context cancellation:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

results := executor.ExecuteHooks(ctx, hooks.PrePromote, hookCtx)

If context is cancelled, running hooks are terminated.

Error Handling

Hook failures are handled gracefully:

  • Non-zero exit code: Marked as failure
  • Command not found: Error returned with helpful message
  • Timeout: Context deadline exceeded
  • Required hook failure: Stops execution of remaining hooks

Deprecated Features

Builtin hooks (type: "builtin") are no longer supported:

  • Previously supported hooks like "test", "lint", "validate"
  • Now users must define hooks explicitly using type: "command"
  • Provides more flexibility and transparency

Testing

Hook execution can be tested by examining results:

results := executor.ExecuteHooks(ctx, hooks.PrePromote, hookCtx)
for _, result := range results {
    if !result.Success {
        t.Errorf("Hook %s failed: %v", result.Hook.Name, result.Error)
    }
}

Integration with Safety System

Hooks integrate with Hitch's safety mechanisms:

  • Hooks run during temp branch testing (safety.TempBranchTester)
  • If hooks fail during testing, actual operation is prevented
  • No changes are made to repository if required hooks fail

Best Practices

  1. Use required: true for validation hooks (tests, linting, security scans)
  2. Use required: false for notifications and metrics
  3. Keep hooks fast - long-running hooks slow down operations
  4. Test hooks in isolation before adding to hitch.json
  5. Use template variables to avoid hardcoding values
  6. Provide clear error messages in hook scripts
  7. Document hooks in README for team members

Example: Complete Hook Setup

{
  "hooks": {
    "pre-promote": [
      {
        "name": "validate-branch",
        "type": "command",
        "command": "./scripts/validate.sh",
        "args": ["{{.Branch}}"],
        "required": true
      },
      {
        "name": "run-tests",
        "type": "command",
        "command": "npm",
        "args": ["test"],
        "required": true
      }
    ],
    "post-promote": [
      {
        "name": "deploy-dev",
        "type": "command",
        "command": "./deploy.sh",
        "args": ["{{.Environment}}"],
        "required": false
      },
      {
        "name": "notify-team",
        "type": "command",
        "command": "curl",
        "args": ["-X", "POST", "https://notify.example.com",
                 "-d", "{\"branch\": \"{{.Branch}}\", \"env\": \"{{.Environment}}\"}"],
        "required": false
      }
    ]
  }
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type HookConfig

type HookConfig struct {
	Name       string            `yaml:"name"`
	Type       string            `yaml:"type"`        // "builtin" or "command"
	Command    string            `yaml:"command"`     // for type=command
	WorkingDir string            `yaml:"working_dir"` // optional working directory
	Args       []string          `yaml:"args"`        // arguments for the command
	Config     map[string]string `yaml:"config"`      // configuration for builtin hooks
	Required   bool              `yaml:"required"`    // whether failure should block operation
}

HookConfig represents a single hook configuration

type HookContext

type HookContext struct {
	Branch      string
	Environment string
	UserEmail   string
	UserName    string
	BaseBranch  string
}

HookContext provides context to hook execution

type HookExecutor

type HookExecutor struct {
	// contains filtered or unexported fields
}

HookExecutor handles hook execution

func NewHookExecutor

func NewHookExecutor(config *HooksConfig) *HookExecutor

NewHookExecutor creates a new hook executor

func (*HookExecutor) ExecuteHooks

func (he *HookExecutor) ExecuteHooks(ctx context.Context, hookType HookType, hookCtx HookContext) []HookResult

ExecuteHooks executes hooks of a specific type

type HookResult

type HookResult struct {
	Hook    HookConfig
	Success bool
	Output  string
	Error   error
}

HookResult represents the result of hook execution

type HookType

type HookType string

HookType represents different types of hooks

const (
	PreDemote         HookType = "pre-demote"
	PostDemote        HookType = "post-demote"
	PrePromote        HookType = "pre-promote"
	PostPromote       HookType = "post-promote"
	PreRelease        HookType = "pre-release"
	PostRelease       HookType = "post-release"
	PreMetadataWrite  HookType = "pre-metadata-write"
	PostMetadataWrite HookType = "post-metadata-write"
	PreRebuild        HookType = "pre-rebuild"
	PostRebuild       HookType = "post-rebuild"
	ConflictDetected  HookType = "conflict-detected"
	ResumeRebuild     HookType = "resume-rebuild"
)

type HooksConfig

type HooksConfig struct {
	PreDemote         []HookConfig `yaml:"pre-demote,omitempty"`
	PostDemote        []HookConfig `yaml:"post-demote,omitempty"`
	PrePromote        []HookConfig `yaml:"pre-promote,omitempty"`
	PostPromote       []HookConfig `yaml:"post-promote,omitempty"`
	PreRelease        []HookConfig `yaml:"pre-release,omitempty"`
	PostRelease       []HookConfig `yaml:"post-release,omitempty"`
	PreMetadataWrite  []HookConfig `yaml:"pre-metadata-write,omitempty"`
	PostMetadataWrite []HookConfig `yaml:"post-metadata-write,omitempty"`
	PreRebuild        []HookConfig `yaml:"pre-rebuild,omitempty"`
	PostRebuild       []HookConfig `yaml:"post-rebuild,omitempty"`
	ConflictDetected  []HookConfig `yaml:"conflict-detected,omitempty"`
	ResumeRebuild     []HookConfig `yaml:"resume-rebuild,omitempty"`
}

HookConfig represents the complete hook configuration

func LoadConfig

func LoadConfig(meta *metadata.Metadata) (*HooksConfig, error)

LoadConfig loads hook configuration from metadata

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL