migrate

package
v1.59.1 Latest Latest
Warning

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

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 26 Imported by: 0

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:

  1. 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.
  2. Copy what is new (and, with Overwrite, what differs), item by item.
  3. 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

View Source
const (
	DomainDirectory = "directory"
	DomainGitHub    = "github"
	DomainSlack     = "slack"
	DomainConsole   = "console"
	DomainIssuer    = "issuer"
	DomainBlobs     = "blobs"
)

The domains a run can skip by name.

Variables

View Source
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.

AllDomains is every name a run accepts to skip.

Functions

func BlobID

func BlobID(c store.Config) string

BlobID says where a configuration keeps its blobs, so that two sides that keep them in one place are known to: the same S3 bucket and prefix, or, with no `ports.blob`, the same release's objects. The memory adapter keeps them nowhere another process reads, so it is never "the same".

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
	Shared      sharedStore
	Channels    channelStore
	SessionKey  sessionKeys
}

Domains is every domain store of one side.

func OpenDomains

func OpenDomains(ctx context.Context, st *store.Stores, create bool) (*Domains, error)

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
}

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.

func Run

func Run(ctx context.Context, from, to Side, opt Options) (*Report, error)

Run copies from one side to the other and returns what it did. The report is returned with every error that is not a failure to start, so that a caller can still show what was found; a nil report is a run that did not begin.

func (*Report) JSON

func (r *Report) JSON() []byte

JSON renders the report, indented.

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.

Jump to

Keyboard shortcuts

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