github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/display/events.go (about)

     1  package display
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/pulumi/pulumi/pkg/v3/engine"
     9  	"github.com/pulumi/pulumi/pkg/v3/resource/stack"
    10  	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
    11  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
    12  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
    13  	"github.com/pulumi/pulumi/sdk/v3/go/common/display"
    14  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    15  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    16  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    17  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    18  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    19  )
    20  
    21  // ConvertEngineEvent converts a raw engine.Event into an apitype.EngineEvent used in the Pulumi
    22  // REST API. Returns an error if the engine event is unknown or not in an expected format.
    23  // EngineEvent.{ Sequence, Timestamp } are expected to be set by the caller.
    24  //
    25  // IMPORTANT: Any resource secret data stored in the engine event will be encrypted using the
    26  // blinding encrypter, and unrecoverable. So this operation is inherently lossy.
    27  func ConvertEngineEvent(e engine.Event, showSecrets bool) (apitype.EngineEvent, error) {
    28  	var apiEvent apitype.EngineEvent
    29  
    30  	// Error to return if the payload doesn't match expected.
    31  	eventTypePayloadMismatch := fmt.Errorf("unexpected payload for event type %v", e.Type)
    32  
    33  	switch e.Type {
    34  	case engine.CancelEvent:
    35  		apiEvent.CancelEvent = &apitype.CancelEvent{}
    36  
    37  	case engine.StdoutColorEvent:
    38  		p, ok := e.Payload().(engine.StdoutEventPayload)
    39  		if !ok {
    40  			return apiEvent, eventTypePayloadMismatch
    41  		}
    42  		apiEvent.StdoutEvent = &apitype.StdoutEngineEvent{
    43  			Message: p.Message,
    44  			Color:   string(p.Color),
    45  		}
    46  
    47  	case engine.DiagEvent:
    48  		p, ok := e.Payload().(engine.DiagEventPayload)
    49  		if !ok {
    50  			return apiEvent, eventTypePayloadMismatch
    51  		}
    52  		apiEvent.DiagnosticEvent = &apitype.DiagnosticEvent{
    53  			URN:       string(p.URN),
    54  			Prefix:    p.Prefix,
    55  			Message:   p.Message,
    56  			Color:     string(p.Color),
    57  			Severity:  string(p.Severity),
    58  			Ephemeral: p.Ephemeral,
    59  		}
    60  
    61  	case engine.PolicyViolationEvent:
    62  		p, ok := e.Payload().(engine.PolicyViolationEventPayload)
    63  		if !ok {
    64  			return apiEvent, eventTypePayloadMismatch
    65  		}
    66  		apiEvent.PolicyEvent = &apitype.PolicyEvent{
    67  			ResourceURN:          string(p.ResourceURN),
    68  			Message:              p.Message,
    69  			Color:                string(p.Color),
    70  			PolicyName:           p.PolicyName,
    71  			PolicyPackName:       p.PolicyPackName,
    72  			PolicyPackVersion:    p.PolicyPackVersion,
    73  			PolicyPackVersionTag: p.PolicyPackVersion,
    74  			EnforcementLevel:     string(p.EnforcementLevel),
    75  		}
    76  
    77  	case engine.PreludeEvent:
    78  		p, ok := e.Payload().(engine.PreludeEventPayload)
    79  		if !ok {
    80  			return apiEvent, eventTypePayloadMismatch
    81  		}
    82  		// Convert the config bag.
    83  		cfg := make(map[string]string)
    84  		for k, v := range p.Config {
    85  			cfg[k] = v
    86  		}
    87  		apiEvent.PreludeEvent = &apitype.PreludeEvent{
    88  			Config: cfg,
    89  		}
    90  
    91  	case engine.SummaryEvent:
    92  		p, ok := e.Payload().(engine.SummaryEventPayload)
    93  		if !ok {
    94  			return apiEvent, eventTypePayloadMismatch
    95  		}
    96  		// Convert the resource changes.
    97  		changes := make(map[apitype.OpType]int)
    98  		for op, count := range p.ResourceChanges {
    99  			changes[apitype.OpType(op)] = count
   100  		}
   101  		apiEvent.SummaryEvent = &apitype.SummaryEvent{
   102  			MaybeCorrupt:    p.MaybeCorrupt,
   103  			DurationSeconds: int(p.Duration.Seconds()),
   104  			ResourceChanges: changes,
   105  			PolicyPacks:     p.PolicyPacks,
   106  		}
   107  
   108  	case engine.ResourcePreEvent:
   109  		p, ok := e.Payload().(engine.ResourcePreEventPayload)
   110  		if !ok {
   111  			return apiEvent, eventTypePayloadMismatch
   112  		}
   113  		apiEvent.ResourcePreEvent = &apitype.ResourcePreEvent{
   114  			Metadata: convertStepEventMetadata(p.Metadata, showSecrets),
   115  			Planning: p.Planning,
   116  		}
   117  
   118  	case engine.ResourceOutputsEvent:
   119  		p, ok := e.Payload().(engine.ResourceOutputsEventPayload)
   120  		if !ok {
   121  			return apiEvent, eventTypePayloadMismatch
   122  		}
   123  		apiEvent.ResOutputsEvent = &apitype.ResOutputsEvent{
   124  			Metadata: convertStepEventMetadata(p.Metadata, showSecrets),
   125  			Planning: p.Planning,
   126  		}
   127  
   128  	case engine.ResourceOperationFailed:
   129  		p, ok := e.Payload().(engine.ResourceOperationFailedPayload)
   130  		if !ok {
   131  			return apiEvent, eventTypePayloadMismatch
   132  		}
   133  		apiEvent.ResOpFailedEvent = &apitype.ResOpFailedEvent{
   134  			Metadata: convertStepEventMetadata(p.Metadata, showSecrets),
   135  			Status:   int(p.Status),
   136  			Steps:    p.Steps,
   137  		}
   138  
   139  	default:
   140  		return apiEvent, fmt.Errorf("unknown event type %q", e.Type)
   141  	}
   142  
   143  	return apiEvent, nil
   144  }
   145  
   146  func convertStepEventMetadata(md engine.StepEventMetadata, showSecrets bool) apitype.StepEventMetadata {
   147  	keys := make([]string, len(md.Keys))
   148  	for i, v := range md.Keys {
   149  		keys[i] = string(v)
   150  	}
   151  	var diffs []string
   152  	for _, v := range md.Diffs {
   153  		diffs = append(diffs, string(v))
   154  	}
   155  	var detailedDiff map[string]apitype.PropertyDiff
   156  	if md.DetailedDiff != nil {
   157  		detailedDiff = make(map[string]apitype.PropertyDiff)
   158  		for k, v := range md.DetailedDiff {
   159  			var d apitype.DiffKind
   160  			switch v.Kind {
   161  			case plugin.DiffAdd:
   162  				d = apitype.DiffAdd
   163  			case plugin.DiffAddReplace:
   164  				d = apitype.DiffAddReplace
   165  			case plugin.DiffDelete:
   166  				d = apitype.DiffDelete
   167  			case plugin.DiffDeleteReplace:
   168  				d = apitype.DiffDeleteReplace
   169  			case plugin.DiffUpdate:
   170  				d = apitype.DiffUpdate
   171  			case plugin.DiffUpdateReplace:
   172  				d = apitype.DiffUpdateReplace
   173  			default:
   174  				contract.Failf("unrecognized diff kind %v", v)
   175  			}
   176  			detailedDiff[k] = apitype.PropertyDiff{
   177  				Kind:      d,
   178  				InputDiff: v.InputDiff,
   179  			}
   180  		}
   181  	}
   182  
   183  	return apitype.StepEventMetadata{
   184  		Op:   apitype.OpType(md.Op),
   185  		URN:  string(md.URN),
   186  		Type: string(md.Type),
   187  
   188  		Old: convertStepEventStateMetadata(md.Old, showSecrets),
   189  		New: convertStepEventStateMetadata(md.New, showSecrets),
   190  
   191  		Keys:         keys,
   192  		Diffs:        diffs,
   193  		DetailedDiff: detailedDiff,
   194  		Logical:      md.Logical,
   195  		Provider:     md.Provider,
   196  	}
   197  }
   198  
   199  // convertStepEventStateMetadata converts the internal StepEventStateMetadata to the API type
   200  // we send over the wire.
   201  //
   202  // IMPORTANT: Any secret values are encrypted using the blinding encrypter. So any secret data
   203  // in the resource state will be lost and unrecoverable.
   204  func convertStepEventStateMetadata(md *engine.StepEventStateMetadata,
   205  	showSecrets bool) *apitype.StepEventStateMetadata {
   206  
   207  	if md == nil {
   208  		return nil
   209  	}
   210  
   211  	encrypter := config.BlindingCrypter
   212  	inputs, err := stack.SerializeProperties(md.Inputs, encrypter, showSecrets)
   213  	contract.IgnoreError(err)
   214  
   215  	outputs, err := stack.SerializeProperties(md.Outputs, encrypter, showSecrets)
   216  	contract.IgnoreError(err)
   217  
   218  	return &apitype.StepEventStateMetadata{
   219  		Type: string(md.Type),
   220  		URN:  string(md.URN),
   221  
   222  		Custom:     md.Custom,
   223  		Delete:     md.Delete,
   224  		ID:         string(md.ID),
   225  		Parent:     string(md.Parent),
   226  		Protect:    md.Protect,
   227  		Inputs:     inputs,
   228  		Outputs:    outputs,
   229  		InitErrors: md.InitErrors,
   230  	}
   231  }
   232  
   233  // ConvertJSONEvent converts an apitype.EngineEvent from the Pulumi REST API into a raw engine.Event
   234  // Returns an error if the engine event is unknown or not in an expected format.
   235  //
   236  // IMPORTANT: Any resource secret data stored in the engine event will be encrypted using the
   237  // blinding encrypter, and unrecoverable. So this operation is inherently lossy.
   238  func ConvertJSONEvent(apiEvent apitype.EngineEvent) (engine.Event, error) {
   239  	var event engine.Event
   240  
   241  	switch {
   242  	case apiEvent.CancelEvent != nil:
   243  		event = engine.NewEvent(engine.CancelEvent, nil)
   244  
   245  	case apiEvent.StdoutEvent != nil:
   246  		p := apiEvent.StdoutEvent
   247  		event = engine.NewEvent(engine.StdoutColorEvent, engine.StdoutEventPayload{
   248  			Message: p.Message,
   249  			Color:   colors.Colorization(p.Color),
   250  		})
   251  
   252  	case apiEvent.DiagnosticEvent != nil:
   253  		p := apiEvent.DiagnosticEvent
   254  		event = engine.NewEvent(engine.DiagEvent, engine.DiagEventPayload{
   255  			URN:       resource.URN(p.URN),
   256  			Prefix:    p.Prefix,
   257  			Message:   p.Message,
   258  			Color:     colors.Colorization(p.Color),
   259  			Severity:  diag.Severity(p.Severity),
   260  			Ephemeral: p.Ephemeral,
   261  		})
   262  		apiEvent.DiagnosticEvent = &apitype.DiagnosticEvent{}
   263  
   264  	case apiEvent.PolicyEvent != nil:
   265  		p := apiEvent.PolicyEvent
   266  		event = engine.NewEvent(engine.PolicyViolationEvent, engine.PolicyViolationEventPayload{
   267  			ResourceURN:       resource.URN(p.ResourceURN),
   268  			Message:           p.Message,
   269  			Color:             colors.Colorization(p.Color),
   270  			PolicyName:        p.PolicyName,
   271  			PolicyPackName:    p.PolicyPackName,
   272  			PolicyPackVersion: p.PolicyPackVersion,
   273  			EnforcementLevel:  apitype.EnforcementLevel(p.EnforcementLevel),
   274  		})
   275  
   276  	case apiEvent.PreludeEvent != nil:
   277  		p := apiEvent.PreludeEvent
   278  
   279  		// Convert the config bag.
   280  		event = engine.NewEvent(engine.PreludeEvent, engine.PreludeEventPayload{
   281  			Config: p.Config,
   282  		})
   283  
   284  	case apiEvent.SummaryEvent != nil:
   285  		p := apiEvent.SummaryEvent
   286  		// Convert the resource changes.
   287  		changes := display.ResourceChanges{}
   288  		for op, count := range p.ResourceChanges {
   289  			changes[display.StepOp(op)] = count
   290  		}
   291  		event = engine.NewEvent(engine.SummaryEvent, engine.SummaryEventPayload{
   292  			MaybeCorrupt:    p.MaybeCorrupt,
   293  			Duration:        time.Duration(p.DurationSeconds) * time.Second,
   294  			ResourceChanges: changes,
   295  			PolicyPacks:     p.PolicyPacks,
   296  		})
   297  
   298  	case apiEvent.ResourcePreEvent != nil:
   299  		p := apiEvent.ResourcePreEvent
   300  		event = engine.NewEvent(engine.ResourcePreEvent, engine.ResourcePreEventPayload{
   301  			Metadata: convertJSONStepEventMetadata(p.Metadata),
   302  			Planning: p.Planning,
   303  		})
   304  
   305  	case apiEvent.ResOutputsEvent != nil:
   306  		p := apiEvent.ResOutputsEvent
   307  		event = engine.NewEvent(engine.ResourceOutputsEvent, engine.ResourceOutputsEventPayload{
   308  			Metadata: convertJSONStepEventMetadata(p.Metadata),
   309  			Planning: p.Planning,
   310  		})
   311  
   312  	case apiEvent.ResOpFailedEvent != nil:
   313  		p := apiEvent.ResOpFailedEvent
   314  		event = engine.NewEvent(engine.ResourceOperationFailed, engine.ResourceOperationFailedPayload{
   315  			Metadata: convertJSONStepEventMetadata(p.Metadata),
   316  			Status:   resource.Status(p.Status),
   317  			Steps:    p.Steps,
   318  		})
   319  
   320  	default:
   321  		return event, errors.New("unknown event type")
   322  	}
   323  
   324  	return event, nil
   325  }
   326  
   327  func convertJSONStepEventMetadata(md apitype.StepEventMetadata) engine.StepEventMetadata {
   328  	keys := make([]resource.PropertyKey, len(md.Keys))
   329  	for i, v := range md.Keys {
   330  		keys[i] = resource.PropertyKey(v)
   331  	}
   332  	var diffs []resource.PropertyKey
   333  	for _, v := range md.Diffs {
   334  		diffs = append(diffs, resource.PropertyKey(v))
   335  	}
   336  	var detailedDiff map[string]plugin.PropertyDiff
   337  	if md.DetailedDiff != nil {
   338  		detailedDiff = make(map[string]plugin.PropertyDiff)
   339  		for k, v := range md.DetailedDiff {
   340  			var d plugin.DiffKind
   341  			switch v.Kind {
   342  			case apitype.DiffAdd:
   343  				d = plugin.DiffAdd
   344  			case apitype.DiffAddReplace:
   345  				d = plugin.DiffAddReplace
   346  			case apitype.DiffDelete:
   347  				d = plugin.DiffDelete
   348  			case apitype.DiffDeleteReplace:
   349  				d = plugin.DiffDeleteReplace
   350  			case apitype.DiffUpdate:
   351  				d = plugin.DiffUpdate
   352  			case apitype.DiffUpdateReplace:
   353  				d = plugin.DiffUpdateReplace
   354  			default:
   355  				contract.Failf("unrecognized diff kind %v", v)
   356  			}
   357  			detailedDiff[k] = plugin.PropertyDiff{
   358  				Kind:      d,
   359  				InputDiff: v.InputDiff,
   360  			}
   361  		}
   362  	}
   363  
   364  	old, new := convertJSONStepEventStateMetadata(md.Old), convertJSONStepEventStateMetadata(md.New)
   365  
   366  	res := old
   367  	if new != nil {
   368  		res = new
   369  	}
   370  
   371  	return engine.StepEventMetadata{
   372  		Op:   display.StepOp(md.Op),
   373  		URN:  resource.URN(md.URN),
   374  		Type: tokens.Type(md.Type),
   375  
   376  		Old: old,
   377  		New: new,
   378  		Res: res,
   379  
   380  		Keys:         keys,
   381  		Diffs:        diffs,
   382  		DetailedDiff: detailedDiff,
   383  		Logical:      md.Logical,
   384  		Provider:     md.Provider,
   385  	}
   386  }
   387  
   388  // convertJSONStepEventStateMetadata converts the internal StepEventStateMetadata to the API type
   389  // we send over the wire.
   390  //
   391  // IMPORTANT: Any secret values are encrypted using the blinding encrypter. So any secret data
   392  // in the resource state will be lost and unrecoverable.
   393  func convertJSONStepEventStateMetadata(md *apitype.StepEventStateMetadata) *engine.StepEventStateMetadata {
   394  	if md == nil {
   395  		return nil
   396  	}
   397  
   398  	crypter := config.BlindingCrypter
   399  	inputs, err := stack.DeserializeProperties(md.Inputs, crypter, crypter)
   400  	contract.IgnoreError(err)
   401  
   402  	outputs, err := stack.DeserializeProperties(md.Outputs, crypter, crypter)
   403  	contract.IgnoreError(err)
   404  
   405  	return &engine.StepEventStateMetadata{
   406  		Type: tokens.Type(md.Type),
   407  		URN:  resource.URN(md.URN),
   408  
   409  		Custom:     md.Custom,
   410  		Delete:     md.Delete,
   411  		ID:         resource.ID(md.ID),
   412  		Parent:     resource.URN(md.Parent),
   413  		Protect:    md.Protect,
   414  		Inputs:     inputs,
   415  		Outputs:    outputs,
   416  		InitErrors: md.InitErrors,
   417  	}
   418  }