Our checkpoint file is now in a shape such that go's built in marshalling and unmarshalling is sufficent, so we can remove the code we had on our decode path to do extra validation
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/pulumi/pulumi/pkg/encoding"
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
|
"github.com/pulumi/pulumi/pkg/resource/environment"
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
|
"github.com/pulumi/pulumi/pkg/workspace"
|
|
)
|
|
|
|
type localEnvProvider struct{}
|
|
|
|
func (p localEnvProvider) GetTarget(name tokens.QName) (*deploy.Target, error) {
|
|
contract.Require(name != "", "name")
|
|
|
|
target, _, err := getEnvironment(name)
|
|
|
|
return target, err
|
|
}
|
|
|
|
func (p localEnvProvider) GetSnapshot(name tokens.QName) (*deploy.Snapshot, error) {
|
|
contract.Require(name != "", "name")
|
|
|
|
_, snapshot, err := getEnvironment(name)
|
|
|
|
return snapshot, err
|
|
}
|
|
|
|
func (p localEnvProvider) SaveSnapshot(snapshot *deploy.Snapshot) error {
|
|
target, _, err := getEnvironment(snapshot.Namespace)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
|
|
if target == nil {
|
|
target = &deploy.Target{Name: snapshot.Namespace}
|
|
}
|
|
|
|
return saveEnvironment(target, snapshot)
|
|
}
|
|
|
|
func getEnvironment(name tokens.QName) (*deploy.Target, *deploy.Snapshot, error) {
|
|
contract.Require(name != "", "name")
|
|
file := workspace.EnvPath(name)
|
|
|
|
// Detect the encoding of the file so we can do our initial unmarshaling.
|
|
m, ext := encoding.Detect(file)
|
|
if m == nil {
|
|
return nil, nil, errors.Errorf("resource deserialization failed; illegal markup extension: '%v'", ext)
|
|
}
|
|
|
|
// Now read the whole file into a byte blob.
|
|
b, err := ioutil.ReadFile(file)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil, err
|
|
}
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Unmarshal the contents into a checkpoint structure.
|
|
var checkpoint environment.Checkpoint
|
|
if err = m.Unmarshal(b, &checkpoint); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
target, snapshot := environment.DeserializeCheckpoint(&checkpoint)
|
|
contract.Assert(target != nil)
|
|
return target, snapshot, nil
|
|
}
|
|
|
|
func saveEnvironment(env *deploy.Target, snap *deploy.Snapshot) error {
|
|
contract.Require(env != nil, "env")
|
|
file := workspace.EnvPath(env.Name)
|
|
|
|
// Make a serializable environment and then use the encoder to encode it.
|
|
m, ext := encoding.Detect(file)
|
|
if m == nil {
|
|
return errors.Errorf("resource serialization failed; illegal markup extension: '%v'", ext)
|
|
}
|
|
if filepath.Ext(file) == "" {
|
|
file = file + ext
|
|
}
|
|
dep := environment.SerializeCheckpoint(env, snap)
|
|
b, err := m.Marshal(dep)
|
|
if err != nil {
|
|
return errors.Wrap(err, "An IO error occurred during the current operation")
|
|
}
|
|
|
|
// Back up the existing file if it already exists.
|
|
backupTarget(file)
|
|
|
|
// Ensure the directory exists.
|
|
if err = os.MkdirAll(filepath.Dir(file), 0700); err != nil {
|
|
return errors.Wrap(err, "An IO error occurred during the current operation")
|
|
}
|
|
|
|
// And now write out the new snapshot file, overwriting that location.
|
|
if err = ioutil.WriteFile(file, b, 0600); err != nil {
|
|
return errors.Wrap(err, "An IO error occurred during the current operation")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func removeEnvironment(env *deploy.Target) error {
|
|
contract.Require(env != nil, "env")
|
|
// Just make a backup of the file and don't write out anything new.
|
|
file := workspace.EnvPath(env.Name)
|
|
backupTarget(file)
|
|
return nil
|
|
}
|
|
|
|
// backupTarget makes a backup of an existing file, in preparation for writing a new one. Instead of a copy, it
|
|
// simply renames the file, which is simpler, more efficient, etc.
|
|
func backupTarget(file string) {
|
|
contract.Require(file != "", "file")
|
|
err := os.Rename(file, file+".bak")
|
|
contract.IgnoreError(err) // ignore errors.
|
|
// IDEA: consider multiple backups (.bak.bak.bak...etc).
|
|
}
|