Skip to content

Commit db40551

Browse files
committed
feat: add clusterctl alpha migrate command
- initial scaffolding Signed-off-by: Satyam Bhardwaj <sbhardwaj@mirantis.com>
1 parent a922ab8 commit db40551

File tree

6 files changed

+557
-0
lines changed

6 files changed

+557
-0
lines changed

cmd/clusterctl/cmd/alpha.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var alphaCmd = &cobra.Command{
3030
func init() {
3131
// Alpha commands should be added here.
3232
alphaCmd.AddCommand(rolloutCmd)
33+
alphaCmd.AddCommand(migrateCmd)
3334

3435
RootCmd.AddCommand(alphaCmd)
3536
}

cmd/clusterctl/cmd/migrate.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"fmt"
21+
"io"
22+
"os"
23+
24+
"github.com/spf13/cobra"
25+
26+
"sigs.k8s.io/cluster-api/cmd/clusterctl/internal/migrate"
27+
)
28+
29+
type migrateOptions struct {
30+
output string
31+
}
32+
33+
var migrateOpts = &migrateOptions{}
34+
35+
var migrateCmd = &cobra.Command{
36+
Use: "migrate [SOURCE]",
37+
Short: "Migrate CAPI resources from v1beta1 to v1beta2 [ALPHA]",
38+
Long: `Migrate CAPI resources from v1beta1 to v1beta2 API versions [ALPHA].
39+
40+
This command is in ALPHA status and currently supports only v1beta1 to v1beta2 migration.
41+
42+
The command reads YAML files containing Cluster API resources and converts v1beta1
43+
resources to v1beta2 (hub) versions. Non-CAPI resources are passed through unchanged.
44+
45+
LIMITATIONS (Alpha Status):
46+
- Only supports v1beta1 to v1beta2 migration
47+
- ClusterClass patches are not migrated (limitation documented)
48+
- Field order may change and comments will be removed in output
49+
- API version references are dropped during conversion (except cluster class and external remediation references)
50+
51+
Examples:
52+
# Migrate from file to stdout
53+
clusterctl alpha migrate cluster.yaml
54+
55+
# Migrate from stdin to stdout
56+
cat cluster.yaml | clusterctl alpha migrate
57+
58+
# Migrate to output file
59+
clusterctl alpha migrate cluster.yaml --output migrated-cluster.yaml
60+
61+
# Chain with other tools
62+
clusterctl alpha migrate cluster.yaml | kubectl apply -f -`,
63+
Args: cobra.MaximumNArgs(1),
64+
RunE: func(cmd *cobra.Command, args []string) error {
65+
return runMigrate(args)
66+
},
67+
}
68+
69+
func init() {
70+
migrateCmd.Flags().StringVarP(&migrateOpts.output, "output", "o", "", "Output file path (default: stdout)")
71+
}
72+
73+
func runMigrate(args []string) error {
74+
fmt.Fprintf(os.Stderr, "WARNING: This command is in ALPHA status and supports only v1beta1 to v1beta2 migration.\n")
75+
fmt.Fprintf(os.Stderr, "See 'clusterctl alpha migrate --help' for current limitations and future roadmap.\n\n")
76+
77+
// Determine input source
78+
var input io.Reader
79+
var inputName string
80+
81+
if len(args) == 0 {
82+
input = os.Stdin
83+
inputName = "stdin"
84+
} else {
85+
sourceFile := args[0]
86+
file, err := os.Open(sourceFile)
87+
if err != nil {
88+
return fmt.Errorf("failed to open input file %q: %w", sourceFile, err)
89+
}
90+
defer file.Close()
91+
input = file
92+
inputName = sourceFile
93+
}
94+
95+
// Determine output destination
96+
var output io.Writer
97+
var outputFile *os.File
98+
var err error
99+
100+
if migrateOpts.output == "" {
101+
output = os.Stdout
102+
} else {
103+
outputFile, err = os.Create(migrateOpts.output)
104+
if err != nil {
105+
return fmt.Errorf("failed to create output file %q: %w", migrateOpts.output, err)
106+
}
107+
defer outputFile.Close()
108+
output = outputFile
109+
}
110+
111+
// TODO
112+
engine, err := migrate.NewEngine()
113+
if err != nil {
114+
return fmt.Errorf("failed to create migration engine: %w", err)
115+
}
116+
117+
opts := migrate.MigrationOptions{
118+
Input: input,
119+
Output: output,
120+
Errors: os.Stderr,
121+
}
122+
123+
result, err := engine.Migrate(opts)
124+
if err != nil {
125+
return fmt.Errorf("migration failed: %w", err)
126+
}
127+
128+
if result.TotalResources > 0 {
129+
fmt.Fprintf(os.Stderr, "\nMigration completed:\n")
130+
fmt.Fprintf(os.Stderr, " Total resources processed: %d\n", result.TotalResources)
131+
fmt.Fprintf(os.Stderr, " Resources converted: %d\n", result.ConvertedCount)
132+
fmt.Fprintf(os.Stderr, " Resources skipped: %d\n", result.SkippedCount)
133+
134+
if result.ErrorCount > 0 {
135+
fmt.Fprintf(os.Stderr, " Resources with errors: %d\n", result.ErrorCount)
136+
}
137+
138+
if len(result.Warnings) > 0 {
139+
fmt.Fprintf(os.Stderr, " Warnings: %d\n", len(result.Warnings))
140+
}
141+
142+
fmt.Fprintf(os.Stderr, "\nSource: %s\n", inputName)
143+
if migrateOpts.output != "" {
144+
fmt.Fprintf(os.Stderr, "Output: %s\n", migrateOpts.output)
145+
}
146+
}
147+
148+
if result.ErrorCount > 0 {
149+
return fmt.Errorf("migration completed with %d errors", result.ErrorCount)
150+
}
151+
152+
return nil
153+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package migrate
18+
19+
import (
20+
"k8s.io/apimachinery/pkg/runtime"
21+
)
22+
23+
// Converter handles conversion of individual CAPI resources between API versions.
24+
type Converter struct {
25+
// TODO
26+
}
27+
28+
// ConversionResult represents the outcome of converting a single resource.
29+
type ConversionResult struct {
30+
Object runtime.Object
31+
// Converted indicates whether the object was actually converted
32+
Converted bool
33+
Error error
34+
Warnings []string
35+
}
36+
37+
// NewConverter creates a new resource converter.
38+
// TODO
39+
func NewConverter() (*Converter, error) {
40+
return &Converter{}, nil
41+
}
42+
43+
// CanConvert determines if a resource can be converted.
44+
// TODO
45+
func (c *Converter) CanConvert(info ResourceInfo, obj runtime.Object) bool {
46+
return false
47+
}
48+
49+
// ConvertResource converts a single resource between API versions.
50+
// TODO
51+
func (c *Converter) ConvertResource(info ResourceInfo, obj runtime.Object) ConversionResult {
52+
return ConversionResult{
53+
Object: obj,
54+
Converted: false,
55+
Error: nil,
56+
Warnings: nil,
57+
}
58+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package migrate provides functionality for migrating Cluster API resources
18+
// between different API versions.
19+
//
20+
// This package implements a mechanical conversion approach for CAPI resource
21+
// migration, currently supporting v1beta1 to v1beta2 conversion.
22+
23+
// Future enhancements:
24+
// - Multi-version support with auto-detection of input API versions
25+
// - Target version selection with --to-version flag support
26+
// - Validation-only mode for linting existing resources
27+
28+
// The package is designed to be easily promoted from alpha status and
29+
// extended to support additional API version migrations in the future.
30+
package migrate
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package migrate
18+
19+
import (
20+
"fmt"
21+
"io"
22+
23+
"k8s.io/apimachinery/pkg/runtime/schema"
24+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
25+
)
26+
27+
// Engine coordinates YAML parsing, resource conversion, and output serialization.
28+
type Engine struct {
29+
parser *YAMLParser
30+
converter *Converter
31+
32+
errs []error
33+
warnings []string
34+
}
35+
36+
// MigrationOptions contains input/output configuration for migration.
37+
type MigrationOptions struct {
38+
Input io.Reader
39+
Output io.Writer
40+
Errors io.Writer
41+
}
42+
43+
// MigrationResult contains the outcome of a migration operation.
44+
type MigrationResult struct {
45+
TotalResources int
46+
ConvertedCount int
47+
SkippedCount int
48+
ErrorCount int
49+
Warnings []string
50+
Errors []error
51+
}
52+
53+
// ResourceInfo contains metadata about a resource being processed.
54+
type ResourceInfo struct {
55+
GroupVersionKind schema.GroupVersionKind
56+
Name string
57+
Namespace string
58+
// Index in the YAML stream
59+
Index int
60+
}
61+
62+
// NewEngine creates a new migration engine with parser and converter.
63+
func NewEngine(parser *YAMLParser, converter *Converter) (*Engine, error) {
64+
if parser == nil || converter == nil {
65+
return nil, fmt.Errorf("parser and converter must be provided")
66+
}
67+
return &Engine{
68+
parser: parser,
69+
converter: converter,
70+
errs: make([]error, 0),
71+
warnings: make([]string, 0),
72+
}, nil
73+
}
74+
75+
// appendError records an error for later aggregation.
76+
func (e *Engine) appendError(err error) {
77+
if err == nil {
78+
return
79+
}
80+
e.errs = append(e.errs, err)
81+
}
82+
83+
// appendWarning records a warning message.
84+
func (e *Engine) appendWarning(msg string) {
85+
if msg == "" {
86+
return
87+
}
88+
e.warnings = append(e.warnings, msg)
89+
}
90+
91+
// aggregateErrors returns an aggregated error from collected errors.
92+
func (e *Engine) aggregateErrors() error {
93+
return utilerrors.NewAggregate(e.errs)
94+
}
95+
96+
// Migrate handles multi-document YAML streams, error collection, and reporting.
97+
// TODO
98+
func (e *Engine) Migrate(opts MigrationOptions) (*MigrationResult, error) {
99+
return nil, fmt.Errorf("migration engine not yet implemented")
100+
}

0 commit comments

Comments
 (0)