Skip to content

Commit

Permalink
Enable l2-simple
Browse files Browse the repository at this point in the history
  • Loading branch information
Frassle committed Nov 14, 2024
1 parent 58cc134 commit a5563a1
Show file tree
Hide file tree
Showing 9 changed files with 264 additions and 10 deletions.
3 changes: 1 addition & 2 deletions cmd/pulumi-language-yaml/language_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ var expectedFailures = map[string]string{
"l2-invoke-simple": "TODO",
"l2-plain": "TODO",
"l2-ref-ref": "TODO",
"l2-resource-simple": "TODO",
"l2-failed-create-continue-on-error": "TODO",
"l2-invoke-variants": "TODO",
"l2-primitive-ref": "TODO",
Expand All @@ -212,7 +211,7 @@ func TestLanguage(t *testing.T) {
// Run the language plugin
handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{
Init: func(srv *grpc.Server) error {
host := server.NewLanguageHost(engineAddress, "", "")
host := server.NewLanguageHost(engineAddress, "", "", true /* useRPCLoader */)
pulumirpc.RegisterLanguageRuntimeServer(srv, host)
return nil
},
Expand Down
2 changes: 1 addition & 1 deletion cmd/pulumi-language-yaml/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func main() {
// Fire up a gRPC server, letting the kernel choose a free port.
port, done, err := rpcutil.Serve(0, cancelChannel, []func(*grpc.Server) error{
func(srv *grpc.Server) error {
host := server.NewLanguageHost(engineAddress, tracing, compiler)
host := server.NewLanguageHost(engineAddress, tracing, compiler, false /* useRPCLoader */)
pulumirpc.RegisterLanguageRuntimeServer(srv, host)
return nil
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resources:
res:
type: simple:Resource
properties:
value: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name: l2-resource-simple
runtime: yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
packageDeclarationVersion: 1
name: simple
version: 2.0.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
packageDeclarationVersion: 1
name: simple
version: 2.0.0
11 changes: 10 additions & 1 deletion pkg/pulumiyaml/codegen/gen_program.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/pulumi/pulumi/pkg/v3/codegen/pcl"
enc "github.com/pulumi/pulumi/sdk/v3/go/common/encoding"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/fsutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"

"github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/ast"
Expand Down Expand Up @@ -56,7 +57,7 @@ func GenerateProgram(program *pcl.Program) (map[string][]byte, hcl.Diagnostics,
return map[string][]byte{"Main.yaml": w.Bytes()}, g.diags, err
}

func GenerateProject(directory string, project workspace.Project, program *pcl.Program) error {
func GenerateProject(directory string, project workspace.Project, program *pcl.Program, localDependencies map[string]string) error {
files, diagnostics, err := GenerateProgram(program)
if err != nil {
return err
Expand Down Expand Up @@ -94,6 +95,14 @@ func GenerateProject(directory string, project workspace.Project, program *pcl.P
}
}

for name, content := range localDependencies {
outPath := path.Join(directory, "sdks", name+".yaml")
err := fsutil.CopyFile(outPath, content, nil)
if err != nil {
return fmt.Errorf("copy local dependency: %w", err)
}
}

return nil
}

Expand Down
136 changes: 136 additions & 0 deletions pkg/pulumiyaml/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/blang/semver"
"github.com/iancoleman/strcase"
"github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/ast"
"github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/packages"
"github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/syntax"
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
Expand Down Expand Up @@ -121,6 +122,30 @@ type pluginEntry struct {
func GetReferencedPlugins(tmpl *ast.TemplateDecl) ([]Plugin, syntax.Diagnostics) {
pluginMap := map[string]*pluginEntry{}

// Iterate over the package declarations
for _, pkg := range tmpl.Packages {
name := pkg.Name
version := pkg.Version
if pkg.Parameterization != nil {
name = pkg.Parameterization.Name
version = pkg.Parameterization.Version
}

if entry, found := pluginMap[name]; found {
if entry.version == "" {
entry.version = version
}
if entry.pluginDownloadURL == "" {
entry.pluginDownloadURL = pkg.DownloadURL
}
} else {
pluginMap[name] = &pluginEntry{
version: version,
pluginDownloadURL: pkg.DownloadURL,
}
}
}

acceptType := func(r *Runner, typeName string, version, pluginDownloadURL *ast.StringExpr) {
pkg := ResolvePkgName(typeName)
if entry, found := pluginMap[pkg]; found {
Expand Down Expand Up @@ -197,6 +222,117 @@ func GetReferencedPlugins(tmpl *ast.TemplateDecl) ([]Plugin, syntax.Diagnostics)
return plugins, nil
}

// GetReferencedPlugins returns the packages and (if provided) versions for each referenced package
// used in the program.
func GetReferencedPackages(tmpl *ast.TemplateDecl) ([]packages.PackageDecl, syntax.Diagnostics) {
packageMap := map[string]*packages.PackageDecl{}

// Iterate over the package declarations
for _, pkg := range tmpl.Packages {
name := pkg.Name
version := pkg.Version
if pkg.Parameterization != nil {
name = pkg.Parameterization.Name
version = pkg.Parameterization.Version
}

if entry, found := packageMap[name]; found {
if entry.Version == "" {
entry.Version = version
}
if entry.DownloadURL == "" {
entry.DownloadURL = pkg.DownloadURL
}
} else {
packageMap[name] = &pkg
}
}

acceptType := func(r *Runner, typeName string, version, pluginDownloadURL *ast.StringExpr) {
pkg := ResolvePkgName(typeName)
if entry, found := packageMap[pkg]; found {
if v := version.GetValue(); v != "" && entry.Version != v {
if entry.Version == "" {
entry.Version = v
} else {
r.sdiags.Extend(ast.ExprError(version, fmt.Sprintf("Package %v already declared with a conflicting version: %v", pkg, entry.Version), ""))
}
}
if url := pluginDownloadURL.GetValue(); url != "" && entry.DownloadURL != url {
if entry.DownloadURL == "" {
entry.DownloadURL = url
} else {
r.sdiags.Extend(ast.ExprError(pluginDownloadURL, fmt.Sprintf("Package %v already declared with a conflicting plugin download URL: %v", pkg, entry.DownloadURL), ""))
}
}
} else {
packageMap[pkg] = &packages.PackageDecl{
Name: pkg,
Version: version.GetValue(),
DownloadURL: pluginDownloadURL.GetValue(),
}
}
}

diags := newRunner(tmpl, nil).Run(walker{
VisitResource: func(r *Runner, node resourceNode) bool {
res := node.Value

if res.Type == nil {
r.sdiags.Extend(syntax.NodeError(node.Value.Syntax(), fmt.Sprintf("Resource declared without a 'type': %q", node.Key.Value), ""))
return true
}
acceptType(r, res.Type.Value, res.Options.Version, res.Options.PluginDownloadURL)

return true
},
VisitExpr: func(ctx *evalContext, expr ast.Expr) bool {
if expr, ok := expr.(*ast.InvokeExpr); ok {
if expr.Token == nil {
ctx.Runner.sdiags.Extend(syntax.NodeError(expr.Syntax(), "Invoke declared without a 'function' type", ""))
return true
}
acceptType(ctx.Runner, expr.Token.GetValue(), expr.CallOpts.Version, expr.CallOpts.PluginDownloadURL)
}
return true
},
})

if diags.HasErrors() {
return nil, diags
}

var packages []packages.PackageDecl
for _, pkg := range packageMap {
packages = append(packages, *pkg)
}

sort.Slice(packages, func(i, j int) bool {
pI, pJ := packages[i], packages[j]
if pI.Name != pJ.Name {
return pI.Name < pJ.Name
}
if pI.Version != pJ.Version {
return pI.Version < pJ.Version
}
if pI.Parameterization == nil && pJ.Parameterization == nil {
return pI.DownloadURL < pJ.DownloadURL
}
if pI.Parameterization == nil {
return true
}
if pJ.Parameterization == nil {
return false
}
if pI.Parameterization.Name != pJ.Parameterization.Name {
return pI.Parameterization.Name < pJ.Parameterization.Name
}
return pI.Parameterization.Version < pJ.Parameterization.Version
})

return packages, nil
}

func ResolvePkgName(typeString string) string {
typeParts := strings.Split(typeString, ":")

Expand Down
109 changes: 103 additions & 6 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -59,15 +60,17 @@ type yamlLanguageHost struct {
tracing string
compiler string

useRPCLoader bool
templateCache map[string]templateCacheEntry
}

func NewLanguageHost(engineAddress, tracing string, compiler string) pulumirpc.LanguageRuntimeServer {
func NewLanguageHost(engineAddress, tracing, compiler string, useRPCLoader bool) pulumirpc.LanguageRuntimeServer {
return &yamlLanguageHost{
engineAddress: engineAddress,
tracing: tracing,
compiler: compiler,

useRPCLoader: useRPCLoader,
templateCache: make(map[string]templateCacheEntry),
}
}
Expand Down Expand Up @@ -214,9 +217,19 @@ func (host *yamlLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest

// Because of async applies we may need the package loader to outlast the RunTemplate function. But by the
// time RunWithContext returns we should be done with all async work.
loader, err := pulumiyaml.NewPackageLoader(proj.Plugins)
if err != nil {
return &pulumirpc.RunResponse{Error: err.Error()}, nil
var loader pulumiyaml.PackageLoader
if host.useRPCLoader {
rpcLoader, err := schema.NewLoaderClient(req.LoaderTarget)
if err != nil {
return &pulumirpc.RunResponse{Error: err.Error()}, nil
}
loader = pulumiyaml.NewPackageLoaderFromSchemaLoader(
schema.NewCachedLoader(rpcLoader))
} else {
loader, err = pulumiyaml.NewPackageLoader(proj.Plugins)
if err != nil {
return &pulumirpc.RunResponse{Error: err.Error()}, nil
}
}
defer loader.Close()

Expand Down Expand Up @@ -254,7 +267,43 @@ func (host *yamlLanguageHost) InstallDependencies(req *pulumirpc.InstallDependen

// GetProgramDependencies returns the set of dependencies required by the program.
func (host *yamlLanguageHost) GetProgramDependencies(ctx context.Context, req *pulumirpc.GetProgramDependenciesRequest) (*pulumirpc.GetProgramDependenciesResponse, error) {
return &pulumirpc.GetProgramDependenciesResponse{}, nil
// YAML doesn't _really_ have dependencies per-se but we can list all the "packages" that are referenced
// in the program here. In the presesnce of parameterization this could differ to the set of plugins
// reported by GetRequiredPlugins.

template, diags, err := host.loadTemplate(req.Info.ProgramDirectory, nil)
if err != nil {
return nil, err
}
if diags.HasErrors() {
return nil, diags
}

pkgs, pluginDiags := pulumiyaml.GetReferencedPackages(template)
diags.Extend(pluginDiags...)
if diags.HasErrors() {
// We currently swallow the error to allow project config to evaluate
// Specifically, if one sets a config key via the CLI but not within the `config` block
// of their YAML program, it would error.
return &pulumirpc.GetProgramDependenciesResponse{}, nil
}
var dependencies []*pulumirpc.DependencyInfo
for _, pkg := range pkgs {
name := pkg.Name
version := pkg.Version
if pkg.Parameterization != nil {
name = pkg.Parameterization.Name
version = pkg.Parameterization.Version
}

dependencies = append(dependencies, &pulumirpc.DependencyInfo{
Name: name,
Version: version,
})
}
return &pulumirpc.GetProgramDependenciesResponse{
Dependencies: dependencies,
}, nil
}

// RuntimeOptionsPrompts returns a list of additional prompts to ask during `pulumi new`.
Expand Down Expand Up @@ -298,7 +347,7 @@ func (host *yamlLanguageHost) GenerateProject(
return nil, err
}

err = codegen.GenerateProject(req.TargetDirectory, project, program)
err = codegen.GenerateProject(req.TargetDirectory, project, program, req.LocalDependencies)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -451,3 +500,51 @@ func (host *yamlLanguageHost) GeneratePackage(ctx context.Context, req *pulumirp
Diagnostics: rpcDiagnostics,
}, nil
}

func (host *yamlLanguageHost) Pack(ctx context.Context, req *pulumirpc.PackRequest) (*pulumirpc.PackResponse, error) {
// Yaml "SDKs" are just files, we can just copy the file
if err := os.MkdirAll(req.DestinationDirectory, 0700); err != nil {
return nil, err
}

files, err := os.ReadDir(req.PackageDirectory)
if err != nil {
return nil, fmt.Errorf("reading package directory: %w", err)
}

copy := func(src, dst string) error {

Check warning on line 515 in pkg/server/server.go

View workflow job for this annotation

GitHub Actions / lint / lint

redefines-builtin-id: redefinition of the built-in function copy (revive)
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("opening %s: %w", src, err)
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("creating %s: %w", dst, err)
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return fmt.Errorf("copying %s to %s: %w", src, dst, err)
}
return nil
}

// We only expect one file in the package directory
var single string
for _, file := range files {
if single != "" {
return nil, fmt.Errorf("multiple files in package directory %s: %s and %s", req.PackageDirectory, single, file.Name())
}
single = file.Name()
}

src := filepath.Join(req.PackageDirectory, single)
dst := filepath.Join(req.DestinationDirectory, single)
if err := copy(src, dst); err != nil {
return nil, fmt.Errorf("copying %s to %s: %w", src, dst, err)
}

return &pulumirpc.PackResponse{
ArtifactPath: dst,
}, nil
}

0 comments on commit a5563a1

Please sign in to comment.