From 4cb6df57583478e4233772b71275a235f19bb400 Mon Sep 17 00:00:00 2001 From: Muvaffak Onus Date: Fri, 23 Oct 2020 12:51:09 +0300 Subject: [PATCH] crossplane: clean up Crossplane API template Signed-off-by: Muvaffak Onus crossplane: add apis subcommand with provider-dir flag to crossplane subcommand Signed-off-by: Muvaffak Onus crossplane: update with new subtemplates. Signed-off-by: Muvaffak Onus crossplane: add a section about Crossplane Provider Generation to dev-docs Signed-off-by: Muvaffak Onus crossplane: add new crossplane.Generation struct for generating Crossplane types and controllers Signed-off-by: Muvaffak Onus crossplane: update templates for controller Signed-off-by: Muvaffak Onus crossplane: add initial controller template Signed-off-by: Muvaffak Onus crossplane: use kubebuilder required mark in crds Signed-off-by: Muvaffak Onus crossplane: implement IsNotFound func and use it in wherever necessary Signed-off-by: Muvaffak Onus crossplane: pre/post hooks are in place for resource-specific custom code Signed-off-by: Muvaffak Onus crossplane: use SetManyOutput to get only status fields updated Signed-off-by: Muvaffak Onus crossplane: prefix generated files with zz_ to match kubebuilder behavior Signed-off-by: Muvaffak Onus crossplane: ready condition should be set by custom code Signed-off-by: Muvaffak Onus crossplane: run goimports on generated code Signed-off-by: Muvaffak Onus crossplane: add documentation comments to generated API files Signed-off-by: Muvaffak Onus crossplane: use generator config file in the target provider directory Signed-off-by: Muvaffak Onus crossplane: use crd name as module name for its controller Signed-off-by: Muvaffak Onus crossplane: generate boilerplate only once for custom hooks Signed-off-by: Muvaffak Onus crossplane: custom default config for SDKHelper used by Crossplane generation pipeline Signed-off-by: Muvaffak Onus crossplane: licence boilerplates are corrected Signed-off-by: Muvaffak Onus crossplane: added list filtering for readmany results Signed-off-by: Muvaffak Onus crossplane: add inline custom parameter struct to Parameters so that we can add additional fields manually in a different file Signed-off-by: Muvaffak Onus crossplane: use all-lower string for package name of the generated controllers Signed-off-by: Muvaffak Onus crossplane: region field should not have omitempty tag and connect method does not need to call NewSession Signed-off-by: Muvaffak Onus crossplane: ReadOne template is implemented Signed-off-by: Muvaffak Onus crossplane: Make deletion optional and write isUpToDate to hooks Signed-off-by: Muvaffak Onus crossplane: add fallback for the case where service alias does not match the model name in sdk Signed-off-by: Muvaffak Onus --- cmd/ack-generate/command/crossplane.go | 86 ++---- docs/contents/dev-docs/code-generation.md | 12 + pkg/crossplane/apis.go | 110 +++++++ pkg/crossplane/config.go | 26 ++ pkg/crossplane/controller.go | 121 ++++++++ pkg/crossplane/generator.go | 108 +++++++ pkg/generate/config/config.go | 4 +- pkg/names/names.go | 2 + services/crossplane/ecr/apis/v1alpha1/doc.go | 4 - .../crossplane/ecr/apis/v1alpha1/enums.go | 98 ------- .../ecr/apis/v1alpha1/groupversion_info.go | 32 --- .../ecr/apis/v1alpha1/repository.go | 73 ----- .../crossplane/ecr/apis/v1alpha1/types.go | 59 ---- templates/boilerplate.go.tpl | 27 +- templates/boilerplate.txt | 25 +- templates/boilerplate_hash.go.tpl | 25 +- templates/crossplane/README.md | 11 + templates/crossplane/apis/crd.go.tpl | 55 +++- templates/crossplane/apis/doc.go.tpl | 4 +- templates/crossplane/apis/enums.go.tpl | 2 + .../crossplane/apis/groupversion_info.go.tpl | 12 +- templates/crossplane/apis/type_def.go.tpl | 3 + templates/crossplane/apis/types.go.tpl | 2 + templates/crossplane/boilerplate.go.tpl | 27 +- templates/crossplane/boilerplate.txt | 27 +- templates/crossplane/boilerplate_hash.go.tpl | 19 +- .../crossplane/cmd/controller/main.go.tpl | 2 + templates/crossplane/pkg/crd_manager.go.tpl | 154 ---------- templates/crossplane/pkg/crd_resource.go.tpl | 56 ---- templates/crossplane/pkg/crd_sdk.go.tpl | 272 ------------------ .../descriptor.go.tpl} | 14 +- .../identifiers.go.tpl} | 4 +- .../crossplane/pkg/resource/manager.go.tpl | 145 ++++++++++ .../manager_factory.go.tpl} | 12 +- .../crossplane/pkg/resource/resource.go.tpl | 117 ++++++++ templates/crossplane/pkg/resource/sdk.go.tpl | 50 ++++ .../resource/sdk_find_get_attributes.go.tpl | 53 ++++ .../resource/sdk_find_not_implemented.go.tpl | 11 + .../pkg/resource/sdk_find_read_many.go.tpl | 16 ++ .../pkg/resource/sdk_find_read_one.go.tpl | 16 ++ .../crossplane/pkg/resource/sdk_update.go.tpl | 48 ++++ .../pkg/resource/sdk_update_custom.go.tpl | 10 + .../sdk_update_not_implemented.go.tpl | 11 + .../resource/sdk_update_set_attributes.go.tpl | 60 ++++ .../crossplane/pkg/resource_registry.go.tpl | 2 + 45 files changed, 1121 insertions(+), 906 deletions(-) create mode 100644 pkg/crossplane/apis.go create mode 100644 pkg/crossplane/config.go create mode 100644 pkg/crossplane/controller.go create mode 100644 pkg/crossplane/generator.go delete mode 100644 services/crossplane/ecr/apis/v1alpha1/doc.go delete mode 100644 services/crossplane/ecr/apis/v1alpha1/enums.go delete mode 100644 services/crossplane/ecr/apis/v1alpha1/groupversion_info.go delete mode 100644 services/crossplane/ecr/apis/v1alpha1/repository.go delete mode 100644 services/crossplane/ecr/apis/v1alpha1/types.go create mode 100644 templates/crossplane/README.md delete mode 100644 templates/crossplane/pkg/crd_manager.go.tpl delete mode 100644 templates/crossplane/pkg/crd_resource.go.tpl delete mode 100644 templates/crossplane/pkg/crd_sdk.go.tpl rename templates/crossplane/pkg/{crd_descriptor.go.tpl => resource/descriptor.go.tpl} (92%) rename templates/crossplane/pkg/{crd_identifiers.go.tpl => resource/identifiers.go.tpl} (91%) create mode 100644 templates/crossplane/pkg/resource/manager.go.tpl rename templates/crossplane/pkg/{crd_manager_factory.go.tpl => resource/manager_factory.go.tpl} (81%) create mode 100644 templates/crossplane/pkg/resource/resource.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk_find_get_attributes.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk_find_not_implemented.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk_find_read_many.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk_find_read_one.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk_update.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk_update_custom.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk_update_not_implemented.go.tpl create mode 100644 templates/crossplane/pkg/resource/sdk_update_set_attributes.go.tpl diff --git a/cmd/ack-generate/command/crossplane.go b/cmd/ack-generate/command/crossplane.go index e93047ae51..d44aaf630b 100644 --- a/cmd/ack-generate/command/crossplane.go +++ b/cmd/ack-generate/command/crossplane.go @@ -15,91 +15,59 @@ package command import ( "fmt" + "github.com/aws/aws-controllers-k8s/pkg/model" + "os" "path/filepath" "strings" - "github.com/spf13/cobra" + "github.com/aws/aws-controllers-k8s/pkg/crossplane" - "github.com/aws/aws-controllers-k8s/pkg/generate" - "github.com/aws/aws-controllers-k8s/pkg/model" + "github.com/spf13/cobra" ) // crossplaneCmd is the command that generates Crossplane API types var crossplaneCmd = &cobra.Command{ Use: "crossplane ", - Short: "Generate Crossplane-compatible Kubernetes API type definitions for a service", + Short: "Generate Crossplane Provider", RunE: generateCrossplane, } +var providerDir string + func init() { + crossplaneCmd.PersistentFlags().StringVar( + &providerDir, "provider-dir", ".", "the directory of the Crossplane provider", + ) rootCmd.AddCommand(crossplaneCmd) } -// generateCrossplane generates the Go files for Crossplane-compatible -// resources in the AWS service API. -func generateCrossplane(cmd *cobra.Command, args []string) error { +func generateCrossplane(_ *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify the service alias for the AWS service API to generate") } - optTemplatesDir = filepath.Join(optTemplatesDir, "crossplane") - svcAlias := strings.ToLower(args[0]) - if optAPIsOutputPath == "" { - optAPIsOutputPath = filepath.Join(optServicesDir, "crossplane") - } - if !optDryRun { - apisVersionPath = filepath.Join(optAPIsOutputPath, svcAlias, "apis", optGenVersion) - if _, err := ensureDir(apisVersionPath); err != nil { - return err - } - } if err := ensureSDKRepo(optCacheDir); err != nil { return err } + optTemplatesDir = filepath.Join(optTemplatesDir, "crossplane") + svcAlias := strings.ToLower(args[0]) sdkHelper := model.NewSDKHelper(sdkDir) + sdkHelper.APIGroupSuffix = "aws.crossplane.io" sdkAPI, err := sdkHelper.API(svcAlias) if err != nil { - return err - } - g, err := generate.New( - sdkAPI, optGenVersion, optGeneratorConfigPath, optTemplatesDir, - ) - if err != nil { - return err - } - - crds, err := g.GetCRDs() - if err != nil { - return err - } - typeDefs, _, err := g.GetTypeDefs() - if err != nil { - return err - } - enumDefs, err := g.GetEnumDefs() - if err != nil { - return err - } - - if err = writeDocGo(g); err != nil { - return err - } - - if err = writeGroupVersionInfoGo(g); err != nil { - return err - } - - if err = writeEnumsGo(g, enumDefs); err != nil { - return err - } - - if err = writeTypesGo(g, typeDefs); err != nil { - return err - } - - for _, crd := range crds { - if err = writeCRDGo(g, crd); err != nil { + newSvcAlias, err := FallBackFindServiceID(sdkDir, svcAlias) + if err != nil { return err } + sdkAPI, err = sdkHelper.API(newSvcAlias) // retry with serviceID + if err != nil { + return fmt.Errorf("cannot get the API model for service %s", svcAlias) + } + } + var opts []crossplane.GenerationOption + cfgPath := filepath.Join(providerDir, "apis", svcAlias, optGenVersion, "generator-config.yaml") + if _, err := os.Stat(cfgPath); !os.IsNotExist(err) { + opts = append(opts, crossplane.WithGeneratorConfigFilePath(cfgPath)) } - return nil + g := crossplane.NewGeneration(svcAlias, optGenVersion, providerDir, optTemplatesDir, sdkAPI, opts...) + return g.Generate() } diff --git a/docs/contents/dev-docs/code-generation.md b/docs/contents/dev-docs/code-generation.md index 3077f89960..bbd8045fef 100644 --- a/docs/contents/dev-docs/code-generation.md +++ b/docs/contents/dev-docs/code-generation.md @@ -107,6 +107,18 @@ Kind that the service controller manages. This step runs the `controller-gen rbac` command +### Crossplane Provider Generation + +We have experimental support for generating API types and controller code for AWS +services to be used in Crossplane AWS Provider. To try it out, you can run the +following command: + +```console +go run -tags codegen cmd/ack-generate/main.go crossplane apis ecr --provider-dir +cd +go generate ./... +``` + [1]: /~https://github.com/amazon-archives/aws-service-operator/tree/master/code-generation [2]: /~https://github.com/amazon-archives/aws-service-operator/tree/master/models [3]: /~https://github.com/amazon-archives/aws-service-operator/tree/master/code-generation/pkg/codegen/assets diff --git a/pkg/crossplane/apis.go b/pkg/crossplane/apis.go new file mode 100644 index 0000000000..4d66b1a13d --- /dev/null +++ b/pkg/crossplane/apis.go @@ -0,0 +1,110 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package crossplane + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/aws/aws-controllers-k8s/pkg/generate" + "github.com/iancoleman/strcase" + "github.com/pkg/errors" +) + +type APIFileGeneratorChain []func(*generate.Generator, string) error + +func (a APIFileGeneratorChain) Generate(g *generate.Generator, apiPath string) error { + for _, f := range a { + if err := f(g, apiPath); err != nil { + return err + } + } + return nil +} + +type APIFileGeneratorFn func(*generate.Generator, string) error + +func (a APIFileGeneratorFn) Generate(g *generate.Generator, apiPath string) error { + return a(g, apiPath) +} + +func GenerateCRDFiles(g *generate.Generator, apiPath string) error { + crds, err := g.GetCRDs() + if err != nil { + return errors.Wrap(err, "cannot generate CRDs") + } + for _, crd := range crds { + // TODO(muvaf): ACK hard-codes the suffix for the API group as "services.k8s.aws" + content, err := g.GenerateCRDFile(crd.Names.Original) + if err != nil { + return errors.Wrap(err, "cannot generate crd file") + } + path := filepath.Join(apiPath, fmt.Sprintf("zz_%s.go", strcase.ToSnake(crd.Kind))) + if err := ioutil.WriteFile(path, content.Bytes(), 0666); err != nil { + return errors.Wrap(err, "cannot write crd file") + } + } + return nil +} + +func GenerateTypesFile(g *generate.Generator, apiPath string) error { + typeDefs, _, err := g.GetTypeDefs() + if err != nil { + return errors.Wrap(err, "cannot generate type definitions") + } + if len(typeDefs) == 0 { + return nil + } + content, err := g.GenerateAPIFile("types") + if err != nil { + return errors.Wrap(err, "cannot generate types file") + } + path := filepath.Join(apiPath, "zz_types.go") + return errors.Wrap(ioutil.WriteFile(path, content.Bytes(), 0666), "cannot write types file") +} + +func GenerateEnumsFile(g *generate.Generator, apiPath string) error { + enumDefs, err := g.GetEnumDefs() + if err != nil { + return errors.Wrap(err, "cannot generate enum definitions") + } + if len(enumDefs) == 0 { + return nil + } + content, err := g.GenerateAPIFile("enums") + if err != nil { + return errors.Wrap(err, "cannot generate enums file") + } + path := filepath.Join(apiPath, "zz_enums.go") + return errors.Wrap(ioutil.WriteFile(path, content.Bytes(), 0666), "cannot write enums file") +} + +func GenerateGroupVersionInfoFile(g *generate.Generator, apiPath string) error { + gvi, err := g.GenerateAPIFile("groupversion_info") + if err != nil { + return errors.Wrap(err, "cannot generate groupversion_info file") + } + path := filepath.Join(apiPath, "zz_groupversion_info.go") + return errors.Wrap(ioutil.WriteFile(path, gvi.Bytes(), 0666), "cannot write groupversion_info file") +} + +func GenerateDocFile(g *generate.Generator, apiPath string) error { + gvi, err := g.GenerateAPIFile("doc") + if err != nil { + return errors.Wrap(err, "cannot generate doc file") + } + path := filepath.Join(apiPath, "zz_doc.go") + return errors.Wrap(ioutil.WriteFile(path, gvi.Bytes(), 0666), "cannot write doc file") +} diff --git a/pkg/crossplane/config.go b/pkg/crossplane/config.go new file mode 100644 index 0000000000..873afd3b1b --- /dev/null +++ b/pkg/crossplane/config.go @@ -0,0 +1,26 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package crossplane + +import "github.com/aws/aws-controllers-k8s/pkg/generate/config" + +// DefaultConfig is the default config object for Crossplane controllers. +var DefaultConfig = config.Config{ + PrefixConfig: config.PrefixConfig{ + SpecField: ".Spec.ForProvider", + StatusField: ".Status.AtProvider", + }, + IncludeACKMetadata: false, + SetManyOutputNotFoundErrReturn: "return cr", +} diff --git a/pkg/crossplane/controller.go b/pkg/crossplane/controller.go new file mode 100644 index 0000000000..5b85c44959 --- /dev/null +++ b/pkg/crossplane/controller.go @@ -0,0 +1,121 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package crossplane + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + + "github.com/aws/aws-controllers-k8s/pkg/generate" +) + +// TODO(muvaf): Template file names are hard-coded but we are able to write output +// to any file we want. So, we have to reuse the existing template files for any +// file we'd like to be generated, though we're free to change content of any +// template file. + +type ControllerGeneratorChain []func(*generate.Generator, string) error + +func (a ControllerGeneratorChain) Generate(g *generate.Generator, controllerPath string) error { + for _, f := range a { + if err := f(g, controllerPath); err != nil { + return err + } + } + return nil +} + +type ControllerGeneratorFn func(*generate.Generator, string) error + +func (a ControllerGeneratorFn) Generate(g *generate.Generator, controllerPath string) error { + return a(g, controllerPath) +} + +func GenerateController(g *generate.Generator, controllerPath string) error { + crds, err := g.GetCRDs() + if err != nil { + return err + } + for _, crd := range crds { + // TODO(muvaf): "manager" is hard-coded in ACK. + b, err := g.GenerateResourcePackageFile(crd.Names.Original, "manager") + if err != nil { + return err + } + dir := filepath.Join(controllerPath, crd.Names.Lower) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return errors.Wrap(err, "cannot create controller dir") + } + path := filepath.Join(dir, "zz_controller.go") + if err := ioutil.WriteFile(path, b.Bytes(), 0666); err != nil { + return err + } + } + return nil +} + +func GenerateConversions(g *generate.Generator, controllerPath string) error { + crds, err := g.GetCRDs() + if err != nil { + return err + } + for _, crd := range crds { + // TODO(muvaf): "sdk" is hard-coded in ACK. + b, err := g.GenerateResourcePackageFile(crd.Names.Original, "sdk") + if err != nil { + return err + } + dir := filepath.Join(controllerPath, crd.Names.Lower) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return errors.Wrap(err, "cannot create controller dir") + } + path := filepath.Join(dir, "zz_conversions.go") + if err := ioutil.WriteFile(path, b.Bytes(), 0666); err != nil { + return err + } + } + return nil +} + +func GenerateHooksBoilerplate(g *generate.Generator, controllerPath string) error { + crds, err := g.GetCRDs() + if err != nil { + return err + } + for _, crd := range crds { + // TODO(muvaf): "resource" is hard-coded in ACK. + b, err := g.GenerateResourcePackageFile(crd.Names.Original, "resource") + if err != nil { + return err + } + dir := filepath.Join(controllerPath, crd.Names.Lower) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return errors.Wrap(err, "cannot create controller dir") + } + path := filepath.Join(dir, fmt.Sprintf("hooks.go")) + if _, err := os.Stat(path); !os.IsNotExist(err) { + // NOTE(muvaf): Hook files are generated once and can be edited by + // the user later on. + continue + } + if err := ioutil.WriteFile(path, b.Bytes(), 0666); err != nil { + return err + } + } + return nil +} diff --git a/pkg/crossplane/generator.go b/pkg/crossplane/generator.go new file mode 100644 index 0000000000..f923a654ea --- /dev/null +++ b/pkg/crossplane/generator.go @@ -0,0 +1,108 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. + +package crossplane + +import ( + "os" + "os/exec" + "path/filepath" + + "github.com/aws/aws-controllers-k8s/pkg/generate" + "github.com/pkg/errors" + + "github.com/aws/aws-controllers-k8s/pkg/model" +) + +func WithGeneratorConfigFilePath(path string) GenerationOption { + return func(g *Generation) { + g.GeneratorConfigFilePath = path + } +} + +type GenerationOption func(*Generation) + +type APIFileGenerator interface { + Generate(g *generate.Generator, apiPath string) error +} + +type ControllerGenerator interface { + Generate(g *generate.Generator, controllerPath string) error +} + +func NewGeneration(serviceAlias, apiVersion, providerDirectory, templatePath string, sdkAPI *model.SDKAPI, opts ...GenerationOption) *Generation { + g := &Generation{ + ServiceAlias: serviceAlias, + APIVersion: apiVersion, + ProviderDirectory: providerDirectory, + TemplateBasePath: templatePath, + SDKAPI: sdkAPI, + apis: APIFileGeneratorChain{ + GenerateCRDFiles, + GenerateTypesFile, + GenerateEnumsFile, + GenerateGroupVersionInfoFile, + GenerateDocFile, + }, + controller: ControllerGeneratorChain{ + GenerateController, + GenerateConversions, + GenerateHooksBoilerplate, + }, + } + for _, o := range opts { + o(g) + } + return g +} + +type Generation struct { + ServiceAlias string + APIVersion string + ProviderDirectory string + TemplateBasePath string + GeneratorConfigFilePath string + SDKAPI *model.SDKAPI + + apis APIFileGenerator + controller ControllerGenerator +} + +func (g *Generation) Generate() error { + apiPath := filepath.Join(g.ProviderDirectory, "apis", g.ServiceAlias, g.APIVersion) + controllerPath := filepath.Join(g.ProviderDirectory, "pkg", "controller", g.ServiceAlias) + o, err := generate.New(g.SDKAPI, g.APIVersion, g.GeneratorConfigFilePath, g.TemplateBasePath, DefaultConfig) + if err != nil { + return errors.Wrap(err, "cannot create a new ACK Generator") + } + + // TODO(muvaf): Controllers are aware what CRD is used but APIs are not, so, + // we have to include them all in the same folder. + if err := os.MkdirAll(apiPath, os.ModePerm); err != nil { + return errors.Wrap(err, "cannot create api folder") + } + // TODO(muvaf): ACK generator requires all template files to be present during + // initTemplates even though we don't use them. + if err := g.apis.Generate(o, apiPath); err != nil { + return errors.Wrap(err, "cannot generate API files") + } + if err := g.controller.Generate(o, controllerPath); err != nil { + return errors.Wrap(err, "cannot generate controller files") + } + // TODO(muvaf): goimports don't allow to be included as a library. Make sure + // goimports binary exists. + if err := exec.Command("goimports", "-w", apiPath, controllerPath).Run(); err != nil { + return errors.Wrap(err, "cannot run goimports") + } + return nil +} diff --git a/pkg/generate/config/config.go b/pkg/generate/config/config.go index 3d40ba7680..3be7dcc85b 100644 --- a/pkg/generate/config/config.go +++ b/pkg/generate/config/config.go @@ -486,12 +486,12 @@ func New( if configPath == "" { return defaultConfig, nil } - contents, err := ioutil.ReadFile(configPath) + content, err := ioutil.ReadFile(configPath) if err != nil { return Config{}, err } gc := defaultConfig - if err = yaml.Unmarshal(contents, &gc); err != nil { + if err = yaml.Unmarshal(content, &gc); err != nil { return Config{}, err } return gc, nil diff --git a/pkg/names/names.go b/pkg/names/names.go index 2b5dc17a8b..11930e0e4c 100644 --- a/pkg/names/names.go +++ b/pkg/names/names.go @@ -150,6 +150,7 @@ type Names struct { Original string Camel string CamelLower string + Lower string Snake string } @@ -158,6 +159,7 @@ func New(original string) Names { Original: original, Camel: goName(original, false, false), CamelLower: goName(original, true, false), + Lower: strings.ToLower(original), Snake: goName(original, false, true), } } diff --git a/services/crossplane/ecr/apis/v1alpha1/doc.go b/services/crossplane/ecr/apis/v1alpha1/doc.go deleted file mode 100644 index a8f07a770c..0000000000 --- a/services/crossplane/ecr/apis/v1alpha1/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// +k8s:deepcopy-gen=package -// Package v1alpha1 is the v1alpha1 version of the ecr.services.k8s.aws API. -// +groupName=ecr.services.k8s.aws -package v1alpha1 diff --git a/services/crossplane/ecr/apis/v1alpha1/enums.go b/services/crossplane/ecr/apis/v1alpha1/enums.go deleted file mode 100644 index 1352f65ba3..0000000000 --- a/services/crossplane/ecr/apis/v1alpha1/enums.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. - -// Code generated by ack-generate. DO NOT EDIT. - -package v1alpha1 - -type EncryptionType string - -const ( - EncryptionType_AES256 EncryptionType = "AES256" - EncryptionType_KMS EncryptionType = "KMS" -) - -type FindingSeverity string - -const ( - FindingSeverity_INFORMATIONAL FindingSeverity = "INFORMATIONAL" - FindingSeverity_LOW FindingSeverity = "LOW" - FindingSeverity_MEDIUM FindingSeverity = "MEDIUM" - FindingSeverity_HIGH FindingSeverity = "HIGH" - FindingSeverity_CRITICAL FindingSeverity = "CRITICAL" - FindingSeverity_UNDEFINED FindingSeverity = "UNDEFINED" -) - -type ImageActionType string - -const ( - ImageActionType_EXPIRE ImageActionType = "EXPIRE" -) - -type ImageFailureCode string - -const ( - ImageFailureCode_InvalidImageDigest ImageFailureCode = "InvalidImageDigest" - ImageFailureCode_InvalidImageTag ImageFailureCode = "InvalidImageTag" - ImageFailureCode_ImageTagDoesNotMatchDigest ImageFailureCode = "ImageTagDoesNotMatchDigest" - ImageFailureCode_ImageNotFound ImageFailureCode = "ImageNotFound" - ImageFailureCode_MissingDigestAndTag ImageFailureCode = "MissingDigestAndTag" - ImageFailureCode_ImageReferencedByManifestList ImageFailureCode = "ImageReferencedByManifestList" - ImageFailureCode_KmsError ImageFailureCode = "KmsError" -) - -type ImageTagMutability string - -const ( - ImageTagMutability_MUTABLE ImageTagMutability = "MUTABLE" - ImageTagMutability_IMMUTABLE ImageTagMutability = "IMMUTABLE" -) - -type LayerAvailability string - -const ( - LayerAvailability_AVAILABLE LayerAvailability = "AVAILABLE" - LayerAvailability_UNAVAILABLE LayerAvailability = "UNAVAILABLE" -) - -type LayerFailureCode string - -const ( - LayerFailureCode_InvalidLayerDigest LayerFailureCode = "InvalidLayerDigest" - LayerFailureCode_MissingLayerDigest LayerFailureCode = "MissingLayerDigest" -) - -type LifecyclePolicyPreviewStatus string - -const ( - LifecyclePolicyPreviewStatus_IN_PROGRESS LifecyclePolicyPreviewStatus = "IN_PROGRESS" - LifecyclePolicyPreviewStatus_COMPLETE LifecyclePolicyPreviewStatus = "COMPLETE" - LifecyclePolicyPreviewStatus_EXPIRED LifecyclePolicyPreviewStatus = "EXPIRED" - LifecyclePolicyPreviewStatus_FAILED LifecyclePolicyPreviewStatus = "FAILED" -) - -type ScanStatus string - -const ( - ScanStatus_IN_PROGRESS ScanStatus = "IN_PROGRESS" - ScanStatus_COMPLETE ScanStatus = "COMPLETE" - ScanStatus_FAILED ScanStatus = "FAILED" -) - -type TagStatus string - -const ( - TagStatus_TAGGED TagStatus = "TAGGED" - TagStatus_UNTAGGED TagStatus = "UNTAGGED" - TagStatus_ANY TagStatus = "ANY" -) \ No newline at end of file diff --git a/services/crossplane/ecr/apis/v1alpha1/groupversion_info.go b/services/crossplane/ecr/apis/v1alpha1/groupversion_info.go deleted file mode 100644 index 451f8d98b8..0000000000 --- a/services/crossplane/ecr/apis/v1alpha1/groupversion_info.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. - -// Code generated by ack-generate. DO NOT EDIT. - -package v1alpha1 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var ( - // GroupVersion is the API Group Version used to register the objects - GroupVersion = schema.GroupVersion{Group: "ecr.services.k8s.aws", Version: "v1alpha1"} - - // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} - - // AddToScheme adds the types in this group-version to the given scheme. - AddToScheme = SchemeBuilder.AddToScheme -) diff --git a/services/crossplane/ecr/apis/v1alpha1/repository.go b/services/crossplane/ecr/apis/v1alpha1/repository.go deleted file mode 100644 index c3884ec7d3..0000000000 --- a/services/crossplane/ecr/apis/v1alpha1/repository.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. - -// Code generated by ack-generate. DO NOT EDIT. - -package v1alpha1 - -import ( - cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// RepositorySpecParams defines the desired state of Repository -type RepositorySpecParams struct { - EncryptionConfiguration *EncryptionConfiguration `json:"encryptionConfiguration,omitempty"` - ImageScanningConfiguration *ImageScanningConfiguration `json:"imageScanningConfiguration,omitempty"` - ImageTagMutability *string `json:"imageTagMutability,omitempty"` - RepositoryName *string `json:"repositoryName,omitempty"` - Tags []*Tag `json:"tags,omitempty"` -} - -// RepositorySpec defines the desired state of Repository -type RepositorySpec struct { - cpv1alpha1.ResourceSpec `json:",inline"` - ForProvider RepositorySpecParams `json:"forProvider"` -} - -// RepositoryExternalStatus defines the observed state of Repository -type RepositoryExternalStatus struct { - // TODO(negz): place common Crossplane-y stuff. - CreatedAt *metav1.Time `json:"createdAt,omitempty"` - RegistryID *string `json:"registryID,omitempty"` - RepositoryURI *string `json:"repositoryURI,omitempty"` -} - -// RepositoryStatus defines the observed state of Repository -type RepositoryStatus struct { - runtimev1alpha1.ResourceStatus `json:",inline"` - AtProvider RepositoryExternalStatus `json:"atProvider"` -} - -// Repository is the Schema for the Repositories API -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -type Repository struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec RepositorySpec `json:"spec,omitempty"` - Status RepositoryStatus `json:"status,omitempty"` -} - -// RepositoryList contains a list of Repository -// +kubebuilder:object:root=true -type RepositoryList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Repository `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Repository{}, &RepositoryList{}) -} - diff --git a/services/crossplane/ecr/apis/v1alpha1/types.go b/services/crossplane/ecr/apis/v1alpha1/types.go deleted file mode 100644 index 59a2d2ab1c..0000000000 --- a/services/crossplane/ecr/apis/v1alpha1/types.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. - -// Code generated by ack-generate. DO NOT EDIT. - -package v1alpha1 -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - -) - -type EncryptionConfiguration struct { - EncryptionType *string `json:"encryptionType,omitempty"` - KMSKey *string `json:"kmsKey,omitempty"` -} - -type Image struct { - RegistryID *string `json:"registryID,omitempty"` - RepositoryName *string `json:"repositoryName,omitempty"` -} - -type ImageDetail struct { - RegistryID *string `json:"registryID,omitempty"` - RepositoryName *string `json:"repositoryName,omitempty"` -} - -type ImageScanFinding struct { - URI *string `json:"uri,omitempty"` -} - -type ImageScanningConfiguration struct { - ScanOnPush *bool `json:"scanOnPush,omitempty"` -} - -type Repository_SDK struct { - CreatedAt *metav1.Time `json:"createdAt,omitempty"` - EncryptionConfiguration *EncryptionConfiguration `json:"encryptionConfiguration,omitempty"` - ImageScanningConfiguration *ImageScanningConfiguration `json:"imageScanningConfiguration,omitempty"` - ImageTagMutability *string `json:"imageTagMutability,omitempty"` - RegistryID *string `json:"registryID,omitempty"` - RepositoryARN *string `json:"repositoryARN,omitempty"` - RepositoryName *string `json:"repositoryName,omitempty"` - RepositoryURI *string `json:"repositoryURI,omitempty"` -} - -type Tag struct { - Key *string `json:"key,omitempty"` - Value *string `json:"value,omitempty"` -} \ No newline at end of file diff --git a/templates/boilerplate.go.tpl b/templates/boilerplate.go.tpl index c9576982aa..1a4de25c5b 100644 --- a/templates/boilerplate.go.tpl +++ b/templates/boilerplate.go.tpl @@ -1,16 +1,17 @@ {{- define "boilerplate" -}} -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +/* +Copyright 2020 The Crossplane Authors. -// Code generated by ack-generate. DO NOT EDIT. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ {{- end -}} diff --git a/templates/boilerplate.txt b/templates/boilerplate.txt index d81e763092..bc3ad82e01 100644 --- a/templates/boilerplate.txt +++ b/templates/boilerplate.txt @@ -1,14 +1,13 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Copyright 2020 The Crossplane Authors. -// Code generated by ack-generate. DO NOT EDIT. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. \ No newline at end of file diff --git a/templates/boilerplate_hash.go.tpl b/templates/boilerplate_hash.go.tpl index 748913fff1..d860a025e3 100644 --- a/templates/boilerplate_hash.go.tpl +++ b/templates/boilerplate_hash.go.tpl @@ -1,14 +1,15 @@ {{ define "boilerplate_hash" }} -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"). You may -# not use this file except in compliance with the License. A copy of the -# License is located at -# -# http://aws.amazon.com/apache2.0/ -# -# or in the "license" file accompanying this file. This file is distributed -# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -# express or implied. See the License for the specific language governing -# permissions and limitations under the License. +# Copyright 2020 The Crossplane Authors. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. {{- end -}} diff --git a/templates/crossplane/README.md b/templates/crossplane/README.md new file mode 100644 index 0000000000..c3ae289789 --- /dev/null +++ b/templates/crossplane/README.md @@ -0,0 +1,11 @@ +# Crossplane Provider Generation + +This folder includes the templates to generate AWS Crossplane Provider. Run the +following to generate: + +```console +go run -tags codegen cmd/ack-generate/main.go crossplane apis ecr --provider-dir +``` + +Then you will need to run `go generate ./...` in `provider-dir` so that `kubebuiler` +and other generation tools can generate the rest of machinerty for CRDs and Crossplane. \ No newline at end of file diff --git a/templates/crossplane/apis/crd.go.tpl b/templates/crossplane/apis/crd.go.tpl index d4274fd000..0fd938e9de 100644 --- a/templates/crossplane/apis/crd.go.tpl +++ b/templates/crossplane/apis/crd.go.tpl @@ -1,5 +1,7 @@ {{- template "boilerplate" }} +// Code generated by ack-generate. DO NOT EDIT. + package {{ .APIVersion }} import ( @@ -9,40 +11,58 @@ import ( {{ end }} {{- end }} - cpv1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" + runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" ) -// {{ .CRD.Kind }}SpecParams defines the desired state of {{ .CRD.Kind }} -type {{ .CRD.Kind }}SpecParams struct { +// {{ .CRD.Kind }}Parameters defines the desired state of {{ .CRD.Kind }} +type {{ .CRD.Kind }}Parameters struct { + // Region is which region the {{ .CRD.Kind }} will be created. + // +kubebuilder:validation:Required + Region string `json:"region"` {{- range $fieldName, $field := .CRD.SpecFields }} - {{ $field.Names.Camel }} {{ $field.GoType }} `json:"{{ $field.Names.CamelLower }},omitempty"` + {{- if $field.ShapeRef }} + {{ $field.ShapeRef.Documentation }} + {{- end }} + {{ if $field.IsRequired }} // +kubebuilder:validation:Required + {{ $field.Names.Camel }} {{ $field.GoType }} `json:"{{ $field.Names.CamelLower }}"` + {{- else }} {{ $field.Names.Camel }} {{ $field.GoType }} `json:"{{ $field.Names.CamelLower }},omitempty"` {{ end }} {{- end }} + Custom{{ .CRD.Kind }}Parameters `json:",inline"` } // {{ .CRD.Kind }}Spec defines the desired state of {{ .CRD.Kind }} type {{ .CRD.Kind }}Spec struct { - cpv1alpha1.ResourceSpec `json:",inline"` - ForProvider {{ .CRD.Kind }}SpecParams `json:"forProvider"` + runtimev1alpha1.ResourceSpec `json:",inline"` + ForProvider {{ .CRD.Kind }}Parameters `json:"forProvider"` } -// {{ .CRD.Kind }}ExternalStatus defines the observed state of {{ .CRD.Kind }} -type {{ .CRD.Kind }}ExternalStatus struct { - // TODO(negz): place common Crossplane-y stuff. +// {{ .CRD.Kind }}Observation defines the observed state of {{ .CRD.Kind }} +type {{ .CRD.Kind }}Observation struct { {{- range $fieldName, $field := .CRD.StatusFields }} + {{- if $field.ShapeRef }} + {{ $field.ShapeRef.Documentation }} + {{- end }} {{ $field.Names.Camel }} {{ $field.GoType }} `json:"{{ $field.Names.CamelLower }},omitempty"` {{- end }} } -// {{ .CRD.Kind }}Status defines the observed state of {{ .CRD.Kind }} +// {{ .CRD.Kind }}Status defines the observed state of {{ .CRD.Kind }}. type {{ .CRD.Kind }}Status struct { runtimev1alpha1.ResourceStatus `json:",inline"` - AtProvider {{ .CRD.Kind }}ExternalStatus `json:"atProvider"` + AtProvider {{ .CRD.Kind }}Observation `json:"atProvider"` } -// {{ .CRD.Kind }} is the Schema for the {{ .CRD.Plural }} API + // +kubebuilder:object:root=true + +// {{ .CRD.Kind }} is the Schema for the {{ .CRD.Plural }} API +// +kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +// +kubebuilder:printcolumn:name="SYNCED",type="string",JSONPath=".status.conditions[?(@.type=='Synced')].status" +// +kubebuilder:printcolumn:name="EXTERNAL-NAME",type="string",JSONPath=".metadata.annotations.crossplane\\.io/external-name" // +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,aws} type {{ .CRD.Kind }} struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -50,14 +70,23 @@ type {{ .CRD.Kind }} struct { Status {{ .CRD.Kind }}Status `json:"status,omitempty"` } -// {{ .CRD.Kind }}List contains a list of {{ .CRD.Kind }} // +kubebuilder:object:root=true + +// {{ .CRD.Kind }}List contains a list of {{ .CRD.Plural }} type {{ .CRD.Kind }}List struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` Items []{{ .CRD.Kind }} `json:"items"` } +// Repository type metadata. +var ( + {{ .CRD.Kind }}Kind = "{{ .CRD.Kind }}" + {{ .CRD.Kind }}GroupKind = schema.GroupKind{Group: Group, Kind: {{ .CRD.Kind }}Kind}.String() + {{ .CRD.Kind }}KindAPIVersion = {{ .CRD.Kind }}Kind + "." + GroupVersion.String() + {{ .CRD.Kind }}GroupVersionKind = GroupVersion.WithKind({{ .CRD.Kind }}Kind) +) + func init() { SchemeBuilder.Register(&{{ .CRD.Kind }}{}, &{{ .CRD.Kind }}List{}) } diff --git a/templates/crossplane/apis/doc.go.tpl b/templates/crossplane/apis/doc.go.tpl index 09de4845c9..70ba9c61b3 100644 --- a/templates/crossplane/apis/doc.go.tpl +++ b/templates/crossplane/apis/doc.go.tpl @@ -1,4 +1,6 @@ -// +k8s:deepcopy-gen=package +// +kubebuilder:object:generate=true // Package {{ .APIVersion }} is the {{ .APIVersion }} version of the {{ .APIGroup }} API. // +groupName={{ .APIGroup }} +// +versionName={{ .APIVersion }} + package {{ .APIVersion }} diff --git a/templates/crossplane/apis/enums.go.tpl b/templates/crossplane/apis/enums.go.tpl index 8b121c48c7..de7b9c5072 100644 --- a/templates/crossplane/apis/enums.go.tpl +++ b/templates/crossplane/apis/enums.go.tpl @@ -1,5 +1,7 @@ {{- template "boilerplate" }} +// Code generated by ack-generate. DO NOT EDIT. + package {{ .APIVersion }} {{- range $enumDef := .EnumDefs }} diff --git a/templates/crossplane/apis/groupversion_info.go.tpl b/templates/crossplane/apis/groupversion_info.go.tpl index fbf1cadd7a..1999cea94e 100644 --- a/templates/crossplane/apis/groupversion_info.go.tpl +++ b/templates/crossplane/apis/groupversion_info.go.tpl @@ -1,4 +1,6 @@ -{{ template "boilerplate" }} +{{- template "boilerplate" }} + +// Code generated by ack-generate. DO NOT EDIT. package {{ .APIVersion }} @@ -7,9 +9,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/scheme" ) +// Package type metadata. +const ( + Group = "{{ .APIGroup }}" + Version = "{{ .APIVersion }}" +) + var ( // GroupVersion is the API Group Version used to register the objects - GroupVersion = schema.GroupVersion{Group: "{{ .APIGroup }}", Version: "{{ .APIVersion }}"} + GroupVersion = schema.GroupVersion{Group: Group, Version: Version} // SchemeBuilder is used to add go types to the GroupVersionKind scheme SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} diff --git a/templates/crossplane/apis/type_def.go.tpl b/templates/crossplane/apis/type_def.go.tpl index 285f62b83b..6a0cbdcc0a 100644 --- a/templates/crossplane/apis/type_def.go.tpl +++ b/templates/crossplane/apis/type_def.go.tpl @@ -1,6 +1,9 @@ {{- define "type_def" -}} type {{ .Names.Camel }} struct { {{- range $attrName, $attr := .Attrs }} + {{- if $attr.Shape }} + {{ $attr.Shape.Documentation }} + {{- end }} {{ $attr.Names.Camel }} {{ $attr.GoType }} `json:"{{ $attr.Names.CamelLower }},omitempty"` {{- end }} } diff --git a/templates/crossplane/apis/types.go.tpl b/templates/crossplane/apis/types.go.tpl index 13c4e77623..9ff0010daf 100644 --- a/templates/crossplane/apis/types.go.tpl +++ b/templates/crossplane/apis/types.go.tpl @@ -1,5 +1,7 @@ {{- template "boilerplate" }} +// Code generated by ack-generate. DO NOT EDIT. + package {{ .APIVersion }} {{- if .Imports }} import ( diff --git a/templates/crossplane/boilerplate.go.tpl b/templates/crossplane/boilerplate.go.tpl index c9576982aa..1a4de25c5b 100644 --- a/templates/crossplane/boilerplate.go.tpl +++ b/templates/crossplane/boilerplate.go.tpl @@ -1,16 +1,17 @@ {{- define "boilerplate" -}} -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +/* +Copyright 2020 The Crossplane Authors. -// Code generated by ack-generate. DO NOT EDIT. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ {{- end -}} diff --git a/templates/crossplane/boilerplate.txt b/templates/crossplane/boilerplate.txt index d81e763092..e3ac8f2b0b 100644 --- a/templates/crossplane/boilerplate.txt +++ b/templates/crossplane/boilerplate.txt @@ -1,14 +1,15 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at -// -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +/* +Copyright 2020 The Crossplane Authors. -// Code generated by ack-generate. DO NOT EDIT. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ diff --git a/templates/crossplane/boilerplate_hash.go.tpl b/templates/crossplane/boilerplate_hash.go.tpl index 748913fff1..bb0fb99954 100644 --- a/templates/crossplane/boilerplate_hash.go.tpl +++ b/templates/crossplane/boilerplate_hash.go.tpl @@ -1,14 +1,15 @@ {{ define "boilerplate_hash" }} -# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# Copyright 2020 The Crossplane Authors. # -# Licensed under the Apache License, Version 2.0 (the "License"). You may -# not use this file except in compliance with the License. A copy of the -# License is located at +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# http://aws.amazon.com/apache2.0/ +# http://www.apache.org/licenses/LICENSE-2.0 # -# or in the "license" file accompanying this file. This file is distributed -# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -# express or implied. See the License for the specific language governing -# permissions and limitations under the License. +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. {{- end -}} diff --git a/templates/crossplane/cmd/controller/main.go.tpl b/templates/crossplane/cmd/controller/main.go.tpl index 79146dbd9d..2c3e71570b 100644 --- a/templates/crossplane/cmd/controller/main.go.tpl +++ b/templates/crossplane/cmd/controller/main.go.tpl @@ -1,5 +1,7 @@ {{ template "boilerplate" }} +// Code generated by ack-generate. DO NOT EDIT. + package main import ( diff --git a/templates/crossplane/pkg/crd_manager.go.tpl b/templates/crossplane/pkg/crd_manager.go.tpl deleted file mode 100644 index 6183f610c7..0000000000 --- a/templates/crossplane/pkg/crd_manager.go.tpl +++ /dev/null @@ -1,154 +0,0 @@ -{{ template "boilerplate" }} - -package {{ .CRD.Names.Snake }} - -import ( - "context" - "fmt" - - ackv1alpha1 "github.com/aws/aws-controllers-k8s/apis/core/v1alpha1" - ackrt "github.com/aws/aws-controllers-k8s/pkg/runtime" - acktypes "github.com/aws/aws-controllers-k8s/pkg/types" - "github.com/aws/aws-sdk-go/aws/session" - - svcsdk "github.com/aws/aws-sdk-go/service/{{ .ServiceIDClean }}" - svcsdkapi "github.com/aws/aws-sdk-go/service/{{ .ServiceIDClean }}/{{ .ServiceIDClean }}iface" -) - -// +kubebuilder:rbac:groups={{ .APIGroup }},resources={{ ToLower .CRD.Plural }},verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups={{ .APIGroup }},resources={{ ToLower .CRD.Plural }}/status,verbs=get;update;patch - -// resourceManager is responsible for providing a consistent way to perform -// CRUD operations in a backend AWS service API for Book custom resources. -type resourceManager struct { - // rr is the AWSResourceReconciler which can be used for various utility - // functions such as querying for Secret values given a SecretReference - rr acktypes.AWSResourceReconciler - // awsAccountID is the AWS account identifier that contains the resources - // managed by this resource manager - awsAccountID ackv1alpha1.AWSAccountID - // The AWS Region that this resource manager targets - awsRegion ackv1alpha1.AWSRegion - // sess is the AWS SDK Session object used to communicate with the backend - // AWS service API - sess *session.Session - // sdk is a pointer to the AWS service API interface exposed by the - // aws-sdk-go/services/{alias}/{alias}iface package. - sdkapi svcsdkapi.{{ .SDKAPIInterfaceTypeName }}API -} - -// concreteResource returns a pointer to a resource from the supplied -// generic AWSResource interface -func (rm *resourceManager) concreteResource( - res acktypes.AWSResource, -) *resource { - // cast the generic interface into a pointer type specific to the concrete - // implementing resource type managed by this resource manager - return res.(*resource) -} - -// ReadOne returns the currently-observed state of the supplied AWSResource in -// the backend AWS service API. -func (rm *resourceManager) ReadOne( - ctx context.Context, - res acktypes.AWSResource, -) (acktypes.AWSResource, error) { - r := rm.concreteResource(res) - if r.ko == nil { - // Should never happen... if it does, it's buggy code. - panic("resource manager's ReadOne() method received resource with nil CR object") - } - observed, err := rm.sdkFind(ctx, r) - if err != nil { - return nil, err - } - return observed, nil -} - -// Create attempts to create the supplied AWSResource in the backend AWS -// service API, returning an AWSResource representing the newly-created -// resource -func (rm *resourceManager) Create( - ctx context.Context, - res acktypes.AWSResource, -) (acktypes.AWSResource, error) { - r := rm.concreteResource(res) - if r.ko == nil { - // Should never happen... if it does, it's buggy code. - panic("resource manager's Create() method received resource with nil CR object") - } - created, err := rm.sdkCreate(ctx, r) - if err != nil { - return nil, err - } - return created, nil -} - -// Update attempts to mutate the supplied AWSResource in the backend AWS -// service API, returning an AWSResource representing the newly-mutated -// resource. Note that implementers should NOT check to see if the latest -// observed resource differs from the supplied desired state. The higher-level -// reonciler determines whether or not the desired differs from the latest -// observed and decides whether to call the resource manager's Update method -func (rm *resourceManager) Update( - ctx context.Context, - res acktypes.AWSResource, -) (acktypes.AWSResource, error) { - r := rm.concreteResource(res) - if r.ko == nil { - // Should never happen... if it does, it's buggy code. - panic("resource manager's Update() method received resource with nil CR object") - } - updated, err := rm.sdkUpdate(ctx, r) - if err != nil { - return nil, err - } - return updated, nil -} - -// Delete attempts to destroy the supplied AWSResource in the backend AWS -// service API. -func (rm *resourceManager) Delete( - ctx context.Context, - res acktypes.AWSResource, -) error { - r := rm.concreteResource(res) - if r.ko == nil { - // Should never happen... if it does, it's buggy code. - panic("resource manager's Update() method received resource with nil CR object") - } - return rm.sdkDelete(ctx, r) -} - -// ARNFromName returns an AWS Resource Name from a given string name. This -// is useful for constructing ARNs for APIs that require ARNs in their -// GetAttributes operations but all we have (for new CRs at least) is a -// name for the resource -func (rm *resourceManager) ARNFromName(name string) string { - return fmt.Sprintf( - "arn:aws:{{ .ServiceIDClean }}:%s:%s:%s", - rm.awsRegion, - rm.awsAccountID, - name, - ) -} - -// newResourceManager returns a new struct implementing -// acktypes.AWSResourceManager -func newResourceManager( - rr acktypes.AWSResourceReconciler, - id ackv1alpha1.AWSAccountID, - region ackv1alpha1.AWSRegion, -) (*resourceManager, error) { - sess, err := ackrt.NewSession() - if err != nil { - return nil, err - } - return &resourceManager{ - rr: rr, - awsAccountID: id, - awsRegion: region, - sess: sess, - sdkapi: svcsdk.New(sess), - }, nil -} diff --git a/templates/crossplane/pkg/crd_resource.go.tpl b/templates/crossplane/pkg/crd_resource.go.tpl deleted file mode 100644 index e0ecf28310..0000000000 --- a/templates/crossplane/pkg/crd_resource.go.tpl +++ /dev/null @@ -1,56 +0,0 @@ -{{ template "boilerplate" }} - -package {{ .CRD.Names.Snake }} - -import ( - ackv1alpha1 "github.com/aws/aws-controllers-k8s/apis/core/v1alpha1" - acktypes "github.com/aws/aws-controllers-k8s/pkg/types" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8srt "k8s.io/apimachinery/pkg/runtime" - - svcapitypes "github.com/aws/aws-controllers-k8s/services/{{ .ServiceIDClean }}/apis/{{ .APIVersion}}" -) - -// resource implements the `aws-service-operator-k8s/pkg/types.AWSResource` -// interface -type resource struct { - // The Kubernetes-native CR representing the resource - ko *svcapitypes.{{ .CRD.Names.Camel }} -} - -// Identifiers returns an AWSResourceIdentifiers object containing various -// identifying information, including the AWS account ID that owns the -// resource, the resource's AWS Resource Name (ARN) -func (r *resource) Identifiers() acktypes.AWSResourceIdentifiers { - return &resourceIdentifiers{r.ko.Status.ACKResourceMetadata} -} - -// IsBeingDeleted returns true if the Kubernetes resource has a non-zero -// deletion timestemp -func (r *resource) IsBeingDeleted() bool { - return !r.ko.DeletionTimestamp.IsZero() -} - -// RuntimeObject returns the Kubernetes apimachinery/runtime representation of -// the AWSResource -func (r *resource) RuntimeObject() k8srt.Object { - return r.ko -} - -// MetaObject returns the Kubernetes apimachinery/apis/meta/v1.Object -// representation of the AWSResource -func (r *resource) MetaObject() metav1.Object { - return r.ko -} - -// RuntimeMetaObject returns an object that implements both the Kubernetes -// apimachinery/runtime.Object and the Kubernetes -// apimachinery/apis/meta/v1.Object interfaces -func (r *resource) RuntimeMetaObject() acktypes.RuntimeMetaObject { - return r.ko -} - -// Conditions returns the ACK Conditions collection for the AWSResource -func (r *resource) Conditions() []*ackv1alpha1.Condition { - return r.ko.Status.Conditions -} diff --git a/templates/crossplane/pkg/crd_sdk.go.tpl b/templates/crossplane/pkg/crd_sdk.go.tpl deleted file mode 100644 index 51d1deeb76..0000000000 --- a/templates/crossplane/pkg/crd_sdk.go.tpl +++ /dev/null @@ -1,272 +0,0 @@ -{{ template "boilerplate" }} - -package {{ .CRD.Names.Snake }} - -import ( - "context" -{{- if .CRD.TypeImports }} -{{- range $packagePath, $alias := .CRD.TypeImports }} - {{ if $alias }}{{ $alias }} {{ end }}"{{ $packagePath }}" -{{ end }} - -{{- end }} - - ackv1alpha1 "github.com/aws/aws-controllers-k8s/apis/core/v1alpha1" - ackerr "github.com/aws/aws-controllers-k8s/pkg/errors" - "github.com/aws/aws-sdk-go/aws" - svcsdk "github.com/aws/aws-sdk-go/service/{{ .ServiceIDClean }}" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - svcapitypes "github.com/aws/aws-controllers-k8s/services/{{ .ServiceIDClean }}/apis/{{ .APIVersion }}" -) - -// Hack to avoid import errors during build... -var ( - _ = &metav1.Time{} - _ = &aws.JSONValue{} - _ = &svcsdk.{{ .SDKAPIInterfaceTypeName}}{} - _ = &svcapitypes.{{ .CRD.Names.Camel }}{} - _ = ackv1alpha1.AWSAccountID("") - _ = &ackerr.NotFound -) - -// sdkFind returns SDK-specific information about a supplied resource -func (rm *resourceManager) sdkFind( - ctx context.Context, - r *resource, -) (*resource, error) { -{{- if .CRD.Ops.ReadOne }} - // If any required fields in the input shape are missing, AWS resource is - // not created yet. Return NotFound here to indicate to callers that the - // resource isn't yet created. - if rm.requiredFieldsMissingFromReadOneInput(r) { - return nil, ackerr.NotFound - } - - input, err := rm.newDescribeRequestPayload(r) - if err != nil { - return nil, err - } -{{ $setCode := GoCodeSetReadOneOutput .CRD "resp" "ko.Status" 1 }} - {{ if and .CRD.StatusFields ( not ( Empty $setCode ) ) }}resp{{ else }}_{{ end }}, respErr := rm.sdkapi.{{ .CRD.Ops.ReadOne.Name }}WithContext(ctx, input) - if respErr != nil { - if awsErr, ok := ackerr.AWSError(respErr); ok && awsErr.Code() == "{{ ResourceExceptionCode .CRD 404 }}" { - return nil, ackerr.NotFound - } - return nil, err - } - - // Merge in the information we read from the API call above to the copy of - // the original Kubernetes object we passed to the function - ko := r.ko.DeepCopy() -{{ $setCode }} - return &resource{ko}, nil -{{- else if .CRD.Ops.GetAttributes }} - // If any required fields in the input shape are missing, AWS resource is - // not created yet. Return NotFound here to indicate to callers that the - // resource isn't yet created. - if rm.requiredStatusFieldsMissingFromGetAttributesInput(r) { - return nil, ackerr.NotFound - } - - input, err := rm.newGetAttributesRequestPayload(r) - if err != nil { - return nil, err - } -{{ $setCode := GoCodeGetAttributesSetOutput .CRD "resp" "ko.Status" 1 }} - {{ if and .CRD.StatusFields ( not ( Empty $setCode ) ) }}resp{{ else }}_{{ end }}, respErr := rm.sdkapi.{{ .CRD.Ops.GetAttributes.Name }}WithContext(ctx, input) - if respErr != nil { - if awsErr, ok := ackerr.AWSError(respErr); ok && awsErr.Code() == "{{ ResourceExceptionCode .CRD 404 }}" { - return nil, ackerr.NotFound - } - return nil, respErr - } - - // Merge in the information we read from the API call above to the copy of - // the original Kubernetes object we passed to the function - ko := r.ko.DeepCopy() -{{ $setCode }} - return &resource{ko}, nil -{{- else if .CRD.Ops.ReadMany }} - input, err := rm.newListRequestPayload(r) - if err != nil { - return nil, err - } -{{ $setCode := GoCodeSetReadManyOutput .CRD "resp" "ko" 1 }} - {{ if not ( Empty $setCode ) }}resp{{ else }}_{{ end }}, respErr := rm.sdkapi.{{ .CRD.Ops.ReadMany.Name }}WithContext(ctx, input) - if respErr != nil { - if awsErr, ok := ackerr.AWSError(respErr); ok && awsErr.Code() == "{{ ResourceExceptionCode .CRD 404 }}" { - return nil, ackerr.NotFound - } - return nil, respErr - } - - // Merge in the information we read from the API call above to the copy of - // the original Kubernetes object we passed to the function - ko := r.ko.DeepCopy() -{{ $setCode }} - return &resource{ko}, nil -{{- else }} - // Believe it or not, there are API resources that can be created but there - // is no read operation. Point in case: RDS' CreateDBInstanceReadReplica - // has no corresponding read operation that I know of... - return nil, ackerr.NotImplemented -{{- end }} -} - -{{- if .CRD.Ops.ReadOne }} -// requiredFieldsMissingFromReadOneInput returns true if there are any fields -// for the ReadOne Input shape that are required by not present in the -// resource's Spec or Status -func (rm *resourceManager) requiredFieldsMissingFromReadOneInput( - r *resource, -) bool { -{{ GoCodeRequiredFieldsMissingFromReadOneInput .CRD "r.ko" 1 }} -} - -// newDescribeRequestPayload returns SDK-specific struct for the HTTP request -// payload of the Describe API call for the resource -func (rm *resourceManager) newDescribeRequestPayload( - r *resource, -) (*svcsdk.{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}, error) { - res := &svcsdk.{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}{} -{{ GoCodeSetReadOneInput .CRD "r.ko" "res" 1 }} - return res, nil -} -{{- end }} - -{{- if .CRD.Ops.ReadMany }} -// newListRequestPayload returns SDK-specific struct for the HTTP request -// payload of the List API call for the resource -func (rm *resourceManager) newListRequestPayload( - r *resource, -) (*svcsdk.{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}, error) { - res := &svcsdk.{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}{} -{{ GoCodeSetReadManyInput .CRD "r.ko" "res" 1 }} - return res, nil -} -{{- end }} - -{{- if .CRD.Ops.GetAttributes }} -// requiredFieldsMissingFromGetAtttributesInput returns true if there are any -// fields for the GetAttributes Input shape that are required by not present in -// the resource's Spec or Status -func (rm *resourceManager) requiredFieldsMissingFromGetAttributesInput( - r *resource, -) bool { -{{ GoCodeRequiredFieldsMissingFromGetAttributesInput .CRD "r.ko" 1 }} -} - -// newGetAttributesRequestPayload returns SDK-specific struct for the HTTP -// request payload of the GetAttributes API call for the resource -func (rm *resourceManager) newGetAttributesRequestPayload( - r *resource, -) (*svcsdk.{{ .CRD.Ops.GetAttributes.InputRef.Shape.ShapeName }}, error) { - res := &svcsdk.{{ .CRD.Ops.GetAttributes.InputRef.Shape.ShapeName }}{} -{{ GoCodeGetAttributesSetInput .CRD "r.ko" "res" 1 }} - return res, nil -} -{{- end }} - -// sdkCreate creates the supplied resource in the backend AWS service API and -// returns a new resource with any fields in the Status field filled in -func (rm *resourceManager) sdkCreate( - ctx context.Context, - r *resource, -) (*resource, error) { - input, err := rm.newCreateRequestPayload(r) - if err != nil { - return nil, err - } -{{ $createCode := GoCodeSetCreateOutput .CRD "resp" "ko.Status" 1 }} - {{ if and .CRD.StatusFields ( not ( Empty $createCode ) ) }}resp{{ else }}_{{ end }}, respErr := rm.sdkapi.{{ .CRD.Ops.Create.Name }}WithContext(ctx, input) - if respErr != nil { - return nil, respErr - } - // Merge in the information we read from the API call above to the copy of - // the original Kubernetes object we passed to the function - ko := r.ko.DeepCopy() -{{ $createCode }} - ko.Status.ACKResourceMetadata = &ackv1alpha1.ResourceMetadata{OwnerAccountID: &rm.awsAccountID} - ko.Status.Conditions = []*ackv1alpha1.Condition{} - return &resource{ko}, nil -} - -// newCreateRequestPayload returns an SDK-specific struct for the HTTP request -// payload of the Create API call for the resource -func (rm *resourceManager) newCreateRequestPayload( - r *resource, -) (*svcsdk.{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}, error) { - res := &svcsdk.{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}{} -{{ GoCodeSetCreateInput .CRD "r.ko" "res" 1 }} - return res, nil -} - -// sdkUpdate patches the supplied resource in the backend AWS service API and -// returns a new resource with updated fields. -func (rm *resourceManager) sdkUpdate( - ctx context.Context, - r *resource, -) (*resource, error) { -{{- if .CRD.Ops.Update }} - input, err := rm.newUpdateRequestPayload(r) - if err != nil { - return nil, err - } -{{ $setCode := GoCodeSetUpdateOutput .CRD "resp" "ko.Status" 1 }} - {{ if and .CRD.StatusFields ( not ( Empty $setCode ) ) }}resp{{ else }}_{{ end }}, respErr := rm.sdkapi.{{ .CRD.Ops.Update.Name }}WithContext(ctx, input) - if respErr != nil { - return nil, respErr - } - // Merge in the information we read from the API call above to the copy of - // the original Kubernetes object we passed to the function - ko := r.ko.DeepCopy() -{{ $setCode }} - return &resource{ko}, nil -{{- else }} - // TODO(jaypipes): Figure this out... - return nil, nil -{{- end }} -} - -{{- if .CRD.Ops.Update }} -// newUpdateRequestPayload returns an SDK-specific struct for the HTTP request -// payload of the Update API call for the resource -func (rm *resourceManager) newUpdateRequestPayload( - r *resource, -) (*svcsdk.{{ .CRD.Ops.Update.InputRef.Shape.ShapeName }}, error) { - res := &svcsdk.{{ .CRD.Ops.Update.InputRef.Shape.ShapeName }}{} -{{ GoCodeSetUpdateInput .CRD "r.ko" "res" 1 }} - return res, nil -} -{{ end }} - -// sdkDelete deletes the supplied resource in the backend AWS service API -func (rm *resourceManager) sdkDelete( - ctx context.Context, - r *resource, -) error { -{{- if .CRD.Ops.Delete }} - input, err := rm.newDeleteRequestPayload(r) - if err != nil { - return err - } - _, respErr := rm.sdkapi.{{ .CRD.Ops.Delete.Name }}WithContext(ctx, input) - return respErr -{{- else }} - // TODO(jaypipes): Figure this out... - return nil -{{ end }} -} - -{{ if .CRD.Ops.Delete -}} -// newDeleteRequestPayload returns an SDK-specific struct for the HTTP request -// payload of the Delete API call for the resource -func (rm *resourceManager) newDeleteRequestPayload( - r *resource, -) (*svcsdk.{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}, error) { - res := &svcsdk.{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}{} -{{ GoCodeSetDeleteInput .CRD "r.ko" "res" 1 }} - return res, nil -} -{{- end -}} diff --git a/templates/crossplane/pkg/crd_descriptor.go.tpl b/templates/crossplane/pkg/resource/descriptor.go.tpl similarity index 92% rename from templates/crossplane/pkg/crd_descriptor.go.tpl rename to templates/crossplane/pkg/resource/descriptor.go.tpl index 5d5642f9ae..dd1b116ee8 100644 --- a/templates/crossplane/pkg/crd_descriptor.go.tpl +++ b/templates/crossplane/pkg/resource/descriptor.go.tpl @@ -1,9 +1,12 @@ {{ template "boilerplate" }} -package {{ .CRD.Names.Snake }} +// Code generated by ack-generate. DO NOT EDIT. + +package {{ .CRD.Names.Lower }} import ( acktypes "github.com/aws/aws-controllers-k8s/pkg/types" + ackcompare "github.com/aws/aws-controllers-k8s/pkg/compare" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -65,18 +68,19 @@ func (d *resourceDescriptor) Equal( return cmp.Equal(ac.ko, bc.ko, opts) } -// Diff returns a string representing the difference between two supplied +// Diff returns a Reporter which provides the difference between two supplied // AWSResources. The underlying types of the two supplied AWSResources should // be the same. In other words, the Diff() method should be called with the // same concrete implementing AWSResource type func (d *resourceDescriptor) Diff( a acktypes.AWSResource, b acktypes.AWSResource, -) string { +) *ackcompare.Reporter { ac := a.(*resource) bc := b.(*resource) - opts := cmpopts.EquateEmpty() - return cmp.Diff(ac.ko, bc.ko, opts) + var diffReporter ackcompare.Reporter + cmp.Equal(ac.ko, bc.ko, cmp.Reporter(&diffReporter), cmp.AllowUnexported(svcapitypes.{{ .CRD.Kind }}{})) + return &diffReporter } // UpdateCRStatus accepts an AWSResource object and changes the Status diff --git a/templates/crossplane/pkg/crd_identifiers.go.tpl b/templates/crossplane/pkg/resource/identifiers.go.tpl similarity index 91% rename from templates/crossplane/pkg/crd_identifiers.go.tpl rename to templates/crossplane/pkg/resource/identifiers.go.tpl index 9fb74b641d..5fb09b5ca6 100644 --- a/templates/crossplane/pkg/crd_identifiers.go.tpl +++ b/templates/crossplane/pkg/resource/identifiers.go.tpl @@ -1,6 +1,8 @@ {{ template "boilerplate" }} -package {{ .CRD.Names.Snake }} +// Code generated by ack-generate. DO NOT EDIT. + +package {{ .CRD.Names.Lower }} import ( ackv1alpha1 "github.com/aws/aws-controllers-k8s/apis/core/v1alpha1" diff --git a/templates/crossplane/pkg/resource/manager.go.tpl b/templates/crossplane/pkg/resource/manager.go.tpl new file mode 100644 index 0000000000..1e50f5fd1a --- /dev/null +++ b/templates/crossplane/pkg/resource/manager.go.tpl @@ -0,0 +1,145 @@ +{{ template "boilerplate" }} + +// Code generated by ack-generate. DO NOT EDIT. + +package {{ .CRD.Names.Lower }} + +import ( + "context" + + "github.com/pkg/errors" + "github.com/google/go-cmp/cmp" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + svcsdkapi "github.com/aws/aws-sdk-go/service/{{ .ServiceIDClean }}/{{ .ServiceIDClean }}iface" + svcapi "github.com/aws/aws-sdk-go/service/{{ .ServiceIDClean }}" + "github.com/aws/aws-sdk-go/aws/session" + + "github.com/crossplane/crossplane-runtime/pkg/meta" + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + cpresource "github.com/crossplane/crossplane-runtime/pkg/resource" + runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" + + svcapitypes "github.com/crossplane/provider-aws/apis/{{ .ServiceIDClean }}/{{ .APIVersion}}" + awsclient "github.com/crossplane/provider-aws/pkg/clients" +) + +const ( + errUnexpectedObject = "managed resource is not an {{ .CRD.Names.Camel }} resource" + + errCreateSession = "cannot create a new session" + errCreate = "cannot create {{ .CRD.Names.Camel }} in AWS" + errDescribe = "failed to describe {{ .CRD.Names.Camel }}" + errDelete = "failed to delete {{ .CRD.Names.Camel }}" +) + +type connector struct { + kube client.Client +} + +func (c *connector) Connect(ctx context.Context, mg cpresource.Managed) (managed.ExternalClient, error) { + cr, ok := mg.(*svcapitypes.{{ .CRD.Names.Camel }}) + if !ok { + return nil, errors.New(errUnexpectedObject) + } + sess, err := awsclient.GetConfigV1(ctx, c.kube, mg, cr.Spec.ForProvider.Region) + if err != nil { + return nil, err + } + return &external{client: svcapi.New(sess), kube: c.kube}, errors.Wrap(err, errCreateSession) +} + +type external struct { + kube client.Client + client svcsdkapi.{{ .SDKAPIInterfaceTypeName }}API +} + +func (e *external) Observe(ctx context.Context, mg cpresource.Managed) (managed.ExternalObservation, error) { + cr, ok := mg.(*svcapitypes.{{ .CRD.Names.Camel }}) + if !ok { + return managed.ExternalObservation{}, errors.New(errUnexpectedObject) + } + if err := e.preObserve(ctx, cr); err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, "pre-observe failed") + } + if meta.GetExternalName(cr) == "" { + return managed.ExternalObservation{ + ResourceExists: false, + }, nil + } + +{{- if .CRD.Ops.ReadOne }} + input := Generate{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}(cr) + resp, err := e.client.{{ .CRD.Ops.ReadOne.Name }}WithContext(ctx, input) + if err != nil { + return managed.ExternalObservation{ResourceExists: false}, errors.Wrap(cpresource.Ignore(IsNotFound, err), errDescribe) + } +{{- else if .CRD.Ops.ReadMany }} + input := Generate{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}(cr) + + resp, err := e.client.{{ .CRD.Ops.ReadMany.Name }}WithContext(ctx, input) + if err != nil { + return managed.ExternalObservation{ResourceExists: false}, errors.Wrap(cpresource.Ignore(IsNotFound, err), errDescribe) + } + resp = e.filterList(cr, resp) + if len(resp.Items) == 0 { + return managed.ExternalObservation{ResourceExists: false}, nil + } +{{- end }} + currentSpec := cr.Spec.ForProvider.DeepCopy() + lateInitialize(&cr.Spec.ForProvider, resp) + Generate{{ .CRD.Names.Camel }}(resp).Status.AtProvider.DeepCopyInto(&cr.Status.AtProvider) + return e.postObserve(ctx, cr, resp, managed.ExternalObservation{ + ResourceExists: true, + ResourceUpToDate: isUpToDate(cr, resp), + ResourceLateInitialized: !cmp.Equal(&cr.Spec.ForProvider, currentSpec), + }, nil) +} + +func (e *external) Create(ctx context.Context, mg cpresource.Managed) (managed.ExternalCreation, error) { + cr, ok := mg.(*svcapitypes.{{ .CRD.Names.Camel }}) + if !ok { + return managed.ExternalCreation{}, errors.New(errUnexpectedObject) + } + cr.Status.SetConditions(runtimev1alpha1.Creating()) + if err := e.preCreate(ctx, cr); err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, "pre-create failed") + } + input := Generate{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}(cr) + resp, err := e.client.{{ .CRD.Ops.Create.Name }}WithContext(ctx, input) + if err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreate) + } +{{ GoCodeSetCreateOutput .CRD "resp" "cr" 1 false }} + return e.postCreate(ctx, cr, resp, managed.ExternalCreation{}, err) +} + +func (e *external) Update(ctx context.Context, mg cpresource.Managed) (managed.ExternalUpdate, error) { + cr, ok := mg.(*svcapitypes.{{ .CRD.Names.Camel }}) + if !ok { + return managed.ExternalUpdate{}, errors.New(errUnexpectedObject) + } + if err := e.preUpdate(ctx, cr); err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, "pre-update failed") + } + return e.postUpdate(ctx, cr, managed.ExternalUpdate{}, nil) +} + +func (e *external) Delete(ctx context.Context, mg cpresource.Managed) error { + cr, ok := mg.(*svcapitypes.{{ .CRD.Names.Camel }}) + if !ok { + return errors.New(errUnexpectedObject) + } + cr.Status.SetConditions(runtimev1alpha1.Deleting()) + {{- if .CRD.Ops.Delete }} + input := Generate{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}(cr) + _, err := e.client.{{ .CRD.Ops.Delete.Name }}WithContext(ctx, input) + return errors.Wrap(cpresource.Ignore(IsNotFound, err), errDelete) + {{- else }} + // TODO(jaypipes): Figure this out... + return nil + {{ end }} +} diff --git a/templates/crossplane/pkg/crd_manager_factory.go.tpl b/templates/crossplane/pkg/resource/manager_factory.go.tpl similarity index 81% rename from templates/crossplane/pkg/crd_manager_factory.go.tpl rename to templates/crossplane/pkg/resource/manager_factory.go.tpl index 6109d7bbc1..520a723bea 100644 --- a/templates/crossplane/pkg/crd_manager_factory.go.tpl +++ b/templates/crossplane/pkg/resource/manager_factory.go.tpl @@ -1,12 +1,17 @@ {{ template "boilerplate" }} -package {{ .CRD.Names.Snake }} +// Code generated by ack-generate. DO NOT EDIT. + +package {{ .CRD.Names.Lower }} import ( "sync" ackv1alpha1 "github.com/aws/aws-controllers-k8s/apis/core/v1alpha1" + ackmetrics "github.com/aws/aws-controllers-k8s/pkg/metrics" acktypes "github.com/aws/aws-controllers-k8s/pkg/types" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/go-logr/logr" svcresource "github.com/aws/aws-controllers-k8s/services/{{ .ServiceIDClean }}/pkg/resource" ) @@ -28,7 +33,10 @@ func (f *resourceManagerFactory) ResourceDescriptor() acktypes.AWSResourceDescri // ManagerFor returns a resource manager object that can manage resources for a // supplied AWS account func (f *resourceManagerFactory) ManagerFor( + log logr.Logger, + metrics *ackmetrics.Metrics, rr acktypes.AWSResourceReconciler, + sess *session.Session, id ackv1alpha1.AWSAccountID, region ackv1alpha1.AWSRegion, ) (acktypes.AWSResourceManager, error) { @@ -43,7 +51,7 @@ func (f *resourceManagerFactory) ManagerFor( f.Lock() defer f.Unlock() - rm, err := newResourceManager(rr, id, region) + rm, err := newResourceManager(log, metrics, rr, sess, id, region) if err != nil { return nil, err } diff --git a/templates/crossplane/pkg/resource/resource.go.tpl b/templates/crossplane/pkg/resource/resource.go.tpl new file mode 100644 index 0000000000..ed588d697c --- /dev/null +++ b/templates/crossplane/pkg/resource/resource.go.tpl @@ -0,0 +1,117 @@ +{{ template "boilerplate" }} + +package {{ .CRD.Names.Lower }} + +import ( + "context" + + svcsdk "github.com/aws/aws-sdk-go/service/{{ .ServiceIDClean }}" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/crossplane/crossplane-runtime/pkg/event" + "github.com/crossplane/crossplane-runtime/pkg/logging" + "github.com/crossplane/crossplane-runtime/pkg/reconciler/managed" + "github.com/crossplane/crossplane-runtime/pkg/resource" + + svcapitypes "github.com/crossplane/provider-aws/apis/{{ .ServiceIDClean }}/{{ .APIVersion}}" +) + +// Setup{{ .CRD.Names.Camel }} adds a controller that reconciles {{ .CRD.Names.Camel }}. +func Setup{{ .CRD.Names.Camel }}(mgr ctrl.Manager, l logging.Logger) error { + name := managed.ControllerName(svcapitypes.{{ .CRD.Names.Camel }}GroupKind) + return ctrl.NewControllerManagedBy(mgr). + Named(name). + For(&svcapitypes.{{ .CRD.Names.Camel }}{}). + Complete(managed.NewReconciler(mgr, + resource.ManagedKind(svcapitypes.{{ .CRD.Names.Camel }}GroupVersionKind), + managed.WithExternalConnecter(&connector{kube: mgr.GetClient()}), + managed.WithLogger(l.WithValues("controller", name)), + managed.WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name))))) +} + +func (*external) preObserve(context.Context, *svcapitypes.{{ .CRD.Names.Camel }}) error { + return nil +} + +{{- if .CRD.Ops.ReadOne }} +func (*external) postObserve(_ context.Context, _ *svcapitypes.{{ .CRD.Names.Camel }}, _ *svcsdk.{{ .CRD.Ops.ReadOne.OutputRef.Shape.ShapeName }}, obs managed.ExternalObservation, err error) (managed.ExternalObservation, error) { + return obs, err +} +{{- else if .CRD.Ops.ReadMany }} +func (*external) postObserve(_ context.Context, _ *svcapitypes.{{ .CRD.Names.Camel }}, _ *svcsdk.{{ .CRD.Ops.ReadMany.OutputRef.Shape.ShapeName }}, obs managed.ExternalObservation, err error) (managed.ExternalObservation, error) { + return obs, err +} + +func (*external) filterList(_ *svcapitypes.{{ .CRD.Names.Camel }}, list *svcsdk.{{ .CRD.Ops.ReadMany.OutputRef.Shape.ShapeName }}) *svcsdk.{{ .CRD.Ops.ReadMany.OutputRef.Shape.ShapeName }} { + return list +} +{{ end }} + +func (*external) preCreate(context.Context, *svcapitypes.{{ .CRD.Names.Camel }}) error { + return nil +} + +func (*external) postCreate(_ context.Context, _ *svcapitypes.{{ .CRD.Names.Camel }}, _ *svcsdk.{{ .CRD.Ops.Create.OutputRef.Shape.ShapeName }}, cre managed.ExternalCreation, err error) (managed.ExternalCreation, error) { + return cre, err +} + +func (*external) preUpdate(context.Context, *svcapitypes.{{ .CRD.Names.Camel }}) error { + return nil +} + +func (*external) postUpdate(_ context.Context, _ *svcapitypes.{{ .CRD.Names.Camel }}, upd managed.ExternalUpdate, err error) (managed.ExternalUpdate, error) { + return upd, err +} + +{{- if .CRD.Ops.ReadOne }} +func lateInitialize(*svcapitypes.{{ .CRD.Names.Camel }}Parameters,*svcsdk.{{ .CRD.Ops.ReadOne.OutputRef.Shape.ShapeName }}) error { + return nil +} + +func isUpToDate(*svcapitypes.{{ .CRD.Names.Camel }},*svcsdk.{{ .CRD.Ops.ReadOne.OutputRef.Shape.ShapeName }}) bool { + return true +} + +func preGenerate{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}(_ *svcapitypes.{{ .CRD.Names.Camel }}, obj *svcsdk.{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }})*svcsdk.{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }} { + return obj +} + +func postGenerate{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}(_ *svcapitypes.{{ .CRD.Names.Camel }}, obj *svcsdk.{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }})*svcsdk.{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }} { + return obj +} +{{- else if .CRD.Ops.ReadMany }} + +func lateInitialize(*svcapitypes.{{ .CRD.Names.Camel }}Parameters,*svcsdk.{{ .CRD.Ops.ReadMany.OutputRef.Shape.ShapeName }}) error { + return nil +} + +func isUpToDate(*svcapitypes.{{ .CRD.Names.Camel }},*svcsdk.{{ .CRD.Ops.ReadOne.OutputRef.Shape.ShapeName }}) bool { + return true +} + +func preGenerate{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}(_ *svcapitypes.{{ .CRD.Names.Camel }}, obj *svcsdk.{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }})*svcsdk.{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }} { + return obj +} + +func postGenerate{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}(_ *svcapitypes.{{ .CRD.Names.Camel }}, obj *svcsdk.{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }})*svcsdk.{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }} { + return obj +} +{{ end }} + +func preGenerate{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}(_ *svcapitypes.{{ .CRD.Names.Camel }}, obj *svcsdk.{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}) *svcsdk.{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }} { + return obj +} + +func postGenerate{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}(_ *svcapitypes.{{ .CRD.Names.Camel }}, obj *svcsdk.{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}) *svcsdk.{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }} { + return obj +} + +{{- if .CRD.Ops.Delete }} +func preGenerate{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}(_ *svcapitypes.{{ .CRD.Names.Camel }}, obj *svcsdk.{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}) *svcsdk.{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }} { + return obj +} + +func postGenerate{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}(_ *svcapitypes.{{ .CRD.Names.Camel }}, obj *svcsdk.{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}) *svcsdk.{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }} { + return obj +} +{{- end }} \ No newline at end of file diff --git a/templates/crossplane/pkg/resource/sdk.go.tpl b/templates/crossplane/pkg/resource/sdk.go.tpl new file mode 100644 index 0000000000..184d53fd8b --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk.go.tpl @@ -0,0 +1,50 @@ +{{ template "boilerplate" }} + +// Code generated by ack-generate. DO NOT EDIT. + +package {{ .CRD.Names.Lower }} + +import ( +{{- if .CRD.TypeImports }} +{{- range $packagePath, $alias := .CRD.TypeImports }} + {{ if $alias }}{{ $alias }} {{ end }}"{{ $packagePath }}" +{{ end }} +{{- end }} + "github.com/aws/aws-sdk-go/aws/awserr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + svcsdk "github.com/aws/aws-sdk-go/service/{{ .ServiceIDClean }}" + + svcapitypes "github.com/crossplane/provider-aws/apis/{{ .ServiceIDClean }}/{{ .APIVersion}}" +) + +// NOTE(muvaf): We return pointers in case the function needs to start with an +// empty object, hence need to return a new pointer. +// TODO(muvaf): We can generate one-time boilerplate for these hooks but currently +// ACK doesn't support not generating if file exists. + +{{ if .CRD.Ops.ReadOne }} + {{- template "sdk_find_read_one" . }} +{{- else if .CRD.Ops.ReadMany }} + {{- template "sdk_find_read_many" . }} +{{- end }} + +// Generate{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }} returns a create input. +func Generate{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}(cr *svcapitypes.{{ .CRD.Names.Camel }}) *svcsdk.{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }} { + res := preGenerate{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}(cr, &svcsdk.{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}{}) +{{ GoCodeSetCreateInput .CRD "cr" "res" 1 }} + return postGenerate{{ .CRD.Ops.Create.InputRef.Shape.ShapeName }}(cr, res) +} + +{{ if .CRD.Ops.Delete -}} +// Generate{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }} returns a deletion input. +func Generate{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}(cr *svcapitypes.{{ .CRD.Names.Camel }}) *svcsdk.{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }} { + res := preGenerate{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}(cr, &svcsdk.{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}{}) +{{ GoCodeSetDeleteInput .CRD "cr" "res" 1 }} + return postGenerate{{ .CRD.Ops.Delete.InputRef.Shape.ShapeName }}(cr, res) +} +{{ end }} +// IsNotFound returns whether the given error is of type NotFound or not. +func IsNotFound(err error) bool { + awsErr, ok := err.(awserr.Error) + return ok && awsErr.Code() == "{{ ResourceExceptionCode .CRD 404 }}" +} \ No newline at end of file diff --git a/templates/crossplane/pkg/resource/sdk_find_get_attributes.go.tpl b/templates/crossplane/pkg/resource/sdk_find_get_attributes.go.tpl new file mode 100644 index 0000000000..b793e29f45 --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk_find_get_attributes.go.tpl @@ -0,0 +1,53 @@ +{{- define "sdk_find_get_attributes" -}} +func (rm *resourceManager) sdkFind( + ctx context.Context, + r *resource, +) (*resource, error) { + // If any required fields in the input shape are missing, AWS resource is + // not created yet. Return NotFound here to indicate to callers that the + // resource isn't yet created. + if rm.requiredFieldsMissingFromGetAttributesInput(r) { + return nil, ackerr.NotFound + } + + input, err := rm.newGetAttributesRequestPayload(r) + if err != nil { + return nil, err + } +{{ $setCode := GoCodeGetAttributesSetOutput .CRD "resp" "ko.Status" 1 }} + {{ if not ( Empty $setCode ) }}resp{{ else }}_{{ end }}, respErr := rm.sdkapi.{{ .CRD.Ops.GetAttributes.Name }}WithContext(ctx, input) + rm.metrics.RecordAPICall("GET_ATTRIBUTES", "{{ .CRD.Ops.GetAttributes.Name }}", respErr) + if respErr != nil { + if awsErr, ok := ackerr.AWSError(respErr); ok && awsErr.Code() == "{{ ResourceExceptionCode .CRD 404 }}" { + return nil, ackerr.NotFound + } + return nil, respErr + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := r.ko.DeepCopy() +{{ $setCode }} + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// requiredFieldsMissingFromGetAtttributesInput returns true if there are any +// fields for the GetAttributes Input shape that are required by not present in +// the resource's Spec or Status +func (rm *resourceManager) requiredFieldsMissingFromGetAttributesInput( + r *resource, +) bool { +{{ GoCodeRequiredFieldsMissingFromGetAttributesInput .CRD "r.ko" 1 }} +} + +// newGetAttributesRequestPayload returns SDK-specific struct for the HTTP +// request payload of the GetAttributes API call for the resource +func (rm *resourceManager) newGetAttributesRequestPayload( + r *resource, +) (*svcsdk.{{ .CRD.Ops.GetAttributes.InputRef.Shape.ShapeName }}, error) { + res := &svcsdk.{{ .CRD.Ops.GetAttributes.InputRef.Shape.ShapeName }}{} +{{ GoCodeGetAttributesSetInput .CRD "r.ko" "res" 1 }} + return res, nil +} +{{- end -}} diff --git a/templates/crossplane/pkg/resource/sdk_find_not_implemented.go.tpl b/templates/crossplane/pkg/resource/sdk_find_not_implemented.go.tpl new file mode 100644 index 0000000000..fb386c9f16 --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk_find_not_implemented.go.tpl @@ -0,0 +1,11 @@ +{{- define "sdk_find_not_implemented" -}} +func (rm *resourceManager) sdkFind( + ctx context.Context, + r *resource, +) (*resource, error) { + // Believe it or not, there are API resources that can be created but there + // is no read operation. Point in case: RDS' CreateDBInstanceReadReplica + // has no corresponding read operation that I know of... + return nil, ackerr.NotImplemented +} +{{- end -}} diff --git a/templates/crossplane/pkg/resource/sdk_find_read_many.go.tpl b/templates/crossplane/pkg/resource/sdk_find_read_many.go.tpl new file mode 100644 index 0000000000..72a2146392 --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk_find_read_many.go.tpl @@ -0,0 +1,16 @@ +{{- define "sdk_find_read_many" -}} +// Generate{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }} returns input for read +// operation. +func Generate{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}(cr *svcapitypes.{{ .CRD.Names.Camel }}) *svcsdk.{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }} { + res := preGenerate{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}(cr, &svcsdk.{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}{}) +{{ GoCodeSetReadManyInput .CRD "cr" "res" 1 }} + return postGenerate{{ .CRD.Ops.ReadMany.InputRef.Shape.ShapeName }}(cr, res) +} + +// Generate{{ .CRD.Names.Camel }} returns the current state in the form of *svcapitypes.{{ .CRD.Names.Camel }}. +func Generate{{ .CRD.Names.Camel }}(resp *svcsdk.{{ .CRD.Ops.ReadMany.OutputRef.Shape.ShapeName }}) *svcapitypes.{{ .CRD.Names.Camel }} { + cr := &svcapitypes.{{ .CRD.Names.Camel }}{} +{{ GoCodeSetReadManyOutput .CRD "resp" "cr" 1 false }} +return cr +} +{{- end -}} diff --git a/templates/crossplane/pkg/resource/sdk_find_read_one.go.tpl b/templates/crossplane/pkg/resource/sdk_find_read_one.go.tpl new file mode 100644 index 0000000000..aff125199e --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk_find_read_one.go.tpl @@ -0,0 +1,16 @@ +{{- define "sdk_find_read_one" -}} +// Generate{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }} returns input for read +// operation. +func Generate{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}(cr *svcapitypes.{{ .CRD.Names.Camel }}) *svcsdk.{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }} { + res := preGenerate{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}(cr, &svcsdk.{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}{}) +{{ GoCodeSetReadOneInput .CRD "cr" "res" 1 }} + return postGenerate{{ .CRD.Ops.ReadOne.InputRef.Shape.ShapeName }}(cr, res) +} + +// Generate{{ .CRD.Names.Camel }} returns the current state in the form of *svcapitypes.{{ .CRD.Names.Camel }}. +func Generate{{ .CRD.Names.Camel }}(resp *svcsdk.{{ .CRD.Ops.ReadOne.OutputRef.Shape.ShapeName }}) *svcapitypes.{{ .CRD.Names.Camel }} { + cr := &svcapitypes.{{ .CRD.Names.Camel }}{} +{{ GoCodeSetReadOneOutput .CRD "resp" "cr" 1 false }} +return cr +} +{{- end -}} diff --git a/templates/crossplane/pkg/resource/sdk_update.go.tpl b/templates/crossplane/pkg/resource/sdk_update.go.tpl new file mode 100644 index 0000000000..aa351a3375 --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk_update.go.tpl @@ -0,0 +1,48 @@ +{{- define "sdk_update" -}} +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + diffReporter *ackcompare.Reporter, +) (*resource, error) { +{{ $customMethod := .CRD.GetCustomImplementation .CRD.Ops.Update }} +{{ if $customMethod }} + customResp, customRespErr := rm.{{ $customMethod }}(ctx, desired, latest, diffReporter) + if customResp != nil || customRespErr != nil { + return customResp, customRespErr + } +{{ end }} + + input, err := rm.newUpdateRequestPayload(desired) + if err != nil { + return nil, err + } + +{{ $setCode := GoCodeSetUpdateOutput .CRD "resp" "ko" 1 false }} + {{ if not ( Empty $setCode ) }}resp{{ else }}_{{ end }}, respErr := rm.sdkapi.{{ .CRD.Ops.Update.Name }}WithContext(ctx, input) + rm.metrics.RecordAPICall("UPDATE", "{{ .CRD.Ops.Update.Name }}", respErr) + if respErr != nil { + return nil, respErr + } + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := desired.ko.DeepCopy() +{{ $setCode }} + rm.setStatusDefaults(ko) +{{ if $setOutputCustomMethodName := .CRD.SetOutputCustomMethodName .CRD.Ops.Update }} + // custom set output from response + rm.{{ $setOutputCustomMethodName }}(desired, resp, ko) +{{ end }} + return &resource{ko}, nil +} + +// newUpdateRequestPayload returns an SDK-specific struct for the HTTP request +// payload of the Update API call for the resource +func (rm *resourceManager) newUpdateRequestPayload( + r *resource, +) (*svcsdk.{{ .CRD.Ops.Update.InputRef.Shape.ShapeName }}, error) { + res := &svcsdk.{{ .CRD.Ops.Update.InputRef.Shape.ShapeName }}{} +{{ GoCodeSetUpdateInput .CRD "r.ko" "res" 1 }} + return res, nil +} +{{- end -}} diff --git a/templates/crossplane/pkg/resource/sdk_update_custom.go.tpl b/templates/crossplane/pkg/resource/sdk_update_custom.go.tpl new file mode 100644 index 0000000000..9ac9fb99eb --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk_update_custom.go.tpl @@ -0,0 +1,10 @@ +{{- define "sdk_update_custom" -}} +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + diffReporter *ackcompare.Reporter, +) (*resource, error) { + return rm.{{ .CRD.CustomUpdateMethodName }}(ctx, desired, latest, diffReporter) +} +{{- end -}} diff --git a/templates/crossplane/pkg/resource/sdk_update_not_implemented.go.tpl b/templates/crossplane/pkg/resource/sdk_update_not_implemented.go.tpl new file mode 100644 index 0000000000..e91acace04 --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk_update_not_implemented.go.tpl @@ -0,0 +1,11 @@ +{{- define "sdk_update_not_implemented" -}} +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + diffReporter *ackcompare.Reporter, +) (*resource, error) { + // TODO(jaypipes): Figure this out... + return nil, ackerr.NotImplemented +} +{{- end -}} diff --git a/templates/crossplane/pkg/resource/sdk_update_set_attributes.go.tpl b/templates/crossplane/pkg/resource/sdk_update_set_attributes.go.tpl new file mode 100644 index 0000000000..c67f0216d0 --- /dev/null +++ b/templates/crossplane/pkg/resource/sdk_update_set_attributes.go.tpl @@ -0,0 +1,60 @@ +{{- define "sdk_update_set_attributes" -}} +func (rm *resourceManager) sdkUpdate( + ctx context.Context, + desired *resource, + latest *resource, + diffReporter *ackcompare.Reporter, +) (*resource, error) { + // If any required fields in the input shape are missing, AWS resource is + // not created yet. And sdkUpdate should never be called if this is the + // case, and it's an error in the generated code if it is... + if rm.requiredFieldsMissingFromSetAttributesInput(desired) { + panic("Required field in SetAttributes input shape missing!") + } + + input, err := rm.newSetAttributesRequestPayload(desired) + if err != nil { + return nil, err + } + + // NOTE(jaypipes): SetAttributes calls return a response but they don't + // contain any useful information. Instead, below, we'll be returning a + // DeepCopy of the supplied desired state, which should be fine because + // that desired state has been constructed from a call to GetAttributes... + _, respErr := rm.sdkapi.{{ .CRD.Ops.SetAttributes.Name }}WithContext(ctx, input) + rm.metrics.RecordAPICall("SET_ATTRIBUTES", "{{ .CRD.Ops.SetAttributes.Name }}", respErr) + if respErr != nil { + if awsErr, ok := ackerr.AWSError(respErr); ok && awsErr.Code() == "{{ ResourceExceptionCode .CRD 404 }}" { + // Technically, this means someone deleted the backend resource in + // between the time we got a result back from sdkFind() and here... + return nil, ackerr.NotFound + } + return nil, respErr + } + + // Merge in the information we read from the API call above to the copy of + // the original Kubernetes object we passed to the function + ko := desired.ko.DeepCopy() + rm.setStatusDefaults(ko) + return &resource{ko}, nil +} + +// requiredFieldsMissingFromSetAtttributesInput returns true if there are any +// fields for the SetAttributes Input shape that are required by not present in +// the resource's Spec or Status +func (rm *resourceManager) requiredFieldsMissingFromSetAttributesInput( + r *resource, +) bool { +{{ GoCodeRequiredFieldsMissingFromSetAttributesInput .CRD "r.ko" 1 }} +} + +// newSetAttributesRequestPayload returns SDK-specific struct for the HTTP +// request payload of the SetAttributes API call for the resource +func (rm *resourceManager) newSetAttributesRequestPayload( + r *resource, +) (*svcsdk.{{ .CRD.Ops.SetAttributes.InputRef.Shape.ShapeName }}, error) { + res := &svcsdk.{{ .CRD.Ops.SetAttributes.InputRef.Shape.ShapeName }}{} +{{ GoCodeSetAttributesSetInput .CRD "r.ko" "res" 1 }} + return res, nil +} +{{- end -}} diff --git a/templates/crossplane/pkg/resource_registry.go.tpl b/templates/crossplane/pkg/resource_registry.go.tpl index 9f32d93c49..57f4e5ec3f 100644 --- a/templates/crossplane/pkg/resource_registry.go.tpl +++ b/templates/crossplane/pkg/resource_registry.go.tpl @@ -1,5 +1,7 @@ {{ template "boilerplate" }} +// Code generated by ack-generate. DO NOT EDIT. + package resource import (