github.com/crossplane/upjet@v1.3.0/pkg/pipeline/crd.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package pipeline
     6  
     7  import (
     8  	"fmt"
     9  	"go/types"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
    15  	twtypes "github.com/muvaf/typewriter/pkg/types"
    16  	"github.com/muvaf/typewriter/pkg/wrapper"
    17  	"github.com/pkg/errors"
    18  
    19  	tjpkg "github.com/crossplane/upjet/pkg"
    20  	"github.com/crossplane/upjet/pkg/config"
    21  	"github.com/crossplane/upjet/pkg/pipeline/templates"
    22  	tjtypes "github.com/crossplane/upjet/pkg/types"
    23  )
    24  
    25  const (
    26  	// GenStatement is printed on every generated file.
    27  	GenStatement = "// Code generated by upjet. DO NOT EDIT."
    28  
    29  	apiRoot = "apis"
    30  )
    31  
    32  // NewCRDGenerator returns a new CRDGenerator.
    33  func NewCRDGenerator(pkg *types.Package, rootDir, providerShortName, group, version string) *CRDGenerator {
    34  	return &CRDGenerator{
    35  		LocalDirectoryPath: filepath.Join(rootDir, apiRoot, strings.ToLower(strings.Split(group, ".")[0]), version),
    36  		LicenseHeaderPath:  filepath.Join(rootDir, "hack", "boilerplate.go.txt"),
    37  		Group:              group,
    38  		ProviderShortName:  providerShortName,
    39  		pkg:                pkg,
    40  	}
    41  }
    42  
    43  // CRDGenerator takes certain information referencing Terraform resource definition
    44  // and writes kubebuilder CRD file.
    45  type CRDGenerator struct {
    46  	LocalDirectoryPath string
    47  	Group              string
    48  	ProviderShortName  string
    49  	LicenseHeaderPath  string
    50  	Generated          *tjtypes.Generated
    51  
    52  	pkg *types.Package
    53  }
    54  
    55  // Generate builds and writes a new CRD out of Terraform resource definition.
    56  func (cg *CRDGenerator) Generate(cfg *config.Resource) (string, error) {
    57  	file := wrapper.NewFile(cg.pkg.Path(), cg.pkg.Name(), templates.CRDTypesTemplate,
    58  		wrapper.WithGenStatement(GenStatement),
    59  		wrapper.WithHeaderPath(cg.LicenseHeaderPath),
    60  	)
    61  
    62  	deleteOmittedFields(cfg.TerraformResource.Schema, cfg.ExternalName.OmittedFields)
    63  	cfg.TerraformResource.Schema["id"] = &schema.Schema{
    64  		Type:     schema.TypeString,
    65  		Computed: true,
    66  	}
    67  
    68  	gen, err := tjtypes.NewBuilder(cg.pkg).Build(cfg)
    69  	if err != nil {
    70  		return "", errors.Wrapf(err, "cannot build types for %s", cfg.Kind)
    71  	}
    72  	cg.Generated = &gen
    73  
    74  	// TODO(muvaf): TypePrinter uses the given scope to see if the type exists
    75  	// before printing. We should ideally load the package in file system but
    76  	// loading the local package will result in error if there is
    77  	// any compilation errors, which is the case before running kubebuilder
    78  	// generators. For now, we act like the target package is empty.
    79  	pkg := types.NewPackage(cg.pkg.Path(), cg.pkg.Name())
    80  	typePrinter := twtypes.NewPrinter(file.Imports, pkg.Scope(), twtypes.WithComments(gen.Comments))
    81  	typesStr, err := typePrinter.Print(gen.Types)
    82  	if err != nil {
    83  		return "", errors.Wrap(err, "cannot print the type list")
    84  	}
    85  	vars := map[string]any{
    86  		"Types": typesStr,
    87  		"CRD": map[string]string{
    88  			"APIVersion":       cfg.Version,
    89  			"Group":            cg.Group,
    90  			"Kind":             cfg.Kind,
    91  			"ForProviderType":  gen.ForProviderType.Obj().Name(),
    92  			"InitProviderType": gen.InitProviderType.Obj().Name(),
    93  			"AtProviderType":   gen.AtProviderType.Obj().Name(),
    94  			"ValidationRules":  gen.ValidationRules,
    95  			"Path":             cfg.Path,
    96  		},
    97  		"Provider": map[string]string{
    98  			"ShortName": cg.ProviderShortName,
    99  		},
   100  		"XPCommonAPIsPackageAlias": file.Imports.UsePackage(tjtypes.PackagePathXPCommonAPIs),
   101  	}
   102  	if cfg.MetaResource != nil {
   103  		// remove sentences with the `terraform` keyword in them
   104  		vars["CRD"].(map[string]string)["Description"] = tjpkg.FilterDescription(cfg.MetaResource.Description, tjpkg.TerraformKeyword)
   105  	}
   106  	filePath := filepath.Join(cg.LocalDirectoryPath, fmt.Sprintf("zz_%s_types.go", strings.ToLower(cfg.Kind)))
   107  	return gen.ForProviderType.Obj().Name(), errors.Wrap(file.Write(filePath, vars, os.ModePerm), "cannot write crd file")
   108  }
   109  
   110  func deleteOmittedFields(sch map[string]*schema.Schema, omittedFields []string) {
   111  	for _, omit := range omittedFields {
   112  		fields := strings.Split(omit, ".")
   113  		current := sch
   114  		for i, f := range fields {
   115  			if i == len(fields)-1 {
   116  				delete(current, f)
   117  				break
   118  			}
   119  			current = current[f].Elem.(*schema.Resource).Schema
   120  		}
   121  	}
   122  }