pluginfetch

package
v0.1.0-rc.76 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package pluginfetch materialises one plugin, at one published version, out of a git repository.

It is TRANSPORT, deliberately and only. The client is allowed to fetch — that is the same class of work as dialling the console or writing files to disk — but every decision about what a plugin MEANS stays on the console: which version satisfies a constraint, whether the manifest is valid, how the tree is staged into a project. The line this package must not cross is doing compiler work on the way past.

The two checks it does make are not that line. Both are about whether the bytes on disk are the ones the caller asked for, which is the question a fetch is responsible for answering:

  • the manifest must name the plugin the tag names, and the version the tag names. The plugins repository's release (tools/release.sh) refuses to cut a disagreeing tag, but a repo this client did not publish never went through that gate, and the manifest is what travels on into the project once the tag is gone.
  • the caller gets the resolved commit SHA and the content digest, so the lock can pin something a moved tag cannot change.

Index

Constants

View Source
const SignatureFile = "plugin.sig"

SignatureFile is where a published plugin carries its signature, beside the manifest the signature is partly about.

Excluded from the digest by construction: the digest covers the tree the signature is ABOUT, so a file whose content depends on the digest cannot be part of it.

Variables

View Source
var ErrNoReleases = errors.New("no releases published")

ErrNoReleases is returned when a registry ANSWERED and publishes no release of the plugin asked for.

A sentinel rather than a message a caller greps, because the two failures a resolve can have want different advice and must not be confused: a registry nobody can reach is fixed with access or a URL, while one that simply has no such plugin is fixed with another name. A CLI that appended "here is how to list what exists" to both would be telling someone with a broken remote to go and read a list they also cannot fetch.

Functions

func CompareVersions

func CompareVersions(a, b string) int

CompareVersions orders two `vX.Y.Z[-prerelease]` strings: -1, 0 or +1.

Semver's precedence rules, kept to the part this project uses:

  • numeric comparison of major, minor and patch;
  • a version WITH a prerelease is lower than the same version without one, so promoting `v0.1.0-rc.2` to `v0.1.0` supersedes its own candidates;
  • prerelease identifiers compare dot-separated part by part, numerically when both sides are numeric. That is why the tag format is `rc.2` and not `rc2`: glued, the comparison is lexical and `rc10` sorts BELOW `rc2`, which nobody notices until the tenth candidate.

An unparseable version sorts below a parseable one rather than erroring: this orders what a repository already contains, and one hand-made tag should not make every real release unreachable.

func LatestVersion

func LatestVersion(ctx context.Context, repo, plugin string) (string, error)

LatestVersion returns the highest version published for one plugin in a repository, PRERELEASES INCLUDED.

That last part is the whole reason this exists rather than a call into an off-the-shelf resolver. Every plugin release is an `-rc.N` today and will be until w17 ships as a service, and the standard rule — the one semver states and `go get @latest` implements — is that a prerelease is never selected while any release exists. Under that rule today's catalogue resolves correctly by accident, and the day someone cuts one clean `v0.1.0` every later candidate becomes invisible. The rule is written down here instead.

Why the client resolves this

Deciding which version satisfies a constraint is the console's job, and this looks like that but is not: it is an ordering over OUR OWN tag format, not knowledge about schemas or compilation. It has to run here for a plainer reason too — the repository may be a third party's, reachable only with the developer's own credentials, so a rule that cannot run where the data is would not be a rule at all. What the console still decides is everything about the tree that comes back.

func NameInDir

func NameInDir(dir string) (string, error)

NameInDir is the plugin name a local tree declares.

The manifest names it, never the path. A directory can be called anything — `./plugins/payment`, `./p`, `.` — and a tree's identity is the one thing it is required to state, so taking the basename would let a rename on disk change what gets installed and what the lock records.

func PinnedToCommit

func PinnedToCommit(ref string) bool

PinnedToCommit reports whether a lock's recorded git ref names a commit.

func PublishedPlugins

func PublishedPlugins(ctx context.Context, repo string) ([]string, error)

PublishedPlugins names every plugin a registry publishes, read from its tags.

A registry's inventory IS its tag namespace: `<plugin>/v<version>`. Nothing else in the repository says which directories are plugins — a directory with no release is not something a project can pin, so it is not published.

Types

type Fetched

type Fetched struct {
	Dir     string
	Repo    string
	Ref     string
	Version string
	SHA     string
	// Digest covers the tree AS LANDED — after stripSrcSuffix. It is the lock's
	// pin, and `w17ctl verify` recomputes it over the installed tree, so it has
	// to describe what is on disk in a consumer's project.
	Digest string
	// PublishedDigest covers the tree AS PUBLISHED — before stripSrcSuffix, so
	// `.go.src` still carries its suffix and the plugin's own tests are still
	// present.
	//
	// THE SIGNATURE IS ABOUT THIS ONE, and the two are never equal: the strip
	// renames every `.src` and deletes every test, so a tree that has been
	// through it hashes to something the publisher never signed. `plugin sign`
	// digests the registry's bytes (it runs on the rendered tree, before any
	// consumer has touched it), which makes this the only digest a claim can be
	// checked against.
	//
	// Keeping both is not redundancy — they answer two different questions.
	// "Are these the bytes that were released?" is a signature question and only
	// the published form can answer it. "Is this working tree still the one that
	// was installed?" is a pin question and only the landed form can.
	//
	// For an AUTHOR tree fetched by commit (isAuthorTree), "as published" is the
	// tree as RENDERED on this machine, since the repository holds no rendered
	// form for that commit. Such a tree is unsigned, so no signature is checked
	// against it.
	//
	// For a release TAG that carries a signature in its message (tagSignature),
	// it is the tree as the tag holds it, taken before any render: that is what
	// the publisher signed, and the only form every client computes alike — a
	// render's bytes depend on the renderer's version.
	PublishedDigest string
	// Signature is the contents of SignatureFile, VERBATIM, when the published
	// tree carries one, or the signature block of the release tag's message
	// when the tag carries one — and empty otherwise.
	//
	// Not trimmed, not parsed. The file is a structured body the console wrote
	// and the console reads; a client that tidied it would be a third opinion
	// about a format only two ends need to agree on. Verifying an artefact is a
	// console job.
	//
	// Empty is the ordinary case today — every plugin published before signing
	// existed is unsigned — and the console reports that differently from a
	// signature that fails to verify.
	Signature string
}

Fetched is what the lock records: where the tree landed, which release it was, and the two values that make the pin immutable.

Ref and Version are carried back rather than left for the caller to rebuild from its own inputs — a caller that resolved "latest" does not otherwise know what it got, and one that rebuilt the string could rebuild it wrong.

func Fetch

func Fetch(ctx context.Context, src Source, dest string) (Fetched, error)

Fetch materialises src into dest and returns what it resolved.

dest is created; if it exists it is REPLACED, because a fetch that merged into a previous version's leftovers would produce a tree matching neither digest.

func FromDir

func FromDir(src, dest string) (Fetched, error)

FromDir materialises a plugin from a LOCAL author tree into dest.

The dev-loop counterpart of Fetch, and it goes the long way round on purpose: it RENDERS the author tree into its published form first, then unpacks that, so what lands in dest is the tree a consumer receives rather than the tree the author happens to have on disk.

⚠️ THOSE DIFFER, and installing the author tree directly would test bytes nobody is ever served. The Go goes inert (a live `.go` under a consumer's proto dir makes the plugin a module in their build), `src/gen/pb` is left out because it is regenerated, and the tests travel into the published form and are dropped again on the way here — so an author tree copied straight in would carry tests that staging cannot place and pb that the next codegen replaces.

No commit and no repository: there is nothing to pin. What comes back has a Version from the manifest and a Digest over what landed, which is what the install records, and `source: local` is how the lock says a pin was not possible rather than pretending to one.

type Source

type Source struct {
	// Repo is any git URL: the internal plugins repo, or a third party's.
	Repo string
	// Plugin is the catalogue name, which is also the top-level directory in
	// the repo and the tag prefix — one string, so the three cannot drift.
	Plugin string
	// Version is the tag's version part, `v`-prefixed (`v0.1.0-rc.1`).
	//
	// Empty when Commit is set. Exactly one of the two names what to fetch,
	// and validate() enforces that rather than picking a winner: a Source
	// carrying both would resolve to whichever the code happened to read
	// first, and the lock would then record a pin nobody asked for.
	Version string
	// Commit is a full 40-hex commit SHA, for fetching a plugin that has NOT
	// been released.
	//
	// The reason this exists: a plugin's author tree leads its published
	// releases by design, so the only way to try a fix before it is tagged was
	// to tag it. That turns every experiment into a release candidate in a
	// registry other people read. A commit is the honest way to say "this exact
	// tree, which nobody has published".
	//
	// FULL sha, never abbreviated: an abbreviation is not a stable name for a
	// tree (it can become ambiguous as the repository grows), and this value
	// goes into a LOCK, where it has to keep meaning the same bytes years
	// later. Abbreviation is a display concern — see shortSHA in cmd/plugin.
	Commit string
}

Source names one published plugin version.

func (Source) Ref

func (s Source) Ref() string

Ref is what git is asked for, and what the lock records: the release tag, or the commit SHA.

func (Source) Tag

func (s Source) Tag() string

Tag is the release tag a Source names, or "" when it names a commit.

Jump to

Keyboard shortcuts

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