Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Signed-off-by: Alper Rifat Ulucinar <ulucinar@users.noreply.github.com>
  • Loading branch information
ulucinar committed Dec 28, 2023
1 parent 4cb45f9 commit 382fffb
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 2 deletions.
84 changes: 84 additions & 0 deletions pkg/config/conversion/conversions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

package conversion

import (
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/pkg/errors"
)

const (
AllVersions = "*"
)

const (
pathObjectMeta = "ObjectMeta"
)

type Conversion interface {
GetSourceVersion() string
GetTargetVersion() string
}

type PavedConversion interface {
Conversion
ConvertPaved(src, target fieldpath.Paved) error
}

type ManagedConversion interface {
Conversion
ConvertTerraformed(src, target resource.Managed)
}

type baseConversion struct {
sourceVersion string
targetVersion string
}

func newBaseConversion(sourceVersion, targetVersion string) baseConversion {
return baseConversion{
sourceVersion: sourceVersion,
targetVersion: targetVersion,
}
}

func (c *baseConversion) GetSourceVersion() string {
return c.sourceVersion
}

func (c *baseConversion) GetTargetVersion() string {
return c.targetVersion
}

type fieldCopy struct {
baseConversion
sourceField string
targetField string
}

func (f *fieldCopy) ConvertPaved(src, target fieldpath.Paved) error {
v, err := src.GetValue(f.sourceField)
if err != nil {
return errors.Wrapf(err, "failed to get the field %q from the conversion source object", f.sourceField)
}
return errors.Wrapf(target.SetValue(f.targetField, v), "failed to set the field %q of the conversion target object", f.targetField)
}

func NewObjectMetaConversion() Conversion {
return &fieldCopy{
baseConversion: newBaseConversion(AllVersions, AllVersions),
sourceField: pathObjectMeta,
targetField: pathObjectMeta,
}
}

func NewFieldRenameConversion(sourceVersion, sourceField, targetVersion, targetField string) Conversion {
return &fieldCopy{
baseConversion: newBaseConversion(sourceVersion, targetVersion),
sourceField: sourceField,
targetField: targetField,
}
}
4 changes: 4 additions & 0 deletions pkg/config/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"fmt"
"time"

"github.com/crossplane/upjet/pkg/config/conversion"

xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
Expand Down Expand Up @@ -446,6 +448,8 @@ type Resource struct {
// index notation (i.e., array/map components do not need indices).
ServerSideApplyMergeStrategies ServerSideApplyMergeStrategies

Conversions []conversion.Conversion

// useNoForkClient indicates that a no-fork external client should
// be generated instead of the Terraform CLI-forking client.
useNoForkClient bool
Expand Down
2 changes: 1 addition & 1 deletion pkg/examples/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func (eg *Generator) Generate(group, version string, r *config.Resource) error {
// e.g. gvk = ec2/v1beta1/instance
gvk := fmt.Sprintf("%s/%s/%s", groupPrefix, version, strings.ToLower(r.Kind))
pm := paveCRManifest(rm.Examples[0].Paved.UnstructuredContent(), r, rm.Examples[0].Name, group, version, gvk)
manifestDir := filepath.Join(eg.rootDir, "examples-generated", groupPrefix)
manifestDir := filepath.Join(eg.rootDir, "examples-generated", groupPrefix, r.Version)
pm.ManifestPath = filepath.Join(manifestDir, fmt.Sprintf("%s.yaml", strings.ToLower(r.Kind)))
eg.resources[fmt.Sprintf("%s.%s", r.Name, reference.Wildcard)] = pm
return nil
Expand Down
76 changes: 76 additions & 0 deletions pkg/pipeline/conversion_convertible.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

package pipeline

import (
"go/types"
"os"
"path/filepath"
"strings"

"github.com/muvaf/typewriter/pkg/wrapper"
"github.com/pkg/errors"

"github.com/crossplane/upjet/pkg/pipeline/templates"
)

// NewConversionConvertibleGenerator returns a new ConversionConvertibleGenerator.
func NewConversionConvertibleGenerator(pkg *types.Package, rootDir, group, version string) *ConversionConvertibleGenerator {
return &ConversionConvertibleGenerator{
LocalDirectoryPath: filepath.Join(rootDir, "apis", strings.ToLower(strings.Split(group, ".")[0])),
LicenseHeaderPath: filepath.Join(rootDir, "hack", "boilerplate.go.txt"),
pkg: pkg,
version: version,
}
}

// ConversionConvertibleGenerator generates conversion methods implementing the
// conversion.Convertible interface on the CRD structs.
type ConversionConvertibleGenerator struct {
LocalDirectoryPath string
LicenseHeaderPath string

pkg *types.Package
version string
}

// Generate writes generated conversion.Convertible interface functions
func (cg *ConversionConvertibleGenerator) Generate(cfgs []*terraformedInput, apiVersion string) error {
entries, err := os.ReadDir(cg.LocalDirectoryPath)
if err != nil {
return errors.Wrapf(err, "cannot list the directory entries for the source folder %s while generating the conversion.Convertible interface functions", cg.LocalDirectoryPath)
}

for _, e := range entries {
if !e.IsDir() || e.Name() == cg.version {
// we skip spoke generation for the current version as the assumption is
// the current CRD version is the hub version.
continue
}
trFile := wrapper.NewFile(cg.pkg.Path(), cg.pkg.Name(), templates.ConversionConvertibleTemplate,
wrapper.WithGenStatement(GenStatement),
wrapper.WithHeaderPath(cg.LicenseHeaderPath),
)
filePath := filepath.Join(cg.LocalDirectoryPath, "zz_generated.conversion.go")
vars := map[string]any{
"APIVersion": apiVersion,
}
resources := make([]map[string]any, len(cfgs))
index := 0
for _, cfg := range cfgs {
resources[index] = map[string]any{
"CRD": map[string]string{
"Kind": cfg.Kind,
},
}
index++
}
vars["Resources"] = resources
if err := trFile.Write(filePath, vars, os.ModePerm); err != nil {
return errors.Wrapf(err, "cannot write the generated conversion Hub functions file %s", filePath)
}
}
return nil
}
62 changes: 62 additions & 0 deletions pkg/pipeline/conversion_hub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

package pipeline

import (
"go/types"
"os"
"path/filepath"
"strings"

"github.com/muvaf/typewriter/pkg/wrapper"
"github.com/pkg/errors"

"github.com/crossplane/upjet/pkg/pipeline/templates"
)

// NewConversionHubGenerator returns a new ConversionHubGenerator.
func NewConversionHubGenerator(pkg *types.Package, rootDir, group, version string) *ConversionHubGenerator {
return &ConversionHubGenerator{
LocalDirectoryPath: filepath.Join(rootDir, "apis", strings.ToLower(strings.Split(group, ".")[0]), version),
LicenseHeaderPath: filepath.Join(rootDir, "hack", "boilerplate.go.txt"),
pkg: pkg,
}
}

// ConversionHubGenerator generates conversion methods implementing the
// conversion.Hub interface on the CRD structs.
type ConversionHubGenerator struct {
LocalDirectoryPath string
LicenseHeaderPath string

pkg *types.Package
}

// Generate writes generated conversion.Hub interface functions
func (cg *ConversionHubGenerator) Generate(cfgs []*terraformedInput, apiVersion string) error {
trFile := wrapper.NewFile(cg.pkg.Path(), cg.pkg.Name(), templates.ConversionHubTemplate,
wrapper.WithGenStatement(GenStatement),
wrapper.WithHeaderPath(cg.LicenseHeaderPath),
)
filePath := filepath.Join(cg.LocalDirectoryPath, "zz_generated.conversion.go")
vars := map[string]any{
"APIVersion": apiVersion,
}
resources := make([]map[string]any, len(cfgs))
index := 0
for _, cfg := range cfgs {
resources[index] = map[string]any{
"CRD": map[string]string{
"Kind": cfg.Kind,
},
}
index++
}
vars["Resources"] = resources
return errors.Wrapf(
trFile.Write(filePath, vars, os.ModePerm),
"cannot write the generated conversion Hub functions file %s", filePath,
)
}
5 changes: 5 additions & 0 deletions pkg/pipeline/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func Run(pc *config.Provider, rootDir string) { //nolint:gocyclo
versionGen := NewVersionGenerator(rootDir, pc.ModulePath, group, version)
crdGen := NewCRDGenerator(versionGen.Package(), rootDir, pc.ShortName, group, version)
tfGen := NewTerraformedGenerator(versionGen.Package(), rootDir, group, version)
conversionHubGen := NewConversionHubGenerator(versionGen.Package(), rootDir, group, version)
ctrlGen := NewControllerGenerator(rootDir, pc.ModulePath, group)

for _, name := range sortedResources(resources) {
Expand Down Expand Up @@ -127,6 +128,10 @@ func Run(pc *config.Provider, rootDir string) { //nolint:gocyclo
panic(errors.Wrapf(err, "cannot generate terraformed for resource %s", group))
}

if err := conversionHubGen.Generate(tfResources, version); err != nil {
panic(errors.Wrapf(err, "cannot generate the conversion Hub for the resource group %q", group))
}

if err := versionGen.Generate(); err != nil {
panic(errors.Wrap(err, "cannot generate version files"))
}
Expand Down
25 changes: 25 additions & 0 deletions pkg/pipeline/templates/conversion_convertible.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

{{ .Header }}

{{ .GenStatement }}

package {{ .APIVersion }}

import (
"sigs.k8s.io/controller-runtime/pkg/conversion"
)

{{ range .Resources }}
// ConvertTo converts this {{ .CRD.Kind }} to the hub type.
func (tr *{{ .CRD.Kind }}) ConvertTo(dstRaw conversion.Hub) error {
return nil
}

// ConvertFrom converts from the hub type to the {{ .CRD.Kind }} type.
func (tr *{{ .CRD.Kind }}) ConvertFrom(srcRaw conversion.Hub) error {
return nil
}
{{ end }}
14 changes: 14 additions & 0 deletions pkg/pipeline/templates/conversion_hub.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

{{ .Header }}

{{ .GenStatement }}

package {{ .APIVersion }}

{{ range .Resources }}
// Hub marks this type as a conversion hub.
func (tr *{{ .CRD.Kind }}) Hub() {}
{{ end }}
3 changes: 2 additions & 1 deletion pkg/pipeline/templates/crd_types.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ type {{ .CRD.Kind }}Status struct {
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:storageversion

// {{ .CRD.Kind }} is the Schema for the {{ .CRD.Kind }}s API. {{ .CRD.Description }}
// +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:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster,categories={crossplane,managed,{{ .Provider.ShortName }}}{{ if .CRD.Path }},path={{ .CRD.Path }}{{ end }}
type {{ .CRD.Kind }} struct {
metav1.TypeMeta `json:",inline"`
Expand Down
12 changes: 12 additions & 0 deletions pkg/pipeline/templates/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,15 @@ var RegisterTemplate string
//
//go:embed setup.go.tmpl
var SetupTemplate string

// ConversionHubTemplate is populated with the CRD API versions
// conversion.Hub implementation template string.
//
//go:embed conversion_hub.go.tmpl
var ConversionHubTemplate string

// ConversionConvertibleTemplate is populated with the CRD API versions
// conversion.Convertible implementation template string.
//
//go:embed conversion_convertible.go.tmpl
var ConversionConvertibleTemplate string

0 comments on commit 382fffb

Please sign in to comment.