This implements the new algorithm for deciding which resources must be deleted due to a delete-before-replace operation. We need to compute the set of resources that may be replaced by a change to the resource under consideration. We do this by taking the complete set of transitive dependents on the resource under consideration and removing any resources that would not be replaced by changes to their dependencies. We determine whether or not a resource may be replaced by substituting unknowns for input properties that may change due to deletion of the resources their value depends on and calling the resource provider's Diff method. This is perhaps clearer when described by example. Consider the following dependency graph: A __|__ B C | _|_ D E F In this graph, all of B, C, D, E, and F transitively depend on A. It may be the case, however, that changes to the specific properties of any of those resources R that would occur if a resource on the path to A were deleted and recreated may not cause R to be replaced. For example, the edge from B to A may be a simple dependsOn edge such that a change to B does not actually influence any of B's input properties. In that case, neither B nor D would need to be deleted before A could be deleted. In order to make the above algorithm a reality, the resource monitor interface has been updated to include a map that associates an input property key with the list of resources that input property depends on. Older clients of the resource monitor will leave this map empty, in which case all input properties will be treated as depending on all dependencies of the resource. This is probably overly conservative, but it is less conservative than what we currently implement, and is certainly correct.
360 lines
19 KiB
Go
360 lines
19 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 apitype contains the full set of "exchange types" that are serialized and sent across separately versionable
|
|
// boundaries, including service APIs, plugins, and file formats. As a result, we must consider the versioning impacts
|
|
// for each change we make to types within this package. In general, this means the following:
|
|
//
|
|
// 1) DO NOT take anything away
|
|
// 2) DO NOT change processing rules
|
|
// 3) DO NOT make optional things required
|
|
// 4) DO make anything new be optional
|
|
//
|
|
// In the event that this is not possible, a breaking change is implied. The preferred approach is to never make
|
|
// breaking changes. If that isn't possible, the next best approach is to support both the old and new formats
|
|
// side-by-side (for instance, by using a union type for the property in question).
|
|
//
|
|
// nolint: lll
|
|
package apitype
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource"
|
|
"github.com/pulumi/pulumi/pkg/resource/config"
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
|
"github.com/pulumi/pulumi/pkg/workspace"
|
|
)
|
|
|
|
const (
|
|
// DeploymentSchemaVersionCurrent is the current version of the `Deployment` schema.
|
|
// Any deployments newer than this version will be rejected.
|
|
DeploymentSchemaVersionCurrent = 3
|
|
)
|
|
|
|
// VersionedCheckpoint is a version number plus a json document. The version number describes what
|
|
// version of the Checkpoint structure the Checkpoint member's json document can decode into.
|
|
type VersionedCheckpoint struct {
|
|
Version int `json:"version"`
|
|
Checkpoint json.RawMessage `json:"checkpoint"`
|
|
}
|
|
|
|
// CheckpointV1 is a serialized deployment target plus a record of the latest deployment.
|
|
type CheckpointV1 struct {
|
|
// Stack is the stack to update.
|
|
Stack tokens.QName `json:"stack" yaml:"stack"`
|
|
// Config contains a bag of optional configuration keys/values.
|
|
Config config.Map `json:"config,omitempty" yaml:"config,omitempty"`
|
|
// Latest is the latest/current deployment (if an update has occurred).
|
|
Latest *DeploymentV1 `json:"latest,omitempty" yaml:"latest,omitempty"`
|
|
}
|
|
|
|
// CheckpointV2 is the second version of the Checkpoint. It contains a newer version of
|
|
// the latest deployment.
|
|
type CheckpointV2 struct {
|
|
// Stack is the stack to update.
|
|
Stack tokens.QName `json:"stack" yaml:"stack"`
|
|
// Config contains a bag of optional configuration keys/values.
|
|
Config config.Map `json:"config,omitempty" yaml:"config,omitempty"`
|
|
// Latest is the latest/current deployment (if an update has occurred).
|
|
Latest *DeploymentV2 `json:"latest,omitempty" yaml:"latest,omitempty"`
|
|
}
|
|
|
|
// CheckpointV3 is the third version of the Checkpoint. It contains a newer version of
|
|
// the latest deployment.
|
|
type CheckpointV3 struct {
|
|
// Stack is the stack to update.
|
|
Stack tokens.QName `json:"stack" yaml:"stack"`
|
|
// Config contains a bag of optional configuration keys/values.
|
|
Config config.Map `json:"config,omitempty" yaml:"config,omitempty"`
|
|
// Latest is the latest/current deployment (if an update has occurred).
|
|
Latest *DeploymentV3 `json:"latest,omitempty" yaml:"latest,omitempty"`
|
|
}
|
|
|
|
// DeploymentV1 represents a deployment that has actually occurred. It is similar to the engine's snapshot structure,
|
|
// except that it flattens and rearranges a few data structures for serializability.
|
|
type DeploymentV1 struct {
|
|
// Manifest contains metadata about this deployment.
|
|
Manifest ManifestV1 `json:"manifest" yaml:"manifest"`
|
|
// Resources contains all resources that are currently part of this stack after this deployment has finished.
|
|
Resources []ResourceV1 `json:"resources,omitempty" yaml:"resources,omitempty"`
|
|
}
|
|
|
|
// DeploymentV2 is the second version of the Deployment. It contains newer versions of the
|
|
// Resource API type.
|
|
type DeploymentV2 struct {
|
|
// Manifest contains metadata about this deployment.
|
|
Manifest ManifestV1 `json:"manifest" yaml:"manifest"`
|
|
// Resources contains all resources that are currently part of this stack after this deployment has finished.
|
|
Resources []ResourceV2 `json:"resources,omitempty" yaml:"resources,omitempty"`
|
|
// PendingOperations are all operations that were known by the engine to be currently executing.
|
|
PendingOperations []OperationV1 `json:"pending_operations,omitempty" yaml:"pending_operations,omitempty"`
|
|
}
|
|
|
|
// DeploymentV3 is the third version of the Deployment. It contains newer versions of the
|
|
// Resource and Operation API types and a placeholder for a stack's secrets configuration.
|
|
type DeploymentV3 struct {
|
|
// Manifest contains metadata about this deployment.
|
|
Manifest ManifestV1 `json:"manifest" yaml:"manifest"`
|
|
// SecretsProviders is a placeholder for secret provider configuration.
|
|
SecretsProviders interface{} `json:"secrets_providers,omitempty" yaml:"secrets_providers,omitempty"`
|
|
// Resources contains all resources that are currently part of this stack after this deployment has finished.
|
|
Resources []ResourceV3 `json:"resources,omitempty" yaml:"resources,omitempty"`
|
|
// PendingOperations are all operations that were known by the engine to be currently executing.
|
|
PendingOperations []OperationV2 `json:"pending_operations,omitempty" yaml:"pending_operations,omitempty"`
|
|
}
|
|
|
|
// OperationType is the type of an operation initiated by the engine. Its value indicates the type of operation
|
|
// that the engine initiated.
|
|
type OperationType string
|
|
|
|
const (
|
|
// OperationTypeCreating is the state of resources that are being created.
|
|
OperationTypeCreating OperationType = "creating"
|
|
// OperationTypeUpdating is the state of resources that are being updated.
|
|
OperationTypeUpdating OperationType = "updating"
|
|
// OperationTypeDeleting is the state of resources that are being deleted.
|
|
OperationTypeDeleting OperationType = "deleting"
|
|
// OperationTypeReading is the state of resources that are being read.
|
|
OperationTypeReading OperationType = "reading"
|
|
)
|
|
|
|
// OperationV1 represents an operation that the engine is performing. It consists of a Resource, which is the state
|
|
// that the engine used to initiate the operation, and a Status, which is a string representation of the operation
|
|
// that the engine initiated.
|
|
type OperationV1 struct {
|
|
// Resource is the state that the engine used to initiate this operation.
|
|
Resource ResourceV2 `json:"resource" yaml:"resource"`
|
|
// Status is a string representation of the operation that the engine is performing.
|
|
Type OperationType `json:"type" yaml:"type"`
|
|
}
|
|
|
|
// OperationV2 represents an operation that the engine is performing. It consists of a Resource, which is the state
|
|
// that the engine used to initiate the operation, and a Status, which is a string representation of the operation
|
|
// that the engine initiated.
|
|
type OperationV2 struct {
|
|
// Resource is the state that the engine used to initiate this operation.
|
|
Resource ResourceV3 `json:"resource" yaml:"resource"`
|
|
// Status is a string representation of the operation that the engine is performing.
|
|
Type OperationType `json:"type" yaml:"type"`
|
|
}
|
|
|
|
// UntypedDeployment contains an inner, untyped deployment structure.
|
|
type UntypedDeployment struct {
|
|
// Version indicates the schema of the encoded deployment.
|
|
Version int `json:"version,omitempty"`
|
|
// The opaque Pulumi deployment. This is conceptually of type `Deployment`, but we use `json.Message` to
|
|
// permit round-tripping of stack contents when an older client is talking to a newer server. If we unmarshaled
|
|
// the contents, and then remarshaled them, we could end up losing important information.
|
|
Deployment json.RawMessage `json:"deployment,omitempty"`
|
|
}
|
|
|
|
// ResourceV1 describes a Cloud resource constructed by Pulumi.
|
|
type ResourceV1 struct {
|
|
// URN uniquely identifying this resource.
|
|
URN resource.URN `json:"urn" yaml:"urn"`
|
|
// Custom is true when it is managed by a plugin.
|
|
Custom bool `json:"custom" yaml:"custom"`
|
|
// Delete is true when the resource should be deleted during the next update.
|
|
Delete bool `json:"delete,omitempty" yaml:"delete,omitempty"`
|
|
// ID is the provider-assigned resource, if any, for custom resources.
|
|
ID resource.ID `json:"id,omitempty" yaml:"id,omitempty"`
|
|
// Type is the resource's full type token.
|
|
Type tokens.Type `json:"type" yaml:"type"`
|
|
// Inputs are the input properties supplied to the provider.
|
|
Inputs map[string]interface{} `json:"inputs,omitempty" yaml:"inputs,omitempty"`
|
|
// Defaults contains the default values supplied by the provider (DEPRECATED, see #637).
|
|
Defaults map[string]interface{} `json:"defaults,omitempty" yaml:"defaults,omitempty"`
|
|
// Outputs are the output properties returned by the provider after provisioning.
|
|
Outputs map[string]interface{} `json:"outputs,omitempty" yaml:"outputs,omitempty"`
|
|
// Parent is an optional parent URN if this resource is a child of it.
|
|
Parent resource.URN `json:"parent,omitempty" yaml:"parent,omitempty"`
|
|
// Protect is set to true when this resource is "protected" and may not be deleted.
|
|
Protect bool `json:"protect,omitempty" yaml:"protect,omitempty"`
|
|
// Dependencies contains the dependency edges to other resources that this depends on.
|
|
Dependencies []resource.URN `json:"dependencies" yaml:"dependencies,omitempty"`
|
|
// InitErrors is the set of errors encountered in the process of initializing resource (i.e.,
|
|
// during create or update).
|
|
InitErrors []string `json:"initErrors" yaml:"initErrors,omitempty"`
|
|
}
|
|
|
|
// ResourceV2 is the second version of the Resource API type. It absorbs a few breaking changes:
|
|
// 1. The deprecated `Defaults` field is removed because it is not used anywhere,
|
|
// 2. It adds an additional bool field, "External", which reflects whether or not this resource
|
|
// exists because of a call to `ReadResource`. This is motivated by a need to store
|
|
// resources that Pulumi does not own in the deployment.
|
|
// 3. It adds an additional string field, "Provider", that is a reference to a first-class provider
|
|
// associated with this resource.
|
|
//
|
|
// Migrating from ResourceV1 to ResourceV2 involves:
|
|
// 1. Dropping the `Defaults` field (it should be empty anyway)
|
|
// 2. Setting the `External` field to "false", since a ResourceV1 existing for a resource
|
|
// implies that it is owned by Pulumi. Note that since this is the default value for
|
|
// booleans in Go, no explicit assignment needs to be made.
|
|
// 3. Setting the "Provider" field to the empty string, because V1 deployments don't have first-class providers.
|
|
type ResourceV2 struct {
|
|
// URN uniquely identifying this resource.
|
|
URN resource.URN `json:"urn" yaml:"urn"`
|
|
// Custom is true when it is managed by a plugin.
|
|
Custom bool `json:"custom" yaml:"custom"`
|
|
// Delete is true when the resource should be deleted during the next update.
|
|
Delete bool `json:"delete,omitempty" yaml:"delete,omitempty"`
|
|
// ID is the provider-assigned resource, if any, for custom resources.
|
|
ID resource.ID `json:"id,omitempty" yaml:"id,omitempty"`
|
|
// Type is the resource's full type token.
|
|
Type tokens.Type `json:"type" yaml:"type"`
|
|
// Inputs are the input properties supplied to the provider.
|
|
Inputs map[string]interface{} `json:"inputs,omitempty" yaml:"inputs,omitempty"`
|
|
// Outputs are the output properties returned by the provider after provisioning.
|
|
Outputs map[string]interface{} `json:"outputs,omitempty" yaml:"outputs,omitempty"`
|
|
// Parent is an optional parent URN if this resource is a child of it.
|
|
Parent resource.URN `json:"parent,omitempty" yaml:"parent,omitempty"`
|
|
// Protect is set to true when this resource is "protected" and may not be deleted.
|
|
Protect bool `json:"protect,omitempty" yaml:"protect,omitempty"`
|
|
// External is set to true when the lifecycle of this resource is not managed by Pulumi.
|
|
External bool `json:"external,omitempty" yaml:"external,omitempty"`
|
|
// Dependencies contains the dependency edges to other resources that this depends on.
|
|
Dependencies []resource.URN `json:"dependencies,omitempty" yaml:"dependencies,omitempty"`
|
|
// InitErrors is the set of errors encountered in the process of initializing resource (i.e.,
|
|
// during create or update).
|
|
InitErrors []string `json:"initErrors,omitempty" yaml:"initErrors,omitempty"`
|
|
// Provider is a reference to the provider that is associated with this resource.
|
|
Provider string `json:"provider,omitempty" yaml:"provider,omitempty"`
|
|
}
|
|
|
|
// ResourceV3 is the third version of the Resource API type. It absorbs a few breaking changes:
|
|
// 1. It adds a map from input property names to the dependencies that affect that input property. This is used to
|
|
// improve the precision of delete-before-create operations.
|
|
// 2. It adds a new boolean field, `PendingReplacement`, that marks resources that have been deleted as part of a
|
|
// delete-before-create operation but have not yet been recreated.
|
|
//
|
|
// Migrating from ResourceV2 to ResourceV3 involves:
|
|
// 1. Populating the map from input property names to dependencies by assuming that every dependency listed in
|
|
// `Dependencies` affects every input property.
|
|
type ResourceV3 struct {
|
|
// URN uniquely identifying this resource.
|
|
URN resource.URN `json:"urn" yaml:"urn"`
|
|
// Custom is true when it is managed by a plugin.
|
|
Custom bool `json:"custom" yaml:"custom"`
|
|
// Delete is true when the resource should be deleted during the next update.
|
|
Delete bool `json:"delete,omitempty" yaml:"delete,omitempty"`
|
|
// ID is the provider-assigned resource, if any, for custom resources.
|
|
ID resource.ID `json:"id,omitempty" yaml:"id,omitempty"`
|
|
// Type is the resource's full type token.
|
|
Type tokens.Type `json:"type" yaml:"type"`
|
|
// Inputs are the input properties supplied to the provider.
|
|
Inputs map[string]interface{} `json:"inputs,omitempty" yaml:"inputs,omitempty"`
|
|
// Outputs are the output properties returned by the provider after provisioning.
|
|
Outputs map[string]interface{} `json:"outputs,omitempty" yaml:"outputs,omitempty"`
|
|
// Parent is an optional parent URN if this resource is a child of it.
|
|
Parent resource.URN `json:"parent,omitempty" yaml:"parent,omitempty"`
|
|
// Protect is set to true when this resource is "protected" and may not be deleted.
|
|
Protect bool `json:"protect,omitempty" yaml:"protect,omitempty"`
|
|
// External is set to true when the lifecycle of this resource is not managed by Pulumi.
|
|
External bool `json:"external,omitempty" yaml:"external,omitempty"`
|
|
// Dependencies contains the dependency edges to other resources that this depends on.
|
|
Dependencies []resource.URN `json:"dependencies,omitempty" yaml:"dependencies,omitempty"`
|
|
// InitErrors is the set of errors encountered in the process of initializing resource (i.e.,
|
|
// during create or update).
|
|
InitErrors []string `json:"initErrors,omitempty" yaml:"initErrors,omitempty"`
|
|
// Provider is a reference to the provider that is associated with this resource.
|
|
Provider string `json:"provider,omitempty" yaml:"provider,omitempty"`
|
|
// PropertyDependencies maps from an input property name to the set of resources that property depends on.
|
|
PropertyDependencies map[resource.PropertyKey][]resource.URN `json:"propertyDependencies,omitempty" yaml:"property_dependencies,omitempty"`
|
|
// PendingReplacement is used to track delete-before-replace resources that have been deleted but not yet
|
|
// recreated.
|
|
PendingReplacement bool `json:"pendingReplacement,omitempty" yaml:"pendingReplacement,omitempty"`
|
|
}
|
|
|
|
// ManifestV1 captures meta-information about this checkpoint file, such as versions of binaries, etc.
|
|
type ManifestV1 struct {
|
|
// Time of the update.
|
|
Time time.Time `json:"time" yaml:"time"`
|
|
// Magic number, used to identify integrity of the checkpoint.
|
|
Magic string `json:"magic" yaml:"magic"`
|
|
// Version of the Pulumi engine used to render the checkpoint.
|
|
Version string `json:"version" yaml:"version"`
|
|
// Plugins contains the binary version info of plug-ins used.
|
|
Plugins []PluginInfoV1 `json:"plugins,omitempty" yaml:"plugins,omitempty"`
|
|
}
|
|
|
|
// PluginInfoV1 captures the version and information about a plugin.
|
|
type PluginInfoV1 struct {
|
|
Name string `json:"name" yaml:"name"`
|
|
Path string `json:"path" yaml:"path"`
|
|
Type workspace.PluginKind `json:"type" yaml:"type"`
|
|
Version string `json:"version" yaml:"version"`
|
|
}
|
|
|
|
// SecretV1 captures the information that a particular value is secret and must be decrypted before use.
|
|
//
|
|
// NOTE: nothing produces these values yet. This type is merely a placeholder for future use.
|
|
type SecretV1 struct {
|
|
Sig string `json:"4dabf18193072939515e22adb298388d" yaml:"4dabf18193072939515e22adb298388d"`
|
|
Ciphertext string `json:"ciphertext" yaml:"ciphertext"`
|
|
}
|
|
|
|
// ConfigValue describes a single (possibly secret) configuration value.
|
|
type ConfigValue struct {
|
|
// String is either the plaintext value (for non-secrets) or the base64-encoded ciphertext (for secrets).
|
|
String string `json:"string"`
|
|
// Secret is true if this value is a secret and false otherwise.
|
|
Secret bool `json:"secret"`
|
|
}
|
|
|
|
// StackTagName is the key for the tags bag in stack. This is just a string, but we use a type alias to provide a richer
|
|
// description of how the string is used in our apitype definitions.
|
|
type StackTagName = string
|
|
|
|
const (
|
|
// ProjectNameTag is a tag that represents the name of a project (coresponds to the `name` property of Pulumi.yaml).
|
|
ProjectNameTag StackTagName = "pulumi:project"
|
|
// ProjectRuntimeTag is a tag that represents the runtime of a project (the `runtime` property of Pulumi.yaml).
|
|
ProjectRuntimeTag StackTagName = "pulumi:runtime"
|
|
// ProjectDescriptionTag is a tag that represents the description of a project (Pulumi.yaml's `description`).
|
|
ProjectDescriptionTag StackTagName = "pulumi:description"
|
|
// GitHubOwnerNameTag is a tag that represents the name of the owner on GitHub that this stack
|
|
// may be associated with (inferred by the CLI based on git remote info).
|
|
// TODO [pulumi/pulumi-service#2306] Once the UI is updated, we would no longer need the GitHub specific keys.
|
|
GitHubOwnerNameTag StackTagName = "gitHub:owner"
|
|
// GitHubRepositoryNameTag is a tag that represents the name of a repository on GitHub that this stack
|
|
// may be associated with (inferred by the CLI based on git remote info).
|
|
GitHubRepositoryNameTag StackTagName = "gitHub:repo"
|
|
// VCSOwnerNameTag is a tag that represents the name of the owner on the cloud VCS that this stack
|
|
// may be associated with (inferred by the CLI based on git remote info).
|
|
VCSOwnerNameTag StackTagName = "vcs:owner"
|
|
// VCSRepositoryNameTag is a tag that represents the name of a repository on the cloud VCS that this stack
|
|
// may be associated with (inferred by the CLI based on git remote info).
|
|
VCSRepositoryNameTag StackTagName = "vcs:repo"
|
|
// VCSRepositoryKindTag is a tag that represents the kind of the cloud VCS that this stack
|
|
// may be associated with (inferred by the CLI based on the git remote info).
|
|
VCSRepositoryKindTag StackTagName = "vcs:kind"
|
|
)
|
|
|
|
// Stack describes a Stack running on a Pulumi Cloud.
|
|
type Stack struct {
|
|
CloudName string `json:"cloudName"`
|
|
OrgName string `json:"orgName"`
|
|
|
|
RepoName string `json:"repoName"`
|
|
ProjectName string `json:"projectName"`
|
|
StackName tokens.QName `json:"stackName"`
|
|
|
|
ActiveUpdate string `json:"activeUpdate"`
|
|
Resources []ResourceV1 `json:"resources,omitempty"`
|
|
Tags map[StackTagName]string `json:"tags,omitempty"`
|
|
|
|
Version int `json:"version"`
|
|
}
|