pulumi/tests/integration/integration_go_test.go

1186 lines
38 KiB
Go
Raw Permalink Normal View History

// Copyright 2016-2022, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build (go || all) && !xplatform_acceptance
package ints
import (
"bytes"
"context"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/grapl-security/pulumi-hcp/sdk/go/hcp"
"github.com/pulumi/appdash"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pulumi/pulumi/pkg/v3/testing/integration"
"github.com/pulumi/pulumi/sdk/v3/go/auto"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
2022-09-14 21:48:09 +00:00
// This checks that the buildTarget option for Pulumi Go programs does build a binary.
func TestBuildTarget(t *testing.T) {
t.Parallel()
e := ptesting.NewEnvironment(t)
defer e.DeleteIfNotFailed()
2022-09-14 21:48:09 +00:00
e.ImportDirectory(filepath.Join("go", "go-build-target"))
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
e.RunCommand("pulumi", "stack", "init", "go-build-target-test-stack")
e.RunCommand("pulumi", "stack", "select", "go-build-target-test-stack")
e.RunCommand("pulumi", "preview")
_, err := os.Stat(filepath.Join(e.RootPath, "a.out"))
assert.NoError(t, err)
}
// This checks that the Exit Status artifact from Go Run is not being produced
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestNoEmitExitStatus(t *testing.T) {
stderr := &bytes.Buffer{}
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("go", "go-exit-5"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Stderr: stderr,
ExpectFailure: true,
Quick: true,
SkipRefresh: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// ensure exit status is not emitted by the program
assert.NotContains(t, stderr.String(), "exit status")
},
})
}
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestPanickingProgram(t *testing.T) {
var stderr bytes.Buffer
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("go", "program-panic"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Stderr: &stderr,
ExpectFailure: true,
Quick: true,
SkipRefresh: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.Contains(t, stderr.String(), "panic: great sadness\n")
},
})
}
func TestPanickingComponentConfigure(t *testing.T) {
t.Parallel()
var (
testDir = filepath.Join("go", "component-configure-panic")
componentDir = "testcomponent-go"
)
runComponentSetup(t, testDir)
var stderr bytes.Buffer
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("go", "component-configure-panic", "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
LocalProviders: []integration.LocalDependency{
{
Package: "testcomponent",
Path: filepath.Join(testDir, componentDir),
},
},
Stderr: &stderr,
ExpectFailure: true,
Quick: true,
SkipRefresh: true,
NoParallel: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.Contains(t, stderr.String(), "panic: great sadness\n")
},
})
}
// This checks that error logs are not being emitted twice
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestNoLogError(t *testing.T) {
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("go", "go-exit-error"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Stdout: stdout,
Stderr: stderr,
Quick: true,
ExpectFailure: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
errorCount := strings.Count(stderr.String()+stdout.String(), " error: ")
// ensure ` error: ` is only being shown once by the program
assert.Equal(t, 1, errorCount)
},
})
}
// This checks that the PULUMI_GO_USE_RUN=true flag is triggering go run by checking the `exit status`
// string is being emitted. This is a temporary fallback measure in case it breaks users and should
// not be assumed to be stable.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestGoRunEnvFlag(t *testing.T) {
stderr := &bytes.Buffer{}
integration.ProgramTest(t, &integration.ProgramTestOptions{
Env: []string{"PULUMI_GO_USE_RUN=true"},
Dir: filepath.Join("go", "go-exit-5"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Stderr: stderr,
ExpectFailure: true,
Quick: true,
SkipRefresh: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// ensure exit status IS emitted by the program as it indicates `go run` was used
assert.Contains(t, stderr.String(), "exit status")
},
})
}
// TestEmptyGoRun exercises the 'go run' invocation path that doesn't require an explicit build step.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestEmptyGoRun(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("empty", "gorun"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Quick: true,
})
}
// TestEmptyGoRunMain exercises the 'go run' invocation path with a 'main' entrypoint specified in Pulumi.yml
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestEmptyGoRunMain(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("empty", "gorun_main"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Quick: true,
})
}
// TestPrintfGo tests that we capture stdout and stderr streams properly, even when the last line lacks an \n.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestPrintfGo(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("printf", "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Quick: true,
ExtraRuntimeValidation: printfTestValidation,
})
}
// Tests basic configuration from the perspective of a Pulumi Go program.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestConfigBasicGo(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("config_basic", "go"),
Dependencies: []string{"github.com/pulumi/pulumi/sdk/v3"},
Quick: true,
Config: map[string]string{
"aConfigValue": "this value is a value",
},
Secrets: map[string]string{
"bEncryptedSecret": "this super secret is encrypted",
},
OrderedConfig: []integration.ConfigValue{
{Key: "outer.inner", Value: "value", Path: true},
{Key: "names[0]", Value: "a", Path: true},
{Key: "names[1]", Value: "b", Path: true},
{Key: "names[2]", Value: "c", Path: true},
{Key: "names[3]", Value: "super secret name", Path: true, Secret: true},
{Key: "servers[0].port", Value: "80", Path: true},
{Key: "servers[0].host", Value: "example", Path: true},
{Key: "a.b[0].c", Value: "true", Path: true},
{Key: "a.b[1].c", Value: "false", Path: true},
{Key: "tokens[0]", Value: "shh", Path: true, Secret: true},
{Key: "foo.bar", Value: "don't tell", Path: true, Secret: true},
},
})
}
// Tests configuration error from the perspective of a Pulumi Go program.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestConfigMissingGo(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("config_missing", "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Quick: true,
ExpectFailure: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.NotEmpty(t, stackInfo.Events)
text1 := "Missing required configuration variable 'config_missing_go:notFound'"
text2 := "\tplease set a value using the command `pulumi config set --secret config_missing_go:notFound <value>`"
var found1, found2 bool
for _, event := range stackInfo.Events {
if event.DiagnosticEvent != nil && strings.Contains(event.DiagnosticEvent.Message, text1) {
found1 = true
}
if event.DiagnosticEvent != nil && strings.Contains(event.DiagnosticEvent.Message, text2) {
found2 = true
}
}
assert.True(t, found1, "expected error %q", text1)
assert.True(t, found2, "expected error %q", text2)
},
})
}
// Tests that accessing config secrets using non-secret APIs results in warnings being logged.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestConfigSecretsWarnGo(t *testing.T) {
// TODO[pulumi/pulumi#7127]: Re-enabled the warning.
t.Skip("Temporarily skipping test until we've re-enabled the warning - pulumi/pulumi#7127")
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("config_secrets_warn", "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Quick: true,
Config: map[string]string{
"plainstr1": "1",
"plainstr2": "2",
"plainstr3": "3",
"plainstr4": "4",
"plainstr5": "5",
"plainstr6": "6",
"plainstr7": "7",
"plainstr8": "8",
"plainstr9": "9",
"plainstr10": "10",
"plainstr11": "11",
"plainstr12": "12",
"plainbool1": "true",
"plainbool2": "true",
"plainbool3": "true",
"plainbool4": "true",
"plainbool5": "true",
"plainbool6": "true",
"plainbool7": "true",
"plainbool8": "true",
"plainbool9": "true",
"plainbool10": "true",
"plainbool11": "true",
"plainbool12": "true",
"plainint1": "1",
"plainint2": "2",
"plainint3": "3",
"plainint4": "4",
"plainint5": "5",
"plainint6": "6",
"plainint7": "7",
"plainint8": "8",
"plainint9": "9",
"plainint10": "10",
"plainint11": "11",
"plainint12": "12",
"plainfloat1": "1.1",
"plainfloat2": "2.2",
"plainfloat3": "3.3",
"plainfloat4": "4.4",
"plainfloat5": "5.5",
"plainfloat6": "6.6",
"plainfloat7": "7.7",
"plainfloat8": "8.8",
"plainfloat9": "9.9",
"plainfloat10": "10.1",
"plainfloat11": "11.11",
"plainfloat12": "12.12",
"plainobj1": "{}",
"plainobj2": "{}",
"plainobj3": "{}",
"plainobj4": "{}",
"plainobj5": "{}",
"plainobj6": "{}",
"plainobj7": "{}",
"plainobj8": "{}",
"plainobj9": "{}",
"plainobj10": "{}",
"plainobj11": "{}",
"plainobj12": "{}",
},
Secrets: map[string]string{
"str1": "1",
"str2": "2",
"str3": "3",
"str4": "4",
"str5": "5",
"str6": "6",
"str7": "7",
"str8": "8",
"str9": "9",
"str10": "10",
"str11": "11",
"str12": "12",
"bool1": "true",
"bool2": "true",
"bool3": "true",
"bool4": "true",
"bool5": "true",
"bool6": "true",
"bool7": "true",
"bool8": "true",
"bool9": "true",
"bool10": "true",
"bool11": "true",
"bool12": "true",
"int1": "1",
"int2": "2",
"int3": "3",
"int4": "4",
"int5": "5",
"int6": "6",
"int7": "7",
"int8": "8",
"int9": "9",
"int10": "10",
"int11": "11",
"int12": "12",
"float1": "1.1",
"float2": "2.2",
"float3": "3.3",
"float4": "4.4",
"float5": "5.5",
"float6": "6.6",
"float7": "7.7",
"float8": "8.8",
"float9": "9.9",
"float10": "10.1",
"float11": "11.11",
"float12": "12.12",
"obj1": "{}",
"obj2": "{}",
"obj3": "{}",
"obj4": "{}",
"obj5": "{}",
"obj6": "{}",
"obj7": "{}",
"obj8": "{}",
"obj9": "{}",
"obj10": "{}",
"obj11": "{}",
"obj12": "{}",
},
OrderedConfig: []integration.ConfigValue{
{Key: "parent1.foo", Value: "plain1", Path: true},
{Key: "parent1.bar", Value: "secret1", Path: true, Secret: true},
{Key: "parent2.foo", Value: "plain2", Path: true},
{Key: "parent2.bar", Value: "secret2", Path: true, Secret: true},
{Key: "parent3.foo", Value: "plain2", Path: true},
{Key: "parent3.bar", Value: "secret2", Path: true, Secret: true},
{Key: "names1[0]", Value: "plain1", Path: true},
{Key: "names1[1]", Value: "secret1", Path: true, Secret: true},
{Key: "names2[0]", Value: "plain2", Path: true},
{Key: "names2[1]", Value: "secret2", Path: true, Secret: true},
{Key: "names3[0]", Value: "plain2", Path: true},
{Key: "names3[1]", Value: "secret2", Path: true, Secret: true},
},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.NotEmpty(t, stackInfo.Events)
//nolint:lll
expectedWarnings := []string{
"Configuration 'config_secrets_go:str1' value is a secret; use `GetSecret` instead of `Get`",
"Configuration 'config_secrets_go:str2' value is a secret; use `RequireSecret` instead of `Require`",
"Configuration 'config_secrets_go:str3' value is a secret; use `TrySecret` instead of `Try`",
"Configuration 'config_secrets_go:str7' value is a secret; use `GetSecret` instead of `Get`",
"Configuration 'config_secrets_go:str8' value is a secret; use `RequireSecret` instead of `Require`",
"Configuration 'config_secrets_go:str9' value is a secret; use `TrySecret` instead of `Try`",
"Configuration 'config_secrets_go:bool1' value is a secret; use `GetSecretBool` instead of `GetBool`",
"Configuration 'config_secrets_go:bool2' value is a secret; use `RequireSecretBool` instead of `RequireBool`",
"Configuration 'config_secrets_go:bool3' value is a secret; use `TrySecretBool` instead of `TryBool`",
"Configuration 'config_secrets_go:bool7' value is a secret; use `GetSecretBool` instead of `GetBool`",
"Configuration 'config_secrets_go:bool8' value is a secret; use `RequireSecretBool` instead of `RequireBool`",
"Configuration 'config_secrets_go:bool9' value is a secret; use `TrySecretBool` instead of `TryBool`",
"Configuration 'config_secrets_go:int1' value is a secret; use `GetSecretInt` instead of `GetInt`",
"Configuration 'config_secrets_go:int2' value is a secret; use `RequireSecretInt` instead of `RequireInt`",
"Configuration 'config_secrets_go:int3' value is a secret; use `TrySecretInt` instead of `TryInt`",
"Configuration 'config_secrets_go:int7' value is a secret; use `GetSecretInt` instead of `GetInt`",
"Configuration 'config_secrets_go:int8' value is a secret; use `RequireSecretInt` instead of `RequireInt`",
"Configuration 'config_secrets_go:int9' value is a secret; use `TrySecretInt` instead of `TryInt`",
"Configuration 'config_secrets_go:float1' value is a secret; use `GetSecretFloat64` instead of `GetFloat64`",
"Configuration 'config_secrets_go:float2' value is a secret; use `RequireSecretFloat64` instead of `RequireFloat64`",
"Configuration 'config_secrets_go:float3' value is a secret; use `TrySecretFloat64` instead of `TryFloat64`",
"Configuration 'config_secrets_go:float7' value is a secret; use `GetSecretFloat64` instead of `GetFloat64`",
"Configuration 'config_secrets_go:float8' value is a secret; use `RequireSecretFloat64` instead of `RequireFloat64`",
"Configuration 'config_secrets_go:float9' value is a secret; use `TrySecretFloat64` instead of `TryFloat64`",
"Configuration 'config_secrets_go:obj1' value is a secret; use `GetSecretObject` instead of `GetObject`",
"Configuration 'config_secrets_go:obj2' value is a secret; use `RequireSecretObject` instead of `RequireObject`",
"Configuration 'config_secrets_go:obj3' value is a secret; use `TrySecretObject` instead of `TryObject`",
"Configuration 'config_secrets_go:obj7' value is a secret; use `GetSecretObject` instead of `GetObject`",
"Configuration 'config_secrets_go:obj8' value is a secret; use `RequireSecretObject` instead of `RequireObject`",
"Configuration 'config_secrets_go:obj9' value is a secret; use `TrySecretObject` instead of `TryObject`",
"Configuration 'config_secrets_go:parent1' value is a secret; use `GetSecretObject` instead of `GetObject`",
"Configuration 'config_secrets_go:parent2' value is a secret; use `RequireSecretObject` instead of `RequireObject`",
"Configuration 'config_secrets_go:parent3' value is a secret; use `TrySecretObject` instead of `TryObject`",
"Configuration 'config_secrets_go:names1' value is a secret; use `GetSecretObject` instead of `GetObject`",
"Configuration 'config_secrets_go:names2' value is a secret; use `RequireSecretObject` instead of `RequireObject`",
"Configuration 'config_secrets_go:names3' value is a secret; use `TrySecretObject` instead of `TryObject`",
}
for _, warning := range expectedWarnings {
var found bool
for _, event := range stackInfo.Events {
if event.DiagnosticEvent != nil && event.DiagnosticEvent.Severity == "warning" &&
strings.Contains(event.DiagnosticEvent.Message, warning) {
found = true
break
}
}
assert.True(t, found, "expected warning %q", warning)
}
// These keys should not be in any warning messages.
unexpectedWarnings := []string{
"plainstr1",
"plainstr2",
"plainstr3",
"plainstr4",
"plainstr5",
"plainstr6",
"plainstr7",
"plainstr8",
"plainstr9",
"plainstr10",
"plainstr11",
"plainstr12",
"plainbool1",
"plainbool2",
"plainbool3",
"plainbool4",
"plainbool5",
"plainbool6",
"plainbool7",
"plainbool8",
"plainbool9",
"plainbool10",
"plainbool11",
"plainbool12",
"plainint1",
"plainint2",
"plainint3",
"plainint4",
"plainint5",
"plainint6",
"plainint7",
"plainint8",
"plainint9",
"plainint10",
"plainint11",
"plainint12",
"plainfloat1",
"plainfloat2",
"plainfloat3",
"plainfloat4",
"plainfloat5",
"plainfloat6",
"plainfloat7",
"plainfloat8",
"plainfloat9",
"plainfloat10",
"plainfloat11",
"plainfloat12",
"plainobj1",
"plainobj2",
"plainobj3",
"plainobj4",
"plainobj5",
"plainobj6",
"plainobj7",
"plainobj8",
"plainobj9",
"plainobj10",
"plainobj11",
"plainobj12",
}
for _, warning := range unexpectedWarnings {
for _, event := range stackInfo.Events {
if event.DiagnosticEvent != nil {
assert.NotContains(t, event.DiagnosticEvent.Message, warning)
}
}
}
},
})
}
// Tests a resource with a large (>4mb) string prop in Go
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestLargeResourceGo(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Dir: filepath.Join("large_resource", "go"),
})
}
// Test remote component construction with a child resource that takes a long time to be created, ensuring it's created.
func TestConstructSlowGo(t *testing.T) {
t.Parallel()
localProvider := testComponentSlowLocalProvider(t)
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
// module to run `yarn install && yarn link @pulumi/pulumi` in the Go program's directory, allowing
// the Node.js dynamic provider plugin to load.
// When the underlying issue has been fixed, the use of this environment variable inside the integration
// test module should be removed.
const testYarnLinkPulumiEnv = "PULUMI_TEST_YARN_LINK_PULUMI=true"
2022-09-14 02:24:22 +00:00
testDir := "construct_component_slow"
runComponentSetup(t, testDir)
opts := &integration.ProgramTestOptions{
Env: []string{testYarnLinkPulumiEnv},
2022-09-14 02:24:22 +00:00
Dir: filepath.Join(testDir, "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
LocalProviders: []integration.LocalDependency{localProvider},
Quick: true,
NoParallel: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.NotNil(t, stackInfo.Deployment)
if assert.Equal(t, 5, len(stackInfo.Deployment.Resources)) {
stackRes := stackInfo.Deployment.Resources[0]
assert.NotNil(t, stackRes)
assert.Equal(t, resource.RootStackType, stackRes.Type)
assert.Equal(t, "", string(stackRes.Parent))
}
},
}
integration.ProgramTest(t, opts)
}
// Test remote component construction with prompt inputs.
func TestConstructPlainGo(t *testing.T) {
t.Parallel()
2022-09-14 02:24:22 +00:00
testDir := "construct_component_plain"
runComponentSetup(t, testDir)
tests := []struct {
componentDir string
expectedResourceCount int
env []string
}{
{
componentDir: "testcomponent",
expectedResourceCount: 9,
// TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components.
// Until we've addressed this, set PULUMI_TEST_YARN_LINK_PULUMI, which tells the integration test
// module to run `yarn install && yarn link @pulumi/pulumi` in the Go program's directory, allowing
// the Node.js dynamic provider plugin to load.
// When the underlying issue has been fixed, the use of this environment variable inside the integration
// test module should be removed.
env: []string{"PULUMI_TEST_YARN_LINK_PULUMI=true"},
},
{
componentDir: "testcomponent-python",
expectedResourceCount: 9,
},
{
componentDir: "testcomponent-go",
expectedResourceCount: 8, // One less because no dynamic provider.
},
}
//nolint:paralleltest // ProgramTest calls t.Parallel()
for _, test := range tests {
test := test
t.Run(test.componentDir, func(t *testing.T) {
all: Reformat with gofumpt Per team discussion, switching to gofumpt. [gofumpt][1] is an alternative, stricter alternative to gofmt. It addresses other stylistic concerns that gofmt doesn't yet cover. [1]: https://github.com/mvdan/gofumpt See the full list of [Added rules][2], but it includes: - Dropping empty lines around function bodies - Dropping unnecessary variable grouping when there's only one variable - Ensuring an empty line between multi-line functions - simplification (`-s` in gofmt) is always enabled - Ensuring multi-line function signatures end with `) {` on a separate line. [2]: https://github.com/mvdan/gofumpt#Added-rules gofumpt is stricter, but there's no lock-in. All gofumpt output is valid gofmt output, so if we decide we don't like it, it's easy to switch back without any code changes. gofumpt support is built into the tooling we use for development so this won't change development workflows. - golangci-lint includes a gofumpt check (enabled in this PR) - gopls, the LSP for Go, includes a gofumpt option (see [installation instrutions][3]) [3]: https://github.com/mvdan/gofumpt#installation This change was generated by running: ```bash gofumpt -w $(rg --files -g '*.go' | rg -v testdata | rg -v compilation_error) ``` The following files were manually tweaked afterwards: - pkg/cmd/pulumi/stack_change_secrets_provider.go: one of the lines overflowed and had comments in an inconvenient place - pkg/cmd/pulumi/destroy.go: `var x T = y` where `T` wasn't necessary - pkg/cmd/pulumi/policy_new.go: long line because of error message - pkg/backend/snapshot_test.go: long line trying to assign three variables in the same assignment I have included mention of gofumpt in the CONTRIBUTING.md.
2023-03-03 16:36:39 +00:00
localProviders := []integration.LocalDependency{
{Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir)},
}
integration.ProgramTest(t,
optsForConstructPlainGo(t, test.expectedResourceCount, localProviders, test.env...))
})
}
}
func optsForConstructPlainGo(
t *testing.T, expectedResourceCount int, localProviders []integration.LocalDependency, env ...string,
) *integration.ProgramTestOptions {
return &integration.ProgramTestOptions{
Env: env,
Dir: filepath.Join("construct_component_plain", "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
LocalProviders: localProviders,
Quick: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, expectedResourceCount, len(stackInfo.Deployment.Resources))
},
}
}
// Test remote component inputs properly handle unknowns.
func TestConstructUnknownGo(t *testing.T) {
t.Parallel()
testConstructUnknown(t, "go", "github.com/pulumi/pulumi/sdk/v3")
}
func TestConstructMethodsGo(t *testing.T) {
t.Parallel()
2022-09-14 02:24:22 +00:00
testDir := "construct_component_methods"
runComponentSetup(t, testDir)
tests := []struct {
componentDir string
env []string
}{
{
componentDir: "testcomponent",
},
{
componentDir: "testcomponent-python",
},
{
componentDir: "testcomponent-go",
},
}
//nolint:paralleltest // ProgramTest calls t.Parallel()
for _, test := range tests {
test := test
t.Run(test.componentDir, func(t *testing.T) {
localProvider := integration.LocalDependency{
Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir),
}
integration.ProgramTest(t, &integration.ProgramTestOptions{
Env: test.env,
2022-09-14 02:24:22 +00:00
Dir: filepath.Join(testDir, "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
LocalProviders: []integration.LocalDependency{localProvider},
Quick: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.Equal(t, "Hello World, Alice!", stackInfo.Outputs["message"])
// TODO[pulumi/pulumi#12471]: Only the Go SDK has been fixed such that rehydrated
// components are kept as dependencies. So only check this for the provider written
// in Go. Once the other SDKs are fixed, we can test the other providers as well.
if test.componentDir == "testcomponent-go" {
var componentURN string
for _, res := range stackInfo.Deployment.Resources {
if res.URN.Name() == "component" {
componentURN = string(res.URN)
}
}
assert.Contains(t, stackInfo.Outputs["messagedeps"], componentURN)
}
},
})
})
}
}
func TestConstructMethodsUnknownGo(t *testing.T) {
t.Parallel()
testConstructMethodsUnknown(t, "go", "github.com/pulumi/pulumi/sdk/v3")
}
func TestConstructMethodsResourcesGo(t *testing.T) {
t.Parallel()
testConstructMethodsResources(t, "go", "github.com/pulumi/pulumi/sdk/v3")
}
func TestConstructMethodsErrorsGo(t *testing.T) {
t.Parallel()
testConstructMethodsErrors(t, "go", "github.com/pulumi/pulumi/sdk/v3")
}
[sdk/nodejs] Fix provider for resource methods (#13796) The `Resource` class in the Node.js SDK has the following internal property: ```typescript /** @internal */ readonly __prov?: ProviderResource; ``` When a resource is created, the provider specified for the resource is stored in this property. If it is set, it is passed along in the `Call` request when a method is called on the resource. Prior to #13282, the property was only set for custom resources in `Resource`'s constructor: ```typescript this.__prov = custom ? opts.provider : undefined; ``` With #13282, it was changed to also store the value for remote components: ```diff - this.__prov = custom ? opts.provider : undefined; + this.__prov = custom || remote ? opts.provider : undefined; ``` This regressed the behavior when calling a method on a remote component that had an explicit provider that wasn't the component provider, but some other provider (e.g. AWS provider) specified as: ```typescript const component = new MyRemoteComponent("comp", { }, { provider: awsProvider }); ``` The `awsProvider` was being stored in `Resource.__prov`, and when making the method call on the resource, it would try to invoke `Call` on the AWS provider, rather than calling the remote component provider's `Call`, which resulted in an error. Note that specifying the AWS provider using the more verbose `providers: [awsProvider]` works around the issue. The fix is to only set `__prov` if the provider's package is the same as the resource's package. Otherwise, don't set it, because the user is specifying a provider with the `provider: awsProvider` syntax as shorthand for `providers: [awsProvider]`. Fixes #13777
2023-08-30 14:49:53 +00:00
func TestConstructMethodsProviderGo(t *testing.T) {
t.Parallel()
[sdk/nodejs] Fix provider for resource methods (#13796) The `Resource` class in the Node.js SDK has the following internal property: ```typescript /** @internal */ readonly __prov?: ProviderResource; ``` When a resource is created, the provider specified for the resource is stored in this property. If it is set, it is passed along in the `Call` request when a method is called on the resource. Prior to #13282, the property was only set for custom resources in `Resource`'s constructor: ```typescript this.__prov = custom ? opts.provider : undefined; ``` With #13282, it was changed to also store the value for remote components: ```diff - this.__prov = custom ? opts.provider : undefined; + this.__prov = custom || remote ? opts.provider : undefined; ``` This regressed the behavior when calling a method on a remote component that had an explicit provider that wasn't the component provider, but some other provider (e.g. AWS provider) specified as: ```typescript const component = new MyRemoteComponent("comp", { }, { provider: awsProvider }); ``` The `awsProvider` was being stored in `Resource.__prov`, and when making the method call on the resource, it would try to invoke `Call` on the AWS provider, rather than calling the remote component provider's `Call`, which resulted in an error. Note that specifying the AWS provider using the more verbose `providers: [awsProvider]` works around the issue. The fix is to only set `__prov` if the provider's package is the same as the resource's package. Otherwise, don't set it, because the user is specifying a provider with the `provider: awsProvider` syntax as shorthand for `providers: [awsProvider]`. Fixes #13777
2023-08-30 14:49:53 +00:00
testConstructMethodsProvider(t, "go", "github.com/pulumi/pulumi/sdk/v3")
}
func TestConstructProviderGo(t *testing.T) {
t.Parallel()
const testDir = "construct_component_provider"
2022-09-14 02:24:22 +00:00
runComponentSetup(t, testDir)
tests := []struct {
componentDir string
}{
{
componentDir: "testcomponent",
},
{
componentDir: "testcomponent-python",
},
{
componentDir: "testcomponent-go",
},
}
//nolint:paralleltest // ProgramTest calls t.Parallel()
for _, test := range tests {
test := test
t.Run(test.componentDir, func(t *testing.T) {
localProvider := integration.LocalDependency{
Package: "testcomponent", Path: filepath.Join(testDir, test.componentDir),
}
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join(testDir, "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
LocalProviders: []integration.LocalDependency{localProvider},
Quick: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.Equal(t, "hello world", stackInfo.Outputs["message"])
},
})
})
}
}
//nolint:paralleltest // Sets env vars
func TestGetResourceGo(t *testing.T) {
// This uses the random plugin so needs to be able to download it
t.Setenv("PULUMI_DISABLE_AUTOMATIC_PLUGIN_ACQUISITION", "false")
integration.ProgramTest(t, &integration.ProgramTestOptions{
NoParallel: true,
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Dir: filepath.Join("get_resource", "go"),
AllowEmptyPreviewChanges: true,
Secrets: map[string]string{
"bar": "this super secret is encrypted",
},
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.NotNil(t, stack.Outputs)
assert.Equal(t, float64(2), stack.Outputs["getPetLength"])
out, ok := stack.Outputs["secret"].(map[string]interface{})
assert.True(t, ok)
_, ok = out["ciphertext"]
assert.True(t, ok)
},
})
}
func TestComponentProviderSchemaGo(t *testing.T) {
t.Parallel()
2023-03-06 14:14:49 +00:00
// TODO[https://github.com/pulumi/pulumi/issues/12365] We no longer build the go-component in
// component_setup.sh so there's no native binary for the testComponentProviderSchema to just exec. It
// _ought_ to be rewritten to use the plugin host framework so that it starts the component up the same as
// all the other tests are doing (via shimless).
t.Skip("testComponentProviderSchema needs to be updated to use a plugin host to deal with non-native-binary providers")
path := filepath.Join("component_provider_schema", "testcomponent-go", "pulumi-resource-testcomponent")
if runtime.GOOS == WindowsOS {
path += ".exe"
}
testComponentProviderSchema(t, path)
}
// TestTracePropagationGo checks that --tracing flag lets golang sub-process to emit traces.
func TestTracePropagationGo(t *testing.T) {
t.Parallel()
dir := t.TempDir()
opts := &integration.ProgramTestOptions{
Dir: filepath.Join("empty", "go"),
Dependencies: []string{"github.com/pulumi/pulumi/sdk/v3"},
SkipRefresh: true,
2022-09-16 21:11:08 +00:00
SkipPreview: true,
SkipUpdate: false,
SkipExportImport: true,
SkipEmptyPreviewUpdate: true,
Quick: false,
Tracing: "file:" + filepath.Join(dir, "{command}.trace"),
2022-09-16 21:11:08 +00:00
RequireService: true,
NoParallel: true,
}
integration.ProgramTest(t, opts)
2022-09-16 21:11:08 +00:00
store, err := ReadMemoryStoreFromFile(filepath.Join(dir, "pulumi-update-initial.trace"))
assert.NoError(t, err)
assert.NotNil(t, store)
feat(go/host): Support vendored dependencies The Go language host cannot resolve dependencies or plugins if a Pulumi program vendors its dependencies. BACKGROUND The GetRequiredPlugins and GetProgramDependencies methods of the Go language host rely on the following two commands: go list -m -mod=mod all go list -m -mod=mod ... # '...' means current module and its descendants GetRequiredPlugins additionally searches the source directories for each returned module for pulumi-plugin.json files at a pre-determined paths. $module/pulumi-plugin.json $module/go/pulumi-plugin.json $module/go/*/pulumi-plugin.json This works for most Pulumi programs, except those that vendor private dependencies with 'go mod vendor'. For those programs, the above commands fail because -mod=mod forces them to run in module mode, and their private dependencies are not accessible in module mode (because they are not exposed publicly). We use the -mod=mod flag to force 'go list' to run in module mode because otherwise, it will automatically use vendor mode if a vendor directory is present. However, in vendor mode, the two 'go list' commands above are not supported. The following links add more context on why, but in short: vendor does not have enough information for the general 'go list'. - https://stackoverflow.com/a/60660593, - https://github.com/golang/go/issues/35589#issuecomment-554488544 In short, - list all with -mod=mod fails because the dependency is private - list without -mod=mod will use vendor mode - vendor mode doesn't support the listing all SOLUTION Drop the -mod=mod flag so that 'go list' can decide whether to run in module mode or vendor mode. However, instead of running it with 'all' or '...', pass in a list of dependencies extracted from the go.mod. go list -m import/path1 import/path2 # ... This operation is completely offline in vendor mode so it can list information about private dependencies too. This alone isn't enough though because in vendor mode, the JSON output does not include the module root directory. E.g. % go list -mod=vendor -json -m github.com/pulumi/pulumi/sdk/v3 { "Path": "github.com/pulumi/pulumi/sdk/v3", "Version": "v3.55.0", "GoVersion": "1.18" } # Versus % go list -mod=mod -json -m github.com/pulumi/pulumi/sdk/v3 { "Path": "github.com/pulumi/pulumi/sdk/v3", "Version": "v3.55.0", "Time": "2023-02-14T11:04:22Z", "Dir": "[...]/go/pkg/mod/github.com/pulumi/pulumi/sdk/v3@v3.55.0", "GoMod": "[...]/go/pkg/mod/cache/download/github.com/pulumi/pulumi/sdk/v3/@v/v3.55.0.mod", "GoVersion": "1.18" } Therefore, we have to manually calculate the path for each module root. That's easy enough: vendor/$importPath. Lastly, since GetProgramDependencies only needs a dependency list, it now extracts information from the go.mod without calling 'go list'. TESTING Adds a variant of the test added in #12715 that verifies the functionality with vendoring. It removes the sources for the dependencies to simulate private dependencies. The new test fails without the accompanying change. The fix was further manually verified against the reproduction included in #12526. % cd go-output % pulumi plugin rm -a -y % pulumi preview Previewing update (abhinav): Downloading plugin: 15.19 MiB / 15.19 MiB [=========================] 100.00% 0s [resource plugin random-4.8.2] installing Type Name Plan + pulumi:pulumi:Stack go-output-abhinav create + └─ random:index:RandomId rrr create Resources: + 2 to create % pulumi plugin ls NAME KIND VERSION SIZE INSTALLED LAST USED random resource 4.8.2 33 MB 26 seconds ago 26 seconds ago TOTAL plugin cache size: 33 MB Note that the version of random (4.8.2) is what's specified in the go.mod, not the latest release (v4.12.1). % grep pulumi-random go.mod github.com/pulumi/pulumi-random/sdk/v4 v4.8.2 With the plugin downloaded, I ran this again without an internet connection. % pulumi preview Previewing update (abhinav): Type Name Plan + pulumi:pulumi:Stack go-output-abhinav create + └─ random:index:RandomId rrr create Resources: + 2 to create This means that if the dependencies are vendored, and the plugin is already available, we won't make additional network requests, which also addresses #7089. Resolves #12526 Resolves #7089
2023-04-20 23:27:39 +00:00
t.Run("traced `go list -m -json`", func(t *testing.T) {
t.Parallel()
2022-09-16 21:11:08 +00:00
isGoListTrace := func(t *appdash.Trace) bool {
m := t.Span.Annotations.StringMap()
isGoCmd := strings.HasSuffix(m["command"], "go") ||
strings.HasSuffix(m["command"], "go.exe")
feat(go/host): Support vendored dependencies The Go language host cannot resolve dependencies or plugins if a Pulumi program vendors its dependencies. BACKGROUND The GetRequiredPlugins and GetProgramDependencies methods of the Go language host rely on the following two commands: go list -m -mod=mod all go list -m -mod=mod ... # '...' means current module and its descendants GetRequiredPlugins additionally searches the source directories for each returned module for pulumi-plugin.json files at a pre-determined paths. $module/pulumi-plugin.json $module/go/pulumi-plugin.json $module/go/*/pulumi-plugin.json This works for most Pulumi programs, except those that vendor private dependencies with 'go mod vendor'. For those programs, the above commands fail because -mod=mod forces them to run in module mode, and their private dependencies are not accessible in module mode (because they are not exposed publicly). We use the -mod=mod flag to force 'go list' to run in module mode because otherwise, it will automatically use vendor mode if a vendor directory is present. However, in vendor mode, the two 'go list' commands above are not supported. The following links add more context on why, but in short: vendor does not have enough information for the general 'go list'. - https://stackoverflow.com/a/60660593, - https://github.com/golang/go/issues/35589#issuecomment-554488544 In short, - list all with -mod=mod fails because the dependency is private - list without -mod=mod will use vendor mode - vendor mode doesn't support the listing all SOLUTION Drop the -mod=mod flag so that 'go list' can decide whether to run in module mode or vendor mode. However, instead of running it with 'all' or '...', pass in a list of dependencies extracted from the go.mod. go list -m import/path1 import/path2 # ... This operation is completely offline in vendor mode so it can list information about private dependencies too. This alone isn't enough though because in vendor mode, the JSON output does not include the module root directory. E.g. % go list -mod=vendor -json -m github.com/pulumi/pulumi/sdk/v3 { "Path": "github.com/pulumi/pulumi/sdk/v3", "Version": "v3.55.0", "GoVersion": "1.18" } # Versus % go list -mod=mod -json -m github.com/pulumi/pulumi/sdk/v3 { "Path": "github.com/pulumi/pulumi/sdk/v3", "Version": "v3.55.0", "Time": "2023-02-14T11:04:22Z", "Dir": "[...]/go/pkg/mod/github.com/pulumi/pulumi/sdk/v3@v3.55.0", "GoMod": "[...]/go/pkg/mod/cache/download/github.com/pulumi/pulumi/sdk/v3/@v/v3.55.0.mod", "GoVersion": "1.18" } Therefore, we have to manually calculate the path for each module root. That's easy enough: vendor/$importPath. Lastly, since GetProgramDependencies only needs a dependency list, it now extracts information from the go.mod without calling 'go list'. TESTING Adds a variant of the test added in #12715 that verifies the functionality with vendoring. It removes the sources for the dependencies to simulate private dependencies. The new test fails without the accompanying change. The fix was further manually verified against the reproduction included in #12526. % cd go-output % pulumi plugin rm -a -y % pulumi preview Previewing update (abhinav): Downloading plugin: 15.19 MiB / 15.19 MiB [=========================] 100.00% 0s [resource plugin random-4.8.2] installing Type Name Plan + pulumi:pulumi:Stack go-output-abhinav create + └─ random:index:RandomId rrr create Resources: + 2 to create % pulumi plugin ls NAME KIND VERSION SIZE INSTALLED LAST USED random resource 4.8.2 33 MB 26 seconds ago 26 seconds ago TOTAL plugin cache size: 33 MB Note that the version of random (4.8.2) is what's specified in the go.mod, not the latest release (v4.12.1). % grep pulumi-random go.mod github.com/pulumi/pulumi-random/sdk/v4 v4.8.2 With the plugin downloaded, I ran this again without an internet connection. % pulumi preview Previewing update (abhinav): Type Name Plan + pulumi:pulumi:Stack go-output-abhinav create + └─ random:index:RandomId rrr create Resources: + 2 to create This means that if the dependencies are vendored, and the plugin is already available, we won't make additional network requests, which also addresses #7089. Resolves #12526 Resolves #7089
2023-04-20 23:27:39 +00:00
return isGoCmd &&
m["component"] == "exec.Command" &&
strings.Contains(m["args"], "list -m -json")
2022-09-16 21:11:08 +00:00
}
tr, err := FindTrace(store, isGoListTrace)
assert.NoError(t, err)
assert.NotNil(t, tr)
})
t.Run("traced api/exportStack exactly once", func(t *testing.T) {
t.Parallel()
2022-09-16 21:11:08 +00:00
exportStackCounter := 0
err := WalkTracesWithDescendants(store, func(tr *appdash.Trace) error {
name := tr.Span.Name()
if name == "api/exportStack" {
exportStackCounter++
}
return nil
})
assert.NoError(t, err)
assert.Equal(t, 1, exportStackCounter)
})
}
2021-08-27 17:39:49 +00:00
// Test that the about command works as expected. Because about parses the
// results of each runtime independently, we have an integration test in each
// language.
func TestAboutGo(t *testing.T) {
t.Parallel()
2021-08-27 17:39:49 +00:00
dir := filepath.Join("about", "go")
e := ptesting.NewEnvironment(t)
defer e.DeleteIfNotFailed()
2021-08-27 17:39:49 +00:00
e.ImportDirectory(dir)
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
2021-08-28 04:31:46 +00:00
e.RunCommand("pulumi", "stack", "init", "about-stack")
e.RunCommand("pulumi", "stack", "select", "about-stack")
2021-08-28 15:53:17 +00:00
stdout, _ := e.RunCommand("pulumi", "about", "-t")
2021-08-28 04:31:46 +00:00
2021-08-27 17:39:49 +00:00
// Assert we parsed the dependencies
assert.Contains(t, stdout, "github.com/pulumi/pulumi/sdk/v3")
2021-08-27 17:39:49 +00:00
}
func TestConstructOutputValuesGo(t *testing.T) {
t.Parallel()
testConstructOutputValues(t, "go", "github.com/pulumi/pulumi/sdk/v3")
}
2022-09-22 13:46:48 +00:00
// TestProjectMainGo tests out the ability to override the main entrypoint.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
2022-09-22 13:46:48 +00:00
func TestProjectMainGo(t *testing.T) {
test := integration.ProgramTestOptions{
Dir: "project_main/go",
Dependencies: []string{"github.com/pulumi/pulumi/sdk/v3"},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// Simple runtime validation that just ensures the checkpoint was written and read.
assert.NotNil(t, stackInfo.Deployment)
},
}
integration.ProgramTest(t, &test)
}
// TestRefreshGo simply tests that we can build and run an empty Go project with the `refresh` option set.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestRefreshGo(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("refresh", "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Quick: true,
})
}
// TestResourceRefsGetResourceGo tests that invoking the built-in 'pulumi:pulumi:getResource' function
// returns resource references for any resource reference in a resource's state.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestResourceRefsGetResourceGo(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("resource_refs_get_resource", "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Quick: true,
})
}
// TestDeletedWithGo tests the DeletedWith resource option.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestDeletedWithGo(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join("deleted_with", "go"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
LocalProviders: []integration.LocalDependency{
{Package: "testprovider", Path: filepath.Join("..", "testprovider")},
},
Quick: true,
})
}
func TestConstructProviderPropagationGo(t *testing.T) {
t.Parallel()
testConstructProviderPropagation(t, "go", []string{"github.com/pulumi/pulumi/sdk/v3"})
}
func TestConstructResourceOptionsGo(t *testing.T) {
t.Parallel()
testConstructResourceOptions(t, "go", []string{"github.com/pulumi/pulumi/sdk/v3"})
}
// Regression test for https://github.com/pulumi/pulumi/issues/13301.
// The reproduction is a bit involved:
//
// - Set up a fake Pulumi Go project that imports a specific non-Pulumi plugin.
// Specifically, the plugin MUST NOT be imported by any Go file in the project.
// - Install that plugin with 'pulumi plugin install'.
// - Run a Go Automation program that uses that plugin.
//
// The issue in #13301 was that this plugin would not be downloaded by `pulumi plugin install`,
// causing a failure when the Automation program tried to use it.
func TestAutomation_externalPluginDownload_issue13301(t *testing.T) {
t.Parallel()
// Context scoped to the lifetime of the test.
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
e := ptesting.NewEnvironment(t)
defer e.DeleteIfNotFailed()
e.ImportDirectory(filepath.Join("go", "regress-13301"))
// Rename go.mod.bad to go.mod so that the Go toolchain uses it.
require.NoError(t, os.Rename(
filepath.Join(e.CWD, "go.mod.bad"),
filepath.Join(e.CWD, "go.mod"),
))
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
// Plugins are installed globally in PULUMI_HOME.
// We will set that to a temporary directory,
// so this is not polluted by other tests.
pulumiHome := filepath.Join(e.RootPath, ".pulumi-home")
require.NoError(t, os.MkdirAll(pulumiHome, 0o700))
// The commands that follow will make gRPC requests that
// we don't have a lot of visibility into.
// If we run the Pulumi CLI with PULUMI_DEBUG_GRPC set to a path,
// it will log the gRPC requests and responses to that file.
//
// Capture these and print them if the test fails.
grpcLog := filepath.Join(e.RootPath, "debug-grpc.log")
defer func() {
if !t.Failed() {
return
}
if bs, err := os.ReadFile(grpcLog); err == nil {
t.Logf("grpc debug log:\n%s", bs)
}
}()
e.Env = append(e.Env,
"PULUMI_HOME="+pulumiHome,
"PULUMI_DEBUG_GRPC="+grpcLog)
e.RunCommand("pulumi", "plugin", "install")
ws, err := auto.NewLocalWorkspace(ctx,
auto.Project(workspace.Project{
Name: "issue-13301",
Runtime: workspace.NewProjectRuntimeInfo("go", nil),
}),
auto.WorkDir(e.CWD),
auto.PulumiHome(pulumiHome),
auto.EnvVars(map[string]string{
"PULUMI_CONFIG_PASSPHRASE": "not-a-real-passphrase",
"PULUMI_DEBUG_COMMANDS": "true",
"PULUMI_CREDENTIALS_PATH": e.RootPath,
"PULUMI_DEBUG_GRPC": grpcLog,
}),
)
require.NoError(t, err)
ws.SetProgram(func(ctx *pulumi.Context) error {
provider, err := hcp.NewProvider(ctx, "hcp", &hcp.ProviderArgs{})
if err != nil {
return err
}
_ = provider // unused
return nil
})
stack, err := auto.UpsertStack(ctx, "foo", ws)
require.NoError(t, err)
_, err = stack.Preview(ctx)
require.NoError(t, err)
}
func TestConstructProviderExplicitGo(t *testing.T) {
t.Parallel()
testConstructProviderExplicit(t, "go", []string{"github.com/pulumi/pulumi/sdk/v3"})
}
// TestStackOutputsProgramErrorGo tests that when a program error occurs, we update any
// updated stack outputs, but otherwise leave others untouched.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestStackOutputsProgramErrorGo(t *testing.T) {
d := filepath.Join("stack_outputs_program_error", "go")
validateOutputs := func(
expected map[string]interface{},
) func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
return func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.Equal(t, expected, stackInfo.RootResource.Outputs)
}
}
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join(d, "step1"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
Quick: true,
ExtraRuntimeValidation: validateOutputs(map[string]interface{}{
"xyz": "ABC",
"foo": float64(42),
}),
EditDirs: []integration.EditDir{
{
Dir: filepath.Join(d, "step2"),
Additive: true,
ExpectFailure: true,
ExtraRuntimeValidation: validateOutputs(map[string]interface{}{
"xyz": "DEF", // Expected to be updated
"foo": float64(42), // Expected to remain the same
}),
},
},
})
}
// TestStackOutputsResourceErrorGo tests that when a resource error occurs, we update any
// updated stack outputs, but otherwise leave others untouched.
//
//nolint:paralleltest // ProgramTest calls t.Parallel()
func TestStackOutputsResourceErrorGo(t *testing.T) {
d := filepath.Join("stack_outputs_resource_error", "go")
validateOutputs := func(
expected map[string]interface{},
) func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
return func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.Equal(t, expected, stackInfo.RootResource.Outputs)
}
}
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: filepath.Join(d, "step1"),
Dependencies: []string{
"github.com/pulumi/pulumi/sdk/v3",
},
LocalProviders: []integration.LocalDependency{
{Package: "testprovider", Path: filepath.Join("..", "testprovider")},
},
Quick: true,
ExtraRuntimeValidation: validateOutputs(map[string]interface{}{
"xyz": "ABC",
"foo": float64(42),
}),
EditDirs: []integration.EditDir{
{
Dir: filepath.Join(d, "step2"),
Additive: true,
ExpectFailure: true,
// Expect the values to remain the same because the deployment ends before RegisterResourceOutputs is
// called for the stack.
ExtraRuntimeValidation: validateOutputs(map[string]interface{}{
"xyz": "ABC",
"foo": float64(42),
}),
},
{
Dir: filepath.Join(d, "step3"),
Additive: true,
ExpectFailure: true,
// Expect the values to be updated.
ExtraRuntimeValidation: validateOutputs(map[string]interface{}{
"xyz": "DEF",
"foo": float64(1),
}),
},
},
})
}