diff --git a/pkg/backend/snapshot.go b/pkg/backend/snapshot.go index 4458b7be4862..034813d0abef 100644 --- a/pkg/backend/snapshot.go +++ b/pkg/backend/snapshot.go @@ -17,7 +17,6 @@ package backend import ( "errors" "fmt" - "os" "reflect" "sort" "time" @@ -26,16 +25,12 @@ import ( "github.com/pulumi/pulumi/pkg/v3/resource/deploy" "github.com/pulumi/pulumi/pkg/v3/secrets" "github.com/pulumi/pulumi/pkg/v3/version" + "github.com/pulumi/pulumi/sdk/v3/go/common/env" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" - "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" ) -// Experimental flag to skip saving state checkpoints and only save -// the final deployment. See #10668. -const pulumiSkipCheckpointsEnvVar = "PULUMI_SKIP_CHECKPOINTS" - // SnapshotPersister is an interface implemented by our backends that implements snapshot // persistence. In order to fit into our current model, snapshot persisters have two functions: // saving snapshots and invalidating already-persisted snapshots. @@ -720,8 +715,7 @@ func NewSnapshotManager(persister SnapshotPersister, baseSnap *deploy.Snapshot) serviceLoop := manager.defaultServiceLoop - if cmdutil.IsTruthy(os.Getenv("PULUMI_EXPERIMENTAL")) && - cmdutil.IsTruthy(os.Getenv(pulumiSkipCheckpointsEnvVar)) { + if env.SkipCheckpoints.Value() { serviceLoop = manager.unsafeServiceLoop } diff --git a/pkg/backend/snapshot_test.go b/pkg/backend/snapshot_test.go index 93727192036b..594cfe145c01 100644 --- a/pkg/backend/snapshot_test.go +++ b/pkg/backend/snapshot_test.go @@ -25,6 +25,7 @@ import ( "github.com/pulumi/pulumi/pkg/v3/secrets" "github.com/pulumi/pulumi/pkg/v3/secrets/b64" "github.com/pulumi/pulumi/pkg/v3/version" + "github.com/pulumi/pulumi/sdk/v3/go/common/env" "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/resource/plugin" @@ -252,12 +253,12 @@ func TestSamesWithDependencyChanges(t *testing.T) { // This test checks that we only write the Checkpoint once whether or // not there are important changes when asked to via -// pulumiSkipCheckpointsEnvVar. +// env.SkipCheckpoints. // //nolint:paralleltest // mutates environment variables func TestWriteCheckpointOnceUnsafe(t *testing.T) { - t.Setenv("PULUMI_EXPERIMENTAL", "1") - t.Setenv(pulumiSkipCheckpointsEnvVar, "1") + t.Setenv(env.Experimental.Var().Name(), "1") + t.Setenv(env.SkipCheckpoints.Var().Name(), "1") provider := NewResource("urn:pulumi:foo::bar::pulumi:providers:pkgUnsafe::provider") provider.Custom, provider.Type, provider.ID = true, "pulumi:providers:pkgUnsafe", "id" diff --git a/pkg/cmd/pulumi/util.go b/pkg/cmd/pulumi/util.go index c025129353f1..68575f2fb40d 100644 --- a/pkg/cmd/pulumi/util.go +++ b/pkg/cmd/pulumi/util.go @@ -49,6 +49,7 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" "github.com/pulumi/pulumi/sdk/v3/go/common/constant" "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" + "github.com/pulumi/pulumi/sdk/v3/go/common/env" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" "github.com/pulumi/pulumi/sdk/v3/go/common/util/ciutil" "github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil" @@ -59,27 +60,27 @@ import ( ) func hasDebugCommands() bool { - return cmdutil.IsTruthy(os.Getenv("PULUMI_DEBUG_COMMANDS")) + return env.DebugCommands.Value() } func hasExperimentalCommands() bool { - return cmdutil.IsTruthy(os.Getenv("PULUMI_EXPERIMENTAL")) + return env.Experimental.Value() } func useLegacyDiff() bool { - return cmdutil.IsTruthy(os.Getenv("PULUMI_ENABLE_LEGACY_DIFF")) + return env.EnableLegacyDiff.Value() } func disableProviderPreview() bool { - return cmdutil.IsTruthy(os.Getenv("PULUMI_DISABLE_PROVIDER_PREVIEW")) + return env.DisableProviderPreview.Value() } func disableResourceReferences() bool { - return cmdutil.IsTruthy(os.Getenv("PULUMI_DISABLE_RESOURCE_REFERENCES")) + return env.DisableResourceReferences.Value() } func disableOutputValues() bool { - return cmdutil.IsTruthy(os.Getenv("PULUMI_DISABLE_OUTPUT_VALUES")) + return env.DisableOutputValues.Value() } // skipConfirmations returns whether or not confirmation prompts should @@ -89,7 +90,7 @@ func disableOutputValues() bool { // This should NOT be used to bypass protections for destructive // operations, such as those that will fail without a --force parameter. func skipConfirmations() bool { - return cmdutil.IsTruthy(os.Getenv("PULUMI_SKIP_CONFIRMATIONS")) + return env.SkipConfirmations.Value() } // backendInstance is used to inject a backend mock from tests. diff --git a/pkg/engine/deployment.go b/pkg/engine/deployment.go index 6173e53d063a..cca054c0bb97 100644 --- a/pkg/engine/deployment.go +++ b/pkg/engine/deployment.go @@ -18,7 +18,6 @@ import ( "context" "errors" "fmt" - "os" "time" "github.com/opentracing/opentracing-go" @@ -29,6 +28,7 @@ import ( interceptors "github.com/pulumi/pulumi/pkg/v3/util/rpcdebug" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" "github.com/pulumi/pulumi/sdk/v3/go/common/display" + "github.com/pulumi/pulumi/sdk/v3/go/common/env" "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" @@ -60,7 +60,7 @@ func ProjectInfoContext(projinfo *Projinfo, host plugin.Host, return "", "", nil, err } - if logFile := os.Getenv("PULUMI_DEBUG_GRPC"); logFile != "" { + if logFile := env.DebugGRPC.Value(); logFile != "" { di, err := interceptors.NewDebugInterceptor(interceptors.DebugInterceptorOptions{ LogFile: logFile, Mutex: ctx.DebugTraceMutex, diff --git a/pkg/resource/deploy/source_eval.go b/pkg/resource/deploy/source_eval.go index 8dd54d17dff3..374524e315a9 100644 --- a/pkg/resource/deploy/source_eval.go +++ b/pkg/resource/deploy/source_eval.go @@ -19,7 +19,6 @@ import ( "encoding/json" "errors" "fmt" - "os" "strings" "sync" "time" @@ -35,6 +34,7 @@ import ( "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" interceptors "github.com/pulumi/pulumi/pkg/v3/util/rpcdebug" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" + "github.com/pulumi/pulumi/sdk/v3/go/common/env" "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/resource/plugin" @@ -575,7 +575,7 @@ func sourceEvalServeOptions(ctx *plugin.Context, tracingSpan opentracing.Span) [ tracingSpan, otgrpc.SpanDecorator(decorateResourceSpans), ) - if logFile := os.Getenv("PULUMI_DEBUG_GRPC"); logFile != "" { + if logFile := env.DebugGRPC.Value(); logFile != "" { di, err := interceptors.NewDebugInterceptor(interceptors.DebugInterceptorOptions{ LogFile: logFile, Mutex: ctx.DebugTraceMutex, diff --git a/sdk/go/common/env/env.go b/sdk/go/common/env/env.go index 1f584924c2ca..e181a5845f33 100644 --- a/sdk/go/common/env/env.go +++ b/sdk/go/common/env/env.go @@ -43,5 +43,28 @@ var SkipUpdateCheck = env.Bool("SKIP_UPDATE_CHECK", "Disable checking for a new var Dev = env.Bool("DEV", "Enable features for hacking on pulumi itself") +var SkipCheckpoints = env.Bool("SKIP_CHECKPOINTS", "Experimental flag to skip saving state "+ + "checkpoints and only save the final deployment. See #10668", env.Needs(Experimental)) + +var DebugCommands = env.Bool("DEBUG_COMMANDS", "List commands helpful for debugging pulumi itself") + +var EnableLegacyDiff = env.Bool("ENABLE_LEGACY_DIFF", "") + +var DisableProviderPreview = env.Bool("DISABLE_PROVIDER_PREVIEW", "") + +var DisableResourceReferences = env.Bool("DISABLE_RESOURCE_REFERENCES", "") + +var DisableOutputValues = env.Bool("DISABLE_OUTPUT_VALUES", "") + var IgnoreAmbientPlugins = env.Bool("IGNORE_AMBIENT_PLUGINS", "Discover additional plugins by examining the $PATH") + +var SkipConfirmations = env.Bool("SKIP_CONFIRMATIONS", + `Whether or not confirmation prompts should be skipped. This should be used by pass any requirement +that a --yes parameter has been set for non-interactive scenarios. + +This should NOT be used to bypass protections for destructive operations, such as those that will +fail without a --force parameter.`) + +var DebugGRPC = env.String("DEBUG_GRPC", `Enables debug tracing of Pulumi gRPC internals. +The variable should be set to the log file to which gRPC debug traces will be sent.`) diff --git a/sdk/go/common/util/cmdutil/console.go b/sdk/go/common/util/cmdutil/console.go index ff77c06ed80f..43aec16f9e12 100644 --- a/sdk/go/common/util/cmdutil/console.go +++ b/sdk/go/common/util/cmdutil/console.go @@ -152,6 +152,37 @@ func MeasureText(text string) int { return uniseg.GraphemeClusterCount(clean) } +// normalizedRows returns the rows of a table in normalized form. +// +// A row is considered normalized if and only if it has no new lines in any of its fields. +func (table *Table) normalizedRows() []TableRow { + rows := make([]TableRow, 0, len(table.Rows)) + for _, row := range table.Rows { + info := row.AdditionalInfo + buckets := make([][]string, len(row.Columns)) + maxLines := 0 + for i, column := range row.Columns { + buckets[i] = strings.Split(column, "\n") + maxLines = max(maxLines, len(buckets[i])) + } + row := []TableRow{} + for i := 0; i < maxLines; i++ { + part := TableRow{} + for _, b := range buckets { + if i < len(b) { + part.Columns = append(part.Columns, b[i]) + } else { + part.Columns = append(part.Columns, "") + } + } + row = append(row, part) + } + row[len(row)-1].AdditionalInfo = info + rows = append(rows, row...) + } + return rows +} + func (table *Table) ToStringWithGap(columnGap string) string { columnCount := len(table.Headers) @@ -163,7 +194,7 @@ func (table *Table) ToStringWithGap(columnGap string) string { Columns: table.Headers, }} - allRows = append(allRows, table.Rows...) + allRows = append(allRows, table.normalizedRows()...) for rowIndex, row := range allRows { columns := row.Columns