github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/codegen/python/gen_resource_mappings.go (about)

     1  // Copyright 2016-2021, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package python
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io"
    21  	"sort"
    22  )
    23  
    24  // Generates code to build and regsiter ResourceModule and
    25  // ResourcePackage instances with the Pulumi runtime. This code
    26  // supports deserialization of references into fully hydrated Resource
    27  // and Provider instances.
    28  //
    29  // Implementation scans the given root `modContext` for resource
    30  // packages and modules, passes the info down as JSON literals to the
    31  // generated code, and generates a `_utilities.register` call to do
    32  // the heavy lifting.
    33  //
    34  // Generates code only for the top-level `__init__.py`. Whenever any
    35  // of the sub-modules is imported, Python imports top-level module
    36  // also. This scheme ensures all resource modules are registered
    37  // eagerly even when we apply lazy loading for some of the modules.
    38  func genResourceMappings(root *modContext, w io.Writer) error {
    39  	if root.isTopLevel() {
    40  		rm, err := jsonPythonLiteral(allResourceModuleInfos(root))
    41  		if err != nil {
    42  			return err
    43  		}
    44  		rp, err := jsonPythonLiteral(allResourcePackageInfos(root))
    45  		if err != nil {
    46  			return err
    47  		}
    48  		fmt.Fprintf(w, "_utilities.register(\n    resource_modules=%s,\n    resource_packages=%s\n)\n", rm, rp)
    49  		return nil
    50  	}
    51  	return nil
    52  }
    53  
    54  func jsonPythonLiteral(thing interface{}) (string, error) {
    55  	bytes, err := json.MarshalIndent(thing, "", " ")
    56  	if err != nil {
    57  		return "", err
    58  	}
    59  	return fmt.Sprintf("\"\"\"\n%s\n\"\"\"", string(bytes)), nil
    60  }
    61  
    62  // Information about a resource module and how it maps tokens to
    63  // Python classes.
    64  //
    65  // Example:
    66  //
    67  //	{
    68  //	  "pkg": "azure-native",
    69  //	  "mod": "databricks",
    70  //	  "fqn": "pulumi_azure_native.databricks"
    71  //	  "classes": {
    72  //	    "azure-native:databricks:Workspace": "Workspace",
    73  //	    "azure-native:databricks:vNetPeering": "VNetPeering"
    74  //	  }
    75  //	}
    76  type resourceModuleInfo struct {
    77  	Pkg     string            `json:"pkg"`
    78  	Mod     string            `json:"mod"`
    79  	Fqn     string            `json:"fqn"`
    80  	Classes map[string]string `json:"classes"`
    81  }
    82  
    83  func (rmi *resourceModuleInfo) Token() string {
    84  	return fmt.Sprintf("%s:%s", rmi.Pkg, rmi.Mod)
    85  }
    86  
    87  func makeResourceModuleInfo(pkg, mod, fqn string) resourceModuleInfo {
    88  	return resourceModuleInfo{pkg, mod, fqn, make(map[string]string)}
    89  }
    90  
    91  func allResourceModuleInfos(root *modContext) []resourceModuleInfo {
    92  	result := []resourceModuleInfo{}
    93  	for _, mctx := range root.walkSelfWithDescendants() {
    94  		result = append(result, collectResourceModuleInfos(mctx)...)
    95  	}
    96  	sort.Slice(result, func(i, j int) bool {
    97  		return result[i].Token() < result[j].Token()
    98  	})
    99  	return result
   100  }
   101  
   102  func collectResourceModuleInfos(mctx *modContext) []resourceModuleInfo {
   103  	byMod := make(map[string]resourceModuleInfo)
   104  
   105  	for _, res := range mctx.resources {
   106  		if res.IsOverlay {
   107  			// This resource code is generated by the provider, so no further action is required.
   108  			continue
   109  		}
   110  
   111  		if !res.IsProvider {
   112  			pkg := mctx.pkg.Name
   113  			mod := mctx.pkg.TokenToRuntimeModule(res.Token)
   114  			fqn := mctx.fullyQualifiedImportName()
   115  
   116  			rmi, found := byMod[mod]
   117  			if !found {
   118  				rmi = makeResourceModuleInfo(pkg, mod, fqn)
   119  				byMod[mod] = rmi
   120  			}
   121  
   122  			rmi.Classes[res.Token] = tokenToName(res.Token)
   123  		}
   124  	}
   125  
   126  	var result []resourceModuleInfo
   127  	for _, rmi := range byMod {
   128  		result = append(result, rmi)
   129  	}
   130  
   131  	return result
   132  }
   133  
   134  // Information about a package.
   135  //
   136  // Example:
   137  //
   138  // {
   139  //   "pkg": "azure-native",
   140  //   "token": "pulumi:providers:azure-native"
   141  //   "fqn": "pulumi_azure_native"
   142  //   "class": "Provider"
   143  // }
   144  
   145  type resourcePackageInfo struct {
   146  	Pkg   string `json:"pkg"`
   147  	Token string `json:"token"`
   148  	Fqn   string `json:"fqn"`
   149  	Class string `json:"class"`
   150  }
   151  
   152  func allResourcePackageInfos(root *modContext) []resourcePackageInfo {
   153  	result := []resourcePackageInfo{}
   154  	for _, mctx := range root.walkSelfWithDescendants() {
   155  		result = append(result, collectResourcePackageInfos(mctx)...)
   156  	}
   157  	sort.Slice(result, func(i int, j int) bool {
   158  		return result[i].Token < result[j].Token
   159  	})
   160  	return result
   161  }
   162  
   163  func collectResourcePackageInfos(mctx *modContext) []resourcePackageInfo {
   164  	var out []resourcePackageInfo
   165  	for _, res := range mctx.resources {
   166  		if res.IsOverlay {
   167  			// This resource code is generated by the provider, so no further action is required.
   168  			continue
   169  		}
   170  
   171  		if res.IsProvider {
   172  			pkg := mctx.pkg.Name
   173  			token := res.Token
   174  			fqn := mctx.fullyQualifiedImportName()
   175  			class := "Provider"
   176  			out = append(out, resourcePackageInfo{pkg, token, fqn, class})
   177  		}
   178  	}
   179  	return out
   180  }