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 }