Skip to content

Commit

Permalink
[auto/go] Support for remote operations
Browse files Browse the repository at this point in the history
  • Loading branch information
justinvp committed Oct 27, 2022
1 parent 5b6a293 commit 8a98877
Show file tree
Hide file tree
Showing 10 changed files with 886 additions and 11 deletions.
@@ -0,0 +1,4 @@
changes:
- type: feat
scope: auto/go
description: Support for remote operations
92 changes: 86 additions & 6 deletions sdk/go/auto/local_workspace.go
@@ -1,4 +1,4 @@
// Copyright 2016-2021, Pulumi Corporation.
// 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.
Expand Down Expand Up @@ -51,6 +51,10 @@ type LocalWorkspace struct {
envvars map[string]string
secretsProvider string
pulumiVersion semver.Version
repo *GitRepo
remote bool
remoteEnvVars map[string]EnvVarValue
preRunCommands []string
}

var settingsExtensions = []string{".yaml", ".yml", ".json"}
Expand Down Expand Up @@ -324,6 +328,9 @@ func (l *LocalWorkspace) CreateStack(ctx context.Context, stackName string) erro
if l.secretsProvider != "" {
args = append(args, "--secrets-provider", l.secretsProvider)
}
if l.remote {
args = append(args, "--no-select")
}
stdout, stderr, errCode, err := l.runPulumiCmdSync(ctx, args...)
if err != nil {
return newAutoError(errors.Wrap(err, "failed to create stack"), stdout, stderr, errCode)
Expand Down Expand Up @@ -576,6 +583,28 @@ func (l *LocalWorkspace) runPulumiCmdSync(
)
}

// supportsPulumiCmdFlag runs a command with `--help` to see if the specified flag is found within the resulting
// output, in which case we assume the flag is supported.
func (l *LocalWorkspace) supportsPulumiCmdFlag(ctx context.Context, flag string, args ...string) (bool, error) {
env := []string{
"PULUMI_DEBUG_COMMANDS=true",
"PULUMI_EXPERIMENTAL=true",
}

// Run the command with `--help`, and then we'll look for the flag in the output.
stdout, _, _, err := runPulumiCommandSync(ctx, l.WorkDir(), nil, nil, env, append(args, "--help")...)
if err != nil {
return false, err
}

// Does the help test in stdout mention the flag? If so, assume it's supported.
if strings.Contains(stdout, flag) {
return true, nil
}

return false, nil
}

// NewLocalWorkspace creates and configures a LocalWorkspace. LocalWorkspaceOptions can be used to
// configure things like the working directory, the program to execute, and to seed the directory with source code
// from a git repository.
Expand All @@ -598,7 +627,7 @@ func NewLocalWorkspace(ctx context.Context, opts ...LocalWorkspaceOption) (Works
workDir = dir
}

if lwOpts.Repo != nil {
if lwOpts.Repo != nil && !lwOpts.Remote {
// now do the git clone
projDir, err := setupGitRepo(ctx, workDir, lwOpts.Repo)
if err != nil {
Expand All @@ -613,9 +642,13 @@ func NewLocalWorkspace(ctx context.Context, opts ...LocalWorkspaceOption) (Works
}

l := &LocalWorkspace{
workDir: workDir,
program: program,
pulumiHome: lwOpts.PulumiHome,
workDir: workDir,
preRunCommands: lwOpts.PreRunCommands,
program: program,
pulumiHome: lwOpts.PulumiHome,
remote: lwOpts.Remote,
remoteEnvVars: lwOpts.RemoteEnvVars,
repo: lwOpts.Repo,
}

// optOut indicates we should skip the version check.
Expand All @@ -631,6 +664,18 @@ func NewLocalWorkspace(ctx context.Context, opts ...LocalWorkspaceOption) (Works
return nil, err
}

// If remote was specified, ensure the CLI supports it.
if !optOut && l.remote {
// See if `--remote` is present in `pulumi preview --help`'s output.
supportsRemote, err := l.supportsPulumiCmdFlag(ctx, "--remote", "preview")
if err != nil {
return nil, err
}
if !supportsRemote {
return nil, errors.New("Pulumi CLI does not support remote operations; please upgrade")
}
}

if lwOpts.Project != nil {
err := l.SaveProjectSettings(ctx, lwOpts.Project)
if err != nil {
Expand All @@ -647,7 +692,7 @@ func NewLocalWorkspace(ctx context.Context, opts ...LocalWorkspaceOption) (Works
}

// setup
if lwOpts.Repo != nil && lwOpts.Repo.Setup != nil {
if !lwOpts.Remote && lwOpts.Repo != nil && lwOpts.Repo.Setup != nil {
err := lwOpts.Repo.Setup(ctx, l)
if err != nil {
return nil, errors.Wrap(err, "error while running setup function")
Expand All @@ -669,6 +714,13 @@ func NewLocalWorkspace(ctx context.Context, opts ...LocalWorkspaceOption) (Works
return l, nil
}

// EnvVarValue represents the value of an envvar. A value can be a secret, which is passed along
// to remote operations when used with remote workspaces, otherwise, it has no affect.
type EnvVarValue struct {
Value string
Secret bool
}

type localWorkspaceOptions struct {
// WorkDir is the directory to execute commands from and store state.
// Defaults to a tmp dir.
Expand All @@ -690,6 +742,12 @@ type localWorkspaceOptions struct {
// EnvVars is a map of environment values scoped to the workspace.
// These values will be passed to all Workspace and Stack level commands.
EnvVars map[string]string
// Whether the workspace represents a remote workspace.
Remote bool
// Remote environment variables to be passed to the remote Pulumi operation.
RemoteEnvVars map[string]EnvVarValue
// PreRunCommands is an optional list of arbitrary commands to run before the remote Pulumi operation is invoked.
PreRunCommands []string
}

// LocalWorkspaceOption is used to customize and configure a LocalWorkspace at initialization time.
Expand Down Expand Up @@ -809,6 +867,28 @@ func EnvVars(envvars map[string]string) LocalWorkspaceOption {
})
}

// remoteEnvVars is a map of environment values scoped to the workspace.
// These values will be passed to the remote Pulumi operation.
func remoteEnvVars(envvars map[string]EnvVarValue) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.RemoteEnvVars = envvars
})
}

// remote is set on the local workspace to indicate it is actually a remote workspace.
func remote(remote bool) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.Remote = remote
})
}

// preRunCommands is an optional list of arbitrary commands to run before the remote Pulumi operation is invoked.
func preRunCommands(commands ...string) LocalWorkspaceOption {
return localWorkspaceOption(func(lo *localWorkspaceOptions) {
lo.PreRunCommands = commands
})
}

// NewStackLocalSource creates a Stack backed by a LocalWorkspace created on behalf of the user,
// from the specified WorkDir. This Workspace will pick up
// any available Settings files (Pulumi.yaml, Pulumi.<stack>.yaml).
Expand Down
68 changes: 68 additions & 0 deletions sdk/go/auto/optremotedestroy/optremotedestroy.go
@@ -0,0 +1,68 @@
// 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 optremotedestroy contains functional options to be used with remote stack destroy operations
// github.com/sdk/v3/go/auto RemoteStack.Destroy(...optremotedestroy.Option)
package optremotedestroy

import (
"io"

"github.com/pulumi/pulumi/sdk/v3/go/auto/events"
)

// ProgressStreams allows specifying one or more io.Writers to redirect incremental destroy stdout
func ProgressStreams(writers ...io.Writer) Option {
return optionFunc(func(opts *Options) {
opts.ProgressStreams = writers
})
}

// ErrorProgressStreams allows specifying one or more io.Writers to redirect incremental destroy stderr
func ErrorProgressStreams(writers ...io.Writer) Option {
return optionFunc(func(opts *Options) {
opts.ErrorProgressStreams = writers
})
}

// EventStreams allows specifying one or more channels to receive the Pulumi event stream
func EventStreams(channels ...chan<- events.EngineEvent) Option {
return optionFunc(func(opts *Options) {
opts.EventStreams = channels
})
}

// Option is a parameter to be applied to a Stack.Destroy() operation
type Option interface {
ApplyOption(*Options)
}

// ---------------------------------- implementation details ----------------------------------

// Options is an implementation detail
type Options struct {
// ProgressStreams allows specifying one or more io.Writers to redirect incremental destroy stdout
ProgressStreams []io.Writer
// ProgressStreams allows specifying one or more io.Writers to redirect incremental destroy stderr
ErrorProgressStreams []io.Writer
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
EventStreams []chan<- events.EngineEvent
}

type optionFunc func(*Options)

// ApplyOption is an implementation detail
func (o optionFunc) ApplyOption(opts *Options) {
o(opts)
}
68 changes: 68 additions & 0 deletions sdk/go/auto/optremotepreview/optremotepreview.go
@@ -0,0 +1,68 @@
// 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 optremotepreview contains functional options to be used with remote stack preview operations
// github.com/sdk/v3/go/auto RemoteStack.Preview(...optremotepreview.Option)
package optremotepreview

import (
"io"

"github.com/pulumi/pulumi/sdk/v3/go/auto/events"
)

// ProgressStreams allows specifying one or more io.Writers to redirect incremental preview stdout
func ProgressStreams(writers ...io.Writer) Option {
return optionFunc(func(opts *Options) {
opts.ProgressStreams = writers
})
}

// ErrorProgressStreams allows specifying one or more io.Writers to redirect incremental preview stderr
func ErrorProgressStreams(writers ...io.Writer) Option {
return optionFunc(func(opts *Options) {
opts.ErrorProgressStreams = writers
})
}

// EventStreams allows specifying one or more channels to receive the Pulumi event stream
func EventStreams(channels ...chan<- events.EngineEvent) Option {
return optionFunc(func(opts *Options) {
opts.EventStreams = channels
})
}

// Option is a parameter to be applied to a Stack.Preview() operation
type Option interface {
ApplyOption(*Options)
}

// ---------------------------------- implementation details ----------------------------------

// Options is an implementation detail
type Options struct {
// ProgressStreams allows specifying one or more io.Writers to redirect incremental preview stdout
ProgressStreams []io.Writer
// ErrorProgressStreams allows specifying one or more io.Writers to redirect incremental preview stderr
ErrorProgressStreams []io.Writer
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
EventStreams []chan<- events.EngineEvent
}

type optionFunc func(*Options)

// ApplyOption is an implementation detail
func (o optionFunc) ApplyOption(opts *Options) {
o(opts)
}
68 changes: 68 additions & 0 deletions sdk/go/auto/optremoterefresh/optremoterefresh.go
@@ -0,0 +1,68 @@
// 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 optremoterefresh contains functional options to be used with remote stack refresh operations
// github.com/sdk/v3/go/auto RemoteStack.Refresh(...optremoterefresh.Option)
package optremoterefresh

import (
"io"

"github.com/pulumi/pulumi/sdk/v3/go/auto/events"
)

// ProgressStreams allows specifying one or more io.Writers to redirect incremental refresh stdout
func ProgressStreams(writers ...io.Writer) Option {
return optionFunc(func(opts *Options) {
opts.ProgressStreams = writers
})
}

// ErrorProgressStreams allows specifying one or more io.Writers to redirect incremental refresh stderr
func ErrorProgressStreams(writers ...io.Writer) Option {
return optionFunc(func(opts *Options) {
opts.ErrorProgressStreams = writers
})
}

// EventStreams allows specifying one or more channels to receive the Pulumi event stream
func EventStreams(channels ...chan<- events.EngineEvent) Option {
return optionFunc(func(opts *Options) {
opts.EventStreams = channels
})
}

// Option is a parameter to be applied to a Stack.Refresh() operation
type Option interface {
ApplyOption(*Options)
}

// ---------------------------------- implementation details ----------------------------------

// Options is an implementation detail
type Options struct {
// ProgressStreams allows specifying one or more io.Writers to redirect incremental refresh stdout
ProgressStreams []io.Writer
// ErrorProgressStreams allows specifying one or more io.Writers to redirect incremental refresh stderr
ErrorProgressStreams []io.Writer
// EventStreams allows specifying one or more channels to receive the Pulumi event stream
EventStreams []chan<- events.EngineEvent
}

type optionFunc func(*Options)

// ApplyOption is an implementation detail
func (o optionFunc) ApplyOption(opts *Options) {
o(opts)
}

0 comments on commit 8a98877

Please sign in to comment.