2022-10-25 21:45:02 +00:00
|
|
|
|
// Copyright 2016-2022, Pulumi Corporation.
|
2020-08-27 17:43:23 +00:00
|
|
|
|
//
|
|
|
|
|
// 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.
|
|
|
|
|
|
2020-07-28 05:26:39 +00:00
|
|
|
|
// Package auto contains the Pulumi Automation API, the programmatic interface for driving Pulumi programs
|
|
|
|
|
// without the CLI.
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// Generally this can be thought of as encapsulating the functionality of the CLI (`pulumi up`, `pulumi preview`,
|
|
|
|
|
// pulumi destroy`, `pulumi stack init`, etc.) but with more flexibility. This still requires a
|
2021-04-21 15:44:04 +00:00
|
|
|
|
// CLI binary to be installed and available on your $PATH.
|
2020-07-28 05:26:39 +00:00
|
|
|
|
//
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// In addition to fine-grained building blocks, Automation API provides three out of the box ways to work with Stacks:
|
|
|
|
|
//
|
2022-09-14 02:12:02 +00:00
|
|
|
|
// 1. Programs locally available on-disk and addressed via a filepath (NewStackLocalSource)
|
|
|
|
|
// stack, err := NewStackLocalSource(ctx, "myOrg/myProj/myStack", filepath.Join("..", "path", "to", "project"))
|
|
|
|
|
//
|
|
|
|
|
// 2. Programs fetched from a Git URL (NewStackRemoteSource)
|
|
|
|
|
// stack, err := NewStackRemoteSource(ctx, "myOrg/myProj/myStack", GitRepo{
|
|
|
|
|
// URL: "https://github.com/pulumi/test-repo.git",
|
|
|
|
|
// ProjectPath: filepath.Join("project", "path", "repo", "root", "relative"),
|
|
|
|
|
// })
|
|
|
|
|
//
|
|
|
|
|
// 3. Programs defined as a function alongside your Automation API code (NewStackInlineSource)
|
|
|
|
|
// stack, err := NewStackInlineSource(ctx, "myOrg/myProj/myStack", func(pCtx *pulumi.Context) error {
|
|
|
|
|
// bucket, err := s3.NewBucket(pCtx, "bucket", nil)
|
|
|
|
|
// if err != nil {
|
|
|
|
|
// return err
|
|
|
|
|
// }
|
|
|
|
|
// pCtx.Export("bucketName", bucket.Bucket)
|
|
|
|
|
// return nil
|
|
|
|
|
// })
|
|
|
|
|
//
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// Each of these creates a stack with access to the full range of Pulumi lifecycle methods
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// (up/preview/refresh/destroy), as well as methods for managing config, stack, and project settings.
|
2022-09-14 02:12:02 +00:00
|
|
|
|
//
|
|
|
|
|
// err := stack.SetConfig(ctx, "key", ConfigValue{ Value: "value", Secret: true })
|
|
|
|
|
// preRes, err := stack.Preview(ctx)
|
|
|
|
|
// // detailed info about results
|
|
|
|
|
// fmt.Println(preRes.prev.Steps[0].URN)
|
|
|
|
|
//
|
2020-07-28 05:26:39 +00:00
|
|
|
|
// The Automation API provides a natural way to orchestrate multiple stacks,
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// feeding the output of one stack as an input to the next as shown in the package-level example below.
|
2020-07-28 05:26:39 +00:00
|
|
|
|
// The package can be used for a number of use cases:
|
|
|
|
|
//
|
2022-09-14 02:12:02 +00:00
|
|
|
|
// - Driving pulumi deployments within CI/CD workflows
|
2020-07-28 05:26:39 +00:00
|
|
|
|
//
|
2022-09-14 02:12:02 +00:00
|
|
|
|
// - Integration testing
|
2020-07-28 05:26:39 +00:00
|
|
|
|
//
|
2022-09-14 02:12:02 +00:00
|
|
|
|
// - Multi-stage deployments such as blue-green deployment patterns
|
2020-07-28 05:26:39 +00:00
|
|
|
|
//
|
2022-09-14 02:12:02 +00:00
|
|
|
|
// - Deployments involving application code like database migrations
|
2020-07-28 05:26:39 +00:00
|
|
|
|
//
|
2022-09-14 02:12:02 +00:00
|
|
|
|
// - Building higher level tools, custom CLIs over pulumi, etc
|
2020-08-25 18:16:54 +00:00
|
|
|
|
//
|
2022-09-14 02:12:02 +00:00
|
|
|
|
// - Using pulumi behind a REST or GRPC API
|
2020-08-25 18:16:54 +00:00
|
|
|
|
//
|
2022-09-14 02:12:02 +00:00
|
|
|
|
// - Debugging Pulumi programs (by using a single main entrypoint with "inline" programs)
|
2020-09-02 06:28:46 +00:00
|
|
|
|
//
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// To enable a broad range of runtime customization the API defines a `Workspace` interface.
|
|
|
|
|
// A Workspace is the execution context containing a single Pulumi project, a program, and multiple stacks.
|
|
|
|
|
// Workspaces are used to manage the execution environment, providing various utilities such as plugin
|
|
|
|
|
// installation, environment configuration ($PULUMI_HOME), and creation, deletion, and listing of Stacks.
|
|
|
|
|
// Every Stack including those in the above examples are backed by a Workspace which can be accessed via:
|
2022-09-14 02:12:02 +00:00
|
|
|
|
//
|
|
|
|
|
// w = stack.Workspace()
|
|
|
|
|
// err := w.InstallPlugin("aws", "v3.2.0")
|
|
|
|
|
//
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// Workspaces can be explicitly created and customized beyond the three Stack creation helpers noted above:
|
2022-09-14 02:12:02 +00:00
|
|
|
|
//
|
|
|
|
|
// w, err := NewLocalWorkspace(ctx, WorkDir(filepath.Join(".", "project", "path"), PulumiHome("~/.pulumi"))
|
|
|
|
|
// s := NewStack(ctx, "org/proj/stack", w)
|
|
|
|
|
//
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// A default implementation of workspace is provided as `LocalWorkspace`. This implementation relies on Pulumi.yaml
|
|
|
|
|
// and Pulumi.<stack>.yaml as the intermediate format for Project and Stack settings. Modifying ProjectSettings will
|
|
|
|
|
// alter the Workspace Pulumi.yaml file, and setting config on a Stack will modify the Pulumi.<stack>.yaml file.
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// This is identical to the behavior of Pulumi CLI driven workspaces. Custom Workspace
|
|
|
|
|
// implementations can be used to store Project and Stack settings as well as Config in a different format,
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// such as an in-memory data structure, a shared persistent SQL database, or cloud object storage. Regardless of
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// the backing Workspace implementation, the Pulumi SaaS Console will still be able to display configuration
|
|
|
|
|
// applied to updates as it does with the local version of the Workspace today.
|
2020-08-21 02:37:39 +00:00
|
|
|
|
//
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// The Automation API also provides error handling utilities to detect common cases such as concurrent update
|
|
|
|
|
// conflicts:
|
2022-09-14 02:12:02 +00:00
|
|
|
|
//
|
|
|
|
|
// uRes, err :=stack.Up(ctx)
|
|
|
|
|
// if err != nil && IsConcurrentUpdateError(err) { /* retry logic here */ }
|
2020-07-07 21:25:11 +00:00
|
|
|
|
package auto
|
|
|
|
|
|
2020-07-17 17:14:30 +00:00
|
|
|
|
import (
|
2020-08-22 05:20:32 +00:00
|
|
|
|
"context"
|
2020-08-21 02:37:39 +00:00
|
|
|
|
"encoding/json"
|
2023-01-06 22:09:19 +00:00
|
|
|
|
"errors"
|
2020-08-21 16:49:46 +00:00
|
|
|
|
"fmt"
|
2020-09-15 01:56:04 +00:00
|
|
|
|
"io"
|
2021-03-11 04:49:48 +00:00
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2020-09-14 21:08:10 +00:00
|
|
|
|
"regexp"
|
2020-09-15 00:40:17 +00:00
|
|
|
|
"runtime"
|
2023-12-12 12:19:42 +00:00
|
|
|
|
"strconv"
|
2020-08-21 02:37:39 +00:00
|
|
|
|
"strings"
|
2020-09-15 00:40:17 +00:00
|
|
|
|
"sync"
|
2020-07-24 08:31:54 +00:00
|
|
|
|
|
2024-02-20 02:59:35 +00:00
|
|
|
|
"github.com/blang/semver"
|
2021-03-11 04:49:48 +00:00
|
|
|
|
"github.com/nxadm/tail"
|
2020-09-15 00:40:17 +00:00
|
|
|
|
"google.golang.org/grpc"
|
2024-01-17 09:35:20 +00:00
|
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
2020-09-15 00:40:17 +00:00
|
|
|
|
|
2021-03-17 13:20:05 +00:00
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/debug"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/events"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/optdestroy"
|
2022-06-24 17:00:09 +00:00
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/opthistory"
|
2021-03-17 13:20:05 +00:00
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/optpreview"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/optrefresh"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/auto/optup"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/constant"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
2023-06-28 16:02:04 +00:00
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
|
2021-03-17 13:20:05 +00:00
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
|
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
2020-07-17 17:14:30 +00:00
|
|
|
|
)
|
2020-07-07 21:25:11 +00:00
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// Stack is an isolated, independently configurable instance of a Pulumi program.
|
|
|
|
|
// Stack exposes methods for the full pulumi lifecycle (up/preview/refresh/destroy), as well as managing configuration.
|
|
|
|
|
// Multiple Stacks are commonly used to denote different phases of development
|
|
|
|
|
// (such as development, staging and production) or feature branches (such as feature-x-dev, jane-feature-x-dev).
|
2020-08-21 02:37:39 +00:00
|
|
|
|
type Stack struct {
|
|
|
|
|
workspace Workspace
|
2020-09-15 00:45:07 +00:00
|
|
|
|
stackName string
|
2020-07-17 17:14:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:45:07 +00:00
|
|
|
|
// FullyQualifiedStackName returns a stack name formatted with the greatest possible specificity:
|
|
|
|
|
// org/project/stack or user/project/stack
|
|
|
|
|
// Using this format avoids ambiguity in stack identity guards creating or selecting the wrong stack.
|
2024-01-30 15:53:10 +00:00
|
|
|
|
// Note that legacy diy backends (local file, S3, Azure Blob) do not support stack names in this
|
2020-09-15 00:45:07 +00:00
|
|
|
|
// format, and instead only use the stack name without an org/user or project to qualify it.
|
2024-01-30 15:53:10 +00:00
|
|
|
|
// See: https://github.com/pulumi/pulumi/issues/2522.
|
|
|
|
|
// Non-legacy diy backends do support the org/project/stack format but org must be set to "organization".
|
2020-08-21 16:49:46 +00:00
|
|
|
|
func FullyQualifiedStackName(org, project, stack string) string {
|
|
|
|
|
return fmt.Sprintf("%s/%s/%s", org, project, stack)
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:45:07 +00:00
|
|
|
|
// NewStack creates a new stack using the given workspace, and stack name.
|
2020-08-21 02:37:39 +00:00
|
|
|
|
// It fails if a stack with that name already exists
|
2020-09-15 00:45:07 +00:00
|
|
|
|
func NewStack(ctx context.Context, stackName string, ws Workspace) (Stack, error) {
|
2023-01-11 15:59:43 +00:00
|
|
|
|
s := Stack{
|
2020-08-21 02:37:39 +00:00
|
|
|
|
workspace: ws,
|
2020-09-15 00:45:07 +00:00
|
|
|
|
stackName: stackName,
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:45:07 +00:00
|
|
|
|
err := ws.CreateStack(ctx, stackName)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return s, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:45:07 +00:00
|
|
|
|
// SelectStack selects stack using the given workspace, and stack name.
|
2021-04-22 13:10:39 +00:00
|
|
|
|
// It returns an error if the given Stack does not exist.
|
2020-09-15 00:45:07 +00:00
|
|
|
|
func SelectStack(ctx context.Context, stackName string, ws Workspace) (Stack, error) {
|
2023-01-11 15:59:43 +00:00
|
|
|
|
s := Stack{
|
2020-08-21 02:37:39 +00:00
|
|
|
|
workspace: ws,
|
2020-09-15 00:45:07 +00:00
|
|
|
|
stackName: stackName,
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:45:07 +00:00
|
|
|
|
err := ws.SelectStack(ctx, stackName)
|
2020-07-17 17:14:30 +00:00
|
|
|
|
if err != nil {
|
2020-08-21 02:37:39 +00:00
|
|
|
|
return s, err
|
2020-07-17 17:14:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
return s, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-18 17:44:09 +00:00
|
|
|
|
// UpsertStack tries to select a stack using the given workspace and
|
|
|
|
|
// stack name, or falls back to trying to create the stack if
|
|
|
|
|
// it does not exist.
|
2020-09-15 00:45:07 +00:00
|
|
|
|
func UpsertStack(ctx context.Context, stackName string, ws Workspace) (Stack, error) {
|
2022-11-18 17:44:09 +00:00
|
|
|
|
s, err := SelectStack(ctx, stackName, ws)
|
|
|
|
|
// If the stack is not found, attempt to create it.
|
|
|
|
|
if err != nil && IsSelectStack404Error(err) {
|
|
|
|
|
return NewStack(ctx, stackName, ws)
|
2020-09-09 22:20:41 +00:00
|
|
|
|
}
|
2022-11-18 17:44:09 +00:00
|
|
|
|
return s, err
|
2020-09-09 22:20:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:45:07 +00:00
|
|
|
|
// Name returns the stack name
|
2020-08-21 02:37:39 +00:00
|
|
|
|
func (s *Stack) Name() string {
|
2020-09-15 00:45:07 +00:00
|
|
|
|
return s.stackName
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Workspace returns the underlying Workspace backing the Stack.
|
|
|
|
|
// This handles state associated with the Project and child Stacks including
|
|
|
|
|
// settings, configuration, and environment.
|
|
|
|
|
func (s *Stack) Workspace() Workspace {
|
|
|
|
|
return s.workspace
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-12 08:37:33 +00:00
|
|
|
|
// ChangeSecretsProvider edits the secrets provider for the stack.
|
|
|
|
|
func (s *Stack) ChangeSecretsProvider(
|
|
|
|
|
ctx context.Context, newSecretsProvider string, opts *ChangeSecretsProviderOptions,
|
|
|
|
|
) error {
|
|
|
|
|
return s.workspace.ChangeStackSecretsProvider(ctx, s.stackName, newSecretsProvider, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
// Preview preforms a dry-run update to a stack, returning pending changes.
|
2023-05-14 04:07:20 +00:00
|
|
|
|
// https://www.pulumi.com/docs/cli/commands/pulumi_preview/
|
2020-08-23 17:25:17 +00:00
|
|
|
|
func (s *Stack) Preview(ctx context.Context, opts ...optpreview.Option) (PreviewResult, error) {
|
2020-08-21 02:37:39 +00:00
|
|
|
|
var res PreviewResult
|
|
|
|
|
|
2020-08-23 17:25:17 +00:00
|
|
|
|
preOpts := &optpreview.Options{}
|
|
|
|
|
for _, o := range opts {
|
|
|
|
|
o.ApplyOption(preOpts)
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 16:36:39 +00:00
|
|
|
|
bufferSizeHint := len(preOpts.Replace) + len(preOpts.Target) +
|
2023-01-11 20:52:51 +00:00
|
|
|
|
len(preOpts.PolicyPacks) + len(preOpts.PolicyPackConfigs)
|
2023-06-28 16:02:04 +00:00
|
|
|
|
sharedArgs := slice.Prealloc[string](bufferSizeHint)
|
2020-12-22 23:45:37 +00:00
|
|
|
|
|
|
|
|
|
sharedArgs = debug.AddArgs(&preOpts.DebugLogOpts, sharedArgs)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
if preOpts.Message != "" {
|
|
|
|
|
sharedArgs = append(sharedArgs, fmt.Sprintf("--message=%q", preOpts.Message))
|
|
|
|
|
}
|
|
|
|
|
if preOpts.ExpectNoChanges {
|
|
|
|
|
sharedArgs = append(sharedArgs, "--expect-no-changes")
|
|
|
|
|
}
|
2021-02-24 01:38:28 +00:00
|
|
|
|
if preOpts.Diff {
|
|
|
|
|
sharedArgs = append(sharedArgs, "--diff")
|
|
|
|
|
}
|
2020-08-23 17:25:17 +00:00
|
|
|
|
for _, rURN := range preOpts.Replace {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--replace="+rURN)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
}
|
|
|
|
|
for _, tURN := range preOpts.Target {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--target="+tURN)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
}
|
2022-06-16 00:50:38 +00:00
|
|
|
|
for _, pack := range preOpts.PolicyPacks {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--policy-pack="+pack)
|
2022-06-16 00:50:38 +00:00
|
|
|
|
}
|
|
|
|
|
for _, packConfig := range preOpts.PolicyPackConfigs {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--policy-pack-config="+packConfig)
|
2022-06-16 00:50:38 +00:00
|
|
|
|
}
|
2020-08-23 17:25:17 +00:00
|
|
|
|
if preOpts.TargetDependents {
|
|
|
|
|
sharedArgs = append(sharedArgs, "--target-dependents")
|
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
if preOpts.Parallel > 0 {
|
|
|
|
|
sharedArgs = append(sharedArgs, fmt.Sprintf("--parallel=%d", preOpts.Parallel))
|
|
|
|
|
}
|
2021-04-30 14:26:23 +00:00
|
|
|
|
if preOpts.UserAgent != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--exec-agent="+preOpts.UserAgent)
|
2021-04-30 14:26:23 +00:00
|
|
|
|
}
|
2022-01-24 22:34:22 +00:00
|
|
|
|
if preOpts.Color != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--color="+preOpts.Color)
|
2022-01-24 22:34:22 +00:00
|
|
|
|
}
|
2022-04-22 16:50:40 +00:00
|
|
|
|
if preOpts.Plan != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--save-plan="+preOpts.Plan)
|
2022-04-22 16:50:40 +00:00
|
|
|
|
}
|
2024-02-02 22:26:41 +00:00
|
|
|
|
if preOpts.Refresh {
|
|
|
|
|
sharedArgs = append(sharedArgs, "--refresh")
|
|
|
|
|
}
|
2020-08-23 17:25:17 +00:00
|
|
|
|
|
2022-10-25 21:45:02 +00:00
|
|
|
|
// Apply the remote args, if needed.
|
|
|
|
|
sharedArgs = append(sharedArgs, s.remoteArgs()...)
|
|
|
|
|
|
2021-03-11 04:49:48 +00:00
|
|
|
|
kind, args := constant.ExecKindAutoLocal, []string{"preview"}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
if program := s.Workspace().Program(); program != nil {
|
|
|
|
|
server, err := startLanguageRuntimeServer(program)
|
2020-07-24 08:31:54 +00:00
|
|
|
|
if err != nil {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return res, err
|
2020-07-24 08:31:54 +00:00
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
defer contract.IgnoreClose(server)
|
|
|
|
|
|
|
|
|
|
kind, args = constant.ExecKindAutoInline, append(args, "--client="+server.address)
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--exec-kind="+kind)
|
2020-09-15 00:40:17 +00:00
|
|
|
|
args = append(args, sharedArgs...)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
|
|
|
|
|
var summaryEvents []apitype.SummaryEvent
|
|
|
|
|
eventChannel := make(chan events.EngineEvent)
|
2022-04-25 16:26:01 +00:00
|
|
|
|
eventsDone := make(chan bool)
|
|
|
|
|
go func() {
|
2021-03-11 04:49:48 +00:00
|
|
|
|
for {
|
|
|
|
|
event, ok := <-eventChannel
|
|
|
|
|
if !ok {
|
2022-04-25 16:26:01 +00:00
|
|
|
|
close(eventsDone)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if event.SummaryEvent != nil {
|
|
|
|
|
summaryEvents = append(summaryEvents, *event.SummaryEvent)
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-25 16:26:01 +00:00
|
|
|
|
}()
|
2021-03-11 04:49:48 +00:00
|
|
|
|
|
|
|
|
|
eventChannels := []chan<- events.EngineEvent{eventChannel}
|
|
|
|
|
eventChannels = append(eventChannels, preOpts.EventStreams...)
|
|
|
|
|
|
|
|
|
|
t, err := tailLogs("preview", eventChannels)
|
2020-09-15 00:40:17 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, fmt.Errorf("failed to tail logs: %w", err)
|
2020-07-22 21:28:03 +00:00
|
|
|
|
}
|
2022-04-25 16:26:01 +00:00
|
|
|
|
defer t.Close()
|
2021-03-11 04:49:48 +00:00
|
|
|
|
args = append(args, "--event-log", t.Filename)
|
2020-07-22 21:28:03 +00:00
|
|
|
|
|
2022-07-19 17:10:10 +00:00
|
|
|
|
stdout, stderr, code, err := s.runPulumiCmdSync(
|
|
|
|
|
ctx,
|
|
|
|
|
preOpts.ProgressStreams, /* additionalOutput */
|
|
|
|
|
preOpts.ErrorProgressStreams, /* additionalErrorOutput */
|
|
|
|
|
args...,
|
|
|
|
|
)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, newAutoError(fmt.Errorf("failed to run preview: %w", err), stdout, stderr, code)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 16:26:01 +00:00
|
|
|
|
// Close the file watcher wait for all events to send
|
|
|
|
|
t.Close()
|
|
|
|
|
<-eventsDone
|
|
|
|
|
|
2021-03-11 04:49:48 +00:00
|
|
|
|
if len(summaryEvents) == 0 {
|
|
|
|
|
return res, newAutoError(errors.New("failed to get preview summary"), stdout, stderr, code)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2021-03-11 04:49:48 +00:00
|
|
|
|
if len(summaryEvents) > 1 {
|
|
|
|
|
return res, newAutoError(errors.New("got multiple preview summaries"), stdout, stderr, code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.StdOut = stdout
|
|
|
|
|
res.StdErr = stderr
|
|
|
|
|
res.ChangeSummary = summaryEvents[0].ResourceChanges
|
2020-08-21 02:37:39 +00:00
|
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Up creates or updates the resources in a stack by executing the program in the Workspace.
|
2023-05-14 04:07:20 +00:00
|
|
|
|
// https://www.pulumi.com/docs/cli/commands/pulumi_up/
|
2020-08-23 17:25:17 +00:00
|
|
|
|
func (s *Stack) Up(ctx context.Context, opts ...optup.Option) (UpResult, error) {
|
2020-08-21 02:37:39 +00:00
|
|
|
|
var res UpResult
|
|
|
|
|
|
2020-08-23 17:25:17 +00:00
|
|
|
|
upOpts := &optup.Options{}
|
|
|
|
|
for _, o := range opts {
|
|
|
|
|
o.ApplyOption(upOpts)
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 16:36:39 +00:00
|
|
|
|
bufferSizeHint := len(upOpts.Replace) + len(upOpts.Target) + len(upOpts.PolicyPacks) + len(upOpts.PolicyPackConfigs)
|
2023-06-28 16:02:04 +00:00
|
|
|
|
sharedArgs := slice.Prealloc[string](bufferSizeHint)
|
2020-12-22 23:45:37 +00:00
|
|
|
|
|
|
|
|
|
sharedArgs = debug.AddArgs(&upOpts.DebugLogOpts, sharedArgs)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
if upOpts.Message != "" {
|
|
|
|
|
sharedArgs = append(sharedArgs, fmt.Sprintf("--message=%q", upOpts.Message))
|
|
|
|
|
}
|
|
|
|
|
if upOpts.ExpectNoChanges {
|
|
|
|
|
sharedArgs = append(sharedArgs, "--expect-no-changes")
|
|
|
|
|
}
|
2021-02-24 01:38:28 +00:00
|
|
|
|
if upOpts.Diff {
|
|
|
|
|
sharedArgs = append(sharedArgs, "--diff")
|
|
|
|
|
}
|
2020-08-23 17:25:17 +00:00
|
|
|
|
for _, rURN := range upOpts.Replace {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--replace="+rURN)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
}
|
|
|
|
|
for _, tURN := range upOpts.Target {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--target="+tURN)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
}
|
2022-06-16 00:50:38 +00:00
|
|
|
|
for _, pack := range upOpts.PolicyPacks {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--policy-pack="+pack)
|
2022-06-16 00:50:38 +00:00
|
|
|
|
}
|
|
|
|
|
for _, packConfig := range upOpts.PolicyPackConfigs {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--policy-pack-config="+packConfig)
|
2022-06-16 00:50:38 +00:00
|
|
|
|
}
|
2020-08-23 17:25:17 +00:00
|
|
|
|
if upOpts.TargetDependents {
|
|
|
|
|
sharedArgs = append(sharedArgs, "--target-dependents")
|
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
if upOpts.Parallel > 0 {
|
|
|
|
|
sharedArgs = append(sharedArgs, fmt.Sprintf("--parallel=%d", upOpts.Parallel))
|
|
|
|
|
}
|
2021-04-30 14:26:23 +00:00
|
|
|
|
if upOpts.UserAgent != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--exec-agent="+upOpts.UserAgent)
|
2021-04-30 14:26:23 +00:00
|
|
|
|
}
|
2022-01-24 22:34:22 +00:00
|
|
|
|
if upOpts.Color != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--color="+upOpts.Color)
|
2022-01-24 22:34:22 +00:00
|
|
|
|
}
|
2022-04-22 16:50:40 +00:00
|
|
|
|
if upOpts.Plan != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
sharedArgs = append(sharedArgs, "--plan="+upOpts.Plan)
|
2022-04-22 16:50:40 +00:00
|
|
|
|
}
|
2024-02-02 22:26:41 +00:00
|
|
|
|
if upOpts.Refresh {
|
|
|
|
|
sharedArgs = append(sharedArgs, "--refresh")
|
|
|
|
|
}
|
2020-08-23 17:25:17 +00:00
|
|
|
|
|
2022-10-25 21:45:02 +00:00
|
|
|
|
// Apply the remote args, if needed.
|
|
|
|
|
sharedArgs = append(sharedArgs, s.remoteArgs()...)
|
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
kind, args := constant.ExecKindAutoLocal, []string{"up", "--yes", "--skip-preview"}
|
|
|
|
|
if program := s.Workspace().Program(); program != nil {
|
|
|
|
|
server, err := startLanguageRuntimeServer(program)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return res, err
|
2020-08-23 17:25:17 +00:00
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
defer contract.IgnoreClose(server)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
kind, args = constant.ExecKindAutoInline, append(args, "--client="+server.address)
|
|
|
|
|
}
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--exec-kind="+kind)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
|
|
|
|
|
if len(upOpts.EventStreams) > 0 {
|
|
|
|
|
eventChannels := upOpts.EventStreams
|
|
|
|
|
t, err := tailLogs("up", eventChannels)
|
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, fmt.Errorf("failed to tail logs: %w", err)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
}
|
2022-04-25 16:26:01 +00:00
|
|
|
|
defer t.Close()
|
2021-03-11 04:49:48 +00:00
|
|
|
|
args = append(args, "--event-log", t.Filename)
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
args = append(args, sharedArgs...)
|
2022-07-19 17:10:10 +00:00
|
|
|
|
stdout, stderr, code, err := s.runPulumiCmdSync(ctx, upOpts.ProgressStreams, upOpts.ErrorProgressStreams, args...)
|
2020-09-15 00:40:17 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, newAutoError(fmt.Errorf("failed to run update: %w", err), stdout, stderr, code)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 05:20:32 +00:00
|
|
|
|
outs, err := s.Outputs(ctx)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 17:00:09 +00:00
|
|
|
|
historyOpts := []opthistory.Option{}
|
|
|
|
|
if upOpts.ShowSecrets != nil {
|
|
|
|
|
historyOpts = append(historyOpts, opthistory.ShowSecrets(*upOpts.ShowSecrets))
|
|
|
|
|
}
|
2022-10-25 21:45:02 +00:00
|
|
|
|
// If it's a remote workspace, explicitly set ShowSecrets to false to prevent attempting to
|
|
|
|
|
// load the project file.
|
|
|
|
|
if s.isRemote() {
|
|
|
|
|
historyOpts = append(historyOpts, opthistory.ShowSecrets(false))
|
|
|
|
|
}
|
2022-06-24 17:00:09 +00:00
|
|
|
|
history, err := s.History(ctx, 1 /*pageSize*/, 1 /*page*/, historyOpts...)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return res, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = UpResult{
|
|
|
|
|
Outputs: outs,
|
|
|
|
|
StdOut: stdout,
|
|
|
|
|
StdErr: stderr,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(history) > 0 {
|
|
|
|
|
res.Summary = history[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 02:59:35 +00:00
|
|
|
|
func (s *Stack) PreviewRefresh(ctx context.Context, opts ...optrefresh.Option) (PreviewResult, error) {
|
|
|
|
|
var res PreviewResult
|
|
|
|
|
|
|
|
|
|
// 3.105.0 added this flag (https://github.com/pulumi/pulumi/releases/tag/v3.105.0)
|
|
|
|
|
if s.Workspace().PulumiCommand().Version().LT(semver.Version{Major: 3, Minor: 105}) {
|
|
|
|
|
return res, fmt.Errorf("PreviewRefresh requires Pulumi CLI version >= 3.105.0")
|
|
|
|
|
}
|
2020-08-21 02:37:39 +00:00
|
|
|
|
|
2020-08-23 17:25:17 +00:00
|
|
|
|
refreshOpts := &optrefresh.Options{}
|
|
|
|
|
for _, o := range opts {
|
|
|
|
|
o.ApplyOption(refreshOpts)
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 02:59:35 +00:00
|
|
|
|
args := refreshOptsToCmd(refreshOpts, s, true /*isPreview*/)
|
2020-12-22 23:45:37 +00:00
|
|
|
|
|
2024-02-20 02:59:35 +00:00
|
|
|
|
var summaryEvents []apitype.SummaryEvent
|
|
|
|
|
eventChannel := make(chan events.EngineEvent)
|
|
|
|
|
eventsDone := make(chan bool)
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
event, ok := <-eventChannel
|
|
|
|
|
if !ok {
|
|
|
|
|
close(eventsDone)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if event.SummaryEvent != nil {
|
|
|
|
|
summaryEvents = append(summaryEvents, *event.SummaryEvent)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
eventChannels := []chan<- events.EngineEvent{eventChannel}
|
|
|
|
|
eventChannels = append(eventChannels, refreshOpts.EventStreams...)
|
|
|
|
|
|
|
|
|
|
t, err := tailLogs("refresh", eventChannels)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return res, fmt.Errorf("failed to tail logs: %w", err)
|
2020-08-29 02:17:46 +00:00
|
|
|
|
}
|
2024-02-20 02:59:35 +00:00
|
|
|
|
defer t.Close()
|
|
|
|
|
args = append(args, "--event-log", t.Filename)
|
|
|
|
|
|
|
|
|
|
stdout, stderr, code, err := s.runPulumiCmdSync(
|
|
|
|
|
ctx,
|
|
|
|
|
refreshOpts.ProgressStreams, /* additionalOutputs */
|
|
|
|
|
refreshOpts.ErrorProgressStreams, /* additionalErrorOutputs */
|
|
|
|
|
args...,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return res, newAutoError(fmt.Errorf("failed to preview refresh: %w", err), stdout, stderr, code)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
}
|
2024-02-20 02:59:35 +00:00
|
|
|
|
|
|
|
|
|
// Close the file watcher wait for all events to send
|
|
|
|
|
t.Close()
|
|
|
|
|
<-eventsDone
|
|
|
|
|
|
|
|
|
|
if len(summaryEvents) == 0 {
|
|
|
|
|
return res, newAutoError(errors.New("failed to get preview refresh summary"), stdout, stderr, code)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
}
|
2024-02-20 02:59:35 +00:00
|
|
|
|
if len(summaryEvents) > 1 {
|
|
|
|
|
return res, newAutoError(errors.New("got multiple preview refresh summaries"), stdout, stderr, code)
|
2021-04-30 14:26:23 +00:00
|
|
|
|
}
|
2024-02-20 02:59:35 +00:00
|
|
|
|
|
|
|
|
|
res = PreviewResult{
|
|
|
|
|
ChangeSummary: summaryEvents[0].ResourceChanges,
|
|
|
|
|
StdOut: stdout,
|
|
|
|
|
StdErr: stderr,
|
2022-01-24 22:34:22 +00:00
|
|
|
|
}
|
2024-02-20 02:59:35 +00:00
|
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Refresh compares the current stack’s resource state with the state known to exist in the actual
|
|
|
|
|
// cloud provider. Any such changes are adopted into the current stack.
|
|
|
|
|
func (s *Stack) Refresh(ctx context.Context, opts ...optrefresh.Option) (RefreshResult, error) {
|
|
|
|
|
var res RefreshResult
|
|
|
|
|
|
|
|
|
|
refreshOpts := &optrefresh.Options{}
|
|
|
|
|
for _, o := range opts {
|
|
|
|
|
o.ApplyOption(refreshOpts)
|
2020-08-31 16:07:59 +00:00
|
|
|
|
}
|
2024-02-20 02:59:35 +00:00
|
|
|
|
|
|
|
|
|
args := refreshOptsToCmd(refreshOpts, s, false /*isPreview*/)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
|
2021-03-11 04:49:48 +00:00
|
|
|
|
if len(refreshOpts.EventStreams) > 0 {
|
|
|
|
|
eventChannels := refreshOpts.EventStreams
|
|
|
|
|
t, err := tailLogs("refresh", eventChannels)
|
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, fmt.Errorf("failed to tail logs: %w", err)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
}
|
2022-04-25 16:26:01 +00:00
|
|
|
|
defer t.Close()
|
2021-03-11 04:49:48 +00:00
|
|
|
|
args = append(args, "--event-log", t.Filename)
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 17:10:10 +00:00
|
|
|
|
stdout, stderr, code, err := s.runPulumiCmdSync(
|
|
|
|
|
ctx,
|
|
|
|
|
refreshOpts.ProgressStreams, /* additionalOutputs */
|
|
|
|
|
refreshOpts.ErrorProgressStreams, /* additionalErrorOutputs */
|
|
|
|
|
args...,
|
|
|
|
|
)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, newAutoError(fmt.Errorf("failed to refresh stack: %w", err), stdout, stderr, code)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 17:00:09 +00:00
|
|
|
|
historyOpts := []opthistory.Option{}
|
|
|
|
|
if showSecrets := refreshOpts.ShowSecrets; showSecrets != nil {
|
|
|
|
|
historyOpts = append(historyOpts, opthistory.ShowSecrets(*showSecrets))
|
|
|
|
|
}
|
2022-10-25 21:45:02 +00:00
|
|
|
|
// If it's a remote workspace, explicitly set ShowSecrets to false to prevent attempting to
|
|
|
|
|
// load the project file.
|
|
|
|
|
if s.isRemote() {
|
|
|
|
|
historyOpts = append(historyOpts, opthistory.ShowSecrets(false))
|
|
|
|
|
}
|
2022-06-24 17:00:09 +00:00
|
|
|
|
history, err := s.History(ctx, 1 /*pageSize*/, 1 /*page*/, historyOpts...)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, fmt.Errorf("failed to refresh stack: %w", err)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var summary UpdateSummary
|
|
|
|
|
if len(history) > 0 {
|
|
|
|
|
summary = history[0]
|
2020-07-17 17:14:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
res = RefreshResult{
|
|
|
|
|
Summary: summary,
|
|
|
|
|
StdOut: stdout,
|
|
|
|
|
StdErr: stderr,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-20 02:59:35 +00:00
|
|
|
|
func refreshOptsToCmd(o *optrefresh.Options, s *Stack, isPreview bool) []string {
|
|
|
|
|
args := slice.Prealloc[string](len(o.Target))
|
|
|
|
|
|
|
|
|
|
args = debug.AddArgs(&o.DebugLogOpts, args)
|
|
|
|
|
args = append(args, "refresh")
|
|
|
|
|
if isPreview {
|
|
|
|
|
args = append(args, "--preview-only")
|
|
|
|
|
} else {
|
|
|
|
|
args = append(args, "--yes", "--skip-preview")
|
|
|
|
|
}
|
|
|
|
|
if o.Message != "" {
|
|
|
|
|
args = append(args, fmt.Sprintf("--message=%q", o.Message))
|
|
|
|
|
}
|
|
|
|
|
if o.ExpectNoChanges {
|
|
|
|
|
args = append(args, "--expect-no-changes")
|
|
|
|
|
}
|
|
|
|
|
for _, tURN := range o.Target {
|
|
|
|
|
args = append(args, "--target="+tURN)
|
|
|
|
|
}
|
|
|
|
|
if o.Parallel > 0 {
|
|
|
|
|
args = append(args, fmt.Sprintf("--parallel=%d", o.Parallel))
|
|
|
|
|
}
|
|
|
|
|
if o.UserAgent != "" {
|
|
|
|
|
args = append(args, "--exec-agent="+o.UserAgent)
|
|
|
|
|
}
|
|
|
|
|
if o.Color != "" {
|
|
|
|
|
args = append(args, "--color="+o.Color)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply the remote args, if needed.
|
|
|
|
|
args = append(args, s.remoteArgs()...)
|
|
|
|
|
|
|
|
|
|
execKind := constant.ExecKindAutoLocal
|
|
|
|
|
if s.Workspace().Program() != nil {
|
|
|
|
|
execKind = constant.ExecKindAutoInline
|
|
|
|
|
}
|
|
|
|
|
args = append(args, "--exec-kind="+execKind)
|
|
|
|
|
|
|
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// Destroy deletes all resources in a stack, leaving all history and configuration intact.
|
2020-08-23 17:25:17 +00:00
|
|
|
|
func (s *Stack) Destroy(ctx context.Context, opts ...optdestroy.Option) (DestroyResult, error) {
|
2020-08-21 02:37:39 +00:00
|
|
|
|
var res DestroyResult
|
|
|
|
|
|
2020-08-23 17:25:17 +00:00
|
|
|
|
destroyOpts := &optdestroy.Options{}
|
|
|
|
|
for _, o := range opts {
|
|
|
|
|
o.ApplyOption(destroyOpts)
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-28 16:02:04 +00:00
|
|
|
|
args := slice.Prealloc[string](len(destroyOpts.Target))
|
2020-12-22 23:45:37 +00:00
|
|
|
|
|
|
|
|
|
args = debug.AddArgs(&destroyOpts.DebugLogOpts, args)
|
|
|
|
|
args = append(args, "destroy", "--yes", "--skip-preview")
|
2020-08-23 17:25:17 +00:00
|
|
|
|
if destroyOpts.Message != "" {
|
|
|
|
|
args = append(args, fmt.Sprintf("--message=%q", destroyOpts.Message))
|
|
|
|
|
}
|
|
|
|
|
for _, tURN := range destroyOpts.Target {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--target="+tURN)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
}
|
|
|
|
|
if destroyOpts.TargetDependents {
|
|
|
|
|
args = append(args, "--target-dependents")
|
|
|
|
|
}
|
|
|
|
|
if destroyOpts.Parallel > 0 {
|
|
|
|
|
args = append(args, fmt.Sprintf("--parallel=%d", destroyOpts.Parallel))
|
|
|
|
|
}
|
2021-04-30 14:26:23 +00:00
|
|
|
|
if destroyOpts.UserAgent != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--exec-agent="+destroyOpts.UserAgent)
|
2021-04-30 14:26:23 +00:00
|
|
|
|
}
|
2022-01-24 22:34:22 +00:00
|
|
|
|
if destroyOpts.Color != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--color="+destroyOpts.Color)
|
2022-01-24 22:34:22 +00:00
|
|
|
|
}
|
2024-02-02 22:26:41 +00:00
|
|
|
|
if destroyOpts.Refresh {
|
|
|
|
|
args = append(args, "--refresh")
|
|
|
|
|
}
|
2020-08-31 16:07:59 +00:00
|
|
|
|
execKind := constant.ExecKindAutoLocal
|
|
|
|
|
if s.Workspace().Program() != nil {
|
|
|
|
|
execKind = constant.ExecKindAutoInline
|
|
|
|
|
}
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--exec-kind="+execKind)
|
2020-08-23 17:25:17 +00:00
|
|
|
|
|
2021-03-11 04:49:48 +00:00
|
|
|
|
if len(destroyOpts.EventStreams) > 0 {
|
|
|
|
|
eventChannels := destroyOpts.EventStreams
|
|
|
|
|
t, err := tailLogs("destroy", eventChannels)
|
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, fmt.Errorf("failed to tail logs: %w", err)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
}
|
2022-04-25 16:26:01 +00:00
|
|
|
|
defer t.Close()
|
2021-03-11 04:49:48 +00:00
|
|
|
|
args = append(args, "--event-log", t.Filename)
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 21:45:02 +00:00
|
|
|
|
// Apply the remote args, if needed.
|
|
|
|
|
args = append(args, s.remoteArgs()...)
|
|
|
|
|
|
2022-07-19 17:10:10 +00:00
|
|
|
|
stdout, stderr, code, err := s.runPulumiCmdSync(
|
|
|
|
|
ctx,
|
|
|
|
|
destroyOpts.ProgressStreams, /* additionalOutputs */
|
|
|
|
|
destroyOpts.ErrorProgressStreams, /* additionalErrorOutputs */
|
|
|
|
|
args...,
|
|
|
|
|
)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, newAutoError(fmt.Errorf("failed to destroy stack: %w", err), stdout, stderr, code)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-24 17:00:09 +00:00
|
|
|
|
historyOpts := []opthistory.Option{}
|
|
|
|
|
if showSecrets := destroyOpts.ShowSecrets; showSecrets != nil {
|
|
|
|
|
historyOpts = append(historyOpts, opthistory.ShowSecrets(*showSecrets))
|
|
|
|
|
}
|
2022-10-25 21:45:02 +00:00
|
|
|
|
// If it's a remote workspace, explicitly set ShowSecrets to false to prevent attempting to
|
|
|
|
|
// load the project file.
|
|
|
|
|
if s.isRemote() {
|
|
|
|
|
historyOpts = append(historyOpts, opthistory.ShowSecrets(false))
|
|
|
|
|
}
|
2022-06-24 17:00:09 +00:00
|
|
|
|
history, err := s.History(ctx, 1 /*pageSize*/, 1 /*page*/, historyOpts...)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return res, fmt.Errorf("failed to destroy stack: %w", err)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var summary UpdateSummary
|
|
|
|
|
if len(history) > 0 {
|
|
|
|
|
summary = history[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res = DestroyResult{
|
|
|
|
|
Summary: summary,
|
|
|
|
|
StdOut: stdout,
|
|
|
|
|
StdErr: stderr,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// Outputs get the current set of Stack outputs from the last Stack.Up().
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) Outputs(ctx context.Context) (OutputMap, error) {
|
2021-04-26 23:32:30 +00:00
|
|
|
|
return s.Workspace().StackOutputs(ctx, s.Name())
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// History returns a list summarizing all previous and current results from Stack lifecycle operations
|
|
|
|
|
// (up/preview/refresh/destroy).
|
2022-06-24 17:00:09 +00:00
|
|
|
|
func (s *Stack) History(ctx context.Context,
|
2023-03-03 16:36:39 +00:00
|
|
|
|
pageSize int, page int, opts ...opthistory.Option,
|
|
|
|
|
) ([]UpdateSummary, error) {
|
2022-06-24 17:00:09 +00:00
|
|
|
|
var options opthistory.Options
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
|
opt.ApplyOption(&options)
|
|
|
|
|
}
|
|
|
|
|
showSecrets := true
|
|
|
|
|
if options.ShowSecrets != nil {
|
|
|
|
|
showSecrets = *options.ShowSecrets
|
|
|
|
|
}
|
|
|
|
|
args := []string{"stack", "history", "--json"}
|
|
|
|
|
if showSecrets {
|
|
|
|
|
args = append(args, "--show-secrets")
|
|
|
|
|
}
|
2021-02-10 00:20:01 +00:00
|
|
|
|
if pageSize > 0 {
|
|
|
|
|
// default page=1 if unset when pageSize is set
|
|
|
|
|
if page < 1 {
|
|
|
|
|
page = 1
|
|
|
|
|
}
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--page-size", strconv.Itoa(pageSize), "--page", strconv.Itoa(page))
|
2021-02-08 18:49:57 +00:00
|
|
|
|
}
|
2020-07-17 17:14:30 +00:00
|
|
|
|
|
2022-07-19 17:10:10 +00:00
|
|
|
|
stdout, stderr, errCode, err := s.runPulumiCmdSync(
|
|
|
|
|
ctx,
|
|
|
|
|
nil, /* additionalOutputs */
|
|
|
|
|
nil, /* additionalErrorOutputs */
|
|
|
|
|
args...,
|
|
|
|
|
)
|
2020-07-17 17:14:30 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return nil, newAutoError(fmt.Errorf("failed to get stack history: %w", err), stdout, stderr, errCode)
|
2020-07-17 17:14:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
var history []UpdateSummary
|
|
|
|
|
err = json.Unmarshal([]byte(stdout), &history)
|
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return nil, fmt.Errorf("unable to unmarshal history result: %w", err)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return history, nil
|
2020-07-07 21:25:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-11 17:29:13 +00:00
|
|
|
|
// AddEnvironments adds environments to the end of a stack's import list. Imported environments are merged in order
|
|
|
|
|
// per the ESC merge rules. The list of environments behaves as if it were the import list in an anonymous
|
|
|
|
|
// environment.
|
|
|
|
|
func (s *Stack) AddEnvironments(ctx context.Context, envs ...string) error {
|
|
|
|
|
return s.Workspace().AddEnvironments(ctx, s.Name(), envs...)
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-22 05:18:14 +00:00
|
|
|
|
// ListEnvironments returns the list of environments from the stack's configuration.
|
|
|
|
|
func (s *Stack) ListEnvironments(ctx context.Context) ([]string, error) {
|
|
|
|
|
return s.Workspace().ListEnvironments(ctx, s.Name())
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-11 17:29:13 +00:00
|
|
|
|
// RemoveEnvironment removes an environment from a stack's configuration.
|
|
|
|
|
func (s *Stack) RemoveEnvironment(ctx context.Context, env string) error {
|
|
|
|
|
return s.Workspace().RemoveEnvironment(ctx, s.Name(), env)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// GetConfig returns the config value associated with the specified key.
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) GetConfig(ctx context.Context, key string) (ConfigValue, error) {
|
|
|
|
|
return s.Workspace().GetConfig(ctx, s.Name(), key)
|
2020-07-07 21:25:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-28 18:21:07 +00:00
|
|
|
|
// GetConfigWithOptions returns the config value associated with the specified key using the optional ConfigOptions.
|
|
|
|
|
func (s *Stack) GetConfigWithOptions(ctx context.Context, key string, opts *ConfigOptions) (ConfigValue, error) {
|
|
|
|
|
return s.Workspace().GetConfigWithOptions(ctx, s.Name(), key, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
// GetAllConfig returns the full config map.
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) GetAllConfig(ctx context.Context) (ConfigMap, error) {
|
|
|
|
|
return s.Workspace().GetAllConfig(ctx, s.Name())
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2020-07-22 21:28:03 +00:00
|
|
|
|
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// SetConfig sets the specified config key-value pair.
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) SetConfig(ctx context.Context, key string, val ConfigValue) error {
|
|
|
|
|
return s.Workspace().SetConfig(ctx, s.Name(), key, val)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2020-07-22 21:28:03 +00:00
|
|
|
|
|
2023-03-28 18:21:07 +00:00
|
|
|
|
// SetConfigWithOptions sets the specified config key-value pair using the optional ConfigOptions.
|
|
|
|
|
func (s *Stack) SetConfigWithOptions(ctx context.Context, key string, val ConfigValue, opts *ConfigOptions) error {
|
|
|
|
|
return s.Workspace().SetConfigWithOptions(ctx, s.Name(), key, val, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
// SetAllConfig sets all values in the provided config map.
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) SetAllConfig(ctx context.Context, config ConfigMap) error {
|
|
|
|
|
return s.Workspace().SetAllConfig(ctx, s.Name(), config)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2020-07-22 21:28:03 +00:00
|
|
|
|
|
2023-03-28 18:21:07 +00:00
|
|
|
|
// SetAllConfigWithOptions sets all values in the provided config map using the optional ConfigOptions.
|
|
|
|
|
func (s *Stack) SetAllConfigWithOptions(ctx context.Context, config ConfigMap, opts *ConfigOptions) error {
|
|
|
|
|
return s.Workspace().SetAllConfigWithOptions(ctx, s.Name(), config, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// RemoveConfig removes the specified config key-value pair.
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) RemoveConfig(ctx context.Context, key string) error {
|
|
|
|
|
return s.Workspace().RemoveConfig(ctx, s.Name(), key)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2020-07-24 08:31:54 +00:00
|
|
|
|
|
2023-03-28 18:21:07 +00:00
|
|
|
|
// RemoveConfigWithOptions removes the specified config key-value pair using the optional ConfigOptions.
|
|
|
|
|
func (s *Stack) RemoveConfigWithOptions(ctx context.Context, key string, opts *ConfigOptions) error {
|
|
|
|
|
return s.Workspace().RemoveConfigWithOptions(ctx, s.Name(), key, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
// RemoveAllConfig removes all values in the provided list of keys.
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) RemoveAllConfig(ctx context.Context, keys []string) error {
|
|
|
|
|
return s.Workspace().RemoveAllConfig(ctx, s.Name(), keys)
|
2020-07-17 17:14:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-28 18:21:07 +00:00
|
|
|
|
// RemoveAllConfigWithOptions removes all values in the provided list of keys using the optional ConfigOptions.
|
|
|
|
|
func (s *Stack) RemoveAllConfigWithOptions(ctx context.Context, keys []string, opts *ConfigOptions) error {
|
|
|
|
|
return s.Workspace().RemoveAllConfigWithOptions(ctx, s.Name(), keys, opts)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
// RefreshConfig gets and sets the config map used with the last Update.
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) RefreshConfig(ctx context.Context) (ConfigMap, error) {
|
|
|
|
|
return s.Workspace().RefreshConfig(ctx, s.Name())
|
2020-07-22 21:28:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-02-27 17:02:42 +00:00
|
|
|
|
// GetTag returns the tag value associated with specified key.
|
|
|
|
|
func (s *Stack) GetTag(ctx context.Context, key string) (string, error) {
|
|
|
|
|
return s.Workspace().GetTag(ctx, s.Name(), key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetTag sets a tag key-value pair on the stack.
|
|
|
|
|
func (s *Stack) SetTag(ctx context.Context, key string, value string) error {
|
|
|
|
|
return s.Workspace().SetTag(ctx, s.Name(), key, value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RemoveTag removes the specified tag key-value pair from the stack.
|
|
|
|
|
func (s *Stack) RemoveTag(ctx context.Context, key string) error {
|
|
|
|
|
return s.Workspace().RemoveTag(ctx, s.Name(), key)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListTags returns the full key-value tag map associated with the stack.
|
|
|
|
|
func (s *Stack) ListTags(ctx context.Context) (map[string]string, error) {
|
|
|
|
|
return s.Workspace().ListTags(ctx, s.Name())
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// Info returns a summary of the Stack including its URL.
|
2020-08-22 05:20:32 +00:00
|
|
|
|
func (s *Stack) Info(ctx context.Context) (StackSummary, error) {
|
2020-08-21 22:26:58 +00:00
|
|
|
|
var info StackSummary
|
2020-08-22 05:20:32 +00:00
|
|
|
|
err := s.Workspace().SelectStack(ctx, s.Name())
|
2020-08-21 22:26:58 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return info, fmt.Errorf("failed to fetch stack info: %w", err)
|
2020-08-21 22:26:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 05:20:32 +00:00
|
|
|
|
summary, err := s.Workspace().Stack(ctx)
|
2020-08-21 22:26:58 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return info, fmt.Errorf("failed to fetch stack info: %w", err)
|
2020-08-21 22:26:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if summary != nil {
|
|
|
|
|
info = *summary
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return info, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 21:20:58 +00:00
|
|
|
|
// Cancel stops a stack's currently running update. It returns an error if no update is currently running.
|
|
|
|
|
// Note that this operation is _very dangerous_, and may leave the stack in an inconsistent state
|
|
|
|
|
// if a resource operation was pending when the update was canceled.
|
2024-01-30 15:53:10 +00:00
|
|
|
|
// This command is not supported for diy backends.
|
2020-09-15 21:20:58 +00:00
|
|
|
|
func (s *Stack) Cancel(ctx context.Context) error {
|
2021-04-22 13:10:39 +00:00
|
|
|
|
stdout, stderr, errCode, err := s.runPulumiCmdSync(
|
|
|
|
|
ctx,
|
|
|
|
|
nil, /* additionalOutput */
|
2022-07-19 17:10:10 +00:00
|
|
|
|
nil, /* additionalErrorOutput */
|
2021-04-22 13:10:39 +00:00
|
|
|
|
"cancel", "--yes")
|
2020-09-15 21:20:58 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return newAutoError(fmt.Errorf("failed to cancel update: %w", err), stdout, stderr, errCode)
|
2020-09-15 21:20:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Export exports the deployment state of the stack.
|
|
|
|
|
// This can be combined with Stack.Import to edit a stack's state (such as recovery from failed deployments).
|
|
|
|
|
func (s *Stack) Export(ctx context.Context) (apitype.UntypedDeployment, error) {
|
|
|
|
|
return s.Workspace().ExportStack(ctx, s.Name())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Import imports the specified deployment state into the stack.
|
|
|
|
|
// This can be combined with Stack.Export to edit a stack's state (such as recovery from failed deployments).
|
|
|
|
|
func (s *Stack) Import(ctx context.Context, state apitype.UntypedDeployment) error {
|
|
|
|
|
return s.Workspace().ImportStack(ctx, s.Name(), state)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// UpdateSummary provides a summary of a Stack lifecycle operation (up/preview/refresh/destroy).
|
2020-08-21 02:37:39 +00:00
|
|
|
|
type UpdateSummary struct {
|
2021-02-16 20:16:41 +00:00
|
|
|
|
Version int `json:"version"`
|
2020-08-21 02:37:39 +00:00
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
|
StartTime string `json:"startTime"`
|
|
|
|
|
Message string `json:"message"`
|
|
|
|
|
Environment map[string]string `json:"environment"`
|
|
|
|
|
Config ConfigMap `json:"config"`
|
|
|
|
|
Result string `json:"result,omitempty"`
|
|
|
|
|
|
|
|
|
|
// These values are only present once the update finishes
|
|
|
|
|
EndTime *string `json:"endTime,omitempty"`
|
|
|
|
|
ResourceChanges *map[string]int `json:"resourceChanges,omitempty"`
|
2020-07-07 21:25:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// OutputValue models a Pulumi Stack output, providing the plaintext value and a boolean indicating secretness.
|
2020-08-21 02:37:39 +00:00
|
|
|
|
type OutputValue struct {
|
2020-08-24 13:51:49 +00:00
|
|
|
|
Value interface{}
|
2020-08-21 02:37:39 +00:00
|
|
|
|
Secret bool
|
2020-07-07 21:25:11 +00:00
|
|
|
|
}
|
2020-07-17 17:14:30 +00:00
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// UpResult contains information about a Stack.Up operation,
|
|
|
|
|
// including Outputs, and a summary of the deployed changes.
|
2020-08-21 02:37:39 +00:00
|
|
|
|
type UpResult struct {
|
|
|
|
|
StdOut string
|
|
|
|
|
StdErr string
|
|
|
|
|
Outputs OutputMap
|
|
|
|
|
Summary UpdateSummary
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-14 21:08:10 +00:00
|
|
|
|
// GetPermalink returns the permalink URL in the Pulumi Console for the update operation.
|
|
|
|
|
func (ur *UpResult) GetPermalink() (string, error) {
|
|
|
|
|
return GetPermalink(ur.StdOut)
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 01:18:45 +00:00
|
|
|
|
// ErrParsePermalinkFailed occurs when the the generated permalink URL can't be found in the op result
|
|
|
|
|
var ErrParsePermalinkFailed = errors.New("failed to get permalink")
|
|
|
|
|
|
2020-09-14 21:08:10 +00:00
|
|
|
|
// GetPermalink returns the permalink URL in the Pulumi Console for the update
|
2024-01-30 15:53:10 +00:00
|
|
|
|
// or refresh operation. This will error for alternate, diy backends.
|
2020-09-14 21:08:10 +00:00
|
|
|
|
func GetPermalink(stdout string) (string, error) {
|
2023-03-06 17:06:48 +00:00
|
|
|
|
const permalinkSearchStr = `View Live: |View in Browser: |View in Browser \(Ctrl\+O\): |Permalink: `
|
2021-01-08 09:11:32 +00:00
|
|
|
|
startRegex := regexp.MustCompile(permalinkSearchStr)
|
2021-01-12 00:26:05 +00:00
|
|
|
|
endRegex := regexp.MustCompile("\n")
|
2020-09-14 21:08:10 +00:00
|
|
|
|
|
|
|
|
|
// Find the start of the permalink in the output.
|
|
|
|
|
start := startRegex.FindStringIndex(stdout)
|
|
|
|
|
if start == nil {
|
2021-04-27 01:18:45 +00:00
|
|
|
|
return "", ErrParsePermalinkFailed
|
2020-09-14 21:08:10 +00:00
|
|
|
|
}
|
|
|
|
|
permalinkStart := stdout[start[1]:]
|
|
|
|
|
|
|
|
|
|
// Find the end of the permalink.
|
|
|
|
|
end := endRegex.FindStringIndex(permalinkStart)
|
|
|
|
|
if end == nil {
|
2021-04-27 01:18:45 +00:00
|
|
|
|
return "", ErrParsePermalinkFailed
|
2020-09-14 21:08:10 +00:00
|
|
|
|
}
|
|
|
|
|
permalink := permalinkStart[:end[1]-1]
|
|
|
|
|
return permalink, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
// OutputMap is the output result of running a Pulumi program
|
|
|
|
|
type OutputMap map[string]OutputValue
|
|
|
|
|
|
2020-08-28 21:21:56 +00:00
|
|
|
|
// PreviewStep is a summary of the expected state transition of a given resource based on running the current program.
|
2020-08-21 02:37:39 +00:00
|
|
|
|
type PreviewStep struct {
|
|
|
|
|
// Op is the kind of operation being performed.
|
|
|
|
|
Op string `json:"op"`
|
|
|
|
|
// URN is the resource being affected by this operation.
|
|
|
|
|
URN resource.URN `json:"urn"`
|
|
|
|
|
// Provider is the provider that will perform this step.
|
|
|
|
|
Provider string `json:"provider,omitempty"`
|
|
|
|
|
// OldState is the old state for this resource, if appropriate given the operation type.
|
|
|
|
|
OldState *apitype.ResourceV3 `json:"oldState,omitempty"`
|
|
|
|
|
// NewState is the new state for this resource, if appropriate given the operation type.
|
|
|
|
|
NewState *apitype.ResourceV3 `json:"newState,omitempty"`
|
|
|
|
|
// DiffReasons is a list of keys that are causing a diff (for updating steps only).
|
|
|
|
|
DiffReasons []resource.PropertyKey `json:"diffReasons,omitempty"`
|
|
|
|
|
// ReplaceReasons is a list of keys that are causing replacement (for replacement steps only).
|
|
|
|
|
ReplaceReasons []resource.PropertyKey `json:"replaceReasons,omitempty"`
|
|
|
|
|
// DetailedDiff is a structured diff that indicates precise per-property differences.
|
|
|
|
|
DetailedDiff map[string]PropertyDiff `json:"detailedDiff"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PropertyDiff contains information about the difference in a single property value.
|
|
|
|
|
type PropertyDiff struct {
|
|
|
|
|
// Kind is the kind of difference.
|
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
|
// InputDiff is true if this is a difference between old and new inputs instead of old state and new inputs.
|
|
|
|
|
InputDiff bool `json:"inputDiff"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PreviewResult is the output of Stack.Preview() describing the expected set of changes from the next Stack.Up()
|
|
|
|
|
type PreviewResult struct {
|
2021-03-11 04:49:48 +00:00
|
|
|
|
StdOut string
|
|
|
|
|
StdErr string
|
|
|
|
|
ChangeSummary map[apitype.OpType]int
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 01:18:45 +00:00
|
|
|
|
// GetPermalink returns the permalink URL in the Pulumi Console for the preview operation.
|
|
|
|
|
func (pr *PreviewResult) GetPermalink() (string, error) {
|
|
|
|
|
return GetPermalink(pr.StdOut)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// RefreshResult is the output of a successful Stack.Refresh operation
|
2020-08-21 02:37:39 +00:00
|
|
|
|
type RefreshResult struct {
|
|
|
|
|
StdOut string
|
|
|
|
|
StdErr string
|
|
|
|
|
Summary UpdateSummary
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 01:18:45 +00:00
|
|
|
|
// GetPermalink returns the permalink URL in the Pulumi Console for the refresh operation.
|
|
|
|
|
func (rr *RefreshResult) GetPermalink() (string, error) {
|
|
|
|
|
return GetPermalink(rr.StdOut)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-25 18:16:54 +00:00
|
|
|
|
// DestroyResult is the output of a successful Stack.Destroy operation
|
2020-08-21 02:37:39 +00:00
|
|
|
|
type DestroyResult struct {
|
|
|
|
|
StdOut string
|
|
|
|
|
StdErr string
|
|
|
|
|
Summary UpdateSummary
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 01:18:45 +00:00
|
|
|
|
// GetPermalink returns the permalink URL in the Pulumi Console for the destroy operation.
|
|
|
|
|
func (dr *DestroyResult) GetPermalink() (string, error) {
|
|
|
|
|
return GetPermalink(dr.StdOut)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 02:37:39 +00:00
|
|
|
|
// secretSentinel represents the CLI response for an output marked as "secret"
|
|
|
|
|
const secretSentinel = "[secret]"
|
|
|
|
|
|
2020-09-15 01:56:04 +00:00
|
|
|
|
func (s *Stack) runPulumiCmdSync(
|
|
|
|
|
ctx context.Context,
|
|
|
|
|
additionalOutput []io.Writer,
|
2022-07-19 17:10:10 +00:00
|
|
|
|
additionalErrorOutput []io.Writer,
|
2020-09-15 01:56:04 +00:00
|
|
|
|
args ...string,
|
|
|
|
|
) (string, string, int, error) {
|
2020-08-21 02:37:39 +00:00
|
|
|
|
var env []string
|
2021-03-11 04:49:48 +00:00
|
|
|
|
debugEnv := fmt.Sprintf("%s=%s", "PULUMI_DEBUG_COMMANDS", "true")
|
|
|
|
|
env = append(env, debugEnv)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
|
|
|
|
|
var remote bool
|
|
|
|
|
if lws, isLocalWorkspace := s.Workspace().(*LocalWorkspace); isLocalWorkspace {
|
|
|
|
|
remote = lws.remote
|
|
|
|
|
}
|
|
|
|
|
if remote {
|
|
|
|
|
experimentalEnv := fmt.Sprintf("%s=%s", "PULUMI_EXPERIMENTAL", "true")
|
|
|
|
|
env = append(env, experimentalEnv)
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-29 02:10:39 +00:00
|
|
|
|
if s.Workspace().PulumiHome() != "" {
|
|
|
|
|
homeEnv := fmt.Sprintf("%s=%s", pulumiHomeEnv, s.Workspace().PulumiHome())
|
2020-08-21 23:22:45 +00:00
|
|
|
|
env = append(env, homeEnv)
|
2020-07-17 17:14:30 +00:00
|
|
|
|
}
|
2020-09-01 23:34:27 +00:00
|
|
|
|
if envvars := s.Workspace().GetEnvVars(); envvars != nil {
|
|
|
|
|
for k, v := range envvars {
|
|
|
|
|
e := []string{k, v}
|
|
|
|
|
env = append(env, strings.Join(e, "="))
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-22 05:20:32 +00:00
|
|
|
|
additionalArgs, err := s.Workspace().SerializeArgsForOp(ctx, s.Name())
|
2020-08-21 23:22:45 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return "", "", -1, fmt.Errorf("failed to exec command, error getting additional args: %w", err)
|
2020-08-21 23:22:45 +00:00
|
|
|
|
}
|
|
|
|
|
args = append(args, additionalArgs...)
|
2021-04-22 13:10:39 +00:00
|
|
|
|
args = append(args, "--stack", s.Name())
|
|
|
|
|
|
2024-01-26 16:01:17 +00:00
|
|
|
|
stdout, stderr, errCode, err := s.workspace.PulumiCommand().Run(
|
2022-07-19 17:10:10 +00:00
|
|
|
|
ctx,
|
|
|
|
|
s.Workspace().WorkDir(),
|
2023-12-12 08:37:33 +00:00
|
|
|
|
nil,
|
2022-07-19 17:10:10 +00:00
|
|
|
|
additionalOutput,
|
|
|
|
|
additionalErrorOutput,
|
|
|
|
|
env,
|
|
|
|
|
args...,
|
|
|
|
|
)
|
2020-08-21 23:22:45 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return stdout, stderr, errCode, err
|
|
|
|
|
}
|
2020-08-29 02:10:39 +00:00
|
|
|
|
err = s.Workspace().PostCommandCallback(ctx, s.Name())
|
2020-08-21 23:22:45 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return stdout, stderr, errCode, fmt.Errorf("command ran successfully, but error running PostCommandCallback: %w", err)
|
2020-08-21 23:22:45 +00:00
|
|
|
|
}
|
|
|
|
|
return stdout, stderr, errCode, nil
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 21:45:02 +00:00
|
|
|
|
func (s *Stack) isRemote() bool {
|
|
|
|
|
var remote bool
|
|
|
|
|
if lws, isLocalWorkspace := s.Workspace().(*LocalWorkspace); isLocalWorkspace {
|
|
|
|
|
remote = lws.remote
|
|
|
|
|
}
|
|
|
|
|
return remote
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Stack) remoteArgs() []string {
|
|
|
|
|
var remote bool
|
|
|
|
|
var repo *GitRepo
|
|
|
|
|
var preRunCommands []string
|
|
|
|
|
var envvars map[string]EnvVarValue
|
2022-12-18 03:31:44 +00:00
|
|
|
|
var skipInstallDependencies bool
|
2022-10-25 21:45:02 +00:00
|
|
|
|
if lws, isLocalWorkspace := s.Workspace().(*LocalWorkspace); isLocalWorkspace {
|
|
|
|
|
remote = lws.remote
|
|
|
|
|
repo = lws.repo
|
|
|
|
|
preRunCommands = lws.preRunCommands
|
|
|
|
|
envvars = lws.remoteEnvVars
|
2022-12-18 03:31:44 +00:00
|
|
|
|
skipInstallDependencies = lws.remoteSkipInstallDependencies
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
if !remote {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-28 16:02:04 +00:00
|
|
|
|
args := slice.Prealloc[string](len(envvars) + len(preRunCommands))
|
2022-10-25 21:45:02 +00:00
|
|
|
|
args = append(args, "--remote")
|
|
|
|
|
if repo != nil {
|
|
|
|
|
if repo.URL != "" {
|
|
|
|
|
args = append(args, repo.URL)
|
|
|
|
|
}
|
|
|
|
|
if repo.Branch != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--remote-git-branch="+repo.Branch)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
if repo.CommitHash != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--remote-git-commit="+repo.CommitHash)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
if repo.ProjectPath != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--remote-git-repo-dir="+repo.ProjectPath)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
if repo.Auth != nil {
|
|
|
|
|
if repo.Auth.PersonalAccessToken != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--remote-git-auth-access-token="+repo.Auth.PersonalAccessToken)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
if repo.Auth.SSHPrivateKey != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--remote-git-auth-ssh-private-key="+repo.Auth.SSHPrivateKey)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
if repo.Auth.SSHPrivateKeyPath != "" {
|
|
|
|
|
args = append(args,
|
2023-12-12 12:19:42 +00:00
|
|
|
|
"--remote-git-auth-ssh-private-key-path="+repo.Auth.SSHPrivateKeyPath)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
if repo.Auth.Password != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--remote-git-auth-password="+repo.Auth.Password)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
if repo.Auth.Username != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--remote-git-auth-username="+repo.Auth.Username)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for k, v := range envvars {
|
|
|
|
|
flag := "--remote-env"
|
|
|
|
|
if v.Secret {
|
|
|
|
|
flag += "-secret"
|
|
|
|
|
}
|
|
|
|
|
args = append(args, fmt.Sprintf("%s=%s=%s", flag, k, v.Value))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, command := range preRunCommands {
|
2023-12-12 12:19:42 +00:00
|
|
|
|
args = append(args, "--remote-pre-run-command="+command)
|
2022-10-25 21:45:02 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-18 03:31:44 +00:00
|
|
|
|
if skipInstallDependencies {
|
|
|
|
|
args = append(args, "--remote-skip-install-dependencies")
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 21:45:02 +00:00
|
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
const (
|
|
|
|
|
stateWaiting = iota
|
|
|
|
|
stateRunning
|
|
|
|
|
stateCanceled
|
|
|
|
|
stateFinished
|
|
|
|
|
)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
type languageRuntimeServer struct {
|
2022-12-14 19:20:26 +00:00
|
|
|
|
pulumirpc.UnimplementedLanguageRuntimeServer
|
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
m sync.Mutex
|
|
|
|
|
c *sync.Cond
|
2020-08-21 02:37:39 +00:00
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
fn pulumi.RunFunc
|
|
|
|
|
address string
|
|
|
|
|
|
|
|
|
|
state int
|
|
|
|
|
cancel chan bool
|
2022-11-01 15:15:09 +00:00
|
|
|
|
done <-chan error
|
2020-09-15 00:40:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// isNestedInvocation returns true if pulumi.RunWithContext is on the stack.
|
|
|
|
|
func isNestedInvocation() bool {
|
|
|
|
|
depth, callers := 0, make([]uintptr, 32)
|
|
|
|
|
for {
|
|
|
|
|
n := runtime.Callers(depth, callers)
|
|
|
|
|
if n == 0 {
|
|
|
|
|
return false
|
2020-07-24 08:31:54 +00:00
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
depth += n
|
|
|
|
|
|
|
|
|
|
frames := runtime.CallersFrames(callers)
|
|
|
|
|
for f, more := frames.Next(); more; f, more = frames.Next() {
|
2021-03-17 13:20:05 +00:00
|
|
|
|
if f.Function == "github.com/pulumi/pulumi/sdk/v3/go/pulumi.RunWithContext" {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return true
|
|
|
|
|
}
|
2020-07-22 21:28:03 +00:00
|
|
|
|
}
|
2020-07-17 17:14:30 +00:00
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
}
|
2020-08-21 02:37:39 +00:00
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
func startLanguageRuntimeServer(fn pulumi.RunFunc) (*languageRuntimeServer, error) {
|
|
|
|
|
if isNestedInvocation() {
|
|
|
|
|
return nil, errors.New("nested stack operations are not supported https://github.com/pulumi/pulumi/issues/5058")
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2020-08-22 05:20:32 +00:00
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
s := &languageRuntimeServer{
|
|
|
|
|
fn: fn,
|
|
|
|
|
cancel: make(chan bool),
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
s.c = sync.NewCond(&s.m)
|
|
|
|
|
|
2022-11-01 15:15:09 +00:00
|
|
|
|
handle, err := rpcutil.ServeWithOptions(rpcutil.ServeOptions{
|
|
|
|
|
Cancel: s.cancel,
|
|
|
|
|
Init: func(srv *grpc.Server) error {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
pulumirpc.RegisterLanguageRuntimeServer(srv, s)
|
|
|
|
|
return nil
|
|
|
|
|
},
|
2022-11-01 15:15:09 +00:00
|
|
|
|
Options: rpcutil.OpenTracingServerInterceptorOptions(nil),
|
|
|
|
|
})
|
2020-08-21 02:37:39 +00:00
|
|
|
|
if err != nil {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-11-01 15:15:09 +00:00
|
|
|
|
s.address, s.done = fmt.Sprintf("127.0.0.1:%d", handle.Port), handle.Done
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return s, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *languageRuntimeServer) Close() error {
|
|
|
|
|
s.m.Lock()
|
|
|
|
|
switch s.state {
|
|
|
|
|
case stateCanceled:
|
|
|
|
|
s.m.Unlock()
|
|
|
|
|
return nil
|
|
|
|
|
case stateWaiting:
|
|
|
|
|
// Not started yet; go ahead and cancel
|
|
|
|
|
default:
|
|
|
|
|
for s.state != stateFinished {
|
|
|
|
|
s.c.Wait()
|
2020-07-24 08:31:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
s.state = stateCanceled
|
|
|
|
|
s.m.Unlock()
|
2020-07-17 17:14:30 +00:00
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
s.cancel <- true
|
|
|
|
|
close(s.cancel)
|
|
|
|
|
return <-s.done
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *languageRuntimeServer) GetRequiredPlugins(ctx context.Context,
|
2023-03-03 16:36:39 +00:00
|
|
|
|
req *pulumirpc.GetRequiredPluginsRequest,
|
|
|
|
|
) (*pulumirpc.GetRequiredPluginsResponse, error) {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return &pulumirpc.GetRequiredPluginsResponse{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *languageRuntimeServer) Run(ctx context.Context, req *pulumirpc.RunRequest) (*pulumirpc.RunResponse, error) {
|
|
|
|
|
s.m.Lock()
|
|
|
|
|
if s.state == stateCanceled {
|
|
|
|
|
s.m.Unlock()
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return nil, errors.New("program canceled")
|
2020-07-28 01:22:01 +00:00
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
s.state = stateRunning
|
|
|
|
|
s.m.Unlock()
|
2020-08-21 23:22:45 +00:00
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
defer func() {
|
|
|
|
|
s.m.Lock()
|
|
|
|
|
s.state = stateFinished
|
|
|
|
|
s.m.Unlock()
|
|
|
|
|
s.c.Broadcast()
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
var engineAddress string
|
|
|
|
|
if len(req.Args) > 0 {
|
|
|
|
|
engineAddress = req.Args[0]
|
|
|
|
|
}
|
|
|
|
|
runInfo := pulumi.RunInfo{
|
2021-05-18 22:02:43 +00:00
|
|
|
|
EngineAddr: engineAddress,
|
|
|
|
|
MonitorAddr: req.GetMonitorAddress(),
|
|
|
|
|
Config: req.GetConfig(),
|
|
|
|
|
ConfigSecretKeys: req.GetConfigSecretKeys(),
|
|
|
|
|
Project: req.GetProject(),
|
|
|
|
|
Stack: req.GetStack(),
|
|
|
|
|
Parallel: int(req.GetParallel()),
|
|
|
|
|
DryRun: req.GetDryRun(),
|
2022-08-31 09:33:29 +00:00
|
|
|
|
Organization: req.GetOrganization(),
|
2020-08-21 23:22:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
pulumiCtx, err := pulumi.NewContext(ctx, runInfo)
|
2020-08-21 23:22:45 +00:00
|
|
|
|
if err != nil {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return nil, err
|
2020-08-21 23:22:45 +00:00
|
|
|
|
}
|
2023-03-22 22:13:01 +00:00
|
|
|
|
defer pulumiCtx.Close()
|
2020-07-28 01:22:01 +00:00
|
|
|
|
|
2020-09-15 00:40:17 +00:00
|
|
|
|
err = func() (err error) {
|
|
|
|
|
defer func() {
|
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
|
if pErr, ok := r.(error); ok {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
err = fmt.Errorf("go inline source runtime error, an unhandled error occurred: %w", pErr)
|
2020-09-15 00:40:17 +00:00
|
|
|
|
} else {
|
|
|
|
|
err = errors.New("go inline source runtime error, an unhandled error occurred: unknown error")
|
|
|
|
|
}
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
return pulumi.RunWithContext(pulumiCtx, s.fn)
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}()
|
2020-08-22 05:20:32 +00:00
|
|
|
|
if err != nil {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return &pulumirpc.RunResponse{Error: err.Error()}, nil
|
2020-08-22 05:20:32 +00:00
|
|
|
|
}
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return &pulumirpc.RunResponse{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 09:35:20 +00:00
|
|
|
|
func (s *languageRuntimeServer) GetPluginInfo(ctx context.Context, req *emptypb.Empty) (*pulumirpc.PluginInfo, error) {
|
2020-09-15 00:40:17 +00:00
|
|
|
|
return &pulumirpc.PluginInfo{
|
|
|
|
|
Version: "1.0.0",
|
|
|
|
|
}, nil
|
2020-08-21 02:37:39 +00:00
|
|
|
|
}
|
2021-03-11 04:49:48 +00:00
|
|
|
|
|
2022-04-03 14:54:59 +00:00
|
|
|
|
func (s *languageRuntimeServer) InstallDependencies(
|
|
|
|
|
req *pulumirpc.InstallDependenciesRequest,
|
2023-03-03 16:36:39 +00:00
|
|
|
|
server pulumirpc.LanguageRuntime_InstallDependenciesServer,
|
|
|
|
|
) error {
|
2022-04-03 14:54:59 +00:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 16:26:01 +00:00
|
|
|
|
type fileWatcher struct {
|
|
|
|
|
Filename string
|
|
|
|
|
tail *tail.Tail
|
|
|
|
|
receivers []chan<- events.EngineEvent
|
|
|
|
|
done chan bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func watchFile(path string, receivers []chan<- events.EngineEvent) (*fileWatcher, error) {
|
|
|
|
|
t, err := tail.TailFile(path, tail.Config{
|
|
|
|
|
Follow: true,
|
2023-11-29 09:05:02 +00:00
|
|
|
|
Poll: runtime.GOOS == "windows", // on Windows poll for file changes instead of using the default inotify
|
2022-04-25 16:26:01 +00:00
|
|
|
|
Logger: tail.DiscardingLogger,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
done := make(chan bool)
|
|
|
|
|
go func(tailedLog *tail.Tail) {
|
|
|
|
|
for line := range tailedLog.Lines {
|
|
|
|
|
if line.Err != nil {
|
|
|
|
|
for _, r := range receivers {
|
|
|
|
|
r <- events.EngineEvent{Error: line.Err}
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
var e apitype.EngineEvent
|
|
|
|
|
err = json.Unmarshal([]byte(line.Text), &e)
|
|
|
|
|
if err != nil {
|
|
|
|
|
for _, r := range receivers {
|
|
|
|
|
r <- events.EngineEvent{Error: err}
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for _, r := range receivers {
|
|
|
|
|
r <- events.EngineEvent{EngineEvent: e}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, r := range receivers {
|
|
|
|
|
close(r)
|
|
|
|
|
}
|
|
|
|
|
close(done)
|
|
|
|
|
}(t)
|
|
|
|
|
return &fileWatcher{
|
|
|
|
|
Filename: t.Filename,
|
|
|
|
|
tail: t,
|
|
|
|
|
receivers: receivers,
|
|
|
|
|
done: done,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func tailLogs(command string, receivers []chan<- events.EngineEvent) (*fileWatcher, error) {
|
2023-01-06 22:39:16 +00:00
|
|
|
|
logDir, err := os.MkdirTemp("", fmt.Sprintf("automation-logs-%s-", command))
|
2021-03-11 04:49:48 +00:00
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return nil, fmt.Errorf("failed to create logdir: %w", err)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
}
|
|
|
|
|
logFile := filepath.Join(logDir, "eventlog.txt")
|
|
|
|
|
|
|
|
|
|
t, err := watchFile(logFile, receivers)
|
|
|
|
|
if err != nil {
|
2023-01-06 22:09:19 +00:00
|
|
|
|
return nil, fmt.Errorf("failed to watch file: %w", err)
|
2021-03-11 04:49:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return t, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 16:26:01 +00:00
|
|
|
|
func (fw *fileWatcher) Close() {
|
|
|
|
|
if fw.tail == nil {
|
|
|
|
|
return
|
2021-03-11 04:49:48 +00:00
|
|
|
|
}
|
2022-04-25 16:26:01 +00:00
|
|
|
|
|
|
|
|
|
// Tell the watcher to end on next EoF, wait for the done event, then cleanup.
|
|
|
|
|
|
2023-01-06 00:07:45 +00:00
|
|
|
|
//nolint:errcheck
|
2022-04-25 16:26:01 +00:00
|
|
|
|
fw.tail.StopAtEOF()
|
|
|
|
|
<-fw.done
|
|
|
|
|
logDir := filepath.Dir(fw.tail.Filename)
|
|
|
|
|
fw.tail.Cleanup()
|
|
|
|
|
os.RemoveAll(logDir)
|
|
|
|
|
|
|
|
|
|
// set to nil so we can safely close again in defer
|
|
|
|
|
fw.tail = nil
|
2021-03-11 04:49:48 +00:00
|
|
|
|
}
|