diff --git a/.gitignore b/.gitignore index a8ff3055..ddefc16b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /dist *.swp .idea +.vscode /webhook \ No newline at end of file diff --git a/.golangci.json b/.golangci.json index 8b6c85e2..21f29194 100644 --- a/.golangci.json +++ b/.golangci.json @@ -11,6 +11,9 @@ ] }, "run": { + "skip-dirs": [ + "pkg/generated" + ], "skip-files": [ "/zz_generated_" ], diff --git a/go.mod b/go.mod index 9b4839a6..2bec6148 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/rancher/rancher/pkg/apis v0.0.0-20210628154046-7a2fc74f9598 github.com/rancher/wrangler v0.8.3 github.com/sirupsen/logrus v1.8.1 + golang.org/x/tools v0.1.0 k8s.io/api v0.21.0 k8s.io/apiextensions-apiserver v0.21.0 k8s.io/apimachinery v0.21.0 diff --git a/main.go b/main.go index 7c488776..a934bbbb 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,5 @@ //go:generate go run pkg/codegen/cleanup/main.go -//go:generate go run pkg/codegen/main.go +//go:generate go run pkg/codegen/main.go pkg/codegen/template.go package main import ( diff --git a/pkg/codegen/main.go b/pkg/codegen/main.go index b5d5d86b..9f898b7f 100644 --- a/pkg/codegen/main.go +++ b/pkg/codegen/main.go @@ -1,13 +1,25 @@ package main import ( + "bytes" + "fmt" "os" + "path" + "reflect" + "strings" + "text/template" v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" + v1 "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" controllergen "github.com/rancher/wrangler/pkg/controller-gen" "github.com/rancher/wrangler/pkg/controller-gen/args" + "golang.org/x/tools/imports" ) +type typeInfo struct { + Name, Type, Package string +} + func main() { os.Unsetenv("GOPATH") controllergen.Run(args.Options{ @@ -23,4 +35,88 @@ func main() { }, }, }) + + // Generate the FromRequest and OldAndNewFromRequest functions to get the new and old objects from the webhook request. + if err := generateObjectsFromRequest("pkg/generated/objects", map[string]args.Group{ + "management.cattle.io": { + Types: []interface{}{ + &v3.Cluster{}, + &v3.ClusterRoleTemplateBinding{}, + &v3.FleetWorkspace{}, + &v3.GlobalRole{}, + &v3.GlobalRoleBinding{}, + &v3.RoleTemplate{}, + &v3.ProjectRoleTemplateBinding{}, + }, + }, + "provisioning.cattle.io": { + Types: []interface{}{ + &v1.Cluster{}, + }, + }}); err != nil { + fmt.Printf("ERROR: %v\n", err) + } +} + +func generateObjectsFromRequest(outputDir string, groups map[string]args.Group) error { + temp := template.Must(template.New("objectsFromRequest").Funcs(template.FuncMap{ + "replace": strings.ReplaceAll, + }).Parse(objectsFromRequestTemplate)) + + for groupName, group := range groups { + var packageName string + types := make([]typeInfo, 0, len(group.Types)) + + for _, t := range group.Types { + rt := reflect.TypeOf(t) + ti := typeInfo{ + Type: rt.String(), + } + if rt.Kind() == reflect.Ptr { + // PkgPath returns an empty string for pointers + // Elem returns a Type associated to the dereferenced type. + rt = rt.Elem() + } + ti.Package = rt.PkgPath() + ti.Name = rt.Name() + packageName = path.Base(ti.Package) + types = append(types, ti) + } + + groupDir := path.Join(outputDir, groupName, packageName) + if err := os.MkdirAll(groupDir, 0755); err != nil { + return err + } + + data := map[string]interface{}{ + "types": types, + "package": packageName, + } + + var content bytes.Buffer + if err := temp.Execute(&content, data); err != nil { + return err + } + + if err := gofmtAndWriteToFile(path.Join(groupDir, "objects.go"), content.Bytes()); err != nil { + return err + } + } + + return nil +} + +func gofmtAndWriteToFile(path string, content []byte) error { + formatted, err := imports.Process(path, content, &imports.Options{FormatOnly: true, Comments: true}) + if err != nil { + return err + } + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + + _, err = f.Write(formatted) + return err } diff --git a/pkg/codegen/template.go b/pkg/codegen/template.go new file mode 100644 index 00000000..75ba57c0 --- /dev/null +++ b/pkg/codegen/template.go @@ -0,0 +1,61 @@ +package main + +const objectsFromRequestTemplate = ` +package {{ .package }} + +import ( + {{ range .types }} + "{{ .Package }}"{{ end }} + "github.com/rancher/wrangler/pkg/webhook" + admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime" +) +{{ range .types }} + +// {{ .Name }}OldAndNewFromRequest gets the old and new {{ .Name }} objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for {{ .Name }}. +// Similarly, if the request is a Create operation, then the old object is the zero value for {{ .Name }}. +func {{ .Name }}OldAndNewFromRequest(request *webhook.Request) ({{ .Type }}, {{ .Type }}, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = {{ replace .Type "*" "&" }}{} + } + + if request.Operation == admissionv1.Create { + return {{ replace .Type "*" "&" }}{}, object.({{ .Type }}), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.({{ .Type }}), object.({{ .Type }}), nil +} + +// {{ .Name }}FromRequest returns a {{ .Name }} object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func {{ .Name }}FromRequest(request *webhook.Request) ({{ .Type }}, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.({{ .Type }}), nil +} +{{ end }} +` diff --git a/pkg/generated/objects/management.cattle.io/v3/objects.go b/pkg/generated/objects/management.cattle.io/v3/objects.go new file mode 100644 index 00000000..a9a6d082 --- /dev/null +++ b/pkg/generated/objects/management.cattle.io/v3/objects.go @@ -0,0 +1,330 @@ +package v3 + +import ( + "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" + "github.com/rancher/wrangler/pkg/webhook" + admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// ClusterOldAndNewFromRequest gets the old and new Cluster objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for Cluster. +// Similarly, if the request is a Create operation, then the old object is the zero value for Cluster. +func ClusterOldAndNewFromRequest(request *webhook.Request) (*v3.Cluster, *v3.Cluster, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = &v3.Cluster{} + } + + if request.Operation == admissionv1.Create { + return &v3.Cluster{}, object.(*v3.Cluster), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.(*v3.Cluster), object.(*v3.Cluster), nil +} + +// ClusterFromRequest returns a Cluster object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func ClusterFromRequest(request *webhook.Request) (*v3.Cluster, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.(*v3.Cluster), nil +} + +// ClusterRoleTemplateBindingOldAndNewFromRequest gets the old and new ClusterRoleTemplateBinding objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for ClusterRoleTemplateBinding. +// Similarly, if the request is a Create operation, then the old object is the zero value for ClusterRoleTemplateBinding. +func ClusterRoleTemplateBindingOldAndNewFromRequest(request *webhook.Request) (*v3.ClusterRoleTemplateBinding, *v3.ClusterRoleTemplateBinding, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = &v3.ClusterRoleTemplateBinding{} + } + + if request.Operation == admissionv1.Create { + return &v3.ClusterRoleTemplateBinding{}, object.(*v3.ClusterRoleTemplateBinding), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.(*v3.ClusterRoleTemplateBinding), object.(*v3.ClusterRoleTemplateBinding), nil +} + +// ClusterRoleTemplateBindingFromRequest returns a ClusterRoleTemplateBinding object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func ClusterRoleTemplateBindingFromRequest(request *webhook.Request) (*v3.ClusterRoleTemplateBinding, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.(*v3.ClusterRoleTemplateBinding), nil +} + +// FleetWorkspaceOldAndNewFromRequest gets the old and new FleetWorkspace objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for FleetWorkspace. +// Similarly, if the request is a Create operation, then the old object is the zero value for FleetWorkspace. +func FleetWorkspaceOldAndNewFromRequest(request *webhook.Request) (*v3.FleetWorkspace, *v3.FleetWorkspace, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = &v3.FleetWorkspace{} + } + + if request.Operation == admissionv1.Create { + return &v3.FleetWorkspace{}, object.(*v3.FleetWorkspace), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.(*v3.FleetWorkspace), object.(*v3.FleetWorkspace), nil +} + +// FleetWorkspaceFromRequest returns a FleetWorkspace object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func FleetWorkspaceFromRequest(request *webhook.Request) (*v3.FleetWorkspace, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.(*v3.FleetWorkspace), nil +} + +// GlobalRoleOldAndNewFromRequest gets the old and new GlobalRole objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for GlobalRole. +// Similarly, if the request is a Create operation, then the old object is the zero value for GlobalRole. +func GlobalRoleOldAndNewFromRequest(request *webhook.Request) (*v3.GlobalRole, *v3.GlobalRole, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = &v3.GlobalRole{} + } + + if request.Operation == admissionv1.Create { + return &v3.GlobalRole{}, object.(*v3.GlobalRole), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.(*v3.GlobalRole), object.(*v3.GlobalRole), nil +} + +// GlobalRoleFromRequest returns a GlobalRole object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func GlobalRoleFromRequest(request *webhook.Request) (*v3.GlobalRole, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.(*v3.GlobalRole), nil +} + +// GlobalRoleBindingOldAndNewFromRequest gets the old and new GlobalRoleBinding objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for GlobalRoleBinding. +// Similarly, if the request is a Create operation, then the old object is the zero value for GlobalRoleBinding. +func GlobalRoleBindingOldAndNewFromRequest(request *webhook.Request) (*v3.GlobalRoleBinding, *v3.GlobalRoleBinding, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = &v3.GlobalRoleBinding{} + } + + if request.Operation == admissionv1.Create { + return &v3.GlobalRoleBinding{}, object.(*v3.GlobalRoleBinding), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.(*v3.GlobalRoleBinding), object.(*v3.GlobalRoleBinding), nil +} + +// GlobalRoleBindingFromRequest returns a GlobalRoleBinding object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func GlobalRoleBindingFromRequest(request *webhook.Request) (*v3.GlobalRoleBinding, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.(*v3.GlobalRoleBinding), nil +} + +// RoleTemplateOldAndNewFromRequest gets the old and new RoleTemplate objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for RoleTemplate. +// Similarly, if the request is a Create operation, then the old object is the zero value for RoleTemplate. +func RoleTemplateOldAndNewFromRequest(request *webhook.Request) (*v3.RoleTemplate, *v3.RoleTemplate, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = &v3.RoleTemplate{} + } + + if request.Operation == admissionv1.Create { + return &v3.RoleTemplate{}, object.(*v3.RoleTemplate), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.(*v3.RoleTemplate), object.(*v3.RoleTemplate), nil +} + +// RoleTemplateFromRequest returns a RoleTemplate object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func RoleTemplateFromRequest(request *webhook.Request) (*v3.RoleTemplate, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.(*v3.RoleTemplate), nil +} + +// ProjectRoleTemplateBindingOldAndNewFromRequest gets the old and new ProjectRoleTemplateBinding objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for ProjectRoleTemplateBinding. +// Similarly, if the request is a Create operation, then the old object is the zero value for ProjectRoleTemplateBinding. +func ProjectRoleTemplateBindingOldAndNewFromRequest(request *webhook.Request) (*v3.ProjectRoleTemplateBinding, *v3.ProjectRoleTemplateBinding, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = &v3.ProjectRoleTemplateBinding{} + } + + if request.Operation == admissionv1.Create { + return &v3.ProjectRoleTemplateBinding{}, object.(*v3.ProjectRoleTemplateBinding), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.(*v3.ProjectRoleTemplateBinding), object.(*v3.ProjectRoleTemplateBinding), nil +} + +// ProjectRoleTemplateBindingFromRequest returns a ProjectRoleTemplateBinding object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func ProjectRoleTemplateBindingFromRequest(request *webhook.Request) (*v3.ProjectRoleTemplateBinding, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.(*v3.ProjectRoleTemplateBinding), nil +} diff --git a/pkg/generated/objects/provisioning.cattle.io/v1/objects.go b/pkg/generated/objects/provisioning.cattle.io/v1/objects.go new file mode 100644 index 00000000..8dbcd25a --- /dev/null +++ b/pkg/generated/objects/provisioning.cattle.io/v1/objects.go @@ -0,0 +1,54 @@ +package v1 + +import ( + "github.com/rancher/rancher/pkg/apis/provisioning.cattle.io/v1" + "github.com/rancher/wrangler/pkg/webhook" + admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// ClusterOldAndNewFromRequest gets the old and new Cluster objects, respectively, from the webhook request. +// If the request is a Delete operation, then the new object is the zero value for Cluster. +// Similarly, if the request is a Create operation, then the old object is the zero value for Cluster. +func ClusterOldAndNewFromRequest(request *webhook.Request) (*v1.Cluster, *v1.Cluster, error) { + var object runtime.Object + var err error + if request.Operation != admissionv1.Delete { + object, err = request.DecodeObject() + if err != nil { + return nil, nil, err + } + } else { + object = &v1.Cluster{} + } + + if request.Operation == admissionv1.Create { + return &v1.Cluster{}, object.(*v1.Cluster), nil + } + + oldObject, err := request.DecodeOldObject() + if err != nil { + return nil, nil, err + } + + return oldObject.(*v1.Cluster), object.(*v1.Cluster), nil +} + +// ClusterFromRequest returns a Cluster object from the webhook request. +// If the operation is a Delete operation, then the old object is returned. +// Otherwise, the new object is returned. +func ClusterFromRequest(request *webhook.Request) (*v1.Cluster, error) { + var object runtime.Object + var err error + if request.Operation == admissionv1.Delete { + object, err = request.DecodeOldObject() + } else { + object, err = request.DecodeObject() + } + + if err != nil { + return nil, err + } + + return object.(*v1.Cluster), nil +}