Documentation
¶
Overview ¶
Package migrate copies the service's State from any adapter to any other through the ports and the domain stores (ADR 0031), and is the whole of `sluis migrate`.
It copies through the BUSINESS interfaces, never raw bytes: a domain record and its credential are read from the source's own store (the ConfigMaps and Secrets of internal/kube, or the State records of internal/portstore) and written to the destination's, so a credential is sealed under the destination's Sealer with the destination's key as its binding, and a record lands in the layout its adapter keeps. The issuer's logins in progress (sessions, refresh tokens, the keyring's schedule) are not a domain store; they are State records and Index sets under `issuer:`, which are copied with the lifetime each has left, so nobody signs in again.
A run has three phases, and the first writes nothing:
- Plan: read both sides, and say for every item whether it is new on the destination, already there with the same value, or there with another one. Without Overwrite any difference is a conflict and the run stops here, naming the keys, before a single write.
- Copy what is new (and, with Overwrite, what differs), item by item.
- Verify: read the source and the destination again, fresh, and compare every source item with the destination's, decrypted where sealed.
A re-run after a failure plans again and so copies exactly what is missing: the copy is idempotent, and an item the destination already holds equal is never rewritten.
What is not copied: leases (transient, and owned by whoever is running), the hub's snapshots (a cache the hub rewrites on its first refresh), and the controllers' in-flight hand-offs and caches (`share.`, `cache.`, `dedupe.`), which a tick rebuilds.
Index ¶
Constants ¶
const ( DomainDirectory = "directory" DomainGitHub = "github" DomainSlack = "slack" DomainConsole = "console" DomainIssuer = "issuer" DomainBlobs = "blobs" )
The domains a run can skip by name.
Variables ¶
var ( // ErrWritersRunning is a run that was not told the source is quiet. ErrWritersRunning = errors.New("migrate: refusing to copy while the source can still change: " + "stop every writer first (scale the issuer, the console and both controllers to 0) and pass --i-have-stopped-writers; " + "--dry-run reads and writes nothing and needs neither") // ErrConflict is a destination that already holds a different value. ErrConflict = errors.New("migrate: the destination already holds a different value") // ErrMismatch is a copy that did not verify. ErrMismatch = errors.New("migrate: the destination does not match the source after the copy") // ErrUnreadable is a source item that cannot be presented through its store. ErrUnreadable = errors.New("migrate: the source holds items its store cannot read") )
Errors a run ends with. The report says the rest.
var AllDomains = []string{DomainDirectory, DomainGitHub, DomainSlack, DomainConsole, DomainIssuer, DomainBlobs}
AllDomains is every name a run accepts to skip.
Functions ¶
Types ¶
type BlobMode ¶
type BlobMode string
BlobMode says whether the controllers' reports are copied.
const ( // BlobsAuto copies them only when the two sides keep them in different // places. When they share one the next pass rewrites them anyway. BlobsAuto BlobMode = "auto" // BlobsCopy always copies them. BlobsCopy BlobMode = "copy" // BlobsSkip never does. BlobsSkip BlobMode = "skip" )
The blob modes.
type Domains ¶
type Domains struct {
Workspaces hub.Store
Credentials hub.CredentialStore
Orgs orgStore
Links linkStore
Runner runnerStore
Catalogue catalogueStore
SlackApps slackCatalogueStore
Slack slackStore
Channels channelStore
SessionKey sessionKeys
}
Domains is every domain store of one side.
func OpenDomains ¶
OpenDomains builds the domain stores of one side by the switch the service itself makes (internal/app): any adapter but `legacy` keeps them on the State port with every credential sealed, and `legacy` keeps today's ConfigMaps and Secrets. A source is opened as it is; a destination (create) also makes the objects a legacy store needs to exist, as the service does at its start.
type Options ¶
type Options struct {
// DryRun plans and reports and writes nothing, the destination's objects
// included.
DryRun bool
// Overwrite replaces what the destination holds with the source's. Without
// it a destination value that differs fails the run, naming the key.
Overwrite bool
// WritersStopped is the operator's statement that nothing writes to the
// source. A copy is a snapshot: a write during it is a copy of neither
// state. A run that writes refuses without it.
WritersStopped bool
// Skip names domains left out (see the Domain constants).
Skip []string
// Blobs says whether the controllers' reports are copied.
Blobs BlobMode
// ReportBlob, when set, is a Blob name the report is also written to on
// the destination (not by a dry run).
ReportBlob string
// Log receives progress. Nil is silent.
Log *slog.Logger
}
Options is how a run behaves.
type Problem ¶
type Problem struct {
Domain string `json:"domain"`
Kind string `json:"kind"`
Key string `json:"key"`
Reason string `json:"reason"`
}
Problem names one item that is not as it should be. Key is the item's key within its kind; for the issuer's state, whose keys carry bearer values (a code, a session), it is a short hash of the key so that a report is safe to keep.
type Report ¶
type Report struct {
From string `json:"from"`
To string `json:"to"`
FromAdapter string `json:"fromAdapter"`
ToAdapter string `json:"toAdapter"`
DryRun bool `json:"dryRun"`
Overwrite bool `json:"overwrite"`
StartedAt time.Time `json:"startedAt"`
FinishedAt time.Time `json:"finishedAt"`
Steps []Step `json:"steps"`
Totals Step `json:"totals"`
Conflicts []Problem `json:"conflicts,omitempty"`
Unreadable []Problem `json:"unreadable,omitempty"`
Mismatches []Problem `json:"mismatches,omitempty"`
// Notes say what was left out and why (a domain skipped, a side with
// nothing to read).
Notes []string `json:"notes,omitempty"`
// Error is how the run ended, if it did not end well.
Error string `json:"error,omitempty"`
// OK is true when the plan had no conflict, nothing unreadable, and, unless
// this was a dry run, every item verified.
OK bool `json:"ok"`
}
Report is what a run says it did: counts and the keys of what went wrong, never a value.
type Side ¶
type Side struct {
// Name labels it in the report: the configuration file it came from.
Name string
// Stores is the opened ports.
Stores *store.Stores
// BlobID identifies where this side keeps blobs, so that two sides that
// share a place are known to; empty is "nowhere shared".
BlobID string
}
Side is one end of a run: the ports and stores of a configuration, opened.
type Step ¶
type Step struct {
Domain string `json:"domain"`
Kind string `json:"kind"`
// Source is how many readable items the source holds.
Source int `json:"source"`
// Unreadable is how many it holds that its store cannot present: they are
// listed and not copied.
Unreadable int `json:"unreadable,omitempty"`
// New is how many the destination lacks, Present how many it already holds
// with the same value, and Conflicts how many it holds with another.
New int `json:"new"`
Present int `json:"present"`
Conflicts int `json:"conflicts,omitempty"`
// Copied is how many were written: New, and with Overwrite the Conflicts.
Copied int `json:"copied"`
// Verified is how many matched on the second read; Mismatched how many did
// not.
Verified int `json:"verified"`
Mismatched int `json:"mismatched,omitempty"`
}
Step is the outcome of one collection.