Skip to content

Commit 139eb9b

Browse files
authored
Merge pull request #4621 from shuqz/i2g-init
[feat i2g]setup cli and framework
2 parents 3b64ade + ee446a6 commit 139eb9b

File tree

21 files changed

+1437
-22
lines changed

21 files changed

+1437
-22
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*.dylib
99
bin
1010
build
11+
lbc-migrate
1112

1213
# mkdocs generated live docs
1314
site

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ fast-unit-test:
5252
controller: generate fmt vet
5353
go build -o bin/controller main.go
5454

55+
# Build lbc-migrate binary
56+
lbc-migrate: fmt vet
57+
go build -o bin/lbc-migrate ./cmd/lbc-migrate
58+
59+
# Install lbc-migrate to $GOBIN (symlink so rebuilds are picked up automatically)
60+
install-lbc-migrate: lbc-migrate
61+
ln -sf $(MAKEFILE_PATH)bin/lbc-migrate $(GOBIN)/lbc-migrate
62+
5563
# Run against the configured Kubernetes cluster in ~/.kube/config
5664
run: generate fmt vet manifests
5765
go run ./main.go

cmd/lbc-migrate/main.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"github.com/spf13/cobra"
9+
"sigs.k8s.io/aws-load-balancer-controller/pkg/ingress2gateway"
10+
"sigs.k8s.io/aws-load-balancer-controller/pkg/ingress2gateway/reader"
11+
"sigs.k8s.io/aws-load-balancer-controller/pkg/ingress2gateway/warnings"
12+
"sigs.k8s.io/aws-load-balancer-controller/pkg/ingress2gateway/writer"
13+
)
14+
15+
const defaultOutputDir = "./gateway-output"
16+
17+
func main() {
18+
rootCmd := newRootCommand()
19+
if err := rootCmd.Execute(); err != nil {
20+
os.Exit(1)
21+
}
22+
}
23+
24+
func newRootCommand() *cobra.Command {
25+
opts := &ingress2gateway.MigrateOptions{}
26+
27+
cmd := &cobra.Command{
28+
Use: "lbc-migrate",
29+
Short: "Migrate AWS Load Balancer Controller Ingress resources to Gateway API",
30+
Long: `lbc-migrate translates Ingress, Service, IngressClass, and IngressClassParams
31+
resources into Gateway API equivalents (Gateway, HTTPRoute, LoadBalancerConfiguration,
32+
TargetGroupConfiguration, ListenerRuleConfiguration etc).
33+
34+
Input can come from YAML/JSON files, a directory of manifest files, or a live Kubernetes cluster.`,
35+
SilenceUsage: true,
36+
RunE: func(cmd *cobra.Command, args []string) error {
37+
if err := validateFlags(opts); err != nil {
38+
return err
39+
}
40+
return runMigrate(cmd.Context(), opts)
41+
},
42+
}
43+
44+
// Input flags
45+
cmd.Flags().StringSliceVarP(&opts.Files, "file", "f", nil,
46+
"Comma-separated input YAML/JSON file paths (e.g. -f file1.yaml,file2.yaml)")
47+
cmd.Flags().StringVar(&opts.InputDir, "input-dir", "",
48+
"Directory containing YAML/JSON files to read")
49+
cmd.Flags().BoolVar(&opts.FromCluster, "from-cluster", false,
50+
"Read resources from a live Kubernetes cluster")
51+
52+
// Cluster flags
53+
cmd.Flags().StringVar(&opts.Namespace, "namespace", "",
54+
"Namespace to read from (only with --from-cluster)")
55+
cmd.Flags().BoolVar(&opts.AllNamespaces, "all-namespaces", false,
56+
"Read from all namespaces (only with --from-cluster)")
57+
cmd.Flags().StringVar(&opts.Kubeconfig, "kubeconfig", "",
58+
"Path to kubeconfig file (only with --from-cluster, defaults to $KUBECONFIG or ~/.kube/config)")
59+
60+
// Output flags
61+
cmd.Flags().StringVar(&opts.OutputDir, "output-dir", defaultOutputDir,
62+
"Directory to write output manifests")
63+
cmd.Flags().StringVar(&opts.OutputFormat, "output-format", "yaml",
64+
"Output format: yaml or json")
65+
66+
return cmd
67+
}
68+
69+
func validateFlags(opts *ingress2gateway.MigrateOptions) error {
70+
hasFiles := len(opts.Files) > 0
71+
hasInputDir := opts.InputDir != ""
72+
hasFileInput := hasFiles || hasInputDir
73+
74+
// Must provide at least one input mode
75+
if !opts.FromCluster && !hasFileInput {
76+
return fmt.Errorf("must specify at least one of: --file (-f), --input-dir, or --from-cluster")
77+
}
78+
79+
// --from-cluster is mutually exclusive with file-based input
80+
if opts.FromCluster && hasFileInput {
81+
return fmt.Errorf("--from-cluster cannot be used with --file or --input-dir")
82+
}
83+
84+
// Cluster-only flags
85+
if !opts.FromCluster {
86+
if opts.Namespace != "" {
87+
return fmt.Errorf("--namespace can only be used with --from-cluster")
88+
}
89+
if opts.AllNamespaces {
90+
return fmt.Errorf("--all-namespaces can only be used with --from-cluster")
91+
}
92+
if opts.Kubeconfig != "" {
93+
return fmt.Errorf("--kubeconfig can only be used with --from-cluster")
94+
}
95+
}
96+
97+
// --namespace and --all-namespaces are mutually exclusive
98+
if opts.Namespace != "" && opts.AllNamespaces {
99+
return fmt.Errorf("--namespace and --all-namespaces are mutually exclusive")
100+
}
101+
102+
// Validate output format
103+
if opts.OutputFormat != "yaml" && opts.OutputFormat != "json" {
104+
return fmt.Errorf("--output-format must be yaml or json, got %q", opts.OutputFormat)
105+
}
106+
107+
return nil
108+
}
109+
110+
func runMigrate(ctx context.Context, opts *ingress2gateway.MigrateOptions) error {
111+
readFunc := func(ctx context.Context, o ingress2gateway.MigrateOptions) (*ingress2gateway.InputResources, error) {
112+
resources, err := reader.Read(ctx, o)
113+
if err != nil {
114+
return nil, err
115+
}
116+
if !o.FromCluster {
117+
warnings.CheckMissingResources(resources, os.Stderr)
118+
}
119+
return resources, nil
120+
}
121+
122+
writeFunc := writer.Write
123+
124+
return ingress2gateway.Migrate(ctx, *opts, readFunc, writeFunc)
125+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Migrate from Ingress
2+
3+
!!! warning "Under Development"
4+
This tool is under active development and is not ready for production use.
5+
Features may change, and annotation translation is not yet implemented.
6+
Do not use this tool for production migrations at this time.
7+
8+
`lbc-migrate` is a CLI tool that helps migrate AWS Load Balancer Controller (LBC) Ingress resources to Gateway API equivalents. It reads Ingress, Service, IngressClass, and IngressClassParams resources from YAML/JSON files or a live Kubernetes cluster, and writes them to an output directory.
9+
10+
## Installation
11+
12+
Build from source (requires Go):
13+
```bash
14+
# From the root of the aws-load-balancer-controller repo
15+
make lbc-migrate
16+
```
17+
18+
The binary will be at `bin/lbc-migrate`. To install it on your PATH (creates a symlink, so future `make lbc-migrate` rebuilds are picked up automatically):
19+
```bash
20+
make install-lbc-migrate
21+
```
22+
23+
Alternatively, use `go run` directly without building:
24+
```bash
25+
go run ./cmd/lbc-migrate/ [flags]
26+
```
27+
28+
## Usage
29+
30+
```
31+
lbc-migrate [flags]
32+
```
33+
34+
### Input Modes
35+
36+
The tool supports three input modes. You must provide at least one.
37+
38+
#### Option 1: Individual files
39+
```bash
40+
lbc-migrate -f ingress1.yaml,ingress2.yaml
41+
```
42+
43+
#### Option 2: Directory of files
44+
```bash
45+
lbc-migrate --input-dir ./my-manifests/
46+
```
47+
48+
#### Option 3: Read from a live cluster
49+
50+
!!! note "Read-only access"
51+
The `--from-cluster` mode only performs read operations (list/get). It never creates, updates, or deletes any cluster resources. We recommend using a user or service account with read-only RBAC permissions (e.g. a ClusterRole with only `get` and `list` verbs on Ingress, Service, IngressClass, and IngressClassParams resources).
52+
53+
```bash
54+
lbc-migrate --from-cluster --all-namespaces
55+
lbc-migrate --from-cluster --namespace production
56+
```
57+
58+
You can combine `-f` and `--input-dir`, but `--from-cluster` cannot be used with file-based input.
59+
60+
### Flags
61+
62+
| Flag | Required | Description | Default |
63+
|------|----------|-------------|---------|
64+
| `-f, --file` | One of `-f`, `--input-dir`, or `--from-cluster` is required | Comma-separated input YAML/JSON file paths (e.g. `-f file1.yaml,file2.yaml`) | |
65+
| `--input-dir` | One of `-f`, `--input-dir`, or `--from-cluster` is required | Directory containing YAML/JSON files | |
66+
| `--from-cluster` | One of `-f`, `--input-dir`, or `--from-cluster` is required | Read resources from a live Kubernetes cluster | |
67+
| `--namespace` | Optional | Namespace to read from. Only valid with `--from-cluster`. Mutually exclusive with `--all-namespaces` | Current kubeconfig context namespace |
68+
| `--all-namespaces` | Optional | Read from all namespaces. Only valid with `--from-cluster`. Mutually exclusive with `--namespace` | |
69+
| `--kubeconfig` | Optional | Path to kubeconfig file. Only valid with `--from-cluster` | `$KUBECONFIG` or `~/.kube/config` |
70+
| `--output-dir` | Optional | Directory to write output manifests | `./gateway-output` |
71+
| `--output-format` | Optional | Output format: `yaml` or `json` | `yaml` |
72+
73+
### Supported Input Formats
74+
75+
Both YAML and JSON Kubernetes manifests are supported. Multi-document YAML files (separated by `---`) are handled automatically. The tool recognizes the following resource types:
76+
77+
- `networking.k8s.io/v1` Ingress
78+
- `networking.k8s.io/v1` IngressClass
79+
- `v1` Service
80+
- `elbv2.k8s.aws/v1beta1` IngressClassParams
81+
82+
Unrecognized resource types in the input are silently skipped.
83+
84+
## Examples
85+
86+
### Basic: single Ingress file
87+
```bash
88+
lbc-migrate -f ingress.yaml --output-dir ./gateway-output/
89+
```
90+
91+
### Directory of manifests
92+
```bash
93+
lbc-migrate --input-dir ./k8s-manifests/ --output-dir ./gateway-output/
94+
```
95+
96+
### From a live cluster (all namespaces)
97+
```bash
98+
lbc-migrate --from-cluster --all-namespaces --output-dir ./gateway-output/
99+
```
100+
101+
### From a live cluster (specific namespace)
102+
```bash
103+
lbc-migrate --from-cluster --namespace production --output-dir ./gateway-output/
104+
```
105+
106+
### Custom kubeconfig
107+
```bash
108+
lbc-migrate --from-cluster --all-namespaces --kubeconfig ~/.kube/staging-config --output-dir ./gateway-output/
109+
```
110+
111+
### JSON output format
112+
```bash
113+
lbc-migrate -f ingress.yaml --output-dir ./gateway-output/ --output-format json
114+
```
115+
116+
## Missing Resource Warnings
117+
118+
When using file-based input (`-f` or `--input-dir`), the tool checks for missing referenced resources and emits warnings to stderr. For example, if an Ingress references a Service that was not provided in the input files:
119+
120+
```
121+
WARNING: Ingress "default/my-ingress" references Service "api-service"
122+
but it was not provided. Service-level annotation overrides may be missing.
123+
124+
WARNING: Ingress "default/my-ingress" uses IngressClass "alb"
125+
but it was not provided. IngressClassParams overrides may be missing.
126+
127+
Tip: Use --from-cluster to automatically read all referenced resources,
128+
or include Service/IngressClass/IngressClassParams files in your input.
129+
```
130+
131+
These warnings are informational — the tool still generates output. For the most accurate results, use `--from-cluster` which automatically fetches all referenced resources.

pkg/ingress2gateway/migrate.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ingress2gateway
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
)
8+
9+
// Migrate is the top-level orchestrator that reads input resources,
10+
// checks for missing references, and writes output.
11+
// The translate step (annotation mapping) will be added later.
12+
func Migrate(ctx context.Context, opts MigrateOptions, readFunc ReadFunc, writeFunc WriteFunc) error {
13+
resources, err := readFunc(ctx, opts)
14+
if err != nil {
15+
return fmt.Errorf("failed to read input resources: %w", err)
16+
}
17+
18+
if len(resources.Ingresses) == 0 {
19+
fmt.Fprintln(os.Stderr, "No Ingress resources found in input.")
20+
return nil
21+
}
22+
23+
fmt.Fprintf(os.Stderr, "Found %d Ingress(es), %d Service(s), %d IngressClass(es), %d IngressClassParams\n",
24+
len(resources.Ingresses), len(resources.Services),
25+
len(resources.IngressClasses), len(resources.IngressClassParams))
26+
27+
// TODO: translate InputResources → OutputResources (Gateway API manifests)
28+
// For now, we pass through the input resources as-is.
29+
30+
if err := writeFunc(resources, opts.OutputDir, opts.OutputFormat); err != nil {
31+
return fmt.Errorf("failed to write output: %w", err)
32+
}
33+
34+
return nil
35+
}
36+
37+
// ReadFunc is the signature for reading input resources.
38+
type ReadFunc func(ctx context.Context, opts MigrateOptions) (*InputResources, error)
39+
40+
// WriteFunc is the signature for writing output resources.
41+
type WriteFunc func(resources *InputResources, outputDir string, format string) error

0 commit comments

Comments
 (0)