package deploy import ( "context" "errors" "fmt" "sort" uuid "github.com/gofrs/uuid" "github.com/pulumi/pulumi/sdk/v3/go/common/diag" "github.com/pulumi/pulumi/sdk/v3/go/common/resource" "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" ) type builtinProvider struct { context context.Context cancel context.CancelFunc diag diag.Sink backendClient BackendClient resources *resourceMap } func newBuiltinProvider(backendClient BackendClient, resources *resourceMap, d diag.Sink) *builtinProvider { ctx, cancel := context.WithCancel(context.Background()) return &builtinProvider{ context: ctx, cancel: cancel, backendClient: backendClient, resources: resources, diag: d, } } 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 } func (p *builtinProvider) GetMapping(key, provider string) ([]byte, string, error) { return nil, "", nil } func (p *builtinProvider) GetMappings(key string) ([]string, error) { return []string{}, 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, oldInputs, oldOutputs, newInputs 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, randomSeed []byte, ) (resource.PropertyMap, []plugin.CheckFailure, error) { typ := urn.Type() if typ != stackReferenceType { return nil, nil, fmt.Errorf("unrecognized resource type '%v'", urn.Type()) } // We only need to warn about this in Check. This won't be called for Reads but Creates or Updates will // call Check first. msg := "The \"pulumi:pulumi:StackReference\" resource type is deprecated. " + "Update your SDK or if already up to date raise an issue at https://github.com/pulumi/pulumi/issues." p.diag.Warningf(diag.Message(urn, msg)) 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, oldInputs, oldOutputs, newInputs resource.PropertyMap, allowUnknowns bool, ignoreChanges []string, ) (plugin.DiffResult, error) { contract.Assertf(urn.Type() == stackReferenceType, "expected resource type %v, got %v", stackReferenceType, urn.Type()) if !newInputs["name"].DeepEquals(oldOutputs["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, preview bool, ) (resource.ID, resource.PropertyMap, resource.Status, error) { contract.Assertf(urn.Type() == stackReferenceType, "expected resource type %v, got %v", stackReferenceType, urn.Type()) state, err := p.readStackReference(inputs) if err != nil { return "", nil, resource.StatusUnknown, err } var id resource.ID if !preview { // generate a new uuid uuid, err := uuid.NewV4() if err != nil { return "", nil, resource.StatusOK, err } id = resource.ID(uuid.String()) } return id, state, resource.StatusOK, nil } func (p *builtinProvider) Update(urn resource.URN, id resource.ID, oldInputs, oldOutputs, newInputs resource.PropertyMap, timeout float64, ignoreChanges []string, preview bool, ) (resource.PropertyMap, resource.Status, error) { contract.Failf("unexpected update for builtin resource %v", urn) contract.Assertf(urn.Type() == stackReferenceType, "expected resource type %v, got %v", stackReferenceType, urn.Type()) return oldOutputs, resource.StatusOK, errors.New("unexpected update for builtin resource") } func (p *builtinProvider) Delete(urn resource.URN, id resource.ID, oldInputs, oldOutputs resource.PropertyMap, timeout float64, ) (resource.Status, error) { contract.Assertf(urn.Type() == stackReferenceType, "expected resource type %v, got %v", stackReferenceType, urn.Type()) return resource.StatusOK, nil } func (p *builtinProvider) Read(urn resource.URN, id resource.ID, inputs, state resource.PropertyMap, ) (plugin.ReadResult, resource.Status, error) { contract.Requiref(urn != "", "urn", "must not be empty") contract.Requiref(id != "", "id", "must not be empty") contract.Assertf(urn.Type() == stackReferenceType, "expected resource type %v, got %v", stackReferenceType, urn.Type()) for k := range inputs { if k != "name" { return plugin.ReadResult{}, resource.StatusUnknown, fmt.Errorf("unknown property \"%v\"", k) } } 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) Construct(info plugin.ConstructInfo, typ tokens.Type, name string, parent resource.URN, inputs resource.PropertyMap, options plugin.ConstructOptions, ) (plugin.ConstructResult, error) { return plugin.ConstructResult{}, errors.New("builtin resources may not be constructed") } const ( readStackOutputs = "pulumi:pulumi:readStackOutputs" readStackResourceOutputs = "pulumi:pulumi:readStackResourceOutputs" //nolint:gosec // not a credential getResource = "pulumi:pulumi:getResource" ) 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 case getResource: outs, err := p.getResource(args) if err != nil { return nil, nil, err } return outs, nil, nil default: return nil, nil, fmt.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, errors.New("the builtin provider does not implement streaming invokes") } func (p *builtinProvider) Call(tok tokens.ModuleMember, args resource.PropertyMap, info plugin.CallInfo, options plugin.CallOptions, ) (plugin.CallResult, error) { return plugin.CallResult{}, errors.New("the builtin provider does not implement call") } 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.Assertf(ok, "missing required property 'name'") contract.Assertf(name.IsString(), "expected 'name' to be a string") 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 so the order is deterministic, to avoid spurious diffs during updates. 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.Assertf(ok, "missing required property 'stackName'") contract.Assertf(name.IsString(), "expected 'stackName' to be a string") 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 } func (p *builtinProvider) getResource(inputs resource.PropertyMap) (resource.PropertyMap, error) { urn, ok := inputs["urn"] contract.Assertf(ok, "missing required property 'urn'") contract.Assertf(urn.IsString(), "expected 'urn' to be a string") state, ok := p.resources.get(resource.URN(urn.StringValue())) if !ok { return nil, fmt.Errorf("unknown resource %v", urn.StringValue()) } return resource.PropertyMap{ "urn": urn, "id": resource.NewStringProperty(string(state.ID)), "state": resource.NewObjectProperty(state.Outputs), }, nil }