Skip to content

Commit

Permalink
Inject Hub() function on hub storage resources (#1616)
Browse files Browse the repository at this point in the history
  • Loading branch information
theunrepentantgeek authored Jul 7, 2021
1 parent 2e113ed commit 4b1363e
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 1 deletion.
9 changes: 9 additions & 0 deletions hack/generator/pkg/codegen/code_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ func createAllPipelineStages(idFactory astmodel.IdentifierFactory, configuration

pipeline.MarkStorageVersion(),

/*
Disabled until we have the Convertible interface implemented
If we land with a partial implementation, the controller refuses to accept the webhooks
See https://github.com/kubernetes-sigs/controller-runtime/blob/master/pkg/webhook/conversion/conversion.go#L310
pipeline.InjectHubFunction(idFactory).UsedFor(pipeline.ARMTarget),
*/

// Safety checks at the end:
pipeline.EnsureDefinitionsDoNotUseAnyTypes(),
pipeline.EnsureARMTypeExistsForEveryResource().UsedFor(pipeline.ARMTarget),
Expand Down
1 change: 1 addition & 0 deletions hack/generator/pkg/codegen/golden_files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ func NewTestCodeGenerator(testName string, path string, t *testing.T, testConfig
"injectOriginalVersionFunction",
"injectOriginalVersionProperty",
"injectPropertyAssignmentFunctions",
//pipeline.InjectHubFunctionStageId,
"reportTypesAndVersions")
if !testConfig.HasARMResources {
codegen.RemoveStages("createArmTypes", "applyArmConversionInterface")
Expand Down
55 changes: 55 additions & 0 deletions hack/generator/pkg/codegen/pipeline/inject_hub_function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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"
)

// 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 := MakeStage(
InjectHubFunctionStageId,
"Inject the function Hub() into each hub resource",
func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) {
injector := storage.NewFunctionInjector()
result := types.Copy()

resources := storage.FindResourceTypes(types)
for name, def := range resources {
rt, ok := astmodel.AsResourceType(def.Type())
if !ok {
return nil, errors.Errorf("expected %s to be a resource type (should never happen)", name)
}

if rt.IsStorageVersion() {
fn := functions.NewHubFunction(idFactory)
defWithFn, err := injector.Inject(def, fn)
if err != nil {
return nil, errors.Wrapf(err, "injecting Hub() into %s", name)
}

result[name] = defWithFn
}
}

return result, nil
})

result.RequiresPrerequisiteStages(MarkStorageVersionStageId)
return result
}
65 changes: 65 additions & 0 deletions hack/generator/pkg/codegen/pipeline/inject_hub_function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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"
)

func TestInjectHubFunction_WhenResourceIsStorageVersion_GeneratesExpectedFile(t *testing.T) {
g := NewGomegaWithT(t)

idFactory := astmodel.NewIdentifierFactory()

// Define a test resource
spec := test.CreateSpec(pkg2020, "Person", fullNameProperty, familyNameProperty, knownAsProperty)
status := test.CreateStatus(pkg2020, "Person")
resource := test.CreateResource(pkg2020, "Person", spec, status)

resource = resource.WithType(
resource.Type().(*astmodel.ResourceType).MarkAsStorageVersion())

types := make(astmodel.Types)
types.AddAll(resource, status, spec)

injectHubFunction := InjectHubFunction(idFactory)

// Don't need a context when testing
finalTypes, err := injectHubFunction.Run(context.TODO(), types)

g.Expect(err).To(Succeed())

test.AssertPackagesGenerateExpectedCode(t, finalTypes, t.Name())
}

func TestInjectHubFunction_WhenResourceIsNotStorageVersion_GeneratesExpectedFile(t *testing.T) {
g := NewGomegaWithT(t)

idFactory := astmodel.NewIdentifierFactory()

// Define a test resource
spec := test.CreateSpec(pkg2020, "Person", fullNameProperty, familyNameProperty, knownAsProperty)
status := test.CreateStatus(pkg2020, "Person")
resource := test.CreateResource(pkg2020, "Person", spec, status)

types := make(astmodel.Types)
types.AddAll(resource, status, spec)

injectHubFunction := InjectHubFunction(idFactory)

// Don't need a context when testing
finalTypes, err := injectHubFunction.Run(context.TODO(), types)

g.Expect(err).To(Succeed())

test.AssertPackagesGenerateExpectedCode(t, finalTypes, t.Name())
}
5 changes: 4 additions & 1 deletion hack/generator/pkg/codegen/pipeline/mark_storage_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import (
"github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel"
)

// MarkStorageVersionStageId is the unique identifier for this pipeline stage
const MarkStorageVersionStageId = "markStorageVersion"

// MarkStorageVersion creates a Stage to mark a particular version as a storage version
func MarkStorageVersion() Stage {
return MakeStage(
"markStorageVersion",
MarkStorageVersionStageId,
"Mark the latest version of each resource as the storage version",
func(ctx context.Context, types astmodel.Types) (astmodel.Types, error) {
updatedDefs, err := MarkLatestResourceVersionsForStorage(types)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Code generated by azure-service-operator-codegen. DO NOT EDIT.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package v20200101

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// +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"`
}

// +kubebuilder:object:root=true
type PersonList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Person `json:"items"`
}

type Person_Spec struct {
//FamilyName: Shared name of the family
FamilyName string `json:"familyName"`

//FullName: As would be used to address mail
FullName string `json:"fullName"`

//KnownAs: How the person is generally known
KnownAs string `json:"knownAs"`
}

type Person_Status struct {
Status string `json:"status"`
}

func init() {
SchemeBuilder.Register(&Person{}, &PersonList{})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Code generated by azure-service-operator-codegen. DO NOT EDIT.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package v20200101

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// +kubebuilder:rbac:groups=microsoft.person.infra.azure.com,resources=people,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=microsoft.person.infra.azure.com,resources={people/status,people/finalizers},verbs=get;update;patch

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:storageversion
type Person struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec Person_Spec `json:"spec,omitempty"`
Status Person_Status `json:"status,omitempty"`
}

// Hub marks that this Person is the hub type for conversion
func (person *Person) Hub() {}

// +kubebuilder:object:root=true
type PersonList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Person `json:"items"`
}

type Person_Spec struct {
//FamilyName: Shared name of the family
FamilyName string `json:"familyName"`

//FullName: As would be used to address mail
FullName string `json:"fullName"`

//KnownAs: How the person is generally known
KnownAs string `json:"knownAs"`
}

type Person_Status struct {
Status string `json:"status"`
}

func init() {
SchemeBuilder.Register(&Person{}, &PersonList{})
}
72 changes: 72 additions & 0 deletions hack/generator/pkg/functions/hub_function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*/

package functions

import (
"fmt"

"github.com/dave/dst"

"github.com/Azure/azure-service-operator/hack/generator/pkg/astbuilder"
"github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel"
)

// HubFunction generates an empty Hub() function that satisfies the Hub interface required by the controller\
// See https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/conversion#Hub
type HubFunction struct {
idFactory astmodel.IdentifierFactory
}

// Ensure HubFunction properly implements Function
var _ astmodel.Function = &HubFunction{}

// NewHubFunction creates a new instance
func NewHubFunction(idFactory astmodel.IdentifierFactory) *HubFunction {
return &HubFunction{
idFactory: idFactory,
}
}

// Name returns the hard coded name of the function
func (h HubFunction) Name() string {
return "Hub"
}

// RequiredPackageReferences indicates that this function has no required packages
func (h HubFunction) RequiredPackageReferences() *astmodel.PackageReferenceSet {
return astmodel.NewPackageReferenceSet()
}

// References indicates that this function references no types
func (h HubFunction) References() astmodel.TypeNameSet {
return astmodel.NewTypeNameSet()
}

// AsFunc generates the required code
func (h HubFunction) AsFunc(generationContext *astmodel.CodeGenerationContext, receiver astmodel.TypeName) *dst.FuncDecl {
// Create a sensible name for our receiver
receiverName := h.idFactory.CreateIdentifier(receiver.Name(), astmodel.NotExported)

// We always use a pointer receiver so we can modify it
receiverType := astmodel.NewOptionalType(receiver).AsType(generationContext)

details := astbuilder.FuncDetails{
ReceiverIdent: receiverName,
ReceiverType: receiverType,
Name: h.Name(),
Body: []dst.Stmt{}, // empty body
}

details.AddComments(fmt.Sprintf("marks that this %s is the hub type for conversion", receiver.Name()))

return details.DefineFunc()
}

// Equals shows that any hub function is equal to any other
func (h HubFunction) Equals(f astmodel.Function) bool {
_, ok := f.(*HubFunction)
return ok
}
33 changes: 33 additions & 0 deletions hack/generator/pkg/functions/hub_function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*/

package functions

import (
"testing"

"github.com/Azure/azure-service-operator/hack/generator/pkg/astmodel"
"github.com/Azure/azure-service-operator/hack/generator/pkg/test"
)

func Test_HubFunction_GeneratesExpectedCode(t *testing.T) {
idFactory := astmodel.NewIdentifierFactory()

testGroup := "microsoft.person"
testPackage := test.MakeLocalPackageReference(testGroup, "v20200101")

fullNameProperty := astmodel.NewPropertyDefinition("FullName", "fullName", astmodel.StringType).
WithDescription("As would be used to address mail")

hubFunction := NewHubFunction(idFactory)

// Define a test resource
spec := test.CreateSpec(testPackage, "Person", fullNameProperty)
status := test.CreateStatus(testPackage, "Person")
resource := test.CreateResource(testPackage, "Person", spec, status, hubFunction)

fileDef := test.CreateFileDefinition(resource)
test.AssertFileGeneratesExpectedCode(t, fileDef, t.Name())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Code generated by azure-service-operator-codegen. DO NOT EDIT.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package v20200101

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

// +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"`
}

// Hub marks that this Person is the hub type for conversion
func (person *Person) Hub() {}

// +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{})
}

0 comments on commit 4b1363e

Please sign in to comment.