Skip to content

Commit 3e925af

Browse files
committed
Have the start of cmd/migrator to migrate between v3 & v4 CRDs. Use it to migrate all the v3alpha1 testdata for emissary-ingress.dev/v4alpha1 over to proper v4 CRDs.
Signed-off-by: Flynn <[email protected]>
1 parent 40d1957 commit 3e925af

24 files changed

+2954
-1366
lines changed

cmd/migrator/converter.go

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"os"
8+
"reflect"
9+
10+
// yaml "github.com/goccy/go-yaml"
11+
"gopkg.in/yaml.v2"
12+
k8syaml "sigs.k8s.io/yaml"
13+
14+
// v3yaml "github.com/emissary-ingress/emissary/v3/pkg/yaml"
15+
16+
// v3crds "github.com/emissary-ingress/emissary/v3/pkg/api/getambassador.io/v3alpha1"
17+
18+
v4crds "github.com/emissary-ingress/emissary/v3/pkg/api/emissary-ingress.dev/v4alpha1"
19+
v3json "github.com/emissary-ingress/emissary/v3/pkg/json"
20+
)
21+
22+
type v4ConversionFunc func(nativeResource interface{}) error
23+
24+
type conversionInfo struct {
25+
nativeType reflect.Type
26+
toV4 v4ConversionFunc
27+
}
28+
29+
type conversionMap map[string]conversionInfo
30+
31+
func (cMap conversionMap) addConversion(kind string, v interface{}, toV4 v4ConversionFunc) {
32+
nativeType := reflect.TypeOf(v)
33+
// fmt.Printf("add %s: %s - %s\n", kind, nativeType.Kind(), nativeType.Name())
34+
35+
cMap[kind] = conversionInfo{nativeType, toV4}
36+
}
37+
38+
func (cMap conversionMap) lookup(kind string) (bool, reflect.Type, v4ConversionFunc) {
39+
info, ok := cMap[kind]
40+
41+
if !ok {
42+
return false, nil, nil
43+
}
44+
45+
return true, info.nativeType, info.toV4
46+
}
47+
48+
func fixAPIVersion(nativeResource interface{}) error {
49+
// Swap the apiVersion...
50+
reflectedValue := reflect.ValueOf(nativeResource).Elem()
51+
typeMetaField := reflectedValue.FieldByName("TypeMeta")
52+
53+
if !typeMetaField.IsValid() {
54+
return fmt.Errorf("no TypeMeta field in %#v", nativeResource)
55+
}
56+
57+
apiVersionField := typeMetaField.FieldByName("APIVersion")
58+
59+
if !apiVersionField.IsValid() {
60+
return fmt.Errorf("no APIVersion field in %#v", nativeResource)
61+
}
62+
63+
apiVersionField.SetString("emissary-ingress.dev/v4alpha1")
64+
65+
return nil
66+
}
67+
68+
func convertV3toV4(originalResource interface{}, typeMap conversionMap) (interface{}, error) {
69+
var unstructuredResource map[string]interface{}
70+
71+
switch originalResource := originalResource.(type) {
72+
case map[string]interface{}, map[interface{}]interface{}:
73+
unstructuredResource = convertMap(originalResource).(map[string]interface{})
74+
75+
default:
76+
return nil, fmt.Errorf("unsupported type %s: %#v", reflect.TypeOf(originalResource).Name(), originalResource)
77+
}
78+
79+
// fmt.Printf("---\n")
80+
// fmt.Printf("%#v\n", unstructuredResource)
81+
82+
// fmt.Printf("\nTypeMap:\n")
83+
// for k, v := range typeMap {
84+
// fmt.Printf(" %s: %s\n", k, v.nativeType.Name())
85+
// }
86+
87+
// Grab the apiVersion and kind.
88+
apiVersion, ok := unstructuredResource["apiVersion"].(string)
89+
90+
if !ok {
91+
return nil, fmt.Errorf("apiVersion not found or not a string: %#v", unstructuredResource)
92+
}
93+
94+
// Is this a getambassador.io/v3alpha1 resource?
95+
if apiVersion != "getambassador.io/v3alpha1" {
96+
// Nope.
97+
return nil, fmt.Errorf("unknown apiVersion %s: %#v", apiVersion, unstructuredResource)
98+
}
99+
100+
kind, ok := unstructuredResource["kind"].(string)
101+
102+
if !ok {
103+
return nil, fmt.Errorf("kind not found or not a string: %#v", unstructuredResource)
104+
}
105+
106+
ok, nativeType, toV4 := typeMap.lookup(kind)
107+
108+
if !ok {
109+
return nil, fmt.Errorf("unknown kind %s: %#v", kind, unstructuredResource)
110+
}
111+
112+
// fmt.Printf("kind %s: native type %s\n", kind, nativeType.Name())
113+
114+
nativeResource := reflect.New(nativeType).Interface()
115+
// fmt.Printf("kind %s: native resource type %s\n", kind, reflect.TypeOf(nativeResource).Name())
116+
117+
// Convert our unstructured resource to JSON...
118+
rawJSON, err := json.MarshalIndent(unstructuredResource, "", " ")
119+
120+
if err != nil {
121+
return nil, fmt.Errorf("couldn't convert unstructured to JSON? %s", err)
122+
}
123+
124+
// fmt.Printf("V3: %s\n", string(rawJSON))
125+
126+
// ...then unmarshal that JSON into our native resource, using the "v3" tag.
127+
err = v3json.Unmarshal(rawJSON, nativeResource)
128+
// err := v4crds.UnstructuredToNative(unstructuredResource, nativeResource, "v3")
129+
130+
if err != nil {
131+
return nil, fmt.Errorf("error converting object to native %#v: %s", unstructuredResource, err)
132+
}
133+
134+
// Run the conversion (if any).
135+
if toV4 == nil {
136+
toV4 = fixAPIVersion
137+
}
138+
139+
err = toV4(nativeResource)
140+
141+
if err != nil {
142+
return nil, fmt.Errorf("error converting object to v4 %#v: %s", unstructuredResource, err)
143+
}
144+
145+
return nativeResource, nil
146+
147+
// // Dump as YAML.
148+
// yamlBytes, err := k8syaml.Marshal(nativeResource)
149+
150+
// if err != nil {
151+
// return nil, fmt.Errorf("error marshaling to YAML %#v: %s", unstructuredResource, err)
152+
// }
153+
154+
// // fmt.Prinf("V4:\n")
155+
// fmt.Printf("---\n%s", string(yamlBytes))
156+
// return nil
157+
}
158+
159+
// convertMap converts a map[interface{}]interface{} to a map[string]interface{}.
160+
// It's kind of infuriating the YAML decoder requires this.
161+
func convertMap(m interface{}) interface{} {
162+
var retval interface{}
163+
164+
switch m := m.(type) {
165+
case []interface{}:
166+
newArray := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(m).Elem()), 0, len(m)).Interface().([]interface{})
167+
for _, e := range m {
168+
newArray = append(newArray, convertMap(e))
169+
}
170+
171+
retval = newArray
172+
173+
case map[interface{}]interface{}:
174+
newMap := make(map[string]interface{}, len(m))
175+
176+
for key, value := range m {
177+
newMap[key.(string)] = convertMap(value)
178+
}
179+
180+
retval = newMap
181+
182+
default:
183+
retval = m
184+
}
185+
186+
return retval
187+
}
188+
189+
func convertSingleResource(unstructuredResource interface{}, typeMap conversionMap) (interface{}, error) {
190+
switch value := unstructuredResource.(type) {
191+
case []interface{}:
192+
// This is a list of resources. Loop through them.
193+
result := make([]interface{}, 0, len(value))
194+
195+
for i, item := range value {
196+
// fmt.Printf("item %d: %#v\n", i, item)
197+
v4obj, error := convertSingleResource(item, typeMap)
198+
199+
if error != nil {
200+
return nil, fmt.Errorf("failed to convert array item %d (%#v): %v", i, item, error)
201+
}
202+
203+
result = append(result, v4obj)
204+
}
205+
206+
return result, nil
207+
208+
default:
209+
return convertV3toV4(unstructuredResource, typeMap)
210+
}
211+
}
212+
213+
func main() {
214+
typeMap := make(conversionMap)
215+
216+
typeMap.addConversion("AuthService", v4crds.AuthService{}, nil)
217+
typeMap.addConversion("DevPortal", v4crds.DevPortal{}, nil)
218+
typeMap.addConversion("Host", v4crds.Host{}, nil)
219+
typeMap.addConversion("Listener", v4crds.Listener{}, nil)
220+
typeMap.addConversion("LogService", v4crds.LogService{}, nil)
221+
typeMap.addConversion("Mapping", v4crds.Mapping{}, nil)
222+
typeMap.addConversion("Module", v4crds.Module{}, nil)
223+
typeMap.addConversion("Features", v4crds.Features{}, nil)
224+
typeMap.addConversion("RateLimitService", v4crds.RateLimitService{}, nil)
225+
typeMap.addConversion("KubernetesServiceResolver", v4crds.KubernetesServiceResolver{}, nil)
226+
typeMap.addConversion("KubernetesEndpointResolver", v4crds.KubernetesEndpointResolver{}, nil)
227+
typeMap.addConversion("ConsulResolver", v4crds.ConsulResolver{}, nil)
228+
typeMap.addConversion("TCPMapping", v4crds.TCPMapping{}, nil)
229+
typeMap.addConversion("TLSContext", v4crds.TLSContext{}, nil)
230+
typeMap.addConversion("TracingService", v4crds.TracingService{}, nil)
231+
232+
if len(os.Args) < 2 {
233+
fmt.Println("Usage: converter <path1> <path2> ... <pathN>")
234+
return
235+
}
236+
237+
for _, path := range os.Args[1:] {
238+
reader, err := os.Open(path)
239+
240+
if err != nil {
241+
fmt.Printf("Error reading file %s: %v\n", path, err)
242+
continue
243+
}
244+
245+
decoder := yaml.NewDecoder(reader)
246+
247+
// Loop through all the YAML documents
248+
for {
249+
// Decode the next YAML document into unstructuredResource.
250+
var unstructuredResource interface{}
251+
err := decoder.Decode(&unstructuredResource)
252+
253+
if err == io.EOF {
254+
// End of the YAML input
255+
break
256+
} else if err != nil {
257+
fmt.Printf("Error decoding YAML: %v\n", err)
258+
continue
259+
}
260+
261+
v4obj, error := convertSingleResource(unstructuredResource, typeMap)
262+
263+
if error != nil {
264+
fmt.Printf("error converting resource %#v: %v\n", v4obj, error)
265+
}
266+
267+
// Dump as YAML.
268+
yamlBytes, err := k8syaml.Marshal(v4obj)
269+
270+
if err != nil {
271+
fmt.Printf("error marshaling to YAML %#v: %s\n", v4obj, err)
272+
} else {
273+
fmt.Printf("%s", string(yamlBytes))
274+
}
275+
}
276+
}
277+
}

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ require (
9090
github.com/envoyproxy/go-control-plane v0.11.1
9191
github.com/fsnotify/fsnotify v1.7.0
9292
github.com/go-logr/zapr v1.2.4
93+
github.com/google/go-cmp v0.6.0
9394
github.com/google/uuid v1.5.0
9495
github.com/gorilla/websocket v1.5.1
9596
github.com/hashicorp/consul/api v1.26.1
@@ -110,6 +111,7 @@ require (
110111
google.golang.org/grpc v1.60.1
111112
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
112113
google.golang.org/protobuf v1.33.0
114+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
113115
gopkg.in/yaml.v2 v2.4.0
114116
k8s.io/api v0.28.5
115117
k8s.io/apiextensions-apiserver v0.28.5
@@ -172,7 +174,6 @@ require (
172174
github.com/google/btree v1.0.1 // indirect
173175
github.com/google/cel-go v0.18.1 // indirect
174176
github.com/google/gnostic-models v0.6.8 // indirect
175-
github.com/google/go-cmp v0.6.0 // indirect
176177
github.com/google/gofuzz v1.2.0 // indirect
177178
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
178179
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
@@ -192,6 +193,8 @@ require (
192193
github.com/josharian/intern v1.0.1-0.20211109044230-42b52b674af5 // indirect
193194
github.com/json-iterator/go v1.1.12 // indirect
194195
github.com/kevinburke/ssh_config v1.2.0 // indirect
196+
github.com/kr/pretty v0.3.1 // indirect
197+
github.com/kr/text v0.2.0 // indirect
195198
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
196199
github.com/magiconair/properties v1.8.1 // indirect
197200
github.com/mailru/easyjson v0.7.7 // indirect
@@ -216,6 +219,7 @@ require (
216219
github.com/prometheus/client_golang v1.17.0 // indirect
217220
github.com/prometheus/common v0.45.0 // indirect
218221
github.com/prometheus/procfs v0.12.0 // indirect
222+
github.com/rogpeppe/go-internal v1.11.0 // indirect
219223
github.com/russross/blackfriday/v2 v2.1.0 // indirect
220224
github.com/sergi/go-diff v1.3.1 // indirect
221225
github.com/skeema/knownhosts v1.2.1 // indirect

0 commit comments

Comments
 (0)