2017-11-01 21:34:19 +01:00
|
|
|
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
2017-11-01 22:55:16 +01:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2017-11-01 21:34:19 +01:00
|
|
|
"strconv"
|
|
|
|
|
2017-11-01 22:55:16 +01:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
2017-11-20 07:28:49 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/encoding"
|
2017-11-01 21:34:19 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/engine"
|
2017-11-20 07:28:49 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/operations"
|
2017-11-01 21:34:19 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/resource/config"
|
|
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
2017-11-20 07:28:49 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
2017-11-01 21:34:19 +01:00
|
|
|
)
|
|
|
|
|
2017-11-09 21:38:03 +01:00
|
|
|
type localPulumiBackend struct {
|
|
|
|
engineCache map[tokens.QName]engine.Engine
|
|
|
|
}
|
2017-11-01 21:34:19 +01:00
|
|
|
|
2017-11-02 18:40:42 +01:00
|
|
|
func (b *localPulumiBackend) CreateStack(stackName tokens.QName, opts StackCreationOptions) error {
|
|
|
|
contract.Requiref(opts.Cloud == "", "cloud", "local backend does not support clouds, cloud must be empty")
|
2017-11-01 22:55:16 +01:00
|
|
|
|
2017-11-21 02:38:09 +01:00
|
|
|
if _, _, _, _, err := getStack(stackName); err == nil {
|
2017-11-01 22:55:16 +01:00
|
|
|
return errors.Errorf("stack '%v' already exists", stackName)
|
2017-11-01 21:34:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return saveStack(stackName, nil, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *localPulumiBackend) GetStacks() ([]stackSummary, error) {
|
2017-11-01 22:55:16 +01:00
|
|
|
stacks, err := getLocalStacks()
|
2017-11-01 21:34:19 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var summaries []stackSummary
|
|
|
|
for _, stack := range stacks {
|
|
|
|
summary := stackSummary{
|
|
|
|
Name: stack,
|
|
|
|
LastDeploy: "n/a",
|
|
|
|
ResourceCount: "n/a",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ignore errors, just leave display settings as "n/a".
|
2017-11-21 02:38:09 +01:00
|
|
|
_, _, snapshot, _, err := getStack(stack)
|
2017-11-01 21:34:19 +01:00
|
|
|
if err == nil && snapshot != nil {
|
2017-12-01 22:50:32 +01:00
|
|
|
summary.LastDeploy = snapshot.Manifest.Time.String()
|
2017-11-01 21:34:19 +01:00
|
|
|
summary.ResourceCount = strconv.Itoa(len(snapshot.Resources))
|
|
|
|
}
|
|
|
|
|
|
|
|
summaries = append(summaries, summary)
|
|
|
|
}
|
|
|
|
|
|
|
|
return summaries, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *localPulumiBackend) RemoveStack(stackName tokens.QName, force bool) error {
|
2017-11-21 02:38:09 +01:00
|
|
|
name, _, snapshot, _, err := getStack(stackName)
|
2017-11-01 21:34:19 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't remove stacks that still have resources.
|
|
|
|
if !force && snapshot != nil && len(snapshot.Resources) > 0 {
|
|
|
|
return errHasResources
|
|
|
|
}
|
|
|
|
|
|
|
|
return removeStack(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *localPulumiBackend) Preview(stackName tokens.QName, debug bool, opts engine.PreviewOptions) error {
|
2017-11-09 21:38:03 +01:00
|
|
|
pulumiEngine, err := b.getEngine(stackName)
|
2017-11-01 21:34:19 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
events := make(chan engine.Event)
|
|
|
|
done := make(chan bool)
|
|
|
|
|
|
|
|
go displayEvents(events, done, debug)
|
|
|
|
|
|
|
|
if err = pulumiEngine.Preview(stackName, events, opts); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
<-done
|
|
|
|
close(events)
|
|
|
|
close(done)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *localPulumiBackend) Update(stackName tokens.QName, debug bool, opts engine.DeployOptions) error {
|
2017-11-09 21:38:03 +01:00
|
|
|
pulumiEngine, err := b.getEngine(stackName)
|
2017-11-01 21:34:19 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
events := make(chan engine.Event)
|
|
|
|
done := make(chan bool)
|
|
|
|
|
|
|
|
go displayEvents(events, done, debug)
|
|
|
|
|
|
|
|
if err = pulumiEngine.Deploy(stackName, events, opts); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
<-done
|
|
|
|
close(events)
|
|
|
|
close(done)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *localPulumiBackend) Destroy(stackName tokens.QName, debug bool, opts engine.DestroyOptions) error {
|
2017-11-09 21:38:03 +01:00
|
|
|
pulumiEngine, err := b.getEngine(stackName)
|
2017-11-01 21:34:19 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
events := make(chan engine.Event)
|
|
|
|
done := make(chan bool)
|
|
|
|
|
|
|
|
go displayEvents(events, done, debug)
|
|
|
|
|
|
|
|
if err := pulumiEngine.Destroy(stackName, events, opts); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
<-done
|
|
|
|
close(events)
|
|
|
|
close(done)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2017-11-01 22:55:16 +01:00
|
|
|
|
2017-11-20 07:28:49 +01:00
|
|
|
func (b *localPulumiBackend) GetLogs(stackName tokens.QName, query operations.LogQuery) ([]operations.LogEntry, error) {
|
2017-11-09 21:38:03 +01:00
|
|
|
pulumiEngine, err := b.getEngine(stackName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
snap, err := pulumiEngine.Snapshots.GetSnapshot(stackName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
target, err := pulumiEngine.Targets.GetTarget(stackName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
contract.Assert(snap != nil)
|
|
|
|
contract.Assert(target != nil)
|
|
|
|
|
2017-11-28 17:13:40 +01:00
|
|
|
components := operations.NewResourceTree(snap.Resources)
|
2017-11-20 07:28:49 +01:00
|
|
|
ops := components.OperationsProvider(target.Config)
|
|
|
|
logs, err := ops.GetLogs(query)
|
|
|
|
if logs == nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return *logs, err
|
2017-11-09 21:38:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *localPulumiBackend) getEngine(stackName tokens.QName) (engine.Engine, error) {
|
|
|
|
if b.engineCache == nil {
|
|
|
|
b.engineCache = make(map[tokens.QName]engine.Engine)
|
|
|
|
}
|
|
|
|
|
|
|
|
if engine, has := b.engineCache[stackName]; has {
|
|
|
|
return engine, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg, err := getConfiguration(stackName)
|
|
|
|
if err != nil {
|
|
|
|
return engine.Engine{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var decrypter config.ValueDecrypter = panicCrypter{}
|
|
|
|
|
|
|
|
if hasSecureValue(cfg) {
|
|
|
|
decrypter, err = getSymmetricCrypter()
|
|
|
|
if err != nil {
|
|
|
|
return engine.Engine{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
localProvider := localStackProvider{decrypter: decrypter}
|
|
|
|
pulumiEngine := engine.Engine{Targets: localProvider, Snapshots: localProvider}
|
|
|
|
b.engineCache[stackName] = pulumiEngine
|
|
|
|
return pulumiEngine, nil
|
|
|
|
}
|
|
|
|
|
2017-11-01 22:55:16 +01:00
|
|
|
func getLocalStacks() ([]tokens.QName, error) {
|
|
|
|
var stacks []tokens.QName
|
|
|
|
|
|
|
|
w, err := newWorkspace()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the stack directory.
|
|
|
|
path := w.StackPath("")
|
|
|
|
|
|
|
|
files, err := ioutil.ReadDir(path)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
return nil, errors.Errorf("could not read stacks: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, file := range files {
|
|
|
|
// Ignore directories.
|
|
|
|
if file.IsDir() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip files without valid extensions (e.g., *.bak files).
|
|
|
|
stackfn := file.Name()
|
|
|
|
ext := filepath.Ext(stackfn)
|
|
|
|
if _, has := encoding.Marshalers[ext]; !has {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read in this stack's information.
|
|
|
|
name := tokens.QName(stackfn[:len(stackfn)-len(ext)])
|
2017-11-21 02:38:09 +01:00
|
|
|
_, _, _, _, err := getStack(name)
|
2017-11-01 22:55:16 +01:00
|
|
|
if err != nil {
|
|
|
|
continue // failure reading the stack information.
|
|
|
|
}
|
|
|
|
|
|
|
|
stacks = append(stacks, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
return stacks, nil
|
|
|
|
}
|