pulumi/pkg/backend/cloud/stack.go
Pat Gavlin 58300f9ee7
Add support for service-managed stacks. (#1067)
These changes add the API types and cloud backend code necessary to
interact with service-managed stacks (i.e. stacks that do not have
PPC-managed deployments). The bulk of these changes are unremarkable:
the API types are straightforward, as are most of the interactions with
the new APIs. The trickiest bits are token and log management.

During an update to a managed stack, the CLI must continually renew the
token used to authorize the operations on that stack that comprise the
update. Once a token has been renewed, the old token should be
discarded. The CLI supports this by running a goroutine that is
responsible for both periodically renewing the token for an update and
servicing requests for the token itself from the rest of the backend.

In addition to token renewal, log output must be captured and uploaded
to the service during an update to a managed stack. Implementing this in
a reasonable fashion required a bit of refactoring in order to reuse
what already exists for the local backend. Each event-specific `Display`
function was replaced with an equivalent `Render` function that returns
a string rather than writing to a stream. This approach was chosen
primarily to avoid dealing with sheared colorization tags, which would
otherwise require clients to fuse log lines before colorizing. We could
take that approach in the future.
2018-03-22 10:42:43 -07:00

114 lines
4.4 KiB
Go

// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
package cloud
import (
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/backend"
"github.com/pulumi/pulumi/pkg/engine"
"github.com/pulumi/pulumi/pkg/operations"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/resource/deploy"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/workspace"
)
// Stack is a cloud stack. This simply adds some cloud-specific properties atop the standard backend stack interface.
type Stack interface {
backend.Stack
CloudURL() string // the URL to the cloud containing this stack.
OrgName() string // the organization that owns this stack.
CloudName() string // the PPC in which this stack is running.
RunLocally() bool // true if previews/updates/destroys targeting this stack run locally.
}
// cloudStack is a cloud stack descriptor.
type cloudStack struct {
name tokens.QName // the stack's name.
cloudURL string // the URL to the cloud containing this stack.
orgName string // the organization that owns this stack.
cloudName string // the PPC in which this stack is running.
config config.Map // the stack's config bag.
snapshot *deploy.Snapshot // a snapshot representing the latest deployment state.
b *cloudBackend // a pointer to the backend this stack belongs to.
}
func newStack(apistack apitype.Stack, b *cloudBackend) Stack {
// Create a fake snapshot out of this stack.
// TODO[pulumi/pulumi-service#249]: add time, version, etc. info to the manifest.
stackName := apistack.StackName
var resources []*resource.State
for _, res := range apistack.Resources {
resources = append(resources, resource.NewState(
res.Type,
res.URN,
res.Custom,
false,
res.ID,
resource.NewPropertyMapFromMap(res.Inputs),
resource.NewPropertyMapFromMap(res.Outputs),
res.Parent,
res.Protect,
// TODO(swgillespie) provide an actual list of dependencies
[]resource.URN{},
))
}
snapshot := deploy.NewSnapshot(stackName, deploy.Manifest{}, resources)
// Now assemble all the pieces into a stack structure.
return &cloudStack{
name: stackName,
cloudURL: b.CloudURL(),
orgName: apistack.OrgName,
cloudName: apistack.CloudName,
config: nil, // TODO[pulumi/pulumi-service#249]: add the config variables.
snapshot: snapshot,
b: b,
}
}
// managedCloudName is the name used to refer to the cloud in the Pulumi Service that owns all of an organization's
// managed stacks. All engine operations for a managed stack--previews, updates, destroys, etc.--run locally.
const managedCloudName = "pulumi"
func (s *cloudStack) Name() tokens.QName { return s.name }
func (s *cloudStack) Config() config.Map { return s.config }
func (s *cloudStack) Snapshot() *deploy.Snapshot { return s.snapshot }
func (s *cloudStack) Backend() backend.Backend { return s.b }
func (s *cloudStack) CloudURL() string { return s.cloudURL }
func (s *cloudStack) OrgName() string { return s.orgName }
func (s *cloudStack) CloudName() string { return s.cloudName }
func (s *cloudStack) RunLocally() bool { return s.cloudName == managedCloudName }
func (s *cloudStack) Remove(force bool) (bool, error) {
return backend.RemoveStack(s, force)
}
func (s *cloudStack) Preview(proj *workspace.Project, root string,
debug bool, opts engine.UpdateOptions, displayOpts backend.DisplayOptions) error {
return backend.PreviewStack(s, proj, root, debug, opts, displayOpts)
}
func (s *cloudStack) Update(proj *workspace.Project, root string,
debug bool, m backend.UpdateMetadata, opts engine.UpdateOptions, displayOpts backend.DisplayOptions) error {
return backend.UpdateStack(s, proj, root, debug, m, opts, displayOpts)
}
func (s *cloudStack) Destroy(proj *workspace.Project, root string,
debug bool, m backend.UpdateMetadata, opts engine.UpdateOptions, displayOpts backend.DisplayOptions) error {
return backend.DestroyStack(s, proj, root, debug, m, opts, displayOpts)
}
func (s *cloudStack) GetLogs(query operations.LogQuery) ([]operations.LogEntry, error) {
return backend.GetStackLogs(s, query)
}
func (s *cloudStack) ExportDeployment() (*apitype.UntypedDeployment, error) {
return backend.ExportStackDeployment(s)
}
func (s *cloudStack) ImportDeployment(deployment *apitype.UntypedDeployment) error {
return backend.ImportStackDeployment(s, deployment)
}