b9f44813fb
When referencing `secretOutputNames` in from another stack, spurious diffs can often be created because the secret output slice was not ordered. This PR orders the slice before it's added to the propertymap, ensuring the order always remains the same
243 lines
7.1 KiB
Go
243 lines
7.1 KiB
Go
package deploy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/pkg/errors"
|
|
uuid "github.com/satori/go.uuid"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
|
|
"github.com/pulumi/pulumi/sdk/v2/go/common/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"
|
|
}
|
|
|
|
// GetSchema returns the JSON-serialized schema for the provider.
|
|
func (p *builtinProvider) GetSchema(version int) ([]byte, error) {
|
|
return []byte("{}"), nil
|
|
}
|
|
|
|
// CheckConfig validates the configuration for this resource provider.
|
|
func (p *builtinProvider) CheckConfig(urn resource.URN, olds,
|
|
news resource.PropertyMap, allowUnknowns bool) (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(urn resource.URN, olds, news resource.PropertyMap,
|
|
allowUnknowns bool, ignoreChanges []string) (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, ignoreChanges []string) (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, timeout float64) (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,
|
|
timeout float64, ignoreChanges []string) (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, timeout float64) (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
|
|
}
|
|
|
|
const readStackOutputs = "pulumi:pulumi:readStackOutputs"
|
|
const readStackResourceOutputs = "pulumi:pulumi:readStackResourceOutputs"
|
|
|
|
func (p *builtinProvider) Invoke(tok tokens.ModuleMember,
|
|
args resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
|
|
|
switch tok {
|
|
case readStackOutputs:
|
|
outs, err := p.readStackReference(args)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return outs, nil, nil
|
|
case readStackResourceOutputs:
|
|
outs, err := p.readStackResourceOutputs(args)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return outs, nil, nil
|
|
default:
|
|
return nil, nil, errors.Errorf("unrecognized function name: '%v'", tok)
|
|
}
|
|
}
|
|
|
|
func (p *builtinProvider) StreamInvoke(
|
|
tok tokens.ModuleMember, args resource.PropertyMap,
|
|
onNext func(resource.PropertyMap) error) ([]plugin.CheckFailure, error) {
|
|
|
|
return nil, fmt.Errorf("the builtin provider does not implement streaming invokes")
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
secretOutputs := make([]resource.PropertyValue, 0)
|
|
for k, v := range outputs {
|
|
if v.ContainsSecrets() {
|
|
secretOutputs = append(secretOutputs, resource.NewStringProperty(string(k)))
|
|
}
|
|
}
|
|
|
|
// Sort the secret outputs
|
|
sort.Slice(secretOutputs, func(i, j int) bool {
|
|
return secretOutputs[i].String() < secretOutputs[j].String()
|
|
})
|
|
|
|
return resource.PropertyMap{
|
|
"name": name,
|
|
"outputs": resource.NewObjectProperty(outputs),
|
|
"secretOutputNames": resource.NewArrayProperty(secretOutputs),
|
|
}, nil
|
|
}
|
|
|
|
func (p *builtinProvider) readStackResourceOutputs(inputs resource.PropertyMap) (resource.PropertyMap, error) {
|
|
name, ok := inputs["stackName"]
|
|
contract.Assert(ok)
|
|
contract.Assert(name.IsString())
|
|
|
|
if p.backendClient == nil {
|
|
return nil, errors.New("no backend client is available")
|
|
}
|
|
|
|
outputs, err := p.backendClient.GetStackResourceOutputs(p.context, name.StringValue())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resource.PropertyMap{
|
|
"name": name,
|
|
"outputs": resource.NewObjectProperty(outputs),
|
|
}, nil
|
|
}
|