Skip to content

Commit

Permalink
crossplane: clean up Crossplane API template
Browse files Browse the repository at this point in the history
Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: add apis subcommand with provider-dir flag to crossplane subcommand

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: update with new subtemplates.

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: add a section about Crossplane Provider Generation to dev-docs

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: add new crossplane.Generation struct for generating Crossplane types and controllers

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: update templates for controller

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: add initial controller template

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: use kubebuilder required mark in crds

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: implement IsNotFound func and use it in wherever necessary

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: pre/post hooks are in place for resource-specific custom code

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: use SetManyOutput to get only status fields updated

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: prefix generated files with zz_ to match kubebuilder behavior

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: ready condition should be set by custom code

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: run goimports on generated code

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: add documentation comments to generated API files

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: use generator config file in the target provider directory

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: use crd name as module name for its controller

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: generate boilerplate only once for custom hooks

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: custom default config for SDKHelper used by Crossplane generation pipeline

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: licence boilerplates are corrected

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: added list filtering for readmany results

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

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 <onus.muvaffak@gmail.com>

crossplane: use all-lower string for package name of the generated controllers

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: region field should not have omitempty tag and connect method does not need to call NewSession

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: ReadOne template is implemented

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: Make deletion optional and write isUpToDate to hooks

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>

crossplane: add fallback for the case where service alias does not match the model name in sdk

Signed-off-by: Muvaffak Onus <onus.muvaffak@gmail.com>
  • Loading branch information
muvaf committed Nov 25, 2020
1 parent b8ae8ab commit 4cb6df5
Show file tree
Hide file tree
Showing 45 changed files with 1,121 additions and 906 deletions.
86 changes: 27 additions & 59 deletions cmd/ack-generate/command/crossplane.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <service>",
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()
}
12 changes: 12 additions & 0 deletions docs/contents/dev-docs/code-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <directory for provider>
cd <directory for provider>
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
Expand Down
110 changes: 110 additions & 0 deletions pkg/crossplane/apis.go
Original file line number Diff line number Diff line change
@@ -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")
}
26 changes: 26 additions & 0 deletions pkg/crossplane/config.go
Original file line number Diff line number Diff line change
@@ -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",
}
121 changes: 121 additions & 0 deletions pkg/crossplane/controller.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 4cb6df5

Please sign in to comment.