kclmigrate

package
v1.0.13 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package kclmigrate rewrites a project's deploy/kcl tree across a forge change that moved a declaration from one place to another.

It works TEXTUALLY, on purpose. The obvious alternative — render the env and read the resolved values — is not available: the old tree no longer compiles against the new schema (the field being migrated is now a closed-schema error), so there is nothing to render. Working on text also means the rewrite preserves comments, ordering and formatting, which a render-and-reprint would destroy.

THE GOVERNING RULE IS THAT THERE IS NO SILENT NO-OP. Every `registry = …` in the tree must be ACCOUNTED FOR — rewritten onto an image, dropped with a stated reason, or refused with its exact location. A registry the reader could not parse, resolve or attribute is a refusal, never a shrug. This is the rule the first version lacked: its two matchers missed the styles real projects write, so both inputs to its ambiguity check came back empty, it found nothing to do, and it reported success on a tree it had not understood. The tree then failed minutes later at render, pointing at a schema error with no trace of the migration that should have prevented it.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AmbiguousImage

type AmbiguousImage struct {
	// Workload is the declaration's name in deploy/kcl/workloads.k.
	Workload string
	// Image is the registry-less artifact it resolves today — its declared
	// image, or the name forge derives from its build when it declares none.
	Image string
	// Derived reports that Image was derived from the build rather than read
	// off an `image = …` line, so the runbook can say where it came from.
	Derived bool
	// ByEnv maps env name → the registry that env declared, for the envs
	// where this workload is bound to a runtime that pulls.
	ByEnv map[string]string
}

AmbiguousImage is one workload whose image cannot be completed unambiguously: it is bound to an image-pulling runtime in two or more envs that declared DIFFERENT registries, so there is no single reference that is correct everywhere.

func (AmbiguousImage) Runbook

func (a AmbiguousImage) Runbook() string

Runbook is the exact KCL to write, per env, to resolve this workload by hand.

It is per-env because that is what the situation actually is: the workload genuinely has a different reference in each env, so the declaration in workloads.k cannot state one, and each env refines it. forge prints this rather than guessing, because picking one registry silently would point some env at a repository its image was never pushed to — an ImagePullBackOff minutes after a deploy reported success.

type ImageRegistryResult

type ImageRegistryResult struct {
	// Rewrites is one entry per file changed, with a human description.
	Rewrites []string
	// Dropped names env registries that were removed WITHOUT being prefixed
	// onto any image, with the reason — a registry on an env where nothing
	// pulls an image says nothing about where an image should live.
	Dropped []string
	// Ambiguous is non-empty when the migration refused because a workload
	// pulls from two different registries. Each entry carries the exact
	// per-env KCL to write instead.
	Ambiguous []AmbiguousImage
	// Unaccounted is non-empty when the migration refused because it found a
	// `registry = …` it could not fully account for. Each entry names a
	// location and says what could not be determined.
	Unaccounted []Unaccounted
}

ImageRegistryResult is what a migration run did or refused to do.

func ImageRegistry

func ImageRegistry(projectDir string, apply bool) (ImageRegistryResult, error)

ImageRegistry migrates a project off env-level registries: it removes `registry = …` from every ClusterTarget / ControlPlane in deploy/kcl, and puts the removed value onto the workloads that pull an image — completing a bare `image`, or ADDING an `image` line to a workload that declared none — when, and only when, that is unambiguous.

UNAMBIGUOUS means: across every env where this workload is bound to a runtime that PULLS an image, exactly one registry was declared. An env that binds the workload OnHost, OnCompose or BuildOnly, or does not bind it at all, is not consulted — its registry was never going to be this workload's.

So a project whose dev env declares a localhost registry and binds everything to the host, while prod declares ghcr.io and deploys to a cluster, migrates cleanly to ghcr.io: dev's registry is DROPPED, because nothing in dev ever pulled an image.

EVERYTHING ELSE IS REFUSED, and refusing writes nothing — a partial migration would leave the tree in a state neither the old nor the new forge understands. There are two refusal kinds and they are equally hard:

  • Ambiguous: the same workload pulls from two different registries. Choosing one silently would point an env at a repository its image was never pushed to. The exact per-env KCL is printed instead.
  • Unaccounted: a `registry = …` this reader could not resolve to a literal, or whose env binds workloads in a way it could not read. Naming the file and line is the whole point: the alternative is dropping a registry the migration never understood, which is the defect this rule exists for.

USER BINDER EXPRESSIONS ARE NEVER EDITED. Binders are lambdas, and rewriting a lambda body is how a migration corrupts a file it does not understand. This reads them to learn which runtime each produces, and writes only to `registry = …` and `image = …` lines.

func (ImageRegistryResult) Applied

func (r ImageRegistryResult) Applied() bool

Applied reports whether anything changed on disk.

func (ImageRegistryResult) Refused

func (r ImageRegistryResult) Refused() bool

Refused reports whether the migration declined to rewrite. Both refusal kinds count: a caller must not be able to treat "I could not read this" as less serious than "this is ambiguous", because both leave a tree that will not render.

type Unaccounted

type Unaccounted struct {
	// File is the repo-relative path, e.g. "deploy/kcl/prod/main.k".
	File string
	// Line is the 1-based line of the `registry = …` declaration.
	Line int
	// Expr is the declaration's right-hand side, verbatim.
	Expr string
	// Reason says what could not be determined, in the author's terms.
	Reason string
}

Unaccounted is one `registry = …` the migration could not fully handle, with the location and the reason.

It exists so that "forge found something it does not understand" has a representation at all. Without one, the only way to express it was to return an empty success, which is what made the original defect invisible.

func (Unaccounted) String

func (u Unaccounted) String() string

Jump to

Keyboard shortcuts

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