mirror of https://github.com/pulumi/pulumi.git
348 lines
9.7 KiB
Go
348 lines
9.7 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.
|
|
|
|
package auto
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/events"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/optremotepreview"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
|
ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing"
|
|
)
|
|
|
|
const (
|
|
remoteTestRepo = "https://github.com/pulumi/test-repo.git"
|
|
remoteTestRepoBranch = "refs/heads/master"
|
|
)
|
|
|
|
func testRemoteStackGitSourceErrors(t *testing.T, fn func(ctx context.Context, stackName string, repo GitRepo,
|
|
opts ...RemoteWorkspaceOption) (RemoteStack, error),
|
|
) {
|
|
ctx := context.Background()
|
|
|
|
const stack = "owner/project/stack"
|
|
|
|
tests := map[string]struct {
|
|
stack string
|
|
repo GitRepo
|
|
executorImage *ExecutorImage
|
|
err string
|
|
inheritSettings bool
|
|
}{
|
|
"stack empty": {
|
|
stack: "",
|
|
err: `stack name "" must be fully qualified`,
|
|
},
|
|
"stack just name": {
|
|
stack: "name",
|
|
err: `stack name "name" must be fully qualified`,
|
|
},
|
|
"stack just name & owner": {
|
|
stack: "owner/name",
|
|
err: `stack name "owner/name" must be fully qualified`,
|
|
},
|
|
"stack just sep": {
|
|
stack: "/",
|
|
err: `stack name "/" must be fully qualified`,
|
|
},
|
|
"stack just two seps": {
|
|
stack: "//",
|
|
err: `stack name "//" must be fully qualified`,
|
|
},
|
|
"stack just three seps": {
|
|
stack: "///",
|
|
err: `stack name "///" must be fully qualified`,
|
|
},
|
|
"stack invalid": {
|
|
stack: "owner/project/stack/wat",
|
|
err: `stack name "owner/project/stack/wat" must be fully qualified`,
|
|
},
|
|
"repo setup": {
|
|
stack: stack,
|
|
repo: GitRepo{Setup: func(context.Context, Workspace) error { return nil }},
|
|
inheritSettings: true,
|
|
err: "repo.Setup cannot be used with remote workspaces",
|
|
},
|
|
"no url": {
|
|
stack: stack,
|
|
repo: GitRepo{},
|
|
err: "repo.URL is required if RemoteInheritSettings(true) is not set",
|
|
},
|
|
"no branch or commit": {
|
|
stack: stack,
|
|
repo: GitRepo{URL: remoteTestRepo},
|
|
err: "either repo.Branch or repo.CommitHash is required if RemoteInheritSettings(true) is not set",
|
|
},
|
|
"both branch and commit": {
|
|
stack: stack,
|
|
repo: GitRepo{URL: remoteTestRepo, Branch: "branch", CommitHash: "commit"},
|
|
err: "repo.Branch and repo.CommitHash cannot both be specified",
|
|
},
|
|
"both ssh private key and path": {
|
|
stack: stack,
|
|
repo: GitRepo{
|
|
URL: remoteTestRepo,
|
|
Branch: "branch",
|
|
Auth: &GitAuth{SSHPrivateKey: "key", SSHPrivateKeyPath: "path"},
|
|
},
|
|
err: "repo.Auth.SSHPrivateKey and repo.Auth.SSHPrivateKeyPath cannot both be specified",
|
|
},
|
|
"executor creds with no image": {
|
|
stack: stack,
|
|
repo: GitRepo{
|
|
URL: remoteTestRepo,
|
|
Branch: "branch",
|
|
},
|
|
executorImage: &ExecutorImage{
|
|
Credentials: &DockerImageCredentials{
|
|
Username: "user",
|
|
Password: "password",
|
|
},
|
|
},
|
|
err: "executorImage.Image cannot be empty",
|
|
},
|
|
"executor image with username and no password": {
|
|
stack: stack,
|
|
repo: GitRepo{
|
|
URL: remoteTestRepo,
|
|
Branch: "branch",
|
|
},
|
|
executorImage: &ExecutorImage{
|
|
Image: "image",
|
|
Credentials: &DockerImageCredentials{
|
|
Username: "username",
|
|
},
|
|
},
|
|
err: "executorImage.Credentials.Password cannot be empty",
|
|
},
|
|
"executor image with password and no username": {
|
|
stack: stack,
|
|
repo: GitRepo{
|
|
URL: remoteTestRepo,
|
|
Branch: "branch",
|
|
},
|
|
executorImage: &ExecutorImage{
|
|
Image: "image",
|
|
Credentials: &DockerImageCredentials{
|
|
Password: "password",
|
|
},
|
|
},
|
|
err: "executorImage.Credentials.Username cannot be empty",
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
tc := tc
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := fn(ctx, tc.stack, tc.repo, RemoteExecutorImage(tc.executorImage),
|
|
RemoteInheritSettings(tc.inheritSettings))
|
|
assert.EqualError(t, err, tc.err)
|
|
})
|
|
}
|
|
}
|
|
|
|
// fetchCommitHash runs `git ls-remote URL branch` to determine the latest commit for the given repo
|
|
// URL and branch.
|
|
func fetchCommitHash(url, branch string) (string, error) {
|
|
cmd := exec.Command("git", "ls-remote", url, branch)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("git ls-remote: %w", err)
|
|
}
|
|
out := strings.TrimSpace(string(output))
|
|
fields := strings.Fields(out)
|
|
if len(fields) == 0 {
|
|
return "", fmt.Errorf("could not determine commit hash from %q", out)
|
|
}
|
|
return fields[0], nil
|
|
}
|
|
|
|
func testRemoteStackGitSource(
|
|
t *testing.T,
|
|
fn func(ctx context.Context, stackName string, repo GitRepo, opts ...RemoteWorkspaceOption) (RemoteStack, error),
|
|
useCommitHash bool,
|
|
useExecutorImage bool,
|
|
) {
|
|
// This test requires the service with access to Pulumi Deployments.
|
|
// Set PULUMI_ACCESS_TOKEN to an access token with access to Pulumi Deployments
|
|
// and set PULUMI_TEST_DEPLOYMENTS_API to any value to enable the test.
|
|
if os.Getenv("PULUMI_ACCESS_TOKEN") == "" {
|
|
t.Skipf("Skipping: PULUMI_ACCESS_TOKEN is not set")
|
|
}
|
|
if os.Getenv("PULUMI_TEST_DEPLOYMENTS_API") == "" {
|
|
t.Skipf("Skipping: PULUMI_TEST_DEPLOYMENTS_API is not set")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
pName := "go_remote_proj"
|
|
sName := ptesting.RandomStackName()
|
|
stackName := FullyQualifiedStackName(pulumiOrg, pName, sName)
|
|
repo := GitRepo{
|
|
URL: remoteTestRepo,
|
|
ProjectPath: "goproj",
|
|
}
|
|
var executorImage *ExecutorImage
|
|
if useCommitHash {
|
|
commitHash, err := fetchCommitHash(remoteTestRepo, remoteTestRepoBranch)
|
|
require.NoError(t, err)
|
|
repo.CommitHash = commitHash
|
|
} else {
|
|
repo.Branch = remoteTestRepoBranch
|
|
}
|
|
|
|
if useExecutorImage {
|
|
executorImage = &ExecutorImage{
|
|
Image: "pulumi/pulumi",
|
|
}
|
|
}
|
|
|
|
// initialize
|
|
s, err := fn(ctx, stackName, repo,
|
|
RemotePreRunCommands(
|
|
"pulumi config set bar abc --stack "+stackName,
|
|
"pulumi config set --secret buzz secret --stack "+stackName),
|
|
RemoteSkipInstallDependencies(true),
|
|
RemoteExecutorImage(executorImage),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("failed to initialize stack, err: %v", err)
|
|
t.FailNow()
|
|
}
|
|
|
|
defer func() {
|
|
// -- pulumi stack rm --
|
|
err = s.stack.Workspace().RemoveStack(ctx, s.Name())
|
|
assert.Nil(t, err, "failed to remove stack. Resources have leaked.")
|
|
}()
|
|
|
|
// -- pulumi up --
|
|
res, err := s.Up(ctx)
|
|
if err != nil {
|
|
t.Errorf("up failed, err: %v", err)
|
|
t.FailNow()
|
|
}
|
|
|
|
assert.Equal(t, 3, len(res.Outputs), "expected two plain outputs")
|
|
assert.Equal(t, "foo", res.Outputs["exp_static"].Value)
|
|
assert.False(t, res.Outputs["exp_static"].Secret)
|
|
assert.Equal(t, "abc", res.Outputs["exp_cfg"].Value)
|
|
assert.False(t, res.Outputs["exp_cfg"].Secret)
|
|
assert.Equal(t, "secret", res.Outputs["exp_secret"].Value)
|
|
assert.True(t, res.Outputs["exp_secret"].Secret)
|
|
assert.Equal(t, "update", res.Summary.Kind)
|
|
assert.Equal(t, "succeeded", res.Summary.Result)
|
|
|
|
// -- pulumi preview --
|
|
|
|
var previewEvents []events.EngineEvent
|
|
prevCh := make(chan events.EngineEvent)
|
|
wg := collectEvents(prevCh, &previewEvents)
|
|
prev, err := s.Preview(ctx, optremotepreview.EventStreams(prevCh))
|
|
if err != nil {
|
|
t.Errorf("preview failed, err: %v", err)
|
|
t.FailNow()
|
|
}
|
|
wg.Wait()
|
|
assert.Equal(t, 1, prev.ChangeSummary[apitype.OpSame])
|
|
steps := countSteps(previewEvents)
|
|
assert.Equal(t, 1, steps)
|
|
|
|
// -- pulumi refresh --
|
|
|
|
ref, err := s.Refresh(ctx)
|
|
if err != nil {
|
|
t.Errorf("refresh failed, err: %v", err)
|
|
t.FailNow()
|
|
}
|
|
assert.Equal(t, "refresh", ref.Summary.Kind)
|
|
assert.Equal(t, "succeeded", ref.Summary.Result)
|
|
|
|
// -- pulumi destroy --
|
|
|
|
dRes, err := s.Destroy(ctx)
|
|
if err != nil {
|
|
t.Errorf("destroy failed, err: %v", err)
|
|
t.FailNow()
|
|
}
|
|
|
|
assert.Equal(t, "destroy", dRes.Summary.Kind)
|
|
assert.Equal(t, "succeeded", dRes.Summary.Result)
|
|
}
|
|
|
|
func TestSelectRemoteStackGitSourceErrors(t *testing.T) {
|
|
t.Parallel()
|
|
testRemoteStackGitSourceErrors(t, SelectRemoteStackGitSource)
|
|
}
|
|
|
|
func TestNewRemoteStackGitSourceErrors(t *testing.T) {
|
|
t.Parallel()
|
|
testRemoteStackGitSourceErrors(t, NewRemoteStackGitSource)
|
|
}
|
|
|
|
func TestNewRemoteStackGitSource(t *testing.T) {
|
|
t.Parallel()
|
|
testRemoteStackGitSource(t, NewRemoteStackGitSource, true /*useCommitHash*/, false /*useExecutorImage*/)
|
|
}
|
|
|
|
func TestUpsertRemoteStackGitSourceErrors(t *testing.T) {
|
|
t.Parallel()
|
|
testRemoteStackGitSourceErrors(t, UpsertRemoteStackGitSource)
|
|
}
|
|
|
|
func TestUpsertRemoteStackGitSource(t *testing.T) {
|
|
t.Parallel()
|
|
testRemoteStackGitSource(t, UpsertRemoteStackGitSource, false /*useCommitHash*/, true /*useExecutorImage*/)
|
|
}
|
|
|
|
func TestIsFullyQualifiedStackName(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected bool
|
|
}{
|
|
{name: "fully qualified", input: "owner/project/stack", expected: true},
|
|
{name: "empty", input: "", expected: false},
|
|
{name: "name", input: "name", expected: false},
|
|
{name: "name & owner", input: "owner/name", expected: false},
|
|
{name: "sep", input: "/", expected: false},
|
|
{name: "two seps", input: "//", expected: false},
|
|
{name: "three seps", input: "///", expected: false},
|
|
{name: "invalid", input: "owner/project/stack/wat", expected: false},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
actual := isFullyQualifiedStackName(tc.input)
|
|
assert.Equal(t, tc.expected, actual)
|
|
})
|
|
}
|
|
}
|