diff --git a/hack/generator/pkg/codegen/storage/function_injector.go b/hack/generator/pkg/astmodel/function_injector.go similarity index 64% rename from hack/generator/pkg/codegen/storage/function_injector.go rename to hack/generator/pkg/astmodel/function_injector.go index 8b238c715c1..158b3e2ed12 100644 --- a/hack/generator/pkg/codegen/storage/function_injector.go +++ b/hack/generator/pkg/astmodel/function_injector.go @@ -3,21 +3,19 @@ * Licensed under the MIT license. */ -package storage - -import "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" +package astmodel // FunctionInjector is a utility for injecting function definitions into resources and objects type FunctionInjector struct { // visitor is used to do the actual injection - visitor astmodel.TypeVisitor + visitor TypeVisitor } // NewFunctionInjector creates a new function injector for modifying resources and objects func NewFunctionInjector() *FunctionInjector { result := &FunctionInjector{} - result.visitor = astmodel.TypeVisitorBuilder{ + result.visitor = TypeVisitorBuilder{ VisitObjectType: result.injectFunctionIntoObject, VisitResourceType: result.injectFunctionIntoResource, }.Build() @@ -26,22 +24,32 @@ func NewFunctionInjector() *FunctionInjector { } // Inject modifies the passed type definition by injecting the passed function -func (fi *FunctionInjector) Inject(def astmodel.TypeDefinition, fn astmodel.Function) (astmodel.TypeDefinition, error) { - return fi.visitor.VisitDefinition(def, fn) +func (fi *FunctionInjector) Inject(def TypeDefinition, fns ...Function) (TypeDefinition, error) { + result := def + + for _, fn := range fns { + var err error + result, err = fi.visitor.VisitDefinition(result, fn) + if err != nil { + return TypeDefinition{}, err + } + } + + return result, nil } // injectFunctionIntoObject takes the function provided as a context and includes it on the // provided object type func (_ *FunctionInjector) injectFunctionIntoObject( - _ *astmodel.TypeVisitor, ot *astmodel.ObjectType, ctx interface{}) (astmodel.Type, error) { - fn := ctx.(astmodel.Function) + _ *TypeVisitor, ot *ObjectType, ctx interface{}) (Type, error) { + fn := ctx.(Function) return ot.WithFunction(fn), nil } // injectFunctionIntoResource takes the function provided as a context and includes it on the // provided resource type func (_ *FunctionInjector) injectFunctionIntoResource( - _ *astmodel.TypeVisitor, rt *astmodel.ResourceType, ctx interface{}) (astmodel.Type, error) { - fn := ctx.(astmodel.Function) + _ *TypeVisitor, rt *ResourceType, ctx interface{}) (Type, error) { + fn := ctx.(Function) return rt.WithFunction(fn), nil } diff --git a/hack/generator/pkg/astmodel/interface_implementation.go b/hack/generator/pkg/astmodel/interface_implementation.go index 3712ac56c39..bfb1a05049f 100644 --- a/hack/generator/pkg/astmodel/interface_implementation.go +++ b/hack/generator/pkg/astmodel/interface_implementation.go @@ -57,6 +57,11 @@ func (iface *InterfaceImplementation) References() TypeNameSet { return results } +// FunctionCount returns the number of included functions +func (iface *InterfaceImplementation) FunctionCount() int { + return len(iface.functions) +} + // Equals determines if this interface is equal to another interface func (iface *InterfaceImplementation) Equals(other *InterfaceImplementation) bool { if len(iface.functions) != len(other.functions) { diff --git a/hack/generator/pkg/astmodel/interface_injector.go b/hack/generator/pkg/astmodel/interface_injector.go new file mode 100644 index 00000000000..807b4b4831d --- /dev/null +++ b/hack/generator/pkg/astmodel/interface_injector.go @@ -0,0 +1,49 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package astmodel + +// InterfaceInjector is a utility for injecting interface implementations into resources and objects +type InterfaceInjector struct { + // visitor is used to do the actual injection + visitor TypeVisitor +} + +// NewInterfaceInjector creates a new interface injector for modifying resources and objects +func NewInterfaceInjector() *InterfaceInjector { + result := &InterfaceInjector{} + + result.visitor = TypeVisitorBuilder{ + VisitObjectType: result.injectInterfaceIntoObject, + VisitResourceType: result.injectInterfaceIntoResource, + }.Build() + + return result +} + +// Inject modifies the passed type definition by injecting the passed function +func (i *InterfaceInjector) Inject(def TypeDefinition, implementation *InterfaceImplementation) (TypeDefinition, error) { + result, err := i.visitor.VisitDefinition(def, implementation) + if err != nil { + return TypeDefinition{}, err + } + return result, nil +} + +// injectFunctionIntoObject takes the function provided as a context and includes it on the +// provided object type +func (i *InterfaceInjector) injectInterfaceIntoObject( + _ *TypeVisitor, ot *ObjectType, ctx interface{}) (Type, error) { + implementation := ctx.(*InterfaceImplementation) + return ot.WithInterface(implementation), nil +} + +// injectFunctionIntoResource takes the function provided as a context and includes it on the +// provided resource type +func (i *InterfaceInjector) injectInterfaceIntoResource( + _ *TypeVisitor, rt *ResourceType, ctx interface{}) (Type, error) { + fn := ctx.(*InterfaceImplementation) + return rt.WithInterface(fn), nil +} diff --git a/hack/generator/pkg/codegen/storage/property_injector.go b/hack/generator/pkg/astmodel/property_injector.go similarity index 65% rename from hack/generator/pkg/codegen/storage/property_injector.go rename to hack/generator/pkg/astmodel/property_injector.go index f933bc177d5..9b4ebe21aec 100644 --- a/hack/generator/pkg/codegen/storage/property_injector.go +++ b/hack/generator/pkg/astmodel/property_injector.go @@ -3,21 +3,19 @@ * Licensed under the MIT license. */ -package storage - -import "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" +package astmodel // PropertyInjector is a utility for injecting property definitions into resources and objects type PropertyInjector struct { // visitor is used to do the actual injection - visitor astmodel.TypeVisitor + visitor TypeVisitor } // NewPropertyInjector creates a new property injector for modifying resources and objects func NewPropertyInjector() *PropertyInjector { result := &PropertyInjector{} - result.visitor = astmodel.TypeVisitorBuilder{ + result.visitor = TypeVisitorBuilder{ VisitObjectType: result.injectPropertyIntoObject, VisitResourceType: result.injectPropertyIntoResource, }.Build() @@ -26,20 +24,20 @@ func NewPropertyInjector() *PropertyInjector { } // Inject modifies the passed type definition by injecting the passed property -func (pi *PropertyInjector) Inject(def astmodel.TypeDefinition, prop *astmodel.PropertyDefinition) (astmodel.TypeDefinition, error) { +func (pi *PropertyInjector) Inject(def TypeDefinition, prop *PropertyDefinition) (TypeDefinition, error) { return pi.visitor.VisitDefinition(def, prop) } // injectPropertyIntoObject takes the property provided as a context and includes it on the provided object type func (pi *PropertyInjector) injectPropertyIntoObject( - _ *astmodel.TypeVisitor, ot *astmodel.ObjectType, ctx interface{}) (astmodel.Type, error) { - prop := ctx.(*astmodel.PropertyDefinition) + _ *TypeVisitor, ot *ObjectType, ctx interface{}) (Type, error) { + prop := ctx.(*PropertyDefinition) return ot.WithProperty(prop), nil } // injectPropertyIntoResource takes the property provided as a context and includes it on the provided resource type func (pi *PropertyInjector) injectPropertyIntoResource( - _ *astmodel.TypeVisitor, rt *astmodel.ResourceType, ctx interface{}) (astmodel.Type, error) { - prop := ctx.(*astmodel.PropertyDefinition) + _ *TypeVisitor, rt *ResourceType, ctx interface{}) (Type, error) { + prop := ctx.(*PropertyDefinition) return rt.WithProperty(prop), nil } diff --git a/hack/generator/pkg/astmodel/types.go b/hack/generator/pkg/astmodel/types.go index ac6d0678e0b..4b02bcc4aa6 100644 --- a/hack/generator/pkg/astmodel/types.go +++ b/hack/generator/pkg/astmodel/types.go @@ -245,7 +245,7 @@ func (types Types) ResolveResourceStatusDefinition( } // Process applies a func to transform all members of this set of type definitions, returning a new set of type -// definitions containing the results of the transfomration, or possibly an error +// definitions containing the results of the transformation, or possibly an error // Only definitions returned by the func will be included in the results of the function. The func may return a nil // TypeDefinition if it doesn't want to include anything in the output set. func (types Types) Process(transformation func(definition TypeDefinition) (*TypeDefinition, error)) (Types, error) { @@ -262,3 +262,73 @@ func (types Types) Process(transformation func(definition TypeDefinition) (*Type return result, nil } + +// FindResourceTypes walks the provided set of TypeDefinitions and returns all the resource types +func FindResourceTypes(types Types) Types { + result := make(Types) + + // Find all our resources and extract all their Specs + for _, def := range types { + _, ok := AsResourceType(def.Type()) + if !ok { + continue + } + + // We have a resource type + result.Add(def) + } + + return result +} + +// FindSpecTypes walks the provided set of TypeDefinitions and returns all the spec types +func FindSpecTypes(types Types) Types { + result := make(Types) + + // Find all our resources and extract all their Specs + for _, def := range types { + rt, ok := AsResourceType(def.Type()) + if !ok { + continue + } + + // We have a resource type + tn, ok := AsTypeName(rt.SpecType()) + if !ok { + continue + } + + // Add the named spec type to our results + if spec, ok := types.TryGet(tn); ok { + result.Add(spec) + } + } + + return result +} + +// FindStatusTypes walks the provided set of TypeDefinitions and returns all the status types +func FindStatusTypes(types Types) Types { + result := make(Types) + + // Find all our resources and extract all their Statuses + for _, def := range types { + rt, ok := AsResourceType(def.Type()) + if !ok { + continue + } + + // We have a resource type + tn, ok := AsTypeName(rt.StatusType()) + if !ok { + continue + } + + // Add the named status type to our results + if status, ok := types.TryGet(tn); ok { + result.Add(status) + } + } + + return result +} diff --git a/hack/generator/pkg/astmodel/types_test.go b/hack/generator/pkg/astmodel/types_test.go index 6481085364f..8515df04165 100644 --- a/hack/generator/pkg/astmodel/types_test.go +++ b/hack/generator/pkg/astmodel/types_test.go @@ -175,6 +175,48 @@ func Test_TypesOverlayWith_GivenOverlappingSets_PrefersTypeInOverlay(t *testing. g.Expect(set).NotTo(ContainElement(deltaDefinition)) } +/* + * FindSpecTypes() tests + */ + +func TestFindSpecTypes(t *testing.T) { + g := NewGomegaWithT(t) + + // Define a test resource + spec := createTestSpec("Person", fullNameProperty, knownAsProperty) + status := createTestStatus("Person") + resource := createTestResource("Person", spec, status) + + types := make(Types) + types.AddAll(resource, status, spec) + + specs := FindSpecTypes(types) + + g.Expect(specs).To(HaveLen(1)) + g.Expect(specs.Contains(spec.Name())).To(BeTrue()) +} + +/* + * FindStatusTypes() tests + */ + +func TestFindStatusTypes(t *testing.T) { + g := NewGomegaWithT(t) + + // Define a test resource + spec := createTestSpec("Person", fullNameProperty, knownAsProperty) + status := createTestStatus("Person") + resource := createTestResource("Person", spec, status) + + types := make(Types) + types.AddAll(resource, status, spec) + + statuses := FindStatusTypes(types) + + g.Expect(statuses).To(HaveLen(1)) + g.Expect(statuses.Contains(status.Name())).To(BeTrue()) +} + /* * Utility functions */ @@ -192,3 +234,32 @@ func createTestTypes(defs ...TypeDefinition) Types { return result } + +// CreateTestResource makes a resource for testing +func createTestResource( + name string, + spec TypeDefinition, + status TypeDefinition) TypeDefinition { + + resourceType := NewResourceType(spec.Name(), status.Name()) + return MakeTypeDefinition(MakeTypeName(pkg, name), resourceType) +} + +// createTestSpec makes a spec for testing +func createTestSpec( + name string, + properties ...*PropertyDefinition) TypeDefinition { + specName := MakeTypeName(pkg, name+"_Spec") + return MakeTypeDefinition( + specName, + NewObjectType().WithProperties(properties...)) +} + +// createTestStatus makes a status for testing +func createTestStatus(name string) TypeDefinition { + statusProperty := NewPropertyDefinition("Status", "status", StringType) + statusName := MakeTypeName(pkg, name+"_Status") + return MakeTypeDefinition( + statusName, + NewObjectType().WithProperties(statusProperty)) +} diff --git a/hack/generator/pkg/codegen/code_generator.go b/hack/generator/pkg/codegen/code_generator.go index 024d4d18f03..57e032d4333 100644 --- a/hack/generator/pkg/codegen/code_generator.go +++ b/hack/generator/pkg/codegen/code_generator.go @@ -117,8 +117,7 @@ func createAllPipelineStages(idFactory astmodel.IdentifierFactory, configuration // De-pluralize resource types // (Must come after type aliases are resolved) - pipeline.ImproveResourcePluralization(). - RequiresPrerequisiteStages("removeAliases"), + pipeline.ImproveResourcePluralization(), pipeline.StripUnreferencedTypeDefinitions(), @@ -175,6 +174,7 @@ func createAllPipelineStages(idFactory astmodel.IdentifierFactory, configuration See https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/webhook/conversion/conversion.go#L310 pipeline.InjectHubFunction(idFactory).UsedFor(pipeline.ARMTarget), + pipeline.ImplementConvertibleInterface(idFactory), */ // Safety checks at the end: @@ -182,9 +182,7 @@ func createAllPipelineStages(idFactory astmodel.IdentifierFactory, configuration pipeline.EnsureARMTypeExistsForEveryResource().UsedFor(pipeline.ARMTarget), pipeline.DeleteGeneratedCode(configuration.FullTypesOutputPath()), - - pipeline.ExportPackages(configuration.FullTypesOutputPath()). - RequiresPrerequisiteStages("deleteGenerated"), + pipeline.ExportPackages(configuration.FullTypesOutputPath()), pipeline.ExportControllerResourceRegistrations(configuration.FullTypesRegistrationOutputFilePath()).UsedFor(pipeline.ARMTarget), } diff --git a/hack/generator/pkg/codegen/golden_files_test.go b/hack/generator/pkg/codegen/golden_files_test.go index a573c8414b8..7395155840e 100644 --- a/hack/generator/pkg/codegen/golden_files_test.go +++ b/hack/generator/pkg/codegen/golden_files_test.go @@ -146,41 +146,43 @@ func NewTestCodeGenerator(testName string, path string, t *testing.T, testConfig switch genPipeline { case config.GenerationPipelineAzure: codegen.RemoveStages( - "deleteGenerated", - "rogueCheck", - "createStorageTypes", - "injectOriginalGVKFunction", - "injectOriginalVersionFunction", - "injectOriginalVersionProperty", - "injectPropertyAssignmentFunctions", - //pipeline.InjectHubFunctionStageId, - "reportTypesAndVersions") + pipeline.DeleteGeneratedCodeStageID, + pipeline.CheckForAnyTypeStageID, + pipeline.CreateStorageTypesStageID, + pipeline.InjectOriginalGVKFunctionStageID, + pipeline.InjectOriginalVersionFunctionStageID, + pipeline.InjectOriginalVersionPropertyStageID, + pipeline.InjectPropertyAssignmentFunctionsStageID, + // TODO: Once the stage is enabled in the pipeline, we may need to remove it here for testing + //pipeline.InjectHubFunctionStageID, + pipeline.ReportOnTypesAndVersionsStageID) if !testConfig.HasARMResources { - codegen.RemoveStages("createArmTypes", "applyArmConversionInterface") + codegen.RemoveStages(pipeline.CreateARMTypesStageID, pipeline.ApplyARMConversionInterfaceStageID) + // These stages treat the collection of types as a graph of types rooted by a resource type. // In the degenerate case where there are no resources it behaves the same as stripUnreferenced - removing // all types. Remove it in phases that have no resources to avoid this. - codegen.RemoveStages("removeEmbeddedResources", "collapseCrossGroupReferences") + codegen.RemoveStages(pipeline.RemoveEmbeddedResourcesStageID, pipeline.CollapseCrossGroupReferencesStageID) - codegen.ReplaceStage("stripUnreferenced", stripUnusedTypesPipelineStage()) + codegen.ReplaceStage(pipeline.StripUnreferencedTypeDefinitionsStageID, stripUnusedTypesPipelineStage()) } else { - codegen.ReplaceStage("addCrossResourceReferences", addCrossResourceReferencesForTest(idFactory)) + codegen.ReplaceStage(pipeline.AddCrossResourceReferencesStageID, addCrossResourceReferencesForTest(idFactory)) } case config.GenerationPipelineCrossplane: - codegen.RemoveStages("deleteGenerated", "rogueCheck") + codegen.RemoveStages(pipeline.DeleteGeneratedCodeStageID, pipeline.CheckForAnyTypeStageID) if !testConfig.HasARMResources { - codegen.ReplaceStage("stripUnreferenced", stripUnusedTypesPipelineStage()) + codegen.ReplaceStage(pipeline.StripUnreferencedTypeDefinitionsStageID, stripUnusedTypesPipelineStage()) } default: return nil, errors.Errorf("unknown pipeline kind %q", string(genPipeline)) } - codegen.ReplaceStage("loadSchema", loadTestSchemaIntoTypes(idFactory, cfg, path)) - codegen.ReplaceStage("exportPackages", exportPackagesTestPipelineStage(t, testName)) + codegen.ReplaceStage(pipeline.LoadSchemaIntoTypesStageID, loadTestSchemaIntoTypes(idFactory, cfg, path)) + codegen.ReplaceStage(pipeline.ExportPackagesStageID, exportPackagesTestPipelineStage(t, testName)) if testConfig.InjectEmbeddedStruct { - codegen.InjectStageAfter("removeAliases", injectEmbeddedStructType()) + codegen.InjectStageAfter(pipeline.RemoveTypeAliasesStageID, injectEmbeddedStructType()) } codegen.RemoveStages() @@ -296,7 +298,7 @@ func stripUnusedTypesPipelineStage() pipeline.Stage { // TODO: we have no way to give Swagger to the golden files tests currently. func addCrossResourceReferencesForTest(idFactory astmodel.IdentifierFactory) pipeline.Stage { return pipeline.MakeLegacyStage( - "addCrossResourceReferences", + pipeline.AddCrossResourceReferencesStageID, "Add cross resource references for test", func(ctx context.Context, defs astmodel.Types) (astmodel.Types, error) { result := make(astmodel.Types) diff --git a/hack/generator/pkg/codegen/pipeline/add_arm_conversion_interface.go b/hack/generator/pkg/codegen/pipeline/add_arm_conversion_interface.go index ce8e078d6ea..109068130ef 100644 --- a/hack/generator/pkg/codegen/pipeline/add_arm_conversion_interface.go +++ b/hack/generator/pkg/codegen/pipeline/add_arm_conversion_interface.go @@ -15,12 +15,15 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel/armconversion" ) +// ApplyARMConversionInterfaceStageID is the unique identifier of this pipeline stage +const ApplyARMConversionInterfaceStageID = "applyArmConversionInterface" + // ApplyARMConversionInterface adds the genruntime.ARMTransformer interface and the Owner property // to all Kubernetes types. // The genruntime.ARMTransformer interface is used to convert from the Kubernetes type to the corresponding ARM type and back. func ApplyARMConversionInterface(idFactory astmodel.IdentifierFactory) Stage { return MakeLegacyStage( - "applyArmConversionInterface", + ApplyARMConversionInterfaceStageID, "Add ARM conversion interfaces to Kubernetes types", func(ctx context.Context, definitions astmodel.Types) (astmodel.Types, error) { converter := &armConversionApplier{ diff --git a/hack/generator/pkg/codegen/pipeline/add_cross_resource_references.go b/hack/generator/pkg/codegen/pipeline/add_cross_resource_references.go index f50e712fc60..bbc3252d7c3 100644 --- a/hack/generator/pkg/codegen/pipeline/add_cross_resource_references.go +++ b/hack/generator/pkg/codegen/pipeline/add_cross_resource_references.go @@ -18,6 +18,9 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/config" ) +// AddCrossResourceReferencesStageID is the unique identifier for this pipeline stage +const AddCrossResourceReferencesStageID = "addCrossResourceReferences" + var armIDDescriptionRegex = regexp.MustCompile("(?i)(.*/subscriptions/.*?/resourceGroups/.*|ARM ID|Resource ID)") // TODO: For now not supporting array or map of references. Unsure if it actually ever happens in practice. @@ -25,7 +28,7 @@ var armIDDescriptionRegex = regexp.MustCompile("(?i)(.*/subscriptions/.*?/resour // AddCrossResourceReferences replaces cross resource references with genruntime.ResourceReference. func AddCrossResourceReferences(configuration *config.Configuration, idFactory astmodel.IdentifierFactory) Stage { return MakeLegacyStage( - "addCrossResourceReferences", + AddCrossResourceReferencesStageID, "Replace cross-resource references with genruntime.ResourceReference", func(ctx context.Context, definitions astmodel.Types) (astmodel.Types, error) { result := make(astmodel.Types) diff --git a/hack/generator/pkg/codegen/pipeline/check_for_anytype.go b/hack/generator/pkg/codegen/pipeline/check_for_anytype.go index 17349ffc108..76d6b2c36ba 100644 --- a/hack/generator/pkg/codegen/pipeline/check_for_anytype.go +++ b/hack/generator/pkg/codegen/pipeline/check_for_anytype.go @@ -16,6 +16,9 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" ) +// CheckForAnyTypeStageID is the unique identifier for this stage +const CheckForAnyTypeStageID = "rogueCheck" + // FilterOutDefinitionsUsingAnyType returns a stage that will check for any definitions // containing AnyTypes. It accepts a set of packages that we expect to contain types // with AnyTypes. Those packages will be quietly filtered out of the output of the @@ -41,7 +44,7 @@ func checkForAnyType(description string, packages []string) Stage { } return MakeLegacyStage( - "rogueCheck", + CheckForAnyTypeStageID, description, func(ctx context.Context, defs astmodel.Types) (astmodel.Types, error) { var badNames []astmodel.TypeName diff --git a/hack/generator/pkg/codegen/pipeline/collapse_cross_group_refs.go b/hack/generator/pkg/codegen/pipeline/collapse_cross_group_refs.go index 1663fa64a51..1438d787d2c 100644 --- a/hack/generator/pkg/codegen/pipeline/collapse_cross_group_refs.go +++ b/hack/generator/pkg/codegen/pipeline/collapse_cross_group_refs.go @@ -13,11 +13,14 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" ) +// CollapseCrossGroupReferencesStageID is the unique identifier for this pipeline stage +const CollapseCrossGroupReferencesStageID = "collapseCrossGroupReferences" + // CollapseCrossGroupReferences finds and removes references between API groups. This isn't particularly common // but does occur in a few instances, for example from Microsoft.Compute -> Microsoft.Compute.Extensions. func CollapseCrossGroupReferences() Stage { return MakeLegacyStage( - "collapseCrossGroupReferences", + CollapseCrossGroupReferencesStageID, "Finds and removes cross group references", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { resources := astmodel.CollectResourceDefinitions(types) diff --git a/hack/generator/pkg/codegen/pipeline/create_arm_types.go b/hack/generator/pkg/codegen/pipeline/create_arm_types.go index eb325696f4b..c5b7d6e0c64 100644 --- a/hack/generator/pkg/codegen/pipeline/create_arm_types.go +++ b/hack/generator/pkg/codegen/pipeline/create_arm_types.go @@ -15,11 +15,14 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" ) +// CreateARMTypesStageID is the unique identifier for this pipeline stage +const CreateARMTypesStageID = "createArmTypes" + // CreateARMTypes walks the type graph and builds new types for communicating // with ARM func CreateARMTypes(idFactory astmodel.IdentifierFactory) Stage { return MakeLegacyStage( - "createArmTypes", + CreateARMTypesStageID, "Create types for interaction with ARM", func(ctx context.Context, definitions astmodel.Types) (astmodel.Types, error) { armTypeCreator := &armTypeCreator{definitions: definitions, idFactory: idFactory} diff --git a/hack/generator/pkg/codegen/pipeline/create_storage_types.go b/hack/generator/pkg/codegen/pipeline/create_storage_types.go index 311ca257820..ced478f5796 100644 --- a/hack/generator/pkg/codegen/pipeline/create_storage_types.go +++ b/hack/generator/pkg/codegen/pipeline/create_storage_types.go @@ -14,14 +14,14 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/codegen/storage" ) -const CreateStorageTypesStageId = "createStorageTypes" +const CreateStorageTypesStageID = "createStorageTypes" // CreateStorageTypes returns a pipeline stage that creates dedicated storage types for each resource and nested object. // Storage versions are created for *all* API versions to allow users of older versions of the operator to easily // upgrade. This is of course a bit odd for the first release, but defining the approach from day one is useful. func CreateStorageTypes() Stage { - result := MakeStage( - CreateStorageTypesStageId, + stage := MakeStage( + CreateStorageTypesStageID, "Create storage versions of CRD types", func(ctx context.Context, state *State) (*State, error) { @@ -59,6 +59,6 @@ func CreateStorageTypes() Stage { return state.WithTypes(types), nil }) - result.RequiresPrerequisiteStages(InjectOriginalVersionFunctionStageId, CreateConversionGraphStageId) - return result + stage.RequiresPrerequisiteStages(InjectOriginalVersionFunctionStageID, CreateConversionGraphStageId) + return stage } diff --git a/hack/generator/pkg/codegen/pipeline/delete_generated_code.go b/hack/generator/pkg/codegen/pipeline/delete_generated_code.go index 4b21c1c652f..bbaaeb55764 100644 --- a/hack/generator/pkg/codegen/pipeline/delete_generated_code.go +++ b/hack/generator/pkg/codegen/pipeline/delete_generated_code.go @@ -23,10 +23,13 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" ) +// DeleteGeneratedCodeStageID is the unique identifier of this stage +const DeleteGeneratedCodeStageID = "deleteGenerated" + // DeleteGeneratedCode creates a pipeline stage for cleanup of our output folder prior to generating files func DeleteGeneratedCode(outputFolder string) Stage { return MakeLegacyStage( - "deleteGenerated", + DeleteGeneratedCodeStageID, "Delete generated code from "+outputFolder, func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { err := deleteGeneratedCodeFromFolder(ctx, outputFolder) diff --git a/hack/generator/pkg/codegen/pipeline/export_generated_code.go b/hack/generator/pkg/codegen/pipeline/export_generated_code.go index 1cb87b62015..7256bd7aff2 100644 --- a/hack/generator/pkg/codegen/pipeline/export_generated_code.go +++ b/hack/generator/pkg/codegen/pipeline/export_generated_code.go @@ -21,11 +21,14 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" ) +// ExportPackagesStageID is the unique identifier for this pipeline stage +const ExportPackagesStageID = "exportPackages" + // ExportPackages creates a Stage to export our generated code as a set of packages func ExportPackages(outputPath string) Stage { description := fmt.Sprintf("Export packages to %q", outputPath) - return MakeLegacyStage( - "exportPackages", + stage := MakeLegacyStage( + ExportPackagesStageID, description, func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { packages, err := CreatePackagesForDefinitions(types) @@ -40,6 +43,8 @@ func ExportPackages(outputPath string) Stage { return types, nil }) + stage.RequiresPrerequisiteStages(DeleteGeneratedCodeStageID) + return stage } // CreatePackagesForDefinitions groups type definitions into packages diff --git a/hack/generator/pkg/codegen/pipeline/implement_convertible_interface.go b/hack/generator/pkg/codegen/pipeline/implement_convertible_interface.go new file mode 100644 index 00000000000..a13e3b30173 --- /dev/null +++ b/hack/generator/pkg/codegen/pipeline/implement_convertible_interface.go @@ -0,0 +1,85 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package pipeline + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" + "github.com/Azure/azure-service-operator/hack/generator/pkg/codegen/storage" + "github.com/Azure/azure-service-operator/hack/generator/pkg/functions" +) + +// ImplementConvertibleInterfaceStageId is the unique identifier for this pipeline stage +const ImplementConvertibleInterfaceStageId = "implementConvertibleInterface" + +// ImplementConvertibleInterface injects the functions ConvertTo() and ConvertFrom() into each non-hub Resource +// Type, providing the required implementation of the Convertible interface needed by the controller +func ImplementConvertibleInterface(idFactory astmodel.IdentifierFactory) Stage { + + stage := MakeStage( + ImplementConvertibleInterfaceStageId, + "Implement the Convertible interface on each non-hub Resource type", + func(ctx context.Context, state *State) (*State, error) { + injector := astmodel.NewInterfaceInjector() + + modifiedTypes := make(astmodel.Types) + resources := astmodel.FindResourceTypes(state.Types()) + for name, def := range resources { + resource, ok := astmodel.AsResourceType(def.Type()) + if !ok { + // Skip non-resources (though, they should be filtered out, above) + continue + } + + if resource.IsStorageVersion() { + // The hub storage version doesn't implement Convertible + continue + } + + convertible := createConvertibleInterfaceImplementation( + name, resource, state.ConversionGraph(), idFactory) + if convertible.FunctionCount() > 0 { + modified, err := injector.Inject(def, convertible) + if err != nil { + return nil, errors.Wrapf(err, "injecting Convertible interface into %s", name) + } + + modifiedTypes.Add(modified) + } + } + + newTypes := state.Types().OverlayWith(modifiedTypes) + return state.WithTypes(newTypes), nil + }) + + stage.RequiresPrerequisiteStages(InjectPropertyAssignmentFunctionsStageID) + return stage +} + +// createConvertibleInterfaceImplementation creates the required implementation of conversion.Convertible, ready for +// injection onto the resource. The ConvertTo() and ConvertFrom() methods chain the required conversion between resource +// versions, but are dependent upon previously injected AssignPropertiesTo() and AssignPropertiesFrom() methods to +// actually copy information across. See resource_conversion_function.go for more information. +func createConvertibleInterfaceImplementation( + name astmodel.TypeName, + resource *astmodel.ResourceType, + conversionGraph *storage.ConversionGraph, + idFactory astmodel.IdentifierFactory) *astmodel.InterfaceImplementation { + var conversionFunctions []astmodel.Function + + for _, fn := range resource.Functions() { + if propertyAssignmentFn, ok := fn.(*functions.PropertyAssignmentFunction); ok { + hub := conversionGraph.FindHubTypeName(name) + conversionFn := functions.NewResourceConversionFunction(hub, propertyAssignmentFn, idFactory) + conversionFunctions = append(conversionFunctions, conversionFn) + } + } + + return astmodel.NewInterfaceImplementation(astmodel.ConvertibleInterface, conversionFunctions...) +} diff --git a/hack/generator/pkg/codegen/pipeline/implement_convertible_interface_test.go b/hack/generator/pkg/codegen/pipeline/implement_convertible_interface_test.go new file mode 100644 index 00000000000..35a17f94397 --- /dev/null +++ b/hack/generator/pkg/codegen/pipeline/implement_convertible_interface_test.go @@ -0,0 +1,64 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package pipeline + +import ( + "context" + "testing" + + . "github.com/onsi/gomega" + + "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" + "github.com/Azure/azure-service-operator/hack/generator/pkg/test" +) + +// TestInjectConvertibleInterface checks that the pipeline stage does what we expect when run in relative isolation, +// with only a few expected (and closely reated) stages in operation +func TestInjectConvertibleInterface(t *testing.T) { + g := NewGomegaWithT(t) + + idFactory := astmodel.NewIdentifierFactory() + // Test Resource V1 + + specV1 := test.CreateSpec(test.Pkg2020, "Person", test.FullNameProperty) + statusV1 := test.CreateStatus(test.Pkg2020, "Person") + resourceV1 := test.CreateResource(test.Pkg2020, "Person", specV1, statusV1) + + // Test Resource V2 + + specV2 := test.CreateSpec(test.Pkg2021, "Person", test.FullNameProperty) + statusV2 := test.CreateStatus(test.Pkg2021, "Person") + resourceV2 := test.CreateResource(test.Pkg2021, "Person", specV2, statusV2) + + types := make(astmodel.Types) + types.AddAll(resourceV1, specV1, statusV1, resourceV2, specV2, statusV2) + + state := NewState().WithTypes(types) + + // Run CreateConversionGraph first to populate the conversion graph + createConversionGraph := CreateConversionGraph() + state, err := createConversionGraph.Run(context.TODO(), state) + g.Expect(err).To(Succeed()) + + // Run CreateStorageTypes to create additional resources into which we will inject + createStorageTypes := CreateStorageTypes() + state, err = createStorageTypes.Run(context.TODO(), state) + g.Expect(err).To(Succeed()) + + // Run InjectPropertyAssignmentFunctions to create those functions + injectPropertyFns := InjectPropertyAssignmentFunctions(idFactory) + state, err = injectPropertyFns.Run(context.TODO(), state) + g.Expect(err).To(Succeed()) + + // Now run our stage + injectFunctions := ImplementConvertibleInterface(idFactory) + state, err = injectFunctions.Run(context.TODO(), state) + g.Expect(err).To(Succeed()) + + // When verifying the golden file, check that the implementations of ConvertTo() and ConvertFrom() are what you + // expect - if you don't have expectations, check that they do the right thing. + test.AssertPackagesGenerateExpectedCode(t, state.Types(), t.Name()) +} diff --git a/hack/generator/pkg/codegen/pipeline/improve_resource_pluralization.go b/hack/generator/pkg/codegen/pipeline/improve_resource_pluralization.go index 00e495f6fbe..14cd13f7ab5 100644 --- a/hack/generator/pkg/codegen/pipeline/improve_resource_pluralization.go +++ b/hack/generator/pkg/codegen/pipeline/improve_resource_pluralization.go @@ -14,7 +14,7 @@ import ( // ImproveResourcePluralization improves pluralization for resources func ImproveResourcePluralization() Stage { - return MakeLegacyStage( + stage := MakeLegacyStage( "pluralizeNames", "Improve resource pluralization", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { @@ -49,6 +49,9 @@ func ImproveResourcePluralization() Stage { // rare since usually resources don't refer directly to other resources, but there are a few places it does happen. return fixNameReferences(result, renames) }) + + stage.RequiresPrerequisiteStages(RemoveTypeAliasesStageID) + return stage } func fixNameReferences(types astmodel.Types, renames map[astmodel.TypeName]astmodel.TypeName) (astmodel.Types, error) { diff --git a/hack/generator/pkg/codegen/pipeline/inject_hub_function.go b/hack/generator/pkg/codegen/pipeline/inject_hub_function.go index d69d2d1899f..f74878900c0 100644 --- a/hack/generator/pkg/codegen/pipeline/inject_hub_function.go +++ b/hack/generator/pkg/codegen/pipeline/inject_hub_function.go @@ -11,25 +11,24 @@ import ( "github.com/pkg/errors" "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" - "github.com/Azure/azure-service-operator/hack/generator/pkg/codegen/storage" "github.com/Azure/azure-service-operator/hack/generator/pkg/functions" ) -// InjectHubFunctionStageId is the unique identifier for this pipeline stage -const InjectHubFunctionStageId = "injectHubFunction" +// InjectHubFunctionStageID is the unique identifier for this pipeline stage +const InjectHubFunctionStageID = "injectHubFunction" // InjectHubFunction modifies the nominates storage version (aka hub version) of each resource by injecting a Hub() // function so that it satisfies the required interface. func InjectHubFunction(idFactory astmodel.IdentifierFactory) Stage { - result := MakeLegacyStage( - InjectHubFunctionStageId, + stage := MakeLegacyStage( + InjectHubFunctionStageID, "Inject the function Hub() into each hub resource", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { - injector := storage.NewFunctionInjector() + injector := astmodel.NewFunctionInjector() result := types.Copy() - resources := storage.FindResourceTypes(types) + resources := astmodel.FindResourceTypes(types) for name, def := range resources { rt, ok := astmodel.AsResourceType(def.Type()) if !ok { @@ -50,6 +49,6 @@ func InjectHubFunction(idFactory astmodel.IdentifierFactory) Stage { return result, nil }) - result.RequiresPrerequisiteStages(MarkStorageVersionStageId) - return result + stage.RequiresPrerequisiteStages(MarkStorageVersionStageId) + return stage } diff --git a/hack/generator/pkg/codegen/pipeline/inject_original_gvk_function.go b/hack/generator/pkg/codegen/pipeline/inject_original_gvk_function.go index 78e698a14e5..7d25dfa6987 100644 --- a/hack/generator/pkg/codegen/pipeline/inject_original_gvk_function.go +++ b/hack/generator/pkg/codegen/pipeline/inject_original_gvk_function.go @@ -11,12 +11,11 @@ import ( "github.com/pkg/errors" "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" - "github.com/Azure/azure-service-operator/hack/generator/pkg/codegen/storage" "github.com/Azure/azure-service-operator/hack/generator/pkg/functions" ) -// injectOriginalGVKFunctionId is the unique identifier for this pipeline stage -const injectOriginalGVKFunctionId = "injectOriginalGVKFunction" +// InjectOriginalGVKFunctionStageID is the unique identifier for this pipeline stage +const InjectOriginalGVKFunctionStageID = "injectOriginalGVKFunction" // InjectOriginalGVKFunction injects the function OriginalGVK() into each Resource type // This function allows us to recover the original version used to create each custom resource, giving the operator the @@ -24,13 +23,13 @@ const injectOriginalGVKFunctionId = "injectOriginalGVKFunction" func InjectOriginalGVKFunction(idFactory astmodel.IdentifierFactory) Stage { stage := MakeLegacyStage( - injectOriginalGVKFunctionId, + InjectOriginalGVKFunctionStageID, "Inject the function OriginalGVK() into each Resource type", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { - injector := storage.NewFunctionInjector() + injector := astmodel.NewFunctionInjector() result := types.Copy() - resources := storage.FindResourceTypes(types) + resources := astmodel.FindResourceTypes(types) for name, def := range resources { var fn *functions.OriginalGVKFunction if astmodel.IsStoragePackageReference(name.PackageReference) { @@ -50,6 +49,6 @@ func InjectOriginalGVKFunction(idFactory astmodel.IdentifierFactory) Stage { return result, nil }) - stage.RequiresPrerequisiteStages(InjectOriginalVersionFunctionStageId) + stage.RequiresPrerequisiteStages(InjectOriginalVersionFunctionStageID) return stage } diff --git a/hack/generator/pkg/codegen/pipeline/inject_original_version_function.go b/hack/generator/pkg/codegen/pipeline/inject_original_version_function.go index 41667b95638..d8a7f5b566e 100644 --- a/hack/generator/pkg/codegen/pipeline/inject_original_version_function.go +++ b/hack/generator/pkg/codegen/pipeline/inject_original_version_function.go @@ -11,12 +11,11 @@ import ( "github.com/pkg/errors" "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" - "github.com/Azure/azure-service-operator/hack/generator/pkg/codegen/storage" "github.com/Azure/azure-service-operator/hack/generator/pkg/functions" ) -// InjectOriginalVersionFunctionStageId is the unique identifier for this pipeline stage -const InjectOriginalVersionFunctionStageId = "injectOriginalVersionFunction" +// InjectOriginalVersionFunctionStageID is the unique identifier for this pipeline stage +const InjectOriginalVersionFunctionStageID = "injectOriginalVersionFunction" // InjectOriginalVersionFunction injects the function OriginalVersion() into each Spec type // This function allows us to recover the original version used to create each custom resource, giving the operator the @@ -25,13 +24,13 @@ const InjectOriginalVersionFunctionStageId = "injectOriginalVersionFunction" func InjectOriginalVersionFunction(idFactory astmodel.IdentifierFactory) Stage { stage := MakeLegacyStage( - InjectOriginalVersionFunctionStageId, + InjectOriginalVersionFunctionStageID, "Inject the function OriginalVersion() into each Spec type", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { - injector := storage.NewFunctionInjector() + injector := astmodel.NewFunctionInjector() result := types.Copy() - specs := storage.FindSpecTypes(types) + specs := astmodel.FindSpecTypes(types) for name, def := range specs { fn := functions.NewOriginalVersionFunction(idFactory) defWithFn, err := injector.Inject(def, fn) @@ -45,6 +44,6 @@ func InjectOriginalVersionFunction(idFactory astmodel.IdentifierFactory) Stage { return result, nil }) - stage.RequiresPostrequisiteStages(CreateStorageTypesStageId, InjectOriginalVersionPropertyId) + stage.RequiresPostrequisiteStages(CreateStorageTypesStageID, InjectOriginalVersionPropertyStageID) return stage } diff --git a/hack/generator/pkg/codegen/pipeline/inject_original_version_property.go b/hack/generator/pkg/codegen/pipeline/inject_original_version_property.go index 817875930d7..5e1c398fea6 100644 --- a/hack/generator/pkg/codegen/pipeline/inject_original_version_property.go +++ b/hack/generator/pkg/codegen/pipeline/inject_original_version_property.go @@ -11,11 +11,10 @@ import ( "github.com/pkg/errors" "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" - "github.com/Azure/azure-service-operator/hack/generator/pkg/codegen/storage" ) -// InjectOriginalVersionPropertyId is the unique identifier for this pipeline stage -const InjectOriginalVersionPropertyId = "injectOriginalVersionProperty" +// InjectOriginalVersionPropertyStageID is the unique identifier for this pipeline stage +const InjectOriginalVersionPropertyStageID = "injectOriginalVersionProperty" // InjectOriginalVersionProperty injects the property OriginalVersion into each Storage Spec type // This property gets populated by reading from the OriginalVersion() function previously injected into the API Spec @@ -24,10 +23,10 @@ const InjectOriginalVersionPropertyId = "injectOriginalVersionProperty" func InjectOriginalVersionProperty() Stage { stage := MakeLegacyStage( - InjectOriginalVersionPropertyId, + InjectOriginalVersionPropertyStageID, "Inject the property OriginalVersion into each Storage Spec type", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { - injector := storage.NewPropertyInjector() + injector := astmodel.NewPropertyInjector() result := types.Copy() doesNotHaveOriginalVersionFunction := func(definition astmodel.TypeDefinition) bool { @@ -41,7 +40,7 @@ func InjectOriginalVersionProperty() Stage { return !ot.HasFunctionWithName("OriginalVersion") } - storageSpecs := storage.FindSpecTypes(types).Where(doesNotHaveOriginalVersionFunction) + storageSpecs := astmodel.FindSpecTypes(types).Where(doesNotHaveOriginalVersionFunction) for name, def := range storageSpecs { prop := astmodel.NewPropertyDefinition("OriginalVersion", "originalVersion", astmodel.StringType) @@ -56,6 +55,6 @@ func InjectOriginalVersionProperty() Stage { return result, nil }) - stage.RequiresPrerequisiteStages(InjectOriginalVersionFunctionStageId) + stage.RequiresPrerequisiteStages(InjectOriginalVersionFunctionStageID) return stage } diff --git a/hack/generator/pkg/codegen/pipeline/inject_original_version_property_test.go b/hack/generator/pkg/codegen/pipeline/inject_original_version_property_test.go index 271698b9aa4..65d11e4fbd5 100644 --- a/hack/generator/pkg/codegen/pipeline/inject_original_version_property_test.go +++ b/hack/generator/pkg/codegen/pipeline/inject_original_version_property_test.go @@ -12,7 +12,6 @@ import ( . "github.com/onsi/gomega" "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" - "github.com/Azure/azure-service-operator/hack/generator/pkg/codegen/storage" "github.com/Azure/azure-service-operator/hack/generator/pkg/functions" "github.com/Azure/azure-service-operator/hack/generator/pkg/test" ) @@ -43,7 +42,7 @@ func TestInjectOriginalVersionProperty_WhenOriginalVersionFunctionFound_DoesNotI g := NewGomegaWithT(t) idFactory := astmodel.NewIdentifierFactory() - fnInjector := storage.NewFunctionInjector() + fnInjector := astmodel.NewFunctionInjector() // Define a test resource spec := test.CreateSpec(test.Pkg2020, "Person", test.FullNameProperty, test.FamilyNameProperty, test.KnownAsProperty) diff --git a/hack/generator/pkg/codegen/pipeline/inject_property_assignment_functions.go b/hack/generator/pkg/codegen/pipeline/inject_property_assignment_functions.go index 39369e6d91d..bcb6a9bd1cd 100644 --- a/hack/generator/pkg/codegen/pipeline/inject_property_assignment_functions.go +++ b/hack/generator/pkg/codegen/pipeline/inject_property_assignment_functions.go @@ -17,8 +17,8 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/functions" ) -// InjectPropertyAssignmentFunctionsStageId is the unique identifier for this pipeline stage -const InjectPropertyAssignmentFunctionsStageId = "injectPropertyAssignmentFunctions" +// InjectPropertyAssignmentFunctionsStageID is the unique identifier for this pipeline stage +const InjectPropertyAssignmentFunctionsStageID = "injectPropertyAssignmentFunctions" // InjectPropertyAssignmentFunctions injects property assignment functions AssignTo*() and AssignFrom*() into both // resources and object types. These functions do the heavy lifting of the conversions between versions of each type and @@ -26,7 +26,7 @@ const InjectPropertyAssignmentFunctionsStageId = "injectPropertyAssignmentFuncti func InjectPropertyAssignmentFunctions(idFactory astmodel.IdentifierFactory) Stage { stage := MakeStage( - InjectPropertyAssignmentFunctionsStageId, + InjectPropertyAssignmentFunctionsStageID, "Inject property assignment functions AssignFrom() and AssignTo() into resources and objects", func(ctx context.Context, state *State) (*State, error) { @@ -72,7 +72,7 @@ func InjectPropertyAssignmentFunctions(idFactory astmodel.IdentifierFactory) Sta }) // Needed to populate the conversion graph - stage.RequiresPrerequisiteStages(CreateStorageTypesStageId) + stage.RequiresPrerequisiteStages(CreateStorageTypesStageID) return stage } @@ -80,7 +80,7 @@ type propertyAssignmentFunctionsFactory struct { graph *storage.ConversionGraph idFactory astmodel.IdentifierFactory types astmodel.Types - functionInjector *storage.FunctionInjector + functionInjector *astmodel.FunctionInjector } func NewPropertyAssignmentFunctionsFactory( @@ -91,7 +91,7 @@ func NewPropertyAssignmentFunctionsFactory( graph: graph, idFactory: idFactory, types: types, - functionInjector: storage.NewFunctionInjector(), + functionInjector: astmodel.NewFunctionInjector(), } } diff --git a/hack/generator/pkg/codegen/pipeline/load_schema.go b/hack/generator/pkg/codegen/pipeline/load_schema.go index 949af0d9acd..d93ef61711b 100644 --- a/hack/generator/pkg/codegen/pipeline/load_schema.go +++ b/hack/generator/pkg/codegen/pipeline/load_schema.go @@ -134,6 +134,9 @@ func DefaultSchemaLoader(ctx context.Context, rewrite *config.RewriteRule, sourc return schema, nil } +// LoadSchemaIntoTypesStageID is the unique identifier for this pipeline stage +const LoadSchemaIntoTypesStageID = "loadSchema" + func LoadSchemaIntoTypes( idFactory astmodel.IdentifierFactory, configuration *config.Configuration, @@ -141,7 +144,7 @@ func LoadSchemaIntoTypes( source := configuration.SchemaURL return MakeLegacyStage( - "loadSchema", + LoadSchemaIntoTypesStageID, "Load and walk schema", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { klog.V(0).Infof("Loading JSON schema %q", source) diff --git a/hack/generator/pkg/codegen/pipeline/remove_embedded_resources.go b/hack/generator/pkg/codegen/pipeline/remove_embedded_resources.go index 297f185ddac..a7027db2166 100644 --- a/hack/generator/pkg/codegen/pipeline/remove_embedded_resources.go +++ b/hack/generator/pkg/codegen/pipeline/remove_embedded_resources.go @@ -12,9 +12,12 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/codegen/embeddedresources" ) +// RemoveEmbeddedResourcesStageID is the unique identifier for this pipeline stage +const RemoveEmbeddedResourcesStageID = "removeEmbeddedResources" + func RemoveEmbeddedResources() Stage { return MakeLegacyStage( - "removeEmbeddedResources", + RemoveEmbeddedResourcesStageID, "Remove properties that point to embedded resources. Only removes structural aspects of embedded resources, Id/ARMId references are retained.", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { diff --git a/hack/generator/pkg/codegen/pipeline/remove_type_aliases.go b/hack/generator/pkg/codegen/pipeline/remove_type_aliases.go index 50e714cf721..64d7a4c1671 100644 --- a/hack/generator/pkg/codegen/pipeline/remove_type_aliases.go +++ b/hack/generator/pkg/codegen/pipeline/remove_type_aliases.go @@ -17,10 +17,13 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" ) +// RemoveTypeAliasesStageID is the unique identifier for this pipeline stage +const RemoveTypeAliasesStageID = "removeAliases" + // RemoveTypeAliases creates a pipeline stage removing type aliases func RemoveTypeAliases() Stage { return MakeLegacyStage( - "removeAliases", + RemoveTypeAliasesStageID, "Remove type aliases", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { diff --git a/hack/generator/pkg/codegen/pipeline/report_type_versions.go b/hack/generator/pkg/codegen/pipeline/report_type_versions.go index 07b731e1069..965037d458d 100644 --- a/hack/generator/pkg/codegen/pipeline/report_type_versions.go +++ b/hack/generator/pkg/codegen/pipeline/report_type_versions.go @@ -20,11 +20,14 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/reporting" ) +// ReportOnTypesAndVersionsStageID is the unique identifier of this stage +const ReportOnTypesAndVersionsStageID = "reportTypesAndVersions" + // ReportOnTypesAndVersions creates a pipeline stage that removes any wrapper types prior to actual code generation func ReportOnTypesAndVersions(configuration *config.Configuration) Stage { return MakeLegacyStage( - "reportTypesAndVersions", + ReportOnTypesAndVersionsStageID, "Generate reports on types and versions in each package", func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) { report := NewPackagesMatrixReport() diff --git a/hack/generator/pkg/codegen/pipeline/strip_unused_types.go b/hack/generator/pkg/codegen/pipeline/strip_unused_types.go index 94f16eb9dda..88de3cbe016 100644 --- a/hack/generator/pkg/codegen/pipeline/strip_unused_types.go +++ b/hack/generator/pkg/codegen/pipeline/strip_unused_types.go @@ -11,9 +11,12 @@ import ( "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" ) +// StripUnreferencedTypeDefinitionsStageID is the unique identifier for this pipeline stage +const StripUnreferencedTypeDefinitionsStageID = "stripUnreferenced" + func StripUnreferencedTypeDefinitions() Stage { return MakeLegacyStage( - "stripUnreferenced", + StripUnreferencedTypeDefinitionsStageID, "Strip unreferenced types", func(ctx context.Context, defs astmodel.Types) (astmodel.Types, error) { resources := astmodel.CollectResourceDefinitions(defs) diff --git a/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20200101.golden b/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20200101.golden new file mode 100644 index 00000000000..02a0eee4e69 --- /dev/null +++ b/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20200101.golden @@ -0,0 +1,172 @@ +// Code generated by azure-service-operator-codegen. DO NOT EDIT. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +package v20200101 + +import ( + "github.com/Azure/azure-service-operator/testing/microsoft.person/v20200101storage" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/conversion" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type Person struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec Person_Spec `json:"spec,omitempty"` + Status Person_Status `json:"status,omitempty"` +} + +var _ conversion.Convertible = &Person{} + +// ConvertFrom populates our Person from the provided hub Person +func (person *Person) ConvertFrom(hub conversion.Hub) error { + // intermediate variable for conversion + var source v20200101storage.Person + + err := source.ConvertFrom(hub) + if err != nil { + return errors.Wrap(err, "converting from hub to source") + } + + err = person.AssignPropertiesFromPerson(&source) + if err != nil { + return errors.Wrap(err, "converting from source to person") + } + + return nil +} + +// ConvertTo populates the provided hub Person from our Person +func (person *Person) ConvertTo(hub conversion.Hub) error { + // intermediate variable for conversion + var destination v20200101storage.Person + err := person.AssignPropertiesToPerson(&destination) + if err != nil { + return errors.Wrap(err, "converting to destination from person") + } + err = destination.ConvertTo(hub) + if err != nil { + return errors.Wrap(err, "converting from destination to hub") + } + + return nil +} + +// AssignPropertiesFromPerson populates our Person from the provided source Person +func (person *Person) AssignPropertiesFromPerson(source *v20200101storage.Person) error { + + // Spec + var spec Person_Spec + err := spec.AssignPropertiesFromPersonSpec(&source.Spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesFromPersonSpec()") + } + person.Spec = spec + + // Status + var status Person_Status + err = status.AssignPropertiesFromPersonStatus(&source.Status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesFromPersonStatus()") + } + person.Status = status + + // No error + return nil +} + +// AssignPropertiesToPerson populates the provided destination Person from our Person +func (person *Person) AssignPropertiesToPerson(destination *v20200101storage.Person) error { + + // Spec + var spec v20200101storage.Person_Spec + err := person.Spec.AssignPropertiesToPersonSpec(&spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesToPersonSpec()") + } + destination.Spec = spec + + // Status + var status v20200101storage.Person_Status + err = person.Status.AssignPropertiesToPersonStatus(&status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesToPersonStatus()") + } + destination.Status = status + + // No error + return nil +} + +// +kubebuilder:object:root=true +type PersonList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Person `json:"items"` +} + +type Person_Spec struct { + //FullName: As would be used to address mail + FullName string `json:"fullName"` +} + +// AssignPropertiesFromPersonSpec populates our Person_Spec from the provided source Person_Spec +func (personSpec *Person_Spec) AssignPropertiesFromPersonSpec(source *v20200101storage.Person_Spec) error { + + // FullName + if source.FullName != nil { + personSpec.FullName = *source.FullName + } else { + personSpec.FullName = "" + } + + // No error + return nil +} + +// AssignPropertiesToPersonSpec populates the provided destination Person_Spec from our Person_Spec +func (personSpec *Person_Spec) AssignPropertiesToPersonSpec(destination *v20200101storage.Person_Spec) error { + + // FullName + fullName := personSpec.FullName + destination.FullName = &fullName + + // No error + return nil +} + +type Person_Status struct { + Status string `json:"status"` +} + +// AssignPropertiesFromPersonStatus populates our Person_Status from the provided source Person_Status +func (personStatus *Person_Status) AssignPropertiesFromPersonStatus(source *v20200101storage.Person_Status) error { + + // Status + if source.Status != nil { + personStatus.Status = *source.Status + } else { + personStatus.Status = "" + } + + // No error + return nil +} + +// AssignPropertiesToPersonStatus populates the provided destination Person_Status from our Person_Status +func (personStatus *Person_Status) AssignPropertiesToPersonStatus(destination *v20200101storage.Person_Status) error { + + // Status + status := personStatus.Status + destination.Status = &status + + // No error + return nil +} + +func init() { + SchemeBuilder.Register(&Person{}, &PersonList{}) +} diff --git a/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20200101storage.golden b/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20200101storage.golden new file mode 100644 index 00000000000..ea345158ccc --- /dev/null +++ b/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20200101storage.golden @@ -0,0 +1,171 @@ +// Code generated by azure-service-operator-codegen. DO NOT EDIT. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +package v20200101storage + +import ( + "fmt" + "github.com/Azure/azure-service-operator/testing/microsoft.person/v20200101storage" + "github.com/Azure/azure-service-operator/testing/microsoft.person/v20211231storage" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/conversion" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +//Storage version of v20200101.Person +type Person struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec v20200101storage.Person_Spec `json:"spec,omitempty"` + Status v20200101storage.Person_Status `json:"status,omitempty"` +} + +var _ conversion.Convertible = &Person{} + +// ConvertFrom populates our Person from the provided hub Person +func (person *v20200101storage.Person) ConvertFrom(hub conversion.Hub) error { + source, ok := hub.(*v20211231storage.Person) + if !ok { + return fmt.Errorf("expected storage:microsoft.person/v20211231storage/Person but received %T instead", hub) + } + return person.AssignPropertiesFromPerson(source) +} + +// ConvertTo populates the provided hub Person from our Person +func (person *v20200101storage.Person) ConvertTo(hub conversion.Hub) error { + destination, ok := hub.(*v20211231storage.Person) + if !ok { + return fmt.Errorf("expected storage:microsoft.person/v20211231storage/Person but received %T instead", hub) + } + return person.AssignPropertiesToPerson(destination) +} + +// AssignPropertiesFromPerson populates our Person from the provided source Person +func (person *v20200101storage.Person) AssignPropertiesFromPerson(source *v20211231storage.Person) error { + + // Spec + var spec Person_Spec + err := spec.AssignPropertiesFromPersonSpec(&source.Spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesFromPersonSpec()") + } + person.Spec = spec + + // Status + var status Person_Status + err = status.AssignPropertiesFromPersonStatus(&source.Status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesFromPersonStatus()") + } + person.Status = status + + // No error + return nil +} + +// AssignPropertiesToPerson populates the provided destination Person from our Person +func (person *v20200101storage.Person) AssignPropertiesToPerson(destination *v20211231storage.Person) error { + + // Spec + var spec v20211231storage.Person_Spec + err := person.Spec.AssignPropertiesToPersonSpec(&spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesToPersonSpec()") + } + destination.Spec = spec + + // Status + var status v20211231storage.Person_Status + err = person.Status.AssignPropertiesToPersonStatus(&status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesToPersonStatus()") + } + destination.Status = status + + // No error + return nil +} + +// +kubebuilder:object:root=true +//Storage version of v20200101.Person +type PersonList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []v20200101storage.Person `json:"items"` +} + +//Storage version of v20200101.Person_Spec +type Person_Spec struct { + FullName *string `json:"fullName,omitempty"` +} + +// AssignPropertiesFromPersonSpec populates our Person_Spec from the provided source Person_Spec +func (personSpec *v20200101storage.Person_Spec) AssignPropertiesFromPersonSpec(source *v20211231storage.Person_Spec) error { + + // FullName + if source.FullName != nil { + fullName := *source.FullName + personSpec.FullName = &fullName + } else { + personSpec.FullName = nil + } + + // No error + return nil +} + +// AssignPropertiesToPersonSpec populates the provided destination Person_Spec from our Person_Spec +func (personSpec *v20200101storage.Person_Spec) AssignPropertiesToPersonSpec(destination *v20211231storage.Person_Spec) error { + + // FullName + if personSpec.FullName != nil { + fullName := *personSpec.FullName + destination.FullName = &fullName + } else { + destination.FullName = nil + } + + // No error + return nil +} + +//Storage version of v20200101.Person_Status +type Person_Status struct { + Status *string `json:"status,omitempty"` +} + +// AssignPropertiesFromPersonStatus populates our Person_Status from the provided source Person_Status +func (personStatus *v20200101storage.Person_Status) AssignPropertiesFromPersonStatus(source *v20211231storage.Person_Status) error { + + // Status + if source.Status != nil { + status := *source.Status + personStatus.Status = &status + } else { + personStatus.Status = nil + } + + // No error + return nil +} + +// AssignPropertiesToPersonStatus populates the provided destination Person_Status from our Person_Status +func (personStatus *v20200101storage.Person_Status) AssignPropertiesToPersonStatus(destination *v20211231storage.Person_Status) error { + + // Status + if personStatus.Status != nil { + status := *personStatus.Status + destination.Status = &status + } else { + destination.Status = nil + } + + // No error + return nil +} + +func init() { + SchemeBuilder.Register(&v20200101storage.Person{}, &v20200101storage.PersonList{}) +} diff --git a/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20211231.golden b/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20211231.golden new file mode 100644 index 00000000000..b8cf0ab8f4d --- /dev/null +++ b/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20211231.golden @@ -0,0 +1,157 @@ +// Code generated by azure-service-operator-codegen. DO NOT EDIT. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +package v20211231 + +import ( + "fmt" + "github.com/Azure/azure-service-operator/testing/microsoft.person/v20211231storage" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/conversion" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type Person struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec Person_Spec `json:"spec,omitempty"` + Status Person_Status `json:"status,omitempty"` +} + +var _ conversion.Convertible = &Person{} + +// ConvertFrom populates our Person from the provided hub Person +func (person *Person) ConvertFrom(hub conversion.Hub) error { + source, ok := hub.(*v20211231storage.Person) + if !ok { + return fmt.Errorf("expected storage:microsoft.person/v20211231storage/Person but received %T instead", hub) + } + return person.AssignPropertiesFromPerson(source) +} + +// ConvertTo populates the provided hub Person from our Person +func (person *Person) ConvertTo(hub conversion.Hub) error { + destination, ok := hub.(*v20211231storage.Person) + if !ok { + return fmt.Errorf("expected storage:microsoft.person/v20211231storage/Person but received %T instead", hub) + } + return person.AssignPropertiesToPerson(destination) +} + +// AssignPropertiesFromPerson populates our Person from the provided source Person +func (person *Person) AssignPropertiesFromPerson(source *v20211231storage.Person) error { + + // Spec + var spec Person_Spec + err := spec.AssignPropertiesFromPersonSpec(&source.Spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesFromPersonSpec()") + } + person.Spec = spec + + // Status + var status Person_Status + err = status.AssignPropertiesFromPersonStatus(&source.Status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesFromPersonStatus()") + } + person.Status = status + + // No error + return nil +} + +// AssignPropertiesToPerson populates the provided destination Person from our Person +func (person *Person) AssignPropertiesToPerson(destination *v20211231storage.Person) error { + + // Spec + var spec v20211231storage.Person_Spec + err := person.Spec.AssignPropertiesToPersonSpec(&spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesToPersonSpec()") + } + destination.Spec = spec + + // Status + var status v20211231storage.Person_Status + err = person.Status.AssignPropertiesToPersonStatus(&status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesToPersonStatus()") + } + destination.Status = status + + // No error + return nil +} + +// +kubebuilder:object:root=true +type PersonList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Person `json:"items"` +} + +type Person_Spec struct { + //FullName: As would be used to address mail + FullName string `json:"fullName"` +} + +// AssignPropertiesFromPersonSpec populates our Person_Spec from the provided source Person_Spec +func (personSpec *Person_Spec) AssignPropertiesFromPersonSpec(source *v20211231storage.Person_Spec) error { + + // FullName + if source.FullName != nil { + personSpec.FullName = *source.FullName + } else { + personSpec.FullName = "" + } + + // No error + return nil +} + +// AssignPropertiesToPersonSpec populates the provided destination Person_Spec from our Person_Spec +func (personSpec *Person_Spec) AssignPropertiesToPersonSpec(destination *v20211231storage.Person_Spec) error { + + // FullName + fullName := personSpec.FullName + destination.FullName = &fullName + + // No error + return nil +} + +type Person_Status struct { + Status string `json:"status"` +} + +// AssignPropertiesFromPersonStatus populates our Person_Status from the provided source Person_Status +func (personStatus *Person_Status) AssignPropertiesFromPersonStatus(source *v20211231storage.Person_Status) error { + + // Status + if source.Status != nil { + personStatus.Status = *source.Status + } else { + personStatus.Status = "" + } + + // No error + return nil +} + +// AssignPropertiesToPersonStatus populates the provided destination Person_Status from our Person_Status +func (personStatus *Person_Status) AssignPropertiesToPersonStatus(destination *v20211231storage.Person_Status) error { + + // Status + status := personStatus.Status + destination.Status = &status + + // No error + return nil +} + +func init() { + SchemeBuilder.Register(&Person{}, &PersonList{}) +} diff --git a/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20211231storage.golden b/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20211231storage.golden new file mode 100644 index 00000000000..4b6f3f1daaa --- /dev/null +++ b/hack/generator/pkg/codegen/pipeline/testdata/TestInjectConvertibleInterface-v20211231storage.golden @@ -0,0 +1,41 @@ +// Code generated by azure-service-operator-codegen. DO NOT EDIT. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +package v20211231storage + +import ( + "github.com/Azure/azure-service-operator/testing/microsoft.person/v20211231storage" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +//Storage version of v20211231.Person +type Person struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec v20211231storage.Person_Spec `json:"spec,omitempty"` + Status v20211231storage.Person_Status `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +//Storage version of v20211231.Person +type PersonList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []v20211231storage.Person `json:"items"` +} + +//Storage version of v20211231.Person_Spec +type Person_Spec struct { + FullName *string `json:"fullName,omitempty"` +} + +//Storage version of v20211231.Person_Status +type Person_Status struct { + Status *string `json:"status,omitempty"` +} + +func init() { + SchemeBuilder.Register(&v20211231storage.Person{}, &v20211231storage.PersonList{}) +} diff --git a/hack/generator/pkg/codegen/storage/types_tools.go b/hack/generator/pkg/codegen/storage/types_tools.go deleted file mode 100644 index 869322a9fee..00000000000 --- a/hack/generator/pkg/codegen/storage/types_tools.go +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - */ - -package storage - -import ( - "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" -) - -// FindResourceTypes walks the provided set of TypeDefinitions and returns all the resource types -func FindResourceTypes(types astmodel.Types) astmodel.Types { - result := make(astmodel.Types) - - // Find all our resources and extract all their Specs - for _, def := range types { - _, ok := astmodel.AsResourceType(def.Type()) - if !ok { - continue - } - - // We have a resource type - result.Add(def) - } - - return result -} - -// FindSpecTypes walks the provided set of TypeDefinitions and returns all the spec types -func FindSpecTypes(types astmodel.Types) astmodel.Types { - result := make(astmodel.Types) - - // Find all our resources and extract all their Specs - for _, def := range types { - rt, ok := astmodel.AsResourceType(def.Type()) - if !ok { - continue - } - - // We have a resource type - tn, ok := astmodel.AsTypeName(rt.SpecType()) - if !ok { - continue - } - - // Add the named spec type to our results - if spec, ok := types.TryGet(tn); ok { - result.Add(spec) - } - } - - return result -} - -// FindStatusTypes walks the provided set of TypeDefinitions and returns all the status types -func FindStatusTypes(types astmodel.Types) astmodel.Types { - result := make(astmodel.Types) - - // Find all our resources and extract all their Statuses - for _, def := range types { - rt, ok := astmodel.AsResourceType(def.Type()) - if !ok { - continue - } - - // We have a resource type - tn, ok := astmodel.AsTypeName(rt.StatusType()) - if !ok { - continue - } - - // Add the named status type to our results - if status, ok := types.TryGet(tn); ok { - result.Add(status) - } - } - - return result -} diff --git a/hack/generator/pkg/codegen/storage/types_tools_test.go b/hack/generator/pkg/codegen/storage/types_tools_test.go deleted file mode 100644 index 00c78e30bcf..00000000000 --- a/hack/generator/pkg/codegen/storage/types_tools_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - */ - -package storage - -import ( - "testing" - - . "github.com/onsi/gomega" - - "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" - "github.com/Azure/azure-service-operator/hack/generator/pkg/test" -) - -var ( - testGroup = "microsoft.person" - - testPackage = test.MakeLocalPackageReference(testGroup, "v20200101") - - fullNameProperty = astmodel.NewPropertyDefinition("FullName", "fullName", astmodel.StringType). - WithDescription("As would be used to address mail") - - familyNameProperty = astmodel.NewPropertyDefinition("FamilyName", "familyName", astmodel.StringType). - WithDescription("Shared name of the family") - - knownAsProperty = astmodel.NewPropertyDefinition("KnownAs", "knownAs", astmodel.StringType). - WithDescription("How the person is generally known") -) - -func TestFindSpecTypes(t *testing.T) { - g := NewGomegaWithT(t) - - // Define a test resource - spec := test.CreateSpec(testPackage, "Person", fullNameProperty, familyNameProperty, knownAsProperty) - status := test.CreateStatus(testPackage, "Person") - resource := test.CreateResource(testPackage, "Person", spec, status) - - types := make(astmodel.Types) - types.AddAll(resource, status, spec) - - specs := FindSpecTypes(types) - - g.Expect(specs).To(HaveLen(1)) - g.Expect(specs.Contains(spec.Name())).To(BeTrue()) -} - -func TestFindStatusTypes(t *testing.T) { - g := NewGomegaWithT(t) - - // Define a test resource - spec := test.CreateSpec(testPackage, "Person", fullNameProperty, familyNameProperty, knownAsProperty) - status := test.CreateStatus(testPackage, "Person") - resource := test.CreateResource(testPackage, "Person", spec, status) - - types := make(astmodel.Types) - types.AddAll(resource, status, spec) - - statuses := FindStatusTypes(types) - - g.Expect(statuses).To(HaveLen(1)) - g.Expect(statuses.Contains(status.Name())).To(BeTrue()) -} diff --git a/hack/generator/pkg/conversions/property_conversions.go b/hack/generator/pkg/conversions/property_conversions.go index 7174a5a4f64..c4d5bf88afc 100644 --- a/hack/generator/pkg/conversions/property_conversions.go +++ b/hack/generator/pkg/conversions/property_conversions.go @@ -145,11 +145,10 @@ func CreateTypeConversion( return nil, err } +// NameOfPropertyAssignmentFunction returns the name of the property assignment function func NameOfPropertyAssignmentFunction(name astmodel.TypeName, direction Direction, idFactory astmodel.IdentifierFactory) string { nameOfOtherType := idFactory.CreateIdentifier(name.Name(), astmodel.Exported) - return direction.SelectString( - "AssignPropertiesFrom"+nameOfOtherType, - "AssignPropertiesTo"+nameOfOtherType) + return "AssignProperties" + direction.SelectString("From", "To") + nameOfOtherType } // assignToOptional will generate a conversion where the destination is optional, if the diff --git a/hack/generator/pkg/functions/resource_conversion_function.go b/hack/generator/pkg/functions/resource_conversion_function.go new file mode 100644 index 00000000000..3b10ddb5b41 --- /dev/null +++ b/hack/generator/pkg/functions/resource_conversion_function.go @@ -0,0 +1,314 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package functions + +import ( + "fmt" + "go/token" + + "github.com/dave/dst" + + "github.com/Azure/azure-service-operator/hack/generator/pkg/astbuilder" + "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" +) + +// ResourceConversionFunction implements conversions to/from our hub type +// Existing PropertyAssignment functions are used to implement stepwise conversion +// +// Direct conversion from the hub type: +// +// func ( ) Convert(hub conversion.Hub) error { +// source, ok := hub.(*) +// if !ok { +// return fmt.Errorf("expected but received %T instead", hub) +// } +// return .AssignProperties(source) +// } +// +// Indirect conversion, multiple steps via an intermediate instance +// +// func (r ) Convert(hub conversion.Hub) error { +// var source +// err := source.Convert(hub) +// if err != nil { +// return errors.Wrap(err, "converting from hub to source") +// } +// +// err = .AssignProperties(&source) +// if err != nil { +// return errors.Wrap(err, "converting from source to ") +// } +// +// return nil +// } +// +type ResourceConversionFunction struct { + // hub is the TypeName of the canonical hub type, the final target or original source for conversion + hub astmodel.TypeName + // propertyFunction is a reference to the function we will call to copy properties across + propertyFunction *PropertyAssignmentFunction + // idFactory is a reference to an identifier factory used for creating Go identifiers + idFactory astmodel.IdentifierFactory +} + +// Ensure we properly implement the function interface +var _ astmodel.Function = &ResourceConversionFunction{} + +// NewResourceConversionFunction creates a conversion function that populates our hub type from the current instance +// hub is the TypeName of our hub type +// propertyFuntion is the function we use to copy properties across +func NewResourceConversionFunction( + hub astmodel.TypeName, + propertyFunction *PropertyAssignmentFunction, + idFactory astmodel.IdentifierFactory) *ResourceConversionFunction { + result := &ResourceConversionFunction{ + hub: hub, + propertyFunction: propertyFunction, + idFactory: idFactory, + } + + return result +} + +func (fn *ResourceConversionFunction) Name() string { + return fn.propertyFunction.direction.SelectString("ConvertFrom", "ConvertTo") +} + +func (fn *ResourceConversionFunction) RequiredPackageReferences() *astmodel.PackageReferenceSet { + result := astmodel.NewPackageReferenceSet( + astmodel.GitHubErrorsReference, + astmodel.ControllerRuntimeConversion, + astmodel.FmtReference, + fn.hub.PackageReference) + + // Include the package required by the parameter of the property assignment function + propertyFunctionParameterTypeName := fn.propertyFunction.otherDefinition.Name() + result.AddReference(propertyFunctionParameterTypeName.PackageReference) + + return result +} + +func (fn *ResourceConversionFunction) References() astmodel.TypeNameSet { + // Include the type of the parameter of the property assignment function + propertyFunctionParameterTypeName := fn.propertyFunction.otherDefinition.Name() + + result := astmodel.NewTypeNameSet(fn.hub, propertyFunctionParameterTypeName) + return result +} + +func (fn *ResourceConversionFunction) AsFunc( + generationContext *astmodel.CodeGenerationContext, receiver astmodel.TypeName) *dst.FuncDecl { + + // Create a sensible name for our receiver + receiverName := fn.idFactory.CreateIdentifier(receiver.Name(), astmodel.NotExported) + + // We always use a pointer receiver so we can modify it + receiverType := astmodel.NewOptionalType(receiver).AsType(generationContext) + + funcDetails := &astbuilder.FuncDetails{ + ReceiverIdent: receiverName, + ReceiverType: receiverType, + Name: fn.Name(), + } + + conversionPackage := generationContext.MustGetImportedPackageName(astmodel.ControllerRuntimeConversion) + + funcDetails.AddParameter("hub", astbuilder.QualifiedTypeName(conversionPackage, "Hub")) + funcDetails.AddReturns("error") + funcDetails.AddComments(fn.declarationDocComment(receiver)) + + if fn.hub.Equals(fn.propertyFunction.otherDefinition.Name()) { + // Not using an intermediate step + funcDetails.Body = fn.directConversion(receiverName, generationContext) + } else { + fn.propertyFunction.direction. + WhenFrom(func() { funcDetails.Body = fn.indirectConversionFromHub(receiverName, generationContext) }). + WhenTo(func() { funcDetails.Body = fn.indirectConversionToHub(receiverName, generationContext) }) + } + + return funcDetails.DefineFunc() +} + +// directConversion creates a simple direct conversion between the two types +// +// , ok := .() +// if !ok { +// return errors.Errorf("expected but received %T instead", ) +// } +// +// return .AssignProperties(To|From)() +// +func (fn *ResourceConversionFunction) directConversion( + receiverName string, generationContext *astmodel.CodeGenerationContext) []dst.Stmt { + fmtPackage := generationContext.MustGetImportedPackageName(astmodel.FmtReference) + + localId := fn.localVariableId() + localIdent := dst.NewIdent(localId) + hubIdent := dst.NewIdent("hub") + + assignLocal := astbuilder.TypeAssert( + localIdent, + hubIdent, + astbuilder.Dereference(fn.hub.AsType(generationContext))) + + checkAssert := astbuilder.ReturnIfNotOk( + astbuilder.FormatError( + fmtPackage, + fmt.Sprintf("expected %s but received %%T instead", fn.hub), + hubIdent)) + + copyAndReturn := astbuilder.Returns( + astbuilder.CallExpr(dst.NewIdent(receiverName), fn.propertyFunction.Name(), localIdent)) + + return astbuilder.Statements( + assignLocal, + checkAssert, + copyAndReturn) +} + +// indirectConversionFromHub generates a conversion when the type we know about isn't the hub type, but is closer to it +// in our conversion graph +// +// var source +// err := source.ConvertFrom() +// if err != nil { +// return errors.Wrap(err, "converting from hub to source") +// } +// +// err = .AssignPropertiesFrom(&source) +// if err != nil { +// return errors.Wrap(err, "converting from source to ") +// } +// +// return nil +// +func (fn *ResourceConversionFunction) indirectConversionFromHub( + receiverName string, generationContext *astmodel.CodeGenerationContext) []dst.Stmt { + errorsPackage := generationContext.MustGetImportedPackageName(astmodel.GitHubErrorsReference) + localId := fn.localVariableId() + errIdent := dst.NewIdent("err") + + intermediateType := fn.propertyFunction.otherDefinition.Name() + + declareLocal := astbuilder.LocalVariableDeclaration( + localId, intermediateType.AsType(generationContext), "// intermediate variable for conversion") + declareLocal.Decorations().Before = dst.NewLine + + populateLocalFromHub := astbuilder.SimpleAssignment( + errIdent, + token.DEFINE, + astbuilder.CallExpr(dst.NewIdent(localId), fn.Name(), dst.NewIdent("hub"))) + populateLocalFromHub.Decs.Before = dst.EmptyLine + + checkForErrorsPopulatingLocal := astbuilder.CheckErrorAndWrap( + errorsPackage, + fmt.Sprintf("converting from hub to %s", localId)) + + populateReceiverFromLocal := astbuilder.SimpleAssignment( + errIdent, + token.ASSIGN, + astbuilder.CallExpr(dst.NewIdent(receiverName), fn.propertyFunction.Name(), astbuilder.AddrOf(dst.NewIdent(localId)))) + populateReceiverFromLocal.Decs.Before = dst.EmptyLine + + checkForErrorsPopulatingReceiver := astbuilder.CheckErrorAndWrap( + errorsPackage, + fmt.Sprintf("converting from %s to %s", localId, receiverName)) + + returnNil := astbuilder.Returns(dst.NewIdent("nil")) + returnNil.Decorations().Before = dst.EmptyLine + + return astbuilder.Statements( + declareLocal, + populateLocalFromHub, + checkForErrorsPopulatingLocal, + populateReceiverFromLocal, + checkForErrorsPopulatingReceiver, + returnNil) +} + +// indirectConversionToHub generates a conversion when the type we know about isn't the hub type, but is closer to it in +// our conversion graph +// +// var destination +// err = .AssignPropertiesTo(&destination) +// if err != nil { +// return errors.Wrap(err, "converting to destination from ") +// } +// +// err := destination.ConvertTo() +// if err != nil { +// return errors.Wrap(err, "converting from destination to hub") +// } +// +// return nil +// +func (fn *ResourceConversionFunction) indirectConversionToHub( + receiverName string, generationContext *astmodel.CodeGenerationContext) []dst.Stmt { + errorsPackage := generationContext.MustGetImportedPackageName(astmodel.GitHubErrorsReference) + localId := fn.localVariableId() + errIdent := dst.NewIdent("err") + + intermediateType := fn.propertyFunction.otherDefinition.Name() + + declareLocal := astbuilder.LocalVariableDeclaration( + localId, intermediateType.AsType(generationContext), "// intermediate variable for conversion") + declareLocal.Decorations().Before = dst.NewLine + + populateLocalFromReceiver := astbuilder.SimpleAssignment( + errIdent, + token.DEFINE, + astbuilder.CallExpr(dst.NewIdent(receiverName), fn.propertyFunction.Name(), astbuilder.AddrOf(dst.NewIdent(localId)))) + + checkForErrorsPopulatingLocal := astbuilder.CheckErrorAndWrap( + errorsPackage, + fmt.Sprintf("converting to %s from %s", localId, receiverName)) + + populateHubFromLocal := astbuilder.SimpleAssignment( + errIdent, + token.ASSIGN, + astbuilder.CallExpr(dst.NewIdent(localId), fn.Name(), dst.NewIdent("hub"))) + + checkForErrorsPopulatingHub := astbuilder.CheckErrorAndWrap( + errorsPackage, + fmt.Sprintf("converting from %s to hub", localId)) + + returnNil := astbuilder.Returns(dst.NewIdent("nil")) + returnNil.Decorations().Before = dst.EmptyLine + + return astbuilder.Statements( + declareLocal, + populateLocalFromReceiver, + checkForErrorsPopulatingLocal, + populateHubFromLocal, + checkForErrorsPopulatingHub, + returnNil) +} + +// localVariableId returns a good identifier to use for a local variable in our function, +// based which direction we are converting +func (fn *ResourceConversionFunction) localVariableId() string { + return fn.propertyFunction.direction.SelectString("source", "destination") +} + +func (fn *ResourceConversionFunction) declarationDocComment(receiver astmodel.TypeName) string { + return fn.propertyFunction.direction.SelectString( + fmt.Sprintf("populates our %s from the provided hub %s", receiver.Name(), fn.hub.Name()), + fmt.Sprintf("populates the provided hub %s from our %s", fn.hub.Name(), receiver.Name())) +} + +func (fn *ResourceConversionFunction) Equals(otherFn astmodel.Function) bool { + rcf, ok := otherFn.(*ResourceConversionFunction) + if !ok { + return false + } + + if !fn.propertyFunction.Equals(rcf.propertyFunction) { + return false + } + + return fn.Name() == rcf.Name() && + fn.hub.Equals(rcf.hub) +} diff --git a/hack/generator/pkg/functions/resource_conversion_function_test.go b/hack/generator/pkg/functions/resource_conversion_function_test.go new file mode 100644 index 00000000000..ffd6594d229 --- /dev/null +++ b/hack/generator/pkg/functions/resource_conversion_function_test.go @@ -0,0 +1,107 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package functions + +import ( + "testing" + + . "github.com/onsi/gomega" + + "github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel" + "github.com/Azure/azure-service-operator/hack/generator/pkg/conversions" + "github.com/Azure/azure-service-operator/hack/generator/pkg/test" +) + +// Test_ResourceConversionFunction_DirectConversion_GeneratesExpectedCode tests the code when the ConvertTo() and +// ConvertFrom() functions are directly converting to/from the Hub type, without any intermediate step. +func Test_ResourceConversionFunction_DirectConversion_GeneratesExpectedCode(t *testing.T) { + g := NewGomegaWithT(t) + idFactory := astmodel.NewIdentifierFactory() + + // Create our upstream type + personSpec2020 := test.CreateSpec(test.Pkg2020, "Person", test.FullNameProperty, test.KnownAsProperty, test.FamilyNameProperty) + personStatus2020 := test.CreateStatus(test.Pkg2020, "Person") + person2020 := test.CreateResource(test.Pkg2020, "Person", personSpec2020, personStatus2020) + + // Create our downstream type + personSpec2021 := test.CreateSpec(test.Pkg2021, "Person", test.FullNameProperty, test.KnownAsProperty, test.FamilyNameProperty, test.CityProperty) + personStatus2021 := test.CreateStatus(test.Pkg2021, "Person") + person2021 := test.CreateResource(test.Pkg2021, "Person", personSpec2021, personStatus2021) + + // Create Property Assignment functions + types := make(astmodel.Types) + types.AddAll(person2020, personSpec2020, personStatus2020) + types.AddAll(person2021, personSpec2021, personStatus2021) + + conversionContext := conversions.NewPropertyConversionContext(types, idFactory) + propertyAssignTo, err := NewPropertyAssignmentToFunction(person2020, person2021, idFactory, conversionContext) + g.Expect(err).To(Succeed()) + + propertyAssignFrom, err := NewPropertyAssignmentFromFunction(person2020, person2021, idFactory, conversionContext) + g.Expect(err).To(Succeed()) + + // Create Resource Conversion Functions + convertTo := NewResourceConversionFunction(person2021.Name(), propertyAssignTo, idFactory) + convertFrom := NewResourceConversionFunction(person2021.Name(), propertyAssignFrom, idFactory) + + // Inject these methods into person2020 + injector := astmodel.NewFunctionInjector() + person2020, err = injector.Inject(person2020, propertyAssignTo, propertyAssignFrom, convertTo, convertFrom) + g.Expect(err).To(Succeed()) + + // Write to a file + fileDef := test.CreateFileDefinition(person2020) + test.AssertFileGeneratesExpectedCode(t, fileDef, t.Name()) +} + +// Test_ResourceConversionFunction_IndirectConversion_GeneratesExpectedCode tests the code when the ConvertTo() and +// ConvertFrom() functions can't convert directly to/from the hub type and are forced to stage the conversion on an +// intermediate type. +func Test_ResourceConversionFunction_IndirectConversion_GeneratesExpectedCode(t *testing.T) { + g := NewGomegaWithT(t) + idFactory := astmodel.NewIdentifierFactory() + + // Create our upstream type + personSpec2020 := test.CreateSpec(test.Pkg2020, "Person", test.FullNameProperty, test.KnownAsProperty, test.FamilyNameProperty) + personStatus2020 := test.CreateStatus(test.Pkg2020, "Person") + person2020 := test.CreateResource(test.Pkg2020, "Person", personSpec2020, personStatus2020) + + // Create our downstream type - directly convertible with the upstream type + personSpec2021 := test.CreateSpec(test.Pkg2021, "Person", test.FullNameProperty, test.KnownAsProperty, test.FamilyNameProperty, test.CityProperty) + personStatus2021 := test.CreateStatus(test.Pkg2021, "Person") + person2021 := test.CreateResource(test.Pkg2021, "Person", personSpec2021, personStatus2021) + + // Create our hub type - multiple conversion steps away from Pkg2020 + personSpec2022 := test.CreateSpec(test.Pkg2022, "Person", test.FullNameProperty, test.KnownAsProperty, test.FamilyNameProperty, test.CityProperty) + personStatus2022 := test.CreateStatus(test.Pkg2022, "Person") + person2022 := test.CreateResource(test.Pkg2022, "Person", personSpec2021, personStatus2021) + + // Create Property Assignment functions + types := make(astmodel.Types) + types.AddAll(person2020, personSpec2020, personStatus2020) + types.AddAll(person2021, personSpec2021, personStatus2021) + types.AddAll(person2022, personSpec2022, personStatus2022) + + conversionContext := conversions.NewPropertyConversionContext(types, idFactory) + propertyAssignTo, err := NewPropertyAssignmentToFunction(person2020, person2021, idFactory, conversionContext) + g.Expect(err).To(Succeed()) + + propertyAssignFrom, err := NewPropertyAssignmentFromFunction(person2020, person2021, idFactory, conversionContext) + g.Expect(err).To(Succeed()) + + // Create Resource Conversion Functions + convertTo := NewResourceConversionFunction(person2022.Name(), propertyAssignTo, idFactory) + convertFrom := NewResourceConversionFunction(person2022.Name(), propertyAssignFrom, idFactory) + + // Inject these methods into person2020 + injector := astmodel.NewFunctionInjector() + person2020, err = injector.Inject(person2020, propertyAssignTo, propertyAssignFrom, convertTo, convertFrom) + g.Expect(err).To(Succeed()) + + // Write to a file + fileDef := test.CreateFileDefinition(person2020) + test.AssertFileGeneratesExpectedCode(t, fileDef, t.Name()) +} diff --git a/hack/generator/pkg/functions/testdata/Test_ResourceConversionFunction_DirectConversion_GeneratesExpectedCode.golden b/hack/generator/pkg/functions/testdata/Test_ResourceConversionFunction_DirectConversion_GeneratesExpectedCode.golden new file mode 100644 index 00000000000..d77d7b81634 --- /dev/null +++ b/hack/generator/pkg/functions/testdata/Test_ResourceConversionFunction_DirectConversion_GeneratesExpectedCode.golden @@ -0,0 +1,96 @@ +// Code generated by azure-service-operator-codegen. DO NOT EDIT. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +package v20200101 + +import ( + "fmt" + person "github.com/Azure/azure-service-operator/testing/microsoft.person/v20211231" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/conversion" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type Person struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec Person_Spec `json:"spec,omitempty"` + Status Person_Status `json:"status,omitempty"` +} + +// AssignPropertiesFromPerson populates our Person from the provided source Person +func (person *Person) AssignPropertiesFromPerson(source *person.Person) error { + + // Spec + var spec Person_Spec + err := spec.AssignPropertiesFromPersonSpec(&source.Spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesFromPersonSpec()") + } + person.Spec = spec + + // Status + var status Person_Status + err = status.AssignPropertiesFromPersonStatus(&source.Status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesFromPersonStatus()") + } + person.Status = status + + // No error + return nil +} + +// AssignPropertiesToPerson populates the provided destination Person from our Person +func (person *Person) AssignPropertiesToPerson(destination *person.Person) error { + + // Spec + var spec person.Person_Spec + err := person.Spec.AssignPropertiesToPersonSpec(&spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesToPersonSpec()") + } + destination.Spec = spec + + // Status + var status person.Person_Status + err = person.Status.AssignPropertiesToPersonStatus(&status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesToPersonStatus()") + } + destination.Status = status + + // No error + return nil +} + +// ConvertFrom populates our Person from the provided hub Person +func (person *Person) ConvertFrom(hub conversion.Hub) error { + source, ok := hub.(*person.Person) + if !ok { + return fmt.Errorf("expected github.com/Azure/azure-service-operator/testing/microsoft.person/v20211231/Person but received %T instead", hub) + } + return person.AssignPropertiesFromPerson(source) +} + +// ConvertTo populates the provided hub Person from our Person +func (person *Person) ConvertTo(hub conversion.Hub) error { + destination, ok := hub.(*person.Person) + if !ok { + return fmt.Errorf("expected github.com/Azure/azure-service-operator/testing/microsoft.person/v20211231/Person but received %T instead", hub) + } + return person.AssignPropertiesToPerson(destination) +} + +// +kubebuilder:object:root=true +type PersonList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Person `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Person{}, &PersonList{}) +} diff --git a/hack/generator/pkg/functions/testdata/Test_ResourceConversionFunction_IndirectConversion_GeneratesExpectedCode.golden b/hack/generator/pkg/functions/testdata/Test_ResourceConversionFunction_IndirectConversion_GeneratesExpectedCode.golden new file mode 100644 index 00000000000..9b0302cc37f --- /dev/null +++ b/hack/generator/pkg/functions/testdata/Test_ResourceConversionFunction_IndirectConversion_GeneratesExpectedCode.golden @@ -0,0 +1,111 @@ +// Code generated by azure-service-operator-codegen. DO NOT EDIT. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +package v20200101 + +import ( + personv20211231 "github.com/Azure/azure-service-operator/testing/microsoft.person/v20211231" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/conversion" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type Person struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec Person_Spec `json:"spec,omitempty"` + Status Person_Status `json:"status,omitempty"` +} + +// AssignPropertiesFromPerson populates our Person from the provided source Person +func (person *Person) AssignPropertiesFromPerson(source *personv20211231.Person) error { + + // Spec + var spec Person_Spec + err := spec.AssignPropertiesFromPersonSpec(&source.Spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesFromPersonSpec()") + } + person.Spec = spec + + // Status + var status Person_Status + err = status.AssignPropertiesFromPersonStatus(&source.Status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesFromPersonStatus()") + } + person.Status = status + + // No error + return nil +} + +// AssignPropertiesToPerson populates the provided destination Person from our Person +func (person *Person) AssignPropertiesToPerson(destination *personv20211231.Person) error { + + // Spec + var spec personv20211231.Person_Spec + err := person.Spec.AssignPropertiesToPersonSpec(&spec) + if err != nil { + return errors.Wrap(err, "populating Spec from Spec, calling AssignPropertiesToPersonSpec()") + } + destination.Spec = spec + + // Status + var status personv20211231.Person_Status + err = person.Status.AssignPropertiesToPersonStatus(&status) + if err != nil { + return errors.Wrap(err, "populating Status from Status, calling AssignPropertiesToPersonStatus()") + } + destination.Status = status + + // No error + return nil +} + +// ConvertFrom populates our Person from the provided hub Person +func (person *Person) ConvertFrom(hub conversion.Hub) error { + // intermediate variable for conversion + var source personv20211231.Person + + err := source.ConvertFrom(hub) + if err != nil { + return errors.Wrap(err, "converting from hub to source") + } + + err = person.AssignPropertiesFromPerson(&source) + if err != nil { + return errors.Wrap(err, "converting from source to person") + } + + return nil +} + +// ConvertTo populates the provided hub Person from our Person +func (person *Person) ConvertTo(hub conversion.Hub) error { + // intermediate variable for conversion + var destination personv20211231.Person + err := person.AssignPropertiesToPerson(&destination) + if err != nil { + return errors.Wrap(err, "converting to destination from person") + } + err = destination.ConvertTo(hub) + if err != nil { + return errors.Wrap(err, "converting from destination to hub") + } + + return nil +} + +// +kubebuilder:object:root=true +type PersonList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Person `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Person{}, &PersonList{}) +}