| name | add-spec-translation |
| description | Add a new Ignition spec version downgrade translation module with dependency update, translation code, and tests |
Add Spec Version Translation
What it does
- Discovers the next spec version to translate by inspecting existing
translate/ directories and the latest Ignition release
- Updates the Ignition dependency in
go.mod to pull in the new spec types
- Runs
go mod tidy && go mod vendor to vendor the new types
- Analyzes the diff between the new and old spec type definitions to identify what changed
- Creates a new translation package
translate/vNEWtoOLD/vNEWtoOLD.go
- Adds comprehensive tests to
translate_test.go
- Validates everything with
go test ./...
Prerequisites
- Go toolchain installed (1.18+)
- The new Ignition spec version must be released and available in the Go module registry
Usage
/add-spec-translation
No arguments are required. The skill determines the versions automatically.
Workflow
Step 1: Discover the Next Translation Pair
Determine what already exists and what comes next.
1a. Find the highest existing translation:
ls translate/
Look at the directory names (e.g., v34tov33, v35tov34, v36tov35). Parse the highest NEW version from the directory named vNEWtoOLD. For example, if the latest is v34tov33, then OLD_COMPACT = 34 and NEW_COMPACT = 35.
The next translation to create is v{OLD_COMPACT+1}tov{OLD_COMPACT} where OLD_COMPACT is the NEW from the latest existing directory.
1b. Find the latest Ignition release version:
gh release list --repo coreos/ignition --limit 5
Or check Go module versions:
go list -m -versions github.com/coreos/ignition/v2 | tr ' ' '\n' | tail -5
Pick the latest release that includes the new spec version's types.
1c. Verify the new spec types exist in that release:
The Ignition library publishes spec types at github.com/coreos/ignition/v2/config/v{NEW_UNDERSCORE}/types/. Confirm the new spec types package exists by checking the release notes or the module contents.
1d. Derive all variables:
From the discovered versions, compute:
NEW_MAJOR_MINOR = e.g., 3.7 (with dot)
OLD_MAJOR_MINOR = e.g., 3.6 (with dot)
NEW_UNDERSCORE = e.g., 3_7 (for Go package paths)
OLD_UNDERSCORE = e.g., 3_6 (for Go package paths)
NEW_COMPACT = e.g., 37 (no dot, for directory names)
OLD_COMPACT = e.g., 36 (no dot, for directory names)
PACKAGE_NAME = e.g., v37tov36
IGNITION_VERSION = e.g., v2.28.0 (from the latest release)
1e. Confirm with the user before proceeding:
Present the discovered values and ask the user to confirm:
"I'll create translation v{NEW_MAJOR_MINOR} -> v{OLD_MAJOR_MINOR} using Ignition {IGNITION_VERSION}. Proceed?"
Step 2: Update Ignition Dependency
Read go.mod and update the Ignition version:
github.com/coreos/ignition/v2 {IGNITION_VERSION}
Then run:
go mod tidy
go mod vendor
Verify the new spec types are available:
ls vendor/github.com/coreos/ignition/v2/config/v{NEW_UNDERSCORE}/types/
If the types directory doesn't exist, the Ignition version may not include the new spec yet. Ask the user for guidance.
Step 3: Analyze Spec Differences
Compare the type definitions between the new and old spec versions to understand what changed:
ls vendor/github.com/coreos/ignition/v2/config/v{NEW_UNDERSCORE}/types/
ls vendor/github.com/coreos/ignition/v2/config/v{OLD_UNDERSCORE}/types/
Read and compare key type files. Look for:
- New types (files in NEW that don't exist in OLD)
- New fields in existing types (diff struct definitions)
- Changed field types (e.g.,
string -> *string)
Also read the upstream translator to understand the translation direction:
cat vendor/github.com/coreos/ignition/v2/config/v{NEW_UNDERSCORE}/translate/translate.go
This file translates from OLD to NEW. We need to reverse it: translate from NEW to OLD.
Step 4: Read the Most Recent Existing Translation
Read the most recent translation module to use as a structural template:
ls translate/
cat translate/{LATEST}/v{LATEST}.go
Also read translate_test.go to understand the test structure:
cat translate_test.go
Step 5: Create the Translation File
Create translate/v{NEW_COMPACT}tov{OLD_COMPACT}/v{NEW_COMPACT}tov{OLD_COMPACT}.go
The file MUST follow this structure:
package {PACKAGE_NAME}
import (
"fmt"
"reflect"
"github.com/coreos/ignition/v2/config/translate"
"github.com/coreos/ignition/v2/config/v{OLD_UNDERSCORE}/types"
old_types "github.com/coreos/ignition/v2/config/v{NEW_UNDERSCORE}/types"
"github.com/coreos/ignition/v2/config/validate"
)
Key rules for the translation code:
-
translateIgnition() is ALWAYS required - it sets ret.Version = types.MaxVersion.String()
-
Custom translate functions are needed for any type where:
- Fields were added in the new spec that don't exist in the old spec
- Field types changed between versions
- The function should copy only the fields that exist in BOTH versions
-
translateConfig() registers all custom translators and runs the full translation
-
Translate() is the public entry point:
- Validates the input config
- Calls
checkValue() to reject unsupported features
- Runs
translateConfig()
- Validates the output config (sanity check)
-
checkValue() uses reflection to walk the config and reject features not supported in the OLD spec:
- Check for new types that don't exist in OLD
- Check for new field values that aren't valid in OLD
- Recursively walk structs, slices, and pointers
- Return clear error messages:
fmt.Errorf("invalid input config: {feature} is not supported in spec v{OLD_MAJOR_MINOR}")
-
Mode bit masking: If the OLD spec version is < 3.4, mask special mode bits in translateFileEmbedded1() and translateDirectoryEmbedded1() using *old.Mode & ^07000. If the OLD spec is >= 3.4, special mode bits are supported and no masking is needed.
Step 6: Add Tests to translate_test.go
Add the following to translate_test.go:
-
Import the new types package:
types{NEW_UNDERSCORE} "github.com/coreos/ignition/v2/config/v{NEW_UNDERSCORE}/types"
-
Import the new translate package:
"github.com/coreos/ign-converter/translate/{PACKAGE_NAME}"
-
Create nonexhaustiveConfig{NEW_UNDERSCORE} - A config variable that exercises ALL undeprecated fields in the new spec version. Model it after the existing nonexhaustiveConfig variables, adding fields for any new features.
-
Create downtranslateConfig{OLD_UNDERSCORE} (if it doesn't already exist as a nonexhaustiveConfig) - The expected config after translation. This should be the same as nonexhaustiveConfig{NEW_UNDERSCORE} but:
- Version string set to OLD version (e.g.,
"3.6.0")
- New-only fields removed
- Mode bits masked if applicable
-
Add test function:
func TestV{NEW_COMPACT}toV{OLD_COMPACT}(t *testing.T) {
res, err := {PACKAGE_NAME}.Translate(nonexhaustiveConfig{NEW_UNDERSCORE})
assert.NoError(t, err)
assert.Equal(t, downtranslateConfig{OLD_UNDERSCORE}, res)
}
Step 7: Validate
Run the tests:
go test ./...
Check formatting:
gofmt -l .
If tests fail, analyze the error and fix. Common issues:
- Missing custom translator for a changed type (causes panic or wrong output)
- Incorrect field mapping in custom translator
- Missing error case in
checkValue()
- Test config doesn't match expected output (version string, missing fields)
Step 8: Verify CI Compatibility
The CI workflow (.github/workflows/go.yml) runs:
go test ./... across multiple Go versions
golangci-lint with -E=gofmt
Make sure the code passes both.
Checklist Coverage
This skill automates:
What's NOT covered
Example Output
When you run /add-spec-translation, you should see:
Discovering next translation pair...
Existing translations: v32tov31, v33tov32, v34tov33
Highest existing: v34 -> v33
Next translation needed: v35 -> v34
Checking latest Ignition release...
Latest: v2.20.0 (includes spec v3.5 types)
I'll create translation v3.5 -> v3.4 using Ignition v2.20.0. Proceed? (y/n)
Updating go.mod to Ignition v2.20.0...
Running go mod tidy && go mod vendor...
Analyzing spec differences between v3.5 and v3.4...
Found changes:
- New type: Cex
- Changed fields in Luks: added OpenOptions, Discard
- Changed fields in Tang: added Advertisement
Creating translate/v35tov34/v35tov34.go...
- translateIgnition()
- translateLuks() (new fields: OpenOptions, Discard)
- translateTang() (new field: Advertisement)
- translateConfig()
- Translate()
- checkValue() (rejects: Cex type)
Updating translate_test.go...
- Added nonexhaustiveConfig3_5
- Added downtranslateConfig3_4
- Added TestV35toV34()
Running go test ./...
PASS
ok github.com/coreos/ign-converter 0.015s
All checks passed!
References
- Design document:
.opencode/skills/add-spec-translation/DESIGN.md
- Example - v36 to v35:
.opencode/skills/add-spec-translation/examples/v36tov35.md
- Example - v34 to v33:
.opencode/skills/add-spec-translation/examples/v34tov33.md
- Existing translations:
translate/v34tov33/, translate/v33tov32/, translate/v32tov31/
- CI workflow:
.github/workflows/go.yml
- Upstream Ignition:
vendor/github.com/coreos/ignition/v2/config/