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

     1  package gen
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	gofmt "go/format"
     7  	"io"
     8  	"io/ioutil"
     9  	"path"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/iancoleman/strcase"
    15  
    16  	"github.com/pulumi/pulumi/pkg/v3/codegen"
    17  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
    18  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model/format"
    19  	"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax"
    20  	"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
    21  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    22  	"github.com/pulumi/pulumi/sdk/v3/go/common/encoding"
    23  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    24  	"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
    25  )
    26  
    27  const (
    28  	IndexToken   = "index"
    29  	fromBase64Fn = "fromBase64"
    30  )
    31  
    32  type generator struct {
    33  	// The formatter to use when generating code.
    34  	*format.Formatter
    35  	program             *pcl.Program
    36  	packages            map[string]*schema.Package
    37  	contexts            map[string]map[string]*pkgContext
    38  	diagnostics         hcl.Diagnostics
    39  	spills              *spills
    40  	jsonTempSpiller     *jsonSpiller
    41  	ternaryTempSpiller  *tempSpiller
    42  	readDirTempSpiller  *readDirSpiller
    43  	splatSpiller        *splatSpiller
    44  	optionalSpiller     *optionalSpiller
    45  	scopeTraversalRoots codegen.StringSet
    46  	arrayHelpers        map[string]*promptToInputArrayHelper
    47  	isErrAssigned       bool
    48  	tmpVarCount         int
    49  	configCreated       bool
    50  	externalCache       *Cache
    51  
    52  	// User-configurable options
    53  	assignResourcesToVariables bool // Assign resource to a new variable instead of _.
    54  }
    55  
    56  // GenerateProgramOptions are used to configure optional generator behavior.
    57  type GenerateProgramOptions struct {
    58  	AssignResourcesToVariables bool // Assign resource to a new variable instead of _.
    59  	ExternalCache              *Cache
    60  }
    61  
    62  func GenerateProgram(program *pcl.Program) (map[string][]byte, hcl.Diagnostics, error) {
    63  	pcl.MapProvidersAsResources(program)
    64  	return GenerateProgramWithOptions(program, GenerateProgramOptions{})
    65  }
    66  
    67  func GenerateProgramWithOptions(program *pcl.Program, opts GenerateProgramOptions) (
    68  	map[string][]byte, hcl.Diagnostics, error) {
    69  	packages, contexts := map[string]*schema.Package{}, map[string]map[string]*pkgContext{}
    70  	packageDefs, err := programPackageDefs(program)
    71  	if err != nil {
    72  		return nil, nil, err
    73  	}
    74  
    75  	if opts.ExternalCache == nil {
    76  		opts.ExternalCache = globalCache
    77  	}
    78  
    79  	for _, pkg := range packageDefs {
    80  		packages[pkg.Name], contexts[pkg.Name] = pkg, getPackages("tool", pkg, opts.ExternalCache)
    81  	}
    82  
    83  	g := &generator{
    84  		program:             program,
    85  		packages:            packages,
    86  		contexts:            contexts,
    87  		spills:              &spills{counts: map[string]int{}},
    88  		jsonTempSpiller:     &jsonSpiller{},
    89  		ternaryTempSpiller:  &tempSpiller{},
    90  		readDirTempSpiller:  &readDirSpiller{},
    91  		splatSpiller:        &splatSpiller{},
    92  		optionalSpiller:     &optionalSpiller{},
    93  		scopeTraversalRoots: codegen.NewStringSet(),
    94  		arrayHelpers:        make(map[string]*promptToInputArrayHelper),
    95  		externalCache:       opts.ExternalCache,
    96  	}
    97  
    98  	// Apply any generate options.
    99  	g.assignResourcesToVariables = opts.AssignResourcesToVariables
   100  
   101  	g.Formatter = format.NewFormatter(g)
   102  
   103  	// We must collect imports once before lowering, and once after.
   104  	// This allows us to avoid complexity of traversing apply expressions for things like JSON
   105  	// but still have access to types provided by __convert intrinsics after lowering.
   106  	pulumiImports := codegen.NewStringSet()
   107  	stdImports := codegen.NewStringSet()
   108  	preambleHelperMethods := codegen.NewStringSet()
   109  	g.collectImports(program, stdImports, pulumiImports, preambleHelperMethods)
   110  
   111  	// Linearize the nodes into an order appropriate for procedural code generation.
   112  	nodes := pcl.Linearize(program)
   113  
   114  	var progPostamble bytes.Buffer
   115  	for _, n := range nodes {
   116  		g.collectScopeRoots(n)
   117  	}
   118  
   119  	for _, n := range nodes {
   120  		g.genNode(&progPostamble, n)
   121  	}
   122  
   123  	g.genPostamble(&progPostamble, nodes)
   124  
   125  	// We must generate the program first and the preamble second and finally cat the two together.
   126  	// This is because nested object/tuple cons expressions can require imports that aren't
   127  	// present in resource declarations or invokes alone. Expressions are lowered when the program is generated
   128  	// and this must happen first so we can access types via __convert intrinsics.
   129  	var index bytes.Buffer
   130  	g.genPreamble(&index, program, stdImports, pulumiImports, preambleHelperMethods)
   131  	index.Write(progPostamble.Bytes())
   132  
   133  	// Run Go formatter on the code before saving to disk
   134  	formattedSource, err := gofmt.Source(index.Bytes())
   135  	if err != nil {
   136  		return nil, g.diagnostics, fmt.Errorf("invalid Go source code:\n\n%s: %w", index.String(), err)
   137  	}
   138  
   139  	files := map[string][]byte{
   140  		"main.go": formattedSource,
   141  	}
   142  	return files, g.diagnostics, nil
   143  }
   144  
   145  func GenerateProject(directory string, project workspace.Project, program *pcl.Program) error {
   146  	files, diagnostics, err := GenerateProgram(program)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	if diagnostics.HasErrors() {
   151  		return diagnostics
   152  	}
   153  
   154  	// Set the runtime to "go" then marshal to Pulumi.yaml
   155  	project.Runtime = workspace.NewProjectRuntimeInfo("go", nil)
   156  	projectBytes, err := encoding.YAML.Marshal(project)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	files["Pulumi.yaml"] = projectBytes
   161  
   162  	// Build a go.mod based on the packages used by program
   163  	var gomod bytes.Buffer
   164  	gomod.WriteString("module " + project.Name.String() + "\n")
   165  	gomod.WriteString(`
   166  go 1.17
   167  
   168  require (
   169  	github.com/pulumi/pulumi/sdk/v3 v3.30.0
   170  `)
   171  
   172  	// For each package add a PackageReference line
   173  	packages, err := programPackageDefs(program)
   174  	if err != nil {
   175  		return err
   176  	}
   177  	for _, p := range packages {
   178  		if p.Name == "pulumi" {
   179  			continue
   180  		}
   181  		if err := p.ImportLanguages(map[string]schema.Language{"go": Importer}); err != nil {
   182  			return err
   183  		}
   184  
   185  		if p.Version != nil && p.Version.Major <= 0 {
   186  			// Let `go mod tidy` resolve pre-1.0 and non-module package versions on InstallDependencies,
   187  			// as it better handles the way we use `importBasePath`. What we need is a `modulePath`. `go
   188  			// get` handles these cases, which are not parseable, they depend on retrieving the target
   189  			// repository and downloading it to disk.
   190  			//
   191  			// Here are two cases, first the parseable case:
   192  			//
   193  			// * go get github.com/pulumi/pulumi-aws/sdk/v5/go/aws@v5.3.0
   194  			//          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ module path
   195  			//                                           ~~ major version
   196  			//                                              ~~~~~~ package path - can be any number of path parts
   197  			//                                                     ~~~~~~ version
   198  			//
   199  			// Here, we can cut on the major version.
   200  
   201  			// * go get github.com/pulumi/pulumi-aws-native/sdk/go/aws@v0.16.0
   202  			//          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ module path
   203  			//                                                  ~~~~~~ package path - can be any number of path parts
   204  			//                                                         ~~~~~~~ version
   205  			//
   206  			// Here we cannot cut on the major version, as it isn't present. The only way to resolve this
   207  			// package is to pull the repo.
   208  			//
   209  			// Fortunately for these pre-1.0 releases, `go mod tidy` on the generated repo will at least
   210  			// add the module based on the import generated in the .go files, but it will always get the
   211  			// latest version.
   212  
   213  			if info, ok := p.Language["go"]; ok {
   214  				if info, ok := info.(GoPackageInfo); ok && info.ModulePath != "" {
   215  					gomod.WriteString(fmt.Sprintf(" %s v%s\n", info.ModulePath, p.Version.String()))
   216  				}
   217  			}
   218  			continue
   219  		}
   220  
   221  		// Relatively safe default, this works for Pulumi provider packages:
   222  		vPath := ""
   223  		if p.Version != nil && p.Version.Major > 1 {
   224  			vPath = fmt.Sprintf("/v%d", p.Version.Major)
   225  		}
   226  		packageName := fmt.Sprintf("github.com/pulumi/pulumi-%s/sdk%s/go/%s", p.Name, vPath, p.Name)
   227  		if langInfo, found := p.Language["go"]; found {
   228  			goInfo, ok := langInfo.(GoPackageInfo)
   229  			if ok && goInfo.ImportBasePath != "" {
   230  				separatorIndex := strings.Index(goInfo.ImportBasePath, vPath)
   231  				if separatorIndex < 0 {
   232  					packageName = ""
   233  				} else {
   234  					modulePrefix := goInfo.ImportBasePath[:separatorIndex]
   235  					packageName = fmt.Sprintf("%s%s", modulePrefix, vPath)
   236  				}
   237  			}
   238  		}
   239  
   240  		if packageName != "" {
   241  			gomod.WriteString(fmt.Sprintf("	%s v%s\n", packageName, p.Version.String()))
   242  		}
   243  	}
   244  
   245  	gomod.WriteString(")")
   246  
   247  	files["go.mod"] = gomod.Bytes()
   248  
   249  	for filename, data := range files {
   250  		outPath := path.Join(directory, filename)
   251  		err := ioutil.WriteFile(outPath, data, 0600)
   252  		if err != nil {
   253  			return fmt.Errorf("could not write output program: %w", err)
   254  		}
   255  	}
   256  
   257  	return nil
   258  }
   259  
   260  var packageContexts sync.Map
   261  
   262  func getPackages(tool string, pkg *schema.Package, cache *Cache) map[string]*pkgContext {
   263  	if v, ok := packageContexts.Load(pkg); ok {
   264  		return v.(map[string]*pkgContext)
   265  	}
   266  
   267  	if err := pkg.ImportLanguages(map[string]schema.Language{"go": Importer}); err != nil {
   268  		return nil
   269  	}
   270  
   271  	var goPkgInfo GoPackageInfo
   272  	if goInfo, ok := pkg.Language["go"].(GoPackageInfo); ok {
   273  		goPkgInfo = goInfo
   274  	}
   275  	v := generatePackageContextMap(tool, pkg, goPkgInfo, cache)
   276  	packageContexts.Store(pkg, v)
   277  	return v
   278  }
   279  
   280  func (g *generator) collectScopeRoots(n pcl.Node) {
   281  	diags := n.VisitExpressions(nil, func(n model.Expression) (model.Expression, hcl.Diagnostics) {
   282  		if st, ok := n.(*model.ScopeTraversalExpression); ok {
   283  			g.scopeTraversalRoots.Add(st.RootName)
   284  		}
   285  		return n, nil
   286  	})
   287  	contract.Assert(len(diags) == 0)
   288  }
   289  
   290  // genPreamble generates package decl, imports, and opens the main func
   291  func (g *generator) genPreamble(w io.Writer, program *pcl.Program, stdImports, pulumiImports,
   292  	preambleHelperMethods codegen.StringSet) {
   293  	g.Fprint(w, "package main\n\n")
   294  	g.Fprintf(w, "import (\n")
   295  
   296  	g.collectImports(program, stdImports, pulumiImports, preambleHelperMethods)
   297  	for _, imp := range stdImports.SortedValues() {
   298  		g.Fprintf(w, "\"%s\"\n", imp)
   299  	}
   300  
   301  	g.Fprintf(w, "\n")
   302  	g.Fprintf(w, "\"github.com/pulumi/pulumi/sdk/v3/go/pulumi\"\n")
   303  
   304  	for _, imp := range pulumiImports.SortedValues() {
   305  		g.Fprintf(w, "%s\n", imp)
   306  	}
   307  
   308  	g.Fprintf(w, ")\n")
   309  
   310  	// If we collected any helper methods that should be added, write them just before the main func
   311  	for _, preambleHelperMethodBody := range preambleHelperMethods.SortedValues() {
   312  		g.Fprintf(w, "%s\n\n", preambleHelperMethodBody)
   313  	}
   314  
   315  	g.Fprintf(w, "func main() {\n")
   316  	g.Fprintf(w, "pulumi.Run(func(ctx *pulumi.Context) error {\n")
   317  }
   318  
   319  func (g *generator) collectTypeImports(program *pcl.Program, t schema.Type, imports codegen.StringSet) {
   320  	var token string
   321  	switch t := t.(type) {
   322  	case *schema.InputType:
   323  		g.collectTypeImports(program, t.ElementType, imports)
   324  		return
   325  	case *schema.OptionalType:
   326  		g.collectTypeImports(program, t.ElementType, imports)
   327  		return
   328  	case *schema.ArrayType:
   329  		g.collectTypeImports(program, t.ElementType, imports)
   330  		return
   331  	case *schema.MapType:
   332  		g.collectTypeImports(program, t.ElementType, imports)
   333  		return
   334  	case *schema.UnionType:
   335  		for _, t := range t.ElementTypes {
   336  			g.collectTypeImports(program, t, imports)
   337  		}
   338  		return
   339  	case *schema.ObjectType:
   340  		token = t.Token
   341  	case *schema.EnumType:
   342  		token = t.Token
   343  	case *schema.TokenType:
   344  		token = t.Token
   345  	case *schema.ResourceType:
   346  		token = t.Token
   347  	}
   348  	if token == "" {
   349  		return
   350  	}
   351  
   352  	var tokenRange hcl.Range
   353  	pkg, mod, name, _ := pcl.DecomposeToken(token, tokenRange)
   354  	vPath, err := g.getVersionPath(program, pkg)
   355  	if err != nil {
   356  		panic(err)
   357  	}
   358  	imports.Add(g.getPulumiImport(pkg, vPath, mod, name))
   359  }
   360  
   361  // collect Imports returns two sets of packages imported by the program, std lib packages and pulumi packages
   362  func (g *generator) collectImports(
   363  	program *pcl.Program,
   364  	stdImports,
   365  	pulumiImports,
   366  	preambleHelperMethods codegen.StringSet) (codegen.StringSet, codegen.StringSet, codegen.StringSet) {
   367  	// Accumulate import statements for the various providers
   368  	for _, n := range program.Nodes {
   369  		if r, isResource := n.(*pcl.Resource); isResource {
   370  			pcl.FixupPulumiPackageTokens(r)
   371  			pkg, mod, name, _ := r.DecomposeToken()
   372  			if pkg == "pulumi" {
   373  				if mod == "providers" {
   374  					pkg = name
   375  					mod = ""
   376  				} else if mod == "" {
   377  					continue
   378  				}
   379  
   380  			}
   381  			vPath, err := g.getVersionPath(program, pkg)
   382  			if err != nil {
   383  				panic(err)
   384  			}
   385  
   386  			pulumiImports.Add(g.getPulumiImport(pkg, vPath, mod, name))
   387  		}
   388  		if _, isConfigVar := n.(*pcl.ConfigVariable); isConfigVar {
   389  			pulumiImports.Add("\"github.com/pulumi/pulumi/sdk/v3/go/pulumi/config\"")
   390  		}
   391  
   392  		diags := n.VisitExpressions(nil, func(n model.Expression) (model.Expression, hcl.Diagnostics) {
   393  			if call, ok := n.(*model.FunctionCallExpression); ok {
   394  				if call.Name == pcl.Invoke {
   395  					tokenArg := call.Args[0]
   396  					token := tokenArg.(*model.TemplateExpression).Parts[0].(*model.LiteralValueExpression).Value.AsString()
   397  					tokenRange := tokenArg.SyntaxNode().Range()
   398  					pkg, mod, name, diagnostics := pcl.DecomposeToken(token, tokenRange)
   399  
   400  					contract.Assert(len(diagnostics) == 0)
   401  
   402  					vPath, err := g.getVersionPath(program, pkg)
   403  					if err != nil {
   404  						panic(err)
   405  					}
   406  					pulumiImports.Add(g.getPulumiImport(pkg, vPath, mod, name))
   407  				} else if call.Name == pcl.IntrinsicConvert {
   408  					g.collectConvertImports(program, call, pulumiImports)
   409  				}
   410  
   411  				// Checking to see if this function call deserves its own dedicated helper method in the preamble
   412  				if helperMethodBody, ok := getHelperMethodIfNeeded(call.Name); ok {
   413  					preambleHelperMethods.Add(helperMethodBody)
   414  				}
   415  			}
   416  			return n, nil
   417  		})
   418  		contract.Assert(len(diags) == 0)
   419  
   420  		diags = n.VisitExpressions(nil, func(n model.Expression) (model.Expression, hcl.Diagnostics) {
   421  			if call, ok := n.(*model.FunctionCallExpression); ok {
   422  				for _, fnPkg := range g.genFunctionPackages(call) {
   423  					stdImports.Add(fnPkg)
   424  				}
   425  			}
   426  			if t, ok := n.(*model.TemplateExpression); ok {
   427  				if len(t.Parts) > 1 {
   428  					stdImports.Add("fmt")
   429  				}
   430  			}
   431  			return n, nil
   432  		})
   433  		contract.Assert(len(diags) == 0)
   434  	}
   435  
   436  	return stdImports, pulumiImports, preambleHelperMethods
   437  }
   438  
   439  func (g *generator) collectConvertImports(
   440  	program *pcl.Program,
   441  	call *model.FunctionCallExpression,
   442  	pulumiImports codegen.StringSet) {
   443  	if schemaType, ok := pcl.GetSchemaForType(call.Type()); ok {
   444  		// Sometimes code for a `__convert` call does not
   445  		// really use the import of the result type. In such
   446  		// cases it is important not to generate a
   447  		// non-compiling unused import. Detect some of these
   448  		// cases here.
   449  		//
   450  		// Fully solving this is deferred for later:
   451  		// TODO[pulumi/pulumi#8324].
   452  		if expr, ok := call.Args[0].(*model.TemplateExpression); ok {
   453  			if lit, ok := expr.Parts[0].(*model.LiteralValueExpression); ok &&
   454  				model.StringType.AssignableFrom(lit.Type()) &&
   455  				call.Type().AssignableFrom(lit.Type()) {
   456  				return
   457  			}
   458  		}
   459  		g.collectTypeImports(program, schemaType, pulumiImports)
   460  	}
   461  }
   462  
   463  func (g *generator) getVersionPath(program *pcl.Program, pkg string) (string, error) {
   464  	for _, p := range program.PackageReferences() {
   465  		if p.Name() == pkg {
   466  			if ver := p.Version(); ver != nil && ver.Major > 1 {
   467  				return fmt.Sprintf("/v%d", ver.Major), nil
   468  			}
   469  			return "", nil
   470  		}
   471  	}
   472  
   473  	return "", fmt.Errorf("could not find package version information for pkg: %s", pkg)
   474  
   475  }
   476  
   477  func (g *generator) getPkgContext(pkg, mod string) (*pkgContext, bool) {
   478  	p, ok := g.contexts[pkg]
   479  	if !ok {
   480  		return nil, false
   481  	}
   482  	m, ok := p[mod]
   483  	return m, ok
   484  }
   485  
   486  func (g *generator) getGoPackageInfo(pkg string) (GoPackageInfo, bool) {
   487  	p, ok := g.packages[pkg]
   488  	if !ok {
   489  		return GoPackageInfo{}, false
   490  	}
   491  	info, ok := p.Language["go"].(GoPackageInfo)
   492  	return info, ok
   493  }
   494  
   495  func (g *generator) getPulumiImport(pkg, vPath, mod, name string) string {
   496  	info, _ := g.getGoPackageInfo(pkg)
   497  	if m, ok := info.ModuleToPackage[mod]; ok {
   498  		mod = m
   499  	}
   500  
   501  	imp := fmt.Sprintf("github.com/pulumi/pulumi-%s/sdk%s/go/%s/%s", pkg, vPath, pkg, mod)
   502  	// namespaceless invokes "aws:index:..."
   503  	if mod == "" {
   504  		imp = fmt.Sprintf("github.com/pulumi/pulumi-%s/sdk%s/go/%s", pkg, vPath, pkg)
   505  	}
   506  
   507  	// All providers don't follow the sdk/go/<package> scheme. Allow ImportBasePath as
   508  	// a means to override this assumption.
   509  	if info.ImportBasePath != "" {
   510  		if mod != "" {
   511  			imp = fmt.Sprintf("%s/%s", info.ImportBasePath, mod)
   512  		} else {
   513  			imp = info.ImportBasePath
   514  		}
   515  	}
   516  
   517  	if alias, ok := info.PackageImportAliases[imp]; ok {
   518  		return fmt.Sprintf("%s %q", alias, imp)
   519  	}
   520  
   521  	modSplit := strings.Split(mod, "/")
   522  	// account for mods like "eks/ClusterVpcConfig" index...
   523  	if len(modSplit) > 1 {
   524  		if modSplit[0] == "" || modSplit[0] == IndexToken {
   525  			imp = fmt.Sprintf("github.com/pulumi/pulumi-%s/sdk%s/go/%s", pkg, vPath, pkg)
   526  			if info.ImportBasePath != "" {
   527  				imp = info.ImportBasePath
   528  			}
   529  		} else {
   530  			imp = fmt.Sprintf("github.com/pulumi/pulumi-%s/sdk%s/go/%s/%s", pkg, vPath, pkg, modSplit[0])
   531  			if info.ImportBasePath != "" {
   532  				imp = fmt.Sprintf("%s/%s", info.ImportBasePath, modSplit[0])
   533  			}
   534  		}
   535  	}
   536  	return fmt.Sprintf("%q", imp)
   537  }
   538  
   539  // genPostamble closes the method
   540  func (g *generator) genPostamble(w io.Writer, nodes []pcl.Node) {
   541  
   542  	g.Fprint(w, "return nil\n")
   543  	g.Fprintf(w, "})\n")
   544  	g.Fprintf(w, "}\n")
   545  
   546  	g.genHelpers(w)
   547  }
   548  
   549  func (g *generator) genHelpers(w io.Writer) {
   550  	for _, v := range g.arrayHelpers {
   551  		v.generateHelperMethod(w)
   552  	}
   553  }
   554  
   555  func (g *generator) genNode(w io.Writer, n pcl.Node) {
   556  	switch n := n.(type) {
   557  	case *pcl.Resource:
   558  		g.genResource(w, n)
   559  	case *pcl.OutputVariable:
   560  		g.genOutputAssignment(w, n)
   561  	case *pcl.ConfigVariable:
   562  		g.genConfigVariable(w, n)
   563  	case *pcl.LocalVariable:
   564  		g.genLocalVariable(w, n)
   565  	}
   566  }
   567  
   568  var resourceType = model.NewOpaqueType("pulumi.Resource")
   569  
   570  func (g *generator) lowerResourceOptions(opts *pcl.ResourceOptions) (*model.Block, []interface{}) {
   571  	if opts == nil {
   572  		return nil, nil
   573  	}
   574  
   575  	var block *model.Block
   576  	var temps []interface{}
   577  	appendOption := func(name string, value model.Expression, destType model.Type) {
   578  		if block == nil {
   579  			block = &model.Block{
   580  				Type: "options",
   581  				Body: &model.Body{},
   582  			}
   583  		}
   584  
   585  		value, valueTemps := g.lowerExpression(value, destType)
   586  		temps = append(temps, valueTemps...)
   587  
   588  		block.Body.Items = append(block.Body.Items, &model.Attribute{
   589  			Tokens: syntax.NewAttributeTokens(name),
   590  			Name:   name,
   591  			Value:  value,
   592  		})
   593  	}
   594  
   595  	if opts.Parent != nil {
   596  		appendOption("Parent", opts.Parent, model.DynamicType)
   597  	}
   598  	if opts.Provider != nil {
   599  		appendOption("Provider", opts.Provider, model.DynamicType)
   600  	}
   601  	if opts.DependsOn != nil {
   602  		appendOption("DependsOn", opts.DependsOn, model.NewListType(resourceType))
   603  	}
   604  	if opts.Protect != nil {
   605  		appendOption("Protect", opts.Protect, model.BoolType)
   606  	}
   607  	if opts.IgnoreChanges != nil {
   608  		appendOption("IgnoreChanges", opts.IgnoreChanges, model.NewListType(model.StringType))
   609  	}
   610  
   611  	return block, temps
   612  }
   613  
   614  func (g *generator) genResourceOptions(w io.Writer, block *model.Block) {
   615  	if block == nil {
   616  		return
   617  	}
   618  
   619  	for _, item := range block.Body.Items {
   620  		attr := item.(*model.Attribute)
   621  		g.Fgenf(w, ", pulumi.%s(%v)", attr.Name, attr.Value)
   622  	}
   623  }
   624  
   625  func (g *generator) genResource(w io.Writer, r *pcl.Resource) {
   626  
   627  	resName, resNameVar := r.LogicalName(), makeValidIdentifier(r.Name())
   628  	pkg, mod, typ, _ := r.DecomposeToken()
   629  	originalMod := mod
   630  	if pkg == "pulumi" && mod == "providers" {
   631  		pkg = typ
   632  		mod = ""
   633  		typ = "Provider"
   634  	}
   635  	if mod == "" || strings.HasPrefix(mod, "/") || strings.HasPrefix(mod, "index/") {
   636  		originalMod = mod
   637  		mod = pkg
   638  	}
   639  
   640  	// Compute resource options
   641  	options, temps := g.lowerResourceOptions(r.Options)
   642  	g.genTemps(w, temps)
   643  
   644  	// Add conversions to input properties
   645  	for _, input := range r.Inputs {
   646  		destType, diagnostics := r.InputType.Traverse(hcl.TraverseAttr{Name: input.Name})
   647  		g.diagnostics = append(g.diagnostics, diagnostics...)
   648  		expr, temps := g.lowerExpression(input.Value, destType.(model.Type))
   649  		input.Value = expr
   650  		g.genTemps(w, temps)
   651  	}
   652  
   653  	modOrAlias := g.getModOrAlias(pkg, mod, originalMod)
   654  
   655  	instantiate := func(varName, resourceName string, w io.Writer) {
   656  		if g.scopeTraversalRoots.Has(varName) || strings.HasPrefix(varName, "__") {
   657  			g.Fgenf(w, "%s, err := %s.New%s(ctx, %s, ", varName, modOrAlias, typ, resourceName)
   658  		} else {
   659  			assignment := ":="
   660  			if g.isErrAssigned {
   661  				assignment = "="
   662  			}
   663  			if g.assignResourcesToVariables {
   664  				g.Fgenf(w, "%s, err := %s.New%s(ctx, %s, ",
   665  					strcase.ToLowerCamel(resourceName), modOrAlias, typ, resourceName)
   666  			} else {
   667  				g.Fgenf(w, "_, err %s %s.New%s(ctx, %s, ", assignment, modOrAlias, typ, resourceName)
   668  			}
   669  		}
   670  		g.isErrAssigned = true
   671  
   672  		if len(r.Inputs) > 0 {
   673  			g.Fgenf(w, "&%s.%sArgs{\n", modOrAlias, typ)
   674  			for _, attr := range r.Inputs {
   675  				g.Fgenf(w, "%s: ", strings.Title(attr.Name))
   676  				g.Fgenf(w, "%.v,\n", attr.Value)
   677  
   678  			}
   679  			g.Fprint(w, "}")
   680  		} else {
   681  			g.Fprint(w, "nil")
   682  		}
   683  		g.genResourceOptions(w, options)
   684  		g.Fprint(w, ")\n")
   685  		g.Fgenf(w, "if err != nil {\n")
   686  		g.Fgenf(w, "return err\n")
   687  		g.Fgenf(w, "}\n")
   688  	}
   689  
   690  	if r.Options != nil && r.Options.Range != nil {
   691  		rangeType := model.ResolveOutputs(r.Options.Range.Type())
   692  		rangeExpr, temps := g.lowerExpression(r.Options.Range, rangeType)
   693  		g.genTemps(w, temps)
   694  
   695  		g.Fgenf(w, "var %s []*%s.%s\n", resNameVar, modOrAlias, typ)
   696  
   697  		// ahead of range statement declaration generate the resource instantiation
   698  		// to detect and removed unused k,v variables
   699  		var buf bytes.Buffer
   700  		instantiate("__res", fmt.Sprintf(`fmt.Sprintf("%s-%%v", key0)`, resName), &buf)
   701  		instantiation := buf.String()
   702  		isValUsed := strings.Contains(instantiation, "val0")
   703  		valVar := "_"
   704  		if isValUsed {
   705  			valVar = "val0"
   706  		}
   707  		if model.InputType(model.NumberType).ConversionFrom(rangeExpr.Type()) != model.NoConversion {
   708  			g.Fgenf(w, "for index := 0; index < %.v; index++ {\n", rangeExpr)
   709  			g.Indented(func() {
   710  				g.Fgenf(w, "%skey0 := index\n", g.Indent)
   711  				g.Fgenf(w, "%s%s := index\n", g.Indent, valVar)
   712  			})
   713  		} else {
   714  			g.Fgenf(w, "for key0, %s := range %.v {\n", valVar, rangeExpr)
   715  		}
   716  
   717  		g.Fgen(w, instantiation)
   718  		g.Fgenf(w, "%[1]s = append(%[1]s, __res)\n", resNameVar)
   719  		g.Fgenf(w, "}\n")
   720  
   721  	} else {
   722  		instantiate(resNameVar, fmt.Sprintf("%q", resName), w)
   723  	}
   724  
   725  }
   726  
   727  func (g *generator) genOutputAssignment(w io.Writer, v *pcl.OutputVariable) {
   728  	expr, temps := g.lowerExpression(v.Value, v.Type())
   729  	g.genTemps(w, temps)
   730  	g.Fgenf(w, "ctx.Export(%q, %.3v)\n", v.LogicalName(), expr)
   731  }
   732  func (g *generator) genTemps(w io.Writer, temps []interface{}) {
   733  	singleReturn := ""
   734  	g.genTempsMultiReturn(w, temps, singleReturn)
   735  }
   736  
   737  func (g *generator) genTempsMultiReturn(w io.Writer, temps []interface{}, zeroValueType string) {
   738  	genZeroValueDecl := false
   739  
   740  	if zeroValueType != "" {
   741  		for _, t := range temps {
   742  			switch t.(type) {
   743  			case *spillTemp, *jsonTemp, *readDirTemp:
   744  				genZeroValueDecl = true
   745  			default:
   746  			}
   747  		}
   748  		if genZeroValueDecl {
   749  			// TODO add entropy to var name
   750  			// currently only used inside anonymous functions (no scope collisions)
   751  			g.Fgenf(w, "var _zero %s\n", zeroValueType)
   752  		}
   753  
   754  	}
   755  
   756  	for _, t := range temps {
   757  		switch t := t.(type) {
   758  		case *ternaryTemp:
   759  			// TODO derive from ambient context
   760  			isInput := false
   761  			g.Fgenf(w, "var %s %s\n", t.Name, g.argumentTypeName(t.Value.TrueResult, t.Type(), isInput))
   762  			g.Fgenf(w, "if %.v {\n", t.Value.Condition)
   763  			g.Fgenf(w, "%s = %.v\n", t.Name, t.Value.TrueResult)
   764  			g.Fgenf(w, "} else {\n")
   765  			g.Fgenf(w, "%s = %.v\n", t.Name, t.Value.FalseResult)
   766  			g.Fgenf(w, "}\n")
   767  		case *spillTemp:
   768  			bytesVar := fmt.Sprintf("tmp%s", strings.ToUpper(t.Variable.Name))
   769  			g.Fgenf(w, "%s, err := json.Marshal(", bytesVar)
   770  			args := t.Value.(*model.FunctionCallExpression).Args[0]
   771  			g.Fgenf(w, "%.v)\n", args)
   772  			g.Fgenf(w, "if err != nil {\n")
   773  			if genZeroValueDecl {
   774  				g.Fgenf(w, "return _zero, err\n")
   775  			} else {
   776  				g.Fgenf(w, "return err\n")
   777  			}
   778  			g.Fgenf(w, "}\n")
   779  			g.Fgenf(w, "%s := string(%s)\n", t.Variable.Name, bytesVar)
   780  			g.isErrAssigned = true
   781  		case *readDirTemp:
   782  			tmpSuffix := strings.Split(t.Name, "files")[1]
   783  			g.Fgenf(w, "%s, err := os.ReadDir(%.v)\n", t.Name, t.Value.Args[0])
   784  			g.Fgenf(w, "if err != nil {\n")
   785  			if genZeroValueDecl {
   786  				g.Fgenf(w, "return _zero, err\n")
   787  			} else {
   788  				g.Fgenf(w, "return err\n")
   789  			}
   790  			g.Fgenf(w, "}\n")
   791  			namesVar := fmt.Sprintf("fileNames%s", tmpSuffix)
   792  			g.Fgenf(w, "%s := make([]string, len(%s))\n", namesVar, t.Name)
   793  			iVar := fmt.Sprintf("key%s", tmpSuffix)
   794  			valVar := fmt.Sprintf("val%s", tmpSuffix)
   795  			g.Fgenf(w, "for %s, %s := range %s {\n", iVar, valVar, t.Name)
   796  			g.Fgenf(w, "%s[%s] = %s.Name()\n", namesVar, iVar, valVar)
   797  			g.Fgenf(w, "}\n")
   798  			g.isErrAssigned = true
   799  		case *splatTemp:
   800  			argTyp := g.argumentTypeName(t.Value.Each, t.Value.Each.Type(), false)
   801  			if strings.Contains(argTyp, ".") {
   802  				g.Fgenf(w, "var %s %sArray\n", t.Name, argTyp)
   803  			} else {
   804  				g.Fgenf(w, "var %s []%s\n", t.Name, argTyp)
   805  			}
   806  			g.Fgenf(w, "for _, val0 := range %.v {\n", t.Value.Source)
   807  			g.Fgenf(w, "%s = append(%s, %.v)\n", t.Name, t.Name, t.Value.Each)
   808  			g.Fgenf(w, "}\n")
   809  		case *optionalTemp:
   810  			g.Fgenf(w, "%s := %.v\n", t.Name, t.Value)
   811  		default:
   812  			contract.Failf("unexpected temp type: %v", t)
   813  		}
   814  	}
   815  }
   816  
   817  func (g *generator) genLocalVariable(w io.Writer, v *pcl.LocalVariable) {
   818  	expr, temps := g.lowerExpression(v.Definition.Value, v.Type())
   819  	g.genTemps(w, temps)
   820  	name := makeValidIdentifier(v.Name())
   821  	assignment := ":="
   822  	if !g.scopeTraversalRoots.Has(v.Name()) {
   823  		name = "_"
   824  		if g.isErrAssigned {
   825  			assignment = "="
   826  		}
   827  	}
   828  	if name == "_" {
   829  		assignment = "="
   830  	}
   831  	switch expr := expr.(type) {
   832  	case *model.FunctionCallExpression:
   833  		switch expr.Name {
   834  		case pcl.Invoke:
   835  			// OutputVersionedInvoke does not return an error
   836  			noError, _, _ := pcl.RecognizeOutputVersionedInvoke(expr)
   837  			if noError {
   838  				if name == "_" {
   839  					assignment = "="
   840  				}
   841  				g.Fgenf(w, "%s %s %.3v;\n", name, assignment, expr)
   842  			} else {
   843  				g.Fgenf(w, "%s, err %s %.3v;\n", name, assignment, expr)
   844  				g.isErrAssigned = true
   845  				g.Fgenf(w, "if err != nil {\n")
   846  				g.Fgenf(w, "return err\n")
   847  				g.Fgenf(w, "}\n")
   848  			}
   849  		case pcl.IntrinsicApply:
   850  			if name == "_" {
   851  				assignment = "="
   852  			}
   853  			g.Fgenf(w, "%s %s ", name, assignment)
   854  			g.genApply(w, expr)
   855  			g.Fgenf(w, "\n")
   856  		case "join", "mimeType",
   857  			"fileArchive", "remoteArchive", "assetArchive",
   858  			"fileAsset", "stringAsset", "remoteAsset",
   859  			"toBase64":
   860  			if name == "_" {
   861  				assignment = "="
   862  			}
   863  			g.Fgenf(w, "%s %s %.3v;\n", name, assignment, expr)
   864  		case fromBase64Fn:
   865  			tmpVar := fmt.Sprintf("%s%d", "tmpVar", g.tmpVarCount)
   866  			g.Fgenf(w, "%s, _ %s %.3v;\n", tmpVar, assignment, expr)
   867  			if name == "_" {
   868  				assignment = ":="
   869  			}
   870  			g.Fgenf(w, "%s %s string(%s)\n", name, assignment, tmpVar)
   871  			g.tmpVarCount++
   872  			g.isErrAssigned = true
   873  		default:
   874  			g.Fgenf(w, "%s := %.3v;\n", name, expr)
   875  		}
   876  	default:
   877  		g.Fgenf(w, "%s := %.3v;\n", name, expr)
   878  
   879  	}
   880  }
   881  
   882  func (g *generator) genConfigVariable(w io.Writer, v *pcl.ConfigVariable) {
   883  	if !g.configCreated {
   884  		g.Fprint(w, "cfg := config.New(ctx, \"\")\n")
   885  		g.configCreated = true
   886  	}
   887  
   888  	getType := ""
   889  	switch v.Type() {
   890  	case model.StringType: // Already default
   891  	case model.NumberType:
   892  		getType = "Float"
   893  	case model.IntType:
   894  		getType = "Int"
   895  	case model.BoolType:
   896  		getType = "Boolean"
   897  	case model.DynamicType:
   898  		getType = "Object"
   899  	}
   900  
   901  	getOrRequire := "Get"
   902  	if v.DefaultValue == nil {
   903  		getOrRequire = "Require"
   904  	}
   905  
   906  	name := makeValidIdentifier(v.Name())
   907  	if v.DefaultValue == nil {
   908  		g.Fgenf(w, "%s := cfg.%s%s(\"%s\")\n", name, getOrRequire, getType, v.LogicalName())
   909  	} else {
   910  		expr, temps := g.lowerExpression(v.DefaultValue, v.DefaultValue.Type())
   911  		g.genTemps(w, temps)
   912  		switch expr := expr.(type) {
   913  		case *model.FunctionCallExpression:
   914  			switch expr.Name {
   915  			case pcl.Invoke:
   916  				g.Fgenf(w, "%s, err := %.3v;\n", name, expr)
   917  				g.isErrAssigned = true
   918  				g.Fgenf(w, "if err != nil {\n")
   919  				g.Fgenf(w, "return err\n")
   920  				g.Fgenf(w, "}\n")
   921  			}
   922  		default:
   923  			switch v.Type() {
   924  			// Go will default to interpreting integers (i.e. 3) as ints, even if the config is Number
   925  			case model.NumberType:
   926  				g.Fgenf(w, "%s := float64(%.3v);\n", name, expr)
   927  			default:
   928  				g.Fgenf(w, "%s := %.3v;\n", name, expr)
   929  			}
   930  		}
   931  		switch v.Type() {
   932  		case model.StringType:
   933  			g.Fgenf(w, "if param := cfg.Get(\"%s\"); param != \"\"{\n", v.LogicalName())
   934  		case model.NumberType:
   935  			g.Fgenf(w, "if param := cfg.GetFloat64(\"%s\"); param != 0 {\n", v.LogicalName())
   936  		case model.IntType:
   937  			g.Fgenf(w, "if param := cfg.GetInt(\"%s\"); param != 0 {\n", v.LogicalName())
   938  		case model.BoolType:
   939  			g.Fgenf(w, "if param := cfg.GetBool(\"%s\"); param {\n", v.LogicalName())
   940  		default:
   941  			g.Fgenf(w, "if param := cfg.GetBool(\"%s\"); param != nil {\n", v.LogicalName())
   942  		}
   943  		g.Fgenf(w, "%s = param\n", name)
   944  		g.Fgen(w, "}\n")
   945  	}
   946  }
   947  
   948  // nolint: lll
   949  // useLookupInvokeForm takes a token for an invoke and determines whether to use the
   950  // .Get or .Lookup form. The Go SDK has collisions in .Get methods that require renaming.
   951  // For instance, gen.go creates a resource getter for AWS VPCs that collides with a function:
   952  // GetVPC resource getter: https://github.com/pulumi/pulumi-aws/blob/7835df354694e2f9f23371602a9febebc6b45be8/sdk/go/aws/ec2/getVpc.go#L15
   953  // LookupVPC function: https://github.com/pulumi/pulumi-aws/blob/7835df354694e2f9f23371602a9febebc6b45be8/sdk/go/aws/ec2/getVpc.go#L15
   954  // Given that the naming here is not consisten, we must reverse the process from gen.go.
   955  func (g *generator) useLookupInvokeForm(token string) bool {
   956  	pkg, module, member, _ := pcl.DecomposeToken(token, *new(hcl.Range))
   957  	modSplit := strings.Split(module, "/")
   958  	mod := modSplit[0]
   959  	fn := Title(member)
   960  	if mod == IndexToken && len(modSplit) >= 2 {
   961  		// e.g. "aws:index/getPartition:getPartition" where module is "index/getPartition"
   962  		mod = ""
   963  		fn = Title(modSplit[1])
   964  	} else {
   965  		// e.g. for type "ec2/getVpc:getVpcArgs"
   966  		if _, has := g.contexts[pkg][mod]; !has {
   967  			mod = module
   968  		}
   969  	}
   970  	fnLookup := "Lookup" + fn[3:]
   971  	pkgContext, has := g.contexts[pkg][mod]
   972  	if has && pkgContext.names.Has(fnLookup) {
   973  		return true
   974  	}
   975  	return false
   976  }
   977  
   978  // getModOrAlias attempts to reconstruct the import statement and check if the imported package
   979  // is aliased, returning that alias if available.
   980  func (g *generator) getModOrAlias(pkg, mod, originalMod string) string {
   981  	info, ok := g.getGoPackageInfo(pkg)
   982  	if !ok {
   983  		return mod
   984  	}
   985  	if m, ok := info.ModuleToPackage[mod]; ok {
   986  		mod = m
   987  	}
   988  
   989  	imp := fmt.Sprintf("%s/%s", info.ImportBasePath, mod)
   990  	if alias, ok := info.PackageImportAliases[imp]; ok {
   991  		return alias
   992  	} else if info.ImportBasePath != "" {
   993  		if originalMod == "" || originalMod == IndexToken {
   994  			return path.Base(info.ImportBasePath)
   995  		}
   996  	}
   997  	mod = strings.Split(mod, "/")[0]
   998  	return mod
   999  }
  1000  
  1001  // Go needs complete package definitions in order to properly resolve names.
  1002  //
  1003  // TODO: naming decisions should really be encoded statically so that they can be decided locally.
  1004  func programPackageDefs(program *pcl.Program) ([]*schema.Package, error) {
  1005  	refs := program.PackageReferences()
  1006  	defs := make([]*schema.Package, len(refs))
  1007  	for i, ref := range refs {
  1008  		def, err := ref.Definition()
  1009  		if err != nil {
  1010  			return nil, err
  1011  		}
  1012  		defs[i] = def
  1013  	}
  1014  	return defs, nil
  1015  }