pulumi/cmd/backend_local.go
Matt Ellis f5dc3a2b53 Add a very barebones pulumi logs command
This is the smallest possible thing that could work for both the local
development case and the case where we are targeting the Pulumi
Service.

Pulling down the pulumiframework and component packages here is a bit
of a feel bad, but we plan to rework the model soon enough to a
provider model which will remove the need for us to hold on to this
code (and will bring back the factoring where the CLI does not have
baked in knowledge of the Pulumi Cloud Framework).

Fixes #527
2017-11-14 12:26:55 -08:00

240 lines
5.4 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package cmd
import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/component"
"github.com/pulumi/pulumi/pkg/encoding"
"github.com/pulumi/pulumi/pkg/pulumiframework"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/engine"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/tokens"
)
type localPulumiBackend struct {
engineCache map[tokens.QName]engine.Engine
}
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")
if _, _, _, err := getStack(stackName); err == nil {
return errors.Errorf("stack '%v' already exists", stackName)
}
return saveStack(stackName, nil, nil)
}
func (b *localPulumiBackend) GetStacks() ([]stackSummary, error) {
stacks, err := getLocalStacks()
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".
_, _, snapshot, err := getStack(stack)
if err == nil && snapshot != nil {
summary.LastDeploy = snapshot.Time.String()
summary.ResourceCount = strconv.Itoa(len(snapshot.Resources))
}
summaries = append(summaries, summary)
}
return summaries, nil
}
func (b *localPulumiBackend) RemoveStack(stackName tokens.QName, force bool) error {
name, _, snapshot, err := getStack(stackName)
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 {
pulumiEngine, err := b.getEngine(stackName)
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 {
pulumiEngine, err := b.getEngine(stackName)
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 {
pulumiEngine, err := b.getEngine(stackName)
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
}
func (b *localPulumiBackend) GetLogs(stackName tokens.QName, query component.LogQuery) ([]component.LogEntry, error) {
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)
// TODO[pulumi/pulumi#54]: replace this with a call into a generalized operations provider.
components := pulumiframework.GetComponents(snap.Resources)
ops, err := pulumiframework.OperationsProviderForComponents(target.Config, components)
if err != nil {
return nil, err
}
return ops.GetLogs(query)
}
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
}
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)])
_, _, _, err := getStack(name)
if err != nil {
continue // failure reading the stack information.
}
stacks = append(stacks, name)
}
return stacks, nil
}