package kubesource import ( "bytes" "io" "os" "reflect" "sort" "strings" "testing" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) func yamlDocs(t *testing.T, path string) []map[string]any { t.Helper() b, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var out []map[string]any dec := yaml.NewDecoder(bytes.NewReader(b)) for { var m map[string]any if err := dec.Decode(&m); err == io.EOF { break } else if err != nil { t.Fatalf("%s: %v", path, err) } if m != nil { out = append(out, m) } } return out } func jsonFields(typ reflect.Type) []string { var out []string for i := 0; i < typ.NumField(); i++ { tag := strings.Split(typ.Field(i).Tag.Get("json"), ",")[0] if tag != "" && tag != "-" { out = append(out, tag) } } sort.Strings(out) return out } // TestCRDSchemasMatchTheGoSpecs keeps deploy/k8s/crds/crds.yaml and the spec // structs from drifting: a field the schema lacks is pruned by the apiserver // before the runtime ever sees it, and a schema field the struct lacks is // accepted and silently ignored. func TestCRDSchemasMatchTheGoSpecs(t *testing.T) { want := map[string]reflect.Type{ "DNSZone": reflect.TypeOf(DNSZoneSpec{}), "ClientGroup": reflect.TypeOf(ClientGroupSpec{}), "BlockList": reflect.TypeOf(BlockListSpec{}), } seen := 0 for _, doc := range yamlDocs(t, "../../deploy/k8s/crds/crds.yaml") { names := doc["spec"].(map[string]any)["names"].(map[string]any) kind := names["kind"].(string) typ, ok := want[kind] if !ok { t.Errorf("unexpected CRD kind %s", kind) continue } seen++ versions := doc["spec"].(map[string]any)["versions"].([]any) schema := versions[0].(map[string]any)["schema"].(map[string]any)["openAPIV3Schema"].(map[string]any) props := schema["properties"].(map[string]any)["spec"].(map[string]any)["properties"].(map[string]any) var got []string for k := range props { got = append(got, k) } sort.Strings(got) if !reflect.DeepEqual(got, jsonFields(typ)) { t.Errorf("%s: schema fields %v, Go fields %v", kind, got, jsonFields(typ)) } if plural := names["plural"].(string); plural != map[string]string{ "DNSZone": ZoneGVR.Resource, "ClientGroup": GroupGVR.Resource, "BlockList": ListGVR.Resource, }[kind] { t.Errorf("%s: plural %q does not match the GVR the runtime watches", kind, plural) } } if seen != len(want) { t.Errorf("saw %d CRDs, want %d", seen, len(want)) } } // TestExampleObjectsConvert makes sure the shipped examples are accepted by // the runtime, so the first thing an operator copies is not a rejection. func TestExampleObjectsConvert(t *testing.T) { n := 0 for _, doc := range yamlDocs(t, "../../deploy/k8s/crds/examples.yaml") { u := &unstructured.Unstructured{Object: doc} var err error switch u.GetKind() { case "DNSZone": _, err = convertZone(u) case "ClientGroup": _, err = convertGroup(u) case "BlockList": _, err = convertList(u) default: t.Fatalf("unknown kind %s", u.GetKind()) } if err != nil { t.Errorf("%s %s: %v", u.GetKind(), u.GetName(), err) } n++ } if n != 3 { t.Fatalf("converted %d examples, want 3", n) } }