[cli] Disable permalinks to the update details page when using elf-managed backends (S3, Azure, GCS) (#6251)

Fixes: #4029
Fixes: #3537

Should the user want to get permalinks when using a self-managed backend, they can pass a flag:
```
$ pulumi up --suppress-permalink false
```

Permalinks for these self-managed backends will be suppressed on `update`, `preview`, `destroy`,
`import` and `refresh` operations.
This commit is contained in:
Paul Stack 2021-02-19 23:55:35 +00:00 committed by GitHub
parent f7397bb798
commit 1731053b18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 156 additions and 42 deletions

View file

@ -11,6 +11,13 @@ CHANGELOG
- [sdk/dotnet] F# API to specify stack options. - [sdk/dotnet] F# API to specify stack options.
[#5077](https://github.com/pulumi/pulumi/pull/5077) [#5077](https://github.com/pulumi/pulumi/pull/5077)
- [cli] Disable permalinks to the update details page when using self-managed backends (S3, Azure, GCS). Should the user
want to get permalinks when using a self backend, they can pass a flag:
`pulumi up --suppress-permalink false`.
Permalinks for these self-managed backends will be suppressed on `update`, `preview`, `destroy`, `import` and `refresh`
operations.
[#6251](https://github.com/pulumi/pulumi/pull/6251)
### Bug Fixes ### Bug Fixes
- [sdk/nodejs] Don't error when loading multiple copies of the same version of a Node.js - [sdk/nodejs] Don't error when loading multiple copies of the same version of a Node.js
@ -65,7 +72,7 @@ CHANGELOG
- [sdk/python] Fixed a bug in `contains_unknowns` where outputs with a property named "values" failed with a TypeError. - [sdk/python] Fixed a bug in `contains_unknowns` where outputs with a property named "values" failed with a TypeError.
[#6264](https://github.com/pulumi/pulumi/pull/6264) [#6264](https://github.com/pulumi/pulumi/pull/6264)
- [sdk/python] Allowed keyword args in Output.all() to create a dict. - [sdk/python] Allowed keyword args in Output.all() to create a dict.
[#6269](https://github.com/pulumi/pulumi/pull/6269) [#6269](https://github.com/pulumi/pulumi/pull/6269)
@ -75,7 +82,6 @@ CHANGELOG
- [automation/python] Fixed a bug in nested configuration parsing. - [automation/python] Fixed a bug in nested configuration parsing.
[#6349](https://github.com/pulumi/pulumi/pull/6349) [#6349](https://github.com/pulumi/pulumi/pull/6349)
## 2.20.0 (2021-02-03) ## 2.20.0 (2021-02-03)
- [sdk/python] Fix `Output.from_input` to unwrap nested output values in input types (args classes), which addresses - [sdk/python] Fix `Output.from_input` to unwrap nested output values in input types (args classes), which addresses

View file

@ -599,18 +599,25 @@ func (b *localBackend) apply(
} else { } else {
link, err = b.bucket.SignedURL(context.TODO(), b.stackPath(stackName), nil) link, err = b.bucket.SignedURL(context.TODO(), b.stackPath(stackName), nil)
if err != nil { if err != nil {
// set link to be empty to when there is an error to hide use of Permalinks
link = ""
// we log a warning here rather then returning an error to avoid exiting // we log a warning here rather then returning an error to avoid exiting
// pulumi with an error code. // pulumi with an error code.
// printing a statefile perma link happens after all the providers have finished // printing a statefile perma link happens after all the providers have finished
// deploying the infrastructure, failing the pulumi update because there was a // deploying the infrastructure, failing the pulumi update because there was a
// problem printing a statefile perma link can be missleading in automated CI environments. // problem printing a statefile perma link can be missleading in automated CI environments.
cmdutil.Diag().Warningf(diag.Message("", "Could not get signed url for stack location: %v"), err) cmdutil.Diag().Warningf(diag.Message("", "Unable to create signed url for current backend to "+
"create a Permalink. Please visit https://www.pulumi.com/docs/troubleshooting/ "+
"for more information\n"))
} }
} }
fmt.Printf(op.Opts.Display.Color.Colorize( if link != "" {
colors.SpecHeadline+"Permalink: "+ fmt.Printf(op.Opts.Display.Color.Colorize(
colors.Underline+colors.BrightBlue+"%s"+colors.Reset+"\n"), link) colors.SpecHeadline+"Permalink: "+
colors.Underline+colors.BrightBlue+"%s"+colors.Reset+"\n"), link)
}
} }
return changes, nil return changes, nil

View file

@ -5,9 +5,6 @@ import (
"encoding/json" "encoding/json"
"os" "os"
"github.com/pulumi/pulumi/sdk/v2/go/common/diag"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/cmdutil"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
"gocloud.dev/blob/gcsblob" "gocloud.dev/blob/gcsblob"
@ -68,11 +65,6 @@ func GoogleCredentialsMux(ctx context.Context) (*blob.URLMux, error) {
if err == nil && account.ClientEmail != "" && account.PrivateKey != "" { if err == nil && account.ClientEmail != "" && account.PrivateKey != "" {
options.GoogleAccessID = account.ClientEmail options.GoogleAccessID = account.ClientEmail
options.PrivateKey = []byte(account.PrivateKey) options.PrivateKey = []byte(account.PrivateKey)
} else {
cmdutil.Diag().Warningf(diag.Message("",
"Pulumi will not be able to print a statefile permalink using these credentials. "+
"Neither a GoogleAccessID or PrivateKey are available. "+
"Try using a GCP Service Account."))
} }
blobmux := &blob.URLMux{} blobmux := &blob.URLMux{}

View file

@ -46,7 +46,7 @@ func newDestroyCmd() *cobra.Command {
var showSames bool var showSames bool
var skipPreview bool var skipPreview bool
var suppressOutputs bool var suppressOutputs bool
var suppressPermaLink bool var suppressPermaLink string
var yes bool var yes bool
var targets *[]string var targets *[]string
var targetDependents bool var targetDependents bool
@ -86,13 +86,33 @@ func newDestroyCmd() *cobra.Command {
ShowReplacementSteps: showReplacementSteps, ShowReplacementSteps: showReplacementSteps,
ShowSameResources: showSames, ShowSameResources: showSames,
SuppressOutputs: suppressOutputs, SuppressOutputs: suppressOutputs,
SuppressPermaLink: suppressPermaLink,
IsInteractive: interactive, IsInteractive: interactive,
Type: displayType, Type: displayType,
EventLogPath: eventLogPath, EventLogPath: eventLogPath,
Debug: debug, Debug: debug,
} }
// we only suppress permalinks if the user passes true. the default is an empty string
// which we pass as 'false'
//nolint:goconst
if suppressPermaLink == "true" {
opts.Display.SuppressPermaLink = true
} else {
opts.Display.SuppressPermaLink = false
}
filestateBackend, err := isFilestateBackend(opts.Display)
if err != nil {
return result.FromError(err)
}
// by default, we are going to suppress the permalink when using self-managed backends
// this can be re-enabled by explicitly passing "false" to the `supppress-permalink` flag
//nolint:goconst
if suppressPermaLink != "false" && filestateBackend {
opts.Display.SuppressPermaLink = true
}
s, err := requireStack(stack, false, opts.Display, true /*setCurrent*/) s, err := requireStack(stack, false, opts.Display, true /*setCurrent*/)
if err != nil { if err != nil {
return result.FromError(err) return result.FromError(err)
@ -200,9 +220,10 @@ func newDestroyCmd() *cobra.Command {
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&suppressOutputs, "suppress-outputs", false, &suppressOutputs, "suppress-outputs", false,
"Suppress display of stack outputs (in case they contain sensitive values)") "Suppress display of stack outputs (in case they contain sensitive values)")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().StringVar(
&suppressPermaLink, "suppress-permalink", false, &suppressPermaLink, "suppress-permalink", "",
"Suppress display of the state permalink") "Suppress display of the state permalink")
cmd.Flag("suppress-permalink").NoOptDefVal = "false"
cmd.PersistentFlags().BoolVarP( cmd.PersistentFlags().BoolVarP(
&yes, "yes", "y", false, &yes, "yes", "y", false,

View file

@ -270,7 +270,7 @@ func newImportCmd() *cobra.Command {
var showConfig bool var showConfig bool
var skipPreview bool var skipPreview bool
var suppressOutputs bool var suppressOutputs bool
var suppressPermaLink bool var suppressPermaLink string
var yes bool var yes bool
var protectResources bool var protectResources bool
@ -383,14 +383,32 @@ func newImportCmd() *cobra.Command {
} }
opts.Display = display.Options{ opts.Display = display.Options{
Color: cmdutil.GetGlobalColorization(), Color: cmdutil.GetGlobalColorization(),
ShowConfig: showConfig, ShowConfig: showConfig,
SuppressOutputs: suppressOutputs, SuppressOutputs: suppressOutputs,
SuppressPermaLink: suppressPermaLink, IsInteractive: interactive,
IsInteractive: interactive, Type: displayType,
Type: displayType, EventLogPath: eventLogPath,
EventLogPath: eventLogPath, Debug: debug,
Debug: debug, }
// we only suppress permalinks if the user passes true. the default is an empty string
// which we pass as 'false'
if suppressPermaLink == "true" {
opts.Display.SuppressPermaLink = true
} else {
opts.Display.SuppressPermaLink = false
}
filestateBackend, err := isFilestateBackend(opts.Display)
if err != nil {
return result.FromError(err)
}
// by default, we are going to suppress the permalink when using self-managed backends
// this can be re-enabled by explicitly passing "false" to the `supppress-permalink` flag
if suppressPermaLink != "false" && filestateBackend {
opts.Display.SuppressPermaLink = true
} }
// Fetch the project. // Fetch the project.
@ -541,9 +559,10 @@ func newImportCmd() *cobra.Command {
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&suppressOutputs, "suppress-outputs", false, &suppressOutputs, "suppress-outputs", false,
"Suppress display of stack outputs (in case they contain sensitive values)") "Suppress display of stack outputs (in case they contain sensitive values)")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().StringVar(
&suppressPermaLink, "suppress-permalink", false, &suppressPermaLink, "suppress-permalink", "",
"Suppress display of the state permalink") "Suppress display of the state permalink")
cmd.Flag("suppress-permalink").NoOptDefVal = "false"
cmd.PersistentFlags().BoolVarP( cmd.PersistentFlags().BoolVarP(
&yes, "yes", "y", false, &yes, "yes", "y", false,
"Automatically approve and perform the refresh after previewing it") "Automatically approve and perform the refresh after previewing it")

View file

@ -49,7 +49,7 @@ func newPreviewCmd() *cobra.Command {
var showSames bool var showSames bool
var showReads bool var showReads bool
var suppressOutputs bool var suppressOutputs bool
var suppressPermaLink bool var suppressPermaLink string
var targets []string var targets []string
var replaces []string var replaces []string
var targetReplaces []string var targetReplaces []string
@ -85,7 +85,6 @@ func newPreviewCmd() *cobra.Command {
ShowSameResources: showSames, ShowSameResources: showSames,
ShowReads: showReads, ShowReads: showReads,
SuppressOutputs: suppressOutputs, SuppressOutputs: suppressOutputs,
SuppressPermaLink: suppressPermaLink,
IsInteractive: cmdutil.Interactive(), IsInteractive: cmdutil.Interactive(),
Type: displayType, Type: displayType,
JSONDisplay: jsonDisplay, JSONDisplay: jsonDisplay,
@ -93,6 +92,24 @@ func newPreviewCmd() *cobra.Command {
Debug: debug, Debug: debug,
} }
// we only suppress permalinks if the user passes true. the default is an empty string
// which we pass as 'false'
if suppressPermaLink == "true" {
displayOpts.SuppressPermaLink = true
} else {
displayOpts.SuppressPermaLink = false
}
filestateBackend, err := isFilestateBackend(displayOpts)
if err != nil {
return result.FromError(err)
}
// by default, we are going to suppress the permalink when using self-managed backends
// this can be re-enabled by explicitly passing "false" to the `supppress-permalink` flag
if suppressPermaLink != "false" && filestateBackend {
displayOpts.SuppressPermaLink = true
}
if err := validatePolicyPackConfig(policyPackPaths, policyPackConfigPaths); err != nil { if err := validatePolicyPackConfig(policyPackPaths, policyPackConfigPaths); err != nil {
return result.FromError(err) return result.FromError(err)
} }
@ -253,13 +270,14 @@ func newPreviewCmd() *cobra.Command {
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&showReads, "show-reads", false, &showReads, "show-reads", false,
"Show resources that are being read in, alongside those being managed directly in the stack") "Show resources that are being read in, alongside those being managed directly in the stack")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&suppressOutputs, "suppress-outputs", false, &suppressOutputs, "suppress-outputs", false,
"Suppress display of stack outputs (in case they contain sensitive values)") "Suppress display of stack outputs (in case they contain sensitive values)")
cmd.PersistentFlags().BoolVar(
&suppressPermaLink, "suppress-permalink", false, cmd.PersistentFlags().StringVar(
&suppressPermaLink, "suppress-permalink", "",
"Suppress display of the state permalink") "Suppress display of the state permalink")
cmd.Flag("suppress-permalink").NoOptDefVal = "false"
if hasDebugCommands() { if hasDebugCommands() {
cmd.PersistentFlags().StringVar( cmd.PersistentFlags().StringVar(

View file

@ -44,7 +44,7 @@ func newRefreshCmd() *cobra.Command {
var showSames bool var showSames bool
var skipPreview bool var skipPreview bool
var suppressOutputs bool var suppressOutputs bool
var suppressPermaLink bool var suppressPermaLink string
var yes bool var yes bool
var targets *[]string var targets *[]string
@ -84,13 +84,31 @@ func newRefreshCmd() *cobra.Command {
ShowReplacementSteps: showReplacementSteps, ShowReplacementSteps: showReplacementSteps,
ShowSameResources: showSames, ShowSameResources: showSames,
SuppressOutputs: suppressOutputs, SuppressOutputs: suppressOutputs,
SuppressPermaLink: suppressPermaLink,
IsInteractive: interactive, IsInteractive: interactive,
Type: displayType, Type: displayType,
EventLogPath: eventLogPath, EventLogPath: eventLogPath,
Debug: debug, Debug: debug,
} }
// we only suppress permalinks if the user passes true. the default is an empty string
// which we pass as 'false'
if suppressPermaLink == "true" {
opts.Display.SuppressPermaLink = true
} else {
opts.Display.SuppressPermaLink = false
}
filestateBackend, err := isFilestateBackend(opts.Display)
if err != nil {
return result.FromError(err)
}
// by default, we are going to suppress the permalink when using self-managed backends
// this can be re-enabled by explicitly passing "false" to the `supppress-permalink` flag
if suppressPermaLink != "false" && filestateBackend {
opts.Display.SuppressPermaLink = true
}
s, err := requireStack(stack, true, opts.Display, true /*setCurrent*/) s, err := requireStack(stack, true, opts.Display, true /*setCurrent*/)
if err != nil { if err != nil {
return result.FromError(err) return result.FromError(err)
@ -193,9 +211,10 @@ func newRefreshCmd() *cobra.Command {
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&suppressOutputs, "suppress-outputs", false, &suppressOutputs, "suppress-outputs", false,
"Suppress display of stack outputs (in case they contain sensitive values)") "Suppress display of stack outputs (in case they contain sensitive values)")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().StringVar(
&suppressPermaLink, "suppress-permalink", false, &suppressPermaLink, "suppress-permalink", "",
"Suppress display of the state permalink") "Suppress display of the state permalink")
cmd.Flag("suppress-permalink").NoOptDefVal = "false"
cmd.PersistentFlags().BoolVarP( cmd.PersistentFlags().BoolVarP(
&yes, "yes", "y", false, &yes, "yes", "y", false,
"Automatically approve and perform the refresh after previewing it") "Automatically approve and perform the refresh after previewing it")

View file

@ -66,7 +66,7 @@ func newUpCmd() *cobra.Command {
var showReads bool var showReads bool
var skipPreview bool var skipPreview bool
var suppressOutputs bool var suppressOutputs bool
var suppressPermaLink bool var suppressPermaLink string
var yes bool var yes bool
var secretsProvider string var secretsProvider string
var targets []string var targets []string
@ -364,13 +364,31 @@ func newUpCmd() *cobra.Command {
ShowSameResources: showSames, ShowSameResources: showSames,
ShowReads: showReads, ShowReads: showReads,
SuppressOutputs: suppressOutputs, SuppressOutputs: suppressOutputs,
SuppressPermaLink: suppressPermaLink,
IsInteractive: interactive, IsInteractive: interactive,
Type: displayType, Type: displayType,
EventLogPath: eventLogPath, EventLogPath: eventLogPath,
Debug: debug, Debug: debug,
} }
// we only suppress permalinks if the user passes true. the default is an empty string
// which we pass as 'false'
if suppressPermaLink == "true" {
opts.Display.SuppressPermaLink = true
} else {
opts.Display.SuppressPermaLink = false
}
filestateBackend, err := isFilestateBackend(opts.Display)
if err != nil {
return result.FromError(err)
}
// by default, we are going to suppress the permalink when using self-managed backends
// this can be re-enabled by explicitly passing "false" to the `supppress-permalink` flag
if suppressPermaLink != "false" && filestateBackend {
opts.Display.SuppressPermaLink = true
}
if len(args) > 0 { if len(args) > 0 {
return upTemplateNameOrURL(args[0], opts) return upTemplateNameOrURL(args[0], opts)
} }
@ -461,9 +479,10 @@ func newUpCmd() *cobra.Command {
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().BoolVar(
&suppressOutputs, "suppress-outputs", false, &suppressOutputs, "suppress-outputs", false,
"Suppress display of stack outputs (in case they contain sensitive values)") "Suppress display of stack outputs (in case they contain sensitive values)")
cmd.PersistentFlags().BoolVar( cmd.PersistentFlags().StringVar(
&suppressPermaLink, "suppress-permalink", false, &suppressPermaLink, "suppress-permalink", "",
"Suppress display of the state permalink") "Suppress display of the state permalink")
cmd.Flag("suppress-permalink").NoOptDefVal = "false"
cmd.PersistentFlags().BoolVarP( cmd.PersistentFlags().BoolVarP(
&yes, "yes", "y", false, &yes, "yes", "y", false,
"Automatically approve and perform the update after previewing it") "Automatically approve and perform the update after previewing it")

View file

@ -88,6 +88,19 @@ func skipConfirmations() bool {
// backendInstance is used to inject a backend mock from tests. // backendInstance is used to inject a backend mock from tests.
var backendInstance backend.Backend var backendInstance backend.Backend
func isFilestateBackend(opts display.Options) (bool, error) {
if backendInstance != nil {
return false, nil
}
url, err := workspace.GetCurrentCloudURL()
if err != nil {
return false, errors.Wrapf(err, "could not get cloud url")
}
return filestate.IsFileStateBackendURL(url), nil
}
func currentBackend(opts display.Options) (backend.Backend, error) { func currentBackend(opts display.Options) (backend.Backend, error) {
if backendInstance != nil { if backendInstance != nil {
return backendInstance, nil return backendInstance, nil