mirror of https://github.com/pulumi/pulumi.git
108 lines
4.6 KiB
Go
108 lines
4.6 KiB
Go
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
package deploy
|
|
|
|
import (
|
|
"os"
|
|
|
|
"github.com/pulumi/pulumi/pkg/diag"
|
|
"github.com/pulumi/pulumi/pkg/resource"
|
|
"github.com/pulumi/pulumi/pkg/resource/plugin"
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
|
)
|
|
|
|
// TODO[pulumi/pulumi#106]: plan parallelism.
|
|
|
|
// Plan is the output of analyzing resource graphs and contains the steps necessary to perform an infrastructure
|
|
// deployment. A plan can be generated out of whole cloth from a resource graph -- in the case of new deployments --
|
|
// however, it can alternatively be generated by diffing two resource graphs -- in the case of updates to existing
|
|
// stacks (presumably more common). The plan contains step objects that can be used to drive a deployment.
|
|
type Plan struct {
|
|
ctx *plugin.Context // the plugin context (for provider operations).
|
|
target *Target // the deployment target.
|
|
prev *Snapshot // the old resource snapshot for comparison.
|
|
olds map[resource.URN]*resource.State // a map of all old resources.
|
|
source Source // the source of new resources.
|
|
analyzers []tokens.QName // the analyzers to run during this plan's generation.
|
|
preview bool // true if this plan is to be previewed rather than applied.
|
|
}
|
|
|
|
// NewPlan creates a new deployment plan from a resource snapshot plus a package to evaluate.
|
|
//
|
|
// From the old and new states, it understands how to orchestrate an evaluation and analyze the resulting resources.
|
|
// The plan may be used to simply inspect a series of operations, or actually perform them; these operations are
|
|
// generated based on analysis of the old and new states. If a resource exists in new, but not old, for example, it
|
|
// results in a create; if it exists in both, but is different, it results in an update; and so on and so forth.
|
|
//
|
|
// Note that a plan uses internal concurrency and parallelism in various ways, so it must be closed if for some reason
|
|
// a plan isn't carried out to its final conclusion. This will result in cancelation and reclamation of OS resources.
|
|
func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source, analyzers []tokens.QName,
|
|
preview bool) *Plan {
|
|
|
|
contract.Assert(ctx != nil)
|
|
contract.Assert(target != nil)
|
|
contract.Assert(source != nil)
|
|
|
|
// Produce a map of all old resources for fast resources.
|
|
olds := make(map[resource.URN]*resource.State)
|
|
if prev != nil {
|
|
for _, oldres := range prev.Resources {
|
|
// Ignore resources that are pending deletion; these should not be recorded in the LUT.
|
|
if oldres.Delete {
|
|
continue
|
|
}
|
|
|
|
urn := oldres.URN
|
|
contract.Assert(olds[urn] == nil)
|
|
olds[urn] = oldres
|
|
}
|
|
}
|
|
|
|
return &Plan{
|
|
ctx: ctx,
|
|
target: target,
|
|
prev: prev,
|
|
olds: olds,
|
|
source: source,
|
|
analyzers: analyzers,
|
|
preview: preview,
|
|
}
|
|
}
|
|
|
|
func (p *Plan) Ctx() *plugin.Context { return p.ctx }
|
|
func (p *Plan) Target() *Target { return p.target }
|
|
func (p *Plan) Diag() diag.Sink { return p.ctx.Diag }
|
|
func (p *Plan) Prev() *Snapshot { return p.prev }
|
|
func (p *Plan) Olds() map[resource.URN]*resource.State { return p.olds }
|
|
func (p *Plan) Source() Source { return p.source }
|
|
|
|
// Provider fetches the provider for a given resource type, possibly lazily allocating the plugins for it. If a
|
|
// provider could not be found, or an error occurred while creating it, a non-nil error is returned.
|
|
func (p *Plan) Provider(pkg tokens.Package) (plugin.Provider, error) {
|
|
// TODO: ideally we would flow versions on specific requests along to the underlying host function. Absent that,
|
|
// we will just pass nil, which returns us the most recent version available to us.
|
|
return p.ctx.Host.Provider(pkg, nil)
|
|
}
|
|
|
|
// TryProvider attempts to load a provider for the given package. If it is missing, nil is returned.
|
|
func (p *Plan) TryProvider(pkg tokens.Package) (plugin.Provider, error) {
|
|
// TODO: ideally we would flow versions on specific requests along to the underlying host function. Absent that,
|
|
// we will just pass nil, which returns us the most recent version available to us.
|
|
prov, err := p.ctx.Host.Provider(pkg, nil)
|
|
if err != nil {
|
|
// If a plugin missing error, just return nil.
|
|
if _, ok := err.(*plugin.MissingError); ok {
|
|
return nil, nil
|
|
}
|
|
// If an OS file not found error, also just return nil.
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
// Otherwise propagate the error.
|
|
return nil, err
|
|
}
|
|
|
|
return prov, nil
|
|
}
|