2017-08-30 03:25:50 +02:00
|
|
|
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
2017-08-31 19:28:20 +02:00
|
|
|
"github.com/pkg/errors"
|
2017-08-30 03:25:50 +02:00
|
|
|
"github.com/pulumi/pulumi-fabric/pkg/encoding"
|
|
|
|
"github.com/pulumi/pulumi-fabric/pkg/resource/deploy"
|
|
|
|
"github.com/pulumi/pulumi-fabric/pkg/resource/environment"
|
|
|
|
"github.com/pulumi/pulumi-fabric/pkg/tokens"
|
|
|
|
"github.com/pulumi/pulumi-fabric/pkg/util/contract"
|
|
|
|
"github.com/pulumi/pulumi-fabric/pkg/util/mapper"
|
|
|
|
"github.com/pulumi/pulumi-fabric/pkg/workspace"
|
|
|
|
)
|
|
|
|
|
2017-08-31 19:27:41 +02:00
|
|
|
type localEnvProvider struct{}
|
2017-08-30 03:25:50 +02:00
|
|
|
|
2017-08-31 19:27:41 +02:00
|
|
|
func (p localEnvProvider) GetEnvironment(name tokens.QName) (*deploy.Target,
|
2017-08-30 03:25:50 +02:00
|
|
|
*deploy.Snapshot, *environment.Checkpoint, 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 {
|
2017-08-31 19:28:20 +02:00
|
|
|
return nil, nil, nil, errors.Errorf("resource deserialization failed; illegal markup extension: '%v'", ext)
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now read the whole file into a byte blob.
|
|
|
|
b, err := ioutil.ReadFile(file)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
2017-08-31 19:28:20 +02:00
|
|
|
return nil, nil, nil, errors.Errorf("Environment '%v' could not be found in the current workspace", name)
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
2017-08-31 19:28:20 +02:00
|
|
|
return nil, nil, nil, errors.Wrapf(err, "An IO error occurred during the current operation")
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal the contents into a checkpoint structure.
|
|
|
|
var checkpoint environment.Checkpoint
|
|
|
|
if err = m.Unmarshal(b, &checkpoint); err != nil {
|
2017-08-31 19:28:20 +02:00
|
|
|
return nil, nil, nil, errors.Wrapf(err, "Could not read deployment file '%v'", file)
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Next, use the mapping infrastructure to validate the contents.
|
|
|
|
// IDEA: we can eliminate this redundant unmarshaling once Go supports strict unmarshaling.
|
|
|
|
var obj map[string]interface{}
|
|
|
|
if err = m.Unmarshal(b, &obj); err != nil {
|
2017-08-31 19:28:20 +02:00
|
|
|
return nil, nil, nil, errors.Wrapf(err, "Could not read deployment file '%v'", file)
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if obj["latest"] != nil {
|
|
|
|
if latest, islatest := obj["latest"].(map[string]interface{}); islatest {
|
|
|
|
delete(latest, "resources") // remove the resources, since they require custom marshaling.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
md := mapper.New(nil)
|
|
|
|
var ignore environment.Checkpoint // just for errors.
|
|
|
|
if err = md.Decode(obj, &ignore); err != nil {
|
2017-08-31 19:28:20 +02:00
|
|
|
return nil, nil, nil, errors.Wrapf(err, "Could not read deployment file '%v'", file)
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
target, snapshot := environment.DeserializeCheckpoint(&checkpoint)
|
|
|
|
contract.Assert(target != nil)
|
|
|
|
return target, snapshot, &checkpoint, nil
|
|
|
|
}
|
|
|
|
|
2017-08-31 19:27:41 +02:00
|
|
|
func (p localEnvProvider) SaveEnvironment(env *deploy.Target, snap *deploy.Snapshot) error {
|
2017-08-30 03:25:50 +02:00
|
|
|
contract.Require(env != nil, "env")
|
|
|
|
file := workspace.EnvPath(env.Name)
|
|
|
|
|
|
|
|
// Make a serializable LumiGL data structure and then use the encoder to encode it.
|
|
|
|
m, ext := encoding.Detect(file)
|
|
|
|
if m == nil {
|
2017-08-31 19:28:20 +02:00
|
|
|
return errors.Errorf("resource serialization failed; illegal markup extension: '%v'", ext)
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
if filepath.Ext(file) == "" {
|
|
|
|
file = file + ext
|
|
|
|
}
|
|
|
|
dep := environment.SerializeCheckpoint(env, snap)
|
|
|
|
b, err := m.Marshal(dep)
|
|
|
|
if err != nil {
|
2017-08-31 19:28:20 +02:00
|
|
|
return errors.Wrap(err, "An IO error occurred during the current operation")
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2017-08-31 19:27:41 +02:00
|
|
|
return errors.Wrap(err, "An IO error occurred during the current operation")
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// And now write out the new snapshot file, overwriting that location.
|
|
|
|
if err = ioutil.WriteFile(file, b, 0600); err != nil {
|
2017-08-31 19:27:41 +02:00
|
|
|
return errors.Wrap(err, "An IO error occurred during the current operation")
|
2017-08-30 03:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-08-31 19:27:41 +02:00
|
|
|
func (p localEnvProvider) RemoveEnvironment(env *deploy.Target) error {
|
2017-08-30 03:25:50 +02:00
|
|
|
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).
|
|
|
|
}
|