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 lands in the destination's Secrets, 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, secrets read from each side's Secrets.
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") // ErrRefused is a source item the destination would refuse: nothing is // written until it is dealt with, and --overwrite does not change that. ErrRefused = errors.New("migrate: the destination would refuse source items") // 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 ConcernSummary ¶ added in v1.58.0
type ConcernSummary struct {
Concern string `json:"concern"`
Items int `json:"items"`
Bytes int `json:"bytes"`
Refused int `json:"refused,omitempty"`
}
ConcernSummary is what a run moves for one concern of the installation: state records, secrets, blobs. Items and Bytes are what the source holds and the run would write; Refused is what the destination would not take.
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 in Secrets, 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
// Sessions also copies the issuer's logins in progress (sessions, refresh
// tokens, the codes in flight, the Index sets). An installation's move does
// not: people sign in again, and only the key ring's schedule is copied, so
// that a token issued before the move keeps verifying.
Sessions 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
// Now is the clock the copied lifetimes are measured against; nil is
// time.Now. For tests, which inject the delay between reading and writing.
Now func() time.Time
}
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"`
// Refused are the items the destination would refuse, and why.
Refused []Problem `json:"refused,omitempty"`
// Concerns is the summary per concern: state, secrets, blobs.
Concerns []ConcernSummary `json:"concerns,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"`
// Refused is how many the destination would refuse (an item over a size
// limit, an invalid key): they are listed under refused and stop the run
// before anything is written.
Refused int `json:"refused,omitempty"`
// Bytes is the size of the readable source items (a record with its
// credentials, a blob, a State value); Secrets and SecretBytes are the
// credentials among them, which go to the Secrets port.
Bytes int `json:"bytes"`
Secrets int `json:"secrets,omitempty"`
SecretBytes int `json:"secretBytes,omitempty"`
}
Step is the outcome of one collection.