Documentation
¶
Overview ¶
Package capistrano is a pure-Go (no cgo) port of the core of Ruby's Capistrano — the remote-server automation / deployment framework. It reproduces Capistrano's configuration variable store (set / fetch / ask with lazily-evaluated, memoized callable values), the server / role registry and host filtering (role, server, roles(:web), release_roles, primary), the task DSL built on a Rake-style task graph (task / namespace / desc / before / after / invoke / invoke!, with circular-dependency detection), and the SSHKit-style execution context (on(hosts) { execute / test / capture / upload! / download! }) with its faithful nonzero-exit / boolean / stdout semantics.
What it is — and isn't. The *pure-compute* half of Capistrano — the variable store and its lazy/memoized values, the server/role model and filtering, the task graph and its invoke-once / circular / before-after semantics, the deploy flow wiring, and the execute/test/capture semantics — is deterministic and needs no interpreter, so it lives here as plain Go. The *effectful* half — the actual SSH command execution and file transfer — is an injectable Backend interface: the default is a real SSH backend over golang.org/x/crypto/ssh, but tests inject an in-process FakeBackend that records commands and returns scripted output, so the whole suite is deterministic and network-free. A future rbgo binding wraps this Go API as `require "capistrano"`; it does not import rbgo.
The task graph mirrors Rake (Capistrano is built on Rake); the standalone pure-Go Rake port is github.com/go-ruby-rake/rake. Capistrano's before/after (prepend-prerequisite / enhance-with-post-invoke) and invoke! semantics differ enough that this package carries a small purpose-built graph rather than depending on that module.
Index ¶
- type Application
- func (app *Application) AddServer(host string, roles []string, props map[string]any) *Server
- func (app *Application) After(task, post string) error
- func (app *Application) Ask(key, prompt string, def any)
- func (app *Application) Backend() Backend
- func (app *Application) Before(task, prerequisite string) error
- func (app *Application) Config() *Configuration
- func (app *Application) Desc(text string)
- func (app *Application) Fetch(key string, def ...any) any
- func (app *Application) Invoke(name string) error
- func (app *Application) InvokeBang(name string) error
- func (app *Application) IsSet(key string) bool
- func (app *Application) LoadStage(name string) error
- func (app *Application) Namespace(name string, body func())
- func (app *Application) On(hosts []*Server, block OnBlock) error
- func (app *Application) Primary(role string) *Server
- func (app *Application) ReleaseRoles(names ...string) []*Server
- func (app *Application) Role(role string, hosts []string, props map[string]any)
- func (app *Application) Roles(names ...string) []*Server
- func (app *Application) Servers() *Servers
- func (app *Application) Set(key string, value any) any
- func (app *Application) SetBackend(b Backend)
- func (app *Application) SetInput(fn func(prompt string) string)
- func (app *Application) SetReleaseTimestamp(fn func() string)
- func (app *Application) SetSCM(s SCM)
- func (app *Application) Stage(name string, body func())
- func (app *Application) Task(name string, deps []string, body TaskBody) *Task
- func (app *Application) Tasks() *TaskManager
- type Backend
- type Callable
- type CapistranoError
- type CommandError
- type CommandResult
- type Configuration
- type Dialer
- type Error
- type FakeBackend
- func (f *FakeBackend) Download(host *Server, remote, local string) error
- func (f *FakeBackend) FailDownloads(err error) *FakeBackend
- func (f *FakeBackend) FailTransport(err error) *FakeBackend
- func (f *FakeBackend) FailUploads(err error) *FakeBackend
- func (f *FakeBackend) Run(host *Server, command string) (CommandResult, error)
- func (f *FakeBackend) Script(command string, res CommandResult, err error) *FakeBackend
- func (f *FakeBackend) Upload(host *Server, local, remote string) error
- type Git
- type NoMatchingServersError
- type OnBlock
- type SCM
- type SSHConn
- type Server
- func (s *Server) AddRole(roles ...string) *Server
- func (s *Server) Fetch(key string) any
- func (s *Server) HasProperty(key string) bool
- func (s *Server) HasRole(role string) bool
- func (s *Server) HostPort() string
- func (s *Server) IsPrimary() bool
- func (s *Server) NoRelease() bool
- func (s *Server) Roles() []string
- func (s *Server) Set(key string, value any) *Server
- func (s *Server) String() string
- type Servers
- func (s *Servers) Add(host string, roles []string, props map[string]any) *Server
- func (s *Servers) AddRole(role string, hosts []string, props map[string]any)
- func (s *Servers) All() []*Server
- func (s *Servers) Primary(role string) *Server
- func (s *Servers) ReleaseRoles(names ...string) []*Server
- func (s *Servers) RoleNames() []string
- func (s *Servers) Roles(names ...string) []*Server
- type Session
- func (s *Session) Capture(command string, args ...string) (string, error)
- func (s *Session) Download(remote, local string) error
- func (s *Session) Execute(command string, args ...string) error
- func (s *Session) Host() *Server
- func (s *Session) Test(command string, args ...string) (bool, error)
- func (s *Session) Upload(local, remote string) error
- type Task
- type TaskBody
- type TaskManager
- func (m *TaskManager) After(task, post string) error
- func (m *TaskManager) Before(task, prerequisite string) error
- func (m *TaskManager) Define(name string, deps []string, body TaskBody) *Task
- func (m *TaskManager) Desc(text string)
- func (m *TaskManager) Invoke(name string) error
- func (m *TaskManager) InvokeBang(name string) error
- func (m *TaskManager) Lookup(name string) (*Task, bool)
- func (m *TaskManager) Namespace(name string, body func())
- func (m *TaskManager) Tasks() []*Task
- type TaskNotFoundError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Application ¶
type Application struct {
// contains filtered or unexported fields
}
Application is the Capistrano DSL facade — the port of the global `env` (Capistrano::Configuration.env) plus the Rake application. It aggregates the variable store, the server/role registry, the task graph, the injectable command Backend, the interactive-input seam (for ask), and the registered stages, and exposes the Ruby-named DSL methods (set/fetch/ask, role/server/ roles/…, task/namespace/before/after/invoke, on) a host binds.
func NewApplication ¶
func NewApplication() *Application
NewApplication returns a fully-wired DSL facade: an empty variable store, server registry and task graph, the default real-SSH Backend (host-key checks disabled, no auth — a deploy overrides it via set / a custom ssh.ClientConfig), a non-interactive input seam (ask falls back to its default), a fixed release timestamp, and the Git SCM strategy. The default deploy flow is registered.
func (*Application) AddServer ¶
AddServer registers a server with roles and properties (the `server` DSL).
func (*Application) After ¶
func (app *Application) After(task, post string) error
After wires post to run after task (the `after` DSL).
func (*Application) Ask ¶
func (app *Application) Ask(key, prompt string, def any)
Ask registers key as a question (the `ask` DSL): on first fetch it prompts via the input seam, falling back to def when the answer is blank, and memoizes.
func (*Application) Backend ¶
func (app *Application) Backend() Backend
Backend returns the installed command Backend.
func (*Application) Before ¶
func (app *Application) Before(task, prerequisite string) error
Before wires prerequisite to run before task (the `before` DSL).
func (*Application) Config ¶
func (app *Application) Config() *Configuration
Config exposes the underlying variable store.
func (*Application) Desc ¶
func (app *Application) Desc(text string)
Desc sets the next task's description (the `desc` DSL).
func (*Application) Fetch ¶
func (app *Application) Fetch(key string, def ...any) any
Fetch resolves key with an optional default (the `fetch` DSL), memoizing any callable it evaluates.
func (*Application) Invoke ¶
func (app *Application) Invoke(name string) error
Invoke runs a task once (the `invoke` DSL).
func (*Application) InvokeBang ¶
func (app *Application) InvokeBang(name string) error
InvokeBang re-runs a task even if already invoked (the `invoke!` DSL).
func (*Application) IsSet ¶
func (app *Application) IsSet(key string) bool
IsSet reports whether key is assigned (the `set?` DSL).
func (*Application) LoadStage ¶
func (app *Application) LoadStage(name string) error
LoadStage runs the named stage's body, defining its servers, roles and variables. It raises a Capistrano error when no such stage is registered.
func (*Application) Namespace ¶
func (app *Application) Namespace(name string, body func())
Namespace scopes body's task definitions under name (the `namespace` DSL).
func (*Application) On ¶
func (app *Application) On(hosts []*Server, block OnBlock) error
On runs block once per host with a Session bound to it (the `on(hosts) { … }` DSL). With no hosts it raises NoMatchingServersError; the first block error aborts and is returned.
func (*Application) Primary ¶
func (app *Application) Primary(role string) *Server
Primary returns the primary server for role (the `primary` DSL).
func (*Application) ReleaseRoles ¶
func (app *Application) ReleaseRoles(names ...string) []*Server
ReleaseRoles returns the release-eligible servers in the roles (the `release_roles` DSL).
func (*Application) Role ¶
func (app *Application) Role(role string, hosts []string, props map[string]any)
Role assigns role to hosts (the `role name, hosts` DSL).
func (*Application) Roles ¶
func (app *Application) Roles(names ...string) []*Server
Roles returns the servers in any of the named roles (the `roles` DSL).
func (*Application) Servers ¶
func (app *Application) Servers() *Servers
Servers exposes the underlying server/role registry.
func (*Application) Set ¶
func (app *Application) Set(key string, value any) any
Set binds key (the `set` DSL); value may be a Callable for a lazy variable.
func (*Application) SetBackend ¶
func (app *Application) SetBackend(b Backend)
SetBackend injects a Backend (a FakeBackend in tests, a custom transferring backend in production) — the seam the whole execution layer hangs on.
func (*Application) SetInput ¶
func (app *Application) SetInput(fn func(prompt string) string)
SetInput injects the interactive-input seam ask reads answers from (rbgo wires it to $stdin; tests wire a scripted function).
func (*Application) SetReleaseTimestamp ¶
func (app *Application) SetReleaseTimestamp(fn func() string)
SetReleaseTimestamp injects the release-directory naming seam.
func (*Application) SetSCM ¶
func (app *Application) SetSCM(s SCM)
SetSCM injects the source-control strategy the deploy flow uses.
func (*Application) Stage ¶
func (app *Application) Stage(name string, body func())
Stage registers a named stage body (a production/staging config block); Load runs it (the `capistrano/setup` stage mechanism).
func (*Application) Task ¶
func (app *Application) Task(name string, deps []string, body TaskBody) *Task
Task defines/enhances a task (the `task` DSL).
func (*Application) Tasks ¶
func (app *Application) Tasks() *TaskManager
Tasks exposes the underlying task graph.
type Backend ¶
type Backend interface {
Run(host *Server, command string) (CommandResult, error)
Upload(host *Server, local, remote string) error
Download(host *Server, remote, local string) error
}
Backend is the injectable command-execution seam — the SSHKit backend behind Capistrano's on(hosts) { … } context. Run executes one command on one host and reports the CommandResult; a returned error means the command could not be run at all (dial/transport failure), distinct from a non-zero exit. Upload and Download transfer a file to/from the host. The default is a real SSH backend (see NewSSHBackend); tests inject a FakeBackend.
func NewSSHBackend ¶
func NewSSHBackend(config *ssh.ClientConfig) Backend
NewSSHBackend returns the default real-SSH Backend using config to authenticate. It is the Backend NewApplication installs; a deploy that never injects a different Backend uses this one to reach real hosts over golang.org/x/crypto/ssh.
type Callable ¶
type Callable func() any
Callable is a lazily-evaluated configuration value — the port of a Proc stored in the Capistrano variable table. When fetch encounters one it calls it and memoizes the result back into the table (Configuration#fetch's `while callable? … set(key, value.call)` loop), so the body runs at most once.
type CapistranoError ¶
type CapistranoError interface {
error
// contains filtered or unexported methods
}
CapistranoError is the root of the Capistrano error tree (Capistrano::Error < StandardError). Every error this package raises satisfies it, so a host (rbgo) can map the whole family onto a single Ruby exception class and still switch on the concrete Go type for the specific subclass.
type CommandError ¶
CommandError is the port of SSHKit::Command::Failed — raised when execute / capture run a command that exits non-zero. It carries the host, the command line, the exit status, and the captured stderr so the message reproduces SSHKit's multi-line report.
func (*CommandError) Error ¶
func (e *CommandError) Error() string
type CommandResult ¶
CommandResult is the outcome of running one shell command on one host — the pure-data slice of an SSHKit::Command after it has run. ExitStatus is the process exit code (0 on success); Stdout / Stderr are the captured streams. A non-zero ExitStatus is NOT a transport error — the Backend reports it here and the Session decides whether to raise (execute / capture do; test does not).
type Configuration ¶
type Configuration struct {
// contains filtered or unexported fields
}
Configuration is Capistrano's variable store — the port of the variable half of Capistrano::Configuration. It maps symbols (as strings) to values, where a value may be a plain any or a Callable that is resolved and memoized on first fetch.
func NewConfiguration ¶
func NewConfiguration() *Configuration
NewConfiguration returns an empty variable store.
func (*Configuration) Delete ¶
func (c *Configuration) Delete(key string) any
Delete removes key, returning its previous value (Configuration#delete).
func (*Configuration) Fetch ¶
func (c *Configuration) Fetch(key string, def ...any) any
Fetch resolves key (Capistrano's `fetch`). When key is unset the optional default is used (a Callable default is honoured, as MRI's block default is). Any Callable encountered — stored value or default — is invoked and its result memoized back under key, so the body runs at most once and later fetches are cheap. A missing key with no default yields nil.
func (*Configuration) IsSet ¶
func (c *Configuration) IsSet(key string) bool
IsSet reports whether key has been assigned (Configuration#key? / `set?`).
func (*Configuration) Keys ¶
func (c *Configuration) Keys() []string
Keys returns every assigned variable name, sorted (Configuration#keys).
type Dialer ¶
Dialer opens an SSHConn to a host — the injectable seam under the SSH backend. The default (sshDialer) dials over the network with x/crypto/ssh; tests inject a fake, and an in-process ssh server exercises the default.
type Error ¶
type Error struct{ Message string }
Error is a plain Capistrano::Error carrying a message — the base node of the tree, raised where MRI would raise Capistrano::Error itself (e.g. an unknown stage, a circular task dependency).
type FakeBackend ¶
type FakeBackend struct {
// Commands is the ordered log of every command Run received.
Commands []string
// Uploads / Downloads log every transfer as "local -> remote".
Uploads []string
Downloads []string
// contains filtered or unexported fields
}
FakeBackend is an in-process, deterministic Backend for tests — the port of SSHKit's test backend. It records every command / upload / download and returns scripted CommandResults keyed by the command string (falling back to a zero-exit empty result, so a plain command "succeeds" unless scripted otherwise). It performs no I/O and needs no network, so the suite is reproducible on every architecture.
func NewFakeBackend ¶
func NewFakeBackend() *FakeBackend
NewFakeBackend returns an empty fake backend.
func (*FakeBackend) Download ¶
func (f *FakeBackend) Download(host *Server, remote, local string) error
Download records the transfer and honours a scripted download failure.
func (*FakeBackend) FailDownloads ¶
func (f *FakeBackend) FailDownloads(err error) *FakeBackend
func (*FakeBackend) FailTransport ¶
func (f *FakeBackend) FailTransport(err error) *FakeBackend
FailTransport makes every Run report err as a transport failure (dial down).
func (*FakeBackend) FailUploads ¶
func (f *FakeBackend) FailUploads(err error) *FakeBackend
FailUploads / FailDownloads make every transfer of that kind fail.
func (*FakeBackend) Run ¶
func (f *FakeBackend) Run(host *Server, command string) (CommandResult, error)
Run records command and returns its scripted result (or a zero-exit empty result when unscripted), honouring a global transport failure.
func (*FakeBackend) Script ¶
func (f *FakeBackend) Script(command string, res CommandResult, err error) *FakeBackend
Script makes the fake return res (and err as the transport error) whenever the exact command line is run. It returns the backend for chaining.
type Git ¶
type Git struct{}
Git is the default SCM strategy — the port of Capistrano::SCM::Git. It issues git over the Session; it does not implement git itself.
func (Git) Check ¶
func (Git) Check(s *Session, c *Configuration) error
Check runs `git ls-remote <repo_url>` on the host, raising if the repository is unreachable (deploy:check's git access probe).
func (Git) Release ¶
func (Git) Release(s *Session, c *Configuration) error
Release clones the branch into release_path (deploy:updating). It first ensures the release directory exists, then clones — a compact stand-in for Capistrano's mirror-clone + archive strategy (see the README "Deferred" note).
type NoMatchingServersError ¶
type NoMatchingServersError struct{ Filter string }
NoMatchingServersError is Capistrano::NoMatchingServersError — raised when an action needs at least one server but the role/property filter selected none.
func (*NoMatchingServersError) Error ¶
func (e *NoMatchingServersError) Error() string
type OnBlock ¶
OnBlock is the body of an on(hosts) { … } context, run once per matching host with a Session bound to that host — the port of the SSHKit block. A returned error aborts the run and is propagated to the caller.
type SCM ¶
type SCM interface {
// Check verifies the deploy host can reach the repository (deploy:check).
Check(s *Session, c *Configuration) error
// Release materialises a release into c's release_path (deploy:updating).
Release(s *Session, c *Configuration) error
// Revision captures the deployed revision SHA (deploy:log_revision).
Revision(s *Session, c *Configuration) (string, error)
}
SCM is the source-control strategy the deploy flow drives through the execution seam — the port of Capistrano's SCM plugins (Capistrano::SCM::Git and friends). Every method issues its commands through the given Session, so the actual git invocations flow through the injectable Backend like any other command.
type SSHConn ¶
type SSHConn interface {
// Run executes command over the connection and reports the CommandResult. A
// non-zero exit is returned in the result (ExitStatus), not as an error; a
// returned error is a transport/protocol failure.
Run(command string) (CommandResult, error)
// Close tears the connection down.
Close() error
}
SSHConn is one live SSH connection to a host — the small surface the SSH backend needs from golang.org/x/crypto/ssh. It is an interface so the backend orchestration is testable with an in-process fake, while the real implementation (sshConn) wraps *ssh.Client.
type Server ¶
Server is a single deployment target — the port of Capistrano::Configuration:: Server (an SSHKit::Host). It carries the connection triple (user, host, port), the set of roles it plays, and an arbitrary property bag (Capistrano's per-server variables such as primary, no_release, ssh_options).
func NewServer ¶
NewServer parses an SSHKit host string ("[user@]host[:port]") into a Server — the port of Server.new / SSHKit::Host#initialize. A missing user or port is left zero-valued (the host's defaults apply).
func (*Server) AddRole ¶
AddRole records that the server plays each of roles (Server#add_role), returning the server for chaining. The special role :all is never stored — it is a wildcard the registry expands.
func (*Server) HasProperty ¶
HasProperty reports whether a per-server property has been set.
func (*Server) HasRole ¶
HasRole reports whether the server plays role (Server#roles.include?). The wildcard :all matches every server.
func (*Server) HostPort ¶
HostPort returns the "host:port" dial target, defaulting the port to 22 when unset (the SSH default).
func (*Server) IsPrimary ¶
IsPrimary reports whether the server was flagged `primary: true` (Server# primary?). The registry prefers such a server when resolving primary(role).
func (*Server) NoRelease ¶
NoRelease reports whether the server opted out of releases via `no_release: true` (Server#no_release?), excluding it from release_roles.
type Servers ¶
type Servers struct {
// contains filtered or unexported fields
}
Servers is the server/role registry — the port of Capistrano::Configuration:: Servers. It keeps an ordered, de-duplicated list of Server targets (SSHKit merges re-declarations of the same endpoint) and answers the role/property filter queries the DSL exposes: roles(:web), release_roles, primary(role).
func (*Servers) Add ¶
Add registers a server with the given roles and properties (the DSL's `server host, roles: [...], **props`), merging into any existing endpoint.
func (*Servers) AddRole ¶
AddRole assigns role to every host, with shared properties (the DSL's `role name, hosts, **props`).
func (*Servers) Primary ¶
Primary returns the primary server for role (the DSL's primary(role)): the first host flagged `primary: true`, else the first host in the role, else nil.
func (*Servers) ReleaseRoles ¶
ReleaseRoles is Roles minus any server flagged no_release (the DSL's release_roles) — the hosts that actually receive a release.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is the per-host execution context yielded to an on-block — the port of SSHKit's backend receiver. Its execute / test / capture reproduce SSHKit's semantics faithfully: execute raises on a non-zero exit, test returns a boolean, capture returns stripped stdout (and raises on a non-zero exit).
func (*Session) Capture ¶
Capture runs command and returns its stripped stdout — the port of SSHKit's `capture`. Like execute it raises a CommandError on a non-zero exit.
func (*Session) Download ¶
Download transfers remote to local from the host, raising on failure — the port of `download!`.
func (*Session) Execute ¶
Execute runs command and raises a CommandError if it exits non-zero — the port of SSHKit's `execute` (and Capistrano's `execute`/`execute!`). It returns nil on success and the transport error if the command could not be run.
type Task ¶
type Task struct {
// contains filtered or unexported fields
}
Task is one unit of work in the graph — the Capistrano/Rake Task. It carries a fully-qualified (namespace-prefixed) name, its ordinary prerequisites, its after-hooks (invoked once its own actions succeed), the action bodies, a description, and the invoke-once guard with its remembered error.
func (*Task) Description ¶
Description returns the task's desc text (Rake::Task#comment).
func (*Task) Prerequisites ¶
Prerequisites returns the ordinary prerequisite names, in invoke order.
type TaskBody ¶
type TaskBody func() error
TaskBody is a task's action body — the seam for a Capistrano `task … do … end` block. A host (rbgo) wires each body to the Ruby block; tests pass Go closures. Returning an error aborts the invocation (an exception escaping the block), and the error is remembered so a later invoke re-raises it.
type TaskManager ¶
type TaskManager struct {
// contains filtered or unexported fields
}
TaskManager is the task registry and namespace resolver — the Rake half of Capistrano's DSL. It owns the task table (keyed by fully-qualified name), the live namespace stack, and the pending description, and drives invoke with its depth-first, prerequisite-first, invoke-once walk plus circular detection.
func NewTaskManager ¶
func NewTaskManager() *TaskManager
NewTaskManager returns an empty registry at the top-level scope.
func (*TaskManager) After ¶
func (m *TaskManager) After(task, post string) error
After makes post run after task's own actions — the `after` DSL. Capistrano enhances task to invoke post once its body has run.
func (*TaskManager) Before ¶
func (m *TaskManager) Before(task, prerequisite string) error
Before makes prerequisite run before task — the `before` DSL. Capistrano prepends it to task's prerequisites, so the most-recently-declared before-hook runs first. Both names are resolved against the current scope.
func (*TaskManager) Define ¶
func (m *TaskManager) Define(name string, deps []string, body TaskBody) *Task
Define registers (or, when it already exists, enhances) a task named within the current namespace, with the given prerequisites and body — the `task` DSL. Re-defining a task appends its prerequisites (union) and its body, matching Rake's define_task enhancement.
func (*TaskManager) Desc ¶
func (m *TaskManager) Desc(text string)
Desc records the description applied to the next defined task (the `desc` DSL); it is consumed by the following Define, then cleared.
func (*TaskManager) Invoke ¶
func (m *TaskManager) Invoke(name string) error
Invoke runs the task, prerequisites first, honouring the invoke-once guard — the `invoke` DSL. A task already invoked is skipped (its remembered error, if any, is re-raised).
func (*TaskManager) InvokeBang ¶
func (m *TaskManager) InvokeBang(name string) error
InvokeBang re-enables the task (only) and invokes it, forcing it to run again even if already invoked — the `invoke!` DSL (Rake's invoke!).
func (*TaskManager) Lookup ¶
func (m *TaskManager) Lookup(name string) (*Task, bool)
Lookup resolves a task by fully-qualified name (Rake::Task[]). It returns the task and whether it was found.
func (*TaskManager) Namespace ¶
func (m *TaskManager) Namespace(name string, body func())
Namespace evaluates body with name pushed on the scope stack (the `namespace` DSL), so tasks defined inside are prefixed "name:". Namespaces nest.
func (*TaskManager) Tasks ¶
func (m *TaskManager) Tasks() []*Task
Tasks returns every registered task, sorted by name.
type TaskNotFoundError ¶
type TaskNotFoundError struct{ Name string }
TaskNotFoundError is raised when invoke targets a name no task is registered under — the port of the "Don't know how to build task" failure Rake raises through Capistrano's DSL.
func (*TaskNotFoundError) Error ¶
func (e *TaskNotFoundError) Error() string
