github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/display/json.go (about) 1 // Copyright 2016-2018, Pulumi Corporation. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package display 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "os" 21 22 "github.com/pulumi/pulumi/pkg/v3/engine" 23 "github.com/pulumi/pulumi/pkg/v3/resource/stack" 24 "github.com/pulumi/pulumi/sdk/v3/go/common/diag" 25 "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" 26 "github.com/pulumi/pulumi/sdk/v3/go/common/display" 27 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 28 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" 29 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 30 "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" 31 ) 32 33 // massagePropertyValue takes a property value and strips out the secrets annotations from it. If showSecrets is 34 // not true any secret values are replaced with "[secret]". 35 func massagePropertyValue(v resource.PropertyValue, showSecrets bool) resource.PropertyValue { 36 switch { 37 case v.IsArray(): 38 new := make([]resource.PropertyValue, len(v.ArrayValue())) 39 for i, e := range v.ArrayValue() { 40 new[i] = massagePropertyValue(e, showSecrets) 41 } 42 return resource.NewArrayProperty(new) 43 case v.IsObject(): 44 new := make(resource.PropertyMap, len(v.ObjectValue())) 45 for k, e := range v.ObjectValue() { 46 new[k] = massagePropertyValue(e, showSecrets) 47 } 48 return resource.NewObjectProperty(new) 49 case v.IsSecret() && showSecrets: 50 return massagePropertyValue(v.SecretValue().Element, showSecrets) 51 case v.IsSecret(): 52 return resource.NewStringProperty("[secret]") 53 default: 54 return v 55 } 56 } 57 58 // MassageSecrets takes a property map and returns a new map by transforming each value with massagePropertyValue 59 // This allows us to serialize the resulting map using our existing serialization logic we use for deployments, to 60 // produce sane output for stackOutputs. If we did not do this, SecretValues would be serialized as objects 61 // with the signature key and value. 62 func MassageSecrets(m resource.PropertyMap, showSecrets bool) resource.PropertyMap { 63 new := make(resource.PropertyMap, len(m)) 64 for k, e := range m { 65 new[k] = massagePropertyValue(e, showSecrets) 66 } 67 return new 68 } 69 70 // stateForJSONOutput prepares some resource's state for JSON output. This includes filtering the output based 71 // on the supplied options, in addition to massaging secret fields. 72 func stateForJSONOutput(s *resource.State, opts Options) *resource.State { 73 var inputs resource.PropertyMap 74 var outputs resource.PropertyMap 75 if !isRootURN(s.URN) || !opts.SuppressOutputs { 76 // For now, replace any secret properties as the string [secret] and then serialize what we have. 77 inputs = MassageSecrets(s.Inputs, false) 78 outputs = MassageSecrets(s.Outputs, false) 79 } else { 80 // If we're suppressing outputs, don't show the root stack properties. 81 inputs = resource.PropertyMap{} 82 outputs = resource.PropertyMap{} 83 } 84 85 return resource.NewState(s.Type, s.URN, s.Custom, s.Delete, s.ID, inputs, 86 outputs, s.Parent, s.Protect, s.External, s.Dependencies, s.InitErrors, s.Provider, 87 s.PropertyDependencies, s.PendingReplacement, s.AdditionalSecretOutputs, s.Aliases, &s.CustomTimeouts, 88 s.ImportID, s.RetainOnDelete, s.DeletedWith) 89 } 90 91 // ShowJSONEvents renders incremental engine events to stdout. 92 func ShowJSONEvents(events <-chan engine.Event, done chan<- bool, opts Options) { 93 // Ensure we close the done channel before exiting. 94 defer func() { close(done) }() 95 96 sequence := 0 97 encoder := json.NewEncoder(os.Stdout) 98 encoder.SetEscapeHTML(false) 99 for e := range events { 100 if err := logJSONEvent(encoder, e, opts, sequence); err != nil { 101 logging.V(7).Infof("failed to log event: %v", err) 102 } 103 sequence++ 104 105 // In the event of cancellation, break out of the loop. 106 if e.Type == engine.CancelEvent { 107 break 108 } 109 } 110 } 111 112 // ShowPreviewDigest renders engine events from a preview into a well-formed JSON document. Note that this does not 113 // emit events incrementally so that it can guarantee anything emitted to stdout is well-formed. This means that, 114 // if used interactively, the experience will lead to potentially very long pauses. If run in CI, it is up to the 115 // end user to ensure that output is periodically printed to prevent tools from thinking preview has hung. 116 func ShowPreviewDigest(events <-chan engine.Event, done chan<- bool, opts Options) { 117 // Ensure we close the done channel before exiting. 118 defer func() { close(done) }() 119 120 // Now loop and accumulate our digest until the event stream is closed, or we hit a cancellation. 121 var digest display.PreviewDigest 122 for e := range events { 123 // In the event of cancellation, break out of the loop immediately. 124 if e.Type == engine.CancelEvent { 125 break 126 } 127 128 // For all other events, use the payload to build up the JSON digest we'll emit later. 129 switch e.Type { 130 // Events occurring early: 131 case engine.PreludeEvent: 132 // Capture the config map from the prelude. Note that all secrets will remain blinded for safety. 133 digest.Config = e.Payload().(engine.PreludeEventPayload).Config 134 135 // Events throughout the execution: 136 case engine.DiagEvent: 137 // Skip any ephemeral or debug messages, and elide all colorization. 138 p := e.Payload().(engine.DiagEventPayload) 139 if !p.Ephemeral && p.Severity != diag.Debug { 140 digest.Diagnostics = append(digest.Diagnostics, display.PreviewDiagnostic{ 141 URN: p.URN, 142 Message: colors.Never.Colorize(p.Prefix + p.Message), 143 Severity: p.Severity, 144 }) 145 } 146 case engine.StdoutColorEvent: 147 // Append stdout events as informational messages, and elide all colorization. 148 p := e.Payload().(engine.StdoutEventPayload) 149 digest.Diagnostics = append(digest.Diagnostics, display.PreviewDiagnostic{ 150 Message: colors.Never.Colorize(p.Message), 151 Severity: diag.Info, 152 }) 153 case engine.ResourcePreEvent: 154 // Create the detailed metadata for this step and the initial state of its resource. Later, 155 // if new outputs arrive, we'll search for and swap in those new values. 156 if m := e.Payload().(engine.ResourcePreEventPayload).Metadata; shouldShow(m, opts) || isRootStack(m) { 157 var detailedDiff map[string]display.PropertyDiff 158 if m.DetailedDiff != nil { 159 detailedDiff = make(map[string]display.PropertyDiff) 160 for k, v := range m.DetailedDiff { 161 detailedDiff[k] = display.PropertyDiff{ 162 Kind: v.Kind.String(), 163 InputDiff: v.InputDiff, 164 } 165 } 166 } 167 168 step := &display.PreviewStep{ 169 Op: m.Op, 170 URN: m.URN, 171 Provider: m.Provider, 172 DiffReasons: m.Diffs, 173 ReplaceReasons: m.Keys, 174 DetailedDiff: detailedDiff, 175 } 176 177 if m.Old != nil { 178 oldState := stateForJSONOutput(m.Old.State, opts) 179 res, err := stack.SerializeResource(oldState, config.NewPanicCrypter(), false /* showSecrets */) 180 if err == nil { 181 step.OldState = &res 182 } else { 183 logging.V(7).Infof("not adding old state as there was an error serializing: %s", err) 184 } 185 } 186 if m.New != nil { 187 newState := stateForJSONOutput(m.New.State, opts) 188 res, err := stack.SerializeResource(newState, config.NewPanicCrypter(), false /* showSecrets */) 189 if err == nil { 190 step.NewState = &res 191 } else { 192 logging.V(7).Infof("not adding new state as there was an error serializing: %s", err) 193 } 194 } 195 196 digest.Steps = append(digest.Steps, step) 197 } 198 case engine.ResourceOutputsEvent, engine.ResourceOperationFailed: 199 // Because we are only JSON serializing previews, we don't need to worry about outputs 200 // resolving or operations failing. 201 202 // Events occurring late: 203 case engine.PolicyViolationEvent: 204 // At this point in time, we don't handle policy events in JSON serialization 205 continue 206 case engine.SummaryEvent: 207 // At the end of the preview, a summary event indicates the final conclusions. 208 p := e.Payload().(engine.SummaryEventPayload) 209 digest.Duration = p.Duration 210 digest.ChangeSummary = p.ResourceChanges 211 digest.MaybeCorrupt = p.MaybeCorrupt 212 default: 213 contract.Failf("unknown event type '%s'", e.Type) 214 } 215 } 216 // Finally, go ahead and render the JSON to stdout. 217 out, err := json.MarshalIndent(&digest, "", " ") 218 contract.Assertf(err == nil, "unexpected JSON error: %v", err) 219 fmt.Println(string(out)) 220 }