github.com/crossplane/upjet@v1.3.0/pkg/transformers/resolver.go (about)

     1  // SPDX-FileCopyrightText: 2024 The Crossplane Authors <https://crossplane.io>
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package transformers
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/format"
    11  	"go/token"
    12  	"path/filepath"
    13  	"slices"
    14  	"strings"
    15  
    16  	"github.com/crossplane/crossplane-runtime/pkg/logging"
    17  	"github.com/pkg/errors"
    18  	"github.com/spf13/afero"
    19  	"golang.org/x/tools/go/ast/astutil"
    20  	"golang.org/x/tools/go/packages"
    21  )
    22  
    23  const (
    24  	varManagedResource     = "m"
    25  	varManagedResourceList = "l"
    26  	commentFileTransformed = "// Code transformed by upjet. DO NOT EDIT."
    27  
    28  	defaultLoadMode = packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax
    29  )
    30  
    31  // Resolver transforms the source resolver implementations so that
    32  // the resolution source managed resources are no longer statically typed
    33  // and thus, the implementations no longer need to import the corresponding
    34  // API packages. This transformer is helpful in preventing the import cycles
    35  // described in https://github.com/crossplane/upjet/issues/96
    36  // and elsewhere. Please see TransformPackages for the details of the
    37  // transformation applied.
    38  type Resolver struct {
    39  	// the FS implementation used for storing the transformed output
    40  	fs afero.Fs
    41  	// the API group suffix to be used for the resolution source
    42  	// managed resources, such as aws.upbound.io. Then a sample
    43  	// API group for a resource is ec2.aws.upbound.io.
    44  	apiGroupSuffix string
    45  	// API group overrides for the provider. Certain providers need
    46  	// to rename the short API group names they use, breaking the
    47  	// convention that the short group name matches the package name.
    48  	// An example is upbound/provider-azure, where the ResourceGroup.azure
    49  	// resource's short API group is the empty string. This map allows such
    50  	// providers to control the names of the generated API group names by this
    51  	// Resolver transformer.
    52  	apiGroupOverrides map[string]string
    53  	// the API resolver package that contains the
    54  	// `GetManagedResource("group", "version", "kind", "listKind")`
    55  	// function. This function is used to initialize a managed resource and
    56  	// its list type, owned by the provider, with the given API group, version,
    57  	// kind and list kind. Signature of the resolver function is as follows:
    58  	// func GetManagedResource(group, version, kind, listKind string) (xpresource.Managed, xpresource.ManagedList, error)
    59  	apiResolverPackage string
    60  	// When set, any errors encountered while loading the source packages is
    61  	// silently ignored if a logger is not configured,
    62  	// or logged via the configured logger.
    63  	// We need to set this when, for example, loading resolver implementations
    64  	// with import cycles, or when transforming just one package and not loading
    65  	// the referenced typed.
    66  	ignorePackageLoadErrors bool
    67  	logger                  logging.Logger
    68  	config                  *packages.Config
    69  }
    70  
    71  // NewResolver initializes a new Resolver with the specified configuration.
    72  func NewResolver(fs afero.Fs, apiGroupSuffix, apiResolverPackage string, ignorePackageLoadErrors bool, logger logging.Logger, opts ...ResolverOption) *Resolver {
    73  	if logger == nil {
    74  		logger = logging.NewNopLogger()
    75  	}
    76  	r := &Resolver{
    77  		fs:                      fs,
    78  		apiGroupSuffix:          apiGroupSuffix,
    79  		apiResolverPackage:      apiResolverPackage,
    80  		ignorePackageLoadErrors: ignorePackageLoadErrors,
    81  		logger:                  logger,
    82  		config: &packages.Config{
    83  			Mode: defaultLoadMode,
    84  		},
    85  	}
    86  	for _, o := range opts {
    87  		o(r)
    88  	}
    89  	return r
    90  }
    91  
    92  // ResolverOption is an option used to configure the Resolver.
    93  type ResolverOption func(resolver *Resolver)
    94  
    95  // WithLoaderConfig configures the package loader config for a Resolver.
    96  func WithLoaderConfig(c *packages.Config) ResolverOption {
    97  	return func(r *Resolver) {
    98  		r.config = c
    99  	}
   100  }
   101  
   102  // WithAPIGroupOverrides configures the API group overrides for a Resolver.
   103  // Certain providers need to rename the short API group names they use,
   104  // breaking the convention that the short group name matches the package name.
   105  // An example is upbound/provider-azure, where the ResourceGroup.azure
   106  // resource's short API group is the empty string.
   107  func WithAPIGroupOverrides(overrides map[string]string) ResolverOption {
   108  	return func(r *Resolver) {
   109  		r.apiGroupOverrides = overrides
   110  	}
   111  }
   112  
   113  // TransformPackages applies the dynamic resolver transformation to
   114  // the resolver modules loaded from the specified patterns and
   115  // implemented in the specified resolver files. If `r.ignorePackageLoadErrors`
   116  // is set, any errors encountered while loading the source packages are
   117  // ignored. This may be required when the transformation source files have
   118  // compile errors, such as import cycles. The transformed resolver
   119  // implementations will use the specified API group suffix, such as,
   120  // "aws.upbound.io" when determining the API groups of the resolution
   121  // source managed resources.
   122  // A sample transformation implemented by this transformer is from:
   123  // ```
   124  //
   125  //	func (mg *Subnet) ResolveReferences(ctx context.Context, c client.Reader) error {
   126  //	  r := reference.NewAPIResolver(c, mg)
   127  //
   128  //	  var rsp reference.ResolutionResponse
   129  //	  var err error
   130  //
   131  //	  rsp, err = r.Resolve(ctx, reference.ResolutionRequest{
   132  //	    CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.VPCID),
   133  //	    Extract:      reference.ExternalName(),
   134  //	    Reference:    mg.Spec.ForProvider.VPCIDRef,
   135  //	    Selector:     mg.Spec.ForProvider.VPCIDSelector,
   136  //	    To: reference.To{
   137  //	      List:    &VPCList{},
   138  //	      Managed: &VPC{},
   139  //	    },
   140  //	  })
   141  //	  if err != nil {
   142  //	    return errors.Wrap(err, "mg.Spec.ForProvider.VPCID")
   143  //	  }
   144  //	  mg.Spec.ForProvider.VPCID = reference.ToPtrValue(rsp.ResolvedValue)
   145  //	  mg.Spec.ForProvider.VPCIDRef = rsp.ResolvedReference
   146  //
   147  // ```
   148  // to the following:
   149  // ```
   150  //
   151  //	func (mg *Subnet) ResolveReferences(ctx context.Context, c client.Reader) error {
   152  //	  var m xpresource.Managed
   153  //	  var l xpresource.ManagedList
   154  //	  r := reference.NewAPIResolver(c, mg)
   155  //
   156  //	  var rsp reference.ResolutionResponse
   157  //	  var err error
   158  //	  {
   159  //	    m, l, err = apisresolver.GetManagedResource("ec2.aws.upbound.io", "v1beta1", "VPC", "VPCList")
   160  //	    if err != nil {
   161  //	      return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution")
   162  //	    }
   163  //
   164  //	    rsp, err = r.Resolve(ctx, reference.ResolutionRequest{
   165  //	      CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.VPCID),
   166  //	      Extract:      reference.ExternalName(),
   167  //	      Reference:    mg.Spec.ForProvider.VPCIDRef,
   168  //	      Selector:     mg.Spec.ForProvider.VPCIDSelector,
   169  //	      To:           reference.To{List: l, Managed: m},
   170  //	    })
   171  //	  }
   172  //	  if err != nil {
   173  //	    return errors.Wrap(err, "mg.Spec.ForProvider.VPCID")
   174  //	  }
   175  //	  mg.Spec.ForProvider.VPCID = reference.ToPtrValue(rsp.ResolvedValue)
   176  //	  mg.Spec.ForProvider.VPCIDRef = rsp.ResolvedReference
   177  //
   178  // ```
   179  func (r *Resolver) TransformPackages(resolverFilePattern string, patterns ...string) error {
   180  	pkgs, err := packages.Load(r.config, patterns...)
   181  	if err != nil {
   182  		return errors.Wrapf(err, "failed to load the packages using the patterns %q", strings.Join(patterns, ","))
   183  	}
   184  
   185  	for _, p := range pkgs {
   186  		if err := toError(p); err != nil {
   187  			if !r.ignorePackageLoadErrors {
   188  				return errors.Wrapf(err, "failed to load the package %q", p.Name)
   189  			}
   190  			r.logger.Info("Encounter the following issues when loading a package", "package", p.Name, "issues", err.Error())
   191  		}
   192  		for i, f := range p.GoFiles {
   193  			if filepath.Base(f) != resolverFilePattern {
   194  				continue
   195  			}
   196  			if err := r.transformResolverFile(p.Fset, p.Syntax[i], f, strings.Trim(r.apiGroupSuffix, ".")); err != nil {
   197  				return errors.Wrapf(err, "failed to transform the resolver file %s", f)
   198  			}
   199  		}
   200  	}
   201  	return nil
   202  }
   203  
   204  func toError(p *packages.Package) error {
   205  	if p == nil || len(p.Errors) == 0 {
   206  		return nil
   207  	}
   208  	sb := &strings.Builder{}
   209  	for _, e := range p.Errors {
   210  		if _, err := fmt.Fprintln(sb, e); err != nil {
   211  			return errors.Wrap(err, "failed to write the package parse error to the string builder")
   212  		}
   213  	}
   214  	return errors.New(sb.String())
   215  }
   216  
   217  type importUsage struct {
   218  	path string
   219  	used bool
   220  }
   221  
   222  func addTransformedComment(fset *token.FileSet, node *ast.File) bool {
   223  	cMap := ast.NewCommentMap(fset, node, node.Comments)
   224  	cgl := cMap[node]
   225  	for _, cg := range cgl {
   226  		for _, c := range cg.List {
   227  			if c != nil && c.Text == commentFileTransformed {
   228  				return false
   229  			}
   230  		}
   231  	}
   232  	switch {
   233  	case len(cgl) == 0:
   234  		cgl = []*ast.CommentGroup{
   235  			{
   236  				List: []*ast.Comment{
   237  					{
   238  						Text:  commentFileTransformed,
   239  						Slash: node.FileStart,
   240  					},
   241  				},
   242  			},
   243  		}
   244  
   245  	default:
   246  		cgl[0].List = append(cgl[0].List, &ast.Comment{
   247  			Text:  commentFileTransformed,
   248  			Slash: cgl[0].End(),
   249  		})
   250  	}
   251  	cMap[node] = cgl
   252  	return true
   253  }
   254  
   255  func (r *Resolver) transformResolverFile(fset *token.FileSet, node *ast.File, filePath, apiGroupSuffix string) error { //nolint:gocyclo // Arguably, easier to follow
   256  	if !addTransformedComment(fset, node) {
   257  		return nil
   258  	}
   259  	// add resolution source variable declarations to the `ResolveReferences`
   260  	// function bodies.
   261  	importMap := addMRVariableDeclarations(node)
   262  	// Map to track imports used in reference.To structs
   263  	importsUsed := make(map[string]importUsage)
   264  	// assign is the assignment statement that assigns the values returned from
   265  	// `APIResolver.Resolve` or `APIResolver.ResolveMultiple` to the local
   266  	// variables in the MR kind's `ResolveReferences` function.
   267  	var assign *ast.AssignStmt
   268  	// block is the MR kind's `ResolveReferences` function's body block.
   269  	// We use this to find the correct place to inject MR variable
   270  	// declarations, calls to the type registry and error checks, etc.
   271  	var block *ast.BlockStmt
   272  	// these are the GVKs for the MR kind and the associated list kind
   273  	var group, version, kind, listKind string
   274  
   275  	// traverse the AST loaded from the given source file to remove the
   276  	// cross API-group import statements from it. This helps in avoiding
   277  	// the import cycles related to the cross-resource references.
   278  	var inspectErr error
   279  	ast.Inspect(node, func(n ast.Node) bool {
   280  		switch x := n.(type) {
   281  		// this transformer takes care of removing the unneeded import statements
   282  		// (after the transformation), which are the target cross API-group
   283  		// references we are trying to avoid to prevent import cycles and appear
   284  		// in cross-resource reference targets.
   285  		case *ast.ImportSpec:
   286  			// initially, mark all imports as needed
   287  			key := ""
   288  			if x.Name != nil {
   289  				key = x.Name.Name
   290  			} else {
   291  				key = x.Path.Value
   292  			}
   293  			importsUsed[key] = importUsage{
   294  				path: strings.Trim(x.Path.Value, `"`),
   295  				used: true,
   296  			}
   297  
   298  			// keep a hold of the `ResolveReferences` function body so that we can
   299  		// properly inject variable declarations, error checks, etc. into the
   300  		// correct positions.
   301  		case *ast.FuncDecl:
   302  			block = x.Body
   303  
   304  			// keep a hold of the `APIResolver.Resolve` and
   305  		// `APIResolver.ResolveMultiple` return value assignments as we will
   306  		// inject code right above it.
   307  		case *ast.AssignStmt:
   308  			assign = x
   309  
   310  		// we will attempt to transform expressions such as
   311  		// `reference.To{List: &v1beta1.MRList{}, Managed: &v1beta1.MR{}}`
   312  		// into:
   313  		// `reference.To{List: l, Managed: m}`, where
   314  		// l and m are local variables holding the correctly types MR kind
   315  		// and MR list kind objects as the reference targets.
   316  		// Such expressions are the primary sources of cross API-group
   317  		// import statements.
   318  		// Cross API-group extractors are rare, and they should be
   319  		// handled when they're being added, this transformer does not
   320  		// consider them.
   321  		case *ast.KeyValueExpr:
   322  			// check if the key is "To" and the value is a CompositeLit
   323  			if key, ok := x.Key.(*ast.Ident); ok && key.Name == "To" {
   324  				// prevent a previous GVK from being reused
   325  				group, version, kind, listKind = "", "", "", ""
   326  				if cl, ok := x.Value.(*ast.CompositeLit); ok {
   327  					// check if there are any package qualifiers in the CompositeLit
   328  					for _, elt := range cl.Elts {
   329  						if kv, ok := elt.(*ast.KeyValueExpr); ok {
   330  							if uexpr, ok := kv.Value.(*ast.UnaryExpr); ok {
   331  								if cl, ok := uexpr.X.(*ast.CompositeLit); ok {
   332  									// then the reference target resides in another API group
   333  									// and the composite literal is a selector expression such as
   334  									// v1beta1.MR. In this case, we deduce the GV of the MR and
   335  									// list using the selector expression and the corresponding
   336  									// import statements (with the name as the expression).
   337  									// Kind and list kind are determined from the field selector.
   338  									key := kv.Key.(*ast.Ident).Name
   339  									if sexpr, ok := cl.Type.(*ast.SelectorExpr); ok {
   340  										if ident, ok := sexpr.X.(*ast.Ident); ok {
   341  											path := importsUsed[ident.Name].path
   342  											importsUsed[ident.Name] = importUsage{
   343  												path: path,
   344  												used: false,
   345  											}
   346  											// we will parse the import path such as:
   347  											// github.com/upbound/provider-aws/apis/ec2/v1beta1
   348  											// and extract the GV information from it.
   349  											tokens := strings.Split(path, "/")
   350  											// e.g., v1beta1
   351  											version = tokens[len(tokens)-1]
   352  											// e.g., ec2.aws.upbound.io
   353  											group = fmt.Sprintf("%s.%s", tokens[len(tokens)-2], apiGroupSuffix)
   354  											// apply any configured group name overrides
   355  											group = r.overrideGroupName(group)
   356  											// extract the kind and list kind names from the field
   357  											// selector.
   358  											if sexpr.Sel != nil {
   359  												if key == "List" {
   360  													listKind = sexpr.Sel.Name
   361  												} else {
   362  													kind = sexpr.Sel.Name
   363  												}
   364  											}
   365  										}
   366  									} else {
   367  										// then the reference target is in the same package as the
   368  										// source. We still transform it for uniformity and
   369  										// in the future, the source and target might still be
   370  										// moved to different packages.
   371  										// The GV information comes from file name in this case:
   372  										// apis/cur/v1beta1/zz_generated.resolvers.go
   373  										tokens := strings.Split(filePath, "/")
   374  										// e.g., v1beta1
   375  										version = tokens[len(tokens)-2]
   376  										// e.g., cur.aws.upbound.io
   377  										group = fmt.Sprintf("%s.%s", tokens[len(tokens)-3], apiGroupSuffix)
   378  										// apply any configured group name overrides
   379  										group = r.overrideGroupName(group)
   380  										if ident, ok := cl.Type.(*ast.Ident); ok {
   381  											if key == "List" {
   382  												listKind = ident.Name
   383  											} else {
   384  												kind = ident.Name
   385  											}
   386  										}
   387  									}
   388  								}
   389  							}
   390  						}
   391  					}
   392  
   393  					// we will error if we could not determine the reference target GVKs
   394  					// for the MR and its list kind.
   395  					if group == "" || version == "" || kind == "" || listKind == "" {
   396  						inspectErr = errors.Errorf("failed to extract the GVKs for the reference targets. Group: %q, Version: %q, Kind: %q, List Kind: %q", group, version, kind, listKind)
   397  						return false
   398  					}
   399  
   400  					// replace the value with a CompositeLit of type reference.To
   401  					// It's transformed into:
   402  					// reference.To{List: l, Managed: m}
   403  					x.Value = &ast.CompositeLit{
   404  						Type: &ast.SelectorExpr{
   405  							X:   ast.NewIdent("reference"),
   406  							Sel: ast.NewIdent("To"),
   407  						},
   408  						// here, l & m
   409  						Elts: []ast.Expr{
   410  							&ast.KeyValueExpr{
   411  								Key:   ast.NewIdent("List"),
   412  								Value: ast.NewIdent(varManagedResourceList),
   413  							},
   414  							&ast.KeyValueExpr{
   415  								Key:   ast.NewIdent("Managed"),
   416  								Value: ast.NewIdent(varManagedResource),
   417  							},
   418  						},
   419  					}
   420  
   421  					// get the statements including the import statements we need to make
   422  					// calls to the type registry.
   423  					mrImports, stmts := getManagedResourceStatements(group, version, kind, listKind, r.apiResolverPackage)
   424  					// insert the statements that implement type registry lookups
   425  					if !insertStatements(stmts, block, assign) {
   426  						inspectErr = errors.Errorf("failed to insert the type registry lookup statements for Group: %q, Version: %q, Kind: %q, List Kind: %q", group, version, kind, listKind)
   427  						return false
   428  					}
   429  					// add the new import statements we need to implement the
   430  					// type registry lookups.
   431  					for k, v := range mrImports {
   432  						importMap[k] = v
   433  					}
   434  				}
   435  			}
   436  		}
   437  		return true
   438  	})
   439  
   440  	if inspectErr != nil {
   441  		return errors.Wrap(inspectErr, "failed to inspect the resolver file for transformation")
   442  	}
   443  
   444  	// remove the imports that are no longer used.
   445  	for _, decl := range node.Decls {
   446  		if gd, ok := decl.(*ast.GenDecl); ok && gd.Tok == token.IMPORT {
   447  			var newSpecs []ast.Spec
   448  			for _, spec := range gd.Specs {
   449  				if imp, ok := spec.(*ast.ImportSpec); ok {
   450  					var name string
   451  					if imp.Name != nil {
   452  						name = imp.Name.Name
   453  					} else {
   454  						name = imp.Path.Value
   455  					}
   456  					if usage, exists := importsUsed[name]; !exists || usage.used {
   457  						newSpecs = append(newSpecs, spec)
   458  					}
   459  				}
   460  			}
   461  			gd.Specs = newSpecs
   462  
   463  			newImportKeys := make([]string, 0, len(importMap))
   464  			for k := range importMap {
   465  				newImportKeys = append(newImportKeys, k)
   466  			}
   467  			slices.Sort(newImportKeys)
   468  
   469  			for _, path := range newImportKeys {
   470  				gd.Specs = append(gd.Specs, &ast.ImportSpec{
   471  					Name: &ast.Ident{
   472  						Name: importMap[path],
   473  					},
   474  					Path: &ast.BasicLit{
   475  						Kind:  token.STRING,
   476  						Value: path,
   477  					},
   478  				})
   479  			}
   480  		}
   481  	}
   482  	return r.dumpTransformed(fset, node, filePath)
   483  }
   484  
   485  func (r *Resolver) dumpTransformed(fset *token.FileSet, node *ast.File, filePath string) error {
   486  	// dump the transformed resolver file
   487  	adjustFunctionDocs(node)
   488  	outFile, err := r.fs.Create(filepath.Clean(filePath))
   489  	if err != nil {
   490  		return errors.Wrap(err, "failed to open the resolver file for writing the transformed AST")
   491  	}
   492  	defer func() { _ = outFile.Close() }()
   493  
   494  	// write the modified AST back to the resolver file
   495  	return errors.Wrap(format.Node(outFile, fset, node), "failed to dump the transformed AST back into the resolver file")
   496  }
   497  
   498  func adjustFunctionDocs(node *ast.File) {
   499  	node.Decls[1].(*ast.FuncDecl).Doc.List[0].Slash = node.Decls[1].(*ast.FuncDecl).Name.Pos()
   500  }
   501  
   502  func insertStatements(stmts []ast.Stmt, block *ast.BlockStmt, assign *ast.AssignStmt) bool {
   503  	astutil.Apply(block, nil, func(c *astutil.Cursor) bool {
   504  		n := c.Node()
   505  		if n != assign {
   506  			return true
   507  		}
   508  		c.Replace(&ast.BlockStmt{
   509  			List: append(stmts, assign),
   510  		})
   511  		return false
   512  	})
   513  	return true
   514  }
   515  
   516  func addMRVariableDeclarations(f *ast.File) map[string]string {
   517  	// prepare the first variable declaration:
   518  	// `var m xpresource.Managed`
   519  	varDecl1 := &ast.GenDecl{
   520  		Tok: token.VAR,
   521  		Specs: []ast.Spec{
   522  			&ast.ValueSpec{
   523  				Names: []*ast.Ident{ast.NewIdent("m")},
   524  				Type: &ast.SelectorExpr{
   525  					X:   ast.NewIdent("xpresource"),
   526  					Sel: ast.NewIdent("Managed"),
   527  				},
   528  			},
   529  		},
   530  	}
   531  
   532  	// prepare the second variable declaration:
   533  	// `var l xpresource.ManagedList`
   534  	varDecl2 := &ast.GenDecl{
   535  		Tok: token.VAR,
   536  		Specs: []ast.Spec{
   537  			&ast.ValueSpec{
   538  				Names: []*ast.Ident{ast.NewIdent("l")},
   539  				Type: &ast.SelectorExpr{
   540  					X:   ast.NewIdent("xpresource"),
   541  					Sel: ast.NewIdent("ManagedList"),
   542  				},
   543  			},
   544  		},
   545  	}
   546  
   547  	ast.Inspect(f, func(n ast.Node) bool {
   548  		fn, ok := n.(*ast.FuncDecl)
   549  		if !ok {
   550  			return true
   551  		}
   552  
   553  		if fn.Name.Name == "ResolveReferences" && len(fn.Recv.List) > 0 {
   554  			fn.Body.List = append([]ast.Stmt{
   555  				&ast.DeclStmt{Decl: varDecl1},
   556  				&ast.DeclStmt{Decl: varDecl2},
   557  			}, fn.Body.List...)
   558  		}
   559  		return true
   560  	})
   561  	return map[string]string{
   562  		`"github.com/crossplane/crossplane-runtime/pkg/resource"`: "xpresource",
   563  	}
   564  }
   565  
   566  func getManagedResourceStatements(group, version, kind, listKind, apiResolverPackage string) (map[string]string, []ast.Stmt) {
   567  	// prepare the assignment statement:
   568  	// `m, l, err = apisresolver.GetManagedResource("group", "version", "kind", "listKind")`
   569  	assignStmt := &ast.AssignStmt{
   570  		Lhs: []ast.Expr{
   571  			ast.NewIdent("m"),
   572  			ast.NewIdent("l"),
   573  			ast.NewIdent("err"),
   574  		},
   575  		Tok: token.ASSIGN,
   576  		Rhs: []ast.Expr{
   577  			&ast.CallExpr{
   578  				Fun: &ast.SelectorExpr{
   579  					X:   ast.NewIdent("apisresolver"),
   580  					Sel: ast.NewIdent("GetManagedResource"),
   581  				},
   582  				Args: []ast.Expr{
   583  					&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(`"%s"`, group)},
   584  					&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(`"%s"`, version)},
   585  					&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(`"%s"`, kind)},
   586  					&ast.BasicLit{Kind: token.STRING, Value: fmt.Sprintf(`"%s"`, listKind)},
   587  				},
   588  			},
   589  		},
   590  	}
   591  
   592  	// prepare the if statement:
   593  	// ```
   594  	// if err != nil {
   595  	//   return errors.Wrap(err, "failed to get the reference target managed resource and its list for reference resolution")
   596  	// }
   597  	// ```
   598  	ifStmt := &ast.IfStmt{
   599  		Cond: &ast.BinaryExpr{
   600  			X:  ast.NewIdent("err"),
   601  			Op: token.NEQ,
   602  			Y:  &ast.Ident{Name: "nil"},
   603  		},
   604  		Body: &ast.BlockStmt{
   605  			List: []ast.Stmt{
   606  				&ast.ReturnStmt{
   607  					Results: []ast.Expr{
   608  						&ast.CallExpr{
   609  							Fun: &ast.SelectorExpr{
   610  								X:   ast.NewIdent("errors"),
   611  								Sel: ast.NewIdent("Wrap"),
   612  							},
   613  							Args: []ast.Expr{
   614  								ast.NewIdent("err"),
   615  								&ast.BasicLit{Kind: token.STRING, Value: `"failed to get the reference target managed resource and its list for reference resolution"`},
   616  							},
   617  						},
   618  					},
   619  				},
   620  			},
   621  		},
   622  	}
   623  	return map[string]string{
   624  		// TODO: we may need to parameterize the import alias in the future, if
   625  		// any provider that uses the transformer has an import alias collision
   626  		// which is not very likely.
   627  		fmt.Sprintf(`"%s"`, apiResolverPackage): "apisresolver",
   628  	}, []ast.Stmt{assignStmt, ifStmt}
   629  }
   630  
   631  func (r *Resolver) overrideGroupName(group string) string {
   632  	g, ok := r.apiGroupOverrides[group]
   633  	// we need to allow overrides with an empty string
   634  	if !ok {
   635  		return group
   636  	}
   637  	return g
   638  }