mirror of https://github.com/pulumi/pulumi.git
1434 lines
46 KiB
Go
1434 lines
46 KiB
Go
// 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 (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
dap "github.com/google/go-dap"
|
|
"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/apitype"
|
|
"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/util/contract"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
)
|
|
|
|
// 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()
|
|
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"
|
|
|
|
testDir := "construct_component_slow"
|
|
runComponentSetup(t, testDir)
|
|
|
|
opts := &integration.ProgramTestOptions{
|
|
Env: []string{testYarnLinkPulumiEnv},
|
|
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()
|
|
|
|
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) {
|
|
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()
|
|
|
|
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,
|
|
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")
|
|
}
|
|
|
|
func TestConstructMethodsProviderGo(t *testing.T) {
|
|
t.Parallel()
|
|
testConstructMethodsProvider(t, "go", "github.com/pulumi/pulumi/sdk/v3")
|
|
}
|
|
|
|
func TestConstructProviderGo(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const testDir = "construct_component_provider"
|
|
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()
|
|
// 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,
|
|
SkipPreview: true,
|
|
SkipUpdate: false,
|
|
SkipExportImport: true,
|
|
SkipEmptyPreviewUpdate: true,
|
|
Quick: false,
|
|
Tracing: "file:" + filepath.Join(dir, "{command}.trace"),
|
|
RequireService: true,
|
|
NoParallel: true,
|
|
}
|
|
|
|
integration.ProgramTest(t, opts)
|
|
|
|
store, err := ReadMemoryStoreFromFile(filepath.Join(dir, "pulumi-update-initial.trace"))
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, store)
|
|
|
|
t.Run("traced `go list -m -json`", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
isGoListTrace := func(t *appdash.Trace) bool {
|
|
m := t.Span.Annotations.StringMap()
|
|
|
|
isGoCmd := strings.HasSuffix(m["command"], "go") ||
|
|
strings.HasSuffix(m["command"], "go.exe")
|
|
|
|
return isGoCmd &&
|
|
m["component"] == "exec.Command" &&
|
|
strings.Contains(m["args"], "list -m -json")
|
|
}
|
|
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()
|
|
|
|
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)
|
|
})
|
|
}
|
|
|
|
// 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()
|
|
|
|
dir := filepath.Join("about", "go")
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
e.ImportDirectory(dir)
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
e.RunCommand("pulumi", "stack", "init", "about-stack")
|
|
e.RunCommand("pulumi", "stack", "select", "about-stack")
|
|
stdout, _ := e.RunCommand("pulumi", "about", "-t")
|
|
|
|
// Assert we parsed the dependencies
|
|
assert.Contains(t, stdout, "github.com/pulumi/pulumi/sdk/v3")
|
|
}
|
|
|
|
func TestConstructOutputValuesGo(t *testing.T) {
|
|
t.Parallel()
|
|
testConstructOutputValues(t, "go", "github.com/pulumi/pulumi/sdk/v3")
|
|
}
|
|
|
|
// TestProjectMainGo tests out the ability to override the main entrypoint.
|
|
//
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
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),
|
|
}),
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// Test a paramaterized provider with go.
|
|
//
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
func TestParameterizedGo(t *testing.T) {
|
|
e := ptesting.NewEnvironment(t)
|
|
|
|
// We can't use ImportDirectory here because we need to run this in the right directory such that the relative paths
|
|
// work.
|
|
var err error
|
|
e.CWD, err = filepath.Abs("go/parameterized")
|
|
require.NoError(t, err)
|
|
|
|
err = os.RemoveAll(filepath.Join("go", "parameterized", "sdk"))
|
|
require.NoError(t, err)
|
|
|
|
_, _ = e.RunCommand("pulumi", "package", "gen-sdk", "../../../testprovider", "pkg", "--language", "go")
|
|
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("go", "parameterized"),
|
|
Dependencies: []string{
|
|
"github.com/pulumi/pulumi/sdk/v3",
|
|
},
|
|
LocalProviders: []integration.LocalDependency{
|
|
{Package: "testprovider", Path: filepath.Join("..", "testprovider")},
|
|
},
|
|
})
|
|
}
|
|
|
|
func readUpdateEventLog(logfile string) ([]apitype.EngineEvent, error) {
|
|
events := make([]apitype.EngineEvent, 0)
|
|
eventsFile, err := os.Open(logfile)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("expected to be able to open event log file %s: %w",
|
|
logfile, err)
|
|
}
|
|
|
|
defer contract.IgnoreClose(eventsFile)
|
|
|
|
decoder := json.NewDecoder(eventsFile)
|
|
for {
|
|
var event apitype.EngineEvent
|
|
if err = decoder.Decode(&event); err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf("failed decoding engine event from log file %s: %w",
|
|
logfile, err)
|
|
}
|
|
events = append(events, event)
|
|
}
|
|
return events, nil
|
|
}
|
|
|
|
func newDAPRequest(seq int, command string) dap.Request {
|
|
request := dap.Request{}
|
|
request.Type = "request"
|
|
request.Command = command
|
|
request.Seq = seq
|
|
return request
|
|
}
|
|
|
|
func TestDebuggerAttach(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
e.ImportDirectory(filepath.Join("go", "go-build-target"))
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
e.Env = append(e.Env, "PULUMI_DEBUG_COMMANDS=true")
|
|
e.RunCommand("pulumi", "stack", "init", "debugger-test")
|
|
e.RunCommand("pulumi", "stack", "select", "debugger-test")
|
|
e.RunCommand("pulumi", "preview", "--attach-debugger",
|
|
"--event-log", filepath.Join(e.RootPath, "debugger.log"))
|
|
}()
|
|
|
|
// Wait for the debugging event
|
|
wait := 20 * time.Millisecond
|
|
var debugEvent *apitype.StartDebuggingEvent
|
|
outer:
|
|
for i := 0; i < 50; i++ {
|
|
events, err := readUpdateEventLog(filepath.Join(e.RootPath, "debugger.log"))
|
|
require.NoError(t, err)
|
|
for _, event := range events {
|
|
if event.StartDebuggingEvent != nil {
|
|
debugEvent = event.StartDebuggingEvent
|
|
break outer
|
|
}
|
|
}
|
|
time.Sleep(wait)
|
|
wait *= 2
|
|
}
|
|
require.NotNil(t, debugEvent)
|
|
|
|
// We've attached a debugger, so we need to connect to it and let the program continue.
|
|
conn, err := net.Dial("tcp", "localhost:"+strconv.Itoa(int(debugEvent.Config["port"].(float64))))
|
|
if err != nil {
|
|
log.Fatalf("Failed to connect to debugger: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
seq := 0
|
|
err = dap.WriteProtocolMessage(conn, &dap.InitializeRequest{
|
|
Request: newDAPRequest(seq, "initialize"),
|
|
Arguments: dap.InitializeRequestArguments{
|
|
ClientID: "pulumi",
|
|
ClientName: "Pulumi",
|
|
AdapterID: "pulumi",
|
|
Locale: "en-us",
|
|
LinesStartAt1: true,
|
|
ColumnsStartAt1: true,
|
|
},
|
|
})
|
|
assert.NoError(t, err)
|
|
seq++
|
|
reader := bufio.NewReader(conn)
|
|
// We need to read the response, but we don't actually care
|
|
// about it. It just includes the capabilities of the
|
|
// debugger.
|
|
resp, err := dap.ReadProtocolMessage(reader)
|
|
assert.NoError(t, err)
|
|
assert.IsType(t, &dap.InitializeResponse{}, resp)
|
|
json, err := json.Marshal(debugEvent.Config)
|
|
assert.NoError(t, err)
|
|
err = dap.WriteProtocolMessage(conn, &dap.AttachRequest{
|
|
Request: newDAPRequest(seq, "attach"),
|
|
Arguments: json,
|
|
})
|
|
assert.NoError(t, err)
|
|
seq++
|
|
// read the initialized event, and then the response to the attach request.
|
|
resp, err = dap.ReadProtocolMessage(reader)
|
|
assert.NoError(t, err)
|
|
assert.IsType(t, &dap.InitializedEvent{}, resp)
|
|
resp, err = dap.ReadProtocolMessage(reader)
|
|
assert.NoError(t, err)
|
|
assert.IsType(t, &dap.AttachResponse{}, resp)
|
|
|
|
err = dap.WriteProtocolMessage(conn, &dap.ContinueRequest{
|
|
Request: newDAPRequest(seq, "continue"),
|
|
})
|
|
assert.NoError(t, err)
|
|
seq++
|
|
resp, err = dap.ReadProtocolMessage(reader)
|
|
assert.NoError(t, err)
|
|
assert.IsType(t, &dap.ContinueResponse{}, resp)
|
|
resp, err = dap.ReadProtocolMessage(reader)
|
|
assert.NoError(t, err)
|
|
assert.IsType(t, &dap.TerminatedEvent{}, resp)
|
|
|
|
err = dap.WriteProtocolMessage(conn, &dap.DisconnectRequest{
|
|
Request: newDAPRequest(seq, "disconnect"),
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
// Make sure the program finished successfully.
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestConstructFailuresGo(t *testing.T) {
|
|
t.Parallel()
|
|
testConstructFailures(t, "go", "github.com/pulumi/pulumi/sdk/v3")
|
|
}
|
|
|
|
func TestCallFailuresGo(t *testing.T) {
|
|
t.Parallel()
|
|
testCallFailures(t, "go", "github.com/pulumi/pulumi/sdk/v3")
|
|
}
|
|
|
|
// TestLogDebugGo tests that the amount of debug logs is reasonable.
|
|
//
|
|
//nolint:paralleltest // ProgramTest calls t.Parallel()
|
|
func TestLogDebugGo(t *testing.T) {
|
|
integration.ProgramTest(t, &integration.ProgramTestOptions{
|
|
Dir: filepath.Join("log_debug", "go"),
|
|
Dependencies: []string{
|
|
"github.com/pulumi/pulumi/sdk/v3",
|
|
},
|
|
Quick: true,
|
|
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
|
|
var count int
|
|
for _, ev := range stack.Events {
|
|
if de := ev.DiagnosticEvent; de != nil && de.Severity == "debug" {
|
|
count++
|
|
}
|
|
}
|
|
t.Logf("Found %v debug log events", count)
|
|
|
|
// Ensure at least 1 debug log events are emitted, confirming debug logs are working as expected.
|
|
assert.Greaterf(t, count, 0, "%v is not enough debug log events", count)
|
|
|
|
// More than 25 debug log events on such a simple program is very likely unintended.
|
|
assert.LessOrEqual(t, count, 25, "%v is too many debug log events", count)
|
|
},
|
|
})
|
|
}
|
|
|
|
// Test that we can successfully start up a shimless provider and it gets the right environment
|
|
// variables set.
|
|
func TestRunPlugin(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
e := ptesting.NewEnvironment(t)
|
|
defer e.DeleteIfNotFailed()
|
|
e.ImportDirectory(filepath.Join("run_plugin"))
|
|
|
|
e.RunCommand("pulumi", "login", "--cloud-url", e.LocalURL())
|
|
|
|
e.CWD = filepath.Join(e.RootPath, "provider-nodejs")
|
|
e.RunCommand("npm", "install")
|
|
|
|
e.CWD = filepath.Join(e.RootPath, "provider-python")
|
|
e.RunCommand("python", "-m", "venv", "venv", "--clear")
|
|
pythonSdkPath, err := filepath.Abs(filepath.Join("..", "..", "sdk", "python", "env", "src"))
|
|
require.NoError(t, err)
|
|
e.RunCommand(filepath.Join("venv", "bin", "python"), "-m", "pip", "install", "-e", pythonSdkPath)
|
|
e.Env = append(e.Env, "PATH="+filepath.Join(e.CWD, "venv", "bin")+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
|
|
e.CWD = filepath.Join(e.RootPath, "go")
|
|
sdkPath, err := filepath.Abs("../../sdk/")
|
|
require.NoError(t, err)
|
|
|
|
e.RunCommand("go", "mod", "edit", "-replace=github.com/pulumi/pulumi/sdk/v3="+sdkPath)
|
|
e.RunCommand("pulumi", "stack", "init", "runplugin-test")
|
|
e.RunCommand("pulumi", "stack", "select", "runplugin-test")
|
|
e.RunCommand("pulumi", "preview")
|
|
}
|