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 }