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