Skip to content

Commit

Permalink
Have the start of cmd/migrator to migrate between v3 & v4 CRDs. Use i…
Browse files Browse the repository at this point in the history
…t to migrate all the v3alpha1 testdata for emissary-ingress.dev/v4alpha1 over to proper v4 CRDs.

Signed-off-by: Flynn <[email protected]>
  • Loading branch information
kflynn committed Oct 16, 2024
1 parent 68797c5 commit dc6ddc6
Show file tree
Hide file tree
Showing 24 changed files with 2,954 additions and 1,366 deletions.
277 changes: 277 additions & 0 deletions cmd/migrator/converter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
package main

import (
"encoding/json"
"fmt"
"io"
"os"
"reflect"

// yaml "github.com/goccy/go-yaml"
"gopkg.in/yaml.v2"
k8syaml "sigs.k8s.io/yaml"

// v3yaml "github.com/emissary-ingress/emissary/v3/pkg/yaml"

// v3crds "github.com/emissary-ingress/emissary/v3/pkg/api/getambassador.io/v3alpha1"

v4crds "github.com/emissary-ingress/emissary/v3/pkg/api/emissary-ingress.dev/v4alpha1"
v3json "github.com/emissary-ingress/emissary/v3/pkg/json"

Check failure on line 19 in cmd/migrator/converter.go

View workflow job for this annotation

GitHub Actions / Lint

could not import github.com/emissary-ingress/emissary/v3/pkg/json (-: # github.com/emissary-ingress/emissary/v3/pkg/json
)

type v4ConversionFunc func(nativeResource interface{}) error

type conversionInfo struct {
nativeType reflect.Type
toV4 v4ConversionFunc
}

type conversionMap map[string]conversionInfo

func (cMap conversionMap) addConversion(kind string, v interface{}, toV4 v4ConversionFunc) {
nativeType := reflect.TypeOf(v)
// fmt.Printf("add %s: %s - %s\n", kind, nativeType.Kind(), nativeType.Name())

cMap[kind] = conversionInfo{nativeType, toV4}
}

func (cMap conversionMap) lookup(kind string) (bool, reflect.Type, v4ConversionFunc) {
info, ok := cMap[kind]

if !ok {
return false, nil, nil
}

return true, info.nativeType, info.toV4
}

func fixAPIVersion(nativeResource interface{}) error {
// Swap the apiVersion...
reflectedValue := reflect.ValueOf(nativeResource).Elem()
typeMetaField := reflectedValue.FieldByName("TypeMeta")

if !typeMetaField.IsValid() {
return fmt.Errorf("no TypeMeta field in %#v", nativeResource)
}

apiVersionField := typeMetaField.FieldByName("APIVersion")

if !apiVersionField.IsValid() {
return fmt.Errorf("no APIVersion field in %#v", nativeResource)
}

apiVersionField.SetString("emissary-ingress.dev/v4alpha1")

return nil
}

func convertV3toV4(originalResource interface{}, typeMap conversionMap) (interface{}, error) {
var unstructuredResource map[string]interface{}

switch originalResource := originalResource.(type) {
case map[string]interface{}, map[interface{}]interface{}:
unstructuredResource = convertMap(originalResource).(map[string]interface{})

default:
return nil, fmt.Errorf("unsupported type %s: %#v", reflect.TypeOf(originalResource).Name(), originalResource)
}

// fmt.Printf("---\n")
// fmt.Printf("%#v\n", unstructuredResource)

// fmt.Printf("\nTypeMap:\n")
// for k, v := range typeMap {
// fmt.Printf(" %s: %s\n", k, v.nativeType.Name())
// }

// Grab the apiVersion and kind.
apiVersion, ok := unstructuredResource["apiVersion"].(string)

if !ok {
return nil, fmt.Errorf("apiVersion not found or not a string: %#v", unstructuredResource)
}

// Is this a getambassador.io/v3alpha1 resource?
if apiVersion != "getambassador.io/v3alpha1" {
// Nope.
return nil, fmt.Errorf("unknown apiVersion %s: %#v", apiVersion, unstructuredResource)
}

kind, ok := unstructuredResource["kind"].(string)

if !ok {
return nil, fmt.Errorf("kind not found or not a string: %#v", unstructuredResource)
}

ok, nativeType, toV4 := typeMap.lookup(kind)

if !ok {
return nil, fmt.Errorf("unknown kind %s: %#v", kind, unstructuredResource)
}

// fmt.Printf("kind %s: native type %s\n", kind, nativeType.Name())

nativeResource := reflect.New(nativeType).Interface()
// fmt.Printf("kind %s: native resource type %s\n", kind, reflect.TypeOf(nativeResource).Name())

// Convert our unstructured resource to JSON...
rawJSON, err := json.MarshalIndent(unstructuredResource, "", " ")

if err != nil {
return nil, fmt.Errorf("couldn't convert unstructured to JSON? %s", err)
}

// fmt.Printf("V3: %s\n", string(rawJSON))

// ...then unmarshal that JSON into our native resource, using the "v3" tag.
err = v3json.Unmarshal(rawJSON, nativeResource)
// err := v4crds.UnstructuredToNative(unstructuredResource, nativeResource, "v3")

if err != nil {
return nil, fmt.Errorf("error converting object to native %#v: %s", unstructuredResource, err)
}

// Run the conversion (if any).
if toV4 == nil {
toV4 = fixAPIVersion
}

err = toV4(nativeResource)

if err != nil {
return nil, fmt.Errorf("error converting object to v4 %#v: %s", unstructuredResource, err)
}

return nativeResource, nil

// // Dump as YAML.
// yamlBytes, err := k8syaml.Marshal(nativeResource)

// if err != nil {
// return nil, fmt.Errorf("error marshaling to YAML %#v: %s", unstructuredResource, err)
// }

// // fmt.Prinf("V4:\n")
// fmt.Printf("---\n%s", string(yamlBytes))
// return nil
}

// convertMap converts a map[interface{}]interface{} to a map[string]interface{}.
// It's kind of infuriating the YAML decoder requires this.
func convertMap(m interface{}) interface{} {
var retval interface{}

switch m := m.(type) {
case []interface{}:
newArray := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(m).Elem()), 0, len(m)).Interface().([]interface{})
for _, e := range m {
newArray = append(newArray, convertMap(e))
}

retval = newArray

case map[interface{}]interface{}:
newMap := make(map[string]interface{}, len(m))

for key, value := range m {
newMap[key.(string)] = convertMap(value)
}

retval = newMap

default:
retval = m
}

return retval
}

func convertSingleResource(unstructuredResource interface{}, typeMap conversionMap) (interface{}, error) {
switch value := unstructuredResource.(type) {
case []interface{}:
// This is a list of resources. Loop through them.
result := make([]interface{}, 0, len(value))

for i, item := range value {
// fmt.Printf("item %d: %#v\n", i, item)
v4obj, error := convertSingleResource(item, typeMap)

if error != nil {
return nil, fmt.Errorf("failed to convert array item %d (%#v): %v", i, item, error)
}

result = append(result, v4obj)
}

return result, nil

default:
return convertV3toV4(unstructuredResource, typeMap)
}
}

func main() {
typeMap := make(conversionMap)

typeMap.addConversion("AuthService", v4crds.AuthService{}, nil)
typeMap.addConversion("DevPortal", v4crds.DevPortal{}, nil)
typeMap.addConversion("Host", v4crds.Host{}, nil)
typeMap.addConversion("Listener", v4crds.Listener{}, nil)
typeMap.addConversion("LogService", v4crds.LogService{}, nil)
typeMap.addConversion("Mapping", v4crds.Mapping{}, nil)
typeMap.addConversion("Module", v4crds.Module{}, nil)
typeMap.addConversion("Features", v4crds.Features{}, nil)
typeMap.addConversion("RateLimitService", v4crds.RateLimitService{}, nil)
typeMap.addConversion("KubernetesServiceResolver", v4crds.KubernetesServiceResolver{}, nil)
typeMap.addConversion("KubernetesEndpointResolver", v4crds.KubernetesEndpointResolver{}, nil)
typeMap.addConversion("ConsulResolver", v4crds.ConsulResolver{}, nil)
typeMap.addConversion("TCPMapping", v4crds.TCPMapping{}, nil)
typeMap.addConversion("TLSContext", v4crds.TLSContext{}, nil)
typeMap.addConversion("TracingService", v4crds.TracingService{}, nil)

if len(os.Args) < 2 {
fmt.Println("Usage: converter <path1> <path2> ... <pathN>")
return
}

for _, path := range os.Args[1:] {
reader, err := os.Open(path)

if err != nil {
fmt.Printf("Error reading file %s: %v\n", path, err)
continue
}

decoder := yaml.NewDecoder(reader)

// Loop through all the YAML documents
for {
// Decode the next YAML document into unstructuredResource.
var unstructuredResource interface{}
err := decoder.Decode(&unstructuredResource)

if err == io.EOF {
// End of the YAML input
break
} else if err != nil {
fmt.Printf("Error decoding YAML: %v\n", err)
continue
}

v4obj, error := convertSingleResource(unstructuredResource, typeMap)

if error != nil {
fmt.Printf("error converting resource %#v: %v\n", v4obj, error)
}

// Dump as YAML.
yamlBytes, err := k8syaml.Marshal(v4obj)

if err != nil {
fmt.Printf("error marshaling to YAML %#v: %s\n", v4obj, err)
} else {
fmt.Printf("%s", string(yamlBytes))
}
}
}
}
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ require (
github.com/envoyproxy/go-control-plane v0.11.1
github.com/fsnotify/fsnotify v1.7.0
github.com/go-logr/zapr v1.2.4
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.5.0
github.com/gorilla/websocket v1.5.1
github.com/hashicorp/consul/api v1.26.1
Expand All @@ -110,6 +111,7 @@ require (
google.golang.org/grpc v1.60.1
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
google.golang.org/protobuf v1.33.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.28.5
k8s.io/apiextensions-apiserver v0.28.5
Expand Down Expand Up @@ -172,7 +174,6 @@ require (
github.com/google/btree v1.0.1 // indirect
github.com/google/cel-go v0.18.1 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
Expand All @@ -192,6 +193,8 @@ require (
github.com/josharian/intern v1.0.1-0.20211109044230-42b52b674af5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/magiconair/properties v1.8.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand All @@ -216,6 +219,7 @@ require (
github.com/prometheus/client_golang v1.17.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
Expand Down
Loading

0 comments on commit dc6ddc6

Please sign in to comment.