Remove "down" operation for migration - it is not semantically valid in

general.
This commit is contained in:
Sean Gillespie 2018-07-27 14:22:16 -07:00
parent 9ca76b65c0
commit 48d095d66f
13 changed files with 31 additions and 220 deletions

View file

@ -39,7 +39,7 @@ import (
const (
// DeploymentSchemaVersionCurrent is the current version of the `Deployment` schema.
// Any deployments newer than this version will be rejected.
DeploymentSchemaVersionCurrent = 2
DeploymentSchemaVersionCurrent = 1
)
// VersionedCheckpoint is a version number plus a json document. The version number describes what

View file

@ -32,17 +32,3 @@ func UpToCheckpointV2(v1 apitype.CheckpointV1) apitype.CheckpointV2 {
v2.Latest = &v2deploy
return v2
}
// DownToCheckpointV1 migrates a CheckpointV2 to a CheckpointV1
func DownToCheckpointV1(v2 apitype.CheckpointV2) apitype.CheckpointV1 {
var v1 apitype.CheckpointV1
v1.Stack = v2.Stack
v1.Config = make(config.Map)
for key, value := range v2.Config {
v1.Config[key] = value
}
v1deploy := DownToDeploymentV1(*v2.Latest)
v1.Latest = &v1deploy
return v1
}

View file

@ -42,23 +42,3 @@ func TestCheckpointV1ToV2(t *testing.T) {
}, v2.Config)
assert.Len(t, v2.Latest.Resources, 0)
}
func TestCheckpointV2ToV1(t *testing.T) {
v2 := apitype.CheckpointV2{
Stack: tokens.QName("mystack"),
Config: config.Map{
config.MustMakeKey("foo", "number"): config.NewValue("42"),
},
Latest: &apitype.DeploymentV2{
Manifest: apitype.ManifestV1{},
Resources: []apitype.ResourceV2{},
},
}
v1 := DownToCheckpointV1(v2)
assert.Equal(t, tokens.QName("mystack"), v1.Stack)
assert.Equal(t, config.Map{
config.MustMakeKey("foo", "number"): config.NewValue("42"),
}, v1.Config)
assert.Len(t, v1.Latest.Resources, 0)
}

View file

@ -27,19 +27,3 @@ func UpToDeploymentV2(v1 apitype.DeploymentV1) apitype.DeploymentV2 {
return v2
}
// DownToDeploymentV1 migrates a deployment from DeploymentV2 to DeploymentV1.
func DownToDeploymentV1(v2 apitype.DeploymentV2) apitype.DeploymentV1 {
var v1 apitype.DeploymentV1
v1.Manifest = v2.Manifest
for _, res := range v2.Resources {
if res.External {
// External resources were not stored in the deployment prior to V2.
continue
}
v1.Resources = append(v1.Resources, DownToResourceV1(res))
}
return v1
}

View file

@ -41,24 +41,3 @@ func TestDeploymentV1ToV2(t *testing.T) {
assert.Equal(t, resource.URN("a"), v1.Resources[0].URN)
assert.Equal(t, resource.URN("b"), v1.Resources[1].URN)
}
func TestDeploymentV2ToV1(t *testing.T) {
v2 := apitype.DeploymentV2{
Manifest: apitype.ManifestV1{},
Resources: []apitype.ResourceV2{
{
URN: resource.URN("a"),
},
{
URN: resource.URN("b"),
// this resource gets excluded from the V1 snapshot because it's external
External: true,
},
},
}
v1 := DownToDeploymentV1(v2)
assert.Equal(t, v2.Manifest, v1.Manifest)
assert.Len(t, v1.Resources, 1)
assert.Equal(t, resource.URN("a"), v1.Resources[0].URN)
}

View file

@ -13,14 +13,14 @@
// limitations under the License.
// Package migrate is responsible for converting to and from the various API
// type versions that are in use in Pulumi. This package can migrate "up" and
// "down" versions for every versioned API that needs to be migrated between
// versions. Today, there are three versionable entities that can be migrated
// type versions that are in use in Pulumi. This package can migrate "up" for
// every versioned API that needs to be migrated between versions. Today, there
// are three versionable entities that can be migrated
// with this package:
// * Checkpoint, the on-disk format for Fire-and-Forget stack state,
// * Deployment, the wire format for service-managed stacks,
// * Resource, the wire format for resources saved in deployments,
//
// The migrations in this package are designed to preserve semantics between
// versions. It is always safe to migrate an entity from one version to another.
// versions. It is always safe to migrate an entity up from one version to another.
package migrate

View file

@ -16,7 +16,6 @@ package migrate
import (
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/util/contract"
)
// UpToResourceV2 migrates a resource from ResourceV1 to ResourceV2.
@ -42,33 +41,12 @@ func UpToResourceV2(v1 apitype.ResourceV1) apitype.ResourceV2 {
// lifecycle is not owned by Pulumi. Since all V1 resources have their lifecycles
// owned by Pulumi, this is `false` for all V1 resources.
v2.External = false
v2.Dependencies = append(v1.Dependencies, v2.Dependencies...)
v2.InitErrors = append(v1.InitErrors, v2.InitErrors...)
// v2.Provider is a reference to a first-class provider associated with this resource.
v2.Provider = ""
// v2.Status is the "dirtiness" of this resource - if the engine knows that the last
// operation against it succeeded or not.
v2.Status = ""
v2.Dependencies = append(v2.Dependencies, v1.Dependencies...)
v2.InitErrors = append(v2.InitErrors, v1.InitErrors...)
return v2
}
// DownToResourceV1 migrates a resource from ResourceV2 to ResourceV1.
func DownToResourceV1(v2 apitype.ResourceV2) apitype.ResourceV1 {
contract.Assertf(!v2.External, "Can't convert a V2 External resource to V1")
var v1 apitype.ResourceV1
v1.URN = v2.URN
v1.Custom = v2.Custom
v1.Delete = v2.Delete
v1.ID = v2.ID
v1.Type = v2.Type
v1.Inputs = make(map[string]interface{})
for key, value := range v2.Inputs {
v1.Inputs[key] = value
}
// Defaults was deprecated in v2.
v1.Defaults = make(map[string]interface{})
v1.Outputs = make(map[string]interface{})
for key, value := range v2.Outputs {
v1.Outputs[key] = value
}
v1.Parent = v2.Parent
v1.Protect = v2.Protect
v1.Dependencies = append(v1.Dependencies, v2.Dependencies...)
v1.InitErrors = append(v1.InitErrors, v2.InitErrors...)
return v1
}

View file

@ -66,74 +66,6 @@ func TestV1ToV2(t *testing.T) {
resource.URN("dep1"),
resource.URN("dep2"),
}, v2.Dependencies)
}
func TestV2ToV1(t *testing.T) {
v2 := apitype.ResourceV2{
URN: resource.URN("foo"),
Custom: true,
Delete: true,
ID: resource.ID("bar"),
Type: tokens.Type("special"),
Inputs: map[string]interface{}{
"foo_in": "baz",
},
Outputs: map[string]interface{}{
"foo_out": "out",
},
Parent: resource.URN("parent"),
Protect: true,
External: false,
Dependencies: []resource.URN{
resource.URN("dep1"),
resource.URN("dep2"),
},
}
v1 := DownToResourceV1(v2)
assert.Equal(t, resource.URN("foo"), v1.URN)
assert.True(t, v1.Custom)
assert.True(t, v1.Delete)
assert.Equal(t, resource.ID("bar"), v1.ID)
assert.Equal(t, tokens.Type("special"), v1.Type)
assert.Equal(t, map[string]interface{}{
"foo_in": "baz",
}, v1.Inputs)
assert.Equal(t, map[string]interface{}{}, v1.Defaults)
assert.Equal(t, map[string]interface{}{
"foo_out": "out",
}, v1.Outputs)
assert.Equal(t, resource.URN("parent"), v1.Parent)
assert.True(t, v1.Protect)
assert.Equal(t, []resource.URN{
resource.URN("dep1"),
resource.URN("dep2"),
}, v2.Dependencies)
}
func TestInvalidV2ToV1(t *testing.T) {
v2 := apitype.ResourceV2{
URN: resource.URN("foo"),
Custom: true,
Delete: true,
ID: resource.ID("bar"),
Type: tokens.Type("special"),
Inputs: map[string]interface{}{
"foo_in": "baz",
},
Outputs: map[string]interface{}{
"foo_out": "out",
},
Parent: resource.URN("parent"),
Protect: true,
External: true,
Dependencies: []resource.URN{
resource.URN("dep1"),
resource.URN("dep2"),
},
}
assert.Panics(t, func() {
DownToResourceV1(v2)
})
assert.Empty(t, v2.Provider)
assert.Empty(t, v2.Status)
}

View file

@ -27,7 +27,6 @@ import (
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/apitype/migrate"
"github.com/pulumi/pulumi/pkg/backend"
"github.com/pulumi/pulumi/pkg/diag/colors"
"github.com/pulumi/pulumi/pkg/engine"
@ -35,7 +34,6 @@ import (
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/util/logging"
"github.com/pulumi/pulumi/pkg/workspace"
)
@ -499,13 +497,10 @@ func (pc *Client) InvalidateUpdateCheckpoint(ctx context.Context, update UpdateI
}
// PatchUpdateCheckpoint patches the checkpoint for the indicated update with the given contents.
func (pc *Client) PatchUpdateCheckpoint(ctx context.Context, update UpdateIdentifier, deployment *apitype.DeploymentV2,
func (pc *Client) PatchUpdateCheckpoint(ctx context.Context, update UpdateIdentifier, deployment *apitype.DeploymentV1,
token string) error {
// TODO(pulumi/pulumi#1521): until the service can understand V2 checkpoints, downgrade to V1 before sending it.
logging.V(7).Infof("PatchUpdateCheckpoint: downgrading V2 checkpoint to V1 to speak to service")
v1deployment := migrate.DownToDeploymentV1(*deployment)
rawDeployment, err := json.Marshal(v1deployment)
rawDeployment, err := json.Marshal(deployment)
if err != nil {
return err
}

View file

@ -136,7 +136,7 @@ func (b *localBackend) getStack(name tokens.QName) (config.Map, *deploy.Snapshot
}
// GetCheckpoint loads a checkpoint file for the given stack in this project, from the current project workspace.
func (b *localBackend) getCheckpoint(stackName tokens.QName) (*apitype.CheckpointV2, error) {
func (b *localBackend) getCheckpoint(stackName tokens.QName) (*apitype.CheckpointV1, error) {
chkpath := b.stackPath(stackName)
bytes, err := ioutil.ReadFile(chkpath)
if err != nil {

View file

@ -27,7 +27,7 @@ import (
)
func getPulumiResources(t *testing.T, path string) *Resource {
var checkpoint apitype.CheckpointV2
var checkpoint apitype.CheckpointV1
byts, err := ioutil.ReadFile(path)
assert.NoError(t, err)
err = json.Unmarshal(byts, &checkpoint)

View file

@ -23,7 +23,6 @@ import (
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/apitype/migrate"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/resource/deploy"
@ -32,7 +31,7 @@ import (
"github.com/pulumi/pulumi/pkg/workspace"
)
func UnmarshalVersionedCheckpointToLatestCheckpoint(bytes []byte) (*apitype.CheckpointV2, error) {
func UnmarshalVersionedCheckpointToLatestCheckpoint(bytes []byte) (*apitype.CheckpointV1, error) {
var versionedCheckpoint apitype.VersionedCheckpoint
if err := json.Unmarshal(bytes, &versionedCheckpoint); err != nil {
return nil, err
@ -49,22 +48,13 @@ func UnmarshalVersionedCheckpointToLatestCheckpoint(bytes []byte) (*apitype.Chec
return nil, err
}
v2checkpoint := migrate.UpToCheckpointV2(checkpoint)
return &v2checkpoint, nil
return &checkpoint, nil
case 1:
var checkpoint apitype.CheckpointV1
if err := json.Unmarshal(versionedCheckpoint.Checkpoint, &checkpoint); err != nil {
return nil, err
}
v2checkpoint := migrate.UpToCheckpointV2(checkpoint)
return &v2checkpoint, nil
case 2:
var checkpoint apitype.CheckpointV2
if err := json.Unmarshal(versionedCheckpoint.Checkpoint, &checkpoint); err != nil {
return nil, err
}
return &checkpoint, nil
default:
return nil, errors.Errorf("unsupported checkpoint version %d", versionedCheckpoint.Version)
@ -74,12 +64,12 @@ func UnmarshalVersionedCheckpointToLatestCheckpoint(bytes []byte) (*apitype.Chec
// SerializeCheckpoint turns a snapshot into a data structure suitable for serialization.
func SerializeCheckpoint(stack tokens.QName, config config.Map, snap *deploy.Snapshot) *apitype.VersionedCheckpoint {
// If snap is nil, that's okay, we will just create an empty deployment; otherwise, serialize the whole snapshot.
var latest *apitype.DeploymentV2
var latest *apitype.DeploymentV1
if snap != nil {
latest = SerializeDeployment(snap)
}
b, err := json.Marshal(apitype.CheckpointV2{
b, err := json.Marshal(apitype.CheckpointV1{
Stack: stack,
Config: config,
Latest: latest,
@ -93,7 +83,7 @@ func SerializeCheckpoint(stack tokens.QName, config config.Map, snap *deploy.Sna
}
// DeserializeCheckpoint takes a serialized deployment record and returns its associated snapshot.
func DeserializeCheckpoint(chkpoint *apitype.CheckpointV2) (*deploy.Snapshot, error) {
func DeserializeCheckpoint(chkpoint *apitype.CheckpointV1) (*deploy.Snapshot, error) {
contract.Require(chkpoint != nil, "chkpoint")
var snap *deploy.Snapshot

View file

@ -20,11 +20,9 @@ import (
"reflect"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/apitype/migrate"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/util/logging"
)
const (
@ -46,7 +44,7 @@ var (
)
// SerializeDeployment serializes an entire snapshot as a deploy record.
func SerializeDeployment(snap *deploy.Snapshot) *apitype.DeploymentV2 {
func SerializeDeployment(snap *deploy.Snapshot) *apitype.DeploymentV1 {
contract.Require(snap != nil, "snap")
// Capture the version information into a manifest.
@ -69,12 +67,12 @@ func SerializeDeployment(snap *deploy.Snapshot) *apitype.DeploymentV2 {
}
// Serialize all vertices and only include a vertex section if non-empty.
var resources []apitype.ResourceV2
var resources []apitype.ResourceV1
for _, res := range snap.Resources {
resources = append(resources, SerializeResource(res))
}
return &apitype.DeploymentV2{
return &apitype.DeploymentV1{
Manifest: manifest,
Resources: resources,
}
@ -92,30 +90,19 @@ func DeserializeDeployment(deployment *apitype.UntypedDeployment) (*deploy.Snaps
return nil, ErrDeploymentSchemaVersionTooOld
}
var checkpoint apitype.CheckpointV2
var checkpoint apitype.CheckpointV1
switch deployment.Version {
case 1:
v1checkpoint := apitype.CheckpointV1{}
if err := json.Unmarshal([]byte(deployment.Deployment), &v1checkpoint.Latest); err != nil {
if err := json.Unmarshal([]byte(deployment.Deployment), &checkpoint.Latest); err != nil {
return nil, err
}
logging.V(7).Infof("DeserializeDeployment: migrating V1 checkpoint to V2")
checkpoint = migrate.UpToCheckpointV2(v1checkpoint)
case 2:
v2checkpoint := apitype.CheckpointV2{}
if err := json.Unmarshal([]byte(deployment.Deployment), &v2checkpoint.Latest); err != nil {
return nil, err
}
checkpoint = v2checkpoint
}
return DeserializeCheckpoint(&checkpoint)
}
// SerializeResource turns a resource into a structure suitable for serialization.
func SerializeResource(res *resource.State) apitype.ResourceV2 {
func SerializeResource(res *resource.State) apitype.ResourceV1 {
contract.Assert(res != nil)
contract.Assertf(string(res.URN) != "", "Unexpected empty resource resource.URN")
@ -129,7 +116,7 @@ func SerializeResource(res *resource.State) apitype.ResourceV2 {
outputs = SerializeProperties(outp)
}
return apitype.ResourceV2{
return apitype.ResourceV1{
URN: res.URN,
Custom: res.Custom,
Delete: res.Delete,
@ -190,7 +177,7 @@ func SerializePropertyValue(prop resource.PropertyValue) interface{} {
}
// DeserializeResource turns a serialized resource back into its usual form.
func DeserializeResource(res apitype.ResourceV2) (*resource.State, error) {
func DeserializeResource(res apitype.ResourceV1) (*resource.State, error) {
// Deserialize the resource properties, if they exist.
inputs, err := DeserializeProperties(res.Inputs)
if err != nil {