diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 921cc5c64..e234e7c10 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,10 +16,9 @@ "remoteUser": "user", - "extensions": ["golang.go", "ms-dotnettools.csharp", "ms-python.python"], + "extensions": ["golang.go", "ms-dotnettools.csharp", "ms-python.python", "formulahendry.dotnet-test-explorer"], - // We want to dotnet restore all projects on startup so that omnisharp doesn't complain about lots of missing types on startup. - "postCreateCommand": "find -name \"*.??proj\" | xargs -L1 dotnet restore", + "postCreateCommand": "make ensure", "settings": { "extensions.ignoreRecommendations": true diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 026ba4bfe..5d215ba98 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -160,6 +160,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet-version }} + - run: mkdir -p ${{ runner.temp }}/opt/pulumi/nuget - run: dotnet nuget add source ${{ runner.temp }}/opt/pulumi/nuget - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -229,6 +230,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet-version }} + - run: mkdir -p ${{ runner.temp }}/opt/pulumi/nuget - run: dotnet nuget add source ${{ runner.temp }}/opt/pulumi/nuget - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -303,6 +305,9 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet }} + - name: Create Local Nuget + run: mkdir -p "${{ env.PULUMI_LOCAL_NUGET }}" + shell: bash - run: dotnet nuget add source ${{ env.PULUMI_LOCAL_NUGET }} - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index f2615b03f..987c8339c 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -143,6 +143,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet-version }} + - run: mkdir -p ${{ runner.temp }}/opt/pulumi/nuget - run: dotnet nuget add source ${{ runner.temp }}/opt/pulumi/nuget - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -206,6 +207,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet-version }} + - run: mkdir -p ${{ runner.temp }}/opt/pulumi/nuget - run: dotnet nuget add source ${{ runner.temp }}/opt/pulumi/nuget - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -280,6 +282,9 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet }} + - name: Create Local Nuget + run: mkdir -p "${{ env.PULUMI_LOCAL_NUGET }}" + shell: bash - run: dotnet nuget add source ${{ env.PULUMI_LOCAL_NUGET }} - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -295,9 +300,6 @@ jobs: python-version: ${{ matrix.python-version }} - name: Clean run: dotnet nuget locals all --clear - - name: Create Local Nuget - run: mkdir -p "D:\\Pulumi\\nuget" - shell: bash - name: Install Python Deps run: | pip3 install pyenv-win diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f248fdb3..c60e9d107 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -236,6 +236,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet-version }} + - run: mkdir -p ${{ runner.temp }}/opt/pulumi/nuget - run: dotnet nuget add source ${{ runner.temp }}/opt/pulumi/nuget - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -305,6 +306,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet-version }} + - run: mkdir -p ${{ runner.temp }}/opt/pulumi/nuget - run: dotnet nuget add source ${{ runner.temp }}/opt/pulumi/nuget - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -386,6 +388,9 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet }} + - name: Create Local Nuget + run: mkdir -p "${{ env.PULUMI_LOCAL_NUGET }}" + shell: bash - run: dotnet nuget add source ${{ env.PULUMI_LOCAL_NUGET }} - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 diff --git a/.github/workflows/run-build-and-acceptance-tests.yml b/.github/workflows/run-build-and-acceptance-tests.yml index d9208bdb6..b533feb6a 100644 --- a/.github/workflows/run-build-and-acceptance-tests.yml +++ b/.github/workflows/run-build-and-acceptance-tests.yml @@ -82,6 +82,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet-version }} + - run: mkdir -p ${{ runner.temp }}/opt/pulumi/nuget - run: dotnet nuget add source ${{ runner.temp }}/opt/pulumi/nuget - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -170,6 +171,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.dotnet-version }} + - run: mkdir -p ${{ runner.temp }}/opt/pulumi/nuget - run: dotnet nuget add source ${{ runner.temp }}/opt/pulumi/nuget - name: Set up Node ${{ matrix.node-version }} uses: actions/setup-node@v1 @@ -279,6 +281,9 @@ jobs: run: | pip3 install pyenv-win pip3 install pipenv + - name: Create Local Nuget + run: mkdir -p "${{ env.PULUMI_LOCAL_NUGET }}" + shell: bash - run: dotnet nuget add source ${{ env.PULUMI_LOCAL_NUGET }} - name: Set Build Env Vars shell: bash diff --git a/.vscode/settings.json b/.vscode/settings.json index 7c9840ead..a66bf3a2e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,4 +11,7 @@ // Experimental but seems to work and means we don't need a vscode instance per go.mod file. "experimentalWorkspaceModule": true, }, + + "omnisharp.defaultLaunchSolution": "sdk/dotnet/dotnet.sln", + "dotnet-test-explorer.testProjectPath": "sdk/dotnet", } \ No newline at end of file diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index a7a4b5de5..98f445d43 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,18 +1,16 @@ ### Improvements -- [codegen/docs] Edit docs codegen to document `$fnOutput` function - invoke forms in API documentation. - [#8287](https://github.com/pulumi/pulumi/pull/8287) +- [CLI] Adding the ability to use `pulumi org set [name]` to set a default org + to use when creating a stacks in the Pulumi Service backend or Self -hosted Service + [#8352](https://github.com/pulumi/pulumi/pull/8352) +- [schema] Add IsOverlay option to disable codegen for particular types + [#8338](https://github.com/pulumi/pulumi/pull/8338) + +- [sdk/dotnet] - Marshal output values. + [#8316](https://github.com/pulumi/pulumi/pull/8316) ### Bug Fixes -- [automation/python] - Fix deserialization of events. - [#8375](https://github.com/pulumi/pulumi/pull/8375) - -- [sdk/dotnet] - Fixes failing preview for programs that call data - sources (`F.Invoke`) with unknown outputs - [#8339](https://github.com/pulumi/pulumi/pull/8339) - -- [programgen/go] - Don't change imported resource names. - [#8353](https://github.com/pulumi/pulumi/pull/8353) +- [engine] - Compute dependents correctly during targeted deletes. + [#8360](https://github.com/pulumi/pulumi/pull/8360) diff --git a/developer-docs/providers/metaschema.md b/developer-docs/providers/metaschema.md index 4136ed0d7..5c496c966 100644 --- a/developer-docs/providers/metaschema.md +++ b/developer-docs/providers/metaschema.md @@ -297,7 +297,7 @@ Enum: `"boolean"` | `"integer"` | `"number"` | `"string"` #### `deprecationMessage` -Indicates whether or not the value is deprecated. +Indicates whether the value is deprecated. `string` @@ -339,7 +339,7 @@ Describes a function. #### `deprecationMessage` -Indicates whether or not the function is deprecated +Indicates whether the function is deprecated `string` @@ -564,7 +564,7 @@ Additional language-specific data about the default value. #### `deprecationMessage` -Indicates whether or not the property is deprecated +Indicates whether the property is deprecated `string` @@ -627,7 +627,7 @@ Items: [Alias Definition](#alias-definition) #### `deprecationMessage` -Indicates whether or not the resource is deprecated +Indicates whether the resource is deprecated `string` @@ -653,7 +653,15 @@ Additional properties: [Property Definition](#property-definition) #### `isComponent` -Indicates whether or not the resource is a component. +Indicates whether the resource is a component. + +`boolean` + +--- + +#### `isOverlay` + +Indicates whether the resource is an overlay (code is generated by the package rather than by the core Pulumi codegen). `boolean` diff --git a/pkg/backend/apply.go b/pkg/backend/apply.go index 7f2f39880..19d30a06c 100644 --- a/pkg/backend/apply.go +++ b/pkg/backend/apply.go @@ -21,7 +21,6 @@ import ( "os" "strings" - "github.com/pkg/errors" survey "gopkg.in/AlecAivazis/survey.v1" surveycore "gopkg.in/AlecAivazis/survey.v1/core" @@ -172,7 +171,7 @@ func confirmBeforeUpdating(kind apitype.UpdateKind, stack Stack, Options: choices, Default: string(no), }, &response, nil); err != nil { - return result.FromError(errors.Wrapf(err, "confirmation cancelled, not proceeding with the %s", kind)) + return result.FromError(fmt.Errorf("confirmation cancelled, not proceeding with the %s: %w", kind, err)) } if response == string(no) { diff --git a/pkg/backend/backend.go b/pkg/backend/backend.go index c48c67e80..9f002db82 100644 --- a/pkg/backend/backend.go +++ b/pkg/backend/backend.go @@ -16,12 +16,11 @@ package backend import ( "context" + "errors" "fmt" "strings" "time" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/backend/display" "github.com/pulumi/pulumi/pkg/v3/engine" "github.com/pulumi/pulumi/pkg/v3/operations" @@ -298,7 +297,7 @@ func (c *backendClient) GetStackOutputs(ctx context.Context, name string) (resou return nil, err } if s == nil { - return nil, errors.Errorf("unknown stack %q", name) + return nil, fmt.Errorf("unknown stack %q", name) } snap, err := s.Snapshot(ctx) if err != nil { @@ -306,7 +305,7 @@ func (c *backendClient) GetStackOutputs(ctx context.Context, name string) (resou } res, err := stack.GetRootStackResource(snap) if err != nil { - return nil, errors.Wrap(err, "getting root stack resources") + return nil, fmt.Errorf("getting root stack resources: %w", err) } if res == nil { return resource.PropertyMap{}, nil @@ -325,7 +324,7 @@ func (c *backendClient) GetStackResourceOutputs( return nil, err } if s == nil { - return nil, errors.Errorf("unknown stack %q", name) + return nil, fmt.Errorf("unknown stack %q", name) } snap, err := s.Snapshot(ctx) if err != nil { diff --git a/pkg/backend/display/events.go b/pkg/backend/display/events.go index 2f7f05eed..325b0b260 100644 --- a/pkg/backend/display/events.go +++ b/pkg/backend/display/events.go @@ -1,7 +1,7 @@ package display import ( - "github.com/pkg/errors" + "fmt" "github.com/pulumi/pulumi/pkg/v3/engine" "github.com/pulumi/pulumi/pkg/v3/resource/stack" @@ -21,7 +21,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) { var apiEvent apitype.EngineEvent // Error to return if the payload doesn't match expected. - eventTypePayloadMismatch := errors.Errorf("unexpected payload for event type %v", e.Type) + eventTypePayloadMismatch := fmt.Errorf("unexpected payload for event type %v", e.Type) switch e.Type { case engine.CancelEvent: @@ -130,7 +130,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) { } default: - return apiEvent, errors.Errorf("unknown event type %q", e.Type) + return apiEvent, fmt.Errorf("unknown event type %q", e.Type) } return apiEvent, nil diff --git a/pkg/backend/filestate/backend.go b/pkg/backend/filestate/backend.go index 3a58f97df..ba8239115 100644 --- a/pkg/backend/filestate/backend.go +++ b/pkg/backend/filestate/backend.go @@ -17,6 +17,7 @@ package filestate import ( "context" "encoding/json" + "errors" "fmt" "net/url" "os" @@ -28,7 +29,7 @@ import ( "time" "github.com/gofrs/uuid" - "github.com/pkg/errors" + user "github.com/tweekmonster/luser" "gocloud.dev/blob" _ "gocloud.dev/blob/azureblob" // driver for azblob:// @@ -103,7 +104,7 @@ const FilePathPrefix = "file://" func New(d diag.Sink, originalURL string) (Backend, error) { if !IsFileStateBackendURL(originalURL) { - return nil, errors.Errorf("local URL %s has an illegal prefix; expected one of: %s", + return nil, fmt.Errorf("local URL %s has an illegal prefix; expected one of: %s", originalURL, strings.Join(blob.DefaultURLMux().BucketSchemes(), ", ")) } @@ -130,7 +131,7 @@ func New(d diag.Sink, originalURL string) (Backend, error) { bucket, err := blobmux.OpenBucket(context.TODO(), u) if err != nil { - return nil, errors.Wrapf(err, "unable to open bucket %s", u) + return nil, fmt.Errorf("unable to open bucket %s: %w", u, err) } if !strings.HasPrefix(u, FilePathPrefix) { @@ -178,7 +179,7 @@ func massageBlobPath(path string) (string, error) { if strings.HasPrefix(path, "~") { usr, err := user.Current() if err != nil { - return "", errors.Wrap(err, "Could not determine current user to resolve `file://~` path.") + return "", fmt.Errorf("Could not determine current user to resolve `file://~` path.: %w", err) } if path == "~" { @@ -191,7 +192,7 @@ func massageBlobPath(path string) (string, error) { // For file:// backend, ensure a relative path is resolved. fileblob only supports absolute paths. path, err := filepath.Abs(path) if err != nil { - return "", errors.Wrap(err, "An IO error occurred while building the absolute path") + return "", fmt.Errorf("An IO error occurred while building the absolute path: %w", err) } // Using example from https://godoc.org/gocloud.dev/blob/fileblob#example-package--OpenBucket @@ -300,10 +301,10 @@ func (b *localBackend) CreateStack(ctx context.Context, stackRef backend.StackRe tags, err := backend.GetEnvironmentTagsForCurrentStack() if err != nil { - return nil, errors.Wrap(err, "getting stack tags") + return nil, fmt.Errorf("getting stack tags: %w", err) } if err = validation.ValidateStackProperties(string(stackName), tags); err != nil { - return nil, errors.Wrap(err, "validating stack properties") + return nil, fmt.Errorf("validating stack properties: %w", err) } file, err := b.saveStack(stackName, nil, nil) @@ -320,8 +321,9 @@ func (b *localBackend) CreateStack(ctx context.Context, stackRef backend.StackRe func (b *localBackend) GetStack(ctx context.Context, stackRef backend.StackReference) (backend.Stack, error) { stackName := stackRef.Name() snapshot, path, err := b.getStack(stackName) + switch { - case gcerrors.Code(errors.Cause(err)) == gcerrors.NotFound: + case gcerrors.Code(drillError(err)) == gcerrors.NotFound: return nil, nil case err != nil: return nil, err @@ -410,7 +412,7 @@ func (b *localBackend) RenameStack(ctx context.Context, stack backend.Stack, return nil, err } if hasExisting { - return nil, errors.Errorf("a stack named %s already exists", newName) + return nil, fmt.Errorf("a stack named %s already exists", newName) } // If we have a snapshot, we need to rename the URNs inside it to use the new stack name. @@ -664,11 +666,11 @@ func (b *localBackend) apply( if saveErr != nil { // We swallow backupErr as it is less important than the saveErr. - return plan, changes, result.FromError(errors.Wrap(saveErr, "saving update info")) + return plan, changes, result.FromError(fmt.Errorf("saving update info: %w", saveErr)) } if backupErr != nil { - return plan, changes, result.FromError(errors.Wrap(backupErr, "saving backup")) + return plan, changes, result.FromError(fmt.Errorf("saving backup: %w", backupErr)) } // Make sure to print a link to the stack's checkpoint before exiting. @@ -777,7 +779,7 @@ func (b *localBackend) ExportDeployment(ctx context.Context, sdep, err := stack.SerializeDeployment(snap, snap.SecretsManager /* showSecrsts */, false) if err != nil { - return nil, errors.Wrap(err, "serializing deployment") + return nil, fmt.Errorf("serializing deployment: %w", err) } data, err := json.Marshal(sdep) @@ -841,7 +843,7 @@ func (b *localBackend) getLocalStacks() ([]tokens.QName, error) { files, err := listBucket(b.bucket, path) if err != nil { - return nil, errors.Wrap(err, "error listing stacks") + return nil, fmt.Errorf("error listing stacks: %w", err) } for _, file := range files { @@ -881,3 +883,12 @@ func (b *localBackend) UpdateStackTags(ctx context.Context, // The local backend does not currently persist tags. return errors.New("stack tags not supported in --local mode") } + +// Returns the original error in the chain. If `err` is nil, nil is returned. +func drillError(err error) error { + e := err + for errors.Unwrap(e) != nil { + e = errors.Unwrap(e) + } + return e +} diff --git a/pkg/backend/filestate/bucket.go b/pkg/backend/filestate/bucket.go index e26531424..d5bfc640f 100644 --- a/pkg/backend/filestate/bucket.go +++ b/pkg/backend/filestate/bucket.go @@ -2,11 +2,11 @@ package filestate import ( "context" + "fmt" "io" "path" "path/filepath" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" "gocloud.dev/blob" ) @@ -77,7 +77,7 @@ func listBucket(bucket Bucket, dir string) ([]*blob.ListObject, error) { break } if err != nil { - return nil, errors.Wrap(err, "could not list bucket") + return nil, fmt.Errorf("could not list bucket: %w", err) } files = append(files, file) } @@ -95,7 +95,7 @@ func objectName(obj *blob.ListObject) string { func removeAllByPrefix(bucket Bucket, dir string) error { files, err := listBucket(bucket, dir) if err != nil { - return errors.Wrap(err, "unable to list bucket objects for removal") + return fmt.Errorf("unable to list bucket objects for removal: %w", err) } for _, file := range files { @@ -113,7 +113,7 @@ func removeAllByPrefix(bucket Bucket, dir string) error { func renameObject(bucket Bucket, source string, dest string) error { err := bucket.Copy(context.TODO(), dest, source, nil) if err != nil { - return errors.Wrapf(err, "copying %s to %s", source, dest) + return fmt.Errorf("copying %s to %s: %w", source, dest, err) } err = bucket.Delete(context.TODO(), source) diff --git a/pkg/backend/filestate/gcpauth.go b/pkg/backend/filestate/gcpauth.go index 93e0ac387..7954a177b 100644 --- a/pkg/backend/filestate/gcpauth.go +++ b/pkg/backend/filestate/gcpauth.go @@ -3,6 +3,8 @@ package filestate import ( "context" "encoding/json" + "errors" + "fmt" "os" "golang.org/x/oauth2/google" @@ -11,7 +13,6 @@ import ( "cloud.google.com/go/storage" - "github.com/pkg/errors" "gocloud.dev/blob" "gocloud.dev/gcp" ) @@ -33,7 +34,7 @@ func googleCredentials(ctx context.Context) (*google.Credentials, error) { // so that users can override the default creds credentials, err := google.CredentialsFromJSON(ctx, []byte(creds), storage.ScopeReadWrite) if err != nil { - return nil, errors.Wrap(err, "unable to parse credentials from $GOOGLE_CREDENTIALS") + return nil, fmt.Errorf("unable to parse credentials from $GOOGLE_CREDENTIALS: %w", err) } return credentials, nil } @@ -43,7 +44,7 @@ func googleCredentials(ctx context.Context) (*google.Credentials, error) { // 2. application_default_credentials.json file in ~/.config/gcloud or $APPDATA\gcloud credentials, err := gcp.DefaultCredentials(ctx) if err != nil { - return nil, errors.Wrap(err, "unable to find gcp credentials") + return nil, fmt.Errorf("unable to find gcp credentials: %w", err) } return credentials, nil } diff --git a/pkg/backend/filestate/lock.go b/pkg/backend/filestate/lock.go index 730b24c91..0cabbb925 100644 --- a/pkg/backend/filestate/lock.go +++ b/pkg/backend/filestate/lock.go @@ -17,14 +17,13 @@ package filestate import ( "context" "encoding/json" + "errors" "fmt" "os" "os/user" "path" "time" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" diff --git a/pkg/backend/filestate/state.go b/pkg/backend/filestate/state.go index a713cfff0..231e17c5c 100644 --- a/pkg/backend/filestate/state.go +++ b/pkg/backend/filestate/state.go @@ -17,6 +17,7 @@ package filestate import ( "context" "encoding/json" + "errors" "fmt" "os" "path" @@ -28,7 +29,6 @@ import ( "github.com/pulumi/pulumi/pkg/v3/engine" - "github.com/pkg/errors" "gocloud.dev/blob" "gocloud.dev/gcerrors" @@ -133,7 +133,7 @@ func (b *localBackend) getStack(name tokens.QName) (*deploy.Snapshot, string, er chk, err := b.getCheckpoint(name) if err != nil { - return nil, file, errors.Wrap(err, "failed to load checkpoint") + return nil, file, fmt.Errorf("failed to load checkpoint: %w", err) } // Materialize an actual snapshot object. @@ -145,8 +145,7 @@ func (b *localBackend) getStack(name tokens.QName) (*deploy.Snapshot, string, er // Ensure the snapshot passes verification before returning it, to catch bugs early. if !DisableIntegrityChecking { if verifyerr := snapshot.VerifyIntegrity(); verifyerr != nil { - return nil, file, - errors.Wrapf(verifyerr, "%s: snapshot integrity failure; refusing to use it", file) + return nil, file, fmt.Errorf("%s: snapshot integrity failure; refusing to use it: %w", file, verifyerr) } } @@ -169,18 +168,18 @@ func (b *localBackend) saveStack(name tokens.QName, snap *deploy.Snapshot, sm se file := b.stackPath(name) m, ext := encoding.Detect(file) if m == nil { - return "", errors.Errorf("resource serialization failed; illegal markup extension: '%v'", ext) + return "", fmt.Errorf("resource serialization failed; illegal markup extension: '%v'", ext) } if filepath.Ext(file) == "" { file = file + ext } chk, err := stack.SerializeCheckpoint(name, snap, sm, false /* showSecrets */) if err != nil { - return "", errors.Wrap(err, "serializaing checkpoint") + return "", fmt.Errorf("serializaing checkpoint: %w", err) } byts, err := m.Marshal(chk) if err != nil { - return "", errors.Wrap(err, "An IO error occurred while marshalling the checkpoint") + return "", fmt.Errorf("An IO error occurred while marshalling the checkpoint: %w", err) } // Back up the existing file if it already exists. @@ -208,7 +207,7 @@ func (b *localBackend) saveStack(name tokens.QName, snap *deploy.Snapshot, sm se if err != nil { logging.V(7).Infof("Error while writing snapshot to: %s (attempt=%d, error=%s)", file, try, err) if try > 10 { - return false, nil, errors.Wrap(err, "An IO error occurred while writing the new snapshot file") + return false, nil, fmt.Errorf("An IO error occurred while writing the new snapshot file: %w", err) } return false, nil, nil } @@ -225,7 +224,7 @@ func (b *localBackend) saveStack(name tokens.QName, snap *deploy.Snapshot, sm se // And if we are retaining historical checkpoint information, write it out again if cmdutil.IsTruthy(os.Getenv("PULUMI_RETAIN_CHECKPOINTS")) { if err = b.bucket.WriteAll(context.TODO(), fmt.Sprintf("%v.%v", file, time.Now().UnixNano()), byts, nil); err != nil { - return "", errors.Wrap(err, "An IO error occurred while writing the new snapshot file") + return "", fmt.Errorf("An IO error occurred while writing the new snapshot file: %w", err) } } @@ -234,9 +233,10 @@ func (b *localBackend) saveStack(name tokens.QName, snap *deploy.Snapshot, sm se // out the checkpoint file since it may contain resource state updates. But we will warn the user that the // file is already written and might be bad. if verifyerr := snap.VerifyIntegrity(); verifyerr != nil { - return "", errors.Wrapf(verifyerr, - "%s: snapshot integrity failure; it was already written, but is invalid (backup available at %s)", - file, bck) + return "", fmt.Errorf( + "%s: snapshot integrity failure; it was already written, but is invalid (backup available at %s): %w", + file, bck, verifyerr) + } } @@ -323,7 +323,7 @@ func (b *localBackend) getHistory(name tokens.QName, pageSize int, page int) ([] allFiles, err := listBucket(b.bucket, dir) if err != nil { // History doesn't exist until a stack has been updated. - if gcerrors.Code(errors.Cause(err)) == gcerrors.NotFound { + if gcerrors.Code(drillError(err)) == gcerrors.NotFound { return nil, nil } return nil, err @@ -368,11 +368,11 @@ func (b *localBackend) getHistory(name tokens.QName, pageSize int, page int) ([] var update backend.UpdateInfo b, err := b.bucket.ReadAll(context.TODO(), filepath) if err != nil { - return nil, errors.Wrapf(err, "reading history file %s", filepath) + return nil, fmt.Errorf("reading history file %s: %w", filepath, err) } err = json.Unmarshal(b, &update) if err != nil { - return nil, errors.Wrapf(err, "reading history file %s", filepath) + return nil, fmt.Errorf("reading history file %s: %w", filepath, err) } updates = append(updates, update) @@ -391,7 +391,7 @@ func (b *localBackend) renameHistory(oldName tokens.QName, newName tokens.QName) allFiles, err := listBucket(b.bucket, oldHistory) if err != nil { // if there's nothing there, we don't really need to do a rename. - if gcerrors.Code(errors.Cause(err)) == gcerrors.NotFound { + if gcerrors.Code(drillError(err)) == gcerrors.NotFound { return nil } return err @@ -407,10 +407,10 @@ func (b *localBackend) renameHistory(oldName tokens.QName, newName tokens.QName) newBlob := path.Join(newHistory, newFileName) if err := b.bucket.Copy(context.TODO(), newBlob, oldBlob, nil); err != nil { - return errors.Wrap(err, "copying history file") + return fmt.Errorf("copying history file: %w", err) } if err := b.bucket.Delete(context.TODO(), oldBlob); err != nil { - return errors.Wrap(err, "deleting existing history file") + return fmt.Errorf("deleting existing history file: %w", err) } } diff --git a/pkg/backend/httpstate/backend.go b/pkg/backend/httpstate/backend.go index 97960d9ad..bf032e66f 100644 --- a/pkg/backend/httpstate/backend.go +++ b/pkg/backend/httpstate/backend.go @@ -18,6 +18,7 @@ import ( "context" cryptorand "crypto/rand" "encoding/hex" + "errors" "fmt" "io" "net" @@ -31,7 +32,7 @@ import ( "time" opentracing "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" + "github.com/skratchdot/open-golang/open" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -126,7 +127,7 @@ func New(d diag.Sink, cloudURL string) (Backend, error) { cloudURL = ValueOrDefaultURL(cloudURL) account, err := workspace.GetAccount(cloudURL) if err != nil { - return nil, errors.Wrap(err, "getting stored credentials") + return nil, fmt.Errorf("getting stored credentials: %w", err) } apiToken := account.AccessToken @@ -163,13 +164,13 @@ func loginWithBrowser(ctx context.Context, d diag.Sink, cloudURL string, opts di c := make(chan string) l, err := net.Listen("tcp", "127.0.0.1:") if err != nil { - return nil, errors.Wrap(err, "could not start listener") + return nil, fmt.Errorf("could not start listener: %w", err) } // Extract the port _, port, err := net.SplitHostPort(l.Addr().String()) if err != nil { - return nil, errors.Wrap(err, "could not determine port") + return nil, fmt.Errorf("could not determine port: %w", err) } // Generate a nonce we'll send with the request. @@ -229,6 +230,10 @@ func loginWithBrowser(ctx context.Context, d diag.Sink, cloudURL string, opts di return New(d, cloudURL) } +func SetDefaultOrg(url string, orgName string) error { + return workspace.SetBackendConfigDefaultOrg(url, orgName) +} + // Login logs into the target cloud URL and returns the cloud backend for it. func Login(ctx context.Context, d diag.Sink, cloudURL string, opts display.Options) (Backend, error) { cloudURL = ValueOrDefaultURL(cloudURL) @@ -269,8 +274,7 @@ func Login(ctx context.Context, d diag.Sink, cloudURL string, opts display.Optio } else if !cmdutil.Interactive() { // If interactive mode isn't enabled, the only way to specify a token is through the environment variable. // Fail the attempt to login. - return nil, errors.Errorf( - "%s must be set for login during non-interactive CLI sessions", AccessTokenEnvVar) + return nil, fmt.Errorf("%s must be set for login during non-interactive CLI sessions", AccessTokenEnvVar) } else { // If no access token is available from the environment, and we are interactive, prompt and offer to // open a browser to make it easy to generate and use a fresh token. @@ -333,7 +337,7 @@ func Login(ctx context.Context, d diag.Sink, cloudURL string, opts display.Optio if err != nil { return nil, err } else if !valid { - return nil, errors.Errorf("invalid access token") + return nil, fmt.Errorf("invalid access token") } // Save them. @@ -426,7 +430,7 @@ func (b *cloudBackend) parsePolicyPackReference(s string) (backend.PolicyPackRef orgName = split[0] policyPackName = split[1] default: - return nil, errors.Errorf("could not parse policy pack name '%s'; must be of the form "+ + return nil, fmt.Errorf("could not parse policy pack name '%s'; must be of the form "+ "/", s) } @@ -507,7 +511,7 @@ func (b *cloudBackend) parseStackName(s string) (qualifiedStackReference, error) q.Project = split[1] q.Name = split[2] default: - return qualifiedStackReference{}, errors.Errorf("could not parse stack name '%s'", s) + return qualifiedStackReference{}, fmt.Errorf("could not parse stack name '%s'", s) } return q, nil @@ -692,14 +696,14 @@ func (b *cloudBackend) CreateStack( tags, err := backend.GetEnvironmentTagsForCurrentStack() if err != nil { - return nil, errors.Wrap(err, "error determining initial tags") + return nil, fmt.Errorf("error determining initial tags: %w", err) } // Confirm the stack identity matches the environment. e.g. stack init foo/bar/baz shouldn't work // if the project name in Pulumi.yaml is anything other than "bar". projNameTag, ok := tags[apitype.ProjectNameTag] if ok && stackID.Project != projNameTag { - return nil, errors.Errorf("provided project name %q doesn't match Pulumi.yaml", stackID.Project) + return nil, fmt.Errorf("provided project name %q doesn't match Pulumi.yaml", stackID.Project) } apistack, err := b.client.CreateStack(ctx, stackID, tags) @@ -905,7 +909,7 @@ func (b *cloudBackend) createAndStartUpdate( // metadata changes. tags, err := backend.GetMergedStackTags(ctx, stack) if err != nil { - return client.UpdateIdentifier{}, 0, "", errors.Wrap(err, "getting stack tags") + return client.UpdateIdentifier{}, 0, "", fmt.Errorf("getting stack tags: %w", err) } version, token, err := b.client.StartUpdate(ctx, update, tags) if err != nil { @@ -1071,7 +1075,7 @@ func (b *cloudBackend) runEngineAction( } completeErr := u.Complete(status) if completeErr != nil { - res = result.Merge(res, result.FromError(errors.Wrap(completeErr, "failed to complete update"))) + res = result.Merge(res, result.FromError(fmt.Errorf("failed to complete update: %w", completeErr))) } return plan, changes, res @@ -1088,7 +1092,7 @@ func (b *cloudBackend) CancelCurrentUpdate(ctx context.Context, stackRef backend } if stack.ActiveUpdate == "" { - return errors.Errorf("stack %v has never been updated", stackRef) + return fmt.Errorf("stack %v has never been updated", stackRef) } // Compute the update identifier and attempt to cancel the update. @@ -1123,7 +1127,7 @@ func (b *cloudBackend) GetHistory( // Convert types from the apitype package into their internal counterparts. cfg, err := convertConfig(update.Config) if err != nil { - return nil, errors.Wrap(err, "converting configuration") + return nil, fmt.Errorf("converting configuration: %w", err) } beUpdates = append(beUpdates, backend.UpdateInfo{ @@ -1218,7 +1222,9 @@ func (b *cloudBackend) ExportDeploymentForVersion( // The first stack update version is 1, and monotonically increasing from there. versionNumber, err := strconv.Atoi(version) if err != nil || versionNumber <= 0 { - return nil, errors.Errorf("%q is not a valid stack version. It should be a positive integer.", version) + return nil, fmt.Errorf( + "%q is not a valid stack version. It should be a positive integer", + version) } return b.exportDeployment(ctx, stack.Ref(), &versionNumber) @@ -1258,9 +1264,9 @@ func (b *cloudBackend) ImportDeployment(ctx context.Context, stack backend.Stack ctx, backend.ActionLabel(apitype.StackImportUpdate, false /*dryRun*/), update, display.Options{Color: colors.Always}) if err != nil { - return errors.Wrap(err, "waiting for import") + return fmt.Errorf("waiting for import: %w", err) } else if status != apitype.StatusSucceeded { - return errors.Errorf("import unsuccessful: status %v", status) + return fmt.Errorf("import unsuccessful: status %v", status) } return nil } @@ -1449,7 +1455,7 @@ func IsValidAccessToken(ctx context.Context, cloudURL, accessToken string) (bool if errResp, ok := err.(*apitype.ErrorResponse); ok && errResp.Code == 401 { return false, "", nil } - return false, "", errors.Wrapf(err, "getting user info from %v", cloudURL) + return false, "", fmt.Errorf("getting user info from %v: %w", cloudURL, err) } return true, username, nil @@ -1481,7 +1487,7 @@ func (c httpstateBackendClient) GetStackOutputs(ctx context.Context, name string // When using the cloud backend, require that stack references are fully qualified so they // look like "//" if strings.Count(name, "/") != 2 { - return nil, errors.Errorf("a stack reference's name should be of the form " + + return nil, fmt.Errorf("a stack reference's name should be of the form " + "'//'. See https://pulumi.io/help/stack-reference for more information.") } diff --git a/pkg/backend/httpstate/backend_test.go b/pkg/backend/httpstate/backend_test.go index d78d4d36a..3a516b899 100644 --- a/pkg/backend/httpstate/backend_test.go +++ b/pkg/backend/httpstate/backend_test.go @@ -14,9 +14,10 @@ package httpstate import ( - "github.com/stretchr/testify/assert" "os" "testing" + + "github.com/stretchr/testify/assert" ) func TestValueOrDefaultURL(t *testing.T) { diff --git a/pkg/backend/httpstate/client/api.go b/pkg/backend/httpstate/client/api.go index 4ea9415e3..6567dcb67 100644 --- a/pkg/backend/httpstate/client/api.go +++ b/pkg/backend/httpstate/client/api.go @@ -19,6 +19,7 @@ import ( "compress/gzip" "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -31,7 +32,6 @@ import ( "github.com/google/go-querystring/query" "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/util/tracing" "github.com/pulumi/pulumi/pkg/v3/version" @@ -134,14 +134,14 @@ func pulumiAPICall(ctx context.Context, d diag.Sink, cloudAPI, method, path stri writer := gzip.NewWriter(&buf) defer contract.IgnoreClose(writer) if _, err := writer.Write(body); err != nil { - return "", nil, errors.Wrapf(err, "compressing payload") + return "", nil, fmt.Errorf("compressing payload: %w", err) } // gzip.Writer will not actually write anything unless it is flushed, // and it will not actually write the GZip footer unless it is closed. (Close also flushes) // Without this, the compressed bytes do not decompress properly e.g. in python. if err := writer.Close(); err != nil { - return "", nil, errors.Wrapf(err, "closing compressed payload") + return "", nil, fmt.Errorf("closing compressed payload: %w", err) } logging.V(apiRequestDetailLogLevel).Infof("gzip compression ratio: %f, original size: %d bytes", @@ -153,7 +153,7 @@ func pulumiAPICall(ctx context.Context, d diag.Sink, cloudAPI, method, path stri req, err := http.NewRequest(method, url, bodyReader) if err != nil { - return "", nil, errors.Wrapf(err, "creating new HTTP request") + return "", nil, fmt.Errorf("creating new HTTP request: %w", err) } requestSpan, requestContext := opentracing.StartSpanFromContext(ctx, getEndpointName(method, path), @@ -210,7 +210,7 @@ func pulumiAPICall(ctx context.Context, d diag.Sink, cloudAPI, method, path stri } if err != nil { - return "", nil, errors.Wrapf(err, "performing HTTP request") + return "", nil, fmt.Errorf("performing HTTP request: %w", err) } logging.V(apiRequestLogLevel).Infof("Pulumi API call response code (%s): %v", url, resp.Status) @@ -228,8 +228,7 @@ func pulumiAPICall(ctx context.Context, d diag.Sink, cloudAPI, method, path stri // type, and if not just return the raw response text. respBody, err := readBody(resp) if err != nil { - return "", nil, errors.Wrapf( - err, "API call failed (%s), could not read response", resp.Status) + return "", nil, fmt.Errorf("API call failed (%s), could not read response: %w", resp.Status, err) } // Provide a better error if using an authenticated call without having logged in first. @@ -260,7 +259,7 @@ func pulumiRESTCall(ctx context.Context, diag diag.Sink, cloudAPI, method, path if queryObj != nil { queryValues, err := query.Values(queryObj) if err != nil { - return errors.Wrapf(err, "marshalling query object as JSON") + return fmt.Errorf("marshalling query object as JSON: %w", err) } query := queryValues.Encode() if len(query) > 0 { @@ -274,7 +273,7 @@ func pulumiRESTCall(ctx context.Context, diag diag.Sink, cloudAPI, method, path if reqObj != nil { reqBody, err = json.Marshal(reqObj) if err != nil { - return errors.Wrapf(err, "marshalling request object as JSON") + return fmt.Errorf("marshalling request object as JSON: %w", err) } } @@ -287,7 +286,7 @@ func pulumiRESTCall(ctx context.Context, diag diag.Sink, cloudAPI, method, path // Read API response respBody, err := readBody(resp) if err != nil { - return errors.Wrapf(err, "reading response from API") + return fmt.Errorf("reading response from API: %w", err) } if logging.V(apiRequestDetailLogLevel) { logging.V(apiRequestDetailLogLevel).Infof("Pulumi API call response body (%s): %v", url, string(respBody)) @@ -303,7 +302,7 @@ func pulumiRESTCall(ctx context.Context, diag diag.Sink, cloudAPI, method, path } else { // Else, unmarshal as JSON. if err = json.Unmarshal(respBody, respObj); err != nil { - return errors.Wrapf(err, "unmarshalling response object") + return fmt.Errorf("unmarshalling response object: %w", err) } } } @@ -323,7 +322,7 @@ func readBody(resp *http.Response) ([]byte, error) { if len(contentEncoding) > 1 { // We only know how to deal with gzip. We can't handle additional encodings layered on top of it. - return nil, errors.Errorf("can't handle content encodings %v", contentEncoding) + return nil, fmt.Errorf("can't handle content encodings %v", contentEncoding) } switch contentEncoding[0] { @@ -337,11 +336,11 @@ func readBody(resp *http.Response) ([]byte, error) { defer contract.IgnoreClose(reader) } if err != nil { - return nil, errors.Wrap(err, "reading gzip-compressed body") + return nil, fmt.Errorf("reading gzip-compressed body: %w", err) } return ioutil.ReadAll(reader) default: - return nil, errors.Errorf("unrecognized encoding %s", contentEncoding[0]) + return nil, fmt.Errorf("unrecognized encoding %s", contentEncoding[0]) } } diff --git a/pkg/backend/httpstate/client/client.go b/pkg/backend/httpstate/client/client.go index bd5543947..b7afbc60a 100644 --- a/pkg/backend/httpstate/client/client.go +++ b/pkg/backend/httpstate/client/client.go @@ -17,6 +17,7 @@ package client import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -28,7 +29,6 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" "github.com/blang/semver" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/engine" "github.com/pulumi/pulumi/pkg/v3/util/validation" @@ -303,7 +303,7 @@ func (pc *Client) CreateStack( ctx context.Context, stackID StackIdentifier, tags map[apitype.StackTagName]string) (apitype.Stack, error) { // Validate names and tags. if err := validation.ValidateStackProperties(stackID.Stack, tags); err != nil { - return apitype.Stack{}, errors.Wrap(err, "validating stack properties") + return apitype.Stack{}, fmt.Errorf("validating stack properties: %w", err) } stack := apitype.Stack{ @@ -521,7 +521,7 @@ func (pc *Client) StartUpdate(ctx context.Context, update UpdateIdentifier, // Validate names and tags. if err := validation.ValidateStackProperties(update.StackIdentifier.Stack, tags); err != nil { - return 0, "", errors.Wrap(err, "validating stack properties") + return 0, "", fmt.Errorf("validating stack properties: %w", err) } req := apitype.StartUpdateRequest{ @@ -543,7 +543,7 @@ func (pc *Client) ListPolicyGroups(ctx context.Context, orgName string, inContTo var resp apitype.ListPolicyGroupsResponse err := pc.restCall(ctx, "GET", listPolicyGroupsPath(orgName), nil, nil, &resp) if err != nil { - return resp, nil, errors.Wrapf(err, "List Policy Groups failed") + return resp, nil, fmt.Errorf("List Policy Groups failed: %w", err) } return resp, nil, nil } @@ -555,7 +555,7 @@ func (pc *Client) ListPolicyPacks(ctx context.Context, orgName string, inContTok var resp apitype.ListPolicyPacksResponse err := pc.restCall(ctx, "GET", listPolicyPacksPath(orgName), nil, nil, &resp) if err != nil { - return resp, nil, errors.Wrapf(err, "List Policy Packs failed") + return resp, nil, fmt.Errorf("List Policy Packs failed: %w", err) } return resp, nil, nil } @@ -609,7 +609,7 @@ func (pc *Client) PublishPolicyPack(ctx context.Context, orgName string, var resp apitype.CreatePolicyPackResponse err := pc.restCall(ctx, "POST", publishPolicyPackPath(orgName), nil, req, &resp) if err != nil { - return "", errors.Wrapf(err, "Publish policy pack failed") + return "", fmt.Errorf("Publish policy pack failed: %w", err) } // @@ -619,7 +619,7 @@ func (pc *Client) PublishPolicyPack(ctx context.Context, orgName string, putReq, err := http.NewRequest(http.MethodPut, resp.UploadURI, dirArchive) if err != nil { - return "", errors.Wrapf(err, "Failed to upload compressed PolicyPack") + return "", fmt.Errorf("Failed to upload compressed PolicyPack: %w", err) } for k, v := range resp.RequiredHeaders { @@ -628,7 +628,7 @@ func (pc *Client) PublishPolicyPack(ctx context.Context, orgName string, _, err = http.DefaultClient.Do(putReq) if err != nil { - return "", errors.Wrapf(err, "Failed to upload compressed PolicyPack") + return "", fmt.Errorf("Failed to upload compressed PolicyPack: %w", err) } // @@ -645,7 +645,7 @@ func (pc *Client) PublishPolicyPack(ctx context.Context, orgName string, err = pc.restCall(ctx, "POST", publishPolicyPackPublishComplete(orgName, analyzerInfo.Name, version), nil, nil, nil) if err != nil { - return "", errors.Wrapf(err, "Request to signal completion of the publish operation failed") + return "", fmt.Errorf("Request to signal completion of the publish operation failed: %w", err) } return version, nil @@ -708,7 +708,7 @@ func (pc *Client) ApplyPolicyPack(ctx context.Context, orgName, policyGroup, err := pc.restCall(ctx, http.MethodPatch, updatePolicyGroupPath(orgName, policyGroup), nil, req, nil) if err != nil { - return errors.Wrapf(err, "Enable policy pack failed") + return fmt.Errorf("Enable policy pack failed: %w", err) } return nil } @@ -720,7 +720,7 @@ func (pc *Client) GetPolicyPackSchema(ctx context.Context, orgName, err := pc.restCall(ctx, http.MethodGet, getPolicyPackConfigSchemaPath(orgName, policyPackName, versionTag), nil, nil, &resp) if err != nil { - return nil, errors.Wrap(err, "Retrieving policy pack config schema failed") + return nil, fmt.Errorf("Retrieving policy pack config schema failed: %w", err) } return &resp, nil } @@ -744,7 +744,7 @@ func (pc *Client) DisablePolicyPack(ctx context.Context, orgName string, policyG err := pc.restCall(ctx, http.MethodPatch, updatePolicyGroupPath(orgName, policyGroup), nil, req, nil) if err != nil { - return errors.Wrapf(err, "Request to disable policy pack failed") + return fmt.Errorf("Request to disable policy pack failed: %w", err) } return nil } @@ -754,7 +754,7 @@ func (pc *Client) RemovePolicyPack(ctx context.Context, orgName string, policyPa path := deletePolicyPackPath(orgName, policyPackName) err := pc.restCall(ctx, http.MethodDelete, path, nil, nil, nil) if err != nil { - return errors.Wrapf(err, "Request to remove policy pack failed") + return fmt.Errorf("Request to remove policy pack failed: %w", err) } return nil } @@ -767,7 +767,7 @@ func (pc *Client) RemovePolicyPackByVersion(ctx context.Context, orgName string, path := deletePolicyPackVersionPath(orgName, policyPackName, versionTag) err := pc.restCall(ctx, http.MethodDelete, path, nil, nil, nil) if err != nil { - return errors.Wrapf(err, "Request to remove policy pack failed") + return fmt.Errorf("Request to remove policy pack failed: %w", err) } return nil } @@ -776,12 +776,12 @@ func (pc *Client) RemovePolicyPackByVersion(ctx context.Context, orgName string, func (pc *Client) DownloadPolicyPack(ctx context.Context, url string) (io.ReadCloser, error) { getS3Req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { - return nil, errors.Wrapf(err, "Failed to download compressed PolicyPack") + return nil, fmt.Errorf("Failed to download compressed PolicyPack: %w", err) } resp, err := http.DefaultClient.Do(getS3Req) if err != nil { - return nil, errors.Wrapf(err, "Failed to download compressed PolicyPack") + return nil, fmt.Errorf("Failed to download compressed PolicyPack: %w", err) } return resp.Body, nil diff --git a/pkg/backend/httpstate/policypack.go b/pkg/backend/httpstate/policypack.go index 3c302d522..02be05c86 100644 --- a/pkg/backend/httpstate/policypack.go +++ b/pkg/backend/httpstate/policypack.go @@ -12,7 +12,6 @@ import ( "strconv" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/pkg/v3/backend/httpstate/client" "github.com/pulumi/pulumi/pkg/v3/engine" @@ -183,8 +182,7 @@ func (pack *cloudPolicyPack) Publish( if strings.EqualFold(runtime, "nodejs") { packTarball, err = npm.Pack(op.PlugCtx.Pwd, os.Stderr) if err != nil { - return result.FromError( - errors.Wrap(err, "could not publish policies because of error running npm pack")) + return result.FromError(fmt.Errorf("could not publish policies because of error running npm pack: %w", err)) } } else { // npm pack puts all the files in a "package" subdirectory inside the .tgz it produces, so we'll do @@ -192,8 +190,7 @@ func (pack *cloudPolicyPack) Publish( // package directory to determine the runtime of the policy pack. packTarball, err = archive.TGZ(op.PlugCtx.Pwd, "package", true /*useDefaultExcludes*/) if err != nil { - return result.FromError( - errors.Wrap(err, "could not publish policies because of error creating the .tgz")) + return result.FromError(fmt.Errorf("could not publish policies because of error creating the .tgz: %w", err)) } } @@ -253,18 +250,18 @@ func installRequiredPolicy(finalDir string, tgz io.ReadCloser) error { // If part of the directory tree is missing, ioutil.TempDir will return an error, so make sure // the path we're going to create the temporary folder in actually exists. if err := os.MkdirAll(filepath.Dir(finalDir), 0700); err != nil { - return errors.Wrap(err, "creating plugin root") + return fmt.Errorf("creating plugin root: %w", err) } tempDir, err := ioutil.TempDir(filepath.Dir(finalDir), fmt.Sprintf("%s.tmp", filepath.Base(finalDir))) if err != nil { - return errors.Wrapf(err, "creating plugin directory %s", tempDir) + return fmt.Errorf("creating plugin directory %s: %w", tempDir, err) } // The policy pack files are actually in a directory called `package`. tempPackageDir := filepath.Join(tempDir, packageDir) if err := os.MkdirAll(tempPackageDir, 0700); err != nil { - return errors.Wrap(err, "creating plugin root") + return fmt.Errorf("creating plugin root: %w", err) } // If we early out of this function, try to remove the temp folder we created. @@ -284,13 +281,13 @@ func installRequiredPolicy(finalDir string, tgz io.ReadCloser) error { // unable to rename the directory. That's OK, just ignore the error. The temp directory created // as part of the install will be cleaned up when we exit by the defer above. if err := os.Rename(tempPackageDir, finalDir); err != nil && !os.IsExist(err) { - return errors.Wrap(err, "moving plugin") + return fmt.Errorf("moving plugin: %w", err) } projPath := filepath.Join(finalDir, "PulumiPolicy.yaml") proj, err := workspace.LoadPolicyPack(projPath) if err != nil { - return errors.Wrapf(err, "failed to load policy project at %s", finalDir) + return fmt.Errorf("failed to load policy project at %s: %w", finalDir, err) } // TODO[pulumi/pulumi#1334]: move to the language plugins so we don't have to hard code here. @@ -312,10 +309,9 @@ func installRequiredPolicy(finalDir string, tgz io.ReadCloser) error { func completeNodeJSInstall(finalDir string) error { if bin, err := npm.Install(finalDir, false /*production*/, nil, os.Stderr); err != nil { - return errors.Wrapf( - err, - "failed to install dependencies of policy pack; you may need to re-run `%s install` "+ - "in %q before this policy pack works", bin, finalDir) + return fmt.Errorf("failed to install dependencies of policy pack; you may need to re-run `%s install` "+ + "in %q before this policy pack works"+": %w", bin, finalDir, err) + } return nil @@ -330,7 +326,7 @@ func completePythonInstall(finalDir, projPath string, proj *workspace.PolicyPack // Save project with venv info. proj.Runtime.SetOption("virtualenv", venvDir) if err := proj.Save(projPath); err != nil { - return errors.Wrapf(err, "saving project at %s", projPath) + return fmt.Errorf("saving project at %s: %w", projPath, err) } return nil diff --git a/pkg/backend/httpstate/snapshot.go b/pkg/backend/httpstate/snapshot.go index 98a2b66bd..af571084c 100644 --- a/pkg/backend/httpstate/snapshot.go +++ b/pkg/backend/httpstate/snapshot.go @@ -16,8 +16,8 @@ package httpstate import ( "context" + "fmt" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/pkg/v3/backend/httpstate/client" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" @@ -45,7 +45,7 @@ func (persister *cloudSnapshotPersister) Save(snapshot *deploy.Snapshot) error { } deployment, err := stack.SerializeDeployment(snapshot, persister.sm, false /* showSecrets */) if err != nil { - return errors.Wrap(err, "serializing deployment") + return fmt.Errorf("serializing deployment: %w", err) } return persister.backend.client.PatchUpdateCheckpoint(persister.context, persister.update, deployment, token) } diff --git a/pkg/backend/httpstate/state.go b/pkg/backend/httpstate/state.go index 860ab509c..d0d18592b 100644 --- a/pkg/backend/httpstate/state.go +++ b/pkg/backend/httpstate/state.go @@ -24,7 +24,6 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/pkg/v3/backend/display" "github.com/pulumi/pulumi/pkg/v3/backend/httpstate/client" @@ -171,7 +170,7 @@ func (u *cloudUpdate) recordEngineEvents(startingSeqNumber int, events []engine. for idx, event := range events { apiEvent, convErr := display.ConvertEngineEvent(event) if convErr != nil { - return errors.Wrap(convErr, "converting engine event") + return fmt.Errorf("converting engine event: %w", convErr) } // Each event within an update must have a unique sequence number. Any request to @@ -294,7 +293,7 @@ func (b *cloudBackend) getTarget(ctx context.Context, stackRef backend.StackRefe return nil, fmt.Errorf("the stack '%s' is newer than what this version of the Pulumi CLI understands. "+ "Please update your version of the Pulumi CLI", stackRef.Name()) default: - return nil, errors.Wrap(err, "could not deserialize deployment") + return nil, fmt.Errorf("could not deserialize deployment: %w", err) } } diff --git a/pkg/backend/snapshot.go b/pkg/backend/snapshot.go index ffc9e2731..9f6c8cbe8 100644 --- a/pkg/backend/snapshot.go +++ b/pkg/backend/snapshot.go @@ -15,12 +15,12 @@ package backend import ( + "errors" + "fmt" "reflect" "sort" "time" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/engine" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/secrets" @@ -603,14 +603,14 @@ func (sm *SnapshotManager) snap() *deploy.Snapshot { func (sm *SnapshotManager) saveSnapshot() error { snap := sm.snap() if err := snap.NormalizeURNReferences(); err != nil { - return errors.Wrap(err, "failed to normalize URN references") + return fmt.Errorf("failed to normalize URN references: %w", err) } if err := sm.persister.Save(snap); err != nil { - return errors.Wrap(err, "failed to save snapshot") + return fmt.Errorf("failed to save snapshot: %w", err) } if sm.doVerify { if err := snap.VerifyIntegrity(); err != nil { - return errors.Wrapf(err, "failed to verify snapshot") + return fmt.Errorf("failed to verify snapshot: %w", err) } } return nil diff --git a/pkg/backend/stack.go b/pkg/backend/stack.go index 05c82ec38..81f913c59 100644 --- a/pkg/backend/stack.go +++ b/pkg/backend/stack.go @@ -19,8 +19,6 @@ import ( "fmt" "path/filepath" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/engine" "github.com/pulumi/pulumi/pkg/v3/operations" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" @@ -185,7 +183,7 @@ func GetEnvironmentTagsForCurrentStack() (map[apitype.StackTagName]string, error if projPath != "" { proj, err := workspace.LoadProject(projPath) if err != nil { - return nil, errors.Wrapf(err, "error loading project %q", projPath) + return nil, fmt.Errorf("error loading project %q: %w", projPath, err) } tags[apitype.ProjectNameTag] = proj.Name.String() tags[apitype.ProjectRuntimeTag] = proj.Runtime.Name() @@ -226,7 +224,7 @@ func addGitMetadataToStackTags(tags map[apitype.StackTagName]string, projPath st tags[apitype.VCSRepositoryNameTag] = vcsInfo.Repo tags[apitype.VCSRepositoryKindTag] = vcsInfo.Kind } else { - return errors.Wrapf(err, "detecting VCS info for stack tags for remote %v", remoteURL) + return fmt.Errorf("detecting VCS info for stack tags for remote %v: %w", remoteURL, err) } // Set the old stack tags keys as GitHub so that the UI will continue to work, // regardless of whether the remote URL is a GitHub URL or not. diff --git a/pkg/cmd/pulumi/about.go b/pkg/cmd/pulumi/about.go index ea70fcf89..1d04b9c3c 100644 --- a/pkg/cmd/pulumi/about.go +++ b/pkg/cmd/pulumi/about.go @@ -17,6 +17,7 @@ package main import ( "bytes" "encoding/json" + "errors" "flag" "fmt" "io" @@ -29,7 +30,6 @@ import ( "strings" "github.com/blang/semver" - "github.com/pkg/errors" "github.com/shirou/gopsutil/host" "github.com/spf13/cobra" @@ -117,7 +117,7 @@ func getSummaryAbout(transitiveDependencies bool) summaryAbout { } var plugins []pluginAbout addError := func(err error, message string) { - err = errors.Wrap(err, message) + err = fmt.Errorf("%s: %w", message, err) result.ErrorMessages = append(result.ErrorMessages, err.Error()) result.Errors = append(result.Errors, err) } @@ -439,7 +439,7 @@ func getGoProgramDependencies(transitive bool) ([]programDependencieAbout, error cmd := exec.Command(ex, cmdArgs...) var out []byte if out, err = cmd.Output(); err != nil { - return nil, errors.Wrap(err, "Failed to get modules") + return nil, fmt.Errorf("Failed to get modules: %w", err) } dec := json.NewDecoder(bytes.NewReader(out)) @@ -450,7 +450,7 @@ func getGoProgramDependencies(transitive bool) ([]programDependencieAbout, error if err == io.EOF { break } - return nil, errors.Wrapf(err, "Failed to parse \"%s %s\" output", ex, strings.Join(cmdArgs, " ")) + return nil, fmt.Errorf("Failed to parse \"%s %s\" output: %w", ex, strings.Join(cmdArgs, " "), err) } parsed = append(parsed, m) @@ -525,7 +525,7 @@ func getPythonProgramDependencies(proj *workspace.Project, rootDir string, var result []programDependencieAbout err = json.Unmarshal([]byte(out), &result) if err != nil { - return nil, errors.Wrapf(err, "Failed to parse \"python %s\" result", strings.Join(cmdArgs, " ")) + return nil, fmt.Errorf("Failed to parse \"python %s\" result: %w", strings.Join(cmdArgs, " "), err) } return result, nil @@ -553,7 +553,7 @@ func getDotNetProgramDependencies(proj *workspace.Project, transitive bool) ([]p } cmd := exec.Command(ex, cmdArgs...) if out, err = cmd.Output(); err != nil { - return nil, errors.Wrapf(err, "Failed to call \"%s\"", ex) + return nil, fmt.Errorf("Failed to call \"%s\": %w", ex, err) } lines := strings.Split(strings.ReplaceAll(string(out), "\r\n", "\n"), "\n") var packages []programDependencieAbout @@ -577,7 +577,7 @@ func getDotNetProgramDependencies(proj *workspace.Project, transitive bool) ([]p // Transitive package => name version version = 1 } else { - return nil, errors.Errorf("Failed to parse \"%s\"", p) + return nil, fmt.Errorf("Failed to parse \"%s\"", p) } packages = append(packages, programDependencieAbout{ Name: nameRequiredVersion[0], @@ -607,18 +607,18 @@ type yarnLockTree struct { func parseYarnLockFile(path string) ([]programDependencieAbout, error) { ex, err := executable.FindExecutable("yarn") if err != nil { - return nil, errors.Wrapf(err, "Found %s but no yarn executable", path) + return nil, fmt.Errorf("Found %s but no yarn executable: %w", path, err) } cmdArgs := []string{"list", "--json"} cmd := exec.Command(ex, cmdArgs...) out, err := cmd.Output() if err != nil { - return nil, errors.Wrapf(err, "Failed to run \"%s %s\"", ex, strings.Join(cmdArgs, " ")) + return nil, fmt.Errorf("Failed to run \"%s %s\": %w", ex, strings.Join(cmdArgs, " "), err) } var lock yarnLock if err = json.Unmarshal(out, &lock); err != nil { - return nil, errors.Wrapf(err, "Failed to parse\"%s %s\"", ex, strings.Join(cmdArgs, " ")) + return nil, fmt.Errorf("Failed to parse\"%s %s\": %w", ex, strings.Join(cmdArgs, " "), err) } leafs := lock.Data.Trees @@ -627,11 +627,11 @@ func parseYarnLockFile(path string) ([]programDependencieAbout, error) { // Has the form name@version splitName := func(index int, nameVersion string) (string, string, error) { if nameVersion == "" { - return "", "", errors.Errorf("Expected \"name\" in dependency %d", index) + return "", "", fmt.Errorf("Expected \"name\" in dependency %d", index) } split := strings.LastIndex(nameVersion, "@") if split == -1 { - return "", "", errors.Errorf("Failed to parse name and version from %s", nameVersion) + return "", "", fmt.Errorf("Failed to parse name and version from %s", nameVersion) } return nameVersion[:split], nameVersion[split+1:], nil } @@ -667,17 +667,17 @@ type npmPackage struct { func parseNpmLockFile(path string) ([]programDependencieAbout, error) { ex, err := executable.FindExecutable("npm") if err != nil { - return nil, errors.Wrapf(err, "Found %s but not npm", path) + return nil, fmt.Errorf("Found %s but not npm: %w", path, err) } cmdArgs := []string{"ls", "--json", "--depth=0"} cmd := exec.Command(ex, cmdArgs...) out, err := cmd.Output() if err != nil { - return nil, errors.Wrapf(err, `Failed to run "%s %s"`, ex, strings.Join(cmdArgs, " ")) + return nil, fmt.Errorf(`Failed to run "%s %s": %w`, ex, strings.Join(cmdArgs, " "), err) } file := npmFile{} if err = json.Unmarshal(out, &file); err != nil { - return nil, errors.Wrapf(err, `Failed to parse \"%s %s"`, ex, strings.Join(cmdArgs, " ")) + return nil, fmt.Errorf(`Failed to parse \"%s %s": %w`, ex, strings.Join(cmdArgs, " "), err) } result := make([]programDependencieAbout, len(file.Dependencies)) var i int @@ -704,7 +704,7 @@ func crossCheckPackageJSONFile(path string, file []byte, var body packageJSON if err := json.Unmarshal(file, &body); err != nil { - return nil, errors.Wrapf(err, "Could not parse %s", path) + return nil, fmt.Errorf("Could not parse %s: %w", path, err) } dependencies := make(map[string]string) for k, v := range body.Dependencies { @@ -761,19 +761,19 @@ func getNodeProgramDependencies(rootDir string, transitive bool) ([]programDepen return nil, err } } else if os.IsNotExist(err) { - return nil, errors.Errorf("Could not find either %s or %s", yarnFile, npmFile) + return nil, fmt.Errorf("Could not find either %s or %s", yarnFile, npmFile) } else { - return nil, errors.Wrap(err, "Could not get node dependency data") + return nil, fmt.Errorf("Could not get node dependency data: %w", err) } if !transitive { file, err := ioutil.ReadFile(packageFile) if os.IsNotExist(err) { - return nil, errors.Errorf("Could not find %s. "+ + return nil, fmt.Errorf("Could not find %s. "+ "Please include this in your report and run "+ `pulumi about --transitive" to get a list of used packages`, packageFile) } else if err != nil { - return nil, errors.Wrapf(err, "Could not read %s", packageFile) + return nil, fmt.Errorf("Could not read %s: %w", packageFile, err) } return crossCheckPackageJSONFile(packageFile, file, result) } @@ -793,7 +793,7 @@ func getProgramDependenciesAbout(proj *workspace.Project, root string, case langDotnet: return getDotNetProgramDependencies(proj, transitive) default: - return nil, errors.Errorf("Unknown Language: %s", language) + return nil, fmt.Errorf("Unknown Language: %s", language) } } @@ -872,11 +872,11 @@ func getProjectRuntimeAbout(proj *workspace.Project) (projectRuntimeAbout, error case langNodejs: ex, err = executable.FindExecutable("node") if err != nil { - return projectRuntimeAbout{}, errors.Wrap(err, "Could not find node executable") + return projectRuntimeAbout{}, fmt.Errorf("Could not find node executable: %w", err) } cmd := exec.Command(ex, "--version") if out, err = cmd.Output(); err != nil { - return projectRuntimeAbout{}, errors.Wrap(err, "Failed to get node version") + return projectRuntimeAbout{}, fmt.Errorf("Failed to get node version: %w", err) } version = string(out) case langPython: @@ -889,31 +889,31 @@ func getProjectRuntimeAbout(proj *workspace.Project) (projectRuntimeAbout, error return projectRuntimeAbout{}, err } if out, err = cmd.Output(); err != nil { - return projectRuntimeAbout{}, errors.Wrap(err, "Failed to get python version") + return projectRuntimeAbout{}, fmt.Errorf("Failed to get python version: %w", err) } version = "v" + strings.TrimPrefix(string(out), "Python ") case langGo: ex, err = executable.FindExecutable("go") if err != nil { - return projectRuntimeAbout{}, errors.Wrap(err, "Could not find python executable") + return projectRuntimeAbout{}, fmt.Errorf("Could not find python executable: %w", err) } cmd := exec.Command(ex, "version") if out, err = cmd.Output(); err != nil { - return projectRuntimeAbout{}, errors.Wrap(err, "Failed to get go version") + return projectRuntimeAbout{}, fmt.Errorf("Failed to get go version: %w", err) } version = "v" + strings.TrimPrefix(string(out), "go version go") case langDotnet: ex, err = executable.FindExecutable("dotnet") if err != nil { - return projectRuntimeAbout{}, errors.Wrap(err, "Could not find dotnet executable") + return projectRuntimeAbout{}, fmt.Errorf("Could not find dotnet executable: %w", err) } cmd := exec.Command(ex, "--version") if out, err = cmd.Output(); err != nil { - return projectRuntimeAbout{}, errors.Wrap(err, "Failed to get dotnet version") + return projectRuntimeAbout{}, fmt.Errorf("Failed to get dotnet version: %w", err) } version = "v" + string(out) default: - return projectRuntimeAbout{}, errors.Errorf("Unknown Language: %s", language) + return projectRuntimeAbout{}, fmt.Errorf("Unknown Language: %s: %w", language, err) } version = strings.TrimSpace(version) return projectRuntimeAbout{ diff --git a/pkg/cmd/pulumi/config.go b/pkg/cmd/pulumi/config.go index b9ed2730a..5687dcc35 100644 --- a/pkg/cmd/pulumi/config.go +++ b/pkg/cmd/pulumi/config.go @@ -16,6 +16,7 @@ package main import ( "encoding/json" + "errors" "fmt" "io/ioutil" "os" @@ -24,7 +25,7 @@ import ( "strings" zxcvbn "github.com/nbutton23/zxcvbn-go" - "github.com/pkg/errors" + "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" @@ -152,7 +153,7 @@ func copySingleConfigKey(configKey string, path bool, currentStack backend.Stack var decrypter config.Decrypter key, err := parseConfigKey(configKey) if err != nil { - return errors.Wrap(err, "invalid configuration key") + return fmt.Errorf("invalid configuration key: %w", err) } v, ok, err := currentProjectStack.Config.Get(key, path) @@ -163,7 +164,7 @@ func copySingleConfigKey(configKey string, path bool, currentStack backend.Stack if v.Secure() { var err error if decrypter, err = getStackDecrypter(currentStack); err != nil { - return errors.Wrap(err, "could not create a decrypter") + return fmt.Errorf("could not create a decrypter: %w", err) } } else { decrypter = config.NewPanicCrypter() @@ -187,8 +188,7 @@ func copySingleConfigKey(configKey string, path bool, currentStack backend.Stack return saveProjectStack(destinationStack, destinationProjectStack) } - return errors.Errorf( - "configuration key '%s' not found for stack '%s'", prettyKey(key), currentStack.Ref()) + return fmt.Errorf("configuration key '%s' not found for stack '%s'", prettyKey(key), currentStack.Ref()) } func copyEntireConfigMap(currentStack backend.Stack, @@ -264,7 +264,7 @@ func newConfigGetCmd(stack *string) *cobra.Command { key, err := parseConfigKey(args[0]) if err != nil { - return errors.Wrap(err, "invalid configuration key") + return fmt.Errorf("invalid configuration key: %w", err) } return getConfig(s, key, path, jsonOut) @@ -305,7 +305,7 @@ func newConfigRmCmd(stack *string) *cobra.Command { key, err := parseConfigKey(args[0]) if err != nil { - return errors.Wrap(err, "invalid configuration key") + return fmt.Errorf("invalid configuration key: %w", err) } ps, err := loadProjectStack(s) @@ -359,7 +359,7 @@ func newConfigRmAllCmd(stack *string) *cobra.Command { for _, arg := range args { key, err := parseConfigKey(arg) if err != nil { - return errors.Wrap(err, "invalid configuration key") + return fmt.Errorf("invalid configuration key: %w", err) } err = ps.Config.Remove(key, path) @@ -424,13 +424,13 @@ func newConfigRefreshCmd(stack *string) *cobra.Command { _, err = os.Stat(backupFile) if os.IsNotExist(err) { if err = os.Rename(configPath, backupFile); err != nil { - return errors.Wrap(err, "backing up existing configuration file") + return fmt.Errorf("backing up existing configuration file: %w", err) } fmt.Printf("backed up existing configuration file to %s\n", backupFile) break } else if err != nil { - return errors.Wrap(err, "backing up existing configuration file") + return fmt.Errorf("backing up existing configuration file: %w", err) } backupFile = backupFile + ".bak" @@ -481,7 +481,7 @@ func newConfigSetCmd(stack *string) *cobra.Command { key, err := parseConfigKey(args[0]) if err != nil { - return errors.Wrap(err, "invalid configuration key") + return fmt.Errorf("invalid configuration key: %w", err) } var value string @@ -525,9 +525,8 @@ func newConfigSetCmd(stack *string) *cobra.Command { // If we saved a plaintext configuration value, and --plaintext was not passed, warn the user. if !plaintext && looksLikeSecret(key, value) { - return errors.Errorf( - "config value for '%s' looks like a secret; "+ - "rerun with --secret to encrypt it, or --plaintext if you meant to store in plaintext", + return fmt.Errorf("config value for '%s' looks like a secret; "+ + "rerun with --secret to encrypt it, or --plaintext if you meant to store in plaintext", key) } } @@ -662,7 +661,7 @@ func parseKeyValuePair(pair string) (config.Key, string, error) { } key, err := parseConfigKey(splitArg[0]) if err != nil { - return config.Key{}, "", errors.Wrap(err, "invalid configuration key") + return config.Key{}, "", fmt.Errorf("invalid configuration key: %w", err) } value := splitArg[1] @@ -769,7 +768,7 @@ func listConfig(stack backend.Stack, showSecrets bool, jsonOut bool) error { decrypted, err := cfg[key].Value(decrypter) if err != nil { - return errors.Wrap(err, "could not decrypt configuration value") + return fmt.Errorf("could not decrypt configuration value: %w", err) } entry.Value = &decrypted @@ -800,7 +799,7 @@ func listConfig(stack backend.Stack, showSecrets bool, jsonOut bool) error { for _, key := range keys { decrypted, err := cfg[key].Value(decrypter) if err != nil { - return errors.Wrap(err, "could not decrypt configuration value") + return fmt.Errorf("could not decrypt configuration value: %w", err) } rows = append(rows, cmdutil.TableRow{Columns: []string{prettyKey(key), decrypted}}) @@ -832,14 +831,14 @@ func getConfig(stack backend.Stack, key config.Key, path, jsonOut bool) error { if v.Secure() { var err error if d, err = getStackDecrypter(stack); err != nil { - return errors.Wrap(err, "could not create a decrypter") + return fmt.Errorf("could not create a decrypter: %w", err) } } else { d = config.NewPanicCrypter() } raw, err := v.Value(d) if err != nil { - return errors.Wrap(err, "could not decrypt configuration value") + return fmt.Errorf("could not decrypt configuration value: %w", err) } if jsonOut { @@ -868,8 +867,7 @@ func getConfig(stack backend.Stack, key config.Key, path, jsonOut bool) error { return nil } - return errors.Errorf( - "configuration key '%s' not found for stack '%s'", prettyKey(key), stack.Ref()) + return fmt.Errorf("configuration key '%s' not found for stack '%s'", prettyKey(key), stack.Ref()) } var ( @@ -911,7 +909,7 @@ func looksLikeSecret(k config.Key, v string) bool { func getStackConfiguration(stack backend.Stack, sm secrets.Manager) (backend.StackConfiguration, error) { workspaceStack, err := loadProjectStack(stack) if err != nil { - return backend.StackConfiguration{}, errors.Wrap(err, "loading stack configuration") + return backend.StackConfiguration{}, fmt.Errorf("loading stack configuration: %w", err) } // If there are no secrets in the configuration, we should never use the decrypter, so it is safe to return @@ -926,7 +924,7 @@ func getStackConfiguration(stack backend.Stack, sm secrets.Manager) (backend.Sta crypter, err := sm.Decrypter() if err != nil { - return backend.StackConfiguration{}, errors.Wrap(err, "getting configuration decrypter") + return backend.StackConfiguration{}, fmt.Errorf("getting configuration decrypter: %w", err) } return backend.StackConfiguration{ diff --git a/pkg/cmd/pulumi/crypto.go b/pkg/cmd/pulumi/crypto.go index 682d4475f..241acf232 100644 --- a/pkg/cmd/pulumi/crypto.go +++ b/pkg/cmd/pulumi/crypto.go @@ -15,10 +15,10 @@ package main import ( + "fmt" "reflect" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/pkg/v3/backend/filestate" "github.com/pulumi/pulumi/pkg/v3/backend/httpstate" @@ -70,7 +70,7 @@ func getStackSecretsManager(s backend.Stack) (secrets.Manager, error) { return newServiceSecretsManager(s.(httpstate.Stack), s.Ref().Name(), stackConfigFile) } - return nil, errors.Errorf("unknown stack type %s", reflect.TypeOf(s)) + return nil, fmt.Errorf("unknown stack type %s", reflect.TypeOf(s)) }() if err != nil { return nil, err @@ -86,9 +86,8 @@ func validateSecretsProvider(typ string) error { return nil } } - return errors.Errorf( - "unknown secrets provider type '%s' (supported values: %s)", + return fmt.Errorf("unknown secrets provider type '%s' (supported values: %s)", kind, - strings.Join(supportedKinds, ","), - ) + strings.Join(supportedKinds, ",")) + } diff --git a/pkg/cmd/pulumi/crypto_local.go b/pkg/cmd/pulumi/crypto_local.go index f031703cc..2c72799f6 100644 --- a/pkg/cmd/pulumi/crypto_local.go +++ b/pkg/cmd/pulumi/crypto_local.go @@ -17,13 +17,13 @@ package main import ( cryptorand "crypto/rand" "encoding/base64" + "errors" "fmt" "io/ioutil" "os" "path/filepath" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/secrets" "github.com/pulumi/pulumi/pkg/v3/secrets/passphrase" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" @@ -42,11 +42,11 @@ func readPassphraseImpl(prompt string, useEnv bool) (phrase string, interactive if phraseFile, ok := os.LookupEnv("PULUMI_CONFIG_PASSPHRASE_FILE"); ok { phraseFilePath, err := filepath.Abs(phraseFile) if err != nil { - return "", false, errors.Wrap(err, "unable to construct a path the PULUMI_CONFIG_PASSPHRASE_FILE") + return "", false, fmt.Errorf("unable to construct a path the PULUMI_CONFIG_PASSPHRASE_FILE: %w", err) } phraseDetails, err := ioutil.ReadFile(phraseFilePath) if err != nil { - return "", false, errors.Wrap(err, "unable to read PULUMI_CONFIG_PASSPHRASE_FILE") + return "", false, fmt.Errorf("unable to read PULUMI_CONFIG_PASSPHRASE_FILE: %w", err) } return strings.TrimSpace(string(phraseDetails)), false, nil } diff --git a/pkg/cmd/pulumi/destroy.go b/pkg/cmd/pulumi/destroy.go index 430791711..a08555765 100644 --- a/pkg/cmd/pulumi/destroy.go +++ b/pkg/cmd/pulumi/destroy.go @@ -16,9 +16,9 @@ package main import ( "context" + "errors" "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -127,17 +127,17 @@ func newDestroyCmd() *cobra.Command { m, err := getUpdateMetadata(message, root, execKind, execAgent) if err != nil { - return result.FromError(errors.Wrap(err, "gathering environment metadata")) + return result.FromError(fmt.Errorf("gathering environment metadata: %w", err)) } sm, err := getStackSecretsManager(s) if err != nil { - return result.FromError(errors.Wrap(err, "getting secrets manager")) + return result.FromError(fmt.Errorf("getting secrets manager: %w", err)) } cfg, err := getStackConfiguration(s, sm) if err != nil { - return result.FromError(errors.Wrap(err, "getting stack configuration")) + return result.FromError(fmt.Errorf("getting stack configuration: %w", err)) } targetUrns := []resource.URN{} diff --git a/pkg/cmd/pulumi/import.go b/pkg/cmd/pulumi/import.go index 710595fcc..89b76e499 100644 --- a/pkg/cmd/pulumi/import.go +++ b/pkg/cmd/pulumi/import.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "os" @@ -25,7 +26,7 @@ import ( "github.com/blang/semver" "github.com/hashicorp/hcl/v2" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -187,7 +188,7 @@ func getCurrentDeploymentForStack(s backend.Stack) (*deploy.Snapshot, error) { return nil, fmt.Errorf("the stack '%s' is newer than what this version of the Pulumi CLI understands. "+ "Please update your version of the Pulumi CLI", s.Ref().Name()) } - return nil, errors.Wrap(err, "could not deserialize deployment") + return nil, fmt.Errorf("could not deserialize deployment: %w", err) } return snap, err } @@ -351,7 +352,7 @@ func newImportCmd() *cobra.Command { } f, err := readImportFile(importFilePath) if err != nil { - return result.FromError(errors.Wrap(err, "could not read import file")) + return result.FromError(fmt.Errorf("could not read import file: %w", err)) } importFile = f } else { @@ -454,17 +455,17 @@ func newImportCmd() *cobra.Command { m, err := getUpdateMetadata(message, root, execKind, execAgent) if err != nil { - return result.FromError(errors.Wrap(err, "gathering environment metadata")) + return result.FromError(fmt.Errorf("gathering environment metadata: %w", err)) } sm, err := getStackSecretsManager(s) if err != nil { - return result.FromError(errors.Wrap(err, "getting secrets manager")) + return result.FromError(fmt.Errorf("getting secrets manager: %w", err)) } cfg, err := getStackConfiguration(s, sm) if err != nil { - return result.FromError(errors.Wrap(err, "getting stack configuration")) + return result.FromError(fmt.Errorf("getting stack configuration: %w", err)) } opts.Engine = engine.UpdateOptions{ @@ -493,7 +494,7 @@ func newImportCmd() *cobra.Command { protectResources) if err != nil { if _, ok := err.(*importer.DiagnosticsError); ok { - err = errors.Wrap(err, "internal error") + err = fmt.Errorf("internal error: %w", err) } return result.FromError(err) } diff --git a/pkg/cmd/pulumi/login.go b/pkg/cmd/pulumi/login.go index 3a4711d5a..e65975d50 100644 --- a/pkg/cmd/pulumi/login.go +++ b/pkg/cmd/pulumi/login.go @@ -15,12 +15,12 @@ package main import ( + "errors" "fmt" "os" "path/filepath" "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -33,6 +33,7 @@ import ( func newLoginCmd() *cobra.Command { var cloudURL string + var defaultOrg string var localMode bool cmd := &cobra.Command{ @@ -55,6 +56,9 @@ func newLoginCmd() *cobra.Command { "to log in to a self-hosted Pulumi service running at the api.pulumi.acmecorp.com domain.\n" + "\n" + "For `https://` URLs, the CLI will speak REST to a service that manages state and concurrency control.\n" + + "You can specify a default org to use when logging into the Pulumi service backend or a " + + "self-hosted Pulumi service.\n" + + "\n" + "[PREVIEW] If you prefer to operate Pulumi independently of a service, and entirely local to your computer,\n" + "pass `file://`, where `` will be where state checkpoints will be stored. For instance,\n" + "\n" + @@ -114,7 +118,7 @@ func newLoginCmd() *cobra.Command { var err error cloudURL, err = workspace.GetCurrentCloudURL() if err != nil { - return errors.Wrap(err, "could not determine current cloud") + return fmt.Errorf("could not determine current cloud: %w", err) } } else { // Ensure we have the correct cloudurl type before logging in @@ -127,11 +131,24 @@ func newLoginCmd() *cobra.Command { var err error if filestate.IsFileStateBackendURL(cloudURL) { be, err = filestate.Login(cmdutil.Diag(), cloudURL) + if defaultOrg != "" { + return fmt.Errorf("unable to set default org for this type of backend") + } } else { be, err = httpstate.Login(commandContext(), cmdutil.Diag(), cloudURL, displayOptions) + // if the user has specified a default org to associate with the backend + if defaultOrg != "" { + cloudURL, err := workspace.GetCurrentCloudURL() + if err != nil { + return err + } + if err := httpstate.SetDefaultOrg(cloudURL, defaultOrg); err != nil { + return err + } + } } if err != nil { - return errors.Wrapf(err, "problem logging in") + return fmt.Errorf("problem logging in: %w", err) } if currentUser, err := be.CurrentUser(); err == nil { @@ -145,6 +162,8 @@ func newLoginCmd() *cobra.Command { } cmd.PersistentFlags().StringVarP(&cloudURL, "cloud-url", "c", "", "A cloud URL to log in to") + cmd.PersistentFlags().StringVar(&defaultOrg, "default-org", "", "A default org to associate with the login. "+ + "Please note, currently, only the managed and self-hosted backends support organizations") cmd.PersistentFlags().BoolVarP(&localMode, "local", "l", false, "Use Pulumi in local-only mode") return cmd @@ -158,9 +177,8 @@ func validateCloudBackendType(typ string) error { return nil } } - return errors.Errorf( - "unknown backend cloudUrl format '%s' (supported Url formats are: "+ - "azblob://, gs://, s3://, file://, https:// and http://)", - kind, - ) + return fmt.Errorf("unknown backend cloudUrl format '%s' (supported Url formats are: "+ + "azblob://, gs://, s3://, file://, https:// and http://)", + kind) + } diff --git a/pkg/cmd/pulumi/logout.go b/pkg/cmd/pulumi/logout.go index 5ce9c4b5f..c51113fb1 100644 --- a/pkg/cmd/pulumi/logout.go +++ b/pkg/cmd/pulumi/logout.go @@ -15,7 +15,9 @@ package main import ( - "github.com/pkg/errors" + "errors" + "fmt" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -65,7 +67,7 @@ func newLogoutCmd() *cobra.Command { var err error cloudURL, err = workspace.GetCurrentCloudURL() if err != nil { - return errors.Wrap(err, "could not determine current cloud") + return fmt.Errorf("could not determine current cloud: %w", err) } } diff --git a/pkg/cmd/pulumi/logs.go b/pkg/cmd/pulumi/logs.go index d9ac10010..7438ad128 100644 --- a/pkg/cmd/pulumi/logs.go +++ b/pkg/cmd/pulumi/logs.go @@ -20,7 +20,7 @@ import ( "time" mobytime "github.com/docker/docker/api/types/time" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -63,17 +63,17 @@ func newLogsCmd() *cobra.Command { sm, err := getStackSecretsManager(s) if err != nil { - return errors.Wrap(err, "getting secrets manager") + return fmt.Errorf("getting secrets manager: %w", err) } cfg, err := getStackConfiguration(s, sm) if err != nil { - return errors.Wrap(err, "getting stack configuration") + return fmt.Errorf("getting stack configuration: %w", err) } startTime, err := parseSince(since, time.Now()) if err != nil { - return errors.Wrapf(err, "failed to parse argument to '--since' as duration or timestamp") + return fmt.Errorf("failed to parse argument to '--since' as duration or timestamp: %w", err) } var resourceFilter *operations.ResourceFilter if resource != "" { @@ -102,7 +102,7 @@ func newLogsCmd() *cobra.Command { ResourceFilter: resourceFilter, }) if err != nil { - return errors.Wrapf(err, "failed to get logs") + return fmt.Errorf("failed to get logs: %w", err) } // When we are emitting a fixed number of log entries, and outputing JSON, wrap them in an array. diff --git a/pkg/cmd/pulumi/new.go b/pkg/cmd/pulumi/new.go index 9b4f8e057..d098f937e 100644 --- a/pkg/cmd/pulumi/new.go +++ b/pkg/cmd/pulumi/new.go @@ -16,6 +16,7 @@ package main import ( + "errors" "fmt" "io/ioutil" "os" @@ -26,7 +27,6 @@ import ( "strings" "unicode" - "github.com/pkg/errors" "github.com/spf13/cobra" survey "gopkg.in/AlecAivazis/survey.v1" surveycore "gopkg.in/AlecAivazis/survey.v1/core" @@ -83,7 +83,7 @@ func runNew(args newArgs) error { // Validate name (if specified) before further prompts/operations. if args.name != "" && workspace.ValidateProjectName(args.name) != nil { - return errors.Errorf("'%s' is not a valid project name. %s.", args.name, workspace.ValidateProjectName(args.name)) + return fmt.Errorf("'%s' is not a valid project name. %s", args.name, workspace.ValidateProjectName(args.name)) } // Validate secrets provider type @@ -94,7 +94,7 @@ func runNew(args newArgs) error { // Get the current working directory. cwd, err := os.Getwd() if err != nil { - return errors.Wrap(err, "getting the working directory") + return fmt.Errorf("getting the working directory: %w", err) } originalCwd := cwd @@ -159,7 +159,7 @@ func runNew(args newArgs) error { if !args.force { if err = workspace.CopyTemplateFilesDryRun(template.Dir, cwd, args.name); err != nil { if os.IsNotExist(err) { - return errors.Wrapf(err, "template '%s' not found", args.templateNameOrURL) + return fmt.Errorf("template '%s' not found: %w", args.templateNameOrURL, err) } return err } @@ -172,7 +172,11 @@ func runNew(args newArgs) error { // created via the web app. var s backend.Stack if args.stack != "" && strings.Count(args.stack, "/") == 2 { - existingStack, existingName, existingDesc, err := getStack(args.stack, opts) + stackName, err := buildStackName(args.stack) + if err != nil { + return err + } + existingStack, existingName, existingDesc, err := getStack(stackName, opts) if err != nil { return err } @@ -228,7 +232,7 @@ func runNew(args newArgs) error { // Actually copy the files. if err = workspace.CopyTemplateFiles(template.Dir, cwd, args.force, args.name, args.description); err != nil { if os.IsNotExist(err) { - return errors.Wrapf(err, "template '%s' not found", args.templateNameOrURL) + return fmt.Errorf("template '%s' not found: %w", args.templateNameOrURL, err) } return err } @@ -245,7 +249,7 @@ func runNew(args newArgs) error { proj.Description = &args.description proj.Template = nil if err = workspace.SaveProject(proj); err != nil { - return errors.Wrap(err, "saving project") + return fmt.Errorf("saving project: %w", err) } // Create the stack, if needed. @@ -299,19 +303,19 @@ func runNew(args newArgs) error { func useSpecifiedDir(dir string) (string, error) { // Ensure the directory exists. if err := os.MkdirAll(dir, os.ModePerm); err != nil { - return "", errors.Wrap(err, "creating the directory") + return "", fmt.Errorf("creating the directory: %w", err) } // Change the working directory to the specified directory. if err := os.Chdir(dir); err != nil { - return "", errors.Wrap(err, "changing the working directory") + return "", fmt.Errorf("changing the working directory: %w", err) } // Get the new working directory. var cwd string var err error if cwd, err = os.Getwd(); err != nil { - return "", errors.Wrap(err, "getting the working directory") + return "", fmt.Errorf("getting the working directory: %w", err) } return cwd, nil } @@ -445,7 +449,7 @@ func errorIfNotEmptyDirectory(path string) error { } if len(infos) > 0 { - return errors.Errorf("%s is not empty; "+ + return fmt.Errorf("%s is not empty; "+ "rerun in an empty directory, pass the path to an empty directory to --dir, or use --force", path) } @@ -518,7 +522,11 @@ func promptAndCreateStack(prompt promptForValueFunc, } if stack != "" { - s, err := stackInit(b, stack, setCurrent, secretsProvider) + stackName, err := buildStackName(stack) + if err != nil { + return nil, err + } + s, err := stackInit(b, stackName, setCurrent, secretsProvider) if err != nil { return nil, err } @@ -536,7 +544,14 @@ func promptAndCreateStack(prompt promptForValueFunc, if err != nil { return nil, err } - s, err := stackInit(b, stackName, setCurrent, secretsProvider) + formattedStackName, err := buildStackName(stackName) + if err != nil { + return nil, err + } + if err != nil { + return nil, err + } + s, err := stackInit(b, formattedStackName, setCurrent, secretsProvider) if err != nil { if !yes { // Let the user know about the error and loop around to try again. @@ -577,8 +592,9 @@ func installDependencies(proj *workspace.Project, root string) error { // TODO[pulumi/pulumi#1334]: move to the language plugins so we don't have to hard code here. if strings.EqualFold(proj.Runtime.Name(), "nodejs") { if bin, err := nodeInstallDependencies(); err != nil { - return errors.Wrapf(err, "%s install failed; rerun manually to try again, "+ - "then run 'pulumi up' to perform an initial deployment", bin) + return fmt.Errorf("%s install failed; rerun manually to try again, "+ + "then run 'pulumi up' to perform an initial deployment"+": %w", bin, err) + } } else if strings.EqualFold(proj.Runtime.Name(), "python") { return pythonInstallDependencies(proj, root) @@ -620,7 +636,7 @@ func pythonInstallDependencies(proj *workspace.Project, root string) error { // Save project with venv info. proj.Runtime.SetOption("virtualenv", venvDir) if err := workspace.SaveProject(proj); err != nil { - return errors.Wrap(err, "saving project") + return fmt.Errorf("saving project: %w", err) } return nil } @@ -671,8 +687,9 @@ func goInstallDependencies() error { cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr if err := cmd.Run(); err != nil { - return errors.Wrapf(err, "`go mod tidy` failed to install dependencies; rerun manually to try again, "+ - "then run 'pulumi up' to perform an initial deployment") + return fmt.Errorf("`go mod tidy` failed to install dependencies; rerun manually to try again, "+ + "then run 'pulumi up' to perform an initial deployment"+": %w", err) + } fmt.Println("Finished installing dependencies") diff --git a/pkg/cmd/pulumi/new_test.go b/pkg/cmd/pulumi/new_test.go index 1f87c9f66..47e867826 100644 --- a/pkg/cmd/pulumi/new_test.go +++ b/pkg/cmd/pulumi/new_test.go @@ -16,13 +16,14 @@ package main import ( "context" "fmt" - "github.com/stretchr/testify/require" "io/ioutil" "os" "path/filepath" "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/pkg/v3/backend/display" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" diff --git a/pkg/cmd/pulumi/org.go b/pkg/cmd/pulumi/org.go new file mode 100644 index 000000000..952d5cc8f --- /dev/null +++ b/pkg/cmd/pulumi/org.go @@ -0,0 +1,153 @@ +// Copyright 2016-2021, 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 main + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/pulumi/pulumi/pkg/v3/backend/display" + "github.com/pulumi/pulumi/pkg/v3/backend/httpstate" + "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" + "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" +) + +func newOrgCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "org", + Short: "Manage Organization configuration", + Long: "Manage Organization configuration.\n" + + "\n" + + "Use this command to manage organization configuration, " + + "e.g. setting the default organization for a backend", + Args: cmdutil.NoArgs, + Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { + cloudURL, err := workspace.GetCurrentCloudURL() + if err != nil { + return err + } + + defaultOrg, err := workspace.GetBackendConfigDefaultOrg() + if err != nil { + return err + } + + fmt.Printf("Current Backend: %s\n", cloudURL) + if defaultOrg != "" { + fmt.Printf("Default Org: %s", defaultOrg) + } else { + fmt.Println("No Default Org Specified") + } + + return nil + }), + } + + cmd.AddCommand(newOrgSetDefaultCmd()) + cmd.AddCommand(newOrgGetDefaultCmd()) + + return cmd +} + +func newOrgSetDefaultCmd() *cobra.Command { + var orgName string + + var cmd = &cobra.Command{ + Use: "set-default [NAME]", + Args: cmdutil.ExactArgs(1), + Short: "Set the default organization for the current backend", + Long: "Set the default organization for the current backend.\n" + + "\n" + + "This command is used to set the default organization in which to create \n" + + "projects and stacks for the current backend.\n" + + "\n" + + "Currently, only the managed and self-hosted backends support organizations. " + + "If you try and set a default organization for a backend that does not \n" + + "support create organizations, then an error will be returned by the CLI", + Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { + displayOpts := display.Options{ + Color: cmdutil.GetGlobalColorization(), + } + + orgName = args[0] + + currentBe, err := currentBackend(displayOpts) + if err != nil { + return err + } + if !currentBe.SupportsOrganizations() { + return fmt.Errorf("unable to set a default organization for backend type: %s", + currentBe.Name()) + } + + if _, ok := currentBe.(httpstate.Backend); ok { + cloudURL, err := workspace.GetCurrentCloudURL() + if err != nil { + return err + } + if err := httpstate.SetDefaultOrg(cloudURL, orgName); err != nil { + return err + } + } + + return nil + }), + } + + return cmd +} + +func newOrgGetDefaultCmd() *cobra.Command { + var cmd = &cobra.Command{ + Use: "get-default", + Short: "Get the default org for the current backend", + Long: "Get the default org for the current backend.\n" + + "\n" + + "This command is used to get the default organization for which and stacks are created in " + + "the current backend.\n" + + "\n" + + "Currently, only the managed and self-hosted backends support organizations.", + Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error { + displayOpts := display.Options{ + Color: cmdutil.GetGlobalColorization(), + } + + currentBe, err := currentBackend(displayOpts) + if err != nil { + return err + } + if !currentBe.SupportsOrganizations() { + return fmt.Errorf("backends of this type %q do not support organizations", + currentBe.Name()) + } + + defaultOrg, err := workspace.GetBackendConfigDefaultOrg() + if err != nil { + return err + } + + if defaultOrg != "" { + fmt.Println(defaultOrg) + } else { + fmt.Println("No Default Org Specified") + } + + return nil + }), + } + + return cmd +} diff --git a/pkg/cmd/pulumi/plugin_install.go b/pkg/cmd/pulumi/plugin_install.go index 30f1454b3..5701d10f5 100644 --- a/pkg/cmd/pulumi/plugin_install.go +++ b/pkg/cmd/pulumi/plugin_install.go @@ -15,6 +15,7 @@ package main import ( + "errors" "fmt" "io" "os" @@ -22,7 +23,7 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" "github.com/blang/semver" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -59,7 +60,7 @@ func newPluginInstallCmd() *cobra.Command { var installs []workspace.PluginInfo if len(args) > 0 { if !workspace.IsPluginKind(args[0]) { - return errors.Errorf("unrecognized plugin kind: %s", args[0]) + return fmt.Errorf("unrecognized plugin kind: %s", args[0]) } else if len(args) < 2 { return errors.New("missing plugin name argument") } else if len(args) < 3 { @@ -67,7 +68,7 @@ func newPluginInstallCmd() *cobra.Command { } version, err := semver.ParseTolerant(args[2]) if err != nil { - return errors.Wrap(err, "invalid plugin semver") + return fmt.Errorf("invalid plugin semver: %w", err) } installs = append(installs, workspace.PluginInfo{ Kind: workspace.PluginKind(args[0]), @@ -124,19 +125,19 @@ func newPluginInstallCmd() *cobra.Command { if file == "" { var size int64 if tarball, size, err = install.Download(); err != nil { - return errors.Wrapf(err, "%s downloading from %s", label, install.ServerURL) + return fmt.Errorf("%s downloading from %s: %w", label, install.ServerURL, err) } tarball = workspace.ReadCloserProgressBar(tarball, size, "Downloading plugin", displayOpts.Color) } else { source = file logging.V(1).Infof("%s opening tarball from %s", label, file) if tarball, err = os.Open(file); err != nil { - return errors.Wrapf(err, "opening file %s", source) + return fmt.Errorf("opening file %s: %w", source, err) } } logging.V(1).Infof("%s installing tarball ...", label) if err = install.Install(tarball); err != nil { - return errors.Wrapf(err, "installing %s from %s", label, source) + return fmt.Errorf("installing %s from %s: %w", label, source, err) } } diff --git a/pkg/cmd/pulumi/plugin_ls.go b/pkg/cmd/pulumi/plugin_ls.go index 50cb5f29a..a10851b0f 100644 --- a/pkg/cmd/pulumi/plugin_ls.go +++ b/pkg/cmd/pulumi/plugin_ls.go @@ -19,7 +19,7 @@ import ( "sort" "github.com/dustin/go-humanize" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" @@ -39,11 +39,11 @@ func newPluginLsCmd() *cobra.Command { var err error if projectOnly { if plugins, err = getProjectPlugins(); err != nil { - return errors.Wrapf(err, "loading project plugins") + return fmt.Errorf("loading project plugins: %w", err) } } else { if plugins, err = workspace.GetPluginsWithMetadata(); err != nil { - return errors.Wrapf(err, "loading plugins") + return fmt.Errorf("loading plugins: %w", err) } } diff --git a/pkg/cmd/pulumi/plugin_rm.go b/pkg/cmd/pulumi/plugin_rm.go index 10db01529..c50ebcd58 100644 --- a/pkg/cmd/pulumi/plugin_rm.go +++ b/pkg/cmd/pulumi/plugin_rm.go @@ -16,11 +16,12 @@ package main import ( "fmt" + "github.com/pulumi/pulumi/sdk/v3/go/common/diag" "github.com/blang/semver" "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -58,11 +59,11 @@ func newPluginRmCmd() *cobra.Command { var version *semver.Range if len(args) > 0 { if !workspace.IsPluginKind(args[0]) { - return errors.Errorf("unrecognized plugin kind: %s", kind) + return fmt.Errorf("unrecognized plugin kind: %s", kind) } kind = workspace.PluginKind(args[0]) } else if !all { - return errors.Errorf("please pass --all if you'd like to remove all plugins") + return fmt.Errorf("please pass --all if you'd like to remove all plugins") } if len(args) > 1 { name = args[1] @@ -70,7 +71,7 @@ func newPluginRmCmd() *cobra.Command { if len(args) > 2 { r, err := semver.ParseRange(args[2]) if err != nil { - return errors.Wrap(err, "invalid plugin semver") + return fmt.Errorf("invalid plugin semver: %w", err) } version = &r } @@ -79,7 +80,7 @@ func newPluginRmCmd() *cobra.Command { var deletes []workspace.PluginInfo plugins, err := workspace.GetPlugins() if err != nil { - return errors.Wrap(err, "loading plugins") + return fmt.Errorf("loading plugins: %w", err) } for _, plugin := range plugins { if (kind == "" || plugin.Kind == kind) && @@ -112,7 +113,7 @@ func newPluginRmCmd() *cobra.Command { for _, plugin := range deletes { if err := plugin.Delete(); err != nil { result = multierror.Append( - result, errors.Wrapf(err, "failed to delete %s plugin %s", plugin.Kind, plugin)) + result, fmt.Errorf("failed to delete %s plugin %s: %w", plugin.Kind, plugin, err)) } } if result != nil { diff --git a/pkg/cmd/pulumi/policy_new.go b/pkg/cmd/pulumi/policy_new.go index a402af6d1..5b7bf0c60 100644 --- a/pkg/cmd/pulumi/policy_new.go +++ b/pkg/cmd/pulumi/policy_new.go @@ -15,12 +15,12 @@ package main import ( + "errors" "fmt" "os" "sort" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/backend/display" "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" @@ -98,7 +98,7 @@ func runNewPolicyPack(args newPolicyArgs) error { // Get the current working directory. cwd, err := os.Getwd() if err != nil { - return errors.Wrap(err, "getting the working directory") + return fmt.Errorf("getting the working directory: %w", err) } // If dir was specified, ensure it exists and use it as the @@ -147,7 +147,7 @@ func runNewPolicyPack(args newPolicyArgs) error { if !args.force { if err = workspace.CopyTemplateFilesDryRun(template.Dir, cwd, ""); err != nil { if os.IsNotExist(err) { - return errors.Wrapf(err, "template '%s' not found", args.templateNameOrURL) + return fmt.Errorf("template '%s' not found: %w", args.templateNameOrURL, err) } return err } @@ -156,7 +156,7 @@ func runNewPolicyPack(args newPolicyArgs) error { // Actually copy the files. if err = workspace.CopyTemplateFiles(template.Dir, cwd, args.force, "", ""); err != nil { if os.IsNotExist(err) { - return errors.Wrapf(err, "template '%s' not found", args.templateNameOrURL) + return fmt.Errorf("template '%s' not found: %w", args.templateNameOrURL, err) } return err } @@ -190,7 +190,7 @@ func installPolicyPackDependencies(proj *workspace.PolicyPackProject, projPath, // TODO[pulumi/pulumi#1334]: move to the language plugins so we don't have to hard code here. if strings.EqualFold(proj.Runtime.Name(), "nodejs") { if bin, err := nodeInstallDependencies(); err != nil { - return errors.Wrapf(err, "`%s install` failed; rerun manually to try again.", bin) + return fmt.Errorf("`%s install` failed; rerun manually to try again.: %w", bin, err) } } else if strings.EqualFold(proj.Runtime.Name(), "python") { const venvDir = "venv" @@ -201,7 +201,7 @@ func installPolicyPackDependencies(proj *workspace.PolicyPackProject, projPath, // Save project with venv info. proj.Runtime.SetOption("virtualenv", venvDir) if err := proj.Save(projPath); err != nil { - return errors.Wrapf(err, "saving project at %s", projPath) + return fmt.Errorf("saving project at %s: %w", projPath, err) } } return nil diff --git a/pkg/cmd/pulumi/policy_publish.go b/pkg/cmd/pulumi/policy_publish.go index 1fcbea4bf..ffeb1c699 100644 --- a/pkg/cmd/pulumi/policy_publish.go +++ b/pkg/cmd/pulumi/policy_publish.go @@ -15,10 +15,10 @@ package main import ( + "errors" "fmt" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/pkg/v3/backend/display" "github.com/pulumi/pulumi/pkg/v3/backend/httpstate" @@ -110,8 +110,8 @@ func requirePolicyPack(policyPack string) (backend.PolicyPack, error) { cloudURL, err := workspace.GetCurrentCloudURL() if err != nil { - return nil, errors.Wrap(err, - "`pulumi policy` command requires the user to be logged into the Pulumi service") + return nil, fmt.Errorf("`pulumi policy` command requires the user to be logged into the Pulumi service: %w", err) + } displayOptions := display.Options{ diff --git a/pkg/cmd/pulumi/policy_rm.go b/pkg/cmd/pulumi/policy_rm.go index fccc4dd34..ade07d436 100644 --- a/pkg/cmd/pulumi/policy_rm.go +++ b/pkg/cmd/pulumi/policy_rm.go @@ -16,6 +16,7 @@ package main import ( "fmt" + "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/pkg/v3/backend/display" "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" diff --git a/pkg/cmd/pulumi/preview.go b/pkg/cmd/pulumi/preview.go index 40617f480..3c653a5e0 100644 --- a/pkg/cmd/pulumi/preview.go +++ b/pkg/cmd/pulumi/preview.go @@ -15,7 +15,9 @@ package main import ( - "github.com/pkg/errors" + "errors" + "fmt" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -134,17 +136,17 @@ func newPreviewCmd() *cobra.Command { m, err := getUpdateMetadata(message, root, execKind, execAgent) if err != nil { - return result.FromError(errors.Wrap(err, "gathering environment metadata")) + return result.FromError(fmt.Errorf("gathering environment metadata: %w", err)) } sm, err := getStackSecretsManager(s) if err != nil { - return result.FromError(errors.Wrap(err, "getting secrets manager")) + return result.FromError(fmt.Errorf("getting secrets manager: %w", err)) } cfg, err := getStackConfiguration(s, sm) if err != nil { - return result.FromError(errors.Wrap(err, "getting stack configuration")) + return result.FromError(fmt.Errorf("getting stack configuration: %w", err)) } targetURNs := []resource.URN{} diff --git a/pkg/cmd/pulumi/pulumi.go b/pkg/cmd/pulumi/pulumi.go index 98e0a3f9f..1f59fadda 100644 --- a/pkg/cmd/pulumi/pulumi.go +++ b/pkg/cmd/pulumi/pulumi.go @@ -18,8 +18,8 @@ import ( "bufio" "bytes" "encoding/json" + "errors" "fmt" - user "github.com/tweekmonster/luser" "net/http" "net/url" "os" @@ -30,10 +30,12 @@ import ( "strings" "time" + user "github.com/tweekmonster/luser" + "github.com/blang/semver" "github.com/djherbis/times" "github.com/docker/docker/pkg/term" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -210,6 +212,7 @@ func NewPulumiCmd() *cobra.Command { cmd.AddCommand(newConsoleCmd()) cmd.AddCommand(newAboutCmd()) cmd.AddCommand(newSchemaCmd()) + cmd.AddCommand(newOrgCmd()) // Less common, and thus hidden, commands: cmd.AddCommand(newGenCompletionCmd(cmd)) @@ -435,7 +438,7 @@ func isBrewInstall(exe string) (bool, error) { if ee, ok := err.(*exec.ExitError); ok { ee.Stderr = stderr.Bytes() } - return false, errors.Wrapf(err, "'brew --prefix pulumi' failed") + return false, fmt.Errorf("'brew --prefix pulumi' failed: %w", err) } brewPrefixCmdOutput := strings.TrimSpace(stdout.String()) diff --git a/pkg/cmd/pulumi/refresh.go b/pkg/cmd/pulumi/refresh.go index 2556b74b2..5e1a630f2 100644 --- a/pkg/cmd/pulumi/refresh.go +++ b/pkg/cmd/pulumi/refresh.go @@ -16,8 +16,9 @@ package main import ( "context" + "errors" + "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -124,17 +125,17 @@ func newRefreshCmd() *cobra.Command { m, err := getUpdateMetadata(message, root, execKind, execAgent) if err != nil { - return result.FromError(errors.Wrap(err, "gathering environment metadata")) + return result.FromError(fmt.Errorf("gathering environment metadata: %w", err)) } sm, err := getStackSecretsManager(s) if err != nil { - return result.FromError(errors.Wrap(err, "getting secrets manager")) + return result.FromError(fmt.Errorf("getting secrets manager: %w", err)) } cfg, err := getStackConfiguration(s, sm) if err != nil { - return result.FromError(errors.Wrap(err, "getting stack configuration")) + return result.FromError(fmt.Errorf("getting stack configuration: %w", err)) } targetUrns := []resource.URN{} diff --git a/pkg/cmd/pulumi/schema_check.go b/pkg/cmd/pulumi/schema_check.go index e431d3206..882875929 100644 --- a/pkg/cmd/pulumi/schema_check.go +++ b/pkg/cmd/pulumi/schema_check.go @@ -16,13 +16,14 @@ package main import ( "encoding/json" + "errors" "fmt" "io/ioutil" "os" "path/filepath" "github.com/hashicorp/hcl/v2" - "github.com/pkg/errors" + "github.com/spf13/cobra" "gopkg.in/yaml.v3" diff --git a/pkg/cmd/pulumi/stack_change_secrets_provider.go b/pkg/cmd/pulumi/stack_change_secrets_provider.go index 519e4010d..ed04fd4f2 100644 --- a/pkg/cmd/pulumi/stack_change_secrets_provider.go +++ b/pkg/cmd/pulumi/stack_change_secrets_provider.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/pulumi/pulumi/pkg/v3/backend" "github.com/pulumi/pulumi/pkg/v3/backend/display" "github.com/pulumi/pulumi/pkg/v3/resource/stack" diff --git a/pkg/cmd/pulumi/stack_export.go b/pkg/cmd/pulumi/stack_export.go index c31be153d..f6b2498bb 100644 --- a/pkg/cmd/pulumi/stack_export.go +++ b/pkg/cmd/pulumi/stack_export.go @@ -16,9 +16,9 @@ package main import ( "encoding/json" + "fmt" "os" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/resource/stack" "github.com/spf13/cobra" @@ -69,8 +69,7 @@ func newStackExportCmd() *cobra.Command { be := s.Backend() specificExpBE, ok := be.(backend.SpecificDeploymentExporter) if !ok { - return errors.Errorf( - "the current backend (%s) does not provide the ability to export previous deployments", + return fmt.Errorf("the current backend (%s) does not provide the ability to export previous deployments", be.Name()) } @@ -85,7 +84,7 @@ func newStackExportCmd() *cobra.Command { if file != "" { writer, err = os.Create(file) if err != nil { - return errors.Wrap(err, "could not open file") + return fmt.Errorf("could not open file: %w", err) } } @@ -116,7 +115,7 @@ func newStackExportCmd() *cobra.Command { enc.SetIndent("", " ") if err = enc.Encode(deployment); err != nil { - return errors.Wrap(err, "could not export deployment") + return fmt.Errorf("could not export deployment: %w", err) } return nil diff --git a/pkg/cmd/pulumi/stack_graph.go b/pkg/cmd/pulumi/stack_graph.go index 232f32938..decce5437 100644 --- a/pkg/cmd/pulumi/stack_graph.go +++ b/pkg/cmd/pulumi/stack_graph.go @@ -15,7 +15,7 @@ package main import ( - "github.com/pkg/errors" + "fmt" "os" "strings" @@ -68,7 +68,7 @@ func newStackGraphCmd() *cobra.Command { // This will prevent a panic when trying to assemble a dependencyGraph when no snapshot is found if snap == nil { - return errors.Errorf("unable to find snapshot for stack %q", stackName) + return fmt.Errorf("unable to find snapshot for stack %q", stackName) } dg := makeDependencyGraph(snap) diff --git a/pkg/cmd/pulumi/stack_history.go b/pkg/cmd/pulumi/stack_history.go index f5852da81..0be918663 100644 --- a/pkg/cmd/pulumi/stack_history.go +++ b/pkg/cmd/pulumi/stack_history.go @@ -8,7 +8,7 @@ import ( "time" "github.com/dustin/go-humanize" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -47,13 +47,13 @@ This command displays data about previous updates for a stack.`, b := s.Backend() updates, err := b.GetHistory(commandContext(), s.Ref(), pageSize, page) if err != nil { - return errors.Wrap(err, "getting history") + return fmt.Errorf("getting history: %w", err) } var decrypter config.Decrypter if showSecrets { crypter, err := getStackDecrypter(s) if err != nil { - return errors.Wrap(err, "decrypting secrets") + return fmt.Errorf("decrypting secrets: %w", err) } decrypter = crypter } diff --git a/pkg/cmd/pulumi/stack_import.go b/pkg/cmd/pulumi/stack_import.go index c32d0e094..bd65f2c60 100644 --- a/pkg/cmd/pulumi/stack_import.go +++ b/pkg/cmd/pulumi/stack_import.go @@ -16,11 +16,12 @@ package main import ( "encoding/json" + "errors" "fmt" "os" "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -61,7 +62,7 @@ func newStackImportCmd() *cobra.Command { if file != "" { reader, err = os.Open(file) if err != nil { - return errors.Wrap(err, "could not open file") + return fmt.Errorf("could not open file: %w", err) } } @@ -122,7 +123,7 @@ func newStackImportCmd() *cobra.Command { } sdp, err := stack.SerializeDeployment(snapshot, snapshot.SecretsManager, false /* showSecrets */) if err != nil { - return errors.Wrap(err, "constructing deployment for upload") + return fmt.Errorf("constructing deployment for upload: %w", err) } bytes, err := json.Marshal(sdp) @@ -137,7 +138,7 @@ func newStackImportCmd() *cobra.Command { // Now perform the deployment. if err = s.ImportDeployment(commandContext(), &dep); err != nil { - return errors.Wrap(err, "could not import deployment") + return fmt.Errorf("could not import deployment: %w", err) } fmt.Printf("Import successful.\n") return nil diff --git a/pkg/cmd/pulumi/stack_init.go b/pkg/cmd/pulumi/stack_init.go index e4e1b9aee..bc97c4693 100644 --- a/pkg/cmd/pulumi/stack_init.go +++ b/pkg/cmd/pulumi/stack_init.go @@ -15,9 +15,9 @@ package main import ( + "errors" "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -107,11 +107,15 @@ func newStackInitCmd() *cobra.Command { return errors.New("missing stack name") } - if err := b.ValidateStackName(stackName); err != nil { + formattedStackName, err := buildStackName(stackName) + if err != nil { + return err + } + if err := b.ValidateStackName(formattedStackName); err != nil { return err } - stackRef, err := b.ParseStackReference(stackName) + stackRef, err := b.ParseStackReference(formattedStackName) if err != nil { return err } diff --git a/pkg/cmd/pulumi/stack_ls.go b/pkg/cmd/pulumi/stack_ls.go index f28e9bac2..e26afca28 100644 --- a/pkg/cmd/pulumi/stack_ls.go +++ b/pkg/cmd/pulumi/stack_ls.go @@ -15,12 +15,14 @@ package main import ( + "errors" + "fmt" "sort" "strconv" "strings" "github.com/dustin/go-humanize" - "github.com/pkg/errors" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -110,14 +112,14 @@ func runStackLS(args stackLSArgs) error { // Ensure we are in a project; if not, we will fail. projPath, err := workspace.DetectProjectPath() if err != nil { - return errors.Wrapf(err, "could not detect current project") + return fmt.Errorf("could not detect current project: %w", err) } else if projPath == "" { return errors.New("no Pulumi.yaml found; please run this command in a project directory") } proj, err := workspace.LoadProject(projPath) if err != nil { - return errors.Wrap(err, "could not load current project") + return fmt.Errorf("could not load current project: %w", err) } projName := string(proj.Name) filter.Project = &projName diff --git a/pkg/cmd/pulumi/stack_output.go b/pkg/cmd/pulumi/stack_output.go index 66d09c0cf..6c1242158 100644 --- a/pkg/cmd/pulumi/stack_output.go +++ b/pkg/cmd/pulumi/stack_output.go @@ -17,7 +17,6 @@ package main import ( "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -57,7 +56,7 @@ func newStackOutputCmd() *cobra.Command { outputs, err := getStackOutputs(snap, showSecrets) if err != nil { - return errors.Wrap(err, "getting outputs") + return fmt.Errorf("getting outputs: %w", err) } if outputs == nil { outputs = make(map[string]interface{}) @@ -76,7 +75,7 @@ func newStackOutputCmd() *cobra.Command { fmt.Printf("%v\n", stringifyOutput(v)) } } else { - return errors.Errorf("current stack does not have output property '%v'", name) + return fmt.Errorf("current stack does not have output property '%v'", name) } } else if jsonOut { if err := printJSON(outputs); err != nil { diff --git a/pkg/cmd/pulumi/stack_rename.go b/pkg/cmd/pulumi/stack_rename.go index 8f5111942..9a458ede8 100644 --- a/pkg/cmd/pulumi/stack_rename.go +++ b/pkg/cmd/pulumi/stack_rename.go @@ -21,7 +21,6 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -79,15 +78,15 @@ func newStackRenameCmd() *cobra.Command { // Stack doesn't have any configuration, ignore. case configStatErr == nil: if err := os.Rename(oldConfigPath, newConfigPath); err != nil { - return errors.Wrapf(err, "renaming configuration file to %s", filepath.Base(newConfigPath)) + return fmt.Errorf("renaming configuration file to %s: %w", filepath.Base(newConfigPath), err) } default: - return errors.Wrapf(err, "checking current configuration file %v", oldConfigPath) + return fmt.Errorf("checking current configuration file %v: %w", oldConfigPath, err) } // Update the current workspace state to have selected the new stack. if err := state.SetCurrentStack(newStackName); err != nil { - return errors.Wrap(err, "setting current stack") + return fmt.Errorf("setting current stack: %w", err) } fmt.Printf("Renamed %s to %s\n", s.Ref().String(), newStackRef.String()) diff --git a/pkg/cmd/pulumi/stack_select.go b/pkg/cmd/pulumi/stack_select.go index 15ae0c01c..d9b580ec9 100644 --- a/pkg/cmd/pulumi/stack_select.go +++ b/pkg/cmd/pulumi/stack_select.go @@ -15,7 +15,9 @@ package main import ( - "github.com/pkg/errors" + "errors" + "fmt" + "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend/display" @@ -80,7 +82,7 @@ func newStackSelectCmd() *cobra.Command { return state.SetCurrentStack(s.Ref().String()) } - return errors.Errorf("no stack named '%s' found", stackRef) + return fmt.Errorf("no stack named '%s' found", stackRef) } // If no stack was given, prompt the user to select a name from the available ones. diff --git a/pkg/cmd/pulumi/stack_tag.go b/pkg/cmd/pulumi/stack_tag.go index 18da581d5..b1de76f03 100644 --- a/pkg/cmd/pulumi/stack_tag.go +++ b/pkg/cmd/pulumi/stack_tag.go @@ -18,7 +18,6 @@ import ( "fmt" "sort" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -79,8 +78,7 @@ func newStackTagGetCmd(stack *string) *cobra.Command { return nil } - return errors.Errorf( - "stack tag '%s' not found for stack '%s'", name, s.Ref()) + return fmt.Errorf("stack tag '%s' not found for stack '%s'", name, s.Ref()) }), } } diff --git a/pkg/cmd/pulumi/state.go b/pkg/cmd/pulumi/state.go index 73c0454eb..6a0435e35 100644 --- a/pkg/cmd/pulumi/state.go +++ b/pkg/cmd/pulumi/state.go @@ -16,9 +16,9 @@ package main import ( "encoding/json" + "errors" "fmt" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/backend/display" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/resource/edit" @@ -57,7 +57,7 @@ func locateStackResource(opts display.Options, snap *deploy.Snapshot, urn resour candidateResources := edit.LocateResource(snap, urn) switch { case len(candidateResources) == 0: // resource was not found - return nil, errors.Errorf("No such resource %q exists in the current state", urn) + return nil, fmt.Errorf("No such resource %q exists in the current state", urn) case len(candidateResources) == 1: // resource was unambiguously found return candidateResources[0], nil } @@ -170,7 +170,7 @@ func runTotalStateEdit( sdep, err := stack.SerializeDeployment(snap, snap.SecretsManager, false /* showSecrets */) if err != nil { - return result.FromError(errors.Wrap(err, "serializing deployment")) + return result.FromError(fmt.Errorf("serializing deployment: %w", err)) } // Once we've mutated the snapshot, import it back into the backend so that it can be persisted. diff --git a/pkg/cmd/pulumi/up.go b/pkg/cmd/pulumi/up.go index 7bc7afd25..ead82d56b 100644 --- a/pkg/cmd/pulumi/up.go +++ b/pkg/cmd/pulumi/up.go @@ -16,12 +16,12 @@ package main import ( "context" + "errors" "fmt" "io/ioutil" "math" "os" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -97,17 +97,17 @@ func newUpCmd() *cobra.Command { m, err := getUpdateMetadata(message, root, execKind, execAgent) if err != nil { - return result.FromError(errors.Wrap(err, "gathering environment metadata")) + return result.FromError(fmt.Errorf("gathering environment metadata: %w", err)) } sm, err := getStackSecretsManager(s) if err != nil { - return result.FromError(errors.Wrap(err, "getting secrets manager")) + return result.FromError(fmt.Errorf("getting secrets manager: %w", err)) } cfg, err := getStackConfiguration(s, sm) if err != nil { - return result.FromError(errors.Wrap(err, "getting stack configuration")) + return result.FromError(fmt.Errorf("getting stack configuration: %w", err)) } targetURNs := []resource.URN{} @@ -225,7 +225,7 @@ func newUpCmd() *cobra.Command { // Change the working directory to the "virtual workspace" directory. if err = os.Chdir(temp); err != nil { - return result.FromError(errors.Wrap(err, "changing the working directory")) + return result.FromError(fmt.Errorf("changing the working directory: %w", err)) } // If a stack was specified via --stack, see if it already exists. @@ -273,7 +273,7 @@ func newUpCmd() *cobra.Command { proj.Description = &description proj.Template = nil if err = workspace.SaveProject(proj); err != nil { - return result.FromError(errors.Wrap(err, "saving project")) + return result.FromError(fmt.Errorf("saving project: %w", err)) } // Create the stack, if needed. @@ -297,17 +297,17 @@ func newUpCmd() *cobra.Command { m, err := getUpdateMetadata(message, root, execKind, execAgent) if err != nil { - return result.FromError(errors.Wrap(err, "gathering environment metadata")) + return result.FromError(fmt.Errorf("gathering environment metadata: %w", err)) } sm, err := getStackSecretsManager(s) if err != nil { - return result.FromError(errors.Wrap(err, "getting secrets manager")) + return result.FromError(fmt.Errorf("getting secrets manager: %w", err)) } cfg, err := getStackConfiguration(s, sm) if err != nil { - return result.FromError(errors.Wrap(err, "getting stack configuration")) + return result.FromError(fmt.Errorf("getting stack configuration: %w", err)) } refreshOption, err := getRefreshOption(proj, refresh) @@ -613,7 +613,7 @@ func handleConfig( // Save the config. if len(c) > 0 { if err = saveConfig(s, c); err != nil { - return errors.Wrap(err, "saving config") + return fmt.Errorf("saving config: %w", err) } fmt.Println("Saved config") diff --git a/pkg/cmd/pulumi/util.go b/pkg/cmd/pulumi/util.go index 57500ee90..72a9a0a17 100644 --- a/pkg/cmd/pulumi/util.go +++ b/pkg/cmd/pulumi/util.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net/url" "os" @@ -30,7 +31,7 @@ import ( multierror "github.com/hashicorp/go-multierror" opentracing "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" + survey "gopkg.in/AlecAivazis/survey.v1" surveycore "gopkg.in/AlecAivazis/survey.v1/core" git "gopkg.in/src-d/go-git.v4" @@ -102,7 +103,7 @@ func isFilestateBackend(opts display.Options) (bool, error) { url, err := workspace.GetCurrentCloudURL() if err != nil { - return false, errors.Wrapf(err, "could not get cloud url") + return false, fmt.Errorf("could not get cloud url: %w", err) } return filestate.IsFileStateBackendURL(url), nil @@ -115,7 +116,7 @@ func currentBackend(opts display.Options) (backend.Backend, error) { url, err := workspace.GetCurrentCloudURL() if err != nil { - return nil, errors.Wrapf(err, "could not get cloud url") + return nil, fmt.Errorf("could not get cloud url: %w", err) } if filestate.IsFileStateBackendURL(url) { @@ -187,7 +188,7 @@ func createSecretsManager(b backend.Backend, stackRef backend.StackReference, se if strings.HasPrefix(secretsProvider, "azurekeyvault://") { parsed, err := url.Parse(secretsProvider) if err != nil { - return errors.Wrap(err, "failed to parse secrets provider URL") + return fmt.Errorf("failed to parse secrets provider URL: %w", err) } if parsed.Query().Get("algorithm") == "" { @@ -218,7 +219,7 @@ func createStack( if _, ok := err.(*backend.OverStackLimitError); ok { return nil, err } - return nil, errors.Wrapf(err, "could not create stack") + return nil, fmt.Errorf("could not create stack: %w", err) } if err := createSecretsManager(b, stackRef, secretsProvider, @@ -275,7 +276,7 @@ func requireStack( return createStack(b, stackRef, nil, setCurrent, "") } - return nil, errors.Errorf("no stack named '%s' found", stackName) + return nil, fmt.Errorf("no stack named '%s' found", stackName) } func requireCurrentStack(offerNew bool, opts display.Options, setCurrent bool) (backend.Stack, error) { @@ -327,7 +328,7 @@ func chooseStack( for { summaries, outContToken, err := b.ListStacks(ctx, backend.ListStacksFilter{Project: &project}, inContToken) if err != nil { - return nil, errors.Wrapf(err, "could not query backend for stacks") + return nil, fmt.Errorf("could not query backend for stacks: %w", err) } allSummaries = append(allSummaries, summaries...) @@ -407,15 +408,15 @@ func chooseStack( // With the stack name selected, look it up from the backend. stackRef, err := b.ParseStackReference(option) if err != nil { - return nil, errors.Wrap(err, "parsing selected stack") + return nil, fmt.Errorf("parsing selected stack: %w", err) } // GetStack may return (nil, nil) if the stack isn't found. stack, err := b.GetStack(ctx, stackRef) if err != nil { - return nil, errors.Wrap(err, "getting selected stack") + return nil, fmt.Errorf("getting selected stack: %w", err) } if stack == nil { - return nil, errors.Errorf("no stack named '%s' found", stackRef) + return nil, fmt.Errorf("no stack named '%s' found", stackRef) } // If setCurrent is true, we'll persist this choice so it'll be used for future CLI operations. @@ -440,7 +441,7 @@ func parseAndSaveConfigArray(s backend.Stack, configArray []string, path bool) e } if err = saveConfig(s, commandLineConfig); err != nil { - return errors.Wrap(err, "saving config") + return fmt.Errorf("saving config: %w", err) } return nil } @@ -475,8 +476,9 @@ func readProject() (*workspace.Project, string, error) { // Now that we got here, we have a path, so we will try to load it. path, err := workspace.DetectProjectPathFrom(pwd) if err != nil { - return nil, "", errors.Wrapf(err, "failed to find current Pulumi project because of "+ - "an error when searching for the Pulumi.yaml file (searching upwards from %s)", pwd) + return nil, "", fmt.Errorf("failed to find current Pulumi project because of "+ + "an error when searching for the Pulumi.yaml file (searching upwards from %s)"+": %w", pwd, err) + } else if path == "" { return nil, "", fmt.Errorf( "no Pulumi.yaml project file found (searching upwards from %s). If you have not "+ @@ -484,7 +486,7 @@ func readProject() (*workspace.Project, string, error) { } proj, err := workspace.LoadProject(path) if err != nil { - return nil, "", errors.Wrapf(err, "failed to load Pulumi project located at %q", path) + return nil, "", fmt.Errorf("failed to load Pulumi project located at %q: %w", path, err) } return proj, filepath.Dir(path), nil @@ -502,14 +504,15 @@ func readPolicyProject() (*workspace.PolicyPackProject, string, string, error) { // Now that we got here, we have a path, so we will try to load it. path, err := workspace.DetectPolicyPackPathFrom(pwd) if err != nil { - return nil, "", "", errors.Wrapf(err, "failed to find current Pulumi project because of "+ - "an error when searching for the PulumiPolicy.yaml file (searching upwards from %s)", pwd) + return nil, "", "", fmt.Errorf("failed to find current Pulumi project because of "+ + "an error when searching for the PulumiPolicy.yaml file (searching upwards from %s)"+": %w", pwd, err) + } else if path == "" { return nil, "", "", fmt.Errorf("no PulumiPolicy.yaml project file found (searching upwards from %s)", pwd) } proj, err := workspace.LoadPolicyPack(path) if err != nil { - return nil, "", "", errors.Wrapf(err, "failed to load Pulumi policy project located at %q", path) + return nil, "", "", fmt.Errorf("failed to load Pulumi policy project located at %q: %w", path, err) } return proj, path, filepath.Dir(path), nil @@ -543,7 +546,7 @@ func isGitWorkTreeDirty(repoRoot string) (bool, error) { if ee, ok := err.(*exec.ExitError); ok { ee.Stderr = stderr.Bytes() } - return false, errors.Wrapf(err, "'git status' failed") + return false, fmt.Errorf("'git status' failed: %w", err) } return bool(anyOutput), nil @@ -575,7 +578,7 @@ func addGitMetadata(repoRoot string, m *backend.UpdateMetadata) error { // Gather git-related data as appropriate. (Returns nil, nil if no repo found.) repo, err := gitutil.GetGitRepository(repoRoot) if err != nil { - return errors.Wrapf(err, "detecting Git repository") + return fmt.Errorf("detecting Git repository: %w", err) } if repo == nil { return nil @@ -599,7 +602,7 @@ func AddGitRemoteMetadataToMap(repo *git.Repository, env map[string]string) erro // Get the remote URL for this repo. remoteURL, err := gitutil.GetGitRemoteURL(repo, "origin") if err != nil { - return errors.Wrap(err, "detecting Git remote URL") + return fmt.Errorf("detecting Git remote URL: %w", err) } if remoteURL == "" { return nil @@ -618,7 +621,7 @@ func addVCSMetadataToEnvironment(remoteURL string, env map[string]string) error // We don't require a cloud-hosted VCS, so swallow errors. vcsInfo, err := gitutil.TryGetVCSInfo(remoteURL) if err != nil { - return errors.Wrap(err, "detecting VCS project information") + return fmt.Errorf("detecting VCS project information: %w", err) } env[backend.VCSRepoOwner] = vcsInfo.Owner env[backend.VCSRepoName] = vcsInfo.Repo @@ -636,14 +639,14 @@ func addGitCommitMetadata(repo *git.Repository, repoRoot string, m *backend.Upda // Commit at HEAD head, err := repo.Head() if err != nil { - return errors.Wrap(err, "getting repository HEAD") + return fmt.Errorf("getting repository HEAD: %w", err) } hash := head.Hash() m.Environment[backend.GitHead] = hash.String() commit, commitErr := repo.CommitObject(hash) if commitErr != nil { - return errors.Wrap(commitErr, "getting HEAD commit info") + return fmt.Errorf("getting HEAD commit info: %w", commitErr) } // If in detached head, will be "HEAD", and fallback to use value from CI/CD system if possible. @@ -674,7 +677,7 @@ func addGitCommitMetadata(repo *git.Repository, repoRoot string, m *backend.Upda // If the worktree is dirty, set a bit, as this could be a mistake. isDirty, err := isGitWorkTreeDirty(repoRoot) if err != nil { - return errors.Wrapf(err, "checking git worktree dirty state") + return fmt.Errorf("checking git worktree dirty state: %w", err) } m.Environment[backend.GitDirty] = strconv.FormatBool(isDirty) @@ -840,7 +843,7 @@ func checkDeploymentVersionError(err error, stackName string) error { return fmt.Errorf("the stack '%s' is newer than what this version of the Pulumi CLI understands. "+ "Please update your version of the Pulumi CLI", stackName) } - return errors.Wrap(err, "could not deserialize deployment") + return fmt.Errorf("could not deserialize deployment: %w", err) } func getRefreshOption(proj *workspace.Project, refresh string) (bool, error) { @@ -893,3 +896,20 @@ func readPlan(path string, dec config.Decrypter, enc config.Encrypter) (deploy.P } return stack.DeserializePlan(deploymentPlan, dec, enc) } + +func buildStackName(stackName string) (string, error) { + if strings.Count(stackName, "/") == 2 { + return stackName, nil + } + + defaultOrg, err := workspace.GetBackendConfigDefaultOrg() + if err != nil { + return "", err + } + + if defaultOrg != "" { + return fmt.Sprintf("%s/%s", defaultOrg, stackName), nil + } + + return stackName, nil +} diff --git a/pkg/cmd/pulumi/watch.go b/pkg/cmd/pulumi/watch.go index 4db20364f..ccfb1cbbf 100644 --- a/pkg/cmd/pulumi/watch.go +++ b/pkg/cmd/pulumi/watch.go @@ -16,8 +16,9 @@ package main import ( "context" + "errors" + "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/pulumi/pulumi/pkg/v3/backend" @@ -101,17 +102,17 @@ func newWatchCmd() *cobra.Command { m, err := getUpdateMetadata(message, root, execKind, "" /* execAgent */) if err != nil { - return result.FromError(errors.Wrap(err, "gathering environment metadata")) + return result.FromError(fmt.Errorf("gathering environment metadata: %w", err)) } sm, err := getStackSecretsManager(s) if err != nil { - return result.FromError(errors.Wrap(err, "getting secrets manager")) + return result.FromError(fmt.Errorf("getting secrets manager: %w", err)) } cfg, err := getStackConfiguration(s, sm) if err != nil { - return result.FromError(errors.Wrap(err, "getting stack configuration")) + return result.FromError(fmt.Errorf("getting stack configuration: %w", err)) } opts.Engine = engine.UpdateOptions{ diff --git a/pkg/codegen/docs/gen.go b/pkg/codegen/docs/gen.go index 5d53a9c65..47f4aa5fd 100644 --- a/pkg/codegen/docs/gen.go +++ b/pkg/codegen/docs/gen.go @@ -23,6 +23,7 @@ package docs import ( "bytes" "embed" + "errors" "fmt" "html" "html/template" @@ -31,7 +32,6 @@ import ( "strings" "github.com/golang/glog" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/codegen" "github.com/pulumi/pulumi/pkg/v3/codegen/dotnet" @@ -431,7 +431,7 @@ func (dctx *docGenContext) getLanguageDocHelper(lang string) codegen.DocLanguage if h, ok := dctx.docHelpers[lang]; ok { return h } - panic(errors.Errorf("could not find a doc lang helper for %s", lang)) + panic(fmt.Errorf("could not find a doc lang helper for %s", lang)) } type propertyCharacteristics struct { @@ -1174,7 +1174,7 @@ func (mod *modContext) getConstructorResourceInfo(resourceTypeName string) map[s resourceTypeName = fmt.Sprintf("Pulumi.%s.%s.%s", namespace, modName, resourceTypeName) default: - panic(errors.Errorf("cannot generate constructor info for unhandled language %q", lang)) + panic(fmt.Errorf("cannot generate constructor info for unhandled language %q", lang)) } parts := strings.Split(resourceTypeName, ".") diff --git a/pkg/codegen/docs/gen_function.go b/pkg/codegen/docs/gen_function.go index 3d986efb5..ebcaee460 100644 --- a/pkg/codegen/docs/gen_function.go +++ b/pkg/codegen/docs/gen_function.go @@ -23,7 +23,6 @@ import ( "fmt" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/codegen" go_gen "github.com/pulumi/pulumi/pkg/v3/codegen/go" "github.com/pulumi/pulumi/pkg/v3/codegen/python" @@ -113,7 +112,7 @@ func (mod *modContext) getFunctionResourceInfo(f *schema.Function, outputVersion case "python": resultTypeName = docLangHelper.GetResourceFunctionResultName(mod.mod, f) default: - panic(errors.Errorf("cannot generate function resource info for unhandled language %q", lang)) + panic(fmt.Errorf("cannot generate function resource info for unhandled language %q", lang)) } parts := strings.Split(resultTypeName, ".") diff --git a/pkg/codegen/docs/gen_test.go b/pkg/codegen/docs/gen_test.go index 161d26405..dc1cf7812 100644 --- a/pkg/codegen/docs/gen_test.go +++ b/pkg/codegen/docs/gen_test.go @@ -20,10 +20,11 @@ package docs import ( "fmt" + "testing" + "github.com/pulumi/pulumi/pkg/v3/codegen/internal/test" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" "github.com/stretchr/testify/assert" - "testing" ) const ( diff --git a/pkg/codegen/docs/package_tree.go b/pkg/codegen/docs/package_tree.go index cb67c63f2..98fd92956 100644 --- a/pkg/codegen/docs/package_tree.go +++ b/pkg/codegen/docs/package_tree.go @@ -1,7 +1,7 @@ package docs import ( - "github.com/pkg/errors" + "fmt" "sort" ) @@ -36,7 +36,7 @@ func generatePackageTree(rootMod modContext) ([]PackageTreeItem, error) { children, err := generatePackageTree(*m) if err != nil { - return nil, errors.Wrapf(err, "generating children for module %s (mod token: %s)", displayName, m.mod) + return nil, fmt.Errorf("generating children for module %s (mod token: %s): %w", displayName, m.mod, err) } ti := PackageTreeItem{ diff --git a/pkg/codegen/dotnet/gen.go b/pkg/codegen/dotnet/gen.go index 5e9508b9d..0b26c4e7a 100644 --- a/pkg/codegen/dotnet/gen.go +++ b/pkg/codegen/dotnet/gen.go @@ -32,7 +32,6 @@ import ( "strings" "unicode" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/codegen" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" @@ -767,7 +766,7 @@ func primitiveValue(value interface{}) (string, error) { case reflect.String: return fmt.Sprintf("%q", v.String()), nil default: - return "", errors.Errorf("unsupported default value of type %T", value) + return "", fmt.Errorf("unsupported default value of type %T", value) } } @@ -796,7 +795,7 @@ func (mod *modContext) getDefaultValue(dv *schema.DefaultValue, t schema.Type) ( break } if val == "" { - return "", errors.Errorf("default value '%v' not found in enum '%s'", dv.Value, enumName) + return "", fmt.Errorf("default value '%v' not found in enum '%s'", dv.Value, enumName) } default: v, err := primitiveValue(dv.Value) @@ -1995,6 +1994,11 @@ func (mod *modContext) gen(fs fs) error { // Resources for _, r := range mod.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + imports := map[string]codegen.StringSet{} mod.getImportsForResource(r, imports, r) @@ -2017,6 +2021,11 @@ func (mod *modContext) gen(fs fs) error { // Functions for _, f := range mod.functions { + if f.IsOverlay { + // This function code is generated by the provider, so no further action is required. + continue + } + code, err := mod.genFunctionFileCode(f) if err != nil { return err @@ -2026,6 +2035,11 @@ func (mod *modContext) gen(fs fs) error { // Nested types for _, t := range mod.types { + if t.IsOverlay { + // This type is generated by the provider, so no further action is required. + continue + } + if mod.details(t).inputType { buffer := &bytes.Buffer{} mod.genHeader(buffer, pulumiImports) @@ -2186,6 +2200,11 @@ func generateModuleContextMap(tool string, pkg *schema.Package) (map[string]*mod computePropertyNames(pkg.Config, propertyNames) computePropertyNames(pkg.Provider.InputProperties, propertyNames) for _, r := range pkg.Resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + computePropertyNames(r.Properties, propertyNames) computePropertyNames(r.InputProperties, propertyNames) if r.StateInputs != nil { @@ -2193,6 +2212,11 @@ func generateModuleContextMap(tool string, pkg *schema.Package) (map[string]*mod } } for _, f := range pkg.Functions { + if f.IsOverlay { + // This function code is generated by the provider, so no further action is required. + continue + } + if f.Inputs != nil { computePropertyNames(f.Inputs.Properties, propertyNames) } @@ -2289,6 +2313,11 @@ func generateModuleContextMap(tool string, pkg *schema.Package) (map[string]*mod // Find input and output types referenced by functions. for _, f := range pkg.Functions { + if f.IsOverlay { + // This function code is generated by the provider, so no further action is required. + continue + } + mod := getModFromToken(f.Token, pkg) if !f.IsMethod { mod.functions = append(mod.functions, f) @@ -2347,6 +2376,11 @@ func LanguageResources(tool string, pkg *schema.Package) (map[string]LanguageRes continue } for _, r := range mod.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + lr := LanguageResource{ Resource: r, Package: namespaceName(info.Namespaces, modName), diff --git a/pkg/codegen/dotnet/utilities.go b/pkg/codegen/dotnet/utilities.go index 21c13ac5c..481e90251 100644 --- a/pkg/codegen/dotnet/utilities.go +++ b/pkg/codegen/dotnet/utilities.go @@ -15,13 +15,12 @@ package dotnet import ( + "fmt" "regexp" "strings" "unicode" "github.com/pulumi/pulumi/pkg/v3/codegen" - - "github.com/pkg/errors" ) // isReservedWord returns true if s is a C# reserved word as per @@ -97,7 +96,7 @@ func makeSafeEnumName(name, typeName string) (string, error) { // If the name is one illegal character, return an error. if len(safeName) == 1 && !isLegalIdentifierStart(rune(safeName[0])) { - return "", errors.Errorf("enum name %s is not a valid identifier", safeName) + return "", fmt.Errorf("enum name %s is not a valid identifier", safeName) } // Capitalize and make a valid identifier. diff --git a/pkg/codegen/dotnet/utilities_test.go b/pkg/codegen/dotnet/utilities_test.go index 5596d2b01..f2351becd 100644 --- a/pkg/codegen/dotnet/utilities_test.go +++ b/pkg/codegen/dotnet/utilities_test.go @@ -1,6 +1,8 @@ package dotnet -import "testing" +import ( + "testing" +) func TestMakeSafeEnumName(t *testing.T) { tests := []struct { diff --git a/pkg/codegen/go/gen.go b/pkg/codegen/go/gen.go index 9d72cc9f4..06c41b4ed 100644 --- a/pkg/codegen/go/gen.go +++ b/pkg/codegen/go/gen.go @@ -31,8 +31,6 @@ import ( "strings" "unicode" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/codegen" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" @@ -1283,7 +1281,7 @@ func goPrimitiveValue(value interface{}) (string, error) { case reflect.String: return fmt.Sprintf("%q", v.String()), nil default: - return "", errors.Errorf("unsupported default value of type %T", value) + return "", fmt.Errorf("unsupported default value of type %T", value) } } @@ -2019,6 +2017,10 @@ func rewriteCyclicObjectFields(pkg *schema.Package) { func (pkg *pkgContext) genType(w io.Writer, obj *schema.ObjectType) { contract.Assert(!obj.IsInputShape()) + if obj.IsOverlay { + // This type is generated by the provider, so no further action is required. + return + } pkg.genPlainType(w, pkg.tokenToType(obj.Token), obj.Comment, "", obj.Properties) pkg.genInputTypes(w, obj.InputShape, pkg.detailsForType(obj)) @@ -2130,6 +2132,10 @@ func (pkg *pkgContext) genTypeRegistrations(w io.Writer, objTypes []*schema.Obje // Input types. if !pkg.disableInputTypeRegistrations { for _, obj := range objTypes { + if obj.IsOverlay { + // This type is generated by the provider, so no further action is required. + continue + } name, details := pkg.tokenToType(obj.Token), pkg.detailsForType(obj) fmt.Fprintf(w, "\tpulumi.RegisterInputType(reflect.TypeOf((*%[1]sInput)(nil)).Elem(), %[1]sArgs{})\n", name) if details.ptrElement { @@ -2152,6 +2158,10 @@ func (pkg *pkgContext) genTypeRegistrations(w io.Writer, objTypes []*schema.Obje // Output types. for _, obj := range objTypes { + if obj.IsOverlay { + // This type is generated by the provider, so no further action is required. + continue + } name, details := pkg.tokenToType(obj.Token), pkg.detailsForType(obj) fmt.Fprintf(w, "\tpulumi.RegisterOutputType(%sOutput{})\n", name) if details.ptrElement { @@ -2510,6 +2520,17 @@ func (pkg *pkgContext) genConfig(w io.Writer, variables []*schema.Property) erro // definition and its registration to support rehydrating providers. func (pkg *pkgContext) genResourceModule(w io.Writer) { contract.Assert(len(pkg.resources) != 0) + allResourcesAreOverlays := true + for _, r := range pkg.resources { + if !r.IsOverlay { + allResourcesAreOverlays = false + break + } + } + if allResourcesAreOverlays { + // If all resources in this module are overlays, skip further code generation. + return + } basePath := pkg.importBasePath @@ -2545,6 +2566,10 @@ func (pkg *pkgContext) genResourceModule(w io.Writer) { fmt.Fprintf(w, "func (m *module) Construct(ctx *pulumi.Context, name, typ, urn string) (r pulumi.Resource, err error) {\n") fmt.Fprintf(w, "\tswitch typ {\n") for _, r := range pkg.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } if r.IsProvider { contract.Assert(provider == nil) provider = r @@ -3029,6 +3054,11 @@ func LanguageResources(tool string, pkg *schema.Package) (map[string]LanguageRes pkg := packages[mod] for _, r := range pkg.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + packagePath := path.Join(goPkgInfo.ImportBasePath, pkg.mod) resources[r.Token] = LanguageResource{ Resource: r, @@ -3098,14 +3128,14 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error setFile := func(relPath, contents string) { relPath = path.Join(pathPrefix, relPath) if _, ok := files[relPath]; ok { - panic(errors.Errorf("duplicate file: %s", relPath)) + panic(fmt.Errorf("duplicate file: %s", relPath)) } // Run Go formatter on the code before saving to disk formattedSource, err := format.Source([]byte(contents)) if err != nil { fmt.Fprintf(os.Stderr, "Invalid content:\n%s\n%s\n", relPath, contents) - panic(errors.Wrapf(err, "invalid Go source code:\n\n%s\n", relPath)) + panic(fmt.Errorf("invalid Go source code:\n\n%s\n: %w", relPath, err)) } files[relPath] = formattedSource @@ -3142,6 +3172,11 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error // Resources for _, r := range pkg.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + importsAndAliases := map[string]string{} pkg.getImports(r, importsAndAliases) @@ -3157,6 +3192,11 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error // Functions for _, f := range pkg.functions { + if f.IsOverlay { + // This function code is generated by the provider, so no further action is required. + continue + } + fileName := path.Join(mod, camel(tokenToName(f.Token))+".go") code := pkg.genFunctionCodeFile(f) setFile(fileName, code) @@ -3248,7 +3288,7 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error } // If there are resources in this module, register the module with the runtime. - if len(pkg.resources) != 0 { + if len(pkg.resources) != 0 && !allResourcesAreOverlays(pkg.resources) { buffer := &bytes.Buffer{} pkg.genResourceModule(buffer) @@ -3259,6 +3299,15 @@ func GeneratePackage(tool string, pkg *schema.Package) (map[string][]byte, error return files, nil } +func allResourcesAreOverlays(resources []*schema.Resource) bool { + for _, r := range resources { + if !r.IsOverlay { + return false + } + } + return true +} + // goPackage returns the suggested package name for the given string. func goPackage(name string) string { return strings.ReplaceAll(name, "-", "") diff --git a/pkg/codegen/go/gen_crd2pulumi.go b/pkg/codegen/go/gen_crd2pulumi.go index 2d562d6ee..1d3959f76 100644 --- a/pkg/codegen/go/gen_crd2pulumi.go +++ b/pkg/codegen/go/gen_crd2pulumi.go @@ -2,8 +2,8 @@ package gen import ( "bytes" + "fmt" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" ) @@ -37,7 +37,7 @@ func CRDTypes(tool string, pkg *schema.Package) (map[string]*bytes.Buffer, error pkg.genHeader(buffer, []string{"context", "reflect"}, importsAndAliases) if err := pkg.genResource(buffer, r, goPkgInfo.GenerateResourceContainerTypes); err != nil { - return nil, errors.Wrapf(err, "generating resource %s", mod) + return nil, fmt.Errorf("generating resource %s: %w", mod, err) } } diff --git a/pkg/codegen/go/gen_program.go b/pkg/codegen/go/gen_program.go index 7471a3c96..22f3b3e74 100644 --- a/pkg/codegen/go/gen_program.go +++ b/pkg/codegen/go/gen_program.go @@ -9,7 +9,7 @@ import ( "sync" "github.com/hashicorp/hcl/v2" - "github.com/pkg/errors" + "github.com/pulumi/pulumi/pkg/v3/codegen" "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model" "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model/format" @@ -93,7 +93,7 @@ func GenerateProgram(program *pcl.Program) (map[string][]byte, hcl.Diagnostics, // Run Go formatter on the code before saving to disk formattedSource, err := gofmt.Source(index.Bytes()) if err != nil { - panic(errors.Errorf("invalid Go source code:\n\n%s", index.String())) + panic(fmt.Errorf("invalid Go source code:\n\n%s", index.String())) } files := map[string][]byte{ @@ -287,7 +287,7 @@ func (g *generator) getVersionPath(program *pcl.Program, pkg string) (string, er } } - return "", errors.Errorf("could not find package version information for pkg: %s", pkg) + return "", fmt.Errorf("could not find package version information for pkg: %s", pkg) } diff --git a/pkg/codegen/go/utilities.go b/pkg/codegen/go/utilities.go index 34751cc01..1c64461df 100644 --- a/pkg/codegen/go/utilities.go +++ b/pkg/codegen/go/utilities.go @@ -20,7 +20,6 @@ import ( "strings" "unicode" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/codegen" ) @@ -84,7 +83,7 @@ func makeSafeEnumName(name, typeName string) (string, error) { // If the name is one illegal character, return an error. if len(safeName) == 1 && !isLegalIdentifierStart(rune(safeName[0])) { - return "", errors.Errorf("enum name %s is not a valid identifier", safeName) + return "", fmt.Errorf("enum name %s is not a valid identifier", safeName) } // Capitalize and make a valid identifier. diff --git a/pkg/codegen/hcl2/model/format/func.go b/pkg/codegen/hcl2/model/format/func.go index 9b06cc630..698772b2f 100644 --- a/pkg/codegen/hcl2/model/format/func.go +++ b/pkg/codegen/hcl2/model/format/func.go @@ -14,7 +14,9 @@ package format -import "fmt" +import ( + "fmt" +) // Func is a function type that implements the fmt.Formatter interface. This can be used to conveniently // implement this interface for types defined in other packages. diff --git a/pkg/codegen/hcl2/model/type_collection.go b/pkg/codegen/hcl2/model/type_collection.go index 7e52657a2..06d2c8e20 100644 --- a/pkg/codegen/hcl2/model/type_collection.go +++ b/pkg/codegen/hcl2/model/type_collection.go @@ -14,7 +14,9 @@ package model -import "github.com/hashicorp/hcl/v2" +import ( + "github.com/hashicorp/hcl/v2" +) // unwrapIterableSourceType removes any eventual types that wrap a type intended for iteration. func unwrapIterableSourceType(t Type) Type { diff --git a/pkg/codegen/hcl2/model/type_opaque.go b/pkg/codegen/hcl2/model/type_opaque.go index 28e2eab28..99c5e63db 100644 --- a/pkg/codegen/hcl2/model/type_opaque.go +++ b/pkg/codegen/hcl2/model/type_opaque.go @@ -19,7 +19,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/pkg/errors" + "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/syntax" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" ) @@ -55,7 +55,7 @@ func MustNewOpaqueType(name string, annotations ...interface{}) *OpaqueType { // NewOpaqueType creates a new opaque type with the given name. func NewOpaqueType(name string, annotations ...interface{}) (*OpaqueType, error) { if _, ok := opaqueTypes[name]; ok { - return nil, errors.Errorf("opaque type %s is already defined", name) + return nil, fmt.Errorf("opaque type %s is already defined", name) } t := &OpaqueType{Name: name, Annotations: annotations} diff --git a/pkg/codegen/hcl2/syntax/utilities.go b/pkg/codegen/hcl2/syntax/utilities.go index 7b0431117..22c066d96 100644 --- a/pkg/codegen/hcl2/syntax/utilities.go +++ b/pkg/codegen/hcl2/syntax/utilities.go @@ -1,6 +1,8 @@ package syntax -import "github.com/hashicorp/hcl/v2/hclsyntax" +import ( + "github.com/hashicorp/hcl/v2/hclsyntax" +) // None is an HCL syntax node that can be used when a syntax node is required but none is appropriate. var None hclsyntax.Node = &hclsyntax.Body{} diff --git a/pkg/codegen/internal/test/helpers.go b/pkg/codegen/internal/test/helpers.go index 66bbaa6a9..f1226646e 100644 --- a/pkg/codegen/internal/test/helpers.go +++ b/pkg/codegen/internal/test/helpers.go @@ -17,6 +17,7 @@ package test import ( "bytes" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -26,7 +27,6 @@ import ( "sort" "testing" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" @@ -442,7 +442,7 @@ func currentVersion(path string) (string, error) { } json, ok := data.(map[string]interface{}) if !ok { - return "", errors.Errorf("%s could not be read", path) + return "", fmt.Errorf("%s could not be read", path) } version, ok := json["version"] if !ok { @@ -468,7 +468,7 @@ func replaceSchema(c chan error, path, version, url string) { err = os.Remove(path) if !os.IsNotExist(err) && err != nil { - c <- errors.Wrap(err, "failed to replace schema") + c <- fmt.Errorf("failed to replace schema: %w", err) return } schemaFile, err := os.Create(path) diff --git a/pkg/codegen/internal/test/sdk_driver.go b/pkg/codegen/internal/test/sdk_driver.go index 2f204322d..22054dbf4 100644 --- a/pkg/codegen/internal/test/sdk_driver.go +++ b/pkg/codegen/internal/test/sdk_driver.go @@ -217,11 +217,11 @@ type SDKCodegenOptions struct { Checks map[string]CodegenCheck } -// `TestSDKCodegen` runs the complete set of SDK code generation tests +// TestSDKCodegen runs the complete set of SDK code generation tests // against a particular language's code generator. It also verifies // that the generated code is structurally sound. // -// The tests files live in `pkg/codegen/internal/test/testdata` and +// The test files live in `pkg/codegen/internal/test/testdata` and // are registered in `var sdkTests` in `sdk_driver.go`. // // An SDK code generation test files consists of a schema and a set of @@ -245,7 +245,7 @@ type SDKCodegenOptions struct { // PULUMI_ACCEPT=true go test ./... // // This will rebuild subfolders such as `go/` from scratch and store -// the set of code-generated file names in `go/codegen-manfiest.json`. +// the set of code-generated file names in `go/codegen-manifest.json`. // If these outputs look correct, they need to be checked into git and // will then serve as the expected values for the normal test runs: // diff --git a/pkg/codegen/internal/test/testdata/schema/overlay.json b/pkg/codegen/internal/test/testdata/schema/overlay.json new file mode 100644 index 000000000..65d3778d8 --- /dev/null +++ b/pkg/codegen/internal/test/testdata/schema/overlay.json @@ -0,0 +1,87 @@ +{ + "version": "0.0.1", + "name": "example", + "types": { + "example::ConfigMap": { + "properties": { + "config": { + "type": "string" + } + }, + "type": "object" + }, + "example::ConfigMapOverlay": { + "isOverlay": true, + "properties": { + "config": { + "type": "string" + } + }, + "type": "object" + } + }, + "resources": { + "example::Resource": { + "properties": { + "foo": { + "$ref": "#/types/example::ConfigMapOverlay" + } + }, + "inputProperties": { + "foo": { + "$ref": "#/types/example::ConfigMapOverlay" + } + }, + "type": "object" + }, + "example::OverlayResource": { + "isOverlay": true, + "properties": { + "foo": { + "$ref": "#/types/example::ConfigMapOverlay" + } + }, + "inputProperties": { + "foo": { + "$ref": "#/types/example::ConfigMapOverlay" + } + }, + "type": "object" + } + }, + "functions": { + "example::Function": { + "inputs": { + "properties": { + "arg1": { + "type": "string" + } + } + }, + "outputs": { + "properties": { + "result": { + "type": "string" + } + } + } + }, + "example::OverlayFunction": { + "isOverlay": true, + "inputs": { + "properties": { + "arg1": { + "type": "string" + } + } + }, + "outputs": { + "properties": { + "result": { + "type": "string" + } + } + } + } + } +} diff --git a/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/_index.md b/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/_index.md index ebf7803bc..8119fbbdb 100644 --- a/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/_index.md +++ b/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/_index.md @@ -14,6 +14,7 @@ no_edit_this_page: true

Resources

  • OtherResource
  • +
  • OverlayResource
  • Provider
  • Resource
  • TypeUses
  • @@ -22,6 +23,7 @@ no_edit_this_page: true

    Functions

    Package Details

    diff --git a/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/codegen-manifest.json b/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/codegen-manifest.json index a81907550..49c372c11 100644 --- a/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/codegen-manifest.json +++ b/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/codegen-manifest.json @@ -3,6 +3,8 @@ "_index.md", "argfunction/_index.md", "otherresource/_index.md", + "overlayfunction/_index.md", + "overlayresource/_index.md", "provider/_index.md", "resource/_index.md", "typeuses/_index.md" diff --git a/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/overlayfunction/_index.md b/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/overlayfunction/_index.md new file mode 100644 index 000000000..ad9d3da5a --- /dev/null +++ b/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/overlayfunction/_index.md @@ -0,0 +1,190 @@ + +--- +title: "overlayFunction" +title_tag: "example.overlayFunction" +meta_desc: "Documentation for the example.overlayFunction function with examples, input properties, output properties, and supporting types." +layout: api +no_edit_this_page: true +--- + + + + + + + + + +## Using overlayFunction {#using} + +Two invocation forms are available. The direct form accepts plain +arguments and either blocks until the result value is available, or +returns a Promise-wrapped result. The output form accepts +Input-wrapped arguments and returns an Output-wrapped result. + +{{< chooser language "typescript,python,go,csharp" / >}} + + +{{% choosable language nodejs %}} +
    function overlayFunction(args: OverlayFunctionArgs, opts?: InvokeOptions): Promise<OverlayFunctionResult>
    +function overlayFunctionOutput(args: OverlayFunctionOutputArgs, opts?: InvokeOptions): Output<OverlayFunctionResult>
    +{{% /choosable %}} + + +{{% choosable language python %}} +
    def overlay_function(arg1: Optional[Resource] = None,
    +                     opts: Optional[InvokeOptions] = None) -> OverlayFunctionResult
    +def overlay_function_output(arg1: Optional[pulumi.Input[Resource]] = None,
    +                     opts: Optional[InvokeOptions] = None) -> Output[OverlayFunctionResult]
    +{{% /choosable %}} + + +{{% choosable language go %}} +
    func OverlayFunction(ctx *Context, args *OverlayFunctionArgs, opts ...InvokeOption) (*OverlayFunctionResult, error)
    +func OverlayFunctionOutput(ctx *Context, args *OverlayFunctionOutputArgs, opts ...InvokeOption) OverlayFunctionResultOutput
    + +> Note: This function is named `OverlayFunction` in the Go SDK. + +{{% /choosable %}} + + +{{% choosable language csharp %}} +
    public static class OverlayFunction 
    +{
    +    public static Task<OverlayFunctionResult> InvokeAsync(OverlayFunctionArgs args, InvokeOptions? opts = null)
    +    public static Output<OverlayFunctionResult> Invoke(OverlayFunctionInvokeArgs args, InvokeOptions? opts = null)
    +}
    +{{% /choosable %}} + + + +The following arguments are supported: + + +{{% choosable language csharp %}} +
    + +Arg1 + + + Pulumi.Example.Resource +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language go %}} +
    + +Arg1 + + + Resource +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language nodejs %}} +
    + +arg1 + + + Resource +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language python %}} +
    + +arg1 + + + Resource +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + + + + +## overlayFunction Result {#result} + +The following output properties are available: + + + +{{% choosable language csharp %}} +
    + +Result + + + Pulumi.Example.Resource +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language go %}} +
    + +Result + + + Resource +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language nodejs %}} +
    + +result + + + Resource +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language python %}} +
    + +result + + + Resource +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + + + + + +

    Package Details

    +
    +
    Repository
    +
    +
    License
    +
    +
    + diff --git a/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/overlayresource/_index.md b/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/overlayresource/_index.md new file mode 100644 index 000000000..fe940258c --- /dev/null +++ b/pkg/codegen/internal/test/testdata/simple-resource-schema/docs/overlayresource/_index.md @@ -0,0 +1,328 @@ + +--- +title: "OverlayResource" +title_tag: "example.OverlayResource" +meta_desc: "Documentation for the example.OverlayResource resource with examples, input properties, output properties, lookup functions, and supporting types." +layout: api +no_edit_this_page: true +--- + + + + + + + + + +## Create a OverlayResource Resource {#create} +{{< chooser language "typescript,python,go,csharp" / >}} + + +{{% choosable language nodejs %}} +
    new OverlayResource(name: string, args?: OverlayResourceArgs, opts?: CustomResourceOptions);
    +{{% /choosable %}} + +{{% choosable language python %}} +
    @overload
    +def OverlayResource(resource_name: str,
    +                    opts: Optional[ResourceOptions] = None,
    +                    foo: Optional[ConfigMapOverlayArgs] = None)
    +@overload
    +def OverlayResource(resource_name: str,
    +                    args: Optional[OverlayResourceArgs] = None,
    +                    opts: Optional[ResourceOptions] = None)
    +{{% /choosable %}} + +{{% choosable language go %}} +
    func NewOverlayResource(ctx *Context, name string, args *OverlayResourceArgs, opts ...ResourceOption) (*OverlayResource, error)
    +{{% /choosable %}} + +{{% choosable language csharp %}} +
    public OverlayResource(string name, OverlayResourceArgs? args = null, CustomResourceOptions? opts = null)
    +{{% /choosable %}} + +{{% choosable language nodejs %}} + +
    + name + + string +
    +
    The unique name of the resource.
    + args + + OverlayResourceArgs +
    +
    The arguments to resource properties.
    + opts + + CustomResourceOptions +
    +
    Bag of options to control resource's behavior.
    + +{{% /choosable %}} + +{{% choosable language python %}} + +
    + resource_name + + str +
    +
    The unique name of the resource.
    + args + + OverlayResourceArgs +
    +
    The arguments to resource properties.
    + opts + + ResourceOptions +
    +
    Bag of options to control resource's behavior.
    + +{{% /choosable %}} + +{{% choosable language go %}} + +
    + ctx + + Context +
    +
    Context object for the current deployment.
    + name + + string +
    +
    The unique name of the resource.
    + args + + OverlayResourceArgs +
    +
    The arguments to resource properties.
    + opts + + ResourceOption +
    +
    Bag of options to control resource's behavior.
    + +{{% /choosable %}} + +{{% choosable language csharp %}} + +
    + name + + string +
    +
    The unique name of the resource.
    + args + + OverlayResourceArgs +
    +
    The arguments to resource properties.
    + opts + + CustomResourceOptions +
    +
    Bag of options to control resource's behavior.
    + +{{% /choosable %}} + +## OverlayResource Resource Properties {#properties} + +To learn more about resource properties and how to use them, see [Inputs and Outputs]({{< relref "/docs/intro/concepts/inputs-outputs" >}}) in the Architecture and Concepts docs. + +### Inputs + +The OverlayResource resource accepts the following [input]({{< relref "/docs/intro/concepts/inputs-outputs" >}}) properties: + + + +{{% choosable language csharp %}} +
    + +Foo + + + ConfigMapOverlayArgs +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language go %}} +
    + +Foo + + + ConfigMapOverlayArgs +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language nodejs %}} +
    + +foo + + + ConfigMapOverlayArgs +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language python %}} +
    + +foo + + + ConfigMapOverlayArgs +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + + +### Outputs + +All [input](#inputs) properties are implicitly available as output properties. Additionally, the OverlayResource resource produces the following output properties: + + + +{{% choosable language csharp %}} +
    + +Id + + + string +
    +
    {{% md %}}The provider-assigned unique ID for this managed resource.{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language go %}} +
    + +Id + + + string +
    +
    {{% md %}}The provider-assigned unique ID for this managed resource.{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language nodejs %}} +
    + +id + + + string +
    +
    {{% md %}}The provider-assigned unique ID for this managed resource.{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language python %}} +
    + +id + + + str +
    +
    {{% md %}}The provider-assigned unique ID for this managed resource.{{% /md %}}
    +{{% /choosable %}} + + + + + + + +## Supporting Types + + + +

    ConfigMapOverlay

    + +{{% choosable language csharp %}} +
    + +Config + + + string +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language go %}} +
    + +Config + + + string +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language nodejs %}} +
    + +config + + + string +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + +{{% choosable language python %}} +
    + +config + + + str +
    +
    {{% md %}}{{% /md %}}
    +{{% /choosable %}} + + +

    Package Details

    +
    +
    Repository
    +
    +
    License
    +
    +
    + diff --git a/pkg/codegen/internal/test/testdata/simple-resource-schema/schema.json b/pkg/codegen/internal/test/testdata/simple-resource-schema/schema.json index 924a65380..199ddf9ee 100644 --- a/pkg/codegen/internal/test/testdata/simple-resource-schema/schema.json +++ b/pkg/codegen/internal/test/testdata/simple-resource-schema/schema.json @@ -66,6 +66,15 @@ }, "type": "object" }, + "example::ConfigMapOverlay": { + "isOverlay": true, + "properties": { + "config": { + "type": "string" + } + }, + "type": "object" + }, "example::ObjectWithNodeOptionalInputs": { "properties": { "foo": { @@ -114,6 +123,20 @@ }, "type": "object" }, + "example::OverlayResource": { + "isOverlay": true, + "properties": { + "foo": { + "$ref": "#/types/example::ConfigMapOverlay" + } + }, + "inputProperties": { + "foo": { + "$ref": "#/types/example::ConfigMapOverlay" + } + }, + "type": "object" + }, "example::TypeUses": { "properties": { "foo": { @@ -156,6 +179,23 @@ } } } + }, + "example::overlayFunction": { + "isOverlay": true, + "inputs": { + "properties": { + "arg1": { + "$ref": "#/resources/example::Resource" + } + } + }, + "outputs": { + "properties": { + "result": { + "$ref": "#/resources/example::Resource" + } + } + } } }, "language": { diff --git a/pkg/codegen/nodejs/gen.go b/pkg/codegen/nodejs/gen.go index 41bb3f1d8..4f51b8ae7 100644 --- a/pkg/codegen/nodejs/gen.go +++ b/pkg/codegen/nodejs/gen.go @@ -21,6 +21,7 @@ package nodejs import ( "bytes" "encoding/json" + "errors" "fmt" "io" "path" @@ -31,8 +32,6 @@ import ( "strings" "unicode" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/codegen" "github.com/pulumi/pulumi/pkg/v3/codegen/internal/tstypes" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" @@ -443,7 +442,7 @@ func tsPrimitiveValue(value interface{}) (string, error) { case reflect.String: return fmt.Sprintf("%q", v.String()), nil default: - return "", errors.Errorf("unsupported default value of type %T", value) + return "", fmt.Errorf("unsupported default value of type %T", value) } } @@ -1404,6 +1403,11 @@ func (mod *modContext) sdkImports(nested, utilities bool) []string { func (mod *modContext) genTypes() (string, string) { externalImports, imports := codegen.NewStringSet(), map[string]codegen.StringSet{} for _, t := range mod.types { + if t.IsOverlay { + // This type is generated by the provider, so no further action is required. + continue + } + mod.getImports(t, externalImports, imports) } @@ -1454,6 +1458,11 @@ func (mod *modContext) getNamespaces() map[string]*namespace { } for _, t := range mod.types { + if t.IsOverlay { + // This type is generated by the provider, so no further action is required. + continue + } + modName := mod.pkg.TokenToModule(t.Token) if override, ok := mod.modToPkg[modName]; ok { modName = override @@ -1599,6 +1608,11 @@ func (mod *modContext) gen(fs fs) error { // Resources for _, r := range mod.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + externalImports, imports := codegen.NewStringSet(), map[string]codegen.StringSet{} referencesNestedTypes := mod.getImportsForResource(r, externalImports, imports, r) @@ -1615,6 +1629,11 @@ func (mod *modContext) gen(fs fs) error { // Functions for _, f := range mod.functions { + if f.IsOverlay { + // This function code is generated by the provider, so no further action is required. + continue + } + externalImports, imports := codegen.NewStringSet(), map[string]codegen.StringSet{} referencesNestedTypes := mod.getImports(f, externalImports, imports) @@ -1770,6 +1789,11 @@ func (mod *modContext) genResourceModule(w io.Writer) { } else { registrations, first := codegen.StringSet{}, true for _, r := range mod.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + if r.IsProvider { contract.Assert(provider == nil) provider = r @@ -1792,6 +1816,11 @@ func (mod *modContext) genResourceModule(w io.Writer) { fmt.Fprintf(w, " switch (type) {\n") for _, r := range mod.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + if r.IsProvider { continue } @@ -2107,6 +2136,11 @@ func generateModuleContextMap(tool string, pkg *schema.Package, extraFiles map[s }) scanResource := func(r *schema.Resource) { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + return + } + mod := getModFromToken(r.Token) mod.resources = append(mod.resources, r) visitObjectTypes(r.Properties, func(t *schema.ObjectType) { @@ -2225,6 +2259,11 @@ func LanguageResources(pkg *schema.Package) (map[string]LanguageResource, error) for modName, mod := range modules { for _, r := range mod.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + packagePath := strings.Replace(modName, "/", ".", -1) lr := LanguageResource{ Resource: r, diff --git a/pkg/codegen/nodejs/gen_intrinsics.go b/pkg/codegen/nodejs/gen_intrinsics.go index 500b17957..118f5dbde 100644 --- a/pkg/codegen/nodejs/gen_intrinsics.go +++ b/pkg/codegen/nodejs/gen_intrinsics.go @@ -14,7 +14,9 @@ package nodejs -import "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model" +import ( + "github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model" +) const ( // intrinsicAwait is the name of the await intrinsic. diff --git a/pkg/codegen/nodejs/utilities.go b/pkg/codegen/nodejs/utilities.go index 4cc3519d0..b9bad2719 100644 --- a/pkg/codegen/nodejs/utilities.go +++ b/pkg/codegen/nodejs/utilities.go @@ -15,12 +15,12 @@ package nodejs import ( + "fmt" "io" "regexp" "strings" "unicode" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/codegen" ) @@ -109,7 +109,7 @@ func makeSafeEnumName(name, typeName string) (string, error) { // If the name is one illegal character, return an error. if len(safeName) == 1 && !isLegalIdentifierStart(rune(safeName[0])) { - return "", errors.Errorf("enum name %s is not a valid identifier", safeName) + return "", fmt.Errorf("enum name %s is not a valid identifier", safeName) } // Capitalize and make a valid identifier. diff --git a/pkg/codegen/nodejs/utilities_test.go b/pkg/codegen/nodejs/utilities_test.go index b23116773..99de49d77 100644 --- a/pkg/codegen/nodejs/utilities_test.go +++ b/pkg/codegen/nodejs/utilities_test.go @@ -1,7 +1,9 @@ // nolint: lll package nodejs -import "testing" +import ( + "testing" +) func TestMakeSafeEnumName(t *testing.T) { tests := []struct { diff --git a/pkg/codegen/python/gen.go b/pkg/codegen/python/gen.go index be74609ea..901c46a5c 100644 --- a/pkg/codegen/python/gen.go +++ b/pkg/codegen/python/gen.go @@ -32,7 +32,6 @@ import ( "unicode" "github.com/blang/semver" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/codegen" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" @@ -472,6 +471,11 @@ func (mod *modContext) gen(fs fs) error { // Resources for _, r := range mod.resources { + if r.IsOverlay { + // This resource code is generated by the provider, so no further action is required. + continue + } + res, err := mod.genResource(r) if err != nil { return err @@ -490,6 +494,11 @@ func (mod *modContext) gen(fs fs) error { // Functions for _, f := range mod.functions { + if f.IsOverlay { + // This function code is generated by the provider, so no further action is required. + continue + } + fun, err := mod.genFunction(f) if err != nil { return err @@ -523,6 +532,9 @@ func (mod *modContext) gen(fs fs) error { } func (mod *modContext) hasTypes(input bool) bool { + if allTypesAreOverlays(mod.types) { + return false + } for _, t := range mod.types { if input && mod.details(t).inputType { return true @@ -875,12 +887,31 @@ func (mod *modContext) genConfigStubs(variables []*schema.Property) (string, err return w.String(), nil } +func allTypesAreOverlays(types []*schema.ObjectType) bool { + for _, t := range types { + if !t.IsOverlay { + return false + } + } + return true +} + func (mod *modContext) genTypes(dir string, fs fs) error { genTypes := func(file string, input bool) error { w := &bytes.Buffer{} + if allTypesAreOverlays(mod.types) { + // If all resources in this module are overlays, skip further code generation. + return nil + } + imports := imports{} for _, t := range mod.types { + if t.IsOverlay { + // This type is generated by the provider, so no further action is required. + continue + } + if input && mod.details(t).inputType { visitObjectTypes(t.Properties, func(t schema.Type) { switch t := t.(type) { @@ -909,6 +940,11 @@ func (mod *modContext) genTypes(dir string, fs fs) error { // Export only the symbols we want exported. fmt.Fprintf(w, "__all__ = [\n") for _, t := range mod.types { + if t.IsOverlay { + // This type is generated by the provider, so no further action is required. + continue + } + if input && mod.details(t).inputType || !input && mod.details(t).outputType { fmt.Fprintf(w, " '%s',\n", mod.unqualifiedObjectTypeName(t, input)) } @@ -917,6 +953,11 @@ func (mod *modContext) genTypes(dir string, fs fs) error { var hasTypes bool for _, t := range mod.types { + if t.IsOverlay { + // This type is generated by the provider, so no further action is required. + continue + } + if input && mod.details(t).inputType { if err := mod.genObjectType(w, t, true); err != nil { return err @@ -1062,8 +1103,7 @@ func (mod *modContext) genResource(res *schema.Resource) (string, error) { for _, t := range mod.types { if mod.details(t).inputType { if mod.unqualifiedObjectTypeName(t, true) == resourceArgsName { - return "", errors.Errorf( - "resource args class named %s in %s conflicts with input type", resourceArgsName, mod.mod) + return "", fmt.Errorf("resource args class named %s in %s conflicts with input type", resourceArgsName, mod.mod) } } } @@ -1841,7 +1881,7 @@ func (mod *modContext) genEnum(w io.Writer, enum *schema.EnumType) error { } } default: - return errors.Errorf("enums of type %s are not yet implemented for this language", enum.ElementType.String()) + return fmt.Errorf("enums of type %s are not yet implemented for this language", enum.ElementType.String()) } return nil @@ -2012,15 +2052,15 @@ func genPackageMetadata( // We expect a specific pattern of ">=version,=version1,=version1, 0 { @@ -2602,6 +2620,7 @@ func bindResource(path, token string, spec ResourceSpec, types *types, Language: language, IsComponent: spec.IsComponent, Methods: methods, + IsOverlay: spec.IsOverlay, }, diags, nil } @@ -2717,6 +2736,7 @@ func bindFunction(token string, spec FunctionSpec, types *types) (*Function, hcl Outputs: outputs, DeprecationMessage: spec.DeprecationMessage, Language: language, + IsOverlay: spec.IsOverlay, }, diags, nil } diff --git a/pkg/codegen/schema/schema_test.go b/pkg/codegen/schema/schema_test.go index 4e3a99a0b..c0c1d838f 100644 --- a/pkg/codegen/schema/schema_test.go +++ b/pkg/codegen/schema/schema_test.go @@ -22,6 +22,7 @@ import ( "path/filepath" "reflect" "sort" + "strings" "testing" "github.com/blang/semver" @@ -412,6 +413,43 @@ func TestMethods(t *testing.T) { } } +// TestIsOverlay tests that the IsOverlay field is set correctly for resources, types, and functions. Does not test +// codegen. +func TestIsOverlay(t *testing.T) { + t.Run("overlay", func(t *testing.T) { + pkgSpec := readSchemaFile(filepath.Join("schema", "overlay.json")) + + pkg, err := ImportSpec(pkgSpec, nil) + if err != nil { + t.Error(err) + } + for _, v := range pkg.Resources { + if strings.Contains(v.Token, "Overlay") { + assert.Truef(t, v.IsOverlay, "resource %q", v.Token) + } else { + assert.Falsef(t, v.IsOverlay, "resource %q", v.Token) + } + } + for _, v := range pkg.Types { + switch v := v.(type) { + case *ObjectType: + if strings.Contains(v.Token, "Overlay") { + assert.Truef(t, v.IsOverlay, "object type %q", v.Token) + } else { + assert.Falsef(t, v.IsOverlay, "object type %q", v.Token) + } + } + } + for _, v := range pkg.Functions { + if strings.Contains(v.Token, "Overlay") { + assert.Truef(t, v.IsOverlay, "function %q", v.Token) + } else { + assert.Falsef(t, v.IsOverlay, "function %q", v.Token) + } + } + }) +} + // Tests that the method ReplaceOnChanges works as expected. Does not test // codegen. func TestReplaceOnChanges(t *testing.T) { diff --git a/pkg/codegen/utilities_types.go b/pkg/codegen/utilities_types.go index 9d77a728b..407cc1e0c 100644 --- a/pkg/codegen/utilities_types.go +++ b/pkg/codegen/utilities_types.go @@ -1,6 +1,8 @@ package codegen -import "github.com/pulumi/pulumi/pkg/v3/codegen/schema" +import ( + "github.com/pulumi/pulumi/pkg/v3/codegen/schema" +) func visitTypeClosure(t schema.Type, visitor func(t schema.Type), seen Set) { if seen.Has(t) { diff --git a/pkg/engine/deployment.go b/pkg/engine/deployment.go index 8d08c333e..f1afe74f9 100644 --- a/pkg/engine/deployment.go +++ b/pkg/engine/deployment.go @@ -16,10 +16,12 @@ package engine import ( "context" + "errors" + "fmt" "time" "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" + "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" @@ -176,7 +178,7 @@ func newDeployment(ctx *Context, info *deploymentContext, opts deploymentOptions imp := &opts.imports[i] _, err := tokens.ParseTypeToken(imp.Type.String()) if err != nil { - return nil, errors.Errorf("import type %q is not a valid resource type token. "+ + return nil, fmt.Errorf("import type %q is not a valid resource type token. "+ "Type tokens must be of the format :: - "+ "refer to the import section of the provider resource documentation.", imp.Type.String()) } diff --git a/pkg/engine/journal.go b/pkg/engine/journal.go index fb6a5cdc0..edba421e0 100644 --- a/pkg/engine/journal.go +++ b/pkg/engine/journal.go @@ -1,7 +1,7 @@ package engine import ( - "github.com/pkg/errors" + "errors" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/secrets" diff --git a/pkg/engine/lifeycletest/pulumi_test.go b/pkg/engine/lifeycletest/pulumi_test.go index 8a70b6173..5284e5844 100644 --- a/pkg/engine/lifeycletest/pulumi_test.go +++ b/pkg/engine/lifeycletest/pulumi_test.go @@ -17,6 +17,7 @@ package lifecycletest import ( "context" + "errors" "flag" "fmt" "os" @@ -26,7 +27,7 @@ import ( "github.com/blang/semver" pbempty "github.com/golang/protobuf/ptypes/empty" - "github.com/pkg/errors" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -656,7 +657,7 @@ func TestStackReference(t *testing.T) { "foo": "bar", }), nil default: - return nil, errors.Errorf("unknown stack \"%s\"", name) + return nil, fmt.Errorf("unknown stack \"%s\"", name) } }, }, @@ -2104,7 +2105,7 @@ func (ctx *updateContext) Run(_ context.Context, req *pulumirpc.RunRequest) (*pu rpcutil.GrpcChannelOptions(), ) if err != nil { - return nil, errors.Wrapf(err, "could not connect to resource monitor") + return nil, fmt.Errorf("could not connect to resource monitor: %w", err) } defer contract.IgnoreClose(conn) diff --git a/pkg/engine/lifeycletest/target_test.go b/pkg/engine/lifeycletest/target_test.go index 64b3b7e44..c9ba4b1cc 100644 --- a/pkg/engine/lifeycletest/target_test.go +++ b/pkg/engine/lifeycletest/target_test.go @@ -33,15 +33,21 @@ func TestDestroyTarget(t *testing.T) { destroySpecificTargets( t, []string{"A"}, true, /*targetDependents*/ func(urns []resource.URN, deleted map[resource.URN]bool) { - // when deleting 'A' we expect A, B, C, E, F, and K to be deleted + // when deleting 'A' we expect A, B, C, D, E, F, G, H, I, J, K, and L to be deleted names := complexTestDependencyGraphNames assert.Equal(t, map[resource.URN]bool{ pickURN(t, urns, names, "A"): true, pickURN(t, urns, names, "B"): true, pickURN(t, urns, names, "C"): true, + pickURN(t, urns, names, "D"): true, pickURN(t, urns, names, "E"): true, pickURN(t, urns, names, "F"): true, + pickURN(t, urns, names, "G"): true, + pickURN(t, urns, names, "H"): true, + pickURN(t, urns, names, "I"): true, + pickURN(t, urns, names, "J"): true, pickURN(t, urns, names, "K"): true, + pickURN(t, urns, names, "L"): true, }, deleted) }) diff --git a/pkg/engine/plugin_host.go b/pkg/engine/plugin_host.go index 76ce626a3..70975d445 100644 --- a/pkg/engine/plugin_host.go +++ b/pkg/engine/plugin_host.go @@ -15,7 +15,8 @@ package engine import ( - "github.com/pkg/errors" + "fmt" + "google.golang.org/grpc" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" @@ -34,7 +35,7 @@ func connectToLanguageRuntime(ctx *plugin.Context, address string) (plugin.Host, conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithUnaryInterceptor(rpcutil.OpenTracingClientInterceptor()), rpcutil.GrpcChannelOptions()) if err != nil { - return nil, errors.Wrap(err, "could not connect to language host") + return nil, fmt.Errorf("could not connect to language host: %w", err) } client := pulumirpc.NewLanguageRuntimeClient(conn) diff --git a/pkg/engine/plugins.go b/pkg/engine/plugins.go index e9f380748..98b4f0d0a 100644 --- a/pkg/engine/plugins.go +++ b/pkg/engine/plugins.go @@ -20,7 +20,7 @@ import ( "sort" "github.com/blang/semver" - "github.com/pkg/errors" + "golang.org/x/sync/errgroup" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" @@ -183,8 +183,9 @@ func installPlugin(plugin workspace.PluginInfo) error { logging.V(preparePluginVerboseLog).Infof( "installPlugin(%s, %s): extracting tarball to installation directory", plugin.Name, plugin.Version) if err := plugin.Install(stream); err != nil { - return errors.Wrapf(err, "installing plugin; run `pulumi plugin install %s %s v%s` to retry manually", - plugin.Kind, plugin.Name, plugin.Version) + return fmt.Errorf("installing plugin; run `pulumi plugin install %s %s v%s` to retry manually: %w", + plugin.Kind, plugin.Name, plugin.Version, err) + } logging.V(7).Infof("installPlugin(%s, %s): successfully installed", plugin.Name, plugin.Version) diff --git a/pkg/engine/project.go b/pkg/engine/project.go index 0cc676bcc..0d5f51ccc 100644 --- a/pkg/engine/project.go +++ b/pkg/engine/project.go @@ -15,11 +15,10 @@ package engine import ( + "fmt" "os" "path/filepath" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" ) @@ -59,7 +58,7 @@ func getPwdMain(root, main string) (string, string, error) { // of the main program's parent directory. How we do this depends on if the target is a dir or not. maininfo, err := os.Stat(main) if err != nil { - return "", "", errors.Wrapf(err, "project 'main' could not be read") + return "", "", fmt.Errorf("project 'main' could not be read: %w", err) } if maininfo.IsDir() { pwd = main diff --git a/pkg/engine/update.go b/pkg/engine/update.go index e55ddb71b..6429c9234 100644 --- a/pkg/engine/update.go +++ b/pkg/engine/update.go @@ -17,6 +17,7 @@ package engine import ( "context" "encoding/json" + "errors" "fmt" "path/filepath" "sort" @@ -24,7 +25,7 @@ import ( "sync" "github.com/blang/semver" - "github.com/pkg/errors" + resourceanalyzer "github.com/pulumi/pulumi/pkg/v3/resource/analyzer" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" @@ -296,11 +297,11 @@ func installAndLoadPolicyPlugins(plugctx *plugin.Context, d diag.Sink, policies config, validationErrors, err := resourceanalyzer.ReconcilePolicyPackConfig( analyzerInfo.Policies, analyzerInfo.InitialConfig, configFromAPI) if err != nil { - return errors.Wrapf(err, "reconciling config for %q", analyzerInfo.Name) + return fmt.Errorf("reconciling config for %q: %w", analyzerInfo.Name, err) } appendValidationErrors(analyzerInfo.Name, analyzerInfo.Version, validationErrors) if err = analyzer.Configure(config); err != nil { - return errors.Wrapf(err, "configuring policy pack %q", analyzerInfo.Name) + return fmt.Errorf("configuring policy pack %q: %w", analyzerInfo.Name, err) } } @@ -315,7 +316,7 @@ func installAndLoadPolicyPlugins(plugctx *plugin.Context, d diag.Sink, policies if err != nil { return err } else if analyzer == nil { - return errors.Errorf("policy analyzer could not be loaded from path %q", pack.Path) + return fmt.Errorf("policy analyzer could not be loaded from path %q", pack.Path) } // Update the Policy Pack names now that we have loaded the plugins and can access the name. @@ -328,7 +329,7 @@ func installAndLoadPolicyPlugins(plugctx *plugin.Context, d diag.Sink, policies // Load config, reconcile & validate it, and pass it to the policy pack. if !analyzerInfo.SupportsConfig { if pack.Config != "" { - return errors.Errorf("policy pack %q at %q does not support config", analyzerInfo.Name, pack.Path) + return fmt.Errorf("policy pack %q at %q does not support config", analyzerInfo.Name, pack.Path) } continue } @@ -342,11 +343,11 @@ func installAndLoadPolicyPlugins(plugctx *plugin.Context, d diag.Sink, policies config, validationErrors, err := resourceanalyzer.ReconcilePolicyPackConfig( analyzerInfo.Policies, analyzerInfo.InitialConfig, configFromFile) if err != nil { - return errors.Wrapf(err, "reconciling policy config for %q at %q", analyzerInfo.Name, pack.Path) + return fmt.Errorf("reconciling policy config for %q at %q: %w", analyzerInfo.Name, pack.Path, err) } appendValidationErrors(analyzerInfo.Name, analyzerInfo.Version, validationErrors) if err = analyzer.Configure(config); err != nil { - return errors.Wrapf(err, "configuring policy pack %q at %q", analyzerInfo.Name, pack.Path) + return fmt.Errorf("configuring policy pack %q at %q: %w", analyzerInfo.Name, pack.Path, err) } } diff --git a/pkg/go.mod b/pkg/go.mod index d89faba9e..953d55c03 100644 --- a/pkg/go.mod +++ b/pkg/go.mod @@ -34,7 +34,6 @@ require ( github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d github.com/opentracing/opentracing-go v1.1.0 github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 - github.com/pkg/errors v0.9.1 github.com/pulumi/pulumi/sdk/v3 v3.17.1 github.com/rjeczalik/notify v0.9.2 github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 @@ -114,6 +113,8 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.6 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/mattn/go-isatty v0.0.12 // indirect @@ -128,8 +129,10 @@ require ( github.com/modern-go/reflect2 v1.0.1 // indirect github.com/opentracing/basictracer-go v1.0.0 // indirect github.com/pierrec/lz4 v2.6.0+incompatible // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.6.1 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 // indirect diff --git a/pkg/go.sum b/pkg/go.sum index d1ace4103..a1374692a 100644 --- a/pkg/go.sum +++ b/pkg/go.sum @@ -455,6 +455,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= @@ -580,11 +582,15 @@ github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/ryboe/q v1.0.15 h1:atR2S58tRbVv5+t+Kx5qf+VvT2rpXYQPGAn5QtyB5jc= +github.com/ryboe/q v1.0.15/go.mod h1:ecdh6eECsYWI/cWgtRaYjWb8fbz5YndR22B+xpAcHY8= github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0= github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ= github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= diff --git a/pkg/graph/topsort.go b/pkg/graph/topsort.go index 8146514ba..89190bffa 100644 --- a/pkg/graph/topsort.go +++ b/pkg/graph/topsort.go @@ -15,7 +15,7 @@ package graph import ( - "github.com/pkg/errors" + "errors" ) // Topsort topologically sorts the graph, yielding an array of nodes that are in dependency order, using a simple diff --git a/pkg/operations/operations_aws.go b/pkg/operations/operations_aws.go index c74d63a31..245cfe729 100644 --- a/pkg/operations/operations_aws.go +++ b/pkg/operations/operations_aws.go @@ -15,6 +15,8 @@ package operations import ( + "errors" + "fmt" "sort" "sync" "time" @@ -23,7 +25,6 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" @@ -140,7 +141,7 @@ func getAWSSession(awsRegion, awsAccessKey, awsSecretKey, token string) (*sessio if awsDefaultSession == nil { sess, err := session.NewSession() if err != nil { - return nil, errors.Wrap(err, "failed to create AWS session") + return nil, fmt.Errorf("failed to create AWS session: %w", err) } awsDefaultSession = sess diff --git a/pkg/operations/operations_gcp.go b/pkg/operations/operations_gcp.go index 899724823..d210a0cd7 100644 --- a/pkg/operations/operations_gcp.go +++ b/pkg/operations/operations_gcp.go @@ -27,7 +27,6 @@ import ( "google.golang.org/api/option" loggingpb "google.golang.org/genproto/googleapis/logging/v2" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" @@ -147,10 +146,10 @@ func getLogEntryMessage(e *loggingpb.LogEntry) (string, error) { case *loggingpb.LogEntry_JsonPayload: byts, err := json.Marshal(payload.JsonPayload) if err != nil { - return "", errors.Wrap(err, "encoding to JSON") + return "", fmt.Errorf("encoding to JSON: %w", err) } return string(byts), nil default: - return "", errors.Errorf("can't decode entry of type %s", reflect.TypeOf(e)) + return "", fmt.Errorf("can't decode entry of type %s", reflect.TypeOf(e)) } } diff --git a/pkg/resource/analyzer/config.go b/pkg/resource/analyzer/config.go index 99a51c96b..8d5d1aa6b 100644 --- a/pkg/resource/analyzer/config.go +++ b/pkg/resource/analyzer/config.go @@ -16,11 +16,11 @@ package analyzer import ( "encoding/json" + "errors" "fmt" "io/ioutil" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" @@ -54,7 +54,7 @@ func ParsePolicyPackConfigFromAPI(config map[string]*json.RawMessage) (map[strin el, err := extractEnforcementLevel(props) if err != nil { - return nil, errors.Wrapf(err, "parsing enforcement level for %q", k) + return nil, fmt.Errorf("parsing enforcement level for %q: %w", k, err) } enforcementLevel = el if len(props) > 0 { @@ -93,21 +93,20 @@ func parsePolicyPackConfig(b []byte) (map[string]plugin.AnalyzerPolicyConfig, er case string: el := apitype.EnforcementLevel(val) if !el.IsValid() { - return nil, errors.Errorf( - "parsing enforcement level for %q: %q is not a valid enforcement level", k, val) + return nil, fmt.Errorf("parsing enforcement level for %q: %q is not a valid enforcement level", k, val) } enforcementLevel = el case map[string]interface{}: el, err := extractEnforcementLevel(val) if err != nil { - return nil, errors.Wrapf(err, "parsing enforcement level for %q", k) + return nil, fmt.Errorf("parsing enforcement level for %q: %w", k, err) } enforcementLevel = el if len(val) > 0 { properties = val } default: - return nil, errors.Errorf("parsing %q: %v is not a valid value; must be a string or object", k, v) + return nil, fmt.Errorf("parsing %q: %v is not a valid value; must be a string or object", k, v) } // Don't bother including empty configs. @@ -132,11 +131,11 @@ func extractEnforcementLevel(props map[string]interface{}) (apitype.EnforcementL if unknown, ok := props["enforcementLevel"]; ok { enforcementLevelStr, isStr := unknown.(string) if !isStr { - return "", errors.Errorf("%v is not a valid enforcement level; must be a string", unknown) + return "", fmt.Errorf("%v is not a valid enforcement level; must be a string", unknown) } el := apitype.EnforcementLevel(enforcementLevelStr) if !el.IsValid() { - return "", errors.Errorf("%q is not a valid enforcement level", enforcementLevelStr) + return "", fmt.Errorf("%q is not a valid enforcement level", enforcementLevelStr) } enforcementLevel = el // Remove enforcementLevel from the map. @@ -208,7 +207,7 @@ func ValidatePolicyPackConfig(schemaMap map[string]apitype.PolicyConfigSchema, configLoader := gojsonschema.NewBytesLoader(*propertyConfig) result, err := gojsonschema.Validate(schemaLoader, configLoader) if err != nil { - return errors.Wrap(err, "unable to validate schema") + return fmt.Errorf("unable to validate schema: %w", err) } // If the result is invalid, we need to gather the errors to return to the user. diff --git a/pkg/resource/deploy/builtins.go b/pkg/resource/deploy/builtins.go index d769dfe35..4b1e04da2 100644 --- a/pkg/resource/deploy/builtins.go +++ b/pkg/resource/deploy/builtins.go @@ -2,11 +2,11 @@ package deploy import ( "context" + "errors" "fmt" "sort" uuid "github.com/gofrs/uuid" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" @@ -70,7 +70,7 @@ func (p *builtinProvider) Check(urn resource.URN, state, inputs resource.Propert typ := urn.Type() if typ != stackReferenceType { - return nil, nil, errors.Errorf("unrecognized resource type '%v'", urn.Type()) + return nil, nil, fmt.Errorf("unrecognized resource type '%v'", urn.Type()) } var name resource.PropertyValue @@ -193,7 +193,7 @@ func (p *builtinProvider) Invoke(tok tokens.ModuleMember, } return outs, nil, nil default: - return nil, nil, errors.Errorf("unrecognized function name: '%v'", tok) + return nil, nil, fmt.Errorf("unrecognized function name: '%v'", tok) } } @@ -280,7 +280,7 @@ func (p *builtinProvider) getResource(inputs resource.PropertyMap) (resource.Pro state, ok := p.resources.get(resource.URN(urn.StringValue())) if !ok { - return nil, errors.Errorf("unknown resource %v", urn.StringValue()) + return nil, fmt.Errorf("unknown resource %v", urn.StringValue()) } return resource.PropertyMap{ diff --git a/pkg/resource/deploy/deployment.go b/pkg/resource/deploy/deployment.go index f27488915..696dba5f9 100644 --- a/pkg/resource/deploy/deployment.go +++ b/pkg/resource/deploy/deployment.go @@ -16,12 +16,12 @@ package deploy import ( "context" + "fmt" "math" "sync" "github.com/blang/semver" uuid "github.com/gofrs/uuid" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/codegen/schema" "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" @@ -230,7 +230,7 @@ func addDefaultProviders(target *Target, source Source, prev *Snapshot) error { if !ok { inputs, err := target.GetPackageConfig(pkg) if err != nil { - return errors.Errorf("could not fetch configuration for default provider '%v'", pkg) + return fmt.Errorf("could not fetch configuration for default provider '%v'", pkg) } if version, ok := defaultProviderVersions[pkg]; ok { inputs["version"] = resource.NewStringProperty(version.String()) @@ -314,7 +314,7 @@ func buildResourceMap(prev *Snapshot, preview bool) ([]*resource.State, map[reso urn := oldres.URN if olds[urn] != nil { - return nil, nil, errors.Errorf("unexpected duplicate resource '%s'", urn) + return nil, nil, fmt.Errorf("unexpected duplicate resource '%s'", urn) } olds[urn] = oldres } diff --git a/pkg/resource/deploy/deployment_executor.go b/pkg/resource/deploy/deployment_executor.go index 5d992dcf9..a52df7d4e 100644 --- a/pkg/resource/deploy/deployment_executor.go +++ b/pkg/resource/deploy/deployment_executor.go @@ -16,10 +16,10 @@ package deploy import ( "context" + "errors" "fmt" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" "github.com/pulumi/pulumi/pkg/v3/resource/graph" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" diff --git a/pkg/resource/deploy/deploytest/pluginhost.go b/pkg/resource/deploy/deploytest/pluginhost.go index 3b979a1c5..5cb95c1b5 100644 --- a/pkg/resource/deploy/deploytest/pluginhost.go +++ b/pkg/resource/deploy/deploytest/pluginhost.go @@ -16,13 +16,14 @@ package deploytest import ( "context" + "errors" "fmt" "io" "sync" "github.com/blang/semver" pbempty "github.com/golang/protobuf/ptypes/empty" - "github.com/pkg/errors" + "google.golang.org/grpc" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" @@ -148,7 +149,7 @@ func (e *hostEngine) Log(_ context.Context, req *pulumirpc.LogRequest) (*pbempty case pulumirpc.LogSeverity_ERROR: sev = diag.Error default: - return nil, errors.Errorf("Unrecognized logging severity: %v", req.Severity) + return nil, fmt.Errorf("Unrecognized logging severity: %v", req.Severity) } if req.Ephemeral { diff --git a/pkg/resource/deploy/deploytest/resourcemonitor.go b/pkg/resource/deploy/deploytest/resourcemonitor.go index 05cafdbf3..9d2862391 100644 --- a/pkg/resource/deploy/deploytest/resourcemonitor.go +++ b/pkg/resource/deploy/deploytest/resourcemonitor.go @@ -18,7 +18,6 @@ import ( "context" "fmt" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" @@ -44,7 +43,7 @@ func dialMonitor(ctx context.Context, endpoint string) (*ResourceMonitor, error) rpcutil.GrpcChannelOptions(), ) if err != nil { - return nil, errors.Wrapf(err, "could not connect to resource monitor") + return nil, fmt.Errorf("could not connect to resource monitor: %w", err) } resmon := pulumirpc.NewResourceMonitorClient(conn) diff --git a/pkg/resource/deploy/providers/reference.go b/pkg/resource/deploy/providers/reference.go index 1fb856491..83eec15ea 100644 --- a/pkg/resource/deploy/providers/reference.go +++ b/pkg/resource/deploy/providers/reference.go @@ -15,10 +15,10 @@ package providers import ( + "errors" + "fmt" "strings" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" @@ -59,11 +59,11 @@ func GetProviderPackage(typ tokens.Type) tokens.Package { func validateURN(urn resource.URN) error { if !urn.IsValid() { - return errors.Errorf("%s is not a valid URN", urn) + return fmt.Errorf("%s is not a valid URN", urn) } typ := urn.Type() if typ.Module() != "pulumi:providers" { - return errors.Errorf("invalid module in type: expected 'pulumi:providers', got '%v'", typ.Module()) + return fmt.Errorf("invalid module in type: expected 'pulumi:providers', got '%v'", typ.Module()) } if typ.Name() == "" { return errors.New("provider URNs must specify a type name") @@ -117,7 +117,7 @@ func ParseReference(s string) (Reference, error) { // of the reference. lastSep := strings.LastIndex(s, resource.URNNameDelimiter) if lastSep == -1 { - return Reference{}, errors.Errorf("expected '%v' in provider reference '%v'", resource.URNNameDelimiter, s) + return Reference{}, fmt.Errorf("expected '%v' in provider reference '%v'", resource.URNNameDelimiter, s) } urn, id := resource.URN(s[:lastSep]), resource.ID(s[lastSep+len(resource.URNNameDelimiter):]) if err := validateURN(urn); err != nil { diff --git a/pkg/resource/deploy/providers/registry.go b/pkg/resource/deploy/providers/registry.go index 4541a1413..6962ddc8f 100644 --- a/pkg/resource/deploy/providers/registry.go +++ b/pkg/resource/deploy/providers/registry.go @@ -15,12 +15,12 @@ package providers import ( + "errors" "fmt" "sync" "github.com/blang/semver" uuid "github.com/gofrs/uuid" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" @@ -44,7 +44,7 @@ func GetProviderVersion(inputs resource.PropertyMap) (*semver.Version, error) { sv, err := semver.ParseTolerant(versionProp.StringValue()) if err != nil { - return nil, errors.Errorf("could not parse provider version: %v", err) + return nil, fmt.Errorf("could not parse provider version: %v", err) } return &sv, nil } @@ -104,13 +104,13 @@ func NewRegistry(host plugin.Host, prev []*resource.State, isPreview bool, // Ensure that this provider has a known ID. if res.ID == "" || res.ID == UnknownID { - return nil, errors.Errorf("provider '%v' has an unknown ID", urn) + return nil, fmt.Errorf("provider '%v' has an unknown ID", urn) } // Ensure that we have no duplicates. ref := mustNewReference(urn, res.ID) if _, ok := r.providers[ref]; ok { - return nil, errors.Errorf("duplicate provider found in old state: '%v'", ref) + return nil, fmt.Errorf("duplicate provider found in old state: '%v'", ref) } providerPkg := GetProviderPackage(urn.Type()) @@ -118,19 +118,19 @@ func NewRegistry(host plugin.Host, prev []*resource.State, isPreview bool, // Parse the provider version, then load, configure, and register the provider. version, err := GetProviderVersion(res.Inputs) if err != nil { - return nil, errors.Errorf("could not parse version for %v provider '%v': %v", providerPkg, urn, err) + return nil, fmt.Errorf("could not parse version for %v provider '%v': %v", providerPkg, urn, err) } provider, err := loadProvider(providerPkg, version, host, builtins) if err != nil { - return nil, errors.Errorf("could not load plugin for %v provider '%v': %v", providerPkg, urn, err) + return nil, fmt.Errorf("could not load plugin for %v provider '%v': %v", providerPkg, urn, err) } if provider == nil { - return nil, errors.Errorf("could not find plugin for %v provider '%v' at version %v", providerPkg, urn, version) + return nil, fmt.Errorf("could not find plugin for %v provider '%v' at version %v", providerPkg, urn, version) } if err := provider.Configure(res.Inputs); err != nil { closeErr := host.CloseProvider(provider) contract.IgnoreError(closeErr) - return nil, errors.Errorf("could not configure provider '%v': %v", urn, err) + return nil, fmt.Errorf("could not configure provider '%v': %v", urn, err) } logging.V(7).Infof("loaded provider %v", ref) diff --git a/pkg/resource/deploy/providers/registry_test.go b/pkg/resource/deploy/providers/registry_test.go index 08d729881..5cb82be89 100644 --- a/pkg/resource/deploy/providers/registry_test.go +++ b/pkg/resource/deploy/providers/registry_test.go @@ -15,11 +15,12 @@ package providers import ( + "errors" "fmt" "testing" "github.com/blang/semver" - "github.com/pkg/errors" + "github.com/stretchr/testify/assert" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" diff --git a/pkg/resource/deploy/snapshot.go b/pkg/resource/deploy/snapshot.go index 86e12513b..536bf1af1 100644 --- a/pkg/resource/deploy/snapshot.go +++ b/pkg/resource/deploy/snapshot.go @@ -19,8 +19,6 @@ import ( "fmt" "time" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" "github.com/pulumi/pulumi/pkg/v3/secrets" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" @@ -109,7 +107,7 @@ func (snap *Snapshot) NormalizeURNReferences() error { // same resource multiple times. That's fine, only error if we see the same alias, // but it maps to *different* resources. if otherUrn, has := aliased[alias]; has && otherUrn != state.URN { - return errors.Errorf("Two resources ('%s' and '%s') aliased to the same: '%s'", otherUrn, state.URN, alias) + return fmt.Errorf("Two resources ('%s' and '%s') aliased to the same: '%s'", otherUrn, state.URN, alias) } aliased[alias] = state.URN } @@ -133,7 +131,7 @@ func (snap *Snapshot) VerifyIntegrity() error { if snap != nil { // Ensure the magic cookie checks out. if snap.Manifest.Magic != snap.Manifest.NewMagic() { - return errors.Errorf("magic cookie mismatch; possible tampering/corruption detected") + return fmt.Errorf("magic cookie mismatch; possible tampering/corruption detected") } // Now check the resources. For now, we just verify that parents come before children, and that there aren't @@ -146,17 +144,17 @@ func (snap *Snapshot) VerifyIntegrity() error { if providers.IsProviderType(state.Type) { ref, err := providers.NewReference(urn, state.ID) if err != nil { - return errors.Errorf("provider %s is not referenceable: %v", urn, err) + return fmt.Errorf("provider %s is not referenceable: %v", urn, err) } provs[ref] = struct{}{} } if provider := state.Provider; provider != "" { ref, err := providers.ParseReference(provider) if err != nil { - return errors.Errorf("failed to parse provider reference for resource %s: %v", urn, err) + return fmt.Errorf("failed to parse provider reference for resource %s: %v", urn, err) } if _, has := provs[ref]; !has { - return errors.Errorf("resource %s refers to unknown provider %s", urn, ref) + return fmt.Errorf("resource %s refers to unknown provider %s", urn, ref) } } @@ -166,10 +164,10 @@ func (snap *Snapshot) VerifyIntegrity() error { // whether it comes later in the snapshot (neither of which should ever happen). for _, other := range snap.Resources[i+1:] { if other.URN == par { - return errors.Errorf("child resource %s's parent %s comes after it", urn, par) + return fmt.Errorf("child resource %s's parent %s comes after it", urn, par) } } - return errors.Errorf("child resource %s refers to missing parent %s", urn, par) + return fmt.Errorf("child resource %s refers to missing parent %s", urn, par) } } @@ -178,17 +176,17 @@ func (snap *Snapshot) VerifyIntegrity() error { // same as above - doing this for better error messages for _, other := range snap.Resources[i+1:] { if other.URN == dep { - return errors.Errorf("resource %s's dependency %s comes after it", urn, other.URN) + return fmt.Errorf("resource %s's dependency %s comes after it", urn, other.URN) } } - return errors.Errorf("resource %s dependency %s refers to missing resource", urn, dep) + return fmt.Errorf("resource %s dependency %s refers to missing resource", urn, dep) } } if _, has := urns[urn]; has && !state.Delete { // The only time we should have duplicate URNs is when all but one of them are marked for deletion. - return errors.Errorf("duplicate resource %s (not marked for deletion)", urn) + return fmt.Errorf("duplicate resource %s (not marked for deletion)", urn) } urns[urn] = state diff --git a/pkg/resource/deploy/source_eval.go b/pkg/resource/deploy/source_eval.go index 168808db9..aed2ba62a 100644 --- a/pkg/resource/deploy/source_eval.go +++ b/pkg/resource/deploy/source_eval.go @@ -16,6 +16,7 @@ package deploy import ( "context" + "errors" "fmt" "time" @@ -23,7 +24,7 @@ import ( pbempty "github.com/golang/protobuf/ptypes/empty" "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" opentracing "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" + "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -96,7 +97,7 @@ func (src *evalSource) Iterate( // Decrypt the configuration. config, err := src.runinfo.Target.Config.Decrypt(src.runinfo.Target.Decrypter) if err != nil { - return nil, result.FromError(errors.Wrap(err, "failed to decrypt config")) + return nil, result.FromError(fmt.Errorf("failed to decrypt config: %w", err)) } // Keep track of any config keys that have secure values. @@ -109,7 +110,7 @@ func (src *evalSource) Iterate( mon, err := newResourceMonitor( src, providers, regChan, regOutChan, regReadChan, opts, config, configSecretKeys, tracingSpan) if err != nil { - return nil, result.FromError(errors.Wrap(err, "failed to start resource monitor")) + return nil, result.FromError(fmt.Errorf("failed to start resource monitor: %w", err)) } // Create a new iterator with appropriate channels, and gear up to go! @@ -197,7 +198,7 @@ func (iter *evalSourceIterator) forkRun(opts Options, config map[config.Key]stri rt := iter.src.runinfo.Proj.Runtime.Name() langhost, err := iter.src.plugctx.Host.LanguageRuntime(rt) if err != nil { - return result.FromError(errors.Wrapf(err, "failed to launch language host %s", rt)) + return result.FromError(fmt.Errorf("failed to launch language host %s: %w", rt, err)) } contract.Assertf(langhost != nil, "expected non-nil language host %s", rt) @@ -226,7 +227,7 @@ func (iter *evalSourceIterator) forkRun(opts Options, config map[config.Key]stri if err == nil && progerr != "" { // If the program had an unhandled error; propagate it to the caller. - err = errors.Errorf("an unhandled error occurred: %v", progerr) + err = fmt.Errorf("an unhandled error occurred: %v", progerr) } return result.WrapIfNonNil(err) } @@ -486,7 +487,7 @@ func getProviderReference(defaultProviders *defaultProviders, req providers.Prov if rawProviderRef != "" { ref, err := providers.ParseReference(rawProviderRef) if err != nil { - return providers.Reference{}, errors.Errorf("could not parse provider reference: %v", err) + return providers.Reference{}, fmt.Errorf("could not parse provider reference: %v", err) } return ref, nil } @@ -511,7 +512,7 @@ func getProviderFromSource( } provider, ok := providers.GetProvider(providerRef) if !ok { - return nil, errors.Errorf("unknown provider '%v'", rawProviderRef) + return nil, fmt.Errorf("unknown provider '%v'", rawProviderRef) } return provider, nil } @@ -575,14 +576,14 @@ func (rm *resmon) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) (*pu KeepResources: true, }) if err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal %v args", tok) + return nil, fmt.Errorf("failed to unmarshal %v args: %w", tok, err) } // Do the invoke and then return the arguments. logging.V(5).Infof("ResourceMonitor.Invoke received: tok=%v #args=%v", tok, len(args)) ret, failures, err := prov.Invoke(tok, args) if err != nil { - return nil, errors.Wrapf(err, "invocation of %v returned an error", tok) + return nil, fmt.Errorf("invocation of %v returned an error: %w", tok, err) } mret, err := plugin.MarshalProperties(ret, plugin.MarshalOptions{ Label: label, @@ -590,7 +591,7 @@ func (rm *resmon) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) (*pu KeepResources: true, }) if err != nil { - return nil, errors.Wrapf(err, "failed to marshal %v return", tok) + return nil, fmt.Errorf("failed to marshal %v return: %w", tok, err) } var chkfails []*pulumirpc.CheckFailure for _, failure := range failures { @@ -628,7 +629,7 @@ func (rm *resmon) Call(ctx context.Context, req *pulumirpc.CallRequest) (*pulumi KeepOutputValues: true, }) if err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal %v args", tok) + return nil, fmt.Errorf("failed to unmarshal %v args: %w", tok, err) } argDependencies := map[resource.PropertyKey][]resource.URN{} @@ -657,7 +658,7 @@ func (rm *resmon) Call(ctx context.Context, req *pulumirpc.CallRequest) (*pulumi "ResourceMonitor.Call received: tok=%v #args=%v #info=%v #options=%v", tok, len(args), info, options) ret, err := prov.Call(tok, args, info, options) if err != nil { - return nil, errors.Wrapf(err, "call of %v returned an error", tok) + return nil, fmt.Errorf("call of %v returned an error: %w", tok, err) } mret, err := plugin.MarshalProperties(ret.Return, plugin.MarshalOptions{ Label: label, @@ -666,7 +667,7 @@ func (rm *resmon) Call(ctx context.Context, req *pulumirpc.CallRequest) (*pulumi KeepResources: true, }) if err != nil { - return nil, errors.Wrapf(err, "failed to marshal %v return", tok) + return nil, fmt.Errorf("failed to marshal %v return: %w", tok, err) } returnDependencies := map[string]*pulumirpc.CallResponse_ReturnDependencies{} @@ -711,7 +712,7 @@ func (rm *resmon) StreamInvoke( KeepResources: true, }) if err != nil { - return errors.Wrapf(err, "failed to unmarshal %v args", tok) + return fmt.Errorf("failed to unmarshal %v args: %w", tok, err) } // Synchronously do the StreamInvoke and then return the arguments. This will block until the @@ -724,13 +725,13 @@ func (rm *resmon) StreamInvoke( KeepResources: req.GetAcceptResources(), }) if err != nil { - return errors.Wrapf(err, "failed to marshal return") + return fmt.Errorf("failed to marshal return: %w", err) } return stream.Send(&pulumirpc.InvokeResponse{Return: mret}) }) if err != nil { - return errors.Wrapf(err, "streaming invocation of %v returned an error", tok) + return fmt.Errorf("streaming invocation of %v returned an error: %w", tok, err) } var chkfails []*pulumirpc.CheckFailure @@ -829,7 +830,7 @@ func (rm *resmon) ReadResource(ctx context.Context, KeepResources: req.GetAcceptResources(), }) if err != nil { - return nil, errors.Wrapf(err, "failed to marshal %s return state", result.State.URN) + return nil, fmt.Errorf("failed to marshal %s return state: %w", result.State.URN, err) } return &pulumirpc.ReadResourceResponse{ @@ -988,7 +989,7 @@ func (rm *resmon) RegisterResource(ctx context.Context, if remote { provider, ok := rm.providers.GetProvider(providerRef) if !ok { - return nil, errors.Errorf("unknown provider '%v'", providerRef) + return nil, fmt.Errorf("unknown provider '%v'", providerRef) } // Invoke the provider's Construct RPC method. @@ -1108,7 +1109,7 @@ func (rm *resmon) RegisterResourceOutputs(ctx context.Context, KeepResources: true, }) if err != nil { - return nil, errors.Wrapf(err, "cannot unmarshal output properties") + return nil, fmt.Errorf("cannot unmarshal output properties: %w", err) } logging.V(5).Infof("ResourceMonitor.RegisterResourceOutputs received: urn=%v, #outs=%v", urn, len(outs)) @@ -1213,7 +1214,7 @@ func (g *readResourceEvent) Done(result *ReadResult) { func generateTimeoutInSeconds(timeout string) (float64, error) { duration, err := time.ParseDuration(timeout) if err != nil { - return 0, errors.Errorf("unable to parse customTimeout Value %s", timeout) + return 0, fmt.Errorf("unable to parse customTimeout Value %s", timeout) } return duration.Seconds(), nil diff --git a/pkg/resource/deploy/source_query.go b/pkg/resource/deploy/source_query.go index 1295fbe41..f8145a0b7 100644 --- a/pkg/resource/deploy/source_query.go +++ b/pkg/resource/deploy/source_query.go @@ -22,7 +22,7 @@ import ( "github.com/blang/semver" pbempty "github.com/golang/protobuf/ptypes/empty" opentracing "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" + "google.golang.org/grpc" "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" @@ -53,7 +53,7 @@ func NewQuerySource(cancel context.Context, plugctx *plugin.Context, client Back reg, err := providers.NewRegistry(plugctx.Host, nil, false, builtins) if err != nil { - return nil, errors.Wrapf(err, "failed to start resource monitor") + return nil, fmt.Errorf("failed to start resource monitor: %w", err) } // Allows queryResmon to communicate errors loading providers. @@ -67,7 +67,7 @@ func NewQuerySource(cancel context.Context, plugctx *plugin.Context, client Back mon, err := newQueryResourceMonitor(builtins, defaultProviderVersions, provs, reg, plugctx, providerRegErrChan, opentracing.SpanFromContext(cancel), runinfo) if err != nil { - return nil, errors.Wrap(err, "failed to start resource monitor") + return nil, fmt.Errorf("failed to start resource monitor: %w", err) } // Create a new iterator with appropriate channels, and gear up to go! @@ -144,7 +144,7 @@ func runLangPlugin(src *querySource) result.Result { rt := src.runinfo.Proj.Runtime.Name() langhost, err := src.plugctx.Host.LanguageRuntime(rt) if err != nil { - return result.FromError(errors.Wrapf(err, "failed to launch language host %s", rt)) + return result.FromError(fmt.Errorf("failed to launch language host %s: %w", rt, err)) } contract.Assertf(langhost != nil, "expected non-nil language host %s", rt) @@ -187,7 +187,7 @@ func runLangPlugin(src *querySource) result.Result { if err == nil && progerr != "" { // If the program had an unhandled error; propagate it to the caller. - err = errors.Errorf("an unhandled error occurred: %v", progerr) + err = fmt.Errorf("an unhandled error occurred: %v", progerr) } return result.WrapIfNonNil(err) } @@ -342,14 +342,14 @@ func (rm *queryResmon) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) KeepResources: true, }) if err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal %v args", tok) + return nil, fmt.Errorf("failed to unmarshal %v args: %w", tok, err) } // Do the invoke and then return the arguments. logging.V(5).Infof("QueryResourceMonitor.Invoke received: tok=%v #args=%v", tok, len(args)) ret, failures, err := prov.Invoke(tok, args) if err != nil { - return nil, errors.Wrapf(err, "invocation of %v returned an error", tok) + return nil, fmt.Errorf("invocation of %v returned an error: %w", tok, err) } mret, err := plugin.MarshalProperties(ret, plugin.MarshalOptions{ Label: label, @@ -357,7 +357,7 @@ func (rm *queryResmon) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) KeepResources: req.GetAcceptResources(), }) if err != nil { - return nil, errors.Wrapf(err, "failed to marshal return") + return nil, fmt.Errorf("failed to marshal return: %w", err) } var chkfails []*pulumirpc.CheckFailure @@ -389,7 +389,7 @@ func (rm *queryResmon) StreamInvoke( args, err := plugin.UnmarshalProperties( req.GetArgs(), plugin.MarshalOptions{Label: label, KeepUnknowns: true}) if err != nil { - return errors.Wrapf(err, "failed to unmarshal %v args", tok) + return fmt.Errorf("failed to unmarshal %v args: %w", tok, err) } // Synchronously do the StreamInvoke and then return the arguments. This will block until the @@ -398,13 +398,13 @@ func (rm *queryResmon) StreamInvoke( failures, err := prov.StreamInvoke(tok, args, func(event resource.PropertyMap) error { mret, err := plugin.MarshalProperties(event, plugin.MarshalOptions{Label: label, KeepUnknowns: true}) if err != nil { - return errors.Wrapf(err, "failed to marshal return") + return fmt.Errorf("failed to marshal return: %w", err) } return stream.Send(&pulumirpc.InvokeResponse{Return: mret}) }) if err != nil { - return errors.Wrapf(err, "streaming invocation of %v returned an error", tok) + return fmt.Errorf("streaming invocation of %v returned an error: %w", tok, err) } var chkfails []*pulumirpc.CheckFailure @@ -443,7 +443,7 @@ func (rm *queryResmon) Call(ctx context.Context, req *pulumirpc.CallRequest) (*p KeepResources: true, }) if err != nil { - return nil, errors.Wrapf(err, "failed to unmarshal %v args", tok) + return nil, fmt.Errorf("failed to unmarshal %v args: %w", tok, err) } argDependencies := map[resource.PropertyKey][]resource.URN{} @@ -463,7 +463,7 @@ func (rm *queryResmon) Call(ctx context.Context, req *pulumirpc.CallRequest) (*p "QueryResourceMonitor.Call received: tok=%v #args=%v #info=%v #options=%v", tok, len(args), rm.callInfo, options) ret, err := prov.Call(tok, args, rm.callInfo, options) if err != nil { - return nil, errors.Wrapf(err, "call of %v returned an error", tok) + return nil, fmt.Errorf("call of %v returned an error: %w", tok, err) } mret, err := plugin.MarshalProperties(ret.Return, plugin.MarshalOptions{ Label: label, @@ -472,7 +472,7 @@ func (rm *queryResmon) Call(ctx context.Context, req *pulumirpc.CallRequest) (*p KeepResources: true, }) if err != nil { - return nil, errors.Wrapf(err, "failed to marshal return") + return nil, fmt.Errorf("failed to marshal return: %w", err) } returnDependencies := map[string]*pulumirpc.CallResponse_ReturnDependencies{} diff --git a/pkg/resource/deploy/step.go b/pkg/resource/deploy/step.go index a550dfd65..9cff4d60f 100644 --- a/pkg/resource/deploy/step.go +++ b/pkg/resource/deploy/step.go @@ -15,11 +15,10 @@ package deploy import ( + "errors" "fmt" "strings" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" @@ -129,8 +128,8 @@ func (s *SameStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error if providers.IsProviderType(s.new.Type) { ref, err := providers.NewReference(s.new.URN, s.new.ID) if err != nil { - return resource.StatusOK, nil, errors.Errorf( - "bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err) + return resource.StatusOK, nil, + fmt.Errorf("bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err) } if s.Deployment() != nil { s.Deployment().SameProvider(ref) @@ -338,12 +337,11 @@ func (s *DeleteStep) Logical() bool { return !s.replacing } func (s *DeleteStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { // Refuse to delete protected resources. if s.old.Protect { - return resource.StatusOK, nil, - errors.Errorf("unable to delete resource %q\n"+ - "as it is currently marked for protection. To unprotect the resource, "+ - "either remove the `protect` flag from the resource in your Pulumi"+ - "program and run `pulumi up` or use the command:\n"+ - "`pulumi state unprotect %s`", s.old.URN, s.old.URN) + return resource.StatusOK, nil, fmt.Errorf("unable to delete resource %q\n"+ + "as it is currently marked for protection. To unprotect the resource, "+ + "either remove the `protect` flag from the resource in your Pulumi"+ + "program and run `pulumi up` or use the command:\n"+ + "`pulumi state unprotect %s`", s.old.URN, s.old.URN) } // Deleting an External resource is a no-op, since Pulumi does not own the lifecycle. @@ -650,7 +648,7 @@ func (s *ReadStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error // If there is no such resource, return an error indicating as such. if result.Outputs == nil { - return resource.StatusOK, nil, errors.Errorf("resource '%s' does not exist", id) + return resource.StatusOK, nil, fmt.Errorf("resource '%s' does not exist", id) } s.new.Outputs = result.Outputs @@ -875,11 +873,11 @@ func (s *ImportStep) Apply(preview bool) (resource.Status, StepCompleteFunc, err // If this is a planned import, ensure that the resource does not exist in the old state file. if s.planned { if _, ok := s.deployment.olds[s.new.URN]; ok { - return resource.StatusOK, nil, errors.Errorf("resource '%v' already exists", s.new.URN) + return resource.StatusOK, nil, fmt.Errorf("resource '%v' already exists", s.new.URN) } if s.new.Parent.Type() != resource.RootStackType { if _, ok := s.deployment.olds[s.new.Parent]; !ok { - return resource.StatusOK, nil, errors.Errorf("unknown parent '%v' for resource '%v'", + return resource.StatusOK, nil, fmt.Errorf("unknown parent '%v' for resource '%v'", s.new.Parent, s.new.URN) } } @@ -900,12 +898,12 @@ func (s *ImportStep) Apply(preview bool) (resource.Status, StepCompleteFunc, err } } if read.Outputs == nil { - return rst, nil, errors.Errorf("resource '%v' does not exist", s.new.ID) + return rst, nil, fmt.Errorf("resource '%v' does not exist", s.new.ID) } if read.Inputs == nil { - return resource.StatusOK, nil, errors.Errorf( - "provider does not support importing resources; please try updating the '%v' plugin", - s.new.URN.Type().Package()) + return resource.StatusOK, nil, + fmt.Errorf("provider does not support importing resources; please try updating the '%v' plugin", + s.new.URN.Type().Package()) } if read.ID != "" { s.new.ID = read.ID @@ -925,12 +923,12 @@ func (s *ImportStep) Apply(preview bool) (resource.Status, StepCompleteFunc, err pkg, err := s.deployment.schemaLoader.LoadPackage(string(s.new.Type.Package()), nil) if err != nil { - return resource.StatusOK, nil, errors.Wrapf(err, "failed to fetch provider schema") + return resource.StatusOK, nil, fmt.Errorf("failed to fetch provider schema: %w", err) } r, ok := pkg.GetResource(string(s.new.Type)) if !ok { - return resource.StatusOK, nil, errors.Errorf("unknown resource type '%v'", s.new.Type) + return resource.StatusOK, nil, fmt.Errorf("unknown resource type '%v'", s.new.Type) } for _, p := range r.InputProperties { if p.IsRequired() { @@ -1195,11 +1193,11 @@ func getProvider(s Step) (plugin.Provider, error) { } ref, err := providers.ParseReference(s.Provider()) if err != nil { - return nil, errors.Errorf("bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err) + return nil, fmt.Errorf("bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err) } provider, ok := s.Deployment().GetProvider(ref) if !ok { - return nil, errors.Errorf("unknown provider '%v' for resource %v", s.Provider(), s.URN()) + return nil, fmt.Errorf("unknown provider '%v' for resource %v", s.Provider(), s.URN()) } return provider, nil } diff --git a/pkg/resource/deploy/step_executor.go b/pkg/resource/deploy/step_executor.go index b51bdb5fe..479d67d49 100644 --- a/pkg/resource/deploy/step_executor.go +++ b/pkg/resource/deploy/step_executor.go @@ -16,11 +16,11 @@ package deploy import ( "context" + "errors" "fmt" "sync" "sync/atomic" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" @@ -172,7 +172,7 @@ func (se *stepExecutor) ExecuteRegisterResourceOutputs(e RegisterResourceOutputs // clients of stepExecutor to do work on worker threads by e.g. scheduling arbitrary callbacks // or 2) promote RRE to be step-like so that it can be scheduled as if it were a step. Neither // of these are particularly appealing right now. - outErr := errors.Wrap(eventerr, "resource complete event returned an error") + outErr := fmt.Errorf("resource complete event returned an error: %w", eventerr) diagMsg := diag.RawMessage(reg.URN(), outErr.Error()) se.deployment.Diag().Errorf(diagMsg) se.cancelDueToError() @@ -263,7 +263,7 @@ func (se *stepExecutor) executeStep(workerID int, step Step) error { payload, err = events.OnResourceStepPre(step) if err != nil { se.log(workerID, "step %v on %v failed pre-resource step: %v", step.Op(), step.URN(), err) - return errors.Wrap(err, "pre-step event returned an error") + return fmt.Errorf("pre-step event returned an error: %w", err) } } @@ -274,8 +274,7 @@ func (se *stepExecutor) executeStep(workerID int, step Step) error { // If we have a state object, and this is a create or update, remember it, as we may need to update it later. if step.Logical() && step.New() != nil { if prior, has := se.pendingNews.Load(step.URN()); has { - return errors.Errorf( - "resource '%s' registered twice (%s and %s)", step.URN(), prior.(Step).Op(), step.Op()) + return fmt.Errorf("resource '%s' registered twice (%s and %s)", step.URN(), prior.(Step).Op(), step.Op()) } se.pendingNews.Store(step.URN(), step) @@ -306,7 +305,7 @@ func (se *stepExecutor) executeStep(workerID int, step Step) error { if events != nil { if postErr := events.OnResourceStepPost(payload, step, status, err); postErr != nil { se.log(workerID, "step %v on %v failed post-resource step: %v", step.Op(), step.URN(), postErr) - return errors.Wrap(postErr, "post-step event returned an error") + return fmt.Errorf("post-step event returned an error: %w", postErr) } } diff --git a/pkg/resource/deploy/step_generator.go b/pkg/resource/deploy/step_generator.go index 4d24ff8da..2e4a23edf 100644 --- a/pkg/resource/deploy/step_generator.go +++ b/pkg/resource/deploy/step_generator.go @@ -18,7 +18,6 @@ import ( "fmt" "strings" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" "github.com/pulumi/pulumi/pkg/v3/resource/graph" "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" @@ -841,23 +840,44 @@ func (sg *stepGenerator) GenerateDeletes(targetsOpt map[resource.URN]bool) ([]St return dels, nil } -func (sg *stepGenerator) getTargetIncludingChildren(target resource.URN) map[resource.URN]bool { - allTargets := make(map[resource.URN]bool) - allTargets[target] = true - - // The list of resources is a topological sort of the reverse-dependency graph, so any - // resource R will appear in a list before its dependents and its children. We can use this - // to our advantage here and find all of a resource's children via a linear scan of the list - // starting from R. +// getTargetDependents returns the (transitive) set of dependents on the target resources. +// This includes both implicit and explicit dependents in the DAG itself, as well as children. +func (sg *stepGenerator) getTargetDependents(targetsOpt map[resource.URN]bool) map[resource.URN]bool { + // Seed the list with the initial set of targets. + var frontier []*resource.State for _, res := range sg.deployment.prev.Resources { - if _, has := allTargets[res.Parent]; has { - allTargets[res.URN] = true + if _, has := targetsOpt[res.URN]; has { + frontier = append(frontier, res) } } - return allTargets + // Produce a dependency graph of resources. + dg := graph.NewDependencyGraph(sg.deployment.prev.Resources) + + // Now accumulate a list of targets that are implicated because they depend upon the targets. + targets := make(map[resource.URN]bool) + for len(frontier) > 0 { + // Pop the next to explore, mark it, and skip any we've already seen. + next := frontier[0] + frontier = frontier[1:] + if _, has := targets[next.URN]; has { + continue + } + targets[next.URN] = true + + // Compute the set of resources depending on this one, either implicitly, explicitly, + // or because it is a child resource. Add them to the frontier to keep exploring. + deps := dg.DependingOn(next, targets, true) + frontier = append(frontier, deps...) + } + + return targets } +// determineAllowedResourcesToDeleteFromTargets computes the full (transitive) closure of resources +// that need to be deleted to permit the full list of targetsOpt resources to be deleted. This list +// will include the targetsOpt resources, but may contain more than just that, if there are dependent +// or child resources that require the targets to exist (and so are implicated in the deletion). func (sg *stepGenerator) determineAllowedResourcesToDeleteFromTargets( targetsOpt map[resource.URN]bool) (map[resource.URN]bool, result.Result) { @@ -866,21 +886,14 @@ func (sg *stepGenerator) determineAllowedResourcesToDeleteFromTargets( return nil, nil } - targetsIncludingChildren := make(map[resource.URN]bool) - - // Include all the children of each target. - for target := range targetsOpt { - allTargets := sg.getTargetIncludingChildren(target) - for child := range allTargets { - targetsIncludingChildren[child] = true - } - } - + // Produce a map of targets and their dependents, including explicit and implicit + // DAG dependencies, as well as children (transitively). + targets := sg.getTargetDependents(targetsOpt) logging.V(7).Infof("Planner was asked to only delete/update '%v'", targetsOpt) resourcesToDelete := make(map[resource.URN]bool) // Now actually use all the requested targets to figure out the exact set to delete. - for target := range targetsIncludingChildren { + for target := range targets { current := sg.deployment.olds[target] if current == nil { // user specified a target that didn't exist. they will have already gotten a warning @@ -1082,7 +1095,7 @@ func (sg *stepGenerator) providerChanged(urn resource.URN, old, new *resource.St // performance problem, this result can be cached. newProv, ok := sg.deployment.providers.GetProvider(newRef) if !ok { - return false, errors.Errorf("failed to resolve provider reference: %q", oldRef.String()) + return false, fmt.Errorf("failed to resolve provider reference: %q", oldRef.String()) } oldRes, ok := sg.deployment.olds[oldRef.URN()] @@ -1464,7 +1477,7 @@ func (sg *stepGenerator) calculateDependentReplacements(root *resource.State) ([ // that have already been registered must not depend on the root. Thus, we ignore these resources if they are // encountered while walking the old dependency graph to determine the set of dependents. impossibleDependents := sg.urns - for _, d := range sg.deployment.depGraph.DependingOn(root, impossibleDependents) { + for _, d := range sg.deployment.depGraph.DependingOn(root, impossibleDependents, false) { replace, keys, res := requiresReplacement(d) if res != nil { return nil, res diff --git a/pkg/resource/edit/operations.go b/pkg/resource/edit/operations.go index dc4951c61..e8b59eee9 100644 --- a/pkg/resource/edit/operations.go +++ b/pkg/resource/edit/operations.go @@ -15,7 +15,7 @@ package edit import ( - "github.com/pkg/errors" + "fmt" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" @@ -41,7 +41,7 @@ func DeleteResource(snapshot *deploy.Snapshot, condemnedRes *resource.State) err } dg := graph.NewDependencyGraph(snapshot.Resources) - dependencies := dg.DependingOn(condemnedRes, nil) + dependencies := dg.DependingOn(condemnedRes, nil, false) if len(dependencies) != 0 { return ResourceHasDependenciesError{Condemned: condemnedRes, Dependencies: dependencies} } @@ -147,7 +147,7 @@ func RenameStack(snap *deploy.Snapshot, newName tokens.QName, newProject tokens. } if err := snap.VerifyIntegrity(); err != nil { - return errors.Wrap(err, "checkpoint is invalid") + return fmt.Errorf("checkpoint is invalid: %w", err) } for _, res := range snap.Resources { diff --git a/pkg/resource/graph/dependency_graph.go b/pkg/resource/graph/dependency_graph.go index a56023478..3fbd9908c 100644 --- a/pkg/resource/graph/dependency_graph.go +++ b/pkg/resource/graph/dependency_graph.go @@ -20,7 +20,8 @@ type DependencyGraph struct { // order with respect to the snapshot dependency graph. // // The time complexity of DependingOn is linear with respect to the number of resources. -func (dg *DependencyGraph) DependingOn(res *resource.State, ignore map[resource.URN]bool) []*resource.State { +func (dg *DependencyGraph) DependingOn(res *resource.State, + ignore map[resource.URN]bool, includeChildren bool) []*resource.State { // This implementation relies on the detail that snapshots are stored in a valid // topological order. var dependents []*resource.State @@ -34,6 +35,14 @@ func (dg *DependencyGraph) DependingOn(res *resource.State, ignore map[resource. if ignore[candidate.URN] { return false } + if includeChildren && candidate.Parent == res.URN { + return true + } + for _, dependency := range candidate.Dependencies { + if dependentSet[dependency] { + return true + } + } if candidate.Provider != "" { ref, err := providers.ParseReference(candidate.Provider) contract.Assert(err == nil) @@ -41,11 +50,6 @@ func (dg *DependencyGraph) DependingOn(res *resource.State, ignore map[resource. return true } } - for _, dependency := range candidate.Dependencies { - if dependentSet[dependency] { - return true - } - } return false } diff --git a/pkg/resource/graph/dependency_graph_rapid_test.go b/pkg/resource/graph/dependency_graph_rapid_test.go new file mode 100644 index 000000000..906c69339 --- /dev/null +++ b/pkg/resource/graph/dependency_graph_rapid_test.go @@ -0,0 +1,364 @@ +// Copyright 2016-2021, 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. + +// Model-checks dependency_graph functionality against simple models +// using property-based testing. +// +// Currently this assumes a simplified model of `resource.State` +// relevant to dependency calculations; `dependency_graph` only +// accesses these fields: +// +// type State struct { +// Dependencies []resource.URN +// URN resource.URN +// Parent resource.URN +// Provider string +// Custom bool +// } +// +// At the moment only `Custom=true` (Custom, not Component) resources +// are tested. +package graph + +import ( + "bytes" + "fmt" + "io" + "testing" + + "pgregory.net/rapid" + + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Models ------------------------------------------------------------------------------------------ + +var isParent R = func(child, parent *resource.State) bool { + return child.Parent == parent.URN +} + +var hasProvider R = func(res, provider *resource.State) bool { + return resource.URN(res.Provider) == provider.URN +} + +var hasDependency R = func(res, dependency *resource.State) bool { + for _, dep := range res.Dependencies { + if dependency.URN == dep { + return true + } + } + return false +} + +var expectedDependenciesOf R = union(isParent, hasProvider, hasDependency) + +// Verify `DependneciesOf` against `expectedDependenciesOf`. +func TestRapidDependenciesOf(t *testing.T) { + graphCheck(t, func(t *rapid.T, universe []*resource.State) { + dg := NewDependencyGraph(universe) + for _, a := range universe { + aD := dg.DependenciesOf(a) + for _, b := range universe { + if isParent(a, b) { + assert.Truef(t, aD[b], + "DependenciesOf(%v) is missing a parent %v", + a.URN, b.URN) + } + if hasProvider(a, b) { + assert.Truef(t, aD[b], + "DependenciesOf(%v) is missing a provider %v", + a.URN, b.URN) + } + if hasDependency(a, b) { + assert.Truef(t, aD[b], + "DependenciesOf(%v) is missing a dependecy %v", + a.URN, b.URN) + } + if aD[b] { + assert.True(t, expectedDependenciesOf(a, b), + "DependenciesOf(%v) includes an unexpected %v", + a.URN, b.URN) + } + } + } + }) +} + +// Additionally verify no immediate loops in `DependenciesOf`, no `B +// in DependenciesOf(A) && A in DependenciesOf(B)`. +func TestRapidDependenciesOfAntisymmetric(t *testing.T) { + graphCheck(t, func(t *rapid.T, universe []*resource.State) { + dg := NewDependencyGraph(universe) + for _, a := range universe { + aD := dg.DependenciesOf(a) + for _, b := range universe { + bD := dg.DependenciesOf(b) + assert.Falsef(t, aD[b] && bD[a], + "DependenciesOf symmetric over (%v, %v)", a.URN, b.URN) + } + } + }) +} + +// Model `DependingOn`. +func expectedDependingOn(universe []*resource.State, includeChildren bool) R { + if !includeChildren { + // TODO currently DependingOn is not the inverse transitive + // closure of `dependenciesOf`. Should this be + // `expectedDependenciesOf`? + restrictedDependenciesOf := union(hasProvider, hasDependency) + return inverse(transitively(universe)(restrictedDependenciesOf)) + } + + // TODO this extends base `expectedDependingOn` with + // immediate children. Should it be a transitive closure? + dependingOn := expectedDependingOn(universe, false) + return func(a, b *resource.State) bool { + if dependingOn(a, b) || isParent(b, a) { + return true + } + for _, x := range universe { + if dependingOn(x, b) && isParent(x, a) { + return true + } + } + return false + } +} + +// Verify `DependingOn` against `expectedDependingOn`. Note that +// `DependingOn` is specialised with an empty ignore map, the ignore +// map is not tested yet. +func TestRapidDependingOn(t *testing.T) { + test := func(t *rapid.T, universe []*resource.State, includingChildren bool) { + expected := expectedDependingOn(universe, includingChildren) + dg := NewDependencyGraph(universe) + dependingOn := func(a, b *resource.State) bool { + for _, x := range dg.DependingOn(a, nil, includingChildren) { + if b.URN == x.URN { + return true + } + } + return false + } + for _, a := range universe { + for _, b := range universe { + actual := dependingOn(a, b) + assert.Equalf(t, expected(a, b), actual, + "Unexpected %v in dg.DependingOn(%v) = %v", + b.URN, a.URN, actual) + } + } + } + + for _, includingChildren := range []bool{false, true} { + t.Run(fmt.Sprintf("includingChildren=%v", includingChildren), func(t *testing.T) { + graphCheck(t, func(t *rapid.T, universe []*resource.State) { + test(t, universe, includingChildren) + }) + }) + } +} + +// Verify `DependingOn` results are ordered, if `D1` in +// `DependingOn(D2)` then `D1` appears before `D2`. +func TestRapidDependingOnOrdered(t *testing.T) { + test := func(t *rapid.T, universe []*resource.State, includingChildren bool) { + expectedDependingOn := expectedDependingOn(universe, includingChildren) + dg := NewDependencyGraph(universe) + for _, a := range universe { + depOnA := dg.DependingOn(a, nil, includingChildren) + for d1i, d1 := range depOnA { + for d2i, d2 := range depOnA { + if expectedDependingOn(d2, d1) { + require.Truef(t, d2i < d1i, + "%v should appear before %v", + d2.URN, d1.URN) + } + } + } + } + } + + for _, includingChildren := range []bool{false, true} { + t.Run(fmt.Sprintf("includingChildren=%v", includingChildren), func(t *testing.T) { + graphCheck(t, func(t *rapid.T, universe []*resource.State) { + test(t, universe, includingChildren) + }) + }) + } +} + +// Generators -------------------------------------------------------------------------------------- + +// Generates ordered values of type `[]ResourceState` that: +// +// - Have unique URNs +// - May reference preceding resouces in the slice as r.Parent +// - May reference preceding resouces in the slice in r.Dependencies +// +// In other words these slices conform with `NewDependencyGraph` +// ordering assumptions. There is a tradedoff: generated values will +// not test any error-checking code in `NewDependencyGraph`, but will +// more efficiently explore more complicated properties on the valid +// subspace of inputs. +// +// What is not currently done but may need to be extended: +// +// - Support Component resources +// - Support non-nil r.Provider references +func resourceStateSliceGenerator() *rapid.Generator { + urnGen := rapid.StringMatching(`urn:pulumi:a::b::c:d:e::[abcd][123]`) + + stateGen := rapid.Custom(func(t *rapid.T) *resource.State { + urn := urnGen.Draw(t, "URN").(string) + return &resource.State{ + Custom: true, + URN: resource.URN(urn), + } + }) + + getUrn := func(st *resource.State) resource.URN { return st.URN } + + statesGen := rapid.SliceOfDistinct(stateGen, getUrn) + + return rapid.Custom(func(t *rapid.T) []*resource.State { + states := statesGen.Draw(t, "states").([]*resource.State) + + randInt := rapid.IntRange(-len(states), len(states)) + + for i, r := range states { + // Any resource at index `i` may want to declare `j < i` as parent. + // Sample negative `j` to means "no parent". + j := randInt.Draw(t, fmt.Sprintf("j%d", i)).(int) + if j >= 0 && j < i { + r.Parent = states[j].URN + } + // Similarly we can depend on resources defined prior. + deps := rapid.SliceOfDistinct( + randInt, + func(i int) int { return i }, + ).Draw(t, fmt.Sprintf("deps%d", i)).([]int) + for _, dep := range deps { + if dep >= 0 && dep < i { + r.Dependencies = append(r.Dependencies, states[dep].URN) + } + } + } + + return states + }) +} + +// Helper code: relations -------------------------------------------------------------------------- + +// Shorthand for relations over `*resource.State` +type R = func(a, b *resource.State) bool + +// Union of one or more relations. +func union(rs ...R) R { + return func(a, b *resource.State) bool { + for _, r := range rs { + if r(a, b) { + return true + } + } + return false + } +} + +// Flips the relation, `inverse(R)(a,b) = R(b,a)`. +func inverse(r R) R { + return func(a, b *resource.State) bool { + return r(b, a) + } +} + +// Memoized transitive closure of a relation. +func transitively(universe []*resource.State) func(R) R { + return func(rel R) R { + trel := make(map[*resource.State]map[*resource.State]bool) + for _, a := range universe { + trel[a] = make(map[*resource.State]bool) + for _, b := range universe { + if rel(a, b) { + trel[a][b] = true + } + } + } + + extend := func() bool { + more := false + for _, a := range universe { + for _, b := range universe { + if !trel[a][b] { + for _, x := range universe { + if trel[x][b] && rel(a, x) { + trel[a][b] = true + more = true + } + } + } + } + } + return more + } + + for extend() { + } + + return func(a, b *resource.State) bool { + return trel[a][b] + } + } +} + +// Helper code: misc ------------------------------------------------------------------------------- + +func printState(w io.Writer, st *resource.State) { + fmt.Fprintf(w, "%s", st.URN) + if st.Parent != "" { + fmt.Fprintf(w, " parent=%s", st.Parent) + } + if len(st.Dependencies) > 0 { + fmt.Fprintf(w, " deps=[") + for _, d := range st.Dependencies { + fmt.Fprintf(w, "%s, ", d) + } + fmt.Fprintf(w, "]") + } + fmt.Fprintf(w, "\n") +} + +func showStates(sts []*resource.State) string { + buf := &bytes.Buffer{} + fmt.Fprintf(buf, "[\n\n") + for _, st := range sts { + printState(buf, st) + fmt.Fprintf(buf, "\n\n") + } + fmt.Fprintf(buf, "]") + return buf.String() +} + +func graphCheck(t *testing.T, check func(*rapid.T, []*resource.State)) { + rss := resourceStateSliceGenerator() + rapid.Check(t, func(t *rapid.T) { + universe := rss.Draw(t, "universe").([]*resource.State) + t.Logf("Checking universe: %s", showStates(universe)) + check(t, universe) + }) +} diff --git a/pkg/resource/graph/dependency_graph_test.go b/pkg/resource/graph/dependency_graph_test.go index 80b3a1f1a..73d594c72 100644 --- a/pkg/resource/graph/dependency_graph_test.go +++ b/pkg/resource/graph/dependency_graph_test.go @@ -63,58 +63,58 @@ func TestBasicGraph(t *testing.T) { assert.Equal(t, []*resource.State{ a, b, pB, c, d, - }, dg.DependingOn(pA, nil)) + }, dg.DependingOn(pA, nil, false)) assert.Equal(t, []*resource.State{ b, pB, c, d, - }, dg.DependingOn(a, nil)) + }, dg.DependingOn(a, nil, false)) assert.Equal(t, []*resource.State{ pB, c, d, - }, dg.DependingOn(b, nil)) + }, dg.DependingOn(b, nil, false)) assert.Equal(t, []*resource.State{ c, - }, dg.DependingOn(pB, nil)) + }, dg.DependingOn(pB, nil, false)) - assert.Nil(t, dg.DependingOn(c, nil)) - assert.Nil(t, dg.DependingOn(d, nil)) + assert.Nil(t, dg.DependingOn(c, nil, false)) + assert.Nil(t, dg.DependingOn(d, nil, false)) assert.Nil(t, dg.DependingOn(pA, map[resource.URN]bool{ a.URN: true, b.URN: true, - })) + }, false)) assert.Equal(t, []*resource.State{ a, pB, c, }, dg.DependingOn(pA, map[resource.URN]bool{ b.URN: true, - })) + }, false)) assert.Equal(t, []*resource.State{ b, pB, c, d, }, dg.DependingOn(pA, map[resource.URN]bool{ a.URN: true, - })) + }, false)) assert.Equal(t, []*resource.State{ c, }, dg.DependingOn(a, map[resource.URN]bool{ b.URN: true, pB.URN: true, - })) + }, false)) assert.Equal(t, []*resource.State{ pB, c, }, dg.DependingOn(a, map[resource.URN]bool{ b.URN: true, - })) + }, false)) assert.Equal(t, []*resource.State{ d, }, dg.DependingOn(b, map[resource.URN]bool{ pB.URN: true, - })) + }, false)) } // Tests that we don't add the same node to the DependingOn set twice. @@ -133,7 +133,7 @@ func TestGraphNoDuplicates(t *testing.T) { assert.Equal(t, []*resource.State{ b, c, d, - }, dg.DependingOn(a, nil)) + }, dg.DependingOn(a, nil, false)) } func TestDependenciesOf(t *testing.T) { diff --git a/pkg/resource/graph/resource_set.go b/pkg/resource/graph/resource_set.go index 4bfb4487a..d2e766b90 100644 --- a/pkg/resource/graph/resource_set.go +++ b/pkg/resource/graph/resource_set.go @@ -14,7 +14,9 @@ package graph -import "github.com/pulumi/pulumi/sdk/v3/go/common/resource" +import ( + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" +) // ResourceSet represents a set of Resources. type ResourceSet map[*resource.State]bool diff --git a/pkg/resource/provider/component_provider.go b/pkg/resource/provider/component_provider.go index f6c892057..5bbe3575c 100644 --- a/pkg/resource/provider/component_provider.go +++ b/pkg/resource/provider/component_provider.go @@ -15,7 +15,7 @@ package provider import ( - "github.com/pkg/errors" + "fmt" "github.com/pulumi/pulumi/sdk/v3/go/pulumi/provider" pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go" @@ -87,7 +87,7 @@ func (p *componentProvider) GetPluginInfo(context.Context, *pbempty.Empty) (*pul func (p *componentProvider) GetSchema(ctx context.Context, req *pulumirpc.GetSchemaRequest) (*pulumirpc.GetSchemaResponse, error) { if v := req.GetVersion(); v != 0 { - return nil, errors.Errorf("unsupported schema version %d", v) + return nil, fmt.Errorf("unsupported schema version %d", v) } schema := string(p.schema) if schema == "" { diff --git a/pkg/resource/provider/main.go b/pkg/resource/provider/main.go index 7310fd745..1b8b7ba66 100644 --- a/pkg/resource/provider/main.go +++ b/pkg/resource/provider/main.go @@ -15,10 +15,10 @@ package provider import ( + "errors" "flag" "fmt" - "github.com/pkg/errors" "google.golang.org/grpc" "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" @@ -48,7 +48,7 @@ func Main(name string, provMaker func(*HostClient) (pulumirpc.ResourceProviderSe } host, err := NewHostClient(args[0]) if err != nil { - return errors.Errorf("fatal: could not connect to host RPC: %v", err) + return fmt.Errorf("fatal: could not connect to host RPC: %v", err) } // Fire up a gRPC server, letting the kernel choose a free port for us. @@ -63,7 +63,7 @@ func Main(name string, provMaker func(*HostClient) (pulumirpc.ResourceProviderSe }, }, nil) if err != nil { - return errors.Errorf("fatal: %v", err) + return fmt.Errorf("fatal: %v", err) } // The resource provider protocol requires that we now write out the port we have chosen to listen on. @@ -71,7 +71,7 @@ func Main(name string, provMaker func(*HostClient) (pulumirpc.ResourceProviderSe // Finally, wait for the server to stop serving. if err := <-done; err != nil { - return errors.Errorf("fatal: %v", err) + return fmt.Errorf("fatal: %v", err) } return nil diff --git a/pkg/resource/stack/checkpoint.go b/pkg/resource/stack/checkpoint.go index cb7f530ae..b259afd3b 100644 --- a/pkg/resource/stack/checkpoint.go +++ b/pkg/resource/stack/checkpoint.go @@ -16,8 +16,7 @@ package stack import ( "encoding/json" - - "github.com/pkg/errors" + "fmt" "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/secrets" @@ -73,7 +72,7 @@ func UnmarshalVersionedCheckpointToLatestCheckpoint(bytes []byte) (*apitype.Chec return &v3checkpoint, nil default: - return nil, errors.Errorf("unsupported checkpoint version %d", versionedCheckpoint.Version) + return nil, fmt.Errorf("unsupported checkpoint version %d", versionedCheckpoint.Version) } } @@ -85,7 +84,7 @@ func SerializeCheckpoint(stack tokens.QName, snap *deploy.Snapshot, if snap != nil { dep, err := SerializeDeployment(snap, sm, showSecrets) if err != nil { - return nil, errors.Wrap(err, "serializing deployment") + return nil, fmt.Errorf("serializing deployment: %w", err) } latest = dep } @@ -95,7 +94,7 @@ func SerializeCheckpoint(stack tokens.QName, snap *deploy.Snapshot, Latest: latest, }) if err != nil { - return nil, errors.Wrap(err, "marshalling checkpoint") + return nil, fmt.Errorf("marshalling checkpoint: %w", err) } return &apitype.VersionedCheckpoint{ diff --git a/pkg/resource/stack/deployment.go b/pkg/resource/stack/deployment.go index f9430fd79..73187de49 100644 --- a/pkg/resource/stack/deployment.go +++ b/pkg/resource/stack/deployment.go @@ -16,6 +16,7 @@ package stack import ( "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -23,7 +24,7 @@ import ( "strings" "github.com/blang/semver" - "github.com/pkg/errors" + "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/secrets" "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" @@ -131,7 +132,7 @@ func SerializeDeployment(snap *deploy.Snapshot, sm secrets.Manager, showSecrets if sm != nil { e, err := sm.Encrypter() if err != nil { - return nil, errors.Wrap(err, "getting encrypter for deployment") + return nil, fmt.Errorf("getting encrypter for deployment: %w", err) } enc = e } else { @@ -143,7 +144,7 @@ func SerializeDeployment(snap *deploy.Snapshot, sm secrets.Manager, showSecrets for _, res := range snap.Resources { sres, err := SerializeResource(res, enc, showSecrets) if err != nil { - return nil, errors.Wrap(err, "serializing resources") + return nil, fmt.Errorf("serializing resources: %w", err) } resources = append(resources, sres) } @@ -351,7 +352,7 @@ func SerializeResource(res *resource.State, enc config.Encrypter, showSecrets bo func SerializeOperation(op resource.Operation, enc config.Encrypter, showSecrets bool) (apitype.OperationV2, error) { res, err := SerializeResource(op.Resource, enc, showSecrets) if err != nil { - return apitype.OperationV2{}, errors.Wrap(err, "serializing resource") + return apitype.OperationV2{}, fmt.Errorf("serializing resource: %w", err) } return apitype.OperationV2{ Resource: res, @@ -438,7 +439,7 @@ func SerializePropertyValue(prop resource.PropertyValue, enc config.Encrypter, } bytes, err := json.Marshal(value) if err != nil { - return nil, errors.Wrap(err, "encoding serialized property value") + return nil, fmt.Errorf("encoding serialized property value: %w", err) } plaintext := string(bytes) @@ -451,7 +452,7 @@ func SerializePropertyValue(prop resource.PropertyValue, enc config.Encrypter, ciphertext, err = enc.EncryptValue(plaintext) } if err != nil { - return nil, errors.Wrap(err, "failed to encrypt secret value") + return nil, fmt.Errorf("failed to encrypt secret value: %w", err) } contract.AssertNoErrorf(err, "marshalling underlying secret value to JSON") @@ -485,15 +486,15 @@ func DeserializeResource(res apitype.ResourceV3, dec config.Decrypter, enc confi } if res.URN == "" { - return nil, errors.Errorf("resource missing required 'urn' field") + return nil, fmt.Errorf("resource missing required 'urn' field") } if res.Type == "" { - return nil, errors.Errorf("resource '%s' missing required 'type' field", res.URN) + return nil, fmt.Errorf("resource '%s' missing required 'type' field", res.URN) } if !res.Custom && res.ID != "" { - return nil, errors.Errorf("resource '%s' has 'custom' false but non-empty ID", res.URN) + return nil, fmt.Errorf("resource '%s' has 'custom' false but non-empty ID", res.URN) } return resource.NewState( @@ -585,14 +586,14 @@ func DeserializePropertyValue(v interface{}, dec config.Decrypter, if plainOk { encryptedText, err := enc.EncryptValue(plaintext) if err != nil { - return resource.PropertyValue{}, errors.Wrap(err, "encrypting secret value") + return resource.PropertyValue{}, fmt.Errorf("encrypting secret value: %w", err) } ciphertext = encryptedText } else { unencryptedText, err := dec.DecryptValue(ciphertext) if err != nil { - return resource.PropertyValue{}, errors.Wrap(err, "decrypting secret value") + return resource.PropertyValue{}, fmt.Errorf("decrypting secret value: %w", err) } plaintext = unencryptedText } @@ -667,7 +668,7 @@ func DeserializePropertyValue(v interface{}, dec config.Decrypter, } return resource.MakeComponentResourceReference(urn, packageVersion), nil default: - return resource.PropertyValue{}, errors.Errorf("unrecognized signature '%v' in property map", sig) + return resource.PropertyValue{}, fmt.Errorf("unrecognized signature '%v' in property map", sig) } } diff --git a/pkg/resource/stack/secrets.go b/pkg/resource/stack/secrets.go index c60e99b91..03183d3fb 100644 --- a/pkg/resource/stack/secrets.go +++ b/pkg/resource/stack/secrets.go @@ -16,8 +16,8 @@ package stack import ( "encoding/json" + "fmt" - "github.com/pkg/errors" "github.com/pulumi/pulumi/pkg/v3/secrets" "github.com/pulumi/pulumi/pkg/v3/secrets/b64" "github.com/pulumi/pulumi/pkg/v3/secrets/cloud" @@ -56,10 +56,10 @@ func (defaultSecretsProvider) OfType(ty string, state json.RawMessage) (secrets. case cloud.Type: sm, err = cloud.NewCloudSecretsManagerFromState(state) default: - return nil, errors.Errorf("no known secrets provider for type %q", ty) + return nil, fmt.Errorf("no known secrets provider for type %q", ty) } if err != nil { - return nil, errors.Wrapf(err, "constructing secrets manager of type %q", ty) + return nil, fmt.Errorf("constructing secrets manager of type %q: %w", ty, err) } return NewCachingSecretsManager(sm), nil diff --git a/pkg/resource/stack/secrets_test.go b/pkg/resource/stack/secrets_test.go index 3baddc7ab..71a53f0d9 100644 --- a/pkg/resource/stack/secrets_test.go +++ b/pkg/resource/stack/secrets_test.go @@ -2,11 +2,11 @@ package stack import ( "encoding/json" + "errors" "fmt" "strings" "testing" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" "github.com/stretchr/testify/assert" diff --git a/pkg/secrets/cloud/manager.go b/pkg/secrets/cloud/manager.go index 3ce272178..2e94f50b8 100644 --- a/pkg/secrets/cloud/manager.go +++ b/pkg/secrets/cloud/manager.go @@ -19,8 +19,8 @@ import ( "context" "crypto/rand" "encoding/json" + "fmt" - "github.com/pkg/errors" gosecrets "gocloud.dev/secrets" _ "gocloud.dev/secrets/awskms" // support for awskms:// _ "gocloud.dev/secrets/azurekeyvault" // support for azurekeyvault:// @@ -45,7 +45,7 @@ type cloudSecretsManagerState struct { func NewCloudSecretsManagerFromState(state json.RawMessage) (secrets.Manager, error) { var s cloudSecretsManagerState if err := json.Unmarshal(state, &s); err != nil { - return nil, errors.Wrap(err, "unmarshalling state") + return nil, fmt.Errorf("unmarshalling state: %w", err) } return NewCloudSecretsManager(s.URL, s.EncryptedKey) diff --git a/pkg/secrets/passphrase/manager.go b/pkg/secrets/passphrase/manager.go index c8c180257..09dad398e 100644 --- a/pkg/secrets/passphrase/manager.go +++ b/pkg/secrets/passphrase/manager.go @@ -18,14 +18,14 @@ package passphrase import ( "encoding/base64" "encoding/json" + "errors" + "fmt" "io/ioutil" "os" "path/filepath" "strings" "sync" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/secrets" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" @@ -152,12 +152,12 @@ func getConfigPassphrase() (string, bool, error) { if phraseFile, isOk := os.LookupEnv("PULUMI_CONFIG_PASSPHRASE_FILE"); isOk { phraseFilePath, err := filepath.Abs(phraseFile) if err != nil { - return "", false, errors.Wrap(err, "unable to detect passphrase path") + return "", false, fmt.Errorf("unable to detect passphrase path: %w", err) } phraseDetails, err := ioutil.ReadFile(phraseFilePath) if err != nil { - return "", false, errors.Wrap(err, "unable to read PULUMI_CONFIG_PASSPHRASE_FILE") + return "", false, fmt.Errorf("unable to read PULUMI_CONFIG_PASSPHRASE_FILE: %w", err) } return strings.TrimSpace(string(phraseDetails)), true, nil @@ -171,7 +171,7 @@ func getConfigPassphrase() (string, bool, error) { func NewPassphaseSecretsManagerFromState(state json.RawMessage) (secrets.Manager, error) { var s localSecretsManagerState if err := json.Unmarshal(state, &s); err != nil { - return nil, errors.Wrap(err, "unmarshalling state") + return nil, fmt.Errorf("unmarshalling state: %w", err) } // This is not ideal, but we don't have a great way to prompt the user in this case, since this may be @@ -197,7 +197,7 @@ func NewPassphaseSecretsManagerFromState(state json.RawMessage) (secrets.Manager case err == ErrIncorrectPassphrase: return newLockedPasspharseSecretsManager(s), nil case err != nil: - return nil, errors.Wrap(err, "constructing secrets manager") + return nil, fmt.Errorf("constructing secrets manager: %w", err) default: return sm, nil } diff --git a/pkg/secrets/passphrase/manager_test.go b/pkg/secrets/passphrase/manager_test.go index 427914988..5f2fa3cd2 100644 --- a/pkg/secrets/passphrase/manager_test.go +++ b/pkg/secrets/passphrase/manager_test.go @@ -1,10 +1,11 @@ package passphrase import ( - "github.com/stretchr/testify/assert" "os" "strings" "testing" + + "github.com/stretchr/testify/assert" ) const ( diff --git a/pkg/secrets/service/manager.go b/pkg/secrets/service/manager.go index 447e2b9de..720b9c124 100644 --- a/pkg/secrets/service/manager.go +++ b/pkg/secrets/service/manager.go @@ -19,10 +19,9 @@ import ( "context" "encoding/base64" "encoding/json" + "fmt" "io/ioutil" - "github.com/pkg/errors" - "github.com/pulumi/pulumi/pkg/v3/backend/httpstate/client" "github.com/pulumi/pulumi/pkg/v3/secrets" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" @@ -112,17 +111,17 @@ func NewServiceSecretsManager(c *client.Client, id client.StackIdentifier) (secr func NewServiceSecretsManagerFromState(state json.RawMessage) (secrets.Manager, error) { var s serviceSecretsManagerState if err := json.Unmarshal(state, &s); err != nil { - return nil, errors.Wrap(err, "unmarshalling state") + return nil, fmt.Errorf("unmarshalling state: %w", err) } account, err := workspace.GetAccount(s.URL) if err != nil { - return nil, errors.Wrap(err, "getting access token") + return nil, fmt.Errorf("getting access token: %w", err) } token := account.AccessToken if token == "" { - return nil, errors.Errorf("could not find access token for %s, have you logged in?", s.URL) + return nil, fmt.Errorf("could not find access token for %s, have you logged in?", s.URL) } id := client.StackIdentifier{ diff --git a/pkg/testing/integration/program.go b/pkg/testing/integration/program.go index 62606ff16..382c29e73 100644 --- a/pkg/testing/integration/program.go +++ b/pkg/testing/integration/program.go @@ -19,6 +19,7 @@ import ( cryptorand "crypto/rand" "encoding/hex" "encoding/json" + "errors" "flag" "fmt" "io" @@ -34,7 +35,7 @@ import ( "time" multierror "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" + "github.com/pulumi/pulumi/pkg/v3/backend/filestate" "github.com/pulumi/pulumi/pkg/v3/engine" "github.com/pulumi/pulumi/pkg/v3/operations" @@ -716,7 +717,7 @@ func (pt *ProgramTester) getPythonBin() (string, error) { } } if err != nil { - return "", errors.Wrapf(err, "Expected to find one of %q on $PATH", pythonCmds) + return "", fmt.Errorf("Expected to find one of %q on $PATH: %w", pythonCmds, err) } } } @@ -829,7 +830,7 @@ func (pt *ProgramTester) runPulumiCommand(name string, args []string, wd string, } else if _, ok := runerr.(*exec.ExitError); ok && isUpdate && !expectFailure { // the update command failed, let's try again, assuming we haven't failed a few times. if try+1 >= pt.maxStepTries { - return false, nil, errors.Errorf("%v did not succeed after %v tries", cmd, try+1) + return false, nil, fmt.Errorf("%v did not succeed after %v tries", cmd, try+1) } pt.t.Logf("%v failed: %v; retrying...", cmd, runerr) @@ -862,7 +863,7 @@ func (pt *ProgramTester) runYarnCommand(name string, args []string, wd string) e } else if _, ok := runerr.(*exec.ExitError); ok { // yarn failed, let's try again, assuming we haven't failed a few times. if try+1 >= 3 { - return false, nil, errors.Errorf("%v did not complete after %v tries", cmd, try+1) + return false, nil, fmt.Errorf("%v did not complete after %v tries", cmd, try+1) } return false, nil, nil @@ -1006,7 +1007,7 @@ func (pt *ProgramTester) TestCleanUp() { func (pt *ProgramTester) TestLifeCycleInitAndDestroy() error { err := pt.TestLifeCyclePrepare() if err != nil { - return errors.Wrapf(err, "copying test to temp dir %s", pt.tmpdir) + return fmt.Errorf("copying test to temp dir %s: %w", pt.tmpdir, err) } pt.TestFinished = false @@ -1014,7 +1015,7 @@ func (pt *ProgramTester) TestLifeCycleInitAndDestroy() error { err = pt.TestLifeCycleInitialize() if err != nil { - return errors.Wrap(err, "initializing test project") + return fmt.Errorf("initializing test project: %w", err) } // Ensure that before we exit, we attempt to destroy and remove the stack. @@ -1024,17 +1025,17 @@ func (pt *ProgramTester) TestLifeCycleInitAndDestroy() error { }() if err = pt.TestPreviewUpdateAndEdits(); err != nil { - return errors.Wrap(err, "running test preview, update, and edits") + return fmt.Errorf("running test preview, update, and edits: %w", err) } if pt.opts.RunUpdateTest { err = upgradeProjectDeps(pt.projdir, pt) if err != nil { - return errors.Wrap(err, "upgrading project dependencies") + return fmt.Errorf("upgrading project dependencies: %w", err) } if err = pt.TestPreviewUpdateAndEdits(); err != nil { - return errors.Wrap(err, "running test preview, update, and edits") + return fmt.Errorf("running test preview, update, and edits: %w", err) } } @@ -1045,7 +1046,7 @@ func (pt *ProgramTester) TestLifeCycleInitAndDestroy() error { func upgradeProjectDeps(projectDir string, pt *ProgramTester) error { projInfo, err := pt.getProjinfo(projectDir) if err != nil { - return errors.Wrap(err, "getting project info") + return fmt.Errorf("getting project info: %w", err) } switch rt := projInfo.Proj.Runtime.Name(); rt { @@ -1058,7 +1059,7 @@ func upgradeProjectDeps(projectDir string, pt *ProgramTester) error { return err } default: - return errors.Errorf("unrecognized project runtime: %s", rt) + return fmt.Errorf("unrecognized project runtime: %s", rt) } return nil @@ -1355,13 +1356,13 @@ func (pt *ProgramTester) testEdit(dir string, i int, edit EditDir) error { if edit.Additive { // Just copy new files into dir if err := fsutil.CopyFile(dir, edit.Dir, nil); err != nil { - return errors.Wrapf(err, "Couldn't copy %v into %v", edit.Dir, dir) + return fmt.Errorf("Couldn't copy %v into %v: %w", edit.Dir, dir, err) } } else { // Create a new temporary directory newDir, err := ioutil.TempDir("", pt.opts.StackName+"-") if err != nil { - return errors.Wrapf(err, "Couldn't create new temporary directory") + return fmt.Errorf("Couldn't create new temporary directory: %w", err) } // Delete whichever copy of the test is unused when we return @@ -1379,7 +1380,7 @@ func (pt *ProgramTester) testEdit(dir string, i int, edit EditDir) error { exclusions[configYaml] = true if err := fsutil.CopyFile(newDir, edit.Dir, exclusions); err != nil { - return errors.Wrapf(err, "Couldn't copy %v into %v", edit.Dir, newDir) + return fmt.Errorf("Couldn't copy %v into %v: %w", edit.Dir, newDir, err) } // Copy Pulumi.yaml, Pulumi..yaml, and .pulumi from old directory to new directory @@ -1393,25 +1394,25 @@ func (pt *ProgramTester) testEdit(dir string, i int, edit EditDir) error { newProjectDir := filepath.Join(newDir, workspace.BookkeepingDir) if err := fsutil.CopyFile(newProjectYaml, oldProjectYaml, nil); err != nil { - return errors.Wrap(err, "Couldn't copy Pulumi.yaml") + return fmt.Errorf("Couldn't copy Pulumi.yaml: %w", err) } if err := fsutil.CopyFile(newConfigYaml, oldConfigYaml, nil); err != nil { - return errors.Wrapf(err, "Couldn't copy Pulumi.%s.yaml", pt.opts.StackName) + return fmt.Errorf("Couldn't copy Pulumi.%s.yaml: %w", pt.opts.StackName, err) } if err := fsutil.CopyFile(newProjectDir, oldProjectDir, nil); err != nil { - return errors.Wrap(err, "Couldn't copy .pulumi") + return fmt.Errorf("Couldn't copy .pulumi: %w", err) } // Finally, replace our current temp directory with the new one. dirOld := dir + ".old" if err := os.Rename(dir, dirOld); err != nil { - return errors.Wrapf(err, "Couldn't rename %v to %v", dir, dirOld) + return fmt.Errorf("Couldn't rename %v to %v: %w", dir, dirOld, err) } // There's a brief window here where the old temp dir name could be taken from us. if err := os.Rename(newDir, dir); err != nil { - return errors.Wrapf(err, "Couldn't rename %v to %v", newDir, dir) + return fmt.Errorf("Couldn't rename %v to %v: %w", newDir, dir, err) } // Keep dir, delete oldDir @@ -1420,7 +1421,7 @@ func (pt *ProgramTester) testEdit(dir string, i int, edit EditDir) error { err := pt.prepareProjectDir(dir) if err != nil { - return errors.Wrapf(err, "Couldn't prepare project in %v", dir) + return fmt.Errorf("Couldn't prepare project in %v: %w", dir, err) } oldStdOut := pt.opts.Stdout @@ -1482,13 +1483,13 @@ func (pt *ProgramTester) performExtraRuntimeValidation( } if err = pt.runPulumiCommand("pulumi-export", pulumiCommand, dir, false); err != nil { - return errors.Wrapf(err, "expected to export stack to file: %s", fileName) + return fmt.Errorf("expected to export stack to file: %s: %w", fileName, err) } // Open the exported JSON file f, err := os.Open(fileName) if err != nil { - return errors.Wrapf(err, "expected to be able to open file with stack exports: %s", fileName) + return fmt.Errorf("expected to be able to open file with stack exports: %s: %w", fileName, err) } defer func() { contract.IgnoreClose(f) @@ -1518,7 +1519,7 @@ func (pt *ProgramTester) performExtraRuntimeValidation( // Read the event log. eventsFile, err := os.Open(pt.eventLog) if err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "expected to be able to open event log file %s", pt.eventLog) + return fmt.Errorf("expected to be able to open event log file %s: %w", pt.eventLog, err) } defer contract.IgnoreClose(eventsFile) decoder, events := json.NewDecoder(eventsFile), []apitype.EngineEvent{} @@ -1528,7 +1529,7 @@ func (pt *ProgramTester) performExtraRuntimeValidation( if err == io.EOF { break } - return errors.Wrapf(err, "decoding engine event") + return fmt.Errorf("decoding engine event: %w", err) } events = append(events, event) } @@ -1579,14 +1580,14 @@ func (pt *ProgramTester) copyTestToTemporaryDirectory() (string, string, error) if projinfo.Proj.Runtime.Name() == "go" { targetDir, err := tools.CreateTemporaryGoFolder("stackName") if err != nil { - return "", "", errors.Wrap(err, "Couldn't create temporary directory") + return "", "", fmt.Errorf("Couldn't create temporary directory: %w", err) } tmpdir = targetDir projdir = targetDir } else { targetDir, tempErr := ioutil.TempDir("", stackName+"-") if tempErr != nil { - return "", "", errors.Wrap(tempErr, "Couldn't create temporary directory") + return "", "", fmt.Errorf("Couldn't create temporary directory: %w", tempErr) } tmpdir = targetDir projdir = targetDir @@ -1599,7 +1600,7 @@ func (pt *ProgramTester) copyTestToTemporaryDirectory() (string, string, error) err = pt.prepareProject(projinfo) if err != nil { - return "", "", errors.Wrapf(err, "Failed to prepare %v", projdir) + return "", "", fmt.Errorf("Failed to prepare %v: %w", projdir, err) } // TODO[pulumi/pulumi#5455]: Dynamic providers fail to load when used from multi-lang components. @@ -1659,7 +1660,7 @@ func (pt *ProgramTester) prepareProject(projinfo *engine.Projinfo) error { case DotNetRuntime: return pt.prepareDotNetProject(projinfo) default: - return errors.Errorf("unrecognized project runtime: %s", rt) + return fmt.Errorf("unrecognized project runtime: %s", rt) } } @@ -1745,13 +1746,13 @@ func (pt *ProgramTester) prepareNodeJSProject(projinfo *engine.Projinfo) error { func readPackageJSON(pathToPackage string) (map[string]interface{}, error) { f, err := os.Open(filepath.Join(pathToPackage, "package.json")) if err != nil { - return nil, errors.Wrap(err, "opening package.json") + return nil, fmt.Errorf("opening package.json: %w", err) } defer contract.IgnoreClose(f) var ret map[string]interface{} if err := json.NewDecoder(f).Decode(&ret); err != nil { - return nil, errors.Wrap(err, "decoding package.json") + return nil, fmt.Errorf("decoding package.json: %w", err) } return ret, nil @@ -1761,14 +1762,14 @@ func writePackageJSON(pathToPackage string, metadata map[string]interface{}) err // os.Create truncates the already existing file. f, err := os.Create(filepath.Join(pathToPackage, "package.json")) if err != nil { - return errors.Wrap(err, "opening package.json") + return fmt.Errorf("opening package.json: %w", err) } defer contract.IgnoreClose(f) encoder := json.NewEncoder(f) encoder.SetIndent("", " ") - return errors.Wrap(encoder.Encode(metadata), "writing package.json") + return fmt.Errorf("writing package.json: %w", encoder.Encode(metadata)) } // preparePythonProject runs setup necessary to get a Python project ready for `pulumi` commands. @@ -1790,7 +1791,7 @@ func (pt *ProgramTester) preparePythonProject(projinfo *engine.Projinfo) error { projinfo.Proj.Runtime.SetOption("virtualenv", "venv") projfile := filepath.Join(projinfo.Root, workspace.ProjectFile+".yaml") if err = projinfo.Proj.Save(projfile); err != nil { - return errors.Wrap(err, "saving project") + return fmt.Errorf("saving project: %w", err) } if err := pt.runVirtualEnvCommand("virtualenv-pip-install", @@ -1881,7 +1882,7 @@ func getVirtualenvBinPath(cwd, bin string) (string, error) { virtualenvBinPath = filepath.Join(cwd, "venv", "Scripts", fmt.Sprintf("%s.exe", bin)) } if info, err := os.Stat(virtualenvBinPath); err != nil || info.IsDir() { - return "", errors.Errorf("Expected %s to exist in virtual environment at %q", bin, virtualenvBinPath) + return "", fmt.Errorf("Expected %s to exist in virtual environment at %q", bin, virtualenvBinPath) } return virtualenvBinPath, nil } @@ -1927,7 +1928,7 @@ func (pt *ProgramTester) prepareGoProject(projinfo *engine.Projinfo) error { // Go programs are compiled, so we will compile the project first. goBin, err := pt.getGoBin() if err != nil { - return errors.Wrap(err, "locating `go` binary") + return fmt.Errorf("locating `go` binary: %w", err) } // Ensure GOPATH is known. @@ -1997,7 +1998,7 @@ func (pt *ProgramTester) prepareGoProject(projinfo *engine.Projinfo) error { func (pt *ProgramTester) prepareDotNetProject(projinfo *engine.Projinfo) error { dotNetBin, err := pt.getDotNetBin() if err != nil { - return errors.Wrap(err, "locating `dotnet` binary") + return fmt.Errorf("locating `dotnet` binary: %w", err) } cwd, _, err := projinfo.GetPwdMain() @@ -2015,10 +2016,10 @@ func (pt *ProgramTester) prepareDotNetProject(projinfo *engine.Projinfo) error { // dotnet add package requires a specific version in case of a pre-release, so we have to look it up. matches, err := filepath.Glob(filepath.Join(localNuget, dep+".?.*.nupkg")) if err != nil { - return errors.Wrap(err, "failed to find a local Pulumi NuGet package") + return fmt.Errorf("failed to find a local Pulumi NuGet package: %w", err) } if len(matches) != 1 { - return errors.Errorf("attempting to find a local Pulumi NuGet package yielded %v results", matches) + return fmt.Errorf("attempting to find a local Pulumi NuGet package yielded %v results", matches) } file := filepath.Base(matches[0]) r := strings.NewReplacer(dep+".", "", ".nupkg", "") @@ -2027,7 +2028,7 @@ func (pt *ProgramTester) prepareDotNetProject(projinfo *engine.Projinfo) error { err = pt.runCommand("dotnet-add-package", []string{dotNetBin, "add", "package", dep, "-v", version}, cwd) if err != nil { - return errors.Wrapf(err, "failed to add dependency on %s", dep) + return fmt.Errorf("failed to add dependency on %s: %w", dep, err) } } diff --git a/pkg/testing/integration/util.go b/pkg/testing/integration/util.go index 935030c1f..79f6e3c8e 100644 --- a/pkg/testing/integration/util.go +++ b/pkg/testing/integration/util.go @@ -16,7 +16,6 @@ package integration import ( "fmt" - "github.com/stretchr/testify/assert" "io" "io/ioutil" "net/http" @@ -28,7 +27,7 @@ import ( "testing" "time" - "github.com/pkg/errors" + "github.com/stretchr/testify/assert" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" @@ -42,8 +41,9 @@ func DecodeMapString(val string) (map[string]string, error) { for _, overrideClause := range strings.Split(val, ":") { data := strings.Split(overrideClause, "=") if len(data) != 2 { - return nil, errors.Errorf( - "could not decode %s as an override, should be of the form =", overrideClause) + return nil, fmt.Errorf( + "could not decode %s as an override, should be of the form =", + overrideClause) } packageName := data[0] packageVersion := data[1] @@ -73,7 +73,7 @@ func getCmdBin(loc *string, bin, def string) (string, error) { var err error *loc, err = exec.LookPath(bin) if err != nil { - return "", errors.Wrapf(err, "Expected to find `%s` binary on $PATH", bin) + return "", fmt.Errorf("Expected to find `%s` binary on $PATH: %w", bin, err) } } } @@ -95,13 +95,13 @@ const ( func writeCommandOutput(commandName, runDir string, output []byte) (string, error) { logFileDir := filepath.Join(runDir, commandOutputFolderName) if err := os.MkdirAll(logFileDir, 0700); err != nil { - return "", errors.Wrapf(err, "Failed to create '%s'", logFileDir) + return "", fmt.Errorf("Failed to create '%s': %w", logFileDir, err) } logFile := filepath.Join(logFileDir, commandName+uniqueSuffix()+".log") if err := ioutil.WriteFile(logFile, output, 0600); err != nil { - return "", errors.Wrapf(err, "Failed to write '%s'", logFile) + return "", fmt.Errorf("Failed to write '%s': %w", logFile, err) } return logFile, nil diff --git a/pkg/util/tracing/tracing.go b/pkg/util/tracing/tracing.go index 29e0b622b..62588672e 100644 --- a/pkg/util/tracing/tracing.go +++ b/pkg/util/tracing/tracing.go @@ -14,7 +14,9 @@ package tracing -import "context" +import ( + "context" +) // tracingOptionsKey is the value used as the context key for TracingOptions. var tracingOptionsKey struct{} diff --git a/pkg/util/validation/stack.go b/pkg/util/validation/stack.go index c8295701c..c50bdf6c5 100644 --- a/pkg/util/validation/stack.go +++ b/pkg/util/validation/stack.go @@ -15,9 +15,10 @@ package validation import ( + "errors" + "fmt" "regexp" - "github.com/pkg/errors" "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" ) @@ -37,10 +38,10 @@ func validateStackTagName(s string) error { const maxTagName = 40 if len(s) == 0 { - return errors.Errorf("invalid stack tag %q", s) + return fmt.Errorf("invalid stack tag %q", s) } if len(s) > maxTagName { - return errors.Errorf("stack tag %q is too long (max length %d characters)", s, maxTagName) + return fmt.Errorf("stack tag %q is too long (max length %d characters)", s, maxTagName) } var tagNameRE = regexp.MustCompile("^[a-zA-Z0-9-_.:]{1,40}$") @@ -59,7 +60,7 @@ func ValidateStackTags(tags map[apitype.StackTagName]string) error { return err } if len(v) > maxTagValue { - return errors.Errorf("stack tag %q value is too long (max length %d characters)", t, maxTagValue) + return fmt.Errorf("stack tag %q value is too long (max length %d characters)", t, maxTagValue) } } @@ -71,7 +72,7 @@ func ValidateStackTags(tags map[apitype.StackTagName]string) error { func ValidateStackProperties(stack string, tags map[apitype.StackTagName]string) error { const maxStackName = 100 // Derived from the regex in validateStackName. if len(stack) > maxStackName { - return errors.Errorf("stack name too long (max length %d characters)", maxStackName) + return fmt.Errorf("stack name too long (max length %d characters)", maxStackName) } if err := validateStackName(stack); err != nil { return err diff --git a/sdk/dotnet/Makefile b/sdk/dotnet/Makefile index 57f0286e4..eb4f3ae80 100644 --- a/sdk/dotnet/Makefile +++ b/sdk/dotnet/Makefile @@ -14,6 +14,10 @@ include ../../build/common.mk # `test_all` without the dependencies. TEST_ALL_DEPS = install +ensure:: + # We want to dotnet restore all projects on startup so that omnisharp doesn't complain about lots of missing types on startup. + dotnet restore dotnet.sln + build:: # From the nuget docs: # diff --git a/sdk/dotnet/Pulumi.Tests/AssertEx.cs b/sdk/dotnet/Pulumi.Tests/AssertEx.cs index 83fee9957..2be65dc25 100644 --- a/sdk/dotnet/Pulumi.Tests/AssertEx.cs +++ b/sdk/dotnet/Pulumi.Tests/AssertEx.cs @@ -1,6 +1,7 @@ // Copyright 2016-2019, Pulumi Corporation using System.Collections.Generic; +using System.Linq; using Xunit; namespace Pulumi.Tests diff --git a/sdk/dotnet/Pulumi.Tests/Serialization/ConverterTests.cs b/sdk/dotnet/Pulumi.Tests/Serialization/ConverterTests.cs index 745d88251..f80b6f910 100644 --- a/sdk/dotnet/Pulumi.Tests/Serialization/ConverterTests.cs +++ b/sdk/dotnet/Pulumi.Tests/Serialization/ConverterTests.cs @@ -19,7 +19,7 @@ namespace Pulumi.Tests.Serialization Fields = { { Constants.SpecialSigKey, new Value { StringValue = Constants.SpecialSecretSig } }, - { Constants.SecretValueName, value }, + { Constants.ValueName, value }, } } }; diff --git a/sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs b/sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs new file mode 100644 index 000000000..2331d0d93 --- /dev/null +++ b/sdk/dotnet/Pulumi.Tests/Serialization/MarshalOutputTests.cs @@ -0,0 +1,141 @@ +// Copyright 2016-2021, Pulumi Corporation + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Pulumi.Serialization; +using Xunit; + +namespace Pulumi.Tests.Serialization +{ + public class MarshalOutputTests : PulumiTest + { + public static IEnumerable BasicSerializeData => + from value in new object?[] + { + null, + 0.0, + 1.0, + "", + "hi", + ImmutableDictionary.Empty, + ImmutableArray.Empty, + } + from deps in new[] { Array.Empty(), new[] { "fakeURN1", "fakeURN2" } } + from isKnown in new[] { true, false } + from isSecret in new[] { true, false } + select new object[] { value, deps, isKnown, isSecret }; + + [Theory] + [MemberData(nameof(BasicSerializeData))] + public static Task TestBasicSerialize(object? value, string[] deps, bool isKnown, bool isSecret) => RunInNormal(async () => + { + var resources = ImmutableHashSet.CreateRange(deps.Select(d => new DependencyResource(d))); + var data = OutputData.Create(resources, value, isKnown, isSecret); + var input = new Output(Task.FromResult(data)); + + var expected = isKnown && !isSecret && deps.Length == 0 + ? value + : CreateOutputValue(value, isKnown, isSecret, deps); + + var s = new Serializer(excessiveDebugOutput: false); + var actual = await s.SerializeAsync("", input, keepResources: true, keepOutputValues: true); + Assert.Equal(expected, actual); + }); + + public sealed class FooArgs : ResourceArgs + { + [Input("foo")] + public Input? Foo { get; set; } + } + + public sealed class BarArgs : ResourceArgs + { + [Input("foo")] + public Input? Foo { get; set; } + } + + public static IEnumerable SerializeData() => new object[][] + { + new object[] + { + new FooArgs { Foo = "hello" }, + ImmutableDictionary.Empty.Add("foo", "hello") + }, + new object[] + { + new FooArgs { Foo = Output.Create("hello") }, + ImmutableDictionary.Empty.Add("foo", "hello") + }, + new object[] + { + new FooArgs { Foo = Output.CreateSecret("hello") }, + ImmutableDictionary.Empty.Add("foo", CreateOutputValue("hello", isSecret: true)) + }, + new object[] + { + new List> { "hello" }, + ImmutableArray.Empty.Add("hello") + }, + new object[] + { + new List> { Output.Create("hello") }, + ImmutableArray.Empty.Add("hello") + }, + new object[] + { + new List> { Output.CreateSecret("hello") }, + ImmutableArray.Empty.Add(CreateOutputValue("hello", isSecret: true)) + }, + new object[] + { + new Dictionary> { { "foo", "hello" } }, + ImmutableDictionary.Empty.Add("foo", "hello") + }, + new object[] + { + new Dictionary> { { "foo", Output.Create("hello") } }, + ImmutableDictionary.Empty.Add("foo", "hello") + }, + new object[] + { + new Dictionary> { { "foo", Output.CreateSecret("hello") } }, + ImmutableDictionary.Empty.Add("foo", CreateOutputValue("hello", isSecret: true)) + }, + new object[] + { + new BarArgs { Foo = new FooArgs { Foo = "hello" } }, + ImmutableDictionary.Empty.Add("foo", + ImmutableDictionary.Empty.Add("foo", "hello")) + }, + new object[] + { + new BarArgs { Foo = new FooArgs { Foo = Output.Create("hello") } }, + ImmutableDictionary.Empty.Add("foo", + ImmutableDictionary.Empty.Add("foo", "hello")) + }, + }; + + [Theory] + [MemberData(nameof(SerializeData))] + public static Task TestSerialize(object input, object expected) => RunInNormal(async () => + { + var s = new Serializer(excessiveDebugOutput: false); + var actual = await s.SerializeAsync("", input, keepResources: true, keepOutputValues: true); + Assert.Equal(expected, actual); + }); + + private static ImmutableDictionary CreateOutputValue( + object? value, bool isKnown = true, bool isSecret = false, params string[] deps) + { + var b = ImmutableDictionary.CreateBuilder(); + b.Add(Constants.SpecialSigKey, Constants.SpecialOutputValueSig); + if (isKnown) b.Add(Constants.ValueName, value); + if (isSecret) b.Add(Constants.SecretName, isSecret); + if (deps.Length > 0) b.Add(Constants.DependenciesName, deps.ToImmutableArray()); + return b.ToImmutableDictionary(); + } + } +} diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment.cs b/sdk/dotnet/Pulumi/Deployment/Deployment.cs index 994b81bac..9b91e89a8 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment.cs @@ -192,7 +192,7 @@ namespace Pulumi { if (!this._featureSupport.ContainsKey(feature)) { - var request = new SupportsFeatureRequest {Id = feature }; + var request = new SupportsFeatureRequest { Id = feature }; var response = await this.Monitor.SupportsFeatureAsync(request).ConfigureAwait(false); this._featureSupport[feature] = response.HasSupport; } @@ -203,5 +203,16 @@ namespace Pulumi { return MonitorSupportsFeature("resourceReferences"); } + + /// + /// Check if the monitor supports the "outputValues" feature. + /// + internal Task MonitorSupportsOutputValues() + { + return MonitorSupportsFeature("outputValues"); + } + + // Because the secrets feature predates the Pulumi .NET SDK, we assume + // that the monitor supports secrets. } } diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_Call.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_Call.cs index fcfc36dc2..6e258b5d6 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment_Call.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_Call.cs @@ -53,8 +53,10 @@ namespace Pulumi } var (serialized, argDependencies) = await SerializeFilteredPropertiesAsync( - $"call:{token}", - argsDict, _ => true, keepResources: true).ConfigureAwait(false); + $"call:{token}", + argsDict, _ => true, + keepResources: true, + keepOutputValues: await MonitorSupportsOutputValues().ConfigureAwait(false)).ConfigureAwait(false); Log.Debug($"Call RPC prepared: token={token}" + (_excessiveDebugOutput ? $", obj={serialized}" : "")); diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_Invoke.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_Invoke.cs index 7e1347656..3b860e1c0 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment_Invoke.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_Invoke.cs @@ -148,7 +148,8 @@ namespace Pulumi label: $"invoke:{token}", args: argsDict, acceptKey: key => true, - keepResources: keepResources + keepResources: keepResources, + keepOutputValues: false ).ConfigureAwait(false); } diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_Prepare.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_Prepare.cs index 3edebd069..b2db07cb5 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment_Prepare.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_Prepare.cs @@ -33,7 +33,8 @@ namespace Pulumi await SerializeResourcePropertiesAsync( label, dictionary, - await this.MonitorSupportsResourceReferences().ConfigureAwait(false)).ConfigureAwait(false); + await this.MonitorSupportsResourceReferences().ConfigureAwait(false), + keepOutputValues: remote && await MonitorSupportsOutputValues().ConfigureAwait(false)).ConfigureAwait(false); LogExcessive($"Serialized properties: t={type}, name={name}, custom={custom}, remote={remote}"); // Wait for the parent to complete. @@ -123,7 +124,7 @@ namespace Pulumi private static Task> GatherExplicitDependenciesAsync(InputList resources) => resources.ToOutput().GetValueAsync(whenUnknown: ImmutableArray.Empty); - private static async Task> GetAllTransitivelyReferencedResourceUrnsAsync( + internal static async Task> GetAllTransitivelyReferencedResourceUrnsAsync( HashSet resources) { // Go through 'resources', but transitively walk through **Component** resources, collecting any @@ -152,8 +153,10 @@ namespace Pulumi // * Comp3 and Cust5 because Comp3 is a child of a remote component resource var transitivelyReachableResources = GetTransitivelyReferencedChildResourcesOfComponentResources(resources); - var transitivelyReachableCustomResources = transitivelyReachableResources.Where(res => { - switch (res) { + var transitivelyReachableCustomResources = transitivelyReachableResources.Where(res => + { + switch (res) + { case CustomResource _: return true; case ComponentResource component: return component.remote; default: return false; // Unreachable diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_RegisterResourceOutputs.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_RegisterResourceOutputs.cs index ad0366828..ae5e1ed4c 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment_RegisterResourceOutputs.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_RegisterResourceOutputs.cs @@ -28,8 +28,8 @@ namespace Pulumi var urn = await resource.Urn.GetValueAsync(whenUnknown: default!).ConfigureAwait(false); var props = await outputs.GetValueAsync(whenUnknown: default!).ConfigureAwait(false); - var serialized = await SerializeAllPropertiesAsync( - opLabel, props, await MonitorSupportsResourceReferences().ConfigureAwait(false)).ConfigureAwait(false); + var keepResources = await MonitorSupportsResourceReferences().ConfigureAwait(false); + var serialized = await SerializeAllPropertiesAsync(opLabel, props, keepResources).ConfigureAwait(false); Log.Debug($"RegisterResourceOutputs RPC prepared: urn={urn}" + (_excessiveDebugOutput ? $", outputs={JsonFormatter.Default.Format(serialized)}" : "")); diff --git a/sdk/dotnet/Pulumi/Deployment/Deployment_Serialization.cs b/sdk/dotnet/Pulumi/Deployment/Deployment_Serialization.cs index 22e88177a..35b6efa90 100644 --- a/sdk/dotnet/Pulumi/Deployment/Deployment_Serialization.cs +++ b/sdk/dotnet/Pulumi/Deployment/Deployment_Serialization.cs @@ -20,16 +20,20 @@ namespace Pulumi /// to registerResource. /// private static Task SerializeResourcePropertiesAsync( - string label, IDictionary args, bool keepResources) + string label, IDictionary args, bool keepResources, bool keepOutputValues) { return SerializeFilteredPropertiesAsync( - label, args, key => key != Constants.IdPropertyName && key != Constants.UrnPropertyName, keepResources); + label, args, + key => key != Constants.IdPropertyName && key != Constants.UrnPropertyName, + keepResources, keepOutputValues: keepOutputValues); } private static async Task SerializeAllPropertiesAsync( - string label, IDictionary args, bool keepResources) + string label, IDictionary args, bool keepResources, bool keepOutputValues = false) { - var result = await SerializeFilteredPropertiesAsync(label, args, _ => true, keepResources).ConfigureAwait(false); + var result = await SerializeFilteredPropertiesAsync( + label, args, _ => true, + keepResources, keepOutputValues).ConfigureAwait(false); return result.Serialized; } @@ -38,10 +42,20 @@ namespace Pulumi /// awaiting all interior promises for properties with keys that match the provided filter, /// creating a reasonable POCO object that can be remoted over to registerResource. /// + /// + /// label + /// args + /// acceptKey + /// keepResources + /// + /// Specifies if we should marshal output values. It is the callers + /// responsibility to ensure that the monitor supports the OutputValues + /// feature. + /// private static async Task SerializeFilteredPropertiesAsync( - string label, IDictionary args, Predicate acceptKey, bool keepResources) + string label, IDictionary args, Predicate acceptKey, bool keepResources, bool keepOutputValues) { - var result = await SerializeFilteredPropertiesRawAsync(label, args, acceptKey, keepResources); + var result = await SerializeFilteredPropertiesRawAsync(label, args, acceptKey, keepResources, keepOutputValues); return result.ToSerializationResult(); } @@ -50,7 +64,7 @@ namespace Pulumi /// last step of encoding the value into a Protobuf form. /// private static async Task SerializeFilteredPropertiesRawAsync( - string label, IDictionary args, Predicate acceptKey, bool keepResources) + string label, IDictionary args, Predicate acceptKey, bool keepResources, bool keepOutputValues) { var propertyToDependentResources = ImmutableDictionary.CreateBuilder>(); var result = ImmutableDictionary.CreateBuilder(); @@ -61,7 +75,7 @@ namespace Pulumi { // We treat properties with null values as if they do not exist. var serializer = new Serializer(_excessiveDebugOutput); - var v = await serializer.SerializeAsync($"{label}.{key}", val, keepResources).ConfigureAwait(false); + var v = await serializer.SerializeAsync($"{label}.{key}", val, keepResources, keepOutputValues).ConfigureAwait(false); if (v != null) { result[key] = v; diff --git a/sdk/dotnet/Pulumi/Serialization/Constants.cs b/sdk/dotnet/Pulumi/Serialization/Constants.cs index a688c907d..5fa589889 100644 --- a/sdk/dotnet/Pulumi/Serialization/Constants.cs +++ b/sdk/dotnet/Pulumi/Serialization/Constants.cs @@ -34,7 +34,14 @@ namespace Pulumi.Serialization /// public const string SpecialResourceSig = "5cf8f73096256a8f31e491e813e4eb8e"; - public const string SecretValueName = "value"; + /// + /// SpecialOutputValueSig is a randomly assigned hash used to identify outputs in maps. See sdk/go/common/resource/properties.go. + /// + public const string SpecialOutputValueSig = "d0e6a833031e9bbcd3f4e8bde6ca49a4"; + + public const string SecretName = "secret"; + public const string ValueName = "value"; + public const string DependenciesName = "dependencies"; public const string AssetTextName = "text"; public const string ArchiveAssetsName = "assets"; diff --git a/sdk/dotnet/Pulumi/Serialization/Deserializer.cs b/sdk/dotnet/Pulumi/Serialization/Deserializer.cs index 607deb62c..befeb64b4 100644 --- a/sdk/dotnet/Pulumi/Serialization/Deserializer.cs +++ b/sdk/dotnet/Pulumi/Serialization/Deserializer.cs @@ -100,7 +100,7 @@ namespace Pulumi.Serialization }); public static OutputData Deserialize(Value value) - => DeserializeCore(value, + => DeserializeCore(value, v => v.KindCase switch { Value.KindOneofCase.NumberValue => DeserializerDouble(v), @@ -120,7 +120,7 @@ namespace Pulumi.Serialization while (IsSpecialStruct(value, out var sig) && sig == Constants.SpecialSecretSig) { - if (!value.StructValue.Fields.TryGetValue(Constants.SecretValueName, out var secretValue)) + if (!value.StructValue.Fields.TryGetValue(Constants.ValueName, out var secretValue)) throw new InvalidOperationException("Secrets must have a field called 'value'"); isSecret = true; @@ -222,7 +222,8 @@ namespace Pulumi.Serialization throw new InvalidOperationException("Value was marked as a Resource, but did not conform to required shape."); } - if (!TryGetStringValue(value.StructValue.Fields, Constants.ResourceVersionName, out var version)) { + if (!TryGetStringValue(value.StructValue.Fields, Constants.ResourceVersionName, out var version)) + { version = ""; } @@ -231,7 +232,8 @@ namespace Pulumi.Serialization var qualifiedTypeParts = qualifiedType.Split('$'); var type = qualifiedTypeParts[^1]; - if (ResourcePackages.TryConstruct(type, version, urn, out resource)) { + if (ResourcePackages.TryConstruct(type, version, urn, out resource)) + { return true; } diff --git a/sdk/dotnet/Pulumi/Serialization/Serializer.cs b/sdk/dotnet/Pulumi/Serialization/Serializer.cs index 5c31f0a2e..dc3bfa6bd 100644 --- a/sdk/dotnet/Pulumi/Serialization/Serializer.cs +++ b/sdk/dotnet/Pulumi/Serialization/Serializer.cs @@ -1,4 +1,4 @@ -// Copyright 2016-2019, Pulumi Corporation +// Copyright 2016-2021, Pulumi Corporation using System; using System.Collections; @@ -21,7 +21,7 @@ namespace Pulumi.Serialization public Serializer(bool excessiveDebugOutput) { - this.DependentResources = new HashSet(); + DependentResources = new HashSet(); _excessiveDebugOutput = excessiveDebugOutput; } @@ -39,7 +39,7 @@ namespace Pulumi.Serialization /// s /// s /// s - /// s + /// /// /// /// Additionally, other more complex objects can be serialized as long as they are built @@ -67,7 +67,7 @@ namespace Pulumi.Serialization /// /// No other result type are allowed to be returned. /// - public async Task SerializeAsync(string ctx, object? prop, bool keepResources) + public async Task SerializeAsync(string ctx, object? prop, bool keepResources, bool keepOutputValues = false) { // IMPORTANT: // IMPORTANT: Keep this in sync with serializesPropertiesSync in invoke.ts @@ -87,10 +87,15 @@ namespace Pulumi.Serialization } if (prop is InputArgs args) - return await SerializeInputArgsAsync(ctx, args, keepResources).ConfigureAwait(false); + { + return await SerializeInputArgsAsync(ctx, args, keepResources, keepOutputValues).ConfigureAwait(false); + } if (prop is AssetOrArchive assetOrArchive) + { + // There's no need to pass keepOutputValues when serializing assets or archives. return await SerializeAssetOrArchiveAsync(ctx, assetOrArchive, keepResources).ConfigureAwait(false); + } if (prop is Task) { @@ -105,7 +110,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: Log.Debug($"Serialize property[{ctx}]: Recursing into IInput"); } - return await SerializeAsync(ctx, input.ToOutput(), keepResources).ConfigureAwait(false); + return await SerializeAsync(ctx, input.ToOutput(), keepResources, keepOutputValues).ConfigureAwait(false); } if (prop is IUnion union) @@ -115,7 +120,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: Log.Debug($"Serialize property[{ctx}]: Recursing into IUnion"); } - return await SerializeAsync(ctx, union.Value, keepResources).ConfigureAwait(false); + return await SerializeAsync(ctx, union.Value, keepResources, keepOutputValues).ConfigureAwait(false); } if (prop is JsonElement element) @@ -134,9 +139,9 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: { Log.Debug($"Serialize property[{ctx}]: Recursing into Output"); } - var data = await output.GetDataAsync().ConfigureAwait(false); - this.DependentResources.AddRange(data.Resources); + DependentResources.AddRange(data.Resources); + var propResources = new HashSet(data.Resources); // When serializing an Output, we will either serialize it as its resolved value or the "unknown value" // sentinel. We will do the former for all outputs created directly by user code (such outputs always @@ -144,15 +149,56 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: var isKnown = data.IsKnown; var isSecret = data.IsSecret; + var valueSerializer = new Serializer(_excessiveDebugOutput); + var value = await valueSerializer.SerializeAsync($"{ctx}.id", data.Value, keepResources, keepOutputValues: false).ConfigureAwait(false); + var promiseDeps = valueSerializer.DependentResources; + DependentResources.UnionWith(promiseDeps); + propResources.UnionWith(promiseDeps); + + if (keepOutputValues) + { + if (isKnown && !isSecret && propResources.Count == 0) + { + return value; + } + + var urnDeps = new HashSet(); + foreach (var resource in propResources) + { + var urnSerializer = new Serializer(_excessiveDebugOutput); + await urnSerializer.SerializeAsync($"{ctx} dependency", resource.Urn, keepResources, keepOutputValues: false).ConfigureAwait(false); + urnDeps.UnionWith(urnSerializer.DependentResources); + } + DependentResources.UnionWith(urnDeps); + propResources.UnionWith(urnDeps); + + var dependencies = await Deployment.GetAllTransitivelyReferencedResourceUrnsAsync(propResources).ConfigureAwait(false); + var builder = ImmutableDictionary.CreateBuilder(); + builder.Add(Constants.SpecialSigKey, Constants.SpecialOutputValueSig); + if (isKnown) + { + builder.Add(Constants.ValueName, value); + } + if (isSecret) + { + builder.Add(Constants.SecretName, isSecret); + } + if (dependencies.Count > 0) + { + builder.Add(Constants.DependenciesName, + dependencies.OrderBy(x => x, StringComparer.Ordinal).ToImmutableArray()); + } + return builder.ToImmutable(); + } + if (!isKnown) return Constants.UnknownValue; - var value = await SerializeAsync($"{ctx}.id", data.Value, keepResources).ConfigureAwait(false); if (isSecret) { var builder = ImmutableDictionary.CreateBuilder(); builder.Add(Constants.SpecialSigKey, Constants.SpecialSecretSig); - builder.Add(Constants.SecretValueName, value); + builder.Add(Constants.ValueName, value); return builder.ToImmutable(); } @@ -167,12 +213,12 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: Log.Debug($"Serialize property[{ctx}]: Encountered CustomResource"); } - this.DependentResources.Add(customResource); + DependentResources.Add(customResource); - var id = await SerializeAsync($"{ctx}.id", customResource.Id, keepResources).ConfigureAwait(false); + var id = await SerializeAsync($"{ctx}.id", customResource.Id, keepResources, keepOutputValues: false).ConfigureAwait(false); if (keepResources) { - var urn = await SerializeAsync($"{ctx}.urn", customResource.Urn, keepResources).ConfigureAwait(false); + var urn = await SerializeAsync($"{ctx}.urn", customResource.Urn, keepResources, keepOutputValues: false).ConfigureAwait(false); var builder = ImmutableDictionary.CreateBuilder(); builder.Add(Constants.SpecialSigKey, Constants.SpecialResourceSig); builder.Add(Constants.ResourceUrnName, urn); @@ -206,7 +252,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: Log.Debug($"Serialize property[{ctx}]: Encountered ComponentResource"); } - var urn = await SerializeAsync($"{ctx}.urn", componentResource.Urn, keepResources).ConfigureAwait(false); + var urn = await SerializeAsync($"{ctx}.urn", componentResource.Urn, keepResources, keepOutputValues: false).ConfigureAwait(false); if (keepResources) { var builder = ImmutableDictionary.CreateBuilder(); @@ -218,10 +264,14 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: } if (prop is IDictionary dictionary) - return await SerializeDictionaryAsync(ctx, dictionary, keepResources).ConfigureAwait(false); + { + return await SerializeDictionaryAsync(ctx, dictionary, keepResources, keepOutputValues).ConfigureAwait(false); + } if (prop is IList list) - return await SerializeListAsync(ctx, list, keepResources).ConfigureAwait(false); + { + return await SerializeListAsync(ctx, list, keepResources, keepOutputValues).ConfigureAwait(false); + } if (prop is Enum e && e.GetTypeCode() == TypeCode.Int32) { @@ -257,27 +307,27 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: case JsonValueKind.False: return element.GetBoolean(); case JsonValueKind.Array: - { - var result = ImmutableArray.CreateBuilder(); - var index = 0; - foreach (var child in element.EnumerateArray()) { - result.Add(SerializeJson($"{ctx}[{index}]", child)); - index++; - } + var result = ImmutableArray.CreateBuilder(); + var index = 0; + foreach (var child in element.EnumerateArray()) + { + result.Add(SerializeJson($"{ctx}[{index}]", child)); + index++; + } - return result.ToImmutable(); - } + return result.ToImmutable(); + } case JsonValueKind.Object: - { - var result = ImmutableDictionary.CreateBuilder(); - foreach (var x in element.EnumerateObject()) { - result[x.Name] = SerializeJson($"{ctx}.{x.Name}", x.Value); - } + var result = ImmutableDictionary.CreateBuilder(); + foreach (var x in element.EnumerateObject()) + { + result[x.Name] = SerializeJson($"{ctx}.{x.Name}", x.Value); + } - return result.ToImmutable(); - } + return result.ToImmutable(); + } default: throw new InvalidOperationException($"Unknown {nameof(JsonElement)}.{nameof(JsonElement.ValueKind)}: {element.ValueKind}"); } @@ -296,7 +346,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: throw new InvalidOperationException("Cannot serialize invalid archive"); var propName = assetOrArchive.PropName; - var value = await SerializeAsync(ctx + "." + propName, assetOrArchive.Value, keepResources).ConfigureAwait(false); + var value = await SerializeAsync(ctx + "." + propName, assetOrArchive.Value, keepResources, keepOutputValues: false).ConfigureAwait(false); var builder = ImmutableDictionary.CreateBuilder(); builder.Add(Constants.SpecialSigKey, assetOrArchive.SigKey); @@ -304,7 +354,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: return builder.ToImmutable(); } - private async Task> SerializeInputArgsAsync(string ctx, InputArgs args, bool keepResources) + private async Task> SerializeInputArgsAsync(string ctx, InputArgs args, bool keepResources, bool keepOutputValues) { if (_excessiveDebugOutput) { @@ -312,10 +362,10 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: } var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false); - return await SerializeDictionaryAsync(ctx, dictionary, keepResources).ConfigureAwait(false); + return await SerializeDictionaryAsync(ctx, dictionary, keepResources, keepOutputValues).ConfigureAwait(false); } - private async Task> SerializeListAsync(string ctx, IList list, bool keepResources) + private async Task> SerializeListAsync(string ctx, IList list, bool keepResources, bool keepOutputValues) { if (_excessiveDebugOutput) { @@ -330,13 +380,13 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: Log.Debug($"Serialize property[{ctx}]: array[{i}] element"); } - result.Add(await SerializeAsync($"{ctx}[{i}]", list[i], keepResources).ConfigureAwait(false)); + result.Add(await SerializeAsync($"{ctx}[{i}]", list[i], keepResources, keepOutputValues).ConfigureAwait(false)); } return result.MoveToImmutable(); } - private async Task> SerializeDictionaryAsync(string ctx, IDictionary dictionary, bool keepResources) + private async Task> SerializeDictionaryAsync(string ctx, IDictionary dictionary, bool keepResources, bool keepOutputValues) { if (_excessiveDebugOutput) { @@ -359,7 +409,7 @@ $"Tasks are not allowed inside ResourceArgs. Please wrap your Task in an Output: // When serializing an object, we omit any keys with null values. This matches // JSON semantics. - var v = await SerializeAsync($"{ctx}.{stringKey}", dictionary[stringKey], keepResources).ConfigureAwait(false); + var v = await SerializeAsync($"{ctx}.{stringKey}", dictionary[stringKey], keepResources, keepOutputValues).ConfigureAwait(false); if (v != null) { result[stringKey] = v; diff --git a/sdk/go/common/workspace/creds.go b/sdk/go/common/workspace/creds.go index f5d255918..ea886337f 100644 --- a/sdk/go/common/workspace/creds.go +++ b/sdk/go/common/workspace/creds.go @@ -251,3 +251,123 @@ func StoreCredentials(creds Credentials) error { return nil } + +type BackendConfig struct { + DefaultOrg string `json:"defaultOrg,omitempty"` // The default org for this backend config. +} + +type PulumiConfig struct { + BackendConfig map[string]BackendConfig `json:"backends,omitempty"` // a map of arbitrary backends configs. +} + +func getConfigFilePath() (string, error) { + // Allow the folder we use to store config in to be overridden by tests + pulumiFolder := os.Getenv(PulumiCredentialsPathEnvVar) + if pulumiFolder == "" { + folder, err := GetPulumiHomeDir() + if err != nil { + return "", errors.Wrapf(err, "failed to get the home path") + } + pulumiFolder = folder + } + + err := os.MkdirAll(pulumiFolder, 0700) + if err != nil { + return "", errors.Wrapf(err, "failed to create '%s'", pulumiFolder) + } + + return filepath.Join(pulumiFolder, "config.json"), nil +} + +func GetPulumiConfig() (PulumiConfig, error) { + configFile, err := getConfigFilePath() + if err != nil { + return PulumiConfig{}, err + } + + c, err := ioutil.ReadFile(configFile) + if err != nil { + if os.IsNotExist(err) { + return PulumiConfig{}, nil + } + return PulumiConfig{}, errors.Wrapf(err, "reading '%s'", configFile) + } + + var config PulumiConfig + if err = json.Unmarshal(c, &config); err != nil { + return PulumiConfig{}, errors.Wrapf(err, "failed to read Pulumi config file") + } + + return config, nil +} + +func StorePulumiConfig(config PulumiConfig) error { + configFile, err := getConfigFilePath() + if err != nil { + return err + } + + raw, err := json.MarshalIndent(config, "", " ") + if err != nil { + return errors.Wrapf(err, "marshalling config object") + } + + // Use a temporary file and atomic os.Rename to ensure the file contents are + // updated atomically to ensure concurrent `pulumi` CLI operations are safe. + tempConfigFile, err := ioutil.TempFile(filepath.Dir(configFile), "config-*.json") + if err != nil { + return err + } + _, err = tempConfigFile.Write(raw) + if err != nil { + return err + } + err = tempConfigFile.Close() + if err != nil { + return err + } + err = os.Rename(tempConfigFile.Name(), configFile) + if err != nil { + contract.IgnoreError(os.Remove(tempConfigFile.Name())) + return err + } + + return nil +} + +func SetBackendConfigDefaultOrg(backendURL, defaultOrg string) error { + config, err := GetPulumiConfig() + if err != nil && !os.IsNotExist(err) { + return err + } + + if config.BackendConfig == nil { + config.BackendConfig = make(map[string]BackendConfig) + } + + config.BackendConfig[backendURL] = BackendConfig{ + DefaultOrg: defaultOrg, + } + + return StorePulumiConfig(config) +} + +func GetBackendConfigDefaultOrg() (string, error) { + config, err := GetPulumiConfig() + if err != nil && !os.IsNotExist(err) { + return "", err + } + + backendURL, err := GetCurrentCloudURL() + if err != nil { + return "", err + } + + if beConfig, ok := config.BackendConfig[backendURL]; ok { + if beConfig.DefaultOrg != "" { + return beConfig.DefaultOrg, nil + } + } + + return "", nil +} diff --git a/sdk/go/common/workspace/workspace.go b/sdk/go/common/workspace/workspace.go index 6df82440e..0aaeedd5e 100644 --- a/sdk/go/common/workspace/workspace.go +++ b/sdk/go/common/workspace/workspace.go @@ -1,4 +1,4 @@ -// Copyright 2016-2018, Pulumi Corporation. +// Copyright 2016-2021, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import ( "crypto/sha1" "encoding/hex" "encoding/json" + "fmt" "io/ioutil" "os" "path/filepath" @@ -105,7 +106,7 @@ func NewFrom(dir string) (W, error) { err = w.readSettings() if err != nil { - return nil, err + return nil, fmt.Errorf("unable to read workspace settings: %w", err) } upsertIntoCache(dir, w) @@ -139,8 +140,30 @@ func (pw *projectWorkspace) Save() error { if err != nil { return err } + return atomicWriteFile(settingsFile, b) +} - return ioutil.WriteFile(settingsFile, b, 0600) +// atomicWriteFile provides a rename based atomic write through a temporary file. +func atomicWriteFile(path string, b []byte) error { + tmp, err := ioutil.TempFile(filepath.Dir(path), filepath.Base(path)) + if err != nil { + return errors.Wrapf(err, "failed to create temporary file %s", path) + } + defer func() { contract.Ignore(os.Remove(tmp.Name())) }() + + if err = tmp.Chmod(0600); err != nil { + return errors.Wrap(err, "failed to set temporary file permission") + } + if _, err = tmp.Write(b); err != nil { + return errors.Wrap(err, "failed to write to temporary file") + } + if err = tmp.Sync(); err != nil { + return err + } + if err = tmp.Close(); err != nil { + return err + } + return os.Rename(tmp.Name(), path) } func (pw *projectWorkspace) readSettings() error { @@ -159,7 +182,7 @@ func (pw *projectWorkspace) readSettings() error { err = json.Unmarshal(b, &settings) if err != nil { - return err + return errors.Wrapf(err, "could not parse file %s", settingsPath) } pw.settings = &settings diff --git a/tests/integration/targets/delete_targets_many_deps/Pulumi.yaml b/tests/integration/targets/delete_targets_many_deps/Pulumi.yaml new file mode 100644 index 000000000..efe6aa848 --- /dev/null +++ b/tests/integration/targets/delete_targets_many_deps/Pulumi.yaml @@ -0,0 +1,2 @@ +name: delete_targets_many_deps +runtime: nodejs diff --git a/tests/integration/targets/delete_targets_many_deps/index.ts b/tests/integration/targets/delete_targets_many_deps/index.ts new file mode 100644 index 000000000..56a8c9b37 --- /dev/null +++ b/tests/integration/targets/delete_targets_many_deps/index.ts @@ -0,0 +1,47 @@ +// Copyright 2021, Pulumi Corporation. All rights reserved. + +import * as pulumi from "@pulumi/pulumi"; + +interface MyResourceArgs { + input?: pulumi.Input | undefined; +} + +class MyResource extends pulumi.dynamic.Resource { + static latestId: number = 0; + + constructor(name, args?: MyResourceArgs, opts?: pulumi.CustomResourceOptions) { + super({ + async create(inputs: any) { + return { id: (MyResource.latestId++).toString() }; + }, + }, name, args || {}, opts); + } +} + +// Create a chain of resources, such that attempting to delete +// one will fail due to numerous dependency violations. This includes +// both implicit and explicit, as well as parent/child, dependencies. + +// A +// B (impl depends on A) +// C (expl depends on A) +// D (impl depends on B) +// E (expl depends on B) +// F (child of A) +// G (child of B) +// H (expl depends on A, B, impl depends on C, D, child of F) + +const a = new MyResource("a"); +const b = new MyResource("b", { input: a.urn }); +const c = new MyResource("c", { }, { dependsOn: a }); +const d = new MyResource("d", { input: b.urn }); +const e = new MyResource("e", { }, { dependsOn: b }); +const f = new MyResource("f", { }, { parent: a }); +const g = new MyResource("g", { }, { parent: b }); +const h = new MyResource("h", + { input: pulumi.all(([c.urn, d.urn])).apply(([curn, _])=>curn) }, + { + dependsOn: [a, b], + parent: f, + }, +); diff --git a/tests/integration/targets/delete_targets_many_deps/package.json b/tests/integration/targets/delete_targets_many_deps/package.json new file mode 100644 index 000000000..e860e4d2a --- /dev/null +++ b/tests/integration/targets/delete_targets_many_deps/package.json @@ -0,0 +1,12 @@ +{ + "name": "delete_targets_many_deps", + "license": "Apache-2.0", + "main": "bin/index.js", + "typings": "bin/index.d.ts", + "devDependencies": { + "typescript": "^2.5.3" + }, + "peerDependencies": { + "@pulumi/pulumi": "latest" + } +} diff --git a/tests/integration/targets/targets_test.go b/tests/integration/targets/targets_test.go index 4fac125c8..e1957fb2c 100644 --- a/tests/integration/targets/targets_test.go +++ b/tests/integration/targets/targets_test.go @@ -3,6 +3,7 @@ package ints import ( + "fmt" "os" "path" "strings" @@ -10,8 +11,10 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/resource" ptesting "github.com/pulumi/pulumi/sdk/v3/go/common/testing" + "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/common/util/fsutil" + "github.com/stretchr/testify/assert" ) func TestUntargetedCreateDuringTargetedUpdate(t *testing.T) { @@ -48,3 +51,56 @@ func TestUntargetedCreateDuringTargetedUpdate(t *testing.T) { e.RunCommand("pulumi", "destroy", "--skip-preview", "--non-interactive", "--yes") e.RunCommand("pulumi", "stack", "rm", "--yes") } + +func TestDeleteManyTargets(t *testing.T) { + if os.Getenv("PULUMI_ACCESS_TOKEN") == "" { + t.Skipf("Skipping: PULUMI_ACCESS_TOKEN is not set") + } + + e := ptesting.NewEnvironment(t) + defer func() { + if !t.Failed() { + e.DeleteEnvironment() + } + }() + + // First just spin up the project. + projName := "delete_targets_many_deps" + stackName, err := resource.NewUniqueHex("test-", 8, -1) + contract.AssertNoErrorf(err, "resource.NewUniqueHex should not fail with no maximum length is set") + e.ImportDirectory(projName) + e.RunCommand("pulumi", "stack", "init", stackName) + e.RunCommand("yarn", "install") + e.RunCommand("yarn", "link", "@pulumi/pulumi") + e.RunCommand("pulumi", "up", "--non-interactive", "--skip-preview", "--yes") + + // Create a handy mkURN func to create URNs for dynamic resources in this project/stack. + resourceType := tokens.Type("pulumi-nodejs:dynamic:Resource") + mkURNStr := func(resourceName tokens.QName, parentType tokens.Type) string { + return string(resource.NewURN( + tokens.QName(stackName), tokens.PackageName(projName), parentType, resourceType, resourceName)) + } + + // Attempt to destroy the root-most node. It should fail and the error text should + // mention every one of the nodes in the entire graph (since they all transitively depend on a). + stdout, _ := e.RunCommandExpectError("pulumi", "destroy", "--skip-preview", "--yes", "--non-interactive", + "--target", mkURNStr("a", "")) + assert.Contains(t, stdout, mkURNStr("b", "")) + assert.Contains(t, stdout, mkURNStr("c", "")) + assert.Contains(t, stdout, mkURNStr("d", "")) + assert.Contains(t, stdout, mkURNStr("e", "")) + assert.Contains(t, stdout, mkURNStr("f", resourceType)) + assert.Contains(t, stdout, mkURNStr("g", resourceType)) + + // Destroy the leaf-most node. This should work just fine. + e.RunCommand("pulumi", "destroy", "--skip-preview", "--yes", "--non-interactive", + "--target", mkURNStr("h", tokens.Type(fmt.Sprintf("%[1]s$%[1]s", resourceType)))) + + // Finally, go back and try to delete the root-most node, but clean up the transitive closure. + e.RunCommand("pulumi", "destroy", "--skip-preview", "--yes", "--non-interactive", + "--target", mkURNStr("a", ""), "--target-dependents") + + // Finally clean up the entire stack. + e.RunCommand("pulumi", "destroy", "--skip-preview", "--yes", "--non-interactive") + e.RunCommand("pulumi", "stack", "rm", "--yes") +}