Skip to content

Commit

Permalink
feat: Pass PULUMI_STACK, PULUMI_PROJECT, PULUMI_ORGANIZATION an…
Browse files Browse the repository at this point in the history
…d `PULUMI_CONFIG` as environment variable to compiler process
  • Loading branch information
phramz committed Jun 4, 2024
1 parent 67e7225 commit e39a1e6
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
### Improvements

- Pass `PULUMI_STACK`, `PULUMI_ORGANIZATION`, `PULUMI_PROJECT` and `PULUMI_CONFIG` as environment variable to compiler process.

### Bug Fixes
2 changes: 1 addition & 1 deletion pkg/pulumiyaml/codegen/eject.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func LoadTemplate(dir string) (*workspace.Project, *ast.TemplateDecl, hcl.Diagno
if !ok {
return nil, nil, nil, fmt.Errorf("compiler option must be a string, got %v", reflect.TypeOf(compilerOpt))
}
t, diags, err = pulumiyaml.LoadFromCompiler(compiler, main)
t, diags, err = pulumiyaml.LoadFromCompiler(compiler, main, nil)
} else {
t, diags, err = pulumiyaml.LoadDir(main)
}
Expand Down
22 changes: 21 additions & 1 deletion pkg/pulumiyaml/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func Load() (*ast.TemplateDecl, syntax.Diagnostics, error) {
return LoadDir(".")
}

func LoadFromCompiler(compiler string, workingDirectory string) (*ast.TemplateDecl, syntax.Diagnostics, error) {
func LoadFromCompiler(compiler string, workingDirectory string, env []string) (*ast.TemplateDecl, syntax.Diagnostics, error) {
var diags syntax.Diagnostics
var stdout bytes.Buffer
var stderr bytes.Buffer
Expand All @@ -60,6 +60,11 @@ func LoadFromCompiler(compiler string, workingDirectory string) (*ast.TemplateDe
cmd.Dir = workingDirectory
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(os.Environ(), env...)
for _, duplicate := range conflictingEnvVars(cmd.Env) {
diags = append(diags, syntax.Warning(nil, fmt.Sprintf("compiler %v conflicting environment variable %v will be overridden", name, duplicate), ""))
}

err = cmd.Run()
if err != nil {
return nil, nil, fmt.Errorf("error running compiler %v: %v, stderr follows: %v", name, err, stderr.String())
Expand All @@ -74,6 +79,21 @@ func LoadFromCompiler(compiler string, workingDirectory string) (*ast.TemplateDe
return template, tdiags, err
}

func conflictingEnvVars(env []string) []string {
envMap := make(map[string]uint)
var duplicates []string
for _, e := range env {
key := strings.Split(e, "=")[0]
if cnt, exists := envMap[key]; exists && cnt <= 1 {
duplicates = append(duplicates, key)
}

envMap[key]++
}

return duplicates
}

// Load a template from the current working directory.
func LoadDir(cwd string) (*ast.TemplateDecl, syntax.Diagnostics, error) {
// Read in the template file - search first for Main.json, then Main.yaml, then Pulumi.yaml.
Expand Down
47 changes: 47 additions & 0 deletions pkg/pulumiyaml/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2412,3 +2412,50 @@ func TestUnknownsDuringPreviewNotUpdate(t *testing.T) {
assert.NoError(t, runProgram(true))
assert.Error(t, runProgram(false))
}

func TestConflictingEnvVarsNoDuplicates(t *testing.T) {
t.Parallel()

env := []string{"FOO=bar", "BAZ=qux"}
conflicts := conflictingEnvVars(env)
assert.Empty(t, conflicts)
}

func TestConflictingEnvVarsWithDuplicates(t *testing.T) {
t.Parallel()

env := []string{"FOO=bar", "FOO=baz"}
conflicts := conflictingEnvVars(env)
assert.Equal(t, []string{"FOO"}, conflicts)
}

func TestConflictingEnvVarsEmptyEnv(t *testing.T) {
t.Parallel()

env := []string{}
conflicts := conflictingEnvVars(env)
assert.Empty(t, conflicts)
}

func TestConflictingEnvVarsNilEnv(t *testing.T) {
t.Parallel()

conflicts := conflictingEnvVars(nil)
assert.Empty(t, conflicts)
}

func TestConflictingEnvVarsInvalidFormat(t *testing.T) {
t.Parallel()

env := []string{"FOO", "BAR=qux"}
conflicts := conflictingEnvVars(env)
assert.Empty(t, conflicts)
}

func TestConflictingEnvVarsMultipleDuplicates(t *testing.T) {
t.Parallel()

env := []string{"FOO=bar", "FOO=baz", "BAR=qux", "BAR=quux", "FOO=foobar"}
conflicts := conflictingEnvVars(env)
assert.ElementsMatch(t, []string{"FOO", "BAR"}, conflicts)
}
28 changes: 21 additions & 7 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ package server

import (
"context"
"os"

"encoding/json"
"fmt"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v3/go/common/version"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
"google.golang.org/protobuf/types/known/emptypb"
"os"

"github.com/pulumi/pulumi-yaml/pkg/pulumiyaml"
"github.com/pulumi/pulumi-yaml/pkg/pulumiyaml/ast"
Expand All @@ -53,8 +54,8 @@ func NewLanguageHost(engineAddress, tracing string, compiler string) pulumirpc.L
}
}

func (host *yamlLanguageHost) loadTemplate() (*ast.TemplateDecl, syntax.Diagnostics, error) {
if host.template != nil {
func (host *yamlLanguageHost) loadTemplate(compilerEnv []string) (*ast.TemplateDecl, syntax.Diagnostics, error) {
if host.template != nil && host.compiler == "" {
return host.template, host.diags, nil
}

Expand All @@ -64,7 +65,7 @@ func (host *yamlLanguageHost) loadTemplate() (*ast.TemplateDecl, syntax.Diagnost
if host.compiler == "" {
template, diags, err = pulumiyaml.Load()
} else {
template, diags, err = pulumiyaml.LoadFromCompiler(host.compiler, "")
template, diags, err = pulumiyaml.LoadFromCompiler(host.compiler, "", compilerEnv)
}
if err != nil {
return nil, diags, err
Expand All @@ -82,7 +83,7 @@ func (host *yamlLanguageHost) loadTemplate() (*ast.TemplateDecl, syntax.Diagnost
func (host *yamlLanguageHost) GetRequiredPlugins(ctx context.Context,
req *pulumirpc.GetRequiredPluginsRequest,
) (*pulumirpc.GetRequiredPluginsResponse, error) {
template, diags, err := host.loadTemplate()
template, diags, err := host.loadTemplate(nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -121,7 +122,20 @@ func (host *yamlLanguageHost) Run(ctx context.Context, req *pulumirpc.RunRequest
}
}

template, diags, err := host.loadTemplate()
configValue := req.GetConfig()
jsonConfigValue, err := json.Marshal(configValue)
if err != nil {
return nil, err
}

compilerEnv := []string{
fmt.Sprintf(`PULUMI_STACK=%s`, req.GetStack()),
fmt.Sprintf(`PULUMI_ORGANIZATION=%s`, req.GetOrganization()),
fmt.Sprintf(`PULUMI_PROJECT=%s`, req.GetProject()),
fmt.Sprintf(`PULUMI_CONFIG=%s`, jsonConfigValue),
}

template, diags, err := host.loadTemplate(compilerEnv)
if err != nil {
return &pulumirpc.RunResponse{Error: err.Error()}, nil
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,22 @@ func TestProjectConfigWithSecretDecrypted(t *testing.T) {
}
integration.ProgramTest(t, &testOptions)
}

//nolint:paralleltest // uses parallel programtest
func TestEnvVarsPassedToExecCommand(t *testing.T) {
testOptions := integration.ProgramTestOptions{
Dir: filepath.Join(getCwd(t), "testdata", "env-vars"),
Env: []string{"TEST_ENV_VAR=foobar"},
PrepareProject: prepareYamlProject,
StackName: "dev",
SecretsProvider: "default",
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.Equal(t, "foobar", stack.Outputs["TEST_ENV_VAR"])
assert.Equal(t, `dev`, stack.Outputs["PULUMI_STACK"])
assert.Equal(t, `project-env-vars`, stack.Outputs["PULUMI_PROJECT"])
assert.Equal(t, `organization`, stack.Outputs["PULUMI_ORGANIZATION"])
assert.EqualValues(t, map[string]interface{}{"project-env-vars:foo": "hello world"}, stack.Outputs["PULUMI_CONFIG"])
},
}
integration.ProgramTest(t, &testOptions)
}
16 changes: 16 additions & 0 deletions pkg/tests/testdata/env-vars/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: project-env-vars
runtime:
name: yaml
options:
compiler: |
sh -c "cat Pulumi.yaml;
echo \"\";
echo \"outputs:\";
echo \" TEST_ENV_VAR: $TEST_ENV_VAR\";
echo \" PULUMI_STACK: $PULUMI_STACK\";
echo \" PULUMI_PROJECT: $PULUMI_PROJECT\";
echo \" PULUMI_ORGANIZATION: $PULUMI_ORGANIZATION\";
echo \" PULUMI_CONFIG: $PULUMI_CONFIG\";
echo \"\";"
config:
foo: "hello world"

0 comments on commit e39a1e6

Please sign in to comment.