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  }