From fcab371d9b18d3d2eccc4b4fd9f1906bb07be456 Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 7 Aug 2024 12:18:39 -0400 Subject: [PATCH 1/6] Initial work on turning OpenAPI representation of Object type into generated resource.Object.Copy code. --- codegen/jennies/resourceobject.go | 190 ++++++++++++++++++++++++++ codegen/templates/resourceobject.tmpl | 2 +- codegen/templates/templates.go | 1 + 3 files changed, 192 insertions(+), 1 deletion(-) diff --git a/codegen/jennies/resourceobject.go b/codegen/jennies/resourceobject.go index 12a6bbd47..97bad0aa5 100644 --- a/codegen/jennies/resourceobject.go +++ b/codegen/jennies/resourceobject.go @@ -11,6 +11,7 @@ import ( "time" "cuelang.org/go/cue" + "github.com/getkin/kin-openapi/openapi3" "github.com/grafana/codejen" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -61,6 +62,10 @@ type ResourceObjectGenerator struct { // When GroupByKind is false, subresource types (such as spec and status) are assumed to be prefixed with the // kind name, which can be accomplished by setting GroupByKind=false on the GoTypesGenerator. GroupByKind bool + + // GenericCopy toggles whether the generated code for Copy() calls the generic resource.CopyObject method, + // or generates code to deep-copy the entire struct. + GenericCopy bool } func (*ResourceObjectGenerator) JennyName() string { @@ -162,6 +167,25 @@ func (r *ResourceObjectGenerator) generateObjectFile(kind codegen.Kind, version JSONName: it.Label(), }) } + if !r.GenericCopy { + // Deep copy code + buf := strings.Builder{} + buf.WriteString(fmt.Sprintf("cpy := &%s{}\n\n// Copy metadata\no.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta)\n\n// Copy Spec\n", md.TypeName)) + specCopy, err := generateCopyCodeFor(version, "spec", typePrefix) + if err != nil { + return nil, err + } + buf.WriteString(specCopy + "\n") + for _, sr := range md.Subresources { + srCopy, err := generateCopyCodeFor(version, sr.JSONName, typePrefix+"Status") + if err != nil { + return nil, err + } + buf.WriteString(fmt.Sprintf("\n\n// Copy %s\n%s\n", sr.TypeName, srCopy)) + } + buf.WriteString("return cpy") + md.CopyCode = buf.String() + } b := bytes.Buffer{} err = templates.WriteResourceObject(md, &b) if err != nil { @@ -193,3 +217,169 @@ func goTypeFromCUEValue(value cue.Value) templates.CustomMetadataFieldGoType { } return templates.CustomMetadataFieldGoType{} } + +// functions for copy codegen +func generateCopyCodeFor(version *codegen.KindVersion, subresource, namePrefix string) (string, error) { + v := version.Schema.LookupPath(cue.MakePath(cue.Str(subresource))) + openAPIConfig := CUEOpenAPIConfig{ + Name: subresource, + Version: version.Version, + NameFunc: func(value cue.Value, path cue.Path) string { + i := 0 + for ; i < len(path.Selectors()) && i < len(v.Path().Selectors()); i++ { + if !SelEq(path.Selectors()[i], v.Path().Selectors()[i]) { + break + } + } + if i > 0 { + path = cue.MakePath(path.Selectors()[i:]...) + } + return strings.Trim(path.String(), "?#") + }, + } + + yml, err := CUEValueToOAPIYAML(v, openAPIConfig) + if err != nil { + return "", err + } + + loader := openapi3.NewLoader() + oT, err := loader.LoadFromData(yml) + + return copyProps(oT.Components, oT.Components.Schemas[subresource].Value, fmt.Sprintf("o.%s", exportField(subresource)), fmt.Sprintf("cpy.%s", exportField(subresource)), namePrefix) +} + +func copyProps(root *openapi3.Components, sch *openapi3.Schema, srcName, dstName, namingPrefix string) (string, error) { + buf := strings.Builder{} + for k, v := range sch.Properties { + ek := exportField(k) + isPointer := !slices.Contains(sch.Required, k) + if v.Ref != "" { + ref, err := lookupRef(root, v.Ref) + if err != nil { + return "", err + } + refTypeName := namingPrefix + strings.Join(strings.Split(strings.Trim(v.Ref, "#/components/schemas/"), "/"), "") + if v.Value.Type.Is("object") { + if isPointer { + buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n%s.%s = &%s{}\n", srcName, ek, dstName, ek, refTypeName)) + } + str, err := copyProps(root, ref, fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) + if err != nil { + return "", err + } + buf.WriteString(str) + if isPointer { + buf.WriteString("}\n") + } + } + continue + } + + if v.Value.Type.Is("object") { + if v.Value.AdditionalProperties.Schema == nil { + // map[string]any + // TODO something better + buf.WriteString(fmt.Sprintf("%s.%s = make(map[string]any)\n", dstName, ek)) + buf.WriteString(fmt.Sprintf("for key, val := range %s.%s {\n", srcName, ek)) + buf.WriteString(fmt.Sprintf("%s.%s[key] = val\n}\n", dstName, ek)) + continue + } + buf.WriteString(fmt.Sprintf("%s.%s = make(map[string]%s)\n", dstName, ek, oapiTypeToGoType(v.Value.AdditionalProperties.Schema, namingPrefix))) + buf.WriteString(fmt.Sprintf("for key, val := range %s.%s {\n", srcName, ek)) + buf.WriteString(fmt.Sprintf("cpyVal := %s{}\n", oapiTypeToGoType(v.Value.AdditionalProperties.Schema, namingPrefix))) + ref, err := lookupRef(root, v.Value.AdditionalProperties.Schema.Ref) + if err != nil { + return "", err + } + copyStr, err := copyProps(root, ref, "val", "cpyVal", namingPrefix) + if err != nil { + return "", err + } + buf.WriteString(fmt.Sprintf("%s\n%s.%s[key] = cpyVal\n}\n", copyStr, dstName, ek)) + continue + } + if v.Value.Type.Is("array") { + buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) + buf.WriteString(fmt.Sprintf("%s.%s = make([]%s, len(%s.%s))\n", dstName, ek, oapiTypeToGoType(v.Value.Items, namingPrefix), srcName, ek)) + buf.WriteString(fmt.Sprintf("copy(%s.%s, %s.%s)\n", dstName, ek, srcName, ek)) + buf.WriteString("}\n") + continue + } + if isPointer { + buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) + buf.WriteString(fmt.Sprintf("%sCopy := *%s.%s\n", k, srcName, ek)) + buf.WriteString(fmt.Sprintf("%s.%s = &%sCopy\n}\n", dstName, ek, k)) + } else { + buf.WriteString(fmt.Sprintf("%s.%s = %s.%s\n", dstName, ek, srcName, ek)) + } + } + return buf.String(), nil +} + +func oapiTypeToGoType(v *openapi3.SchemaRef, refNamePrefix string) string { + if v.Value.Type.Is("integer") { + switch v.Value.Format { + case "int32", "int64": + return v.Value.Format + } + return "int" + } + if v.Value.Type.Is("boolean") { + return "bool" + } + if v.Value.Type.Is("object") { + if v.Ref != "" { + return refNamePrefix + strings.Join(strings.Split(v.Ref, "/")[3:], "") + } + // TODO: inline structs + return "any" + } + if v.Value.Type.Is("array") { + return "[]" + oapiTypeToGoType(v.Value.Items, refNamePrefix) + } + if v.Value.Type.Is("string") { + if v.Value.Format == "date-time" { + return "time.Time" + } + return "string" + } + if v.Value.Type.Is("number") { + if v.Value.Format == "double" { + return "float64" + } + if v.Value.Format == "float" { + return "float32" + } + } + return "any" +} + +func lookupRef(root *openapi3.Components, ref string) (*openapi3.Schema, error) { + parts := strings.Split(ref, "/") + if len(parts) < 3 || strings.Join(parts[:3], "/") != "#/components/schemas" { + return nil, fmt.Errorf("only references to #/components/schemas are supported") + } + for k, v := range root.Schemas { + if k == parts[3] { + if len(parts) > 4 { + return lookupRefInSchema(v.Value.Properties, strings.Join(parts[3:], "/")) + } + return v.Value, nil + } + } + return nil, fmt.Errorf("reference %s not found", ref) +} + +func lookupRefInSchema(sch openapi3.Schemas, ref string) (*openapi3.Schema, error) { + parts := strings.Split(ref, "/") + for k, v := range sch { + if k == parts[0] { + if len(parts) > 1 { + return lookupRefInSchema(v.Value.Properties, strings.Join(parts[1:], "/")) + } + return v.Value, nil + } + } + return nil, fmt.Errorf("reference %s not found", ref) +} diff --git a/codegen/templates/resourceobject.tmpl b/codegen/templates/resourceobject.tmpl index 0bec4646a..43761af17 100644 --- a/codegen/templates/resourceobject.tmpl +++ b/codegen/templates/resourceobject.tmpl @@ -183,7 +183,7 @@ func ({{$root.ObjectShortName}} *{{$root.TypeName}}) Set{{.FieldName}}({{.JSONNa {{end}} func ({{.ObjectShortName}} *{{.TypeName}}) Copy() resource.Object { - return resource.CopyObject({{.ObjectShortName}}) + {{ if eq .CopyCode "" }}return resource.CopyObject({{.ObjectShortName}}){{ else }}{{ .CopyCode }}{{ end }} } func ({{.ObjectShortName}} *{{.TypeName}}) DeepCopyObject() runtime.Object { diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 940e657f5..18f81d029 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -78,6 +78,7 @@ type ResourceObjectTemplateMetadata struct { SpecTypeName string ObjectTypeName string ObjectShortName string + CopyCode string Subresources []SubresourceMetadata CustomMetadataFields []ObjectMetadataField } From e4ef5c912cf70d34a5c3324a1865a82ddd817619 Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 7 Aug 2024 17:11:52 -0400 Subject: [PATCH 2/6] Handle inline structs in copy. --- codegen/jennies/resourceobject.go | 131 ++++++++++++++++++------------ 1 file changed, 80 insertions(+), 51 deletions(-) diff --git a/codegen/jennies/resourceobject.go b/codegen/jennies/resourceobject.go index 97bad0aa5..2184d2d78 100644 --- a/codegen/jennies/resourceobject.go +++ b/codegen/jennies/resourceobject.go @@ -245,75 +245,104 @@ func generateCopyCodeFor(version *codegen.KindVersion, subresource, namePrefix s loader := openapi3.NewLoader() oT, err := loader.LoadFromData(yml) + if err != nil { + return "", err + } - return copyProps(oT.Components, oT.Components.Schemas[subresource].Value, fmt.Sprintf("o.%s", exportField(subresource)), fmt.Sprintf("cpy.%s", exportField(subresource)), namePrefix) + return generateSchemaCopyCode(oT.Components, oT.Components.Schemas[subresource].Value, fmt.Sprintf("o.%s", exportField(subresource)), fmt.Sprintf("cpy.%s", exportField(subresource)), namePrefix) } -func copyProps(root *openapi3.Components, sch *openapi3.Schema, srcName, dstName, namingPrefix string) (string, error) { +func generateSchemaCopyCode(root *openapi3.Components, sch *openapi3.Schema, srcName, dstName, namingPrefix string) (string, error) { buf := strings.Builder{} for k, v := range sch.Properties { - ek := exportField(k) isPointer := !slices.Contains(sch.Required, k) - if v.Ref != "" { - ref, err := lookupRef(root, v.Ref) + str, err := generateSchemaRefCopyCode(root, k, v, isPointer, srcName, dstName, namingPrefix) + if err != nil { + return "", err + } + buf.WriteString(str) + } + return buf.String(), nil +} + +func generateSchemaRefCopyCode(root *openapi3.Components, field string, schemaRef *openapi3.SchemaRef, isPointer bool, srcName, dstName, namingPrefix string) (string, error) { + fmt.Println(srcName + "." + field) + ek := exportField(field) + buf := strings.Builder{} + if schemaRef.Ref != "" { + // $ref to another object schema + ref, err := lookupRef(root, schemaRef.Ref) + if err != nil { + return "", err + } + refTypeName := namingPrefix + strings.Join(strings.Split(schemaRef.Ref, "/")[3:], "") + if schemaRef.Value.Type.Is("object") { + if isPointer { + buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n%s.%s = &%s{}\n", srcName, ek, dstName, ek, refTypeName)) + } + str, err := generateSchemaCopyCode(root, ref, fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) if err != nil { return "", err } - refTypeName := namingPrefix + strings.Join(strings.Split(strings.Trim(v.Ref, "#/components/schemas/"), "/"), "") - if v.Value.Type.Is("object") { - if isPointer { - buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n%s.%s = &%s{}\n", srcName, ek, dstName, ek, refTypeName)) - } - str, err := copyProps(root, ref, fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) - if err != nil { - return "", err - } - buf.WriteString(str) - if isPointer { - buf.WriteString("}\n") - } + buf.WriteString(str) + if isPointer { + buf.WriteString("}\n") } - continue } + return buf.String(), nil + } - if v.Value.Type.Is("object") { - if v.Value.AdditionalProperties.Schema == nil { - // map[string]any - // TODO something better - buf.WriteString(fmt.Sprintf("%s.%s = make(map[string]any)\n", dstName, ek)) - buf.WriteString(fmt.Sprintf("for key, val := range %s.%s {\n", srcName, ek)) - buf.WriteString(fmt.Sprintf("%s.%s[key] = val\n}\n", dstName, ek)) - continue + // Not a ref, examine the schema + schema := schemaRef.Value + + if schemaRef.Value.Type.Is("object") { + if schemaRef.Value.AdditionalProperties.Schema == nil { + if len(schema.Properties) > 0 { + // Embedded struct + for key, prop := range schema.Properties { + str, err := generateSchemaRefCopyCode(root, key, prop, !slices.Contains(schema.Required, key), fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) + if err != nil { + return "", err + } + buf.WriteString(str) + } + return buf.String(), nil } - buf.WriteString(fmt.Sprintf("%s.%s = make(map[string]%s)\n", dstName, ek, oapiTypeToGoType(v.Value.AdditionalProperties.Schema, namingPrefix))) + // map[string]any + // TODO something better + buf.WriteString(fmt.Sprintf("%s.%s = make(map[string]any)\n", dstName, ek)) buf.WriteString(fmt.Sprintf("for key, val := range %s.%s {\n", srcName, ek)) - buf.WriteString(fmt.Sprintf("cpyVal := %s{}\n", oapiTypeToGoType(v.Value.AdditionalProperties.Schema, namingPrefix))) - ref, err := lookupRef(root, v.Value.AdditionalProperties.Schema.Ref) - if err != nil { - return "", err - } - copyStr, err := copyProps(root, ref, "val", "cpyVal", namingPrefix) - if err != nil { - return "", err - } - buf.WriteString(fmt.Sprintf("%s\n%s.%s[key] = cpyVal\n}\n", copyStr, dstName, ek)) - continue + buf.WriteString(fmt.Sprintf("%s.%s[key] = val\n}\n", dstName, ek)) + return buf.String(), nil } - if v.Value.Type.Is("array") { - buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) - buf.WriteString(fmt.Sprintf("%s.%s = make([]%s, len(%s.%s))\n", dstName, ek, oapiTypeToGoType(v.Value.Items, namingPrefix), srcName, ek)) - buf.WriteString(fmt.Sprintf("copy(%s.%s, %s.%s)\n", dstName, ek, srcName, ek)) - buf.WriteString("}\n") - continue + buf.WriteString(fmt.Sprintf("%s.%s = make(map[string]%s)\n", dstName, ek, oapiTypeToGoType(schema.AdditionalProperties.Schema, namingPrefix))) + buf.WriteString(fmt.Sprintf("for key, val := range %s.%s {\n", srcName, ek)) + buf.WriteString(fmt.Sprintf("cpyVal := %s{}\n", oapiTypeToGoType(schema.AdditionalProperties.Schema, namingPrefix))) + ref, err := lookupRef(root, schema.AdditionalProperties.Schema.Ref) + if err != nil { + return "", err } - if isPointer { - buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) - buf.WriteString(fmt.Sprintf("%sCopy := *%s.%s\n", k, srcName, ek)) - buf.WriteString(fmt.Sprintf("%s.%s = &%sCopy\n}\n", dstName, ek, k)) - } else { - buf.WriteString(fmt.Sprintf("%s.%s = %s.%s\n", dstName, ek, srcName, ek)) + copyStr, err := copyProps(root, ref, "val", "cpyVal", namingPrefix) + if err != nil { + return "", err } + buf.WriteString(fmt.Sprintf("%s\n%s.%s[key] = cpyVal\n}\n", copyStr, dstName, ek)) + return buf.String(), nil + } + if schema.Type.Is("array") { + buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) + buf.WriteString(fmt.Sprintf("%s.%s = make([]%s, len(%s.%s))\n", dstName, ek, oapiTypeToGoType(schema.Items, namingPrefix), srcName, ek)) + buf.WriteString(fmt.Sprintf("copy(%s.%s, %s.%s)\n", dstName, ek, srcName, ek)) + buf.WriteString("}\n") + return buf.String(), nil + } + if isPointer { + buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) + buf.WriteString(fmt.Sprintf("%sCopy := *%s.%s\n", field, srcName, ek)) + buf.WriteString(fmt.Sprintf("%s.%s = &%sCopy\n}\n", dstName, ek, field)) + return buf.String(), nil } + buf.WriteString(fmt.Sprintf("%s.%s = %s.%s\n", dstName, ek, srcName, ek)) return buf.String(), nil } From 147b5ff237e2467cca5c383d37301cd1ace6319f Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 7 Aug 2024 18:20:27 -0400 Subject: [PATCH 3/6] Some code cleanup. --- codegen/jennies/resourceobject.go | 76 +++++++++++++++++-------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/codegen/jennies/resourceobject.go b/codegen/jennies/resourceobject.go index 2184d2d78..b778b2270 100644 --- a/codegen/jennies/resourceobject.go +++ b/codegen/jennies/resourceobject.go @@ -266,7 +266,6 @@ func generateSchemaCopyCode(root *openapi3.Components, sch *openapi3.Schema, src } func generateSchemaRefCopyCode(root *openapi3.Components, field string, schemaRef *openapi3.SchemaRef, isPointer bool, srcName, dstName, namingPrefix string) (string, error) { - fmt.Println(srcName + "." + field) ek := exportField(field) buf := strings.Builder{} if schemaRef.Ref != "" { @@ -296,38 +295,7 @@ func generateSchemaRefCopyCode(root *openapi3.Components, field string, schemaRe schema := schemaRef.Value if schemaRef.Value.Type.Is("object") { - if schemaRef.Value.AdditionalProperties.Schema == nil { - if len(schema.Properties) > 0 { - // Embedded struct - for key, prop := range schema.Properties { - str, err := generateSchemaRefCopyCode(root, key, prop, !slices.Contains(schema.Required, key), fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) - if err != nil { - return "", err - } - buf.WriteString(str) - } - return buf.String(), nil - } - // map[string]any - // TODO something better - buf.WriteString(fmt.Sprintf("%s.%s = make(map[string]any)\n", dstName, ek)) - buf.WriteString(fmt.Sprintf("for key, val := range %s.%s {\n", srcName, ek)) - buf.WriteString(fmt.Sprintf("%s.%s[key] = val\n}\n", dstName, ek)) - return buf.String(), nil - } - buf.WriteString(fmt.Sprintf("%s.%s = make(map[string]%s)\n", dstName, ek, oapiTypeToGoType(schema.AdditionalProperties.Schema, namingPrefix))) - buf.WriteString(fmt.Sprintf("for key, val := range %s.%s {\n", srcName, ek)) - buf.WriteString(fmt.Sprintf("cpyVal := %s{}\n", oapiTypeToGoType(schema.AdditionalProperties.Schema, namingPrefix))) - ref, err := lookupRef(root, schema.AdditionalProperties.Schema.Ref) - if err != nil { - return "", err - } - copyStr, err := copyProps(root, ref, "val", "cpyVal", namingPrefix) - if err != nil { - return "", err - } - buf.WriteString(fmt.Sprintf("%s\n%s.%s[key] = cpyVal\n}\n", copyStr, dstName, ek)) - return buf.String(), nil + return generateObjectCopyCode(root, schemaRef.Value, fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) } if schema.Type.Is("array") { buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) @@ -346,6 +314,48 @@ func generateSchemaRefCopyCode(root *openapi3.Components, field string, schemaRe return buf.String(), nil } +func generateObjectCopyCode(root *openapi3.Components, schema *openapi3.Schema, srcGoField, dstGoField, namingPrefix string) (string, error) { + if schema.AdditionalProperties.Schema == nil { + // No AdditionalProperties, either an untyped map of embedded struct + if len(schema.Properties) > 0 { + // Embedded struct + buf := strings.Builder{} + for key, prop := range schema.Properties { + str, err := generateSchemaRefCopyCode(root, key, prop, !slices.Contains(schema.Required, key), srcGoField, dstGoField, namingPrefix) + if err != nil { + return "", err + } + buf.WriteString(str) + } + return buf.String(), nil + } + // map[string]any + // TODO something better + buf := strings.Builder{} + buf.WriteString(fmt.Sprintf("%s = make(map[string]any)\n", dstGoField)) + buf.WriteString(fmt.Sprintf("for key, val := range %s {\n", srcGoField)) + buf.WriteString(fmt.Sprintf("%s[key] = val\n}\n", dstGoField)) + return buf.String(), nil + } + + // Object has AdditionalProperties, making it a typed map + // Look up value type from $ref and have it use copy code for that for each value in the map + buf := strings.Builder{} + buf.WriteString(fmt.Sprintf("%s = make(map[string]%s)\n", dstGoField, oapiTypeToGoType(schema.AdditionalProperties.Schema, namingPrefix))) + buf.WriteString(fmt.Sprintf("for key, val := range %s {\n", srcGoField)) + buf.WriteString(fmt.Sprintf("cpyVal := %s{}\n", oapiTypeToGoType(schema.AdditionalProperties.Schema, namingPrefix))) + ref, err := lookupRef(root, schema.AdditionalProperties.Schema.Ref) + if err != nil { + return "", err + } + copyStr, err := generateSchemaCopyCode(root, ref, "val", "cpyVal", namingPrefix) + if err != nil { + return "", err + } + buf.WriteString(fmt.Sprintf("%s\n%s[key] = cpyVal\n}\n", copyStr, dstGoField)) + return buf.String(), nil +} + func oapiTypeToGoType(v *openapi3.SchemaRef, refNamePrefix string) string { if v.Value.Type.Is("integer") { switch v.Value.Format { From 9f34ae73919678a3cd7de463b4977b739939f014 Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Thu, 8 Aug 2024 09:28:42 -0400 Subject: [PATCH 4/6] Handle AnyOf, AllOf, and OneOf. --- codegen/jennies/resourceobject.go | 110 +++++++++++++++++++----------- 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/codegen/jennies/resourceobject.go b/codegen/jennies/resourceobject.go index b778b2270..9063fc785 100644 --- a/codegen/jennies/resourceobject.go +++ b/codegen/jennies/resourceobject.go @@ -173,18 +173,20 @@ func (r *ResourceObjectGenerator) generateObjectFile(kind codegen.Kind, version buf.WriteString(fmt.Sprintf("cpy := &%s{}\n\n// Copy metadata\no.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta)\n\n// Copy Spec\n", md.TypeName)) specCopy, err := generateCopyCodeFor(version, "spec", typePrefix) if err != nil { - return nil, err - } - buf.WriteString(specCopy + "\n") - for _, sr := range md.Subresources { - srCopy, err := generateCopyCodeFor(version, sr.JSONName, typePrefix+"Status") - if err != nil { - return nil, err + // If the generated copy code fails, fall back on the generic behavior + md.CopyCode = "return resource.CopyObject(o)" + } else { + buf.WriteString(specCopy + "\n") + for _, sr := range md.Subresources { + srCopy, err := generateCopyCodeFor(version, sr.JSONName, typePrefix+"Status") + if err != nil { + return nil, err + } + buf.WriteString(fmt.Sprintf("\n\n// Copy %s\n%s\n", sr.TypeName, srCopy)) } - buf.WriteString(fmt.Sprintf("\n\n// Copy %s\n%s\n", sr.TypeName, srCopy)) + buf.WriteString("return cpy") + md.CopyCode = buf.String() } - buf.WriteString("return cpy") - md.CopyCode = buf.String() } b := bytes.Buffer{} err = templates.WriteResourceObject(md, &b) @@ -242,6 +244,7 @@ func generateCopyCodeFor(version *codegen.KindVersion, subresource, namePrefix s if err != nil { return "", err } + fmt.Println(string(yml)) loader := openapi3.NewLoader() oT, err := loader.LoadFromData(yml) @@ -253,10 +256,18 @@ func generateCopyCodeFor(version *codegen.KindVersion, subresource, namePrefix s } func generateSchemaCopyCode(root *openapi3.Components, sch *openapi3.Schema, srcName, dstName, namingPrefix string) (string, error) { + // Sort fields so that the generated code is deterministic + fields := make([]string, 0, len(sch.Properties)) + for k := range sch.Properties { + fields = append(fields, k) + } + slices.Sort(fields) + + // For each field, append the copy code generated for the SchemaRef to the string builder buf := strings.Builder{} - for k, v := range sch.Properties { + for _, k := range fields { isPointer := !slices.Contains(sch.Required, k) - str, err := generateSchemaRefCopyCode(root, k, v, isPointer, srcName, dstName, namingPrefix) + str, err := generateSchemaRefCopyCode(root, k, sch.Properties[k], isPointer, srcName, dstName, namingPrefix) if err != nil { return "", err } @@ -266,29 +277,14 @@ func generateSchemaCopyCode(root *openapi3.Components, sch *openapi3.Schema, src } func generateSchemaRefCopyCode(root *openapi3.Components, field string, schemaRef *openapi3.SchemaRef, isPointer bool, srcName, dstName, namingPrefix string) (string, error) { + // Exported field name to use for the go field names ek := exportField(field) buf := strings.Builder{} + + // If this SchemaRef is a reference to another schema ($ref != ""), we need to look up that schema and + // generate copy code using that schema's definition if schemaRef.Ref != "" { - // $ref to another object schema - ref, err := lookupRef(root, schemaRef.Ref) - if err != nil { - return "", err - } - refTypeName := namingPrefix + strings.Join(strings.Split(schemaRef.Ref, "/")[3:], "") - if schemaRef.Value.Type.Is("object") { - if isPointer { - buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n%s.%s = &%s{}\n", srcName, ek, dstName, ek, refTypeName)) - } - str, err := generateSchemaCopyCode(root, ref, fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) - if err != nil { - return "", err - } - buf.WriteString(str) - if isPointer { - buf.WriteString("}\n") - } - } - return buf.String(), nil + return generateRefObjectCopyCode(root, schemaRef, isPointer, fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) } // Not a ref, examine the schema @@ -314,6 +310,36 @@ func generateSchemaRefCopyCode(root *openapi3.Components, field string, schemaRe return buf.String(), nil } +func generateRefObjectCopyCode(root *openapi3.Components, schemaRef *openapi3.SchemaRef, isPointer bool, srcGoField, dstGoField, namingPrefix string) (string, error) { + ref, err := lookupOpenAPISchemaRef(root, schemaRef.Ref) + if err != nil { + return "", err + } + + // Special cases for oneOf, allOf, etc. as the go codegen turns those into untyped interfaces + if len(ref.AllOf) > 0 || len(ref.AnyOf) > 0 || len(ref.OneOf) > 0 { + return fmt.Sprintf("%s = %s", srcGoField, dstGoField), nil + } + + buf := strings.Builder{} + // Generate the correct go type name from the reference and naming prefix + refTypeName := namingPrefix + strings.Join(strings.Split(schemaRef.Ref, "/")[3:], "") + if schemaRef.Value.Type.Is("object") { + if isPointer { + buf.WriteString(fmt.Sprintf("if %s != nil {\n%s = &%s{}\n", srcGoField, dstGoField, refTypeName)) + } + str, err := generateSchemaCopyCode(root, ref, srcGoField, dstGoField, namingPrefix) + if err != nil { + return "", err + } + buf.WriteString(str) + if isPointer { + buf.WriteString("}\n") + } + } + return buf.String(), nil +} + func generateObjectCopyCode(root *openapi3.Components, schema *openapi3.Schema, srcGoField, dstGoField, namingPrefix string) (string, error) { if schema.AdditionalProperties.Schema == nil { // No AdditionalProperties, either an untyped map of embedded struct @@ -330,7 +356,7 @@ func generateObjectCopyCode(root *openapi3.Components, schema *openapi3.Schema, return buf.String(), nil } // map[string]any - // TODO something better + // TODO something better? buf := strings.Builder{} buf.WriteString(fmt.Sprintf("%s = make(map[string]any)\n", dstGoField)) buf.WriteString(fmt.Sprintf("for key, val := range %s {\n", srcGoField)) @@ -344,7 +370,7 @@ func generateObjectCopyCode(root *openapi3.Components, schema *openapi3.Schema, buf.WriteString(fmt.Sprintf("%s = make(map[string]%s)\n", dstGoField, oapiTypeToGoType(schema.AdditionalProperties.Schema, namingPrefix))) buf.WriteString(fmt.Sprintf("for key, val := range %s {\n", srcGoField)) buf.WriteString(fmt.Sprintf("cpyVal := %s{}\n", oapiTypeToGoType(schema.AdditionalProperties.Schema, namingPrefix))) - ref, err := lookupRef(root, schema.AdditionalProperties.Schema.Ref) + ref, err := lookupOpenAPISchemaRef(root, schema.AdditionalProperties.Schema.Ref) if err != nil { return "", err } @@ -356,6 +382,8 @@ func generateObjectCopyCode(root *openapi3.Components, schema *openapi3.Schema, return buf.String(), nil } +// oapiTypeToGoType returns the go type based on the provided OpenAPI type. +// For object reference types, the go type is assumed to be + func oapiTypeToGoType(v *openapi3.SchemaRef, refNamePrefix string) string { if v.Value.Type.Is("integer") { switch v.Value.Format { @@ -394,7 +422,10 @@ func oapiTypeToGoType(v *openapi3.SchemaRef, refNamePrefix string) string { return "any" } -func lookupRef(root *openapi3.Components, ref string) (*openapi3.Schema, error) { +// lookupOpenAPISchemaRef looks up and returns a Schema by its $ref path from the root openapi3.Components +// $ref paths that don't begin with '#/components/schemas' are not supported by this lookup method. +// If no schema can be found, an error is returned. +func lookupOpenAPISchemaRef(root *openapi3.Components, ref string) (*openapi3.Schema, error) { parts := strings.Split(ref, "/") if len(parts) < 3 || strings.Join(parts[:3], "/") != "#/components/schemas" { return nil, fmt.Errorf("only references to #/components/schemas are supported") @@ -402,7 +433,7 @@ func lookupRef(root *openapi3.Components, ref string) (*openapi3.Schema, error) for k, v := range root.Schemas { if k == parts[3] { if len(parts) > 4 { - return lookupRefInSchema(v.Value.Properties, strings.Join(parts[3:], "/")) + return lookupOpenAPISchemaRefInSchema(v.Value.Properties, strings.Join(parts[3:], "/")) } return v.Value, nil } @@ -410,12 +441,15 @@ func lookupRef(root *openapi3.Components, ref string) (*openapi3.Schema, error) return nil, fmt.Errorf("reference %s not found", ref) } -func lookupRefInSchema(sch openapi3.Schemas, ref string) (*openapi3.Schema, error) { +// lookupOpenAPISchemaRefInSchema looks up and returns a Schema based on the $ref path provided, +// assuming the ref path is local to the provided schema. +// If no schema can be found, an error is returned. +func lookupOpenAPISchemaRefInSchema(sch openapi3.Schemas, ref string) (*openapi3.Schema, error) { parts := strings.Split(ref, "/") for k, v := range sch { if k == parts[0] { if len(parts) > 1 { - return lookupRefInSchema(v.Value.Properties, strings.Join(parts[1:], "/")) + return lookupOpenAPISchemaRefInSchema(v.Value.Properties, strings.Join(parts[1:], "/")) } return v.Value, nil } From 6816d2bd1545c6efb45ce945654d0b6aa89d62b8 Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Thu, 8 Aug 2024 10:05:40 -0400 Subject: [PATCH 5/6] Changes for different kind groupings, drive-by fix to GroupByKind=false for GoTypes generation. --- codegen/jennies/gotypes.go | 2 +- codegen/jennies/resourceobject.go | 35 ++++++++++++++++++--------- codegen/templates/resourceobject.tmpl | 8 +++--- codegen/templates/templates.go | 7 +++--- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/codegen/jennies/gotypes.go b/codegen/jennies/gotypes.go index 3f90cd4ee..fec29190d 100644 --- a/codegen/jennies/gotypes.go +++ b/codegen/jennies/gotypes.go @@ -192,7 +192,7 @@ func GoTypesFromCUE(v cue.Value, cfg CUEGoConfig, maxNamingDepth int) ([]byte, e if i > 0 { path = cue.MakePath(path.Selectors()[i:]...) } - return cfg.NamePrefix + strings.Trim(path.String(), "?#") + return cfg.NamePrefix + exportField(strings.Trim(path.String(), "?#")) }, } diff --git a/codegen/jennies/resourceobject.go b/codegen/jennies/resourceobject.go index 9063fc785..b0fa22f36 100644 --- a/codegen/jennies/resourceobject.go +++ b/codegen/jennies/resourceobject.go @@ -163,8 +163,9 @@ func (r *ResourceObjectGenerator) generateObjectFile(kind codegen.Kind, version continue } md.Subresources = append(md.Subresources, templates.SubresourceMetadata{ - TypeName: typePrefix + exportField(it.Label()), - JSONName: it.Label(), + TypeName: typePrefix + exportField(it.Label()), + FieldName: exportField(it.Label()), + JSONName: it.Label(), }) } if !r.GenericCopy { @@ -178,11 +179,11 @@ func (r *ResourceObjectGenerator) generateObjectFile(kind codegen.Kind, version } else { buf.WriteString(specCopy + "\n") for _, sr := range md.Subresources { - srCopy, err := generateCopyCodeFor(version, sr.JSONName, typePrefix+"Status") + srCopy, err := generateCopyCodeFor(version, sr.JSONName, sr.TypeName) if err != nil { return nil, err } - buf.WriteString(fmt.Sprintf("\n\n// Copy %s\n%s\n", sr.TypeName, srCopy)) + buf.WriteString(fmt.Sprintf("\n\n// Copy %s\n%s\n", sr.FieldName, srCopy)) } buf.WriteString("return cpy") md.CopyCode = buf.String() @@ -236,7 +237,7 @@ func generateCopyCodeFor(version *codegen.KindVersion, subresource, namePrefix s if i > 0 { path = cue.MakePath(path.Selectors()[i:]...) } - return strings.Trim(path.String(), "?#") + return namePrefix + exportField(strings.Trim(path.String(), "?#")) }, } @@ -244,7 +245,6 @@ func generateCopyCodeFor(version *codegen.KindVersion, subresource, namePrefix s if err != nil { return "", err } - fmt.Println(string(yml)) loader := openapi3.NewLoader() oT, err := loader.LoadFromData(yml) @@ -252,7 +252,7 @@ func generateCopyCodeFor(version *codegen.KindVersion, subresource, namePrefix s return "", err } - return generateSchemaCopyCode(oT.Components, oT.Components.Schemas[subresource].Value, fmt.Sprintf("o.%s", exportField(subresource)), fmt.Sprintf("cpy.%s", exportField(subresource)), namePrefix) + return generateSchemaCopyCode(oT.Components, oT.Components.Schemas[subresource].Value, fmt.Sprintf("o.%s", exportField(subresource)), fmt.Sprintf("cpy.%s", exportField(subresource)), "") } func generateSchemaCopyCode(root *openapi3.Components, sch *openapi3.Schema, srcName, dstName, namingPrefix string) (string, error) { @@ -279,7 +279,6 @@ func generateSchemaCopyCode(root *openapi3.Components, sch *openapi3.Schema, src func generateSchemaRefCopyCode(root *openapi3.Components, field string, schemaRef *openapi3.SchemaRef, isPointer bool, srcName, dstName, namingPrefix string) (string, error) { // Exported field name to use for the go field names ek := exportField(field) - buf := strings.Builder{} // If this SchemaRef is a reference to another schema ($ref != ""), we need to look up that schema and // generate copy code using that schema's definition @@ -289,25 +288,37 @@ func generateSchemaRefCopyCode(root *openapi3.Components, field string, schemaRe // Not a ref, examine the schema schema := schemaRef.Value + if schema == nil { + // No $ref and no schema, this is a malformed SchemaRef object + return "", fmt.Errorf("encountered openapi3.SchemaRef with no $ref or schema: %s", schemaRef.RefPath()) + } - if schemaRef.Value.Type.Is("object") { + // Non-ref object type (this is a map or an inline struct) + if schema.Type.Is("object") { return generateObjectCopyCode(root, schemaRef.Value, fmt.Sprintf("%s.%s", srcName, ek), fmt.Sprintf("%s.%s", dstName, ek), namingPrefix) } + + // Array type gets converted into a slice, which we copy the values from with the go builtin copy() function if schema.Type.Is("array") { + buf := strings.Builder{} buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) buf.WriteString(fmt.Sprintf("%s.%s = make([]%s, len(%s.%s))\n", dstName, ek, oapiTypeToGoType(schema.Items, namingPrefix), srcName, ek)) buf.WriteString(fmt.Sprintf("copy(%s.%s, %s.%s)\n", dstName, ek, srcName, ek)) buf.WriteString("}\n") return buf.String(), nil } + + // Pointer to a standard type, we need to indirect it to get the value, then create a pointer to that value if isPointer { + buf := strings.Builder{} buf.WriteString(fmt.Sprintf("if %s.%s != nil {\n", srcName, ek)) buf.WriteString(fmt.Sprintf("%sCopy := *%s.%s\n", field, srcName, ek)) buf.WriteString(fmt.Sprintf("%s.%s = &%sCopy\n}\n", dstName, ek, field)) return buf.String(), nil } - buf.WriteString(fmt.Sprintf("%s.%s = %s.%s\n", dstName, ek, srcName, ek)) - return buf.String(), nil + + // Just a normal value field which we can copy with an assignment + return fmt.Sprintf("%s.%s = %s.%s\n", dstName, ek, srcName, ek), nil } func generateRefObjectCopyCode(root *openapi3.Components, schemaRef *openapi3.SchemaRef, isPointer bool, srcGoField, dstGoField, namingPrefix string) (string, error) { @@ -342,7 +353,7 @@ func generateRefObjectCopyCode(root *openapi3.Components, schemaRef *openapi3.Sc func generateObjectCopyCode(root *openapi3.Components, schema *openapi3.Schema, srcGoField, dstGoField, namingPrefix string) (string, error) { if schema.AdditionalProperties.Schema == nil { - // No AdditionalProperties, either an untyped map of embedded struct + // No AdditionalProperties, either an untyped map or embedded struct if len(schema.Properties) > 0 { // Embedded struct buf := strings.Builder{} diff --git a/codegen/templates/resourceobject.tmpl b/codegen/templates/resourceobject.tmpl index 43761af17..302753865 100644 --- a/codegen/templates/resourceobject.tmpl +++ b/codegen/templates/resourceobject.tmpl @@ -20,7 +20,7 @@ type {{.TypeName}} struct { metav1.ObjectMeta `json:"metadata"` Spec {{.SpecTypeName}} `json:"spec"`{{ range .Subresources }}{{ if ne .Comment "" }} // {{.Comment }}{{end}} - {{ .TypeName }} {{.TypeName}} `json:"{{.JSONName}}"`{{ end }} + {{ .FieldName }} {{.TypeName}} `json:"{{.JSONName}}"`{{ end }} } func ({{.ObjectShortName}} *{{.TypeName}}) GetSpec() any { @@ -38,14 +38,14 @@ func ({{.ObjectShortName}} *{{.TypeName}}) SetSpec(spec any) error { func ({{.ObjectShortName}} *{{.TypeName}}) GetSubresources() map[string]any { return map[string]any{ {{ range .Subresources }} - "{{.JSONName}}": {{$root.ObjectShortName}}.{{.TypeName}}, + "{{.JSONName}}": {{$root.ObjectShortName}}.{{.FieldName}}, {{ end }} } } func ({{.ObjectShortName}} *{{.TypeName}}) GetSubresource(name string) (any,bool) { switch name { {{ range .Subresources }} case"{{ .JSONName }}": - return {{$root.ObjectShortName}}.{{.TypeName}}, true + return {{$root.ObjectShortName}}.{{.FieldName}}, true {{ end }}default: return nil, false } @@ -58,7 +58,7 @@ func ({{.ObjectShortName}} *{{.TypeName}}) SetSubresource(name string, value any if !ok { return fmt.Errorf("cannot set {{.JSONName}} type %#v, not of type {{.TypeName}}", value) } - {{$root.ObjectShortName}}.{{.TypeName}} = cast + {{$root.ObjectShortName}}.{{.FieldName}} = cast return nil {{ end }}default: return fmt.Errorf("subresource '%s' does not exist", name) diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 18f81d029..d978d2bac 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -85,9 +85,10 @@ type ResourceObjectTemplateMetadata struct { // SubresourceMetadata is subresource information used in templates type SubresourceMetadata struct { - TypeName string - JSONName string - Comment string + TypeName string + FieldName string + JSONName string + Comment string } // WriteResourceObject executes the Resource Object template, and writes out the generated go code to out From 28bfeef71e1bffc5e5a4d1dc862d9041b093178a Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Thu, 8 Aug 2024 15:31:38 -0400 Subject: [PATCH 6/6] Add flag to toggle generating a copy method, or using resource.Copy. Update test golden files with generated copy code. --- cmd/grafana-app-sdk/generate.go | 10 ++- codegen/cuekind/generators.go | 3 +- codegen/cuekind/generators_test.go | 8 +-- .../custom/v0_0/customkind_object_gen.go.txt | 41 +++++++++-- .../custom/v0_0/customkind_status_gen.go.txt | 20 +++--- .../custom/v1_0/customkind_object_gen.go.txt | 68 +++++++++++++++++-- .../custom/v1_0/customkind_status_gen.go.txt | 20 +++--- .../test/v1/testkind2_object_gen.go.txt | 40 +++++++++-- .../test/v1/testkind2_status_gen.go.txt | 20 +++--- .../test/v1/testkind_object_gen.go.txt | 40 +++++++++-- .../test/v1/testkind_status_gen.go.txt | 20 +++--- .../test/v2/testkind_object_gen.go.txt | 42 ++++++++++-- .../test/v2/testkind_status_gen.go.txt | 20 +++--- .../v0_0/customkind_object_gen.go.txt | 33 ++++++++- .../v0_0/customkind_status_gen.go.txt | 2 +- .../v1_0/customkind_object_gen.go.txt | 60 +++++++++++++++- .../v1_0/customkind_status_gen.go.txt | 2 +- .../customkind/customkind_status_gen.go.txt | 2 +- codegen/thema/generators.go | 1 + 19 files changed, 366 insertions(+), 86 deletions(-) diff --git a/cmd/grafana-app-sdk/generate.go b/cmd/grafana-app-sdk/generate.go index bd7b93253..16f737a25 100644 --- a/cmd/grafana-app-sdk/generate.go +++ b/cmd/grafana-app-sdk/generate.go @@ -46,6 +46,8 @@ Definitions will be created. Only applicable if type=kubernetes`) Allowed values are 'group' and 'kind'. Dictates the packaging of go kinds, where 'group' places all kinds with the same group in the same package, and 'kind' creates separate packages per kind (packaging will always end with the version)`) generateCmd.Flags().Bool("postprocess", false, "Whether to run post-processing on the generated files after they are written to disk. Post-processing includes code generation based on +k8s comments on types. Post-processing will fail if the dependencies required by the generated code are absent from go.mod.") generateCmd.Flags().Lookup("postprocess").NoOptDefVal = "true" + generateCmd.Flags().Bool("simplecopy", false, "Governs whether the generated go resource.Object implementations use a generated deep copy method, or the reflection-based resource.CopyObject. Set this to true if you are having problems with the generated deep copy code.") + generateCmd.Flags().Lookup("simplecopy").NoOptDefVal = "true" // Don't show "usage" information when an error is returned form the command, // because our errors are not command-usage-based @@ -105,6 +107,10 @@ func generateCmdFunc(cmd *cobra.Command, _ []string) error { if err != nil { return err } + simpleCopy, err := cmd.Flags().GetBool("simplecopy") + if err != nil { + return err + } var files codejen.Files switch format { @@ -127,6 +133,7 @@ func generateCmdFunc(cmd *cobra.Command, _ []string) error { CRDEncoding: encType, CRDPath: crdPath, GroupKinds: grouping == kindGroupingGroup, + GenericCopy: simpleCopy, }, selectors...) if err != nil { return err @@ -175,6 +182,7 @@ type kindGenConfig struct { CRDEncoding string CRDPath string GroupKinds bool + GenericCopy bool } func generateKindsThema(modFS fs.FS, cfg kindGenConfig, selectors ...string) (codejen.Files, error) { @@ -300,7 +308,7 @@ func generateKindsCue(modFS fs.FS, cfg kindGenConfig, selectors ...string) (code return nil, err } // Resource - resourceFiles, err := generator.FilteredGenerate(cuekind.ResourceGenerator(true, cfg.GroupKinds), func(kind codegen.Kind) bool { + resourceFiles, err := generator.FilteredGenerate(cuekind.ResourceGenerator(true, cfg.GroupKinds, cfg.GenericCopy), func(kind codegen.Kind) bool { return kind.Properties().APIResource != nil }, selectors...) if err != nil { diff --git a/codegen/cuekind/generators.go b/codegen/cuekind/generators.go index f53bfad97..ed9432a69 100644 --- a/codegen/cuekind/generators.go +++ b/codegen/cuekind/generators.go @@ -23,7 +23,7 @@ func CRDGenerator(outputEncoder jennies.CRDOutputEncoder, outputExtension string // If `groupKinds` is true, kinds within the same group will exist in the same package. // When combined with `versioned`, each version package will contain all kinds in the group // which have a schema for that version. -func ResourceGenerator(versioned bool, groupKinds bool) *codejen.JennyList[codegen.Kind] { +func ResourceGenerator(versioned bool, groupKinds bool, genericCopy bool) *codejen.JennyList[codegen.Kind] { g := codejen.JennyListWithNamer(namerFunc) g.Append( &jennies.GoTypes{ @@ -36,6 +36,7 @@ func ResourceGenerator(versioned bool, groupKinds bool) *codejen.JennyList[codeg OnlyUseCurrentVersion: !versioned, SubresourceTypesArePrefixed: groupKinds, GroupByKind: !groupKinds, + GenericCopy: genericCopy, }, &jennies.SchemaGenerator{ OnlyUseCurrentVersion: !versioned, diff --git a/codegen/cuekind/generators_test.go b/codegen/cuekind/generators_test.go index 35287437f..b867725d9 100644 --- a/codegen/cuekind/generators_test.go +++ b/codegen/cuekind/generators_test.go @@ -58,7 +58,7 @@ func TestResourceGenerator(t *testing.T) { require.Nil(t, err) t.Run("unversioned", func(t *testing.T) { - files, err := ResourceGenerator(false, false).Generate(kinds...) + files, err := ResourceGenerator(false, false, true).Generate(kinds...) require.Nil(t, err) // Check number of files generated // 6 -> object, spec, metadata, status, schema, codec @@ -68,7 +68,7 @@ func TestResourceGenerator(t *testing.T) { }) t.Run("group by kind", func(t *testing.T) { - files, err := ResourceGenerator(true, false).Generate(kinds...) + files, err := ResourceGenerator(true, false, false).Generate(kinds...) require.Nil(t, err) // Check number of files generated // 12 (6 -> object, spec, metadata, status, schema, codec) * 2 versions @@ -78,7 +78,7 @@ func TestResourceGenerator(t *testing.T) { }) t.Run("group by group", func(t *testing.T) { - files, err := ResourceGenerator(true, true).Generate(kinds...) + files, err := ResourceGenerator(true, true, false).Generate(kinds...) require.Nil(t, err) // Check number of files generated // 12 (6 -> object, spec, metadata, status, schema, codec) * 2 versions @@ -88,7 +88,7 @@ func TestResourceGenerator(t *testing.T) { }) t.Run("group by group, multiple kinds", func(t *testing.T) { - files, err := ResourceGenerator(true, true).Generate(sameGroupKinds...) + files, err := ResourceGenerator(true, true, false).Generate(sameGroupKinds...) require.Nil(t, err) // Check number of files generated assert.Len(t, files, 18) diff --git a/codegen/testing/golden_generated/go/groupbygroup/custom/v0_0/customkind_object_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/custom/v0_0/customkind_object_gen.go.txt index faebb43c2..4fe2913e1 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/custom/v0_0/customkind_object_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/custom/v0_0/customkind_object_gen.go.txt @@ -19,7 +19,7 @@ type CustomKind struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` Spec CustomKindSpec `json:"spec"` - CustomKindStatus CustomKindStatus `json:"status"` + Status CustomKindStatus `json:"status"` } func (o *CustomKind) GetSpec() any { @@ -37,14 +37,14 @@ func (o *CustomKind) SetSpec(spec any) error { func (o *CustomKind) GetSubresources() map[string]any { return map[string]any{ - "status": o.CustomKindStatus, + "status": o.Status, } } func (o *CustomKind) GetSubresource(name string) (any, bool) { switch name { case "status": - return o.CustomKindStatus, true + return o.Status, true default: return nil, false } @@ -57,7 +57,7 @@ func (o *CustomKind) SetSubresource(name string, value any) error { if !ok { return fmt.Errorf("cannot set status type %#v, not of type CustomKindStatus", value) } - o.CustomKindStatus = cast + o.Status = cast return nil default: return fmt.Errorf("subresource '%s' does not exist", name) @@ -211,7 +211,38 @@ func (o *CustomKind) SetUpdatedBy(updatedBy string) { } func (o *CustomKind) Copy() resource.Object { - return resource.CopyObject(o) + cpy := &CustomKind{} + + // Copy metadata + o.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta) + + // Copy Spec + cpy.Spec.DeprecatedField = o.Spec.DeprecatedField + cpy.Spec.Field1 = o.Spec.Field1 + + // Copy Status + cpy.Status.AdditionalFields = make(map[string]any) + for key, val := range o.Status.AdditionalFields { + cpy.Status.AdditionalFields[key] = val + } + cpy.Status.OperatorStates = make(map[string]CustomKindStatusOperatorState) + for key, val := range o.Status.OperatorStates { + cpyVal := CustomKindStatusOperatorState{} + if val.DescriptiveState != nil { + descriptiveStateCopy := *val.DescriptiveState + cpyVal.DescriptiveState = &descriptiveStateCopy + } + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.LastEvaluation = val.LastEvaluation + cpyVal.State = val.State + + cpy.Status.OperatorStates[key] = cpyVal + } + + return cpy } func (o *CustomKind) DeepCopyObject() runtime.Object { diff --git a/codegen/testing/golden_generated/go/groupbygroup/custom/v0_0/customkind_status_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/custom/v0_0/customkind_status_gen.go.txt index 34deb3493..0961f2ebf 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/custom/v0_0/customkind_status_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/custom/v0_0/customkind_status_gen.go.txt @@ -7,11 +7,11 @@ const ( CustomKindOperatorStateStateSuccess CustomKindOperatorStateState = "success" ) -// Defines values for CustomKindstatusOperatorStateState. +// Defines values for CustomKindStatusOperatorStateState. const ( - CustomKindstatusOperatorStateStateFailed CustomKindstatusOperatorStateState = "failed" - CustomKindstatusOperatorStateStateInProgress CustomKindstatusOperatorStateState = "in_progress" - CustomKindstatusOperatorStateStateSuccess CustomKindstatusOperatorStateState = "success" + CustomKindStatusOperatorStateStateFailed CustomKindStatusOperatorStateState = "failed" + CustomKindStatusOperatorStateStateInProgress CustomKindStatusOperatorStateState = "in_progress" + CustomKindStatusOperatorStateStateSuccess CustomKindStatusOperatorStateState = "success" ) // CustomKindOperatorState defines model for CustomKindOperatorState. @@ -44,12 +44,12 @@ type CustomKindStatus struct { // operatorStates is a map of operator ID to operator state evaluations. // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]CustomKindstatusOperatorState `json:"operatorStates,omitempty"` + OperatorStates map[string]CustomKindStatusOperatorState `json:"operatorStates,omitempty"` } -// CustomKindstatusOperatorState defines model for CustomKindstatus.#OperatorState. +// CustomKindStatusOperatorState defines model for CustomKindStatus.#OperatorState. // +k8s:openapi-gen=true -type CustomKindstatusOperatorState struct { +type CustomKindStatusOperatorState struct { // descriptiveState is an optional more descriptive state field which has no requirements on format DescriptiveState *string `json:"descriptiveState,omitempty"` @@ -61,10 +61,10 @@ type CustomKindstatusOperatorState struct { // state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. - State CustomKindstatusOperatorStateState `json:"state"` + State CustomKindStatusOperatorStateState `json:"state"` } -// CustomKindstatusOperatorStateState state describes the state of the lastEvaluation. +// CustomKindStatusOperatorStateState state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. // +k8s:openapi-gen=true -type CustomKindstatusOperatorStateState string +type CustomKindStatusOperatorStateState string diff --git a/codegen/testing/golden_generated/go/groupbygroup/custom/v1_0/customkind_object_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/custom/v1_0/customkind_object_gen.go.txt index ecc79f473..fdc87bc3a 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/custom/v1_0/customkind_object_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/custom/v1_0/customkind_object_gen.go.txt @@ -19,7 +19,7 @@ type CustomKind struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` Spec CustomKindSpec `json:"spec"` - CustomKindStatus CustomKindStatus `json:"status"` + Status CustomKindStatus `json:"status"` } func (o *CustomKind) GetSpec() any { @@ -37,14 +37,14 @@ func (o *CustomKind) SetSpec(spec any) error { func (o *CustomKind) GetSubresources() map[string]any { return map[string]any{ - "status": o.CustomKindStatus, + "status": o.Status, } } func (o *CustomKind) GetSubresource(name string) (any, bool) { switch name { case "status": - return o.CustomKindStatus, true + return o.Status, true default: return nil, false } @@ -57,7 +57,7 @@ func (o *CustomKind) SetSubresource(name string, value any) error { if !ok { return fmt.Errorf("cannot set status type %#v, not of type CustomKindStatus", value) } - o.CustomKindStatus = cast + o.Status = cast return nil default: return fmt.Errorf("subresource '%s' does not exist", name) @@ -243,7 +243,65 @@ func (o *CustomKind) SetUpdatedBy(updatedBy string) { } func (o *CustomKind) Copy() resource.Object { - return resource.CopyObject(o) + cpy := &CustomKind{} + + // Copy metadata + o.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta) + + // Copy Spec + cpy.Spec.BoolField = o.Spec.BoolField + cpy.Spec.Enum = o.Spec.Enum + cpy.Spec.Field1 = o.Spec.Field1 + cpy.Spec.FloatField = o.Spec.FloatField + cpy.Spec.I32 = o.Spec.I32 + cpy.Spec.I64 = o.Spec.I64 + cpy.Spec.Inner.InnerField1 = o.Spec.Inner.InnerField1 + if o.Spec.Inner.InnerField2 != nil { + cpy.Spec.Inner.InnerField2 = make([]string, len(o.Spec.Inner.InnerField2)) + copy(cpy.Spec.Inner.InnerField2, o.Spec.Inner.InnerField2) + } + if o.Spec.Inner.InnerField3 != nil { + cpy.Spec.Inner.InnerField3 = make([]CustomKindInnerObject2, len(o.Spec.Inner.InnerField3)) + copy(cpy.Spec.Inner.InnerField3, o.Spec.Inner.InnerField3) + } + cpy.Spec.Map = make(map[string]CustomKindType2) + for key, val := range o.Spec.Map { + cpyVal := CustomKindType2{} + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.Group = val.Group + + cpy.Spec.Map[key] = cpyVal + } + cpy.Spec.Timestamp = o.Spec.Timestamp + o.Spec.Union = cpy.Spec.Union + + // Copy Status + cpy.Status.AdditionalFields = make(map[string]any) + for key, val := range o.Status.AdditionalFields { + cpy.Status.AdditionalFields[key] = val + } + cpy.Status.OperatorStates = make(map[string]CustomKindStatusOperatorState) + for key, val := range o.Status.OperatorStates { + cpyVal := CustomKindStatusOperatorState{} + if val.DescriptiveState != nil { + descriptiveStateCopy := *val.DescriptiveState + cpyVal.DescriptiveState = &descriptiveStateCopy + } + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.LastEvaluation = val.LastEvaluation + cpyVal.State = val.State + + cpy.Status.OperatorStates[key] = cpyVal + } + cpy.Status.StatusField1 = o.Status.StatusField1 + + return cpy } func (o *CustomKind) DeepCopyObject() runtime.Object { diff --git a/codegen/testing/golden_generated/go/groupbygroup/custom/v1_0/customkind_status_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/custom/v1_0/customkind_status_gen.go.txt index 0d4bec6ab..0d42dd6fa 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/custom/v1_0/customkind_status_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/custom/v1_0/customkind_status_gen.go.txt @@ -7,11 +7,11 @@ const ( CustomKindOperatorStateStateSuccess CustomKindOperatorStateState = "success" ) -// Defines values for CustomKindstatusOperatorStateState. +// Defines values for CustomKindStatusOperatorStateState. const ( - CustomKindstatusOperatorStateStateFailed CustomKindstatusOperatorStateState = "failed" - CustomKindstatusOperatorStateStateInProgress CustomKindstatusOperatorStateState = "in_progress" - CustomKindstatusOperatorStateStateSuccess CustomKindstatusOperatorStateState = "success" + CustomKindStatusOperatorStateStateFailed CustomKindStatusOperatorStateState = "failed" + CustomKindStatusOperatorStateStateInProgress CustomKindStatusOperatorStateState = "in_progress" + CustomKindStatusOperatorStateStateSuccess CustomKindStatusOperatorStateState = "success" ) // CustomKindOperatorState defines model for CustomKindOperatorState. @@ -44,13 +44,13 @@ type CustomKindStatus struct { // operatorStates is a map of operator ID to operator state evaluations. // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]CustomKindstatusOperatorState `json:"operatorStates,omitempty"` + OperatorStates map[string]CustomKindStatusOperatorState `json:"operatorStates,omitempty"` StatusField1 string `json:"statusField1"` } -// CustomKindstatusOperatorState defines model for CustomKindstatus.#OperatorState. +// CustomKindStatusOperatorState defines model for CustomKindStatus.#OperatorState. // +k8s:openapi-gen=true -type CustomKindstatusOperatorState struct { +type CustomKindStatusOperatorState struct { // descriptiveState is an optional more descriptive state field which has no requirements on format DescriptiveState *string `json:"descriptiveState,omitempty"` @@ -62,10 +62,10 @@ type CustomKindstatusOperatorState struct { // state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. - State CustomKindstatusOperatorStateState `json:"state"` + State CustomKindStatusOperatorStateState `json:"state"` } -// CustomKindstatusOperatorStateState state describes the state of the lastEvaluation. +// CustomKindStatusOperatorStateState state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. // +k8s:openapi-gen=true -type CustomKindstatusOperatorStateState string +type CustomKindStatusOperatorStateState string diff --git a/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind2_object_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind2_object_gen.go.txt index 6d6dff8a9..45c687cd3 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind2_object_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind2_object_gen.go.txt @@ -19,7 +19,7 @@ type TestKind2 struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` Spec TestKind2Spec `json:"spec"` - TestKind2Status TestKind2Status `json:"status"` + Status TestKind2Status `json:"status"` } func (o *TestKind2) GetSpec() any { @@ -37,14 +37,14 @@ func (o *TestKind2) SetSpec(spec any) error { func (o *TestKind2) GetSubresources() map[string]any { return map[string]any{ - "status": o.TestKind2Status, + "status": o.Status, } } func (o *TestKind2) GetSubresource(name string) (any, bool) { switch name { case "status": - return o.TestKind2Status, true + return o.Status, true default: return nil, false } @@ -57,7 +57,7 @@ func (o *TestKind2) SetSubresource(name string, value any) error { if !ok { return fmt.Errorf("cannot set status type %#v, not of type TestKind2Status", value) } - o.TestKind2Status = cast + o.Status = cast return nil default: return fmt.Errorf("subresource '%s' does not exist", name) @@ -211,7 +211,37 @@ func (o *TestKind2) SetUpdatedBy(updatedBy string) { } func (o *TestKind2) Copy() resource.Object { - return resource.CopyObject(o) + cpy := &TestKind2{} + + // Copy metadata + o.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta) + + // Copy Spec + cpy.Spec.TestField = o.Spec.TestField + + // Copy Status + cpy.Status.AdditionalFields = make(map[string]any) + for key, val := range o.Status.AdditionalFields { + cpy.Status.AdditionalFields[key] = val + } + cpy.Status.OperatorStates = make(map[string]TestKind2StatusOperatorState) + for key, val := range o.Status.OperatorStates { + cpyVal := TestKind2StatusOperatorState{} + if val.DescriptiveState != nil { + descriptiveStateCopy := *val.DescriptiveState + cpyVal.DescriptiveState = &descriptiveStateCopy + } + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.LastEvaluation = val.LastEvaluation + cpyVal.State = val.State + + cpy.Status.OperatorStates[key] = cpyVal + } + + return cpy } func (o *TestKind2) DeepCopyObject() runtime.Object { diff --git a/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind2_status_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind2_status_gen.go.txt index b46b4285d..06a8172f7 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind2_status_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind2_status_gen.go.txt @@ -7,11 +7,11 @@ const ( TestKind2OperatorStateStateSuccess TestKind2OperatorStateState = "success" ) -// Defines values for TestKind2statusOperatorStateState. +// Defines values for TestKind2StatusOperatorStateState. const ( - TestKind2statusOperatorStateStateFailed TestKind2statusOperatorStateState = "failed" - TestKind2statusOperatorStateStateInProgress TestKind2statusOperatorStateState = "in_progress" - TestKind2statusOperatorStateStateSuccess TestKind2statusOperatorStateState = "success" + TestKind2StatusOperatorStateStateFailed TestKind2StatusOperatorStateState = "failed" + TestKind2StatusOperatorStateStateInProgress TestKind2StatusOperatorStateState = "in_progress" + TestKind2StatusOperatorStateStateSuccess TestKind2StatusOperatorStateState = "success" ) // TestKind2OperatorState defines model for TestKind2OperatorState. @@ -44,12 +44,12 @@ type TestKind2Status struct { // operatorStates is a map of operator ID to operator state evaluations. // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]TestKind2statusOperatorState `json:"operatorStates,omitempty"` + OperatorStates map[string]TestKind2StatusOperatorState `json:"operatorStates,omitempty"` } -// TestKind2statusOperatorState defines model for TestKind2status.#OperatorState. +// TestKind2StatusOperatorState defines model for TestKind2Status.#OperatorState. // +k8s:openapi-gen=true -type TestKind2statusOperatorState struct { +type TestKind2StatusOperatorState struct { // descriptiveState is an optional more descriptive state field which has no requirements on format DescriptiveState *string `json:"descriptiveState,omitempty"` @@ -61,10 +61,10 @@ type TestKind2statusOperatorState struct { // state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. - State TestKind2statusOperatorStateState `json:"state"` + State TestKind2StatusOperatorStateState `json:"state"` } -// TestKind2statusOperatorStateState state describes the state of the lastEvaluation. +// TestKind2StatusOperatorStateState state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. // +k8s:openapi-gen=true -type TestKind2statusOperatorStateState string +type TestKind2StatusOperatorStateState string diff --git a/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind_object_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind_object_gen.go.txt index b8f86de99..5800b8022 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind_object_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind_object_gen.go.txt @@ -19,7 +19,7 @@ type TestKind struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` Spec TestKindSpec `json:"spec"` - TestKindStatus TestKindStatus `json:"status"` + Status TestKindStatus `json:"status"` } func (o *TestKind) GetSpec() any { @@ -37,14 +37,14 @@ func (o *TestKind) SetSpec(spec any) error { func (o *TestKind) GetSubresources() map[string]any { return map[string]any{ - "status": o.TestKindStatus, + "status": o.Status, } } func (o *TestKind) GetSubresource(name string) (any, bool) { switch name { case "status": - return o.TestKindStatus, true + return o.Status, true default: return nil, false } @@ -57,7 +57,7 @@ func (o *TestKind) SetSubresource(name string, value any) error { if !ok { return fmt.Errorf("cannot set status type %#v, not of type TestKindStatus", value) } - o.TestKindStatus = cast + o.Status = cast return nil default: return fmt.Errorf("subresource '%s' does not exist", name) @@ -211,7 +211,37 @@ func (o *TestKind) SetUpdatedBy(updatedBy string) { } func (o *TestKind) Copy() resource.Object { - return resource.CopyObject(o) + cpy := &TestKind{} + + // Copy metadata + o.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta) + + // Copy Spec + cpy.Spec.StringField = o.Spec.StringField + + // Copy Status + cpy.Status.AdditionalFields = make(map[string]any) + for key, val := range o.Status.AdditionalFields { + cpy.Status.AdditionalFields[key] = val + } + cpy.Status.OperatorStates = make(map[string]TestKindStatusOperatorState) + for key, val := range o.Status.OperatorStates { + cpyVal := TestKindStatusOperatorState{} + if val.DescriptiveState != nil { + descriptiveStateCopy := *val.DescriptiveState + cpyVal.DescriptiveState = &descriptiveStateCopy + } + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.LastEvaluation = val.LastEvaluation + cpyVal.State = val.State + + cpy.Status.OperatorStates[key] = cpyVal + } + + return cpy } func (o *TestKind) DeepCopyObject() runtime.Object { diff --git a/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind_status_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind_status_gen.go.txt index 05d157e43..83d3fc56a 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind_status_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/test/v1/testkind_status_gen.go.txt @@ -7,11 +7,11 @@ const ( TestKindOperatorStateStateSuccess TestKindOperatorStateState = "success" ) -// Defines values for TestKindstatusOperatorStateState. +// Defines values for TestKindStatusOperatorStateState. const ( - TestKindstatusOperatorStateStateFailed TestKindstatusOperatorStateState = "failed" - TestKindstatusOperatorStateStateInProgress TestKindstatusOperatorStateState = "in_progress" - TestKindstatusOperatorStateStateSuccess TestKindstatusOperatorStateState = "success" + TestKindStatusOperatorStateStateFailed TestKindStatusOperatorStateState = "failed" + TestKindStatusOperatorStateStateInProgress TestKindStatusOperatorStateState = "in_progress" + TestKindStatusOperatorStateStateSuccess TestKindStatusOperatorStateState = "success" ) // TestKindOperatorState defines model for TestKindOperatorState. @@ -44,12 +44,12 @@ type TestKindStatus struct { // operatorStates is a map of operator ID to operator state evaluations. // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]TestKindstatusOperatorState `json:"operatorStates,omitempty"` + OperatorStates map[string]TestKindStatusOperatorState `json:"operatorStates,omitempty"` } -// TestKindstatusOperatorState defines model for TestKindstatus.#OperatorState. +// TestKindStatusOperatorState defines model for TestKindStatus.#OperatorState. // +k8s:openapi-gen=true -type TestKindstatusOperatorState struct { +type TestKindStatusOperatorState struct { // descriptiveState is an optional more descriptive state field which has no requirements on format DescriptiveState *string `json:"descriptiveState,omitempty"` @@ -61,10 +61,10 @@ type TestKindstatusOperatorState struct { // state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. - State TestKindstatusOperatorStateState `json:"state"` + State TestKindStatusOperatorStateState `json:"state"` } -// TestKindstatusOperatorStateState state describes the state of the lastEvaluation. +// TestKindStatusOperatorStateState state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. // +k8s:openapi-gen=true -type TestKindstatusOperatorStateState string +type TestKindStatusOperatorStateState string diff --git a/codegen/testing/golden_generated/go/groupbygroup/test/v2/testkind_object_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/test/v2/testkind_object_gen.go.txt index 04bb179b6..7aab3d304 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/test/v2/testkind_object_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/test/v2/testkind_object_gen.go.txt @@ -19,7 +19,7 @@ type TestKind struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` Spec TestKindSpec `json:"spec"` - TestKindStatus TestKindStatus `json:"status"` + Status TestKindStatus `json:"status"` } func (o *TestKind) GetSpec() any { @@ -37,14 +37,14 @@ func (o *TestKind) SetSpec(spec any) error { func (o *TestKind) GetSubresources() map[string]any { return map[string]any{ - "status": o.TestKindStatus, + "status": o.Status, } } func (o *TestKind) GetSubresource(name string) (any, bool) { switch name { case "status": - return o.TestKindStatus, true + return o.Status, true default: return nil, false } @@ -57,7 +57,7 @@ func (o *TestKind) SetSubresource(name string, value any) error { if !ok { return fmt.Errorf("cannot set status type %#v, not of type TestKindStatus", value) } - o.TestKindStatus = cast + o.Status = cast return nil default: return fmt.Errorf("subresource '%s' does not exist", name) @@ -211,7 +211,39 @@ func (o *TestKind) SetUpdatedBy(updatedBy string) { } func (o *TestKind) Copy() resource.Object { - return resource.CopyObject(o) + cpy := &TestKind{} + + // Copy metadata + o.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta) + + // Copy Spec + cpy.Spec.IntField = o.Spec.IntField + cpy.Spec.StringField = o.Spec.StringField + cpy.Spec.TimeField = o.Spec.TimeField + + // Copy Status + cpy.Status.AdditionalFields = make(map[string]any) + for key, val := range o.Status.AdditionalFields { + cpy.Status.AdditionalFields[key] = val + } + cpy.Status.OperatorStates = make(map[string]TestKindStatusOperatorState) + for key, val := range o.Status.OperatorStates { + cpyVal := TestKindStatusOperatorState{} + if val.DescriptiveState != nil { + descriptiveStateCopy := *val.DescriptiveState + cpyVal.DescriptiveState = &descriptiveStateCopy + } + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.LastEvaluation = val.LastEvaluation + cpyVal.State = val.State + + cpy.Status.OperatorStates[key] = cpyVal + } + + return cpy } func (o *TestKind) DeepCopyObject() runtime.Object { diff --git a/codegen/testing/golden_generated/go/groupbygroup/test/v2/testkind_status_gen.go.txt b/codegen/testing/golden_generated/go/groupbygroup/test/v2/testkind_status_gen.go.txt index 72ffbfe5e..b992fba09 100644 --- a/codegen/testing/golden_generated/go/groupbygroup/test/v2/testkind_status_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbygroup/test/v2/testkind_status_gen.go.txt @@ -7,11 +7,11 @@ const ( TestKindOperatorStateStateSuccess TestKindOperatorStateState = "success" ) -// Defines values for TestKindstatusOperatorStateState. +// Defines values for TestKindStatusOperatorStateState. const ( - TestKindstatusOperatorStateStateFailed TestKindstatusOperatorStateState = "failed" - TestKindstatusOperatorStateStateInProgress TestKindstatusOperatorStateState = "in_progress" - TestKindstatusOperatorStateStateSuccess TestKindstatusOperatorStateState = "success" + TestKindStatusOperatorStateStateFailed TestKindStatusOperatorStateState = "failed" + TestKindStatusOperatorStateStateInProgress TestKindStatusOperatorStateState = "in_progress" + TestKindStatusOperatorStateStateSuccess TestKindStatusOperatorStateState = "success" ) // TestKindOperatorState defines model for TestKindOperatorState. @@ -44,12 +44,12 @@ type TestKindStatus struct { // operatorStates is a map of operator ID to operator state evaluations. // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]TestKindstatusOperatorState `json:"operatorStates,omitempty"` + OperatorStates map[string]TestKindStatusOperatorState `json:"operatorStates,omitempty"` } -// TestKindstatusOperatorState defines model for TestKindstatus.#OperatorState. +// TestKindStatusOperatorState defines model for TestKindStatus.#OperatorState. // +k8s:openapi-gen=true -type TestKindstatusOperatorState struct { +type TestKindStatusOperatorState struct { // descriptiveState is an optional more descriptive state field which has no requirements on format DescriptiveState *string `json:"descriptiveState,omitempty"` @@ -61,10 +61,10 @@ type TestKindstatusOperatorState struct { // state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. - State TestKindstatusOperatorStateState `json:"state"` + State TestKindStatusOperatorStateState `json:"state"` } -// TestKindstatusOperatorStateState state describes the state of the lastEvaluation. +// TestKindStatusOperatorStateState state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. // +k8s:openapi-gen=true -type TestKindstatusOperatorStateState string +type TestKindStatusOperatorStateState string diff --git a/codegen/testing/golden_generated/go/groupbykind/customkind/v0_0/customkind_object_gen.go.txt b/codegen/testing/golden_generated/go/groupbykind/customkind/v0_0/customkind_object_gen.go.txt index 0488aff6f..690c71284 100644 --- a/codegen/testing/golden_generated/go/groupbykind/customkind/v0_0/customkind_object_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbykind/customkind/v0_0/customkind_object_gen.go.txt @@ -211,7 +211,38 @@ func (o *CustomKind) SetUpdatedBy(updatedBy string) { } func (o *CustomKind) Copy() resource.Object { - return resource.CopyObject(o) + cpy := &CustomKind{} + + // Copy metadata + o.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta) + + // Copy Spec + cpy.Spec.DeprecatedField = o.Spec.DeprecatedField + cpy.Spec.Field1 = o.Spec.Field1 + + // Copy Status + cpy.Status.AdditionalFields = make(map[string]any) + for key, val := range o.Status.AdditionalFields { + cpy.Status.AdditionalFields[key] = val + } + cpy.Status.OperatorStates = make(map[string]StatusOperatorState) + for key, val := range o.Status.OperatorStates { + cpyVal := StatusOperatorState{} + if val.DescriptiveState != nil { + descriptiveStateCopy := *val.DescriptiveState + cpyVal.DescriptiveState = &descriptiveStateCopy + } + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.LastEvaluation = val.LastEvaluation + cpyVal.State = val.State + + cpy.Status.OperatorStates[key] = cpyVal + } + + return cpy } func (o *CustomKind) DeepCopyObject() runtime.Object { diff --git a/codegen/testing/golden_generated/go/groupbykind/customkind/v0_0/customkind_status_gen.go.txt b/codegen/testing/golden_generated/go/groupbykind/customkind/v0_0/customkind_status_gen.go.txt index 54ca37c2d..2b9039401 100644 --- a/codegen/testing/golden_generated/go/groupbykind/customkind/v0_0/customkind_status_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbykind/customkind/v0_0/customkind_status_gen.go.txt @@ -47,7 +47,7 @@ type Status struct { OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"` } -// StatusOperatorState defines model for status.#OperatorState. +// StatusOperatorState defines model for Status.#OperatorState. // +k8s:openapi-gen=true type StatusOperatorState struct { // descriptiveState is an optional more descriptive state field which has no requirements on format diff --git a/codegen/testing/golden_generated/go/groupbykind/customkind/v1_0/customkind_object_gen.go.txt b/codegen/testing/golden_generated/go/groupbykind/customkind/v1_0/customkind_object_gen.go.txt index 28c320e80..c80a64394 100644 --- a/codegen/testing/golden_generated/go/groupbykind/customkind/v1_0/customkind_object_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbykind/customkind/v1_0/customkind_object_gen.go.txt @@ -243,7 +243,65 @@ func (o *CustomKind) SetUpdatedBy(updatedBy string) { } func (o *CustomKind) Copy() resource.Object { - return resource.CopyObject(o) + cpy := &CustomKind{} + + // Copy metadata + o.ObjectMeta.DeepCopyInto(&cpy.ObjectMeta) + + // Copy Spec + cpy.Spec.BoolField = o.Spec.BoolField + cpy.Spec.Enum = o.Spec.Enum + cpy.Spec.Field1 = o.Spec.Field1 + cpy.Spec.FloatField = o.Spec.FloatField + cpy.Spec.I32 = o.Spec.I32 + cpy.Spec.I64 = o.Spec.I64 + cpy.Spec.Inner.InnerField1 = o.Spec.Inner.InnerField1 + if o.Spec.Inner.InnerField2 != nil { + cpy.Spec.Inner.InnerField2 = make([]string, len(o.Spec.Inner.InnerField2)) + copy(cpy.Spec.Inner.InnerField2, o.Spec.Inner.InnerField2) + } + if o.Spec.Inner.InnerField3 != nil { + cpy.Spec.Inner.InnerField3 = make([]InnerObject2, len(o.Spec.Inner.InnerField3)) + copy(cpy.Spec.Inner.InnerField3, o.Spec.Inner.InnerField3) + } + cpy.Spec.Map = make(map[string]Type2) + for key, val := range o.Spec.Map { + cpyVal := Type2{} + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.Group = val.Group + + cpy.Spec.Map[key] = cpyVal + } + cpy.Spec.Timestamp = o.Spec.Timestamp + o.Spec.Union = cpy.Spec.Union + + // Copy Status + cpy.Status.AdditionalFields = make(map[string]any) + for key, val := range o.Status.AdditionalFields { + cpy.Status.AdditionalFields[key] = val + } + cpy.Status.OperatorStates = make(map[string]StatusOperatorState) + for key, val := range o.Status.OperatorStates { + cpyVal := StatusOperatorState{} + if val.DescriptiveState != nil { + descriptiveStateCopy := *val.DescriptiveState + cpyVal.DescriptiveState = &descriptiveStateCopy + } + cpyVal.Details = make(map[string]any) + for key, val := range val.Details { + cpyVal.Details[key] = val + } + cpyVal.LastEvaluation = val.LastEvaluation + cpyVal.State = val.State + + cpy.Status.OperatorStates[key] = cpyVal + } + cpy.Status.StatusField1 = o.Status.StatusField1 + + return cpy } func (o *CustomKind) DeepCopyObject() runtime.Object { diff --git a/codegen/testing/golden_generated/go/groupbykind/customkind/v1_0/customkind_status_gen.go.txt b/codegen/testing/golden_generated/go/groupbykind/customkind/v1_0/customkind_status_gen.go.txt index 39c90cf7b..658a1fc7a 100644 --- a/codegen/testing/golden_generated/go/groupbykind/customkind/v1_0/customkind_status_gen.go.txt +++ b/codegen/testing/golden_generated/go/groupbykind/customkind/v1_0/customkind_status_gen.go.txt @@ -48,7 +48,7 @@ type Status struct { StatusField1 string `json:"statusField1"` } -// StatusOperatorState defines model for status.#OperatorState. +// StatusOperatorState defines model for Status.#OperatorState. // +k8s:openapi-gen=true type StatusOperatorState struct { // descriptiveState is an optional more descriptive state field which has no requirements on format diff --git a/codegen/testing/golden_generated/go/unversioned/customkind/customkind_status_gen.go.txt b/codegen/testing/golden_generated/go/unversioned/customkind/customkind_status_gen.go.txt index 4b88db094..4437d75b1 100644 --- a/codegen/testing/golden_generated/go/unversioned/customkind/customkind_status_gen.go.txt +++ b/codegen/testing/golden_generated/go/unversioned/customkind/customkind_status_gen.go.txt @@ -48,7 +48,7 @@ type Status struct { StatusField1 string `json:"statusField1"` } -// StatusOperatorState defines model for status.#OperatorState. +// StatusOperatorState defines model for Status.#OperatorState. // +k8s:openapi-gen=true type StatusOperatorState struct { // descriptiveState is an optional more descriptive state field which has no requirements on format diff --git a/codegen/thema/generators.go b/codegen/thema/generators.go index 5dc9ef1a0..61394c721 100644 --- a/codegen/thema/generators.go +++ b/codegen/thema/generators.go @@ -71,6 +71,7 @@ func ResourceGenerator() *codejen.JennyList[kindsys.Custom] { OnlyUseCurrentVersion: true, GroupByKind: true, SubresourceTypesArePrefixed: false, + GenericCopy: true, }, kindsysCustomToKind), codejen.AdaptOneToMany[codegen.Kind, kindsys.Custom](&jennies.SchemaGenerator{ OnlyUseCurrentVersion: true,