ansiblemod

package
v0.28.1 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package ansiblemod lets the cargoship binary answer Ansible as a module.

There is no second binary and no Python wrapper around this one. The binary presents itself as several module files by looking at the name it was invoked under: a basename of cargoship_ followed by the name of an action it answers as enters module mode, and anything else is the ordinary CLI. The module files are symlinks to the binary, so there is nothing extra to sign, publish, or carry through an airlock, and the thing answering Ansible is the thing doing the work.

A module here does not reimplement a command. It turns the module's JSON parameters into the argument vector an operator would have typed and runs that, so package loading, keyring resolution, timeouts, and phase construction stay on one path.

See docs/agent/choice-ansible-module.md.

Index

Constants

View Source
const (
	// Prefix is the basename prefix that puts the binary into module mode.
	Prefix = "cargoship_"
	// AnsiballZ is the prefix Ansible puts on a module file when it copies it to the machine that
	// runs it. The copy is what actually executes, so the name the process sees is
	// AnsiballZ_cargoship_apply rather than the cargoship_apply the collection holds, and the
	// prefix has to come off before the name means anything.
	AnsiballZ = "AnsiballZ_"
	// EnvModule selects a module without a symlink. Tests have no reason to create symlinks to
	// check the contract, and neither does an operator reproducing a module run by hand.
	EnvModule = "CARGOSHIP_ANSIBLE_MODULE"
)
View Source
const (
	// SignalUnknown means no phase reported whether it changed anything, so the changed field
	// above it is a convention rather than an observation.
	SignalUnknown = "unknown"
	// SignalPartial means some phases reported and some did not. The ones that did not are
	// named in Detail.ChangedUndeclared.
	SignalPartial = "partial"
	// SignalComplete means every phase the run reached reported whether it changed anything.
	SignalComplete = "complete"
)

Changed signal values reported in Detail.ChangedSignal.

Variables

This section is empty.

Functions

func ModuleName

func ModuleName(argv0 string) (string, bool)

ModuleName returns the module this process was invoked as, and whether it was invoked as one at all. The environment variable wins, so a test can drive module mode through the binary it already builds.

The name has to be one of the actions above, not merely carry the prefix. The prefix alone is not rare enough to dispatch on: the repository builds its own binary as cargoship_linux_amd64, every e2e suite runs that file, and an operator who keeps two versions side by side names them something similar. Treating those as modules turns an ordinary command into a JSON object nobody asked for. The cost is that a misspelled symlink runs the CLI instead of saying it is not a module, which fails on the next line rather than this one.

func Modules

func Modules() []string

Modules returns the actions this binary answers as, sorted.

func Run

func Run(ctx context.Context, name string, argv []string, exec Exec) int

Run executes module mode and returns the process exit status.

argv is the process argument vector. Ansible invokes a WANT_JSON module with one argument, the path of a file holding the parameters as JSON.

Types

type Args

type Args struct {
	Control Control
	// contains filtered or unexported fields
}

Args is the arguments file Ansible writes for a WANT_JSON module.

The parameters are held undecoded so that unknown ones can be reported by name. A binary module has no argument_spec, so this is the whole of what stands between a mistyped parameter and a run that silently ignores it, and the ADR accepts the hand-written check on the condition that it names what was wrong.

func ReadArgs

func ReadArgs(path string) (*Args, error)

ReadArgs reads and parses the arguments file at path.

func (*Args) Params

func (a *Args) Params(out any) error

Params decodes the module's own parameters into out, which must be a pointer to a struct whose JSON tags name them.

A parameter the struct does not name is an error naming it, and the error lists what the module does accept. Nested documents are held to the same rule by the decoder, so a misspelled key inside the inventory argument fails here rather than translating into a cluster nobody asked for.

type Control

type Control struct {
	CheckMode bool `json:"_ansible_check_mode"`
	Diff      bool `json:"_ansible_diff"`
	NoLog     bool `json:"_ansible_no_log"`
	Verbosity int  `json:"_ansible_verbosity"`
}

Control is the part of a module's arguments Ansible writes rather than the operator.

type Detail

type Detail struct {
	// Module is the action that ran, so a failure in a playbook that loops over several says
	// which one.
	Module string `json:"module,omitempty"`
	// InventoryPath is the generated ZarfCluster document. It is the file cargoship was
	// actually given, which is the thing to look at when a translation is wrong.
	InventoryPath string `json:"inventoryPath,omitempty"`
	// InventoryKept says whether that file still exists. A run that fails keeps it; a run that
	// succeeds removes it unless the operator named the path themselves.
	InventoryKept bool `json:"inventoryKept,omitempty"`
	// CheckMode says whether this was a dry run, so a playbook cannot mistake a check-mode
	// result for a real one.
	CheckMode bool `json:"checkMode,omitempty"`
	// Command is the cargoship command line the module ran, for an operator reproducing it by
	// hand. Parameters carrying secrets are passed as file paths rather than values, so this
	// holds no key material.
	Command []string `json:"command,omitempty"`
	// PhasesRan and PhasesPlanned name the phases the run executed and, under check mode, the
	// phases it reported instead of running. Ansible shows one result for the whole fleet, so
	// these are what a playbook has in place of per-host detail.
	PhasesRan     []string `json:"phasesRan,omitempty"`
	PhasesPlanned []string `json:"phasesPlanned,omitempty"`
	// ChangedSignal says how much the changed field above is worth: complete when every phase
	// the run reached said whether it changed anything, partial when some did not, unknown when
	// no phase reported at all.
	ChangedSignal string `json:"changedSignal,omitempty"`
	// ChangedUndeclared names the phases that did not say. It is what makes a partial signal
	// actionable rather than a warning with nothing behind it.
	ChangedUndeclared []string `json:"changedUndeclared,omitempty"`
}

Detail is cargoship's own report on the run.

type Exec

type Exec func(ctx context.Context, argv []string) error

Exec runs a cargoship command line, as src/cmd.ExecuteArgs does.

It is injected rather than imported because src/cmd imports this package to reach ModuleName and Run, and a package cannot import the package that imports it. What the indirection costs is that the flag names below are written out here instead of referenced from src/cmd; what keeps them true is TestModuleArgsParse in src/cmd, which parses each module's widest argument vector against the real command.

type Response

type Response struct {
	Changed bool `json:"changed"`
	Failed  bool `json:"failed,omitempty"`
	// Skipped is how a module says it did not run at all. It is set when an operator asked for
	// check mode and the action has no dry run to answer with: Ansible skips a task whose
	// module has declared it cannot check, and a binary module has nowhere to declare that, so
	// it says so in its result instead.
	Skipped   bool    `json:"skipped,omitempty"`
	Msg       string  `json:"msg,omitempty"`
	Cargoship *Detail `json:"cargoship,omitempty"`
}

Response is the single JSON object a module writes.

The field names outside the cargoship key are Ansible's, not ours: changed, failed, and msg are what every module returns and what a playbook's conditionals are written against. Everything cargoship has to say beyond those is nested under one key, so it cannot collide with a field Ansible gives a meaning to later.

Jump to

Keyboard shortcuts

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