This change adds support for lists and maps in config. We now allow lists/maps (and nested structures) in `Pulumi.<stack>.yaml` (or `Pulumi.<stack>.json`; yes, we currently support that). For example: ```yaml config: proj:blah: - a - b - c proj:hello: world proj:outer: inner: value proj:servers: - port: 80 ``` While such structures could be specified in the `.yaml` file manually, we support setting values in maps/lists from the command line. As always, you can specify single values with: ```shell $ pulumi config set hello world ``` Which results in the following YAML: ```yaml proj:hello world ``` And single value secrets via: ```shell $ pulumi config set --secret token shhh ``` Which results in the following YAML: ```yaml proj:token: secure: v1:VZAhuroR69FkEPTk:isKafsoZVMWA9pQayGzbWNynww== ``` Values in a list can be set from the command line using the new `--path` flag, which indicates the config key contains a path to a property in a map or list: ```shell $ pulumi config set --path names[0] a $ pulumi config set --path names[1] b $ pulumi config set --path names[2] c ``` Which results in: ```yaml proj:names - a - b - c ``` Values can be obtained similarly: ```shell $ pulumi config get --path names[1] b ``` Or setting values in a map: ```shell $ pulumi config set --path outer.inner value ``` Which results in: ```yaml proj:outer: inner: value ``` Of course, setting values in nested structures is supported: ```shell $ pulumi config set --path servers[0].port 80 ``` Which results in: ```yaml proj:servers: - port: 80 ``` If you want to include a period in the name of a property, it can be specified as: ``` $ pulumi config set --path 'nested["foo.bar"]' baz ``` Which results in: ```yaml proj:nested: foo.bar: baz ``` Examples of valid paths: - root - root.nested - 'root["nested"]' - root.double.nest - 'root["double"].nest' - 'root["double"]["nest"]' - root.array[0] - root.array[100] - root.array[0].nested - root.array[0][1].nested - root.nested.array[0].double[1] - 'root["key with \"escaped\" quotes"]' - 'root["key with a ."]' - '["root key with \"escaped\" quotes"].nested' - '["root key with a ."][100]' Note: paths that contain quotes can be surrounded by single quotes. When setting values with `--path`, if the value is `"false"` or `"true"`, it will be saved as the boolean value, and if it is convertible to an integer, it will be saved as an integer. Secure values are supported in lists/maps as well: ```shell $ pulumi config set --path --secret tokens[0] shh ``` Will result in: ```yaml proj:tokens: - secure: v1:wpZRCe36sFg1RxwG:WzPeQrCn4n+m4Ks8ps15MxvFXg== ``` Note: maps of length 1 with a key of “secure” and string value are reserved for storing secret values. Attempting to create such a value manually will result in an error: ```shell $ pulumi config set --path parent.secure foo error: "secure" key in maps of length 1 are reserved ``` **Accessing config values from the command line with JSON** ```shell $ pulumi config --json ``` Will output: ```json { "proj:hello": { "value": "world", "secret": false, "object": false }, "proj:names": { "value": "[\"a\",\"b\",\"c\"]", "secret": false, "object": true, "objectValue": [ "a", "b", "c" ] }, "proj:nested": { "value": "{\"foo.bar\":\"baz\"}", "secret": false, "object": true, "objectValue": { "foo.bar": "baz" } }, "proj:outer": { "value": "{\"inner\":\"value\"}", "secret": false, "object": true, "objectValue": { "inner": "value" } }, "proj:servers": { "value": "[{\"port\":80}]", "secret": false, "object": true, "objectValue": [ { "port": 80 } ] }, "proj:token": { "secret": true, "object": false }, "proj:tokens": { "secret": true, "object": true } } ``` If the value is a map or list, `"object"` will be `true`. `"value"` will contain the object as serialized JSON and a new `"objectValue"` property will be available containing the value of the object. If the object contains any secret values, `"secret"` will be `true`, and just like with scalar values, the value will not be outputted unless `--show-secrets` is specified. **Accessing config values from Pulumi programs** Map/list values are available to Pulumi programs as serialized JSON, so the existing `getObject`/`requireObject`/`getSecretObject`/`requireSecretObject` functions can be used to retrieve such values, e.g.: ```typescript import * as pulumi from "@pulumi/pulumi"; interface Server { port: number; } const config = new pulumi.Config(); const names = config.requireObject<string[]>("names"); for (const n of names) { console.log(n); } const servers = config.requireObject<Server[]>("servers"); for (const s of servers) { console.log(s.port); } ```
600 lines
18 KiB
Go
600 lines
18 KiB
Go
// 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 engine
|
|
|
|
import (
|
|
"bytes"
|
|
"reflect"
|
|
"time"
|
|
|
|
"github.com/pulumi/pulumi/pkg/apitype"
|
|
"github.com/pulumi/pulumi/pkg/diag"
|
|
"github.com/pulumi/pulumi/pkg/diag/colors"
|
|
"github.com/pulumi/pulumi/pkg/resource"
|
|
"github.com/pulumi/pulumi/pkg/resource/config"
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
|
"github.com/pulumi/pulumi/pkg/resource/plugin"
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
|
"github.com/pulumi/pulumi/pkg/util/logging"
|
|
)
|
|
|
|
// Event represents an event generated by the engine during an operation. The underlying
|
|
// type for the `Payload` field will differ depending on the value of the `Type` field
|
|
type Event struct {
|
|
Type EventType
|
|
Payload interface{}
|
|
}
|
|
|
|
// EventType is the kind of event being emitted.
|
|
type EventType string
|
|
|
|
const (
|
|
CancelEvent EventType = "cancel"
|
|
StdoutColorEvent EventType = "stdoutcolor"
|
|
DiagEvent EventType = "diag"
|
|
PreludeEvent EventType = "prelude"
|
|
SummaryEvent EventType = "summary"
|
|
ResourcePreEvent EventType = "resource-pre"
|
|
ResourceOutputsEvent EventType = "resource-outputs"
|
|
ResourceOperationFailed EventType = "resource-operationfailed"
|
|
PolicyViolationEvent EventType = "policy-violation"
|
|
)
|
|
|
|
func cancelEvent() Event {
|
|
return Event{Type: CancelEvent}
|
|
}
|
|
|
|
// DiagEventPayload is the payload for an event with type `diag`
|
|
type DiagEventPayload struct {
|
|
URN resource.URN
|
|
Prefix string
|
|
Message string
|
|
Color colors.Colorization
|
|
Severity diag.Severity
|
|
StreamID int32
|
|
Ephemeral bool
|
|
}
|
|
|
|
// PolicyViolationEventPayload is the payload for an event with type `policy-violation`.
|
|
type PolicyViolationEventPayload struct {
|
|
ResourceURN resource.URN
|
|
Message string
|
|
Color colors.Colorization
|
|
PolicyName string
|
|
PolicyPackName string
|
|
PolicyPackVersion string
|
|
EnforcementLevel apitype.EnforcementLevel
|
|
Prefix string
|
|
}
|
|
|
|
type StdoutEventPayload struct {
|
|
Message string
|
|
Color colors.Colorization
|
|
}
|
|
|
|
type PreludeEventPayload struct {
|
|
IsPreview bool // true if this prelude is for a plan operation
|
|
Config map[string]string // the keys and values for config. For encrypted config, the values may be blinded
|
|
}
|
|
|
|
type SummaryEventPayload struct {
|
|
IsPreview bool // true if this summary is for a plan operation
|
|
MaybeCorrupt bool // true if one or more resources may be corrupt
|
|
Duration time.Duration // the duration of the entire update operation (zero values for previews)
|
|
ResourceChanges ResourceChanges // count of changed resources, useful for reporting
|
|
PolicyPacks map[string]string // {policy-pack: version} for each policy pack applied
|
|
}
|
|
|
|
type ResourceOperationFailedPayload struct {
|
|
Metadata StepEventMetadata
|
|
Status resource.Status
|
|
Steps int
|
|
}
|
|
|
|
type ResourceOutputsEventPayload struct {
|
|
Metadata StepEventMetadata
|
|
Planning bool
|
|
Debug bool
|
|
}
|
|
|
|
type ResourcePreEventPayload struct {
|
|
Metadata StepEventMetadata
|
|
Planning bool
|
|
Debug bool
|
|
}
|
|
|
|
// StepEventMetadata contains the metadata associated with a step the engine is performing.
|
|
type StepEventMetadata struct {
|
|
Op deploy.StepOp // the operation performed by this step.
|
|
URN resource.URN // the resource URN (for before and after).
|
|
Type tokens.Type // the type affected by this step.
|
|
Old *StepEventStateMetadata // the state of the resource before performing this step.
|
|
New *StepEventStateMetadata // the state of the resource after performing this step.
|
|
Res *StepEventStateMetadata // the latest state for the resource that is known (worst case, old).
|
|
Keys []resource.PropertyKey // the keys causing replacement (only for CreateStep and ReplaceStep).
|
|
Diffs []resource.PropertyKey // the keys causing diffs
|
|
DetailedDiff map[string]plugin.PropertyDiff // the rich, structured diff
|
|
Logical bool // true if this step represents a logical operation in the program.
|
|
Provider string // the provider that performed this step.
|
|
}
|
|
|
|
// StepEventStateMetadata contains detailed metadata about a resource's state pertaining to a given step.
|
|
type StepEventStateMetadata struct {
|
|
// State contains the raw, complete state, for this resource.
|
|
State *resource.State
|
|
// the resource's type.
|
|
Type tokens.Type
|
|
// the resource's object urn, a human-friendly, unique name for the resource.
|
|
URN resource.URN
|
|
// true if the resource is custom, managed by a plugin.
|
|
Custom bool
|
|
// true if this resource is pending deletion due to a replacement.
|
|
Delete bool
|
|
// the resource's unique ID, assigned by the resource provider (or blank if none/uncreated).
|
|
ID resource.ID
|
|
// an optional parent URN that this resource belongs to.
|
|
Parent resource.URN
|
|
// true to "protect" this resource (protected resources cannot be deleted).
|
|
Protect bool
|
|
// the resource's input properties (as specified by the program). Note: because this will cross
|
|
// over rpc boundaries it will be slightly different than the Inputs found in resource_state.
|
|
// Specifically, secrets will have been filtered out, and large values (like assets) will be
|
|
// have a simple hash-based representation. This allows clients to display this information
|
|
// properly, without worrying about leaking sensitive data, and without having to transmit huge
|
|
// amounts of data.
|
|
Inputs resource.PropertyMap
|
|
// the resource's complete output state (as returned by the resource provider). See "Inputs"
|
|
// for additional details about how data will be transformed before going into this map.
|
|
Outputs resource.PropertyMap
|
|
// the resource's provider reference
|
|
Provider string
|
|
// InitErrors is the set of errors encountered in the process of initializing resource (i.e.,
|
|
// during create or update).
|
|
InitErrors []string
|
|
}
|
|
|
|
func makeEventEmitter(events chan<- Event, update UpdateInfo) (eventEmitter, error) {
|
|
target := update.GetTarget()
|
|
var secrets []string
|
|
if target != nil && target.Config.HasSecureValue() {
|
|
for k, v := range target.Config {
|
|
if !v.Secure() {
|
|
continue
|
|
}
|
|
|
|
secureValues, err := v.SecureValues(target.Decrypter)
|
|
if err != nil {
|
|
return eventEmitter{}, DecryptError{
|
|
Key: k,
|
|
Err: err,
|
|
}
|
|
}
|
|
secrets = append(secrets, secureValues...)
|
|
}
|
|
}
|
|
|
|
logging.AddGlobalFilter(logging.CreateFilter(secrets, "[secret]"))
|
|
|
|
buffer, done := make(chan Event), make(chan bool)
|
|
go queueEvents(events, buffer, done)
|
|
|
|
return eventEmitter{
|
|
done: done,
|
|
ch: buffer,
|
|
}, nil
|
|
}
|
|
|
|
func makeQueryEventEmitter(events chan<- Event) (eventEmitter, error) {
|
|
buffer, done := make(chan Event), make(chan bool)
|
|
|
|
go queueEvents(events, buffer, done)
|
|
|
|
return eventEmitter{
|
|
done: done,
|
|
ch: buffer,
|
|
}, nil
|
|
}
|
|
|
|
type eventEmitter struct {
|
|
done <-chan bool
|
|
ch chan<- Event
|
|
}
|
|
|
|
func queueEvents(events chan<- Event, buffer chan Event, done chan bool) {
|
|
// Instead of sending to the source channel directly, buffer events to account for slow receivers.
|
|
//
|
|
// Buffering is done by a goroutine that concurrently receives from the senders and attempts to send events to the
|
|
// receiver. Events that are received while waiting for the receiver to catch up are buffered in a slice.
|
|
//
|
|
// We do not use a buffered channel because it is empirically less likely that the goroutine reading from a
|
|
// buffered channel will be scheduled when new data is placed in the channel.
|
|
|
|
defer close(done)
|
|
|
|
var queue []Event
|
|
for {
|
|
contract.Assert(buffer != nil)
|
|
|
|
e, ok := <-buffer
|
|
if !ok {
|
|
return
|
|
}
|
|
queue = append(queue, e)
|
|
|
|
// While there are events in the queue, attempt to send them to the waiting receiver. If the receiver is
|
|
// blocked and an event is received from the event senders, stick that event in the queue.
|
|
for len(queue) > 0 {
|
|
select {
|
|
case e, ok := <-buffer:
|
|
if !ok {
|
|
// If the event source has been closed, flush the queue.
|
|
for _, e := range queue {
|
|
events <- e
|
|
}
|
|
return
|
|
}
|
|
queue = append(queue, e)
|
|
case events <- queue[0]:
|
|
queue = queue[1:]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeStepEventMetadata(op deploy.StepOp, step deploy.Step, debug bool) StepEventMetadata {
|
|
contract.Assert(op == step.Op() || step.Op() == deploy.OpRefresh)
|
|
|
|
var keys, diffs []resource.PropertyKey
|
|
if keyer, hasKeys := step.(interface{ Keys() []resource.PropertyKey }); hasKeys {
|
|
keys = keyer.Keys()
|
|
}
|
|
if differ, hasDiffs := step.(interface{ Diffs() []resource.PropertyKey }); hasDiffs {
|
|
diffs = differ.Diffs()
|
|
}
|
|
|
|
var detailedDiff map[string]plugin.PropertyDiff
|
|
if detailedDiffer, hasDetailedDiff := step.(interface {
|
|
DetailedDiff() map[string]plugin.PropertyDiff
|
|
}); hasDetailedDiff {
|
|
detailedDiff = detailedDiffer.DetailedDiff()
|
|
}
|
|
|
|
return StepEventMetadata{
|
|
Op: op,
|
|
URN: step.URN(),
|
|
Type: step.Type(),
|
|
Keys: keys,
|
|
Diffs: diffs,
|
|
DetailedDiff: detailedDiff,
|
|
Old: makeStepEventStateMetadata(step.Old(), debug),
|
|
New: makeStepEventStateMetadata(step.New(), debug),
|
|
Res: makeStepEventStateMetadata(step.Res(), debug),
|
|
Logical: step.Logical(),
|
|
Provider: step.Provider(),
|
|
}
|
|
}
|
|
|
|
func makeStepEventStateMetadata(state *resource.State, debug bool) *StepEventStateMetadata {
|
|
if state == nil {
|
|
return nil
|
|
}
|
|
|
|
return &StepEventStateMetadata{
|
|
State: state,
|
|
Type: state.Type,
|
|
URN: state.URN,
|
|
Custom: state.Custom,
|
|
Delete: state.Delete,
|
|
ID: state.ID,
|
|
Parent: state.Parent,
|
|
Protect: state.Protect,
|
|
Inputs: filterPropertyMap(state.Inputs, debug),
|
|
Outputs: filterPropertyMap(state.Outputs, debug),
|
|
Provider: state.Provider,
|
|
InitErrors: state.InitErrors,
|
|
}
|
|
}
|
|
|
|
func filterPropertyMap(propertyMap resource.PropertyMap, debug bool) resource.PropertyMap {
|
|
mappable := propertyMap.Mappable()
|
|
|
|
var filterValue func(v interface{}) interface{}
|
|
|
|
filterPropertyValue := func(pv resource.PropertyValue) resource.PropertyValue {
|
|
return resource.NewPropertyValue(filterValue(pv.Mappable()))
|
|
}
|
|
|
|
// filter values walks unwrapped (i.e. non-PropertyValue) values and applies the filter function
|
|
// to them recursively. The only thing the filter actually applies to is strings.
|
|
//
|
|
// The return value of this function should have the same type as the input value.
|
|
filterValue = func(v interface{}) interface{} {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
|
|
// Else, check for some known primitive types.
|
|
switch t := v.(type) {
|
|
case bool, int, uint, int32, uint32,
|
|
int64, uint64, float32, float64:
|
|
// simple types. map over as is.
|
|
return v
|
|
case string:
|
|
// have to ensure we filter out secrets.
|
|
return logging.FilterString(t)
|
|
case *resource.Asset:
|
|
text := t.Text
|
|
if text != "" {
|
|
// we don't want to include the full text of an asset as we serialize it over as
|
|
// events. They represent user files and are thus are unbounded in size. Instead,
|
|
// we only include the text if it represents a user's serialized program code, as
|
|
// that is something we want the receiver to see to display as part of
|
|
// progress/diffs/etc.
|
|
if t.IsUserProgramCode() {
|
|
// also make sure we filter this in case there are any secrets in the code.
|
|
text = logging.FilterString(resource.MassageIfUserProgramCodeAsset(t, debug).Text)
|
|
} else {
|
|
// We need to have some string here so that we preserve that this is a
|
|
// text-asset
|
|
text = "<stripped>"
|
|
}
|
|
}
|
|
|
|
return &resource.Asset{
|
|
Sig: t.Sig,
|
|
Hash: t.Hash,
|
|
Text: text,
|
|
Path: t.Path,
|
|
URI: t.URI,
|
|
}
|
|
case *resource.Archive:
|
|
return &resource.Archive{
|
|
Sig: t.Sig,
|
|
Hash: t.Hash,
|
|
Path: t.Path,
|
|
URI: t.URI,
|
|
Assets: filterValue(t.Assets).(map[string]interface{}),
|
|
}
|
|
case resource.Secret:
|
|
return "[secret]"
|
|
case resource.Computed:
|
|
return resource.Computed{
|
|
Element: filterPropertyValue(t.Element),
|
|
}
|
|
case resource.Output:
|
|
return resource.Output{
|
|
Element: filterPropertyValue(t.Element),
|
|
}
|
|
}
|
|
|
|
// Next, see if it's an array, slice, pointer or struct, and handle each accordingly.
|
|
rv := reflect.ValueOf(v)
|
|
switch rk := rv.Type().Kind(); rk {
|
|
case reflect.Array, reflect.Slice:
|
|
// If an array or slice, just create an array out of it.
|
|
var arr []interface{}
|
|
for i := 0; i < rv.Len(); i++ {
|
|
arr = append(arr, filterValue(rv.Index(i).Interface()))
|
|
}
|
|
return arr
|
|
case reflect.Ptr:
|
|
if rv.IsNil() {
|
|
return nil
|
|
}
|
|
|
|
v1 := filterValue(rv.Elem().Interface())
|
|
return &v1
|
|
case reflect.Map:
|
|
obj := make(map[string]interface{})
|
|
for _, key := range rv.MapKeys() {
|
|
k := key.Interface().(string)
|
|
v := rv.MapIndex(key).Interface()
|
|
obj[k] = filterValue(v)
|
|
}
|
|
return obj
|
|
default:
|
|
contract.Failf("Unrecognized value type: type=%v kind=%v", rv.Type(), rk)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
return resource.NewPropertyMapFromMapRepl(
|
|
mappable, nil, /*replk*/
|
|
func(v interface{}) (resource.PropertyValue, bool) {
|
|
return resource.NewPropertyValue(filterValue(v)), true
|
|
})
|
|
}
|
|
|
|
func (e *eventEmitter) Close() {
|
|
close(e.ch)
|
|
<-e.done
|
|
}
|
|
|
|
func (e *eventEmitter) resourceOperationFailedEvent(
|
|
step deploy.Step, status resource.Status, steps int, debug bool) {
|
|
|
|
contract.Requiref(e != nil, "e", "!= nil")
|
|
|
|
e.ch <- Event{
|
|
Type: ResourceOperationFailed,
|
|
Payload: ResourceOperationFailedPayload{
|
|
Metadata: makeStepEventMetadata(step.Op(), step, debug),
|
|
Status: status,
|
|
Steps: steps,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *eventEmitter) resourceOutputsEvent(op deploy.StepOp, step deploy.Step, planning bool, debug bool) {
|
|
contract.Requiref(e != nil, "e", "!= nil")
|
|
|
|
e.ch <- Event{
|
|
Type: ResourceOutputsEvent,
|
|
Payload: ResourceOutputsEventPayload{
|
|
Metadata: makeStepEventMetadata(op, step, debug),
|
|
Planning: planning,
|
|
Debug: debug,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *eventEmitter) resourcePreEvent(
|
|
step deploy.Step, planning bool, debug bool) {
|
|
|
|
contract.Requiref(e != nil, "e", "!= nil")
|
|
|
|
e.ch <- Event{
|
|
Type: ResourcePreEvent,
|
|
Payload: ResourcePreEventPayload{
|
|
Metadata: makeStepEventMetadata(step.Op(), step, debug),
|
|
Planning: planning,
|
|
Debug: debug,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *eventEmitter) preludeEvent(isPreview bool, cfg config.Map) {
|
|
contract.Requiref(e != nil, "e", "!= nil")
|
|
|
|
configStringMap := make(map[string]string, len(cfg))
|
|
for k, v := range cfg {
|
|
keyString := k.String()
|
|
valueString, err := v.Value(config.NewBlindingDecrypter())
|
|
contract.AssertNoError(err)
|
|
configStringMap[keyString] = valueString
|
|
}
|
|
|
|
e.ch <- Event{
|
|
Type: PreludeEvent,
|
|
Payload: PreludeEventPayload{
|
|
IsPreview: isPreview,
|
|
Config: configStringMap,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *eventEmitter) previewSummaryEvent(resourceChanges ResourceChanges, policyPacks map[string]string) {
|
|
contract.Requiref(e != nil, "e", "!= nil")
|
|
|
|
e.ch <- Event{
|
|
Type: SummaryEvent,
|
|
Payload: SummaryEventPayload{
|
|
IsPreview: true,
|
|
MaybeCorrupt: false,
|
|
Duration: 0,
|
|
ResourceChanges: resourceChanges,
|
|
PolicyPacks: policyPacks,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *eventEmitter) updateSummaryEvent(maybeCorrupt bool,
|
|
duration time.Duration, resourceChanges ResourceChanges, policyPacks map[string]string) {
|
|
contract.Requiref(e != nil, "e", "!= nil")
|
|
|
|
e.ch <- Event{
|
|
Type: SummaryEvent,
|
|
Payload: SummaryEventPayload{
|
|
IsPreview: false,
|
|
MaybeCorrupt: maybeCorrupt,
|
|
Duration: duration,
|
|
ResourceChanges: resourceChanges,
|
|
PolicyPacks: policyPacks,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *eventEmitter) policyViolationEvent(urn resource.URN, d plugin.AnalyzeDiagnostic) {
|
|
|
|
contract.Requiref(e != nil, "e", "!= nil")
|
|
|
|
// Write prefix.
|
|
var prefix bytes.Buffer
|
|
switch d.EnforcementLevel {
|
|
case apitype.Mandatory:
|
|
prefix.WriteString(colors.SpecError)
|
|
case apitype.Advisory:
|
|
prefix.WriteString(colors.SpecWarning)
|
|
default:
|
|
contract.Failf("Unrecognized diagnostic severity: %v", d)
|
|
}
|
|
|
|
prefix.WriteString(string(d.EnforcementLevel))
|
|
prefix.WriteString(": ")
|
|
prefix.WriteString(colors.Reset)
|
|
|
|
// Write the message itself.
|
|
var buffer bytes.Buffer
|
|
buffer.WriteString(colors.SpecNote)
|
|
|
|
buffer.WriteString(d.Message)
|
|
|
|
buffer.WriteString(colors.Reset)
|
|
buffer.WriteRune('\n')
|
|
|
|
e.ch <- Event{
|
|
Type: PolicyViolationEvent,
|
|
Payload: PolicyViolationEventPayload{
|
|
ResourceURN: urn,
|
|
Message: logging.FilterString(buffer.String()),
|
|
Color: colors.Raw,
|
|
PolicyName: d.PolicyName,
|
|
PolicyPackName: d.PolicyPackName,
|
|
PolicyPackVersion: d.PolicyPackVersion,
|
|
EnforcementLevel: d.EnforcementLevel,
|
|
Prefix: logging.FilterString(prefix.String()),
|
|
},
|
|
}
|
|
}
|
|
|
|
func diagEvent(e *eventEmitter, d *diag.Diag, prefix, msg string, sev diag.Severity,
|
|
ephemeral bool) {
|
|
contract.Requiref(e != nil, "e", "!= nil")
|
|
|
|
e.ch <- Event{
|
|
Type: DiagEvent,
|
|
Payload: DiagEventPayload{
|
|
URN: d.URN,
|
|
Prefix: logging.FilterString(prefix),
|
|
Message: logging.FilterString(msg),
|
|
Color: colors.Raw,
|
|
Severity: sev,
|
|
StreamID: d.StreamID,
|
|
Ephemeral: ephemeral,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (e *eventEmitter) diagDebugEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
|
|
diagEvent(e, d, prefix, msg, diag.Debug, ephemeral)
|
|
}
|
|
|
|
func (e *eventEmitter) diagInfoEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
|
|
diagEvent(e, d, prefix, msg, diag.Info, ephemeral)
|
|
}
|
|
|
|
func (e *eventEmitter) diagInfoerrEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
|
|
diagEvent(e, d, prefix, msg, diag.Infoerr, ephemeral)
|
|
}
|
|
|
|
func (e *eventEmitter) diagErrorEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
|
|
diagEvent(e, d, prefix, msg, diag.Error, ephemeral)
|
|
}
|
|
|
|
func (e *eventEmitter) diagWarningEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
|
|
diagEvent(e, d, prefix, msg, diag.Warning, ephemeral)
|
|
}
|