Allow passing a non-default secrets provider to AutomationAPI (#5320)

This commit is contained in:
Paul Stack 2020-09-10 19:25:47 +01:00 committed by GitHub
parent 49152e6331
commit 9a74064d2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 7 deletions

View file

@ -23,6 +23,9 @@ CHANGELOG
- Python SDK: Add support for `Sequence[T]` for array types
[#5282](https://github.com/pulumi/pulumi/pull/5282)
- feat(autoapi): Add support for non default secret providers in local workspaces
[#5320](https://github.com/pulumi/pulumi/pull/5320)
## 2.9.2 (2020-08-31)

View file

@ -312,6 +312,28 @@ func ExampleNewLocalWorkspace() {
_, _ = NewLocalWorkspace(ctx, wd, ph, proj)
}
func ExampleLocalWorkspace_secretsProvider() {
ctx := context.Background()
// WorkDir sets the working directory for the LocalWorkspace. The workspace will look for a default
// project settings file (Pulumi.yaml) in this location for information about the Pulumi program.
wd := WorkDir(filepath.Join("..", "path", "to", "pulumi", "project"))
// PulumiHome customizes the location of $PULUMI_HOME where metadata is stored and plugins are installed.
ph := PulumiHome(filepath.Join("~", ".pulumi"))
// Project provides ProjectSettings to set once the workspace is created.
proj := Project(workspace.Project{
Name: tokens.PackageName("myproject"),
Runtime: workspace.NewProjectRuntimeInfo("go", nil),
Backend: &workspace.ProjectBackend{
URL: "https://url.to.custom.saas.backend.com",
},
})
// Secrets provider provides a way of passing a non-default secrets provider to the
// workspace and the stacks created from it. Supported secrets providers are:
// `awskms`, `azurekeyvault`, `gcpkms`, `hashivault` and `passphrase`
secretsProvider := SecretsProvider("awskms://alias/mysecretkeyalias")
_, _ = NewLocalWorkspace(ctx, wd, ph, proj, secretsProvider)
}
func ExampleLocalWorkspace_ListPlugins() {
ctx := context.Background()
// create a workspace from a local project

View file

@ -39,10 +39,11 @@ import (
// alter the Workspace Pulumi.yaml file, and setting config on a Stack will modify the Pulumi.<stack>.yaml file.
// This is identical to the behavior of Pulumi CLI driven workspaces.
type LocalWorkspace struct {
workDir string
pulumiHome string
program pulumi.RunFunc
envvars map[string]string
workDir string
pulumiHome string
program pulumi.RunFunc
envvars map[string]string
secretsProvider string
}
var settingsExtensions = []string{".yaml", ".yml", ".json"}
@ -136,7 +137,7 @@ func (l *LocalWorkspace) GetConfig(ctx context.Context, fqsn string, key string)
if err != nil {
return val, errors.Wrapf(err, "could not get config, unable to select stack %s", fqsn)
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "config", "get", key, "--show-secrets", "--json")
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "config", "get", key, "--json")
if err != nil {
return val, newAutoError(errors.Wrap(err, "unable to read config"), stdout, stderr, errCode)
}
@ -330,7 +331,11 @@ func (l *LocalWorkspace) CreateStack(ctx context.Context, fqsn string) error {
return errors.Wrap(err, "failed to create stack")
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, "stack", "init", fqsn)
args := []string{"stack", "init", fqsn}
if l.secretsProvider != "" {
args = append(args, fmt.Sprintf("--secrets-provider=%s", l.secretsProvider))
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
if err != nil {
return newAutoError(errors.Wrap(err, "failed to create stack"), stdout, stderr, errCode)
}
@ -526,6 +531,11 @@ func NewLocalWorkspace(ctx context.Context, opts ...LocalWorkspaceOption) (Works
}
}
// Secrets providers
if lwOpts.SecretsProvider != "" {
l.secretsProvider = lwOpts.SecretsProvider
}
return l, nil
}
@ -545,6 +555,8 @@ type localWorkspaceOptions struct {
Stacks map[string]workspace.ProjectStack
// Repo is a git repo with a Pulumi Project to clone into the WorkDir.
Repo *GitRepo
// Secrets Provider to use with the current Stack
SecretsProvider string
}
// LocalWorkspaceOption is used to customize and configure a LocalWorkspace at initialization time.
@ -621,6 +633,14 @@ func Repo(gitRepo GitRepo) LocalWorkspaceOption {
})
}
// SecretsProvider is the secrets provider to use with the current
// workspace when interacting with a stack
func SecretsProvider(secretsProvider string) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.SecretsProvider = secretsProvider
})
}
// ValidateFullyQualifiedStackName validates that the fqsn is in the form "org/project/name".
func ValidateFullyQualifiedStackName(fqsn string) error {
parts := strings.Split(fqsn, "/")

View file

@ -18,6 +18,7 @@ import (
"context"
"fmt"
"math/rand"
"os"
"os/exec"
"path/filepath"
"testing"
@ -30,9 +31,79 @@ import (
"github.com/stretchr/testify/assert"
)
const pulumiOrg = "pulumi"
const pulumiOrg = "moolumi"
const pName = "testproj"
func TestWorkspaceSecretsProvider(t *testing.T) {
ctx := context.Background()
sName := fmt.Sprintf("int_test%d", rangeIn(10000000, 99999999))
fqsn := FullyQualifiedStackName(pulumiOrg, pName, sName)
// We can't use Workspace EnvVars as the Workspace uses the secrets provider to
// create the Stack
err := os.Setenv("PULUMI_CONFIG_PASSPHRASE", "password")
assert.Nil(t, err, "failed to set EnvVar.")
// initialize
s, err := NewStackInlineSource(ctx, fqsn, func(ctx *pulumi.Context) error {
c := config.New(ctx, "")
ctx.Export("exp_static", pulumi.String("foo"))
ctx.Export("exp_cfg", pulumi.String(c.Get("bar")))
ctx.Export("exp_secret", c.GetSecret("buzz"))
return nil
}, SecretsProvider("passphrase"))
if err != nil {
t.Errorf("failed to initialize stack, err: %v", err)
t.FailNow()
}
defer func() {
err := os.Unsetenv("PULUMI_CONFIG_PASSPHRASE")
assert.Nil(t, err, "failed to unset EnvVar.")
// -- pulumi stack rm --
err = s.Workspace().RemoveStack(ctx, s.Name())
assert.Nil(t, err, "failed to remove stack. Resources have leaked.")
}()
passwordVal := "Password1234!"
err = s.SetConfig(ctx, "MySecretDatabasePassword", ConfigValue{Value: passwordVal, Secret: true})
if err != nil {
t.Errorf("setConfig failed, err: %v", err)
t.FailNow()
}
// -- pulumi up --
res, err := s.Up(ctx)
if err != nil {
t.Errorf("up failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, "update", res.Summary.Kind)
assert.Equal(t, "succeeded", res.Summary.Result)
// -- get config --
conf, err := s.GetConfig(ctx, "MySecretDatabasePassword")
if err != nil {
t.Errorf("GetConfig failed, err: %v", err)
t.FailNow()
}
assert.Equal(t, passwordVal, conf.Value)
assert.Equal(t, true, conf.Secret)
// -- 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 TestNewStackLocalSource(t *testing.T) {
ctx := context.Background()
sName := fmt.Sprintf("int_test%d", rangeIn(10000000, 99999999))