Skip to content

Commit

Permalink
Make validator and defaulter more extensible (#1507)
Browse files Browse the repository at this point in the history
Co-authored-by: George Pollard <gpollard@microsoft.com>
  • Loading branch information
matthchr and Porges authored May 26, 2021
1 parent 03d66c4 commit 7a9f076
Show file tree
Hide file tree
Showing 33 changed files with 3,133 additions and 213 deletions.
28 changes: 28 additions & 0 deletions hack/generated/pkg/genruntime/admissions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT license.
*/

package genruntime

import (
"k8s.io/apimachinery/pkg/runtime"
)

// Validator is similar to controller-runtime/pkg/webhook/admission Validator. Implementing this interface
// allows you to hook into the code generated validations and add custom handcrafted validations.
type Validator interface {
// CreateValidations returns validation functions that should be run on create.
CreateValidations() []func() error
// UpdateValidations returns validation functions that should be run on update.
UpdateValidations() []func(old runtime.Object) error
// DeleteValidations returns validation functions taht should be run on delete.
DeleteValidations() []func() error
}

// Defaulter is similar to controller-runtime/pkg/webhook/admission Defaulter. Implementing this interface
// allows you to hook into the code generated defaults and add custom handcrafted defaults.
type Defaulter interface {
// CustomDefault performs custom defaults that are run in addition to the code generated defaults.
CustomDefault()
}
23 changes: 23 additions & 0 deletions hack/generator/pkg/astbuilder/assignments.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,26 @@ func SimpleAssignmentWithErr(lhs dst.Expr, tok token.Token, rhs dst.Expr) *dst.A
},
}
}

// AssignToInterface performs an assignment of a well-typed variable to an interface{}. This is usually used to
// perform a type assertion on a concrete type in a subsequent statement (which Go doesn't allow, it only allows type
// assertions on interface types).
// var <lhsVar> interface{} = <rhs>
func AssignToInterface(lhsVar string, rhs dst.Expr) *dst.DeclStmt {
return &dst.DeclStmt{
Decl: &dst.GenDecl{
Tok: token.VAR,
Specs: []dst.Spec{
&dst.ValueSpec{
Names: []*dst.Ident{
dst.NewIdent(lhsVar),
},
Type: dst.NewIdent("interface{}"),
Values: []dst.Expr{
dst.Clone(rhs).(dst.Expr),
},
},
},
},
}
}
18 changes: 15 additions & 3 deletions hack/generator/pkg/astbuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,20 @@ func CheckErrorAndReturn(otherReturns ...dst.Expr) dst.Stmt {
returnValues := append([]dst.Expr{}, cloneExprSlice(otherReturns)...)
returnValues = append(returnValues, dst.NewIdent("err"))

retStmt := &dst.ReturnStmt{
Results: returnValues,
}

return CheckErrorAndSingleStatement(retStmt)
}

// CheckErrorAndSingleStatement checks if the err is non-nil, and if it is executes the provided statement.
//
// if err != nil {
// <stmt>
// }
func CheckErrorAndSingleStatement(stmt dst.Stmt) dst.Stmt {

return &dst.IfStmt{
Cond: &dst.BinaryExpr{
X: dst.NewIdent("err"),
Expand All @@ -30,9 +44,7 @@ func CheckErrorAndReturn(otherReturns ...dst.Expr) dst.Stmt {
},
Body: &dst.BlockStmt{
List: []dst.Stmt{
&dst.ReturnStmt{
Results: returnValues,
},
stmt,
},
},
}
Expand Down
4 changes: 2 additions & 2 deletions hack/generator/pkg/astbuilder/calls.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
//
// <funcName>(<arguments>...)
//
func CallFunc(funcName string, arguments ...dst.Expr) dst.Expr {
func CallFunc(funcName string, arguments ...dst.Expr) *dst.CallExpr {
return &dst.CallExpr{
Fun: dst.NewIdent(funcName),
Args: cloneExprSlice(arguments),
Expand All @@ -25,7 +25,7 @@ func CallFunc(funcName string, arguments ...dst.Expr) dst.Expr {
//
// <qualifier>.<funcName>(arguments...)
//
func CallQualifiedFunc(qualifier string, funcName string, arguments ...dst.Expr) dst.Expr {
func CallQualifiedFunc(qualifier string, funcName string, arguments ...dst.Expr) *dst.CallExpr {
return &dst.CallExpr{
Fun: &dst.SelectorExpr{
X: dst.NewIdent(qualifier),
Expand Down
27 changes: 15 additions & 12 deletions hack/generator/pkg/astmodel/arm_spec_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,27 @@ func NewARMSpecInterfaceImpl(
}

getNameFunc := &objectFunction{
name: "GetName",
o: spec,
idFactory: idFactory,
asFunc: getNameFunction,
name: "GetName",
o: spec,
idFactory: idFactory,
asFunc: getNameFunction,
requiredPackages: NewPackageReferenceSet(GenRuntimeReference),
}

getTypeFunc := &objectFunction{
name: "GetType",
o: spec,
idFactory: idFactory,
asFunc: getTypeFunction,
name: "GetType",
o: spec,
idFactory: idFactory,
asFunc: getTypeFunction,
requiredPackages: NewPackageReferenceSet(GenRuntimeReference),
}

getApiVersionFunc := &objectFunction{
name: "GetApiVersion",
o: spec,
idFactory: idFactory,
asFunc: getApiVersionFunction,
name: "GetApiVersion",
o: spec,
idFactory: idFactory,
asFunc: getApiVersionFunction,
requiredPackages: NewPackageReferenceSet(GenRuntimeReference),
}

result := NewInterfaceImplementation(
Expand Down
1 change: 1 addition & 0 deletions hack/generator/pkg/astmodel/file_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func (file *FileDefinition) generateImports() *PackageImportSet {

// TODO: Make this configurable
requiredImports.ApplyName(MetaV1PackageReference, "metav1")
requiredImports.ApplyName(APIMachineryErrorsReference, "kerrors")

// Force local imports to have explicit names based on the service
for _, imp := range requiredImports.AsSlice() {
Expand Down
90 changes: 90 additions & 0 deletions hack/generator/pkg/astmodel/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,93 @@ type Function interface {
// Equals determines if this Function is equal to another one
Equals(f Function) bool
}

var _ Function = &objectFunction{}

type objectFunctionHandler func(f *objectFunction, codeGenerationContext *CodeGenerationContext, receiver TypeName, methodName string) *dst.FuncDecl

// objectFunction is a simple helper that implements the Function interface. It is intended for use for functions
// that only need information about the object they are operating on
type objectFunction struct {
name string
o *ObjectType
idFactory IdentifierFactory
asFunc objectFunctionHandler
requiredPackages *PackageReferenceSet
}

// Name returns the unique name of this function
// (You can't have two functions with the same name on the same object or resource)
func (k *objectFunction) Name() string {
return k.name
}

// RequiredPackageReferences returns the set of required packages for this function
func (k *objectFunction) RequiredPackageReferences() *PackageReferenceSet {
return k.requiredPackages
}

// References returns the TypeName's referenced by this function
func (k *objectFunction) References() TypeNameSet {
return k.o.References()
}

// AsFunc renders the current instance as a Go abstract syntax tree
func (k *objectFunction) AsFunc(codeGenerationContext *CodeGenerationContext, receiver TypeName) *dst.FuncDecl {
return k.asFunc(k, codeGenerationContext, receiver, k.name)
}

// Equals checks if this function is equal to the passed in function
func (k *objectFunction) Equals(f Function) bool {
typedF, ok := f.(*objectFunction)
if !ok {
return false
}

// TODO: We're not actually checking function structure here
return k.o.Equals(typedF.o) && k.name == typedF.name
}

type resourceFunctionHandler func(f *resourceFunction, codeGenerationContext *CodeGenerationContext, receiver TypeName, methodName string) *dst.FuncDecl

// resourceFunction is a simple helper that implements the Function interface. It is intended for use for functions
// that only need information about the resource they are operating on
type resourceFunction struct {
name string
resource *ResourceType
idFactory IdentifierFactory
asFunc resourceFunctionHandler
requiredPackages *PackageReferenceSet
}

// Name returns the unique name of this function
// (You can't have two functions with the same name on the same object or resource)
func (r *resourceFunction) Name() string {
return r.name
}

// RequiredPackageReferences returns the set of required packages for this function
func (r *resourceFunction) RequiredPackageReferences() *PackageReferenceSet {
return r.requiredPackages
}

// References returns the TypeName's referenced by this function
func (r *resourceFunction) References() TypeNameSet {
return r.resource.References()
}

// AsFunc renders the current instance as a Go abstract syntax tree
func (r *resourceFunction) AsFunc(codeGenerationContext *CodeGenerationContext, receiver TypeName) *dst.FuncDecl {
return r.asFunc(r, codeGenerationContext, receiver, r.name)
}

// Equals determines if this Function is equal to another one
func (r *resourceFunction) Equals(f Function) bool {
typedF, ok := f.(*resourceFunction)
if !ok {
return false
}

// TODO: We're not actually checking function structure here
return r.resource.Equals(typedF.resource) && r.name == typedF.name
}
Loading

0 comments on commit 7a9f076

Please sign in to comment.