github.com/crossplane/upjet@v1.3.0/pkg/registry/reference/references.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package reference
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/pkg/errors"
    12  
    13  	"github.com/crossplane/upjet/pkg/config"
    14  	"github.com/crossplane/upjet/pkg/registry"
    15  	"github.com/crossplane/upjet/pkg/types"
    16  )
    17  
    18  const (
    19  	extractorPackagePath      = "github.com/crossplane/upjet/pkg/resource"
    20  	extractResourceIDFuncPath = extractorPackagePath + ".ExtractResourceID()"
    21  	fmtExtractParamFuncPath   = extractorPackagePath + `.ExtractParamPath("%s",%t)`
    22  )
    23  
    24  // Injector resolves references using provider metadata
    25  type Injector struct {
    26  	ModulePath        string
    27  	ProviderShortName string
    28  }
    29  
    30  // NewInjector initializes a new Injector
    31  func NewInjector(modulePath string) *Injector {
    32  	return &Injector{
    33  		ModulePath: modulePath,
    34  	}
    35  }
    36  
    37  func getExtractorFuncPath(r *config.Resource, sourceAttr string) string {
    38  	switch sourceAttr {
    39  	// value extractor from status.atProvider.id
    40  	case "id":
    41  		return extractResourceIDFuncPath
    42  	// value extractor from spec.forProvider.<attr>
    43  	default:
    44  		for _, n := range r.ExternalName.OmittedFields {
    45  			if sourceAttr == n {
    46  				return ""
    47  			}
    48  		}
    49  		s, ok := r.TerraformResource.Schema[sourceAttr]
    50  		if !ok {
    51  			return ""
    52  		}
    53  		return fmt.Sprintf(fmtExtractParamFuncPath, sourceAttr, types.IsObservation(s))
    54  	}
    55  }
    56  
    57  // InjectReferences injects cross-resource references using the
    58  // provider metadata scraped from the Terraform registry.
    59  func (rr *Injector) InjectReferences(configResources map[string]*config.Resource) error { //nolint:gocyclo
    60  	for n, r := range configResources {
    61  		m := configResources[n].MetaResource
    62  		if m == nil {
    63  			continue
    64  		}
    65  
    66  		for i, re := range m.Examples {
    67  			pm, err := paveExampleManifest(re.Manifest)
    68  			if err != nil {
    69  				return errors.Wrapf(err, "cannot pave example manifest for resource: %s", n)
    70  			}
    71  			resolutionContext, err := PrepareLocalResolutionContext(re, NewRefParts(n, re.Name).GetResourceName(false))
    72  			if err != nil {
    73  				return errors.Wrapf(err, "cannot prepare local resolution context for resource: %s", n)
    74  			}
    75  			if err := rr.ResolveReferencesOfPaved(pm, resolutionContext); err != nil {
    76  				return errors.Wrapf(err, "cannot resolve references of resource with local examples context: %s", n)
    77  			}
    78  			if err := rr.storeResolvedDependencies(&m.Examples[i], resolutionContext.Context); err != nil {
    79  				return errors.Wrapf(err, "cannot store resolved dependencies for resource: %s", n)
    80  			}
    81  			for targetAttr, ref := range re.References {
    82  				// if a reference is already configured for the target attribute
    83  				if _, ok := r.References[targetAttr]; ok {
    84  					continue
    85  				}
    86  				parts := getRefParts(ref)
    87  				// if nil or a references to a nested configuration block
    88  				if parts == nil || strings.Contains(parts.Attribute, ".") || strings.Contains(parts.Attribute, "[") {
    89  					continue
    90  				}
    91  				if _, ok := configResources[parts.Resource]; !ok {
    92  					continue
    93  				}
    94  				r.References[targetAttr] = config.Reference{
    95  					TerraformName: parts.Resource,
    96  					Extractor:     getExtractorFuncPath(configResources[parts.Resource], parts.Attribute),
    97  				}
    98  			}
    99  		}
   100  	}
   101  	return nil
   102  }
   103  
   104  func (rr *Injector) storeResolvedDependencies(re *registry.ResourceExample, context map[string]*PavedWithManifest) error {
   105  	for rn, pm := range context {
   106  		buff, err := pm.Paved.MarshalJSON()
   107  		if err != nil {
   108  			return errors.Wrapf(err, "cannot marshal paved as JSON: %s", rn)
   109  		}
   110  		if _, ok := re.Dependencies[rn]; ok {
   111  			re.Dependencies[rn] = string(buff)
   112  		}
   113  	}
   114  	return nil
   115  }
   116  
   117  func (rr *Injector) getTypePath(tfName string, configResources map[string]*config.Resource) (string, error) {
   118  	r := configResources[tfName]
   119  	if r == nil {
   120  		return "", errors.Errorf("cannot find configuration for Terraform resource: %s", tfName)
   121  	}
   122  	shortGroup := r.ShortGroup
   123  	if len(shortGroup) == 0 {
   124  		shortGroup = rr.ProviderShortName
   125  	}
   126  	return fmt.Sprintf("%s/%s/%s/%s.%s", rr.ModulePath, "apis", shortGroup, r.Version, r.Kind), nil
   127  }
   128  
   129  // SetReferenceTypes resolves reference types of configured references
   130  // using their TerraformNames.
   131  func (rr *Injector) SetReferenceTypes(configResources map[string]*config.Resource) error {
   132  	for _, r := range configResources {
   133  		for attr, ref := range r.References {
   134  			if ref.Type == "" && ref.TerraformName != "" {
   135  				crdTypePath, err := rr.getTypePath(ref.TerraformName, configResources)
   136  				if err != nil {
   137  					return errors.Wrap(err, "cannot set reference types")
   138  				}
   139  				// TODO(aru): if type mapper cannot provide a mapping,
   140  				// currently we remove the reference. Once,
   141  				// we have type mapper implementations available
   142  				// for all providers, then we can keep the refs
   143  				// instead of removing them, and expect resulting
   144  				// compile errors to be fixed by making the types
   145  				// available to the type mapper.
   146  				if crdTypePath == "" {
   147  					delete(r.References, attr)
   148  					continue
   149  				}
   150  				ref.Type = crdTypePath
   151  				r.References[attr] = ref
   152  			}
   153  		}
   154  	}
   155  	return nil
   156  }