github.com/crossplane/upjet@v1.3.0/pkg/pipeline/run.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 "os/exec" 10 "path/filepath" 11 "sort" 12 "strings" 13 14 "github.com/crossplane/crossplane-runtime/pkg/errors" 15 16 "github.com/crossplane/upjet/pkg/config" 17 "github.com/crossplane/upjet/pkg/examples" 18 ) 19 20 type terraformedInput struct { 21 *config.Resource 22 ParametersTypeName string 23 } 24 25 // Run runs the Upjet code generation pipelines. 26 func Run(pc *config.Provider, rootDir string) { //nolint:gocyclo 27 // Note(turkenh): nolint reasoning - this is the main function of the code 28 // generation pipeline. We didn't want to split it into multiple functions 29 // for better readability considering the straightforward logic here. 30 31 // Group resources based on their Group and API Versions. 32 // An example entry in the tree would be: 33 // ec2.aws.upbound.io -> v1beta1 -> aws_vpc 34 resourcesGroups := map[string]map[string]map[string]*config.Resource{} 35 for name, resource := range pc.Resources { 36 group := pc.RootGroup 37 if resource.ShortGroup != "" { 38 group = strings.ToLower(resource.ShortGroup) + "." + pc.RootGroup 39 } 40 if len(resourcesGroups[group]) == 0 { 41 resourcesGroups[group] = map[string]map[string]*config.Resource{} 42 } 43 if len(resourcesGroups[group][resource.Version]) == 0 { 44 resourcesGroups[group][resource.Version] = map[string]*config.Resource{} 45 } 46 resourcesGroups[group][resource.Version][name] = resource 47 } 48 49 exampleGen := examples.NewGenerator(rootDir, pc.ModulePath, pc.ShortName, pc.Resources) 50 if err := exampleGen.SetReferenceTypes(pc.Resources); err != nil { 51 panic(errors.Wrap(err, "cannot set reference types for resources")) 52 } 53 // Add ProviderConfig API package to the list of API version packages. 54 apiVersionPkgList := make([]string, 0) 55 for _, p := range pc.BasePackages.APIVersion { 56 apiVersionPkgList = append(apiVersionPkgList, filepath.Join(pc.ModulePath, p)) 57 } 58 // Add ProviderConfig controller package to the list of controller packages. 59 controllerPkgMap := make(map[string][]string) 60 // new API takes precedence 61 for p, g := range pc.BasePackages.ControllerMap { 62 path := filepath.Join(pc.ModulePath, p) 63 controllerPkgMap[g] = append(controllerPkgMap[g], path) 64 controllerPkgMap[config.PackageNameMonolith] = append(controllerPkgMap[config.PackageNameMonolith], path) 65 } 66 //nolint:staticcheck 67 for _, p := range pc.BasePackages.Controller { 68 path := filepath.Join(pc.ModulePath, p) 69 found := false 70 for _, p := range controllerPkgMap[config.PackageNameConfig] { 71 if path == p { 72 found = true 73 break 74 } 75 } 76 if !found { 77 controllerPkgMap[config.PackageNameConfig] = append(controllerPkgMap[config.PackageNameConfig], path) 78 } 79 found = false 80 for _, p := range controllerPkgMap[config.PackageNameMonolith] { 81 if path == p { 82 found = true 83 break 84 } 85 } 86 if !found { 87 controllerPkgMap[config.PackageNameMonolith] = append(controllerPkgMap[config.PackageNameMonolith], path) 88 } 89 } 90 count := 0 91 for group, versions := range resourcesGroups { 92 for version, resources := range versions { 93 var tfResources []*terraformedInput 94 versionGen := NewVersionGenerator(rootDir, pc.ModulePath, group, version) 95 crdGen := NewCRDGenerator(versionGen.Package(), rootDir, pc.ShortName, group, version) 96 tfGen := NewTerraformedGenerator(versionGen.Package(), rootDir, group, version) 97 conversionHubGen := NewConversionHubGenerator(versionGen.Package(), rootDir, group, version) 98 conversionSpokeGen := NewConversionSpokeGenerator(versionGen.Package(), rootDir, group, version) 99 ctrlGen := NewControllerGenerator(rootDir, pc.ModulePath, group) 100 101 for _, name := range sortedResources(resources) { 102 paramTypeName, err := crdGen.Generate(resources[name]) 103 if err != nil { 104 panic(errors.Wrapf(err, "cannot generate crd for resource %s", name)) 105 } 106 tfResources = append(tfResources, &terraformedInput{ 107 Resource: resources[name], 108 ParametersTypeName: paramTypeName, 109 }) 110 111 featuresPkgPath := "" 112 if pc.FeaturesPackage != "" { 113 featuresPkgPath = filepath.Join(pc.ModulePath, pc.FeaturesPackage) 114 } 115 ctrlPkgPath, err := ctrlGen.Generate(resources[name], versionGen.Package().Path(), featuresPkgPath) 116 if err != nil { 117 panic(errors.Wrapf(err, "cannot generate controller for resource %s", name)) 118 } 119 sGroup := strings.Split(group, ".")[0] 120 controllerPkgMap[sGroup] = append(controllerPkgMap[sGroup], ctrlPkgPath) 121 controllerPkgMap[config.PackageNameMonolith] = append(controllerPkgMap[config.PackageNameMonolith], ctrlPkgPath) 122 if err := exampleGen.Generate(group, version, resources[name]); err != nil { 123 panic(errors.Wrapf(err, "cannot generate example manifest for resource %s", name)) 124 } 125 count++ 126 } 127 128 if err := tfGen.Generate(tfResources, version); err != nil { 129 panic(errors.Wrapf(err, "cannot generate terraformed for resource %s", group)) 130 } 131 132 if err := conversionHubGen.Generate(tfResources, version); err != nil { 133 panic(errors.Wrapf(err, "cannot generate the conversion.Hub function for the resource group %q", group)) 134 } 135 136 if err := conversionSpokeGen.Generate(tfResources); err != nil { 137 panic(errors.Wrapf(err, "cannot generate the conversion.Convertible functions for the resource group %q", group)) 138 } 139 140 if err := versionGen.Generate(); err != nil { 141 panic(errors.Wrap(err, "cannot generate version files")) 142 } 143 p := versionGen.Package().Path() 144 apiVersionPkgList = append(apiVersionPkgList, p) 145 for _, r := range resources { 146 // if there are spoke versions for the given group.Kind 147 if spokeVersions := conversionSpokeGen.SpokeVersionsMap[fmt.Sprintf("%s.%s", r.ShortGroup, r.Kind)]; spokeVersions != nil { 148 base := filepath.Dir(p) 149 for _, sv := range spokeVersions { 150 apiVersionPkgList = append(apiVersionPkgList, filepath.Join(base, sv)) 151 } 152 } 153 } 154 } 155 } 156 157 if err := exampleGen.StoreExamples(); err != nil { 158 panic(errors.Wrapf(err, "cannot store examples")) 159 } 160 161 if err := NewRegisterGenerator(rootDir, pc.ModulePath).Generate(apiVersionPkgList); err != nil { 162 panic(errors.Wrap(err, "cannot generate register file")) 163 } 164 // Generate the provider, 165 // i.e. the setup function and optionally the provider's main program. 166 if err := NewProviderGenerator(rootDir, pc.ModulePath).Generate(controllerPkgMap, pc.MainTemplate); err != nil { 167 panic(errors.Wrap(err, "cannot generate setup file")) 168 } 169 170 // NOTE(muvaf): gosec linter requires that the whole command is hard-coded. 171 // So, we set the directory of the command instead of passing in the directory 172 // as an argument to "find". 173 apisCmd := exec.Command("bash", "-c", "goimports -w $(find . -iname 'zz_*')") 174 apisCmd.Dir = filepath.Clean(filepath.Join(rootDir, "apis")) 175 if out, err := apisCmd.CombinedOutput(); err != nil { 176 panic(errors.Wrap(err, "cannot run goimports for apis folder: "+string(out))) 177 } 178 179 internalCmd := exec.Command("bash", "-c", "goimports -w $(find . -iname 'zz_*')") 180 internalCmd.Dir = filepath.Clean(filepath.Join(rootDir, "internal")) 181 if out, err := internalCmd.CombinedOutput(); err != nil { 182 panic(errors.Wrap(err, "cannot run goimports for internal folder: "+string(out))) 183 } 184 185 fmt.Printf("\nGenerated %d resources!\n", count) 186 } 187 188 func sortedResources(m map[string]*config.Resource) []string { 189 result := make([]string, len(m)) 190 i := 0 191 for g := range m { 192 result[i] = g 193 i++ 194 } 195 sort.Strings(result) 196 return result 197 }