pulumi/pkg/resource/deploy/builtins.go
Pat Gavlin 7ebd70a3e6
Refresh inputs (#2531)
These changes take advantage of the newly-added support for returning
inputs from Read to update a resource's inputs as part of a refresh.
As a consequence, the Pulumi engine will now properly detect drift
between the actual state of a resource and the desired state described
by the program and generate appropriate update or replace steps.

As part of these changes, a resource's old inputs are now passed to the
provider when performing a refresh. The provider can take advantage of
this to maintain the accuracy of any additional data or metadata in the
resource's inputs that may need to be updated during the refresh.

This is required for the complete implementation of
https://github.com/pulumi/pulumi-terraform/pull/349. Without access to
the old inputs for a resource, TF-based providers would lose all
information about default population during a refresh.
2019-03-11 13:50:00 -07:00

180 lines
5 KiB
Go

package deploy
import (
"context"
"fmt"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
"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"
"github.com/pulumi/pulumi/pkg/workspace"
)
type builtinProvider struct {
backendClient BackendClient
context context.Context
cancel context.CancelFunc
}
func newBuiltinProvider(backendClient BackendClient) *builtinProvider {
ctx, cancel := context.WithCancel(context.Background())
return &builtinProvider{
backendClient: backendClient,
context: ctx,
cancel: cancel,
}
}
func (p *builtinProvider) Close() error {
return nil
}
func (p *builtinProvider) Pkg() tokens.Package {
return "pulumi"
}
// CheckConfig validates the configuration for this resource provider.
func (p *builtinProvider) CheckConfig(olds,
news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return nil, nil, nil
}
// DiffConfig checks what impacts a hypothetical change to this provider's configuration will have on the provider.
func (p *builtinProvider) DiffConfig(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
}
func (p *builtinProvider) Configure(props resource.PropertyMap) error {
return nil
}
const stackReferenceType = "pulumi:pulumi:StackReference"
func (p *builtinProvider) Check(urn resource.URN, state, inputs resource.PropertyMap,
allowUnknowns bool) (resource.PropertyMap, []plugin.CheckFailure, error) {
typ := urn.Type()
if typ != stackReferenceType {
return nil, nil, errors.Errorf("unrecognized resource type '%v'", urn.Type())
}
var name resource.PropertyValue
for k := range inputs {
if k != "name" {
return nil, []plugin.CheckFailure{{Property: k, Reason: fmt.Sprintf("unknown property \"%v\"", k)}}, nil
}
}
name, ok := inputs["name"]
if !ok {
return nil, []plugin.CheckFailure{{Property: "name", Reason: `missing required property "name"`}}, nil
}
if !name.IsString() && !name.IsComputed() {
return nil, []plugin.CheckFailure{{Property: "name", Reason: `property "name" must be a string`}}, nil
}
return inputs, nil, nil
}
func (p *builtinProvider) Diff(urn resource.URN, id resource.ID, state, inputs resource.PropertyMap,
allowUnknowns bool) (plugin.DiffResult, error) {
contract.Assert(urn.Type() == stackReferenceType)
if !inputs["name"].DeepEquals(state["name"]) {
return plugin.DiffResult{
Changes: plugin.DiffSome,
ReplaceKeys: []resource.PropertyKey{"name"},
}, nil
}
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
}
func (p *builtinProvider) Create(urn resource.URN,
inputs resource.PropertyMap) (resource.ID, resource.PropertyMap, resource.Status, error) {
contract.Assert(urn.Type() == stackReferenceType)
state, err := p.readStackReference(inputs)
if err != nil {
return "", nil, resource.StatusUnknown, err
}
id := resource.ID(uuid.NewV4().String())
return id, state, resource.StatusOK, nil
}
func (p *builtinProvider) Update(urn resource.URN, id resource.ID, state,
inputs resource.PropertyMap) (resource.PropertyMap, resource.Status, error) {
contract.Failf("unexpected update for builtin resource %v", urn)
contract.Assert(urn.Type() == stackReferenceType)
return state, resource.StatusOK, errors.New("unexpected update for builtin resource")
}
func (p *builtinProvider) Delete(urn resource.URN, id resource.ID,
state resource.PropertyMap) (resource.Status, error) {
contract.Assert(urn.Type() == stackReferenceType)
return resource.StatusOK, nil
}
func (p *builtinProvider) Read(urn resource.URN, id resource.ID,
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
contract.Assert(urn.Type() == stackReferenceType)
outputs, err := p.readStackReference(state)
if err != nil {
return plugin.ReadResult{}, resource.StatusUnknown, err
}
return plugin.ReadResult{
Inputs: inputs,
Outputs: outputs,
}, resource.StatusOK, nil
}
func (p *builtinProvider) Invoke(tok tokens.ModuleMember,
args resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return nil, nil, errors.Errorf("unrecognized function name: '%v'", tok)
}
func (p *builtinProvider) GetPluginInfo() (workspace.PluginInfo, error) {
// return an error: this should not be called for the builtin provider
return workspace.PluginInfo{}, errors.New("the builtin provider does not report plugin info")
}
func (p *builtinProvider) SignalCancellation() error {
p.cancel()
return nil
}
func (p *builtinProvider) readStackReference(inputs resource.PropertyMap) (resource.PropertyMap, error) {
name, ok := inputs["name"]
contract.Assert(ok)
contract.Assert(name.IsString())
if p.backendClient == nil {
return nil, errors.New("no backend client is available")
}
outputs, err := p.backendClient.GetStackOutputs(p.context, name.StringValue())
if err != nil {
return nil, err
}
return resource.PropertyMap{
"name": name,
"outputs": resource.NewObjectProperty(outputs),
}, nil
}