Implement first-class providers. (#1695)

### First-Class Providers
These changes implement support for first-class providers. First-class
providers are provider plugins that are exposed as resources via the
Pulumi programming model so that they may be explicitly and multiply
instantiated. Each instance of a provider resource may be configured
differently, and configuration parameters may be source from the
outputs of other resources.

### Provider Plugin Changes
In order to accommodate the need to verify and diff provider
configuration and configure providers without complete configuration
information, these changes adjust the high-level provider plugin
interface. Two new methods for validating a provider's configuration
and diffing changes to the same have been added (`CheckConfig` and
`DiffConfig`, respectively), and the type of the configuration bag
accepted by `Configure` has been changed to a `PropertyMap`.

These changes have not yet been reflected in the provider plugin gRPC
interface. We will do this in a set of follow-up changes. Until then,
these methods are implemented by adapters:
- `CheckConfig` validates that all configuration parameters are string
  or unknown properties. This is necessary because existing plugins
  only accept string-typed configuration values.
- `DiffConfig` either returns "never replace" if all configuration
  values are known or "must replace" if any configuration value is
  unknown. The justification for this behavior is given
  [here](https://github.com/pulumi/pulumi/pull/1695/files#diff-a6cd5c7f337665f5bb22e92ca5f07537R106)
- `Configure` converts the config bag to a legacy config map and
  configures the provider plugin if all config values are known. If any
  config value is unknown, the underlying plugin is not configured and
  the provider may only perform `Check`, `Read`, and `Invoke`, all of
  which return empty results. We justify this behavior becuase it is
  only possible during a preview and provides the best experience we
  can manage with the existing gRPC interface.

### Resource Model Changes
Providers are now exposed as resources that participate in a stack's
dependency graph. Like other resources, they are explicitly created,
may have multiple instances, and may have dependencies on other
resources. Providers are referred to using provider references, which
are a combination of the provider's URN and its ID. This design
addresses the need during a preview to refer to providers that have not
yet been physically created and therefore have no ID.

All custom resources that are not themselves providers must specify a
single provider via a provider reference. The named provider will be
used to manage that resource's CRUD operations. If a resource's
provider reference changes, the resource must be replaced. Though its
URN is not present in the resource's dependency list, the provider
should be treated as a dependency of the resource when topologically
sorting the dependency graph.

Finally, `Invoke` operations must now specify a provider to use for the
invocation via a provider reference.

### Engine Changes
First-class providers support requires a few changes to the engine:
- The engine must have some way to map from provider references to
  provider plugins. It must be possible to add providers from a stack's
  checkpoint to this map and to register new/updated providers during
  the execution of a plan in response to CRUD operations on provider
  resources.
- In order to support updating existing stacks using existing Pulumi
  programs that may not explicitly instantiate providers, the engine
  must be able to manage the "default" providers for each package
  referenced by a checkpoint or Pulumi program. The configuration for
  a "default" provider is taken from the stack's configuration data.

The former need is addressed by adding a provider registry type that is
responsible for managing all of the plugins required by a plan. In
addition to loading plugins froma checkpoint and providing the ability
to map from a provider reference to a provider plugin, this type serves
as the provider plugin for providers themselves (i.e. it is the
"provider provider").

The latter need is solved via two relatively self-contained changes to
plan setup and the eval source.

During plan setup, the old checkpoint is scanned for custom resources
that do not have a provider reference in order to compute the set of
packages that require a default provider. Once this set has been
computed, the required default provider definitions are conjured and
prepended to the checkpoint's resource list. Each resource that
requires a default provider is then updated to refer to the default
provider for its package.

While an eval source is running, each custom resource registration,
resource read, and invoke that does not name a provider is trapped
before being returned by the source iterator. If no default provider
for the appropriate package has been registered, the eval source
synthesizes an appropriate registration, waits for it to complete, and
records the registered provider's reference. This reference is injected
into the original request, which is then processed as usual. If a
default provider was already registered, the recorded reference is
used and no new registration occurs.

### SDK Changes
These changes only expose first-class providers from the Node.JS SDK.
- A new abstract class, `ProviderResource`, can be subclassed and used
  to instantiate first-class providers.
- A new field in `ResourceOptions`, `provider`, can be used to supply
  a particular provider instance to manage a `CustomResource`'s CRUD
  operations.
- A new type, `InvokeOptions`, can be used to specify options that
  control the behavior of a call to `pulumi.runtime.invoke`. This type
  includes a `provider` field that is analogous to
  `ResourceOptions.provider`.
This commit is contained in:
Pat Gavlin 2018-08-06 17:50:29 -07:00 committed by GitHub
parent 0d0129e400
commit a222705143
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
67 changed files with 4735 additions and 566 deletions

8
Gopkg.lock generated
View file

@ -305,6 +305,12 @@
packages = ["."]
revision = "362f9845770f1606d61ba3ddf9cfb1f0780d2ffe"
[[projects]]
name = "github.com/satori/go.uuid"
packages = ["."]
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/sergi/go-diff"
@ -571,6 +577,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "c841a519c28c6cb917a7d17871050063dc84af04c600b6d5b203e2d5dd0b3e84"
inputs-digest = "a3253944db378ee26f07b4bcdcc8685be1c49dba2998473b8b2da74b10be6afc"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -58,7 +58,7 @@ func getProjectPlugins() ([]workspace.PluginInfo, error) {
}
projinfo := &engine.Projinfo{Proj: proj, Root: root}
pwd, main, ctx, err := engine.ProjectInfoContext(projinfo, nil, nil, cmdutil.Diag(), nil)
pwd, main, ctx, err := engine.ProjectInfoContext(projinfo, nil, nil, nil, cmdutil.Diag(), nil)
if err != nil {
return nil, err
}

View file

@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/testing/integration"
)
@ -54,7 +55,7 @@ func TestExamples(t *testing.T) {
Dependencies: []string{"@pulumi/pulumi"},
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
for _, res := range stackInfo.Deployment.Resources {
if res.Parent == "" {
if !providers.IsProviderType(res.Type) && res.Parent == "" {
assert.Equal(t, stackInfo.RootResource.URN, res.URN,
"every resource but the root resource should have a parent, but %v didn't", res.URN)
}

View file

@ -319,8 +319,25 @@ func (data *resourceRowData) getInfoColumn() string {
changesBuf := &bytes.Buffer{}
if step.Old != nil && step.New != nil && step.Old.Inputs != nil && step.New.Inputs != nil {
diff := step.Old.Inputs.Diff(step.New.Inputs)
if step.Old != nil && step.New != nil {
var diff *resource.ObjectDiff
if step.Old.Inputs != nil && step.New.Inputs != nil {
diff = step.Old.Inputs.Diff(step.New.Inputs)
}
if step.Old.Provider != step.New.Provider {
if diff == nil {
diff = &resource.ObjectDiff{
Adds: make(resource.PropertyMap),
Deletes: make(resource.PropertyMap),
Sames: make(resource.PropertyMap),
Updates: make(map[resource.PropertyKey]resource.ValueDiff),
}
}
diff.Updates["provider"] = resource.ValueDiff{
Old: resource.NewStringProperty(step.Old.Provider),
New: resource.NewStringProperty(step.New.Provider),
}
}
if diff != nil {
writeString(changesBuf, "changes:")

View file

@ -49,7 +49,7 @@ func newDestroySource(
// For destroy, we consult the manifest for the plugin versions/ required to destroy it.
if target != nil && target.Snapshot != nil {
// We don't need the language plugin, since destroy doesn't run code, so we will leave that out.
kinds := plugin.AllPlugins & ^plugin.LanguagePlugins
kinds := plugin.AnalyzerPlugins
if err := plugctx.Host.EnsurePlugins(target.Snapshot.Manifest.Plugins, kinds); err != nil {
return nil, err
}

View file

@ -27,6 +27,7 @@ import (
"github.com/pulumi/pulumi/pkg/diag/colors"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/util/contract"
)
@ -132,7 +133,7 @@ func GetResourcePropertiesSummary(step StepEventMetadata, indent int) string {
id = old.ID
}
// Always print the ID and URN.
// Always print the ID, URN, and provider.
if id != "" {
writeWithIndentNoPrefix(&b, indent+1, simplePropOp, "[id=%s]\n", string(id))
}
@ -140,6 +141,26 @@ func GetResourcePropertiesSummary(step StepEventMetadata, indent int) string {
writeWithIndentNoPrefix(&b, indent+1, simplePropOp, "[urn=%s]\n", urn)
}
if step.Provider != "" {
new := step.New
if old != nil && new != nil && old.Provider != new.Provider {
newProv, err := providers.ParseReference(new.Provider)
contract.Assert(err == nil)
writeWithIndentNoPrefix(&b, indent+1, deploy.OpUpdate, "[provider: ")
write(&b, deploy.OpDelete, "%s", old.Provider)
writeVerbatim(&b, deploy.OpUpdate, " => ")
if newProv.ID() == providers.UnknownID {
write(&b, deploy.OpCreate, "%s", string(newProv.URN())+"::computed<string>")
} else {
write(&b, deploy.OpCreate, "%s", new.Provider)
}
writeVerbatim(&b, deploy.OpUpdate, "]\n")
} else {
writeWithIndentNoPrefix(&b, indent+1, simplePropOp, "[provider=%s]\n", step.Provider)
}
}
return b.String()
}

View file

@ -99,14 +99,15 @@ type ResourcePreEventPayload struct {
}
type StepEventMetadata struct {
Op deploy.StepOp // the operation performed by this step.
URN resource.URN // the resource URN (for before and after).
Type tokens.Type // the type affected by this step.
Old *StepEventStateMetadata // the state of the resource before performing this step.
New *StepEventStateMetadata // the state of the resource after performing this step.
Res *StepEventStateMetadata // the latest state for the resource that is known (worst case, old).
Keys []resource.PropertyKey // the keys causing replacement (only for CreateStep and ReplaceStep).
Logical bool // true if this step represents a logical operation in the program.
Op deploy.StepOp // the operation performed by this step.
URN resource.URN // the resource URN (for before and after).
Type tokens.Type // the type affected by this step.
Old *StepEventStateMetadata // the state of the resource before performing this step.
New *StepEventStateMetadata // the state of the resource after performing this step.
Res *StepEventStateMetadata // the latest state for the resource that is known (worst case, old).
Keys []resource.PropertyKey // the keys causing replacement (only for CreateStep and ReplaceStep).
Logical bool // true if this step represents a logical operation in the program.
Provider string // the provider that performed this step.
}
type StepEventStateMetadata struct {
@ -134,6 +135,8 @@ type StepEventStateMetadata struct {
// the resource's complete output state (as returned by the resource provider). See "Inputs"
// for additional details about how data will be transformed before going into this map.
Outputs resource.PropertyMap
// the resource's provider reference
Provider string
}
func makeEventEmitter(events chan<- Event, update UpdateInfo) eventEmitter {
@ -172,14 +175,15 @@ func makeStepEventMetadata(step deploy.Step, debug bool) StepEventMetadata {
}
return StepEventMetadata{
Op: step.Op(),
URN: step.URN(),
Type: step.Type(),
Keys: keys,
Old: makeStepEventStateMetadata(step.Old(), debug),
New: makeStepEventStateMetadata(step.New(), debug),
Res: makeStepEventStateMetadata(step.Res(), debug),
Logical: step.Logical(),
Op: step.Op(),
URN: step.URN(),
Type: step.Type(),
Keys: keys,
Old: makeStepEventStateMetadata(step.Old(), debug),
New: makeStepEventStateMetadata(step.New(), debug),
Res: makeStepEventStateMetadata(step.Res(), debug),
Logical: step.Logical(),
Provider: step.Provider(),
}
}
@ -189,15 +193,16 @@ func makeStepEventStateMetadata(state *resource.State, debug bool) *StepEventSta
}
return &StepEventStateMetadata{
Type: state.Type,
URN: state.URN,
Custom: state.Custom,
Delete: state.Delete,
ID: state.ID,
Parent: state.Parent,
Protect: state.Protect,
Inputs: filterPropertyMap(state.Inputs, debug),
Outputs: filterPropertyMap(state.Outputs, debug),
Type: state.Type,
URN: state.URN,
Custom: state.Custom,
Delete: state.Delete,
ID: state.ID,
Parent: state.Parent,
Protect: state.Protect,
Inputs: filterPropertyMap(state.Inputs, debug),
Outputs: filterPropertyMap(state.Outputs, debug),
Provider: state.Provider,
}
}

View file

@ -0,0 +1,783 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package engine
import (
"context"
"testing"
"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"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/resource/deploy/deploytest"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/cancel"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/util/logging"
"github.com/pulumi/pulumi/pkg/workspace"
)
type JournalEntryKind int
const (
JournalEntryBegin JournalEntryKind = 0
JournalEntrySuccess JournalEntryKind = 1
JournalEntryFailure JournalEntryKind = 2
JournalEntryOutputs JournalEntryKind = 4
)
type JournalEntry struct {
Kind JournalEntryKind
Step deploy.Step
}
type Journal struct {
Entries []JournalEntry
events chan JournalEntry
cancel chan bool
done chan bool
}
func (j *Journal) Close() error {
close(j.cancel)
close(j.events)
<-j.done
return nil
}
func (j *Journal) BeginMutation(step deploy.Step) (SnapshotMutation, error) {
select {
case j.events <- JournalEntry{Kind: JournalEntryBegin, Step: step}:
return j, nil
case <-j.cancel:
return nil, errors.New("journal closed")
}
}
func (j *Journal) End(step deploy.Step, success bool) error {
kind := JournalEntryFailure
if success {
kind = JournalEntrySuccess
}
select {
case j.events <- JournalEntry{Kind: kind, Step: step}:
return nil
case <-j.cancel:
return errors.New("journal closed")
}
}
func (j *Journal) RegisterResourceOutputs(step deploy.Step) error {
select {
case j.events <- JournalEntry{Kind: JournalEntryOutputs, Step: step}:
return nil
case <-j.cancel:
return errors.New("journal closed")
}
}
func (j *Journal) RecordPlugin(plugin workspace.PluginInfo) error {
return nil
}
func (j *Journal) Snap(base *deploy.Snapshot) *deploy.Snapshot {
// Build up a list of current resources by replaying the journal.
resources, dones := []*resource.State{}, make(map[*resource.State]bool)
for _, e := range j.Entries {
logging.V(7).Infof("%v %v (%v)", e.Step.Op(), e.Step.URN(), e.Kind)
if e.Kind != JournalEntrySuccess {
continue
}
switch e.Step.Op() {
case deploy.OpSame, deploy.OpUpdate:
resources = append(resources, e.Step.New())
dones[e.Step.Old()] = true
case deploy.OpCreate, deploy.OpCreateReplacement:
resources = append(resources, e.Step.New())
case deploy.OpDelete, deploy.OpDeleteReplaced:
dones[e.Step.Old()] = true
case deploy.OpReplace:
// do nothing.
}
}
// Append any resources from the base snapshot that were not produced by the current snapshot.
// See backend.SnapshotManager.snap for why this works.
if base != nil {
for _, res := range base.Resources {
if !dones[res] {
resources = append(resources, res)
}
}
}
manifest := deploy.Manifest{}
manifest.Magic = manifest.NewMagic()
return deploy.NewSnapshot(manifest, resources)
}
func newJournal() *Journal {
j := &Journal{
events: make(chan JournalEntry),
cancel: make(chan bool),
done: make(chan bool),
}
go func() {
for e := range j.events {
j.Entries = append(j.Entries, e)
}
close(j.done)
}()
return j
}
type updateInfo struct {
project workspace.Project
target deploy.Target
}
func (u *updateInfo) GetRoot() string {
return ""
}
func (u *updateInfo) GetProject() *workspace.Project {
return &u.project
}
func (u *updateInfo) GetTarget() *deploy.Target {
return &u.target
}
type TestOp func(UpdateInfo, *Context, UpdateOptions, bool) (ResourceChanges, error)
type ValidateFunc func(project workspace.Project, target deploy.Target, j *Journal, err error) error
func (op TestOp) Run(project workspace.Project, target deploy.Target, opts UpdateOptions,
dryRun bool, validate ValidateFunc) (*deploy.Snapshot, error) {
// Create an appropriate update info and context.
info := &updateInfo{project: project, target: target}
cancelCtx, _ := cancel.NewContext(context.Background())
events := make(chan Event)
journal := newJournal()
ctx := &Context{
Cancel: cancelCtx,
Events: events,
SnapshotManager: journal,
}
// Begin draining events.
go func() {
for range events {
}
}()
// Run the step and its validator.
_, err := op(info, ctx, opts, dryRun)
contract.IgnoreClose(journal)
if dryRun {
return nil, err
}
if validate != nil {
err = validate(project, target, journal, err)
}
snap := journal.Snap(target.Snapshot)
if snap != nil {
err = snap.VerifyIntegrity()
}
return snap, err
}
type TestStep struct {
Op TestOp
Validate ValidateFunc
}
type TestPlan struct {
Project string
Stack string
Runtime string
Config config.Map
Decrypter config.Decrypter
Options UpdateOptions
Steps []TestStep
}
func (p *TestPlan) getNames() (stack tokens.QName, project tokens.PackageName, runtime string) {
project = tokens.PackageName(p.Project)
if project == "" {
project = "test"
}
runtime = p.Runtime
if runtime == "" {
runtime = "test"
}
stack = tokens.QName(p.Stack)
if stack == "" {
stack = "test"
}
return stack, project, runtime
}
func (p *TestPlan) NewURN(typ tokens.Type, name string, parent resource.URN) resource.URN {
stack, project, _ := p.getNames()
var pt tokens.Type
if parent != "" {
pt = parent.Type()
}
return resource.NewURN(stack, project, pt, typ, tokens.QName(name))
}
func (p *TestPlan) NewProviderURN(pkg tokens.Package, name string, parent resource.URN) resource.URN {
return p.NewURN(providers.MakeProviderType(pkg), name, parent)
}
func (p *TestPlan) Run(t *testing.T, snapshot *deploy.Snapshot) *deploy.Snapshot {
stack, projectName, runtime := p.getNames()
cfg := p.Config
if cfg == nil {
cfg = config.Map{}
}
project := &workspace.Project{
Name: projectName,
RuntimeInfo: workspace.NewProjectRuntimeInfo(runtime, nil),
}
target := &deploy.Target{
Name: stack,
Config: cfg,
Decrypter: p.Decrypter,
Snapshot: snapshot,
}
for _, step := range p.Steps {
_, err := step.Op.Run(*project, *target, p.Options, true, step.Validate)
assert.NoError(t, err)
target.Snapshot, err = step.Op.Run(*project, *target, p.Options, false, step.Validate)
assert.NoError(t, err)
}
return target.Snapshot
}
func MakeBasicLifecycleSteps(t *testing.T, resCount int) []TestStep {
return []TestStep{
// Initial update
{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
// Should see only creates.
for _, entry := range j.Entries {
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
}
assert.Len(t, j.Snap(target.Snapshot).Resources, resCount)
return err
},
},
// No-op refresh
{
Op: Refresh,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
// Should see only sames.
for _, entry := range j.Entries {
assert.Equal(t, deploy.OpSame, entry.Step.Op())
}
assert.Len(t, j.Snap(target.Snapshot).Resources, resCount)
return err
},
},
// No-op update
{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
// Should see only sames.
for _, entry := range j.Entries {
assert.Equal(t, deploy.OpSame, entry.Step.Op())
}
assert.Len(t, j.Snap(target.Snapshot).Resources, resCount)
return err
},
},
// No-op refresh
{
Op: Refresh,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
// Should see only sames.
for _, entry := range j.Entries {
assert.Equal(t, deploy.OpSame, entry.Step.Op())
}
assert.Len(t, j.Snap(target.Snapshot).Resources, resCount)
return err
},
},
// Destroy
{
Op: Destroy,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
// Should see only deletes.
for _, entry := range j.Entries {
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
}
assert.Len(t, j.Snap(target.Snapshot).Resources, 0)
return err
},
},
// No-op refresh
{
Op: Refresh,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
assert.Len(t, j.Entries, 0)
assert.Len(t, j.Snap(target.Snapshot).Resources, 0)
return err
},
},
}
}
func TestEmptyProgramLifecycle(t *testing.T) {
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, _ *deploytest.ResourceMonitor) error {
return nil
})
host := deploytest.NewPluginHost(nil, program)
p := &TestPlan{
Options: UpdateOptions{host: host},
Steps: MakeBasicLifecycleSteps(t, 0),
}
p.Run(t, nil)
}
func TestSingleResourceDefaultProviderLifecycle(t *testing.T) {
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{}, nil
}),
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{})
assert.NoError(t, err)
return nil
})
host := deploytest.NewPluginHost(nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{host: host},
Steps: MakeBasicLifecycleSteps(t, 2),
}
p.Run(t, nil)
}
func TestSingleResourceExplicitProviderLifecycle(t *testing.T) {
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{}, nil
}),
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, "",
false, nil, "", resource.PropertyMap{})
assert.NoError(t, err)
if provID == "" {
provID = providers.UnknownID
}
provRef, err := providers.NewReference(provURN, provID)
assert.NoError(t, err)
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, provRef.String(),
resource.PropertyMap{})
assert.NoError(t, err)
return nil
})
host := deploytest.NewPluginHost(nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{host: host},
Steps: MakeBasicLifecycleSteps(t, 2),
}
p.Run(t, nil)
}
func TestSingleResourceDefaultProviderUpgrade(t *testing.T) {
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{}, nil
}),
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{})
assert.NoError(t, err)
return nil
})
host := deploytest.NewPluginHost(nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{host: host},
}
provURN := p.NewProviderURN("pkgA", "default", "")
resURN := p.NewURN("pkgA:m:typA", "resA", "")
// Create an old snapshot with an existing copy of the single resource and no providers.
old := &deploy.Snapshot{
Resources: []*resource.State{{
Type: resURN.Type(),
URN: resURN,
Custom: true,
ID: "0",
Inputs: resource.PropertyMap{},
Outputs: resource.PropertyMap{},
}},
}
validate := func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
// Should see only sames: the default provider should be injected into the old state before the update
// runs.
for _, entry := range j.Entries {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
assert.Equal(t, deploy.OpSame, entry.Step.Op())
default:
t.Fatalf("unexpected resource %v", urn)
}
}
assert.Len(t, j.Snap(target.Snapshot).Resources, 2)
return err
}
// Run a single update step using the base snapshot.
p.Steps = []TestStep{{Op: Update, Validate: validate}}
p.Run(t, old)
// Run a single refresh step using the base snapshot.
p.Steps = []TestStep{{Op: Refresh, Validate: validate}}
p.Run(t, old)
// Run a single destroy step using the base snapshot.
p.Steps = []TestStep{{
Op: Destroy,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
// Should see two deletes: the default provider should be injected into the old state before the update
// runs.
deleted := make(map[resource.URN]bool)
for _, entry := range j.Entries {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
deleted[urn] = true
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
default:
t.Fatalf("unexpected resource %v", urn)
}
}
assert.Len(t, deleted, 2)
assert.Len(t, j.Snap(target.Snapshot).Resources, 0)
return err
},
}}
p.Run(t, old)
// Run a partial lifecycle using the base snapshot, skipping the initial update step.
p.Steps = MakeBasicLifecycleSteps(t, 2)[1:]
p.Run(t, old)
}
func TestSingleResourceDefaultProviderReplace(t *testing.T) {
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{
DiffConfigF: func(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
// Always require replacement.
keys := []resource.PropertyKey{}
for k := range news {
keys = append(keys, k)
}
return plugin.DiffResult{ReplaceKeys: keys}, nil
},
}, nil
}),
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, "",
resource.PropertyMap{})
assert.NoError(t, err)
return nil
})
host := deploytest.NewPluginHost(nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{host: host},
Config: config.Map{
config.MustMakeKey("pkgA", "foo"): config.NewValue("bar"),
},
}
// Build a basic lifecycle.
steps := MakeBasicLifecycleSteps(t, 2)
// Run the lifecycle through its no-op update+refresh.
p.Steps = steps[:4]
snap := p.Run(t, nil)
// Change the config and run an update. We expect everything to require replacement.
p.Config[config.MustMakeKey("pkgA", "foo")] = config.NewValue("baz")
p.Steps = []TestStep{{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
provURN := p.NewProviderURN("pkgA", "default", "")
resURN := p.NewURN("pkgA:m:typA", "resA", "")
// Look for replace steps on the provider and the resource.
replacedProvider, replacedResource := false, false
for _, entry := range j.Entries {
if entry.Kind != JournalEntrySuccess || entry.Step.Op() != deploy.OpDeleteReplaced {
continue
}
switch urn := entry.Step.URN(); urn {
case provURN:
replacedProvider = true
case resURN:
replacedResource = true
default:
t.Fatalf("unexpected resource %v", urn)
}
}
assert.True(t, replacedProvider)
assert.True(t, replacedResource)
return err
},
}}
snap = p.Run(t, snap)
// Resume the lifecycle with another no-op update.
p.Steps = steps[2:]
p.Run(t, snap)
}
func TestSingleResourceExplicitProviderReplace(t *testing.T) {
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{
DiffConfigF: func(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
// Always require replacement.
keys := []resource.PropertyKey{}
for k := range news {
keys = append(keys, k)
}
return plugin.DiffResult{ReplaceKeys: keys}, nil
},
}, nil
}),
}
providerInputs := resource.PropertyMap{
resource.PropertyKey("foo"): resource.NewStringProperty("bar"),
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, "",
false, nil, "", providerInputs)
assert.NoError(t, err)
if provID == "" {
provID = providers.UnknownID
}
provRef, err := providers.NewReference(provURN, provID)
assert.NoError(t, err)
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, provRef.String(),
resource.PropertyMap{})
assert.NoError(t, err)
return nil
})
host := deploytest.NewPluginHost(nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{host: host},
}
// Build a basic lifecycle.
steps := MakeBasicLifecycleSteps(t, 2)
// Run the lifecycle through its no-op update+refresh.
p.Steps = steps[:4]
snap := p.Run(t, nil)
// Change the config and run an update. We expect everything to require replacement.
providerInputs[resource.PropertyKey("foo")] = resource.NewStringProperty("baz")
p.Steps = []TestStep{{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
provURN := p.NewProviderURN("pkgA", "provA", "")
resURN := p.NewURN("pkgA:m:typA", "resA", "")
// Look for replace steps on the provider and the resource.
replacedProvider, replacedResource := false, false
for _, entry := range j.Entries {
if entry.Kind != JournalEntrySuccess || entry.Step.Op() != deploy.OpDeleteReplaced {
continue
}
switch urn := entry.Step.URN(); urn {
case provURN:
replacedProvider = true
case resURN:
replacedResource = true
default:
t.Fatalf("unexpected resource %v", urn)
}
}
assert.True(t, replacedProvider)
assert.True(t, replacedResource)
return err
},
}}
snap = p.Run(t, snap)
// Resume the lifecycle with another no-op update.
p.Steps = steps[2:]
p.Run(t, snap)
}
func TestSingleResourceExplicitProviderDeleteBeforeReplace(t *testing.T) {
loaders := []*deploytest.ProviderLoader{
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
return &deploytest.Provider{
DiffConfigF: func(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
// Always require replacement.
keys := []resource.PropertyKey{}
for k := range news {
keys = append(keys, k)
}
return plugin.DiffResult{ReplaceKeys: keys, DeleteBeforeReplace: true}, nil
},
}, nil
}),
}
providerInputs := resource.PropertyMap{
resource.PropertyKey("foo"): resource.NewStringProperty("bar"),
}
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true, "",
false, nil, "", providerInputs)
assert.NoError(t, err)
if provID == "" {
provID = providers.UnknownID
}
provRef, err := providers.NewReference(provURN, provID)
assert.NoError(t, err)
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, "", false, nil, provRef.String(),
resource.PropertyMap{})
assert.NoError(t, err)
return nil
})
host := deploytest.NewPluginHost(nil, program, loaders...)
p := &TestPlan{
Options: UpdateOptions{host: host},
}
// Build a basic lifecycle.
steps := MakeBasicLifecycleSteps(t, 2)
// Run the lifecycle through its no-op update+refresh.
p.Steps = steps[:4]
snap := p.Run(t, nil)
// Change the config and run an update. We expect everything to require replacement.
providerInputs[resource.PropertyKey("foo")] = resource.NewStringProperty("baz")
p.Steps = []TestStep{{
Op: Update,
Validate: func(project workspace.Project, target deploy.Target, j *Journal, err error) error {
provURN := p.NewProviderURN("pkgA", "provA", "")
resURN := p.NewURN("pkgA:m:typA", "resA", "")
// Look for replace steps on the provider and the resource.
createdProvider, createdResource := false, false
deletedProvider, deletedResource := false, false
for _, entry := range j.Entries {
if entry.Kind != JournalEntrySuccess {
continue
}
switch urn := entry.Step.URN(); urn {
case provURN:
if entry.Step.Op() == deploy.OpDeleteReplaced {
assert.False(t, createdProvider)
assert.False(t, createdResource)
assert.True(t, deletedResource)
deletedProvider = true
} else if entry.Step.Op() == deploy.OpCreateReplacement {
assert.True(t, deletedProvider)
assert.True(t, deletedResource)
assert.False(t, createdResource)
createdProvider = true
}
case resURN:
if entry.Step.Op() == deploy.OpDeleteReplaced {
assert.False(t, deletedProvider)
assert.False(t, deletedResource)
deletedResource = true
} else if entry.Step.Op() == deploy.OpCreateReplacement {
assert.True(t, deletedProvider)
assert.True(t, deletedResource)
assert.True(t, createdProvider)
createdResource = true
}
default:
t.Fatalf("unexpected resource %v", urn)
}
}
assert.True(t, deletedProvider)
assert.True(t, deletedResource)
return err
},
}}
snap = p.Run(t, snap)
// Resume the lifecycle with another no-op update.
p.Steps = steps[2:]
p.Run(t, snap)
}

View file

@ -32,7 +32,7 @@ import (
)
// ProjectInfoContext returns information about the current project, including its pwd, main, and plugin context.
func ProjectInfoContext(projinfo *Projinfo, config plugin.ConfigSource, pluginEvents plugin.Events,
func ProjectInfoContext(projinfo *Projinfo, host plugin.Host, config plugin.ConfigSource, pluginEvents plugin.Events,
diag diag.Sink, tracingSpan opentracing.Span) (string, string, *plugin.Context, error) {
contract.Require(projinfo != nil, "projinfo")
@ -43,7 +43,8 @@ func ProjectInfoContext(projinfo *Projinfo, config plugin.ConfigSource, pluginEv
}
// Create a context for plugins.
ctx, err := plugin.NewContext(diag, nil, config, pluginEvents, pwd, projinfo.Proj.RuntimeInfo.Options(), tracingSpan)
ctx, err := plugin.NewContext(diag, host, config, pluginEvents, pwd, projinfo.Proj.RuntimeInfo.Options(),
tracingSpan)
if err != nil {
return "", "", nil, err
}
@ -119,7 +120,7 @@ func plan(ctx *Context, info *planContext, opts planOptions, dryRun bool) (*plan
contract.Assert(proj != nil)
contract.Assert(target != nil)
projinfo := &Projinfo{Proj: proj, Root: info.Update.GetRoot()}
pwd, main, plugctx, err := ProjectInfoContext(projinfo, target, pluginEvents, opts.Diag, info.TracingSpan)
pwd, main, plugctx, err := ProjectInfoContext(projinfo, opts.host, target, pluginEvents, opts.Diag, info.TracingSpan)
if err != nil {
return nil, err
}
@ -145,7 +146,10 @@ func plan(ctx *Context, info *planContext, opts planOptions, dryRun bool) (*plan
}
// Generate a plan; this API handles all interesting cases (create, update, delete).
plan := deploy.NewPlan(plugctx, target, target.Snapshot, source, analyzers, dryRun)
plan, err := deploy.NewPlan(plugctx, target, target.Snapshot, source, analyzers, dryRun)
if err != nil {
return nil, err
}
return &planResult{
Ctx: info,
Plugctx: plugctx,
@ -191,7 +195,7 @@ func (res *planResult) Walk(cancelCtx *Context, events deploy.Events, preview bo
Parallel: res.Options.Parallel,
}
src, err := res.Plan.Source().Iterate(opts)
src, err := res.Plan.Source().Iterate(opts, res.Plan)
if err != nil {
return nil, err
}

View file

@ -49,7 +49,7 @@ func newRefreshSource(opts planOptions, proj *workspace.Project, pwd, main strin
// First, consult the manifest for the plugins we will need to ask to refresh the state.
if target != nil && target.Snapshot != nil {
// We don't need the language plugin, since refresh doesn't run code, so we will leave that out.
kinds := plugin.AllPlugins & ^plugin.LanguagePlugins
kinds := plugin.AnalyzerPlugins
if err := plugctx.Host.EnsurePlugins(target.Snapshot.Manifest.Plugins, kinds); err != nil {
return nil, err
}

View file

@ -20,16 +20,20 @@ import (
"sync"
"time"
"github.com/blang/semver"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/diag/colors"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy"
"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"
)
// UpdateOptions contains all the settings for customizing how an update (deploy, preview, or destroy) is performed.
// nolint: structcheck, host is used in a different file
type UpdateOptions struct {
// an optional set of analyzers to run as part of this deployment.
Analyzers []string
@ -39,6 +43,9 @@ type UpdateOptions struct {
// true if debugging output it enabled
Debug bool
// the plugin host to use for this update
host plugin.Host
}
// ResourceChanges contains the aggregate resource changes by operation type.
@ -91,10 +98,20 @@ func newUpdateSource(
}
// Now ensure that we have loaded up any plugins that the program will need in advance.
if err = plugctx.Host.EnsurePlugins(plugins, plugin.AllPlugins); err != nil {
const kinds = plugin.AnalyzerPlugins | plugin.LanguagePlugins
if err = plugctx.Host.EnsurePlugins(plugins, kinds); err != nil {
return nil, err
}
// Collect the version information for default providers.
defaultProviderVersions := make(map[tokens.Package]*semver.Version)
for _, p := range plugins {
if p.Kind != workspace.ResourcePlugin {
continue
}
defaultProviderVersions[tokens.Package(p.Name)] = p.Version
}
// If that succeeded, create a new source that will perform interpretation of the compiled program.
// TODO[pulumi/pulumi#88]: we are passing `nil` as the arguments map; we need to allow a way to pass these.
return deploy.NewEvalSource(plugctx, &deploy.EvalRunInfo{
@ -102,7 +119,7 @@ func newUpdateSource(
Pwd: pwd,
Program: main,
Target: target,
}, dryRun), nil
}, defaultProviderVersions, dryRun), nil
}
func update(ctx *Context, info *planContext, opts planOptions, dryRun bool) (ResourceChanges, error) {

View file

@ -0,0 +1,71 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deploytest
import (
"github.com/pkg/errors"
"google.golang.org/grpc"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/workspace"
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
)
type ProgramFunc func(runInfo plugin.RunInfo, monitor *ResourceMonitor) error
func NewLanguageRuntime(program ProgramFunc, requiredPlugins ...workspace.PluginInfo) plugin.LanguageRuntime {
return &languageRuntime{
requiredPlugins: requiredPlugins,
program: program,
}
}
type languageRuntime struct {
requiredPlugins []workspace.PluginInfo
program ProgramFunc
}
func (p *languageRuntime) Close() error {
return nil
}
func (p *languageRuntime) GetRequiredPlugins(info plugin.ProgInfo) ([]workspace.PluginInfo, error) {
return p.requiredPlugins, nil
}
func (p *languageRuntime) Run(info plugin.RunInfo) (string, error) {
// Connect to the resource monitor and create an appropriate client.
conn, err := grpc.Dial(info.MonitorAddress, grpc.WithInsecure())
if err != nil {
return "", errors.Wrapf(err, "could not connect to resource monitor")
}
// Fire up a resource monitor client
resmon := pulumirpc.NewResourceMonitorClient(conn)
// Run the program.
done := make(chan error)
go func() {
done <- p.program(info, &ResourceMonitor{resmon: resmon})
}()
if progerr := <-done; progerr != nil {
return progerr.Error(), nil
}
return "", nil
}
func (p *languageRuntime) GetPluginInfo() (workspace.PluginInfo, error) {
return workspace.PluginInfo{Name: "TestLanguage"}, nil
}

View file

@ -0,0 +1,111 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deploytest
import (
"github.com/blang/semver"
"github.com/pkg/errors"
"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/workspace"
)
type LoadProviderFunc func() (plugin.Provider, error)
type ProviderLoader struct {
pkg tokens.Package
version semver.Version
load LoadProviderFunc
}
func NewProviderLoader(pkg tokens.Package, version semver.Version, load LoadProviderFunc) *ProviderLoader {
return &ProviderLoader{
pkg: pkg,
version: version,
load: load,
}
}
type pluginHost struct {
providerLoaders []*ProviderLoader
languageRuntime plugin.LanguageRuntime
sink diag.Sink
}
func NewPluginHost(sink diag.Sink, languageRuntime plugin.LanguageRuntime,
providerLoaders ...*ProviderLoader) plugin.Host {
return &pluginHost{
providerLoaders: providerLoaders,
languageRuntime: languageRuntime,
sink: sink,
}
}
func (host *pluginHost) Provider(pkg tokens.Package, version *semver.Version) (plugin.Provider, error) {
var best *ProviderLoader
for _, l := range host.providerLoaders {
if l.pkg != pkg {
continue
}
if version != nil && l.version.LT(*version) {
continue
}
if best == nil || l.version.GT(best.version) {
best = l
}
}
if best == nil {
return nil, nil
}
return best.load()
}
func (host *pluginHost) LanguageRuntime(runtime string) (plugin.LanguageRuntime, error) {
return host.languageRuntime, nil
}
func (host *pluginHost) SignalCancellation() error {
return nil
}
func (host *pluginHost) Close() error {
return nil
}
func (host *pluginHost) ServerAddr() string {
panic("Host RPC address not available")
}
func (host *pluginHost) Log(sev diag.Severity, urn resource.URN, msg string, streamID int32) {
host.sink.Logf(sev, diag.StreamMessage(urn, msg, streamID))
}
func (host *pluginHost) Analyzer(nm tokens.QName) (plugin.Analyzer, error) {
return nil, errors.New("unsupported")
}
func (host *pluginHost) CloseProvider(provider plugin.Provider) error {
return nil
}
func (host *pluginHost) ListPlugins() []workspace.PluginInfo {
return nil
}
func (host *pluginHost) EnsurePlugins(plugins []workspace.PluginInfo, kinds plugin.Flags) error {
return nil
}
func (host *pluginHost) GetRequiredPlugins(info plugin.ProgInfo,
kinds plugin.Flags) ([]workspace.PluginInfo, error) {
return nil, nil
}

View file

@ -0,0 +1,144 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deploytest
import (
"github.com/blang/semver"
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 Provider struct {
Name string
Package tokens.Package
Version semver.Version
configured bool
CheckConfigF func(olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error)
DiffConfigF func(olds, news resource.PropertyMap) (plugin.DiffResult, error)
ConfigureF func(news resource.PropertyMap) error
CheckF func(urn resource.URN,
olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error)
DiffF func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap) (plugin.DiffResult, error)
CreateF func(urn resource.URN,
inputs resource.PropertyMap) (resource.ID, resource.PropertyMap, resource.Status, error)
UpdateF func(urn resource.URN, id resource.ID,
olds, news resource.PropertyMap) (resource.PropertyMap, resource.Status, error)
DeleteF func(urn resource.URN, id resource.ID, olds resource.PropertyMap) (resource.Status, error)
ReadF func(urn resource.URN, id resource.ID, props resource.PropertyMap) (resource.PropertyMap, error)
InvokeF func(tok tokens.ModuleMember,
inputs resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error)
}
func (prov *Provider) SignalCancellation() error {
return nil
}
func (prov *Provider) Close() error {
return nil
}
func (prov *Provider) Pkg() tokens.Package {
return prov.Package
}
func (prov *Provider) GetPluginInfo() (workspace.PluginInfo, error) {
return workspace.PluginInfo{
Name: prov.Name,
Version: &prov.Version,
}, nil
}
func (prov *Provider) CheckConfig(olds,
news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
if prov.CheckConfigF == nil {
return news, nil, nil
}
return prov.CheckConfigF(olds, news)
}
func (prov *Provider) DiffConfig(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
if prov.DiffConfigF == nil {
return plugin.DiffResult{}, nil
}
return prov.DiffConfigF(olds, news)
}
func (prov *Provider) Configure(inputs resource.PropertyMap) error {
contract.Assert(!prov.configured)
prov.configured = true
if prov.ConfigureF == nil {
return nil
}
return prov.ConfigureF(inputs)
}
func (prov *Provider) Check(urn resource.URN,
olds, news resource.PropertyMap, _ bool) (resource.PropertyMap, []plugin.CheckFailure, error) {
if prov.CheckF == nil {
return news, nil, nil
}
return prov.CheckF(urn, olds, news)
}
func (prov *Provider) Create(urn resource.URN, props resource.PropertyMap) (resource.ID,
resource.PropertyMap, resource.Status, error) {
if prov.CreateF == nil {
return resource.ID(uuid.NewV4().String()), resource.PropertyMap{}, resource.StatusOK, nil
}
return prov.CreateF(urn, props)
}
func (prov *Provider) Diff(urn resource.URN, id resource.ID,
olds resource.PropertyMap, news resource.PropertyMap, _ bool) (plugin.DiffResult, error) {
if prov.DiffF == nil {
return plugin.DiffResult{}, nil
}
return prov.DiffF(urn, id, olds, news)
}
func (prov *Provider) Update(urn resource.URN, id resource.ID,
olds resource.PropertyMap, news resource.PropertyMap) (resource.PropertyMap, resource.Status, error) {
if prov.UpdateF == nil {
return resource.PropertyMap{}, resource.StatusOK, nil
}
return prov.UpdateF(urn, id, olds, news)
}
func (prov *Provider) Delete(urn resource.URN,
id resource.ID, props resource.PropertyMap) (resource.Status, error) {
if prov.DeleteF == nil {
return resource.StatusOK, nil
}
return prov.DeleteF(urn, id, props)
}
func (prov *Provider) Read(urn resource.URN, id resource.ID,
props resource.PropertyMap) (resource.PropertyMap, error) {
if prov.ReadF == nil {
return resource.PropertyMap{}, nil
}
return prov.ReadF(urn, id, props)
}
func (prov *Provider) Invoke(tok tokens.ModuleMember,
args resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
if prov.InvokeF == nil {
return resource.PropertyMap{}, nil, nil
}
return prov.InvokeF(tok, args)
}

View file

@ -0,0 +1,131 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deploytest
import (
"context"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
pulumirpc "github.com/pulumi/pulumi/sdk/proto/go"
)
type ResourceMonitor struct {
resmon pulumirpc.ResourceMonitorClient
}
func (rm *ResourceMonitor) RegisterResource(t tokens.Type, name string, custom bool, parent resource.URN, protect bool,
dependencies []resource.URN, provider string,
inputs resource.PropertyMap) (resource.URN, resource.ID, resource.PropertyMap, error) {
// marshal inputs
ins, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{KeepUnknowns: true})
if err != nil {
return "", "", nil, err
}
// marshal dependencies
deps := []string{}
for _, d := range dependencies {
deps = append(deps, string(d))
}
// submit request
resp, err := rm.resmon.RegisterResource(context.Background(), &pulumirpc.RegisterResourceRequest{
Type: string(t),
Name: name,
Custom: custom,
Parent: string(parent),
Protect: protect,
Dependencies: deps,
Provider: provider,
Object: ins,
})
if err != nil {
return "", "", nil, err
}
// unmarshal outputs
outs, err := plugin.UnmarshalProperties(resp.Object, plugin.MarshalOptions{KeepUnknowns: true})
if err != nil {
return "", "", nil, err
}
return resource.URN(resp.Urn), resource.ID(resp.Id), outs, nil
}
func (rm *ResourceMonitor) ReadResource(t tokens.Type, name string, id resource.ID, parent resource.URN,
inputs resource.PropertyMap, provider string) (resource.URN, resource.PropertyMap, error) {
// marshal inputs
ins, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{KeepUnknowns: true})
if err != nil {
return "", nil, err
}
// submit request
resp, err := rm.resmon.ReadResource(context.Background(), &pulumirpc.ReadResourceRequest{
Type: string(t),
Name: name,
Parent: string(parent),
Provider: provider,
Properties: ins,
})
if err != nil {
return "", nil, err
}
// unmarshal outputs
outs, err := plugin.UnmarshalProperties(resp.Properties, plugin.MarshalOptions{KeepUnknowns: true})
if err != nil {
return "", nil, err
}
return resource.URN(resp.Urn), outs, nil
}
func (rm *ResourceMonitor) Invoke(tok tokens.ModuleMember,
inputs resource.PropertyMap, provider string) (resource.PropertyMap, []*pulumirpc.CheckFailure, error) {
// marshal inputs
ins, err := plugin.MarshalProperties(inputs, plugin.MarshalOptions{KeepUnknowns: true})
if err != nil {
return nil, nil, err
}
// submit request
resp, err := rm.resmon.Invoke(context.Background(), &pulumirpc.InvokeRequest{
Tok: string(tok),
Provider: provider,
Args: ins,
})
if err != nil {
return nil, nil, err
}
// handle failures
if len(resp.Failures) != 0 {
return nil, resp.Failures, nil
}
// unmarshal outputs
outs, err := plugin.UnmarshalProperties(resp.Return, plugin.MarshalOptions{KeepUnknowns: true})
if err != nil {
return nil, nil, err
}
return outs, nil, nil
}

View file

@ -15,8 +15,13 @@
package deploy
import (
"github.com/blang/semver"
"github.com/pkg/errors"
uuid "github.com/satori/go.uuid"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/graph"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
@ -68,6 +73,77 @@ type Plan struct {
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.
depGraph *graph.DependencyGraph // the dependency graph of the old snapshot
providers *providers.Registry // the provider registry for this plan.
}
// addDefaultProviders adds any necessary default provider definitions and references to the given snapshot. Version
// information for these providers is sourced from the snapshot's manifest; inputs parameters are sourced from the
// stack's configuration.
func addDefaultProviders(target *Target, source Source, prev *Snapshot) error {
if prev == nil {
return nil
}
// Pull the versions we'll use for default providers from the snapshot's manifest.
defaultProviderVersions := make(map[tokens.Package]*semver.Version)
for _, p := range prev.Manifest.Plugins {
defaultProviderVersions[tokens.Package(p.Name)] = p.Version
}
// Determine the necessary set of default providers and inject references to default providers as appropriate.
//
// We do this by scraping the snapshot for custom resources that does not reference a provider and adding
// default providers for these resources' packages. Each of these resources is rewritten to reference the default
// provider for its package.
//
// The configuration for each default provider is pulled from the stack's configuration information.
var defaultProviders []*resource.State
defaultProviderRefs := make(map[tokens.Package]providers.Reference)
for _, res := range prev.Resources {
if providers.IsProviderType(res.URN.Type()) || !res.Custom || res.Provider != "" {
continue
}
pkg := res.URN.Type().Package()
ref, ok := defaultProviderRefs[pkg]
if !ok {
cfg, err := target.GetPackageConfig(pkg)
if err != nil {
return errors.Errorf("could not fetch configuration for default provider '%v'", pkg)
}
inputs := make(resource.PropertyMap)
for k, v := range cfg {
inputs[resource.PropertyKey(k.Name())] = resource.NewStringProperty(v)
}
if version, ok := defaultProviderVersions[pkg]; ok {
inputs["version"] = resource.NewStringProperty(version.String())
}
urn, id := defaultProviderURN(target, source, pkg), resource.ID(uuid.NewV4().String())
ref, err = providers.NewReference(urn, id)
contract.Assert(err == nil)
provider := &resource.State{
Type: urn.Type(),
URN: urn,
Custom: true,
ID: id,
Inputs: inputs,
}
defaultProviders = append(defaultProviders, provider)
defaultProviderRefs[pkg] = ref
}
res.Provider = ref.String()
}
// If any default providers are necessary, prepend their definitions to the snapshot's resources. This trivially
// guarantees that all default provider references name providers that precede the referent in the snapshot.
if len(defaultProviders) != 0 {
prev.Resources = append(defaultProviders, prev.Resources...)
}
return nil
}
// NewPlan creates a new deployment plan from a resource snapshot plus a package to evaluate.
@ -80,17 +156,29 @@ type Plan struct {
// 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 {
preview bool) (*Plan, error) {
contract.Assert(ctx != nil)
contract.Assert(target != nil)
contract.Assert(source != nil)
// Add any necessary default provider references to the previous snapshot in order to accommodate stacks that were
// created prior to the changes that added first-class providers. We do this here rather than in the migration
// package s.t. the inputs to any default providers (which we fetch from the stacks's configuration) are as
// accurate as possible.
if err := addDefaultProviders(target, source, prev); err != nil {
return nil, err
}
var depGraph *graph.DependencyGraph
var oldResources []*resource.State
// 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 {
oldResources = prev.Resources
for _, oldres := range oldResources {
// Ignore resources that are pending deletion; these should not be recorded in the LUT.
if oldres.Delete {
continue
@ -101,7 +189,15 @@ func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source,
olds[urn] = oldres
}
depGraph = graph.NewDependencyGraph(prev.Resources)
depGraph = graph.NewDependencyGraph(oldResources)
}
// Create a new provider registry. Although we really only need to pass in any providers that were present in the
// old resource list, the registry itself will filter out other sorts of resources when processing the prior state,
// so we just pass all of the old resources.
reg, err := providers.NewRegistry(ctx.Host, oldResources, preview)
if err != nil {
return nil, err
}
return &Plan{
@ -113,7 +209,8 @@ func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source,
analyzers: analyzers,
preview: preview,
depGraph: depGraph,
}
providers: reg,
}, nil
}
func (p *Plan) Ctx() *plugin.Context { return p.ctx }
@ -128,10 +225,24 @@ func (p *Plan) SignalCancellation() error {
return p.ctx.Host.SignalCancellation()
}
// 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)
func (p *Plan) GetProvider(ref providers.Reference) (plugin.Provider, bool) {
return p.providers.GetProvider(ref)
}
// generateURN generates a resource's URN from its parent, type, and name under the scope of the plan's stack and
// project.
func (p *Plan) generateURN(parent resource.URN, ty tokens.Type, name tokens.QName) resource.URN {
// Use the resource goal state name to produce a globally unique URN.
parentType := tokens.Type("")
if parent != "" && parent.Type() != resource.RootStackType {
// Skip empty parents and don't use the root stack type; otherwise, use the full qualified type.
parentType = parent.QualifiedType()
}
return resource.NewURN(p.Target().Name, p.source.Project(), parentType, ty, name)
}
// defaultProviderURN generates the URN for the global provider given a package.
func defaultProviderURN(target *Target, source Source, pkg tokens.Package) resource.URN {
return resource.NewURN(target.Name, source.Project(), "", providers.MakeProviderType(pkg), "default")
}

View file

@ -0,0 +1,110 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package providers
import (
"strings"
"github.com/pkg/errors"
"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"
)
// A provider reference is (URN, ID) tuple that refers to a particular provider instance. A provider reference's
// string representation is <URN> "::" <ID>. The URN's type portion must be of the form "pulumi:providers:<pkg>".
// UnknownID is a distinguished token used to indicate that a provider's ID is not known (e.g. because we are
// performing a preview).
const UnknownID = plugin.UnknownStringValue
// IsProviderType returns true if the supplied type token refers to a Pulumi provider.
func IsProviderType(typ tokens.Type) bool {
return typ.Module() == "pulumi:providers" && typ.Name() != ""
}
// MakeProviderType returns the provider type token for the given package.
func MakeProviderType(pkg tokens.Package) tokens.Type {
return tokens.Type("pulumi:providers:" + pkg)
}
func getProviderPackage(typ tokens.Type) tokens.Package {
contract.Require(IsProviderType(typ), "typ")
return tokens.Package(typ.Name())
}
func validateURN(urn resource.URN) error {
typ := urn.Type()
if typ.Module() != "pulumi:providers" {
return errors.Errorf("invalid module in type: expected 'pulumi:providers', got '%v'", typ.Module())
}
if typ.Name() == "" {
return errors.New("provider URNs must specify a type name")
}
return nil
}
// Reference represents a reference to a particular provider.
type Reference struct {
urn resource.URN
id resource.ID
}
// URN returns the provider reference's URN.
func (r Reference) URN() resource.URN {
return r.urn
}
// ID returns the provider reference's ID.
func (r Reference) ID() resource.ID {
return r.id
}
// String returns the string representation of this provider reference.
func (r Reference) String() string {
return string(r.urn) + resource.URNNameDelimiter + string(r.id)
}
// NewReference creates a new reference for the given URN and ID.
func NewReference(urn resource.URN, id resource.ID) (Reference, error) {
if err := validateURN(urn); err != nil {
return Reference{}, err
}
return Reference{urn: urn, id: id}, nil
}
func mustNewReference(urn resource.URN, id resource.ID) Reference {
ref, err := NewReference(urn, id)
contract.Assert(err == nil)
return ref
}
// ParseReference parses the URN and ID from the string representation of a provider reference. If parsing was
// not possible, this function returns false.
func ParseReference(s string) (Reference, error) {
// If this is not a valid URN + ID, return false. Note that we don't try terribly hard to validate the URN portion
// of the reference.
lastSep := strings.LastIndex(s, resource.URNNameDelimiter)
if lastSep == -1 {
return Reference{}, errors.Errorf("expected '%v' in provider reference '%v'", resource.URNNameDelimiter, s)
}
urn, id := resource.URN(s[:lastSep]), resource.ID(s[lastSep+len(resource.URNNameDelimiter):])
if err := validateURN(urn); err != nil {
return Reference{}, err
}
return Reference{urn: urn, id: id}, nil
}

View file

@ -0,0 +1,76 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package providers
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/tokens"
)
func TestRoundTripProviderType(t *testing.T) {
pkg := tokens.Package("abcd")
assert.True(t, IsProviderType(MakeProviderType(pkg)))
}
func TestParseReferenceInvalidURN(t *testing.T) {
str := "not::a:valid:urn::id"
assert.Panics(t, func() { ParseReference(str) })
}
func TestParseReferenceInvalidModule(t *testing.T) {
// Wrong package and module
str := string(resource.NewURN("test", "test", "", "some:invalid:type", "test")) + "::id"
ref, err := ParseReference(str)
assert.Error(t, err)
assert.Equal(t, Reference{}, ref)
// Right package, wrong module
str = string(resource.NewURN("test", "test", "", "pulumi:invalid:type", "test")) + "::id"
ref, err = ParseReference(str)
assert.Error(t, err)
assert.Equal(t, Reference{}, ref)
// Right module, wrong package
str = string(resource.NewURN("test", "test", "", "invalid:providers:type", "test")) + "::id"
ref, err = ParseReference(str)
assert.Error(t, err)
assert.Equal(t, Reference{}, ref)
}
func TestParseReference(t *testing.T) {
urn, id := resource.NewURN("test", "test", "", "pulumi:providers:type", "test"), resource.ID("id")
ref, err := ParseReference(string(urn) + "::" + string(id))
assert.NoError(t, err)
assert.Equal(t, urn, ref.URN())
assert.Equal(t, id, ref.ID())
}
func TestReferenceString(t *testing.T) {
urn, id := resource.NewURN("test", "test", "", "pulumi:providers:type", "test"), resource.ID("id")
ref := Reference{urn: urn, id: id}
assert.Equal(t, string(urn)+"::"+string(id), ref.String())
}
func TestRoundTripReference(t *testing.T) {
str := string(resource.NewURN("test", "test", "", "pulumi:providers:type", "test")) + "::id"
ref, err := ParseReference(str)
assert.NoError(t, err)
assert.Equal(t, str, ref.String())
}

View file

@ -0,0 +1,364 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package providers
import (
"fmt"
"sync"
"github.com/blang/semver"
"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/util/logging"
"github.com/pulumi/pulumi/pkg/workspace"
)
// getProviderVersion fetches and parses a provider version from the given property map. If the version property is not
// present, this function returns nil.
func getProviderVersion(inputs resource.PropertyMap) (*semver.Version, error) {
versionProp, ok := inputs["version"]
if !ok {
return nil, nil
}
if !versionProp.IsString() {
return nil, errors.New("'version' must be a string")
}
sv, err := semver.ParseTolerant(versionProp.StringValue())
if err != nil {
return nil, errors.Errorf("could not parse provider version: %v", err)
}
return &sv, nil
}
// Registry manages the lifecylce of provider resources and their plugins and handles the resolution of provider
// references to loaded plugins.
//
// When a registry is created, it is handed the set of old provider resources that it will manage. Each provider
// resource in this set is loaded and configured as per its recorded inputs and registered under the provider
// reference that corresponds to its URN and ID, both of which must be known. At this point, the created registry is
// prepared to be used to manage the lifecycle of these providers as well as any new provider resources requested by
// invoking the registry's CRUD operations.
//
// In order to fit neatly in to the existing infrastructure for managing resources using Pulumi, a provider regidstry
// itself implements the plugin.Provider interface.
type Registry struct {
host plugin.Host
isPreview bool
providers map[Reference]plugin.Provider
m sync.RWMutex
}
var _ plugin.Provider = (*Registry)(nil)
// NewRegistry creates a new provider registry using the given host and old resources. Each provider present in the old
// resources will be loaded, configured, and added to the returned registry under its reference. If any provider is not
// loadable/configurable or has an invalid ID, this function returns an error.
func NewRegistry(host plugin.Host, prev []*resource.State, isPreview bool) (*Registry, error) {
r := &Registry{
host: host,
isPreview: isPreview,
providers: make(map[Reference]plugin.Provider),
}
for _, res := range prev {
urn := res.URN
if !IsProviderType(urn.Type()) {
logging.V(7).Infof("provider(%v): %v", urn, res.Provider)
continue
}
// Ensure that this provider has a known ID.
if res.ID == "" || res.ID == UnknownID {
return nil, errors.Errorf("provider '%v' has an unknown ID", urn)
}
// Ensure that we have no duplicates.
ref := mustNewReference(urn, res.ID)
if _, ok := r.providers[ref]; ok {
return nil, errors.Errorf("duplicate provider found in old state: '%v'", ref)
}
// Parse the provider version, then load, configure, and register the provider.
version, err := getProviderVersion(res.Inputs)
if err != nil {
return nil, errors.Errorf("could not parse version for provider '%v': %v", urn, err)
}
provider, err := host.Provider(getProviderPackage(urn.Type()), version)
if provider == nil {
return nil, errors.Errorf("could not find plugin for provider '%v'", urn)
}
if err != nil {
return nil, errors.Errorf("could not load plugin for provider '%v': %v", urn, err)
}
if err := provider.Configure(res.Inputs); err != nil {
closeErr := host.CloseProvider(provider)
contract.IgnoreError(closeErr)
return nil, errors.Errorf("could not configure provider '%v': %v", urn, err)
}
logging.V(7).Infof("loaded provider %v", ref)
r.providers[ref] = provider
}
return r, nil
}
// GetProvider returns the provider plugin that is currently registered under the given reference, if any.
func (r *Registry) GetProvider(ref Reference) (plugin.Provider, bool) {
r.m.RLock()
defer r.m.RUnlock()
logging.V(7).Infof("GetProvider(%v)", ref)
provider, ok := r.providers[ref]
return provider, ok
}
func (r *Registry) setProvider(ref Reference, provider plugin.Provider) {
r.m.Lock()
defer r.m.Unlock()
logging.V(7).Infof("setProvider(%v)", ref)
r.providers[ref] = provider
}
func (r *Registry) deleteProvider(ref Reference) (plugin.Provider, bool) {
r.m.Lock()
defer r.m.Unlock()
provider, ok := r.providers[ref]
if !ok {
return nil, false
}
delete(r.providers, ref)
return provider, true
}
// The rest of the methods below are the implementation of the plugin.Provider interface methods.
func (r *Registry) Close() error {
return nil
}
func (r *Registry) Pkg() tokens.Package {
return "pulumi"
}
func (r *Registry) label() string {
return "ProviderRegistry"
}
// CheckConfig validates the configuration for this resource provider.
func (r *Registry) CheckConfig(olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
contract.Fail()
return nil, nil, errors.New("the provider registry is not configurable")
}
// DiffConfig checks what impacts a hypothetical change to this provider's configuration will have on the provider.
func (r *Registry) DiffConfig(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
contract.Fail()
return plugin.DiffResult{}, errors.New("the provider registry is not configurable")
}
func (r *Registry) Configure(props resource.PropertyMap) error {
contract.Fail()
return errors.New("the provider registry is not configurable")
}
// Check validates the configuration for a particular provider resource.
//
// The particulars of Check are a bit subtle for a few reasons:
// - we need to load the provider for the package indicated by the type name portion provider resource's URN in order
// to check its config
// - we need to keep the newly-loaded provider around in case we need to diff its config
// - if we are running a preview, we need to configure the provider, as its corresponding CRUD operations will not run
// (we would normally configure the provider in Create or Update).
func (r *Registry) Check(urn resource.URN, olds, news resource.PropertyMap,
allowUnknowns bool) (resource.PropertyMap, []plugin.CheckFailure, error) {
contract.Require(IsProviderType(urn.Type()), "urn")
label := fmt.Sprintf("%s.Check(%s)", r.label(), urn)
logging.V(7).Infof("%s executing (#olds=%d,#news=%d", label, len(olds), len(news))
// Parse the version from the provider properties and load the provider.
version, err := getProviderVersion(news)
if err != nil {
return nil, []plugin.CheckFailure{{Property: "version", Reason: err.Error()}}, nil
}
provider, err := r.host.Provider(getProviderPackage(urn.Type()), version)
if provider == nil {
return nil, nil, errors.New("could not find plugin")
}
if err != nil {
return nil, nil, err
}
// Check the provider's config. If the check fails, unload the provider.
inputs, failures, err := provider.CheckConfig(olds, news)
if len(failures) != 0 || err != nil {
closeErr := r.host.CloseProvider(provider)
contract.IgnoreError(closeErr)
return nil, failures, err
}
// If we are running a preview, configure the provider now. If we are not running a preview, we will configure the
// provider when it is created or updated.
if r.isPreview {
if err := provider.Configure(inputs); err != nil {
closeErr := r.host.CloseProvider(provider)
contract.IgnoreError(closeErr)
return nil, nil, err
}
}
// Create a provider reference using the URN and the unknown ID and register the provider.
r.setProvider(mustNewReference(urn, UnknownID), provider)
return inputs, nil, nil
}
// Diff diffs the configuration of the indicated provider. The provider corresponding to the given URN must have
// previously been loaded by a call to Check.
func (r *Registry) Diff(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
allowUnknowns bool) (plugin.DiffResult, error) {
contract.Require(id != "", "id")
label := fmt.Sprintf("%s.Diff(%s,%s)", r.label(), urn, id)
logging.V(7).Infof("%s: executing (#olds=%d,#news=%d)", label, len(olds), len(news))
// Create a reference using the URN and the unknown ID and fetch the provider.
provider, ok := r.GetProvider(mustNewReference(urn, UnknownID))
contract.Assertf(ok, "'Check' must be called before 'Diff'")
// Diff the properties.
diff, err := provider.DiffConfig(olds, news)
if err != nil {
return plugin.DiffResult{Changes: plugin.DiffUnknown}, err
}
// If the diff requires replacement, unload the provider: the engine will reload it during its replacememnt Check.
//
// If the diff does not require replacement and we are running a preview, register it under its current ID so that
// references to the provider from other resources will resolve properly.
if len(diff.ReplaceKeys) != 0 {
closeErr := r.host.CloseProvider(provider)
contract.IgnoreError(closeErr)
} else if r.isPreview {
r.setProvider(mustNewReference(urn, id), provider)
}
return diff, nil
}
// Create coonfigures the provider with the given URN using the indicated configuration, assigns it an ID, and
// registers it under the assigned (URN, ID).
//
// The provider must have been loaded by a prior call to Check.
func (r *Registry) Create(urn resource.URN,
news resource.PropertyMap) (resource.ID, resource.PropertyMap, resource.Status, error) {
contract.Assert(!r.isPreview)
label := fmt.Sprintf("%s.Create(%s)", r.label(), urn)
logging.V(7).Infof("%s executing (#news=%v)", label, len(news))
// Fetch the unconfigured provider, configure it, and register it under a new ID.
provider, ok := r.GetProvider(mustNewReference(urn, UnknownID))
contract.Assertf(ok, "'Check' must be called before 'Create'")
if err := provider.Configure(news); err != nil {
return "", nil, resource.StatusOK, err
}
id := resource.ID(uuid.NewV4().String())
contract.Assert(id != UnknownID)
r.setProvider(mustNewReference(urn, id), provider)
return id, resource.PropertyMap{}, resource.StatusOK, nil
}
// Update configures the provider with the given URN and ID using the indicated configuration and registers it at the
// reference indicated by the (URN, ID) pair.
//
// THe provider must have been loaded by a prior call to Check.
func (r *Registry) Update(urn resource.URN, id resource.ID, olds,
news resource.PropertyMap) (resource.PropertyMap, resource.Status, error) {
contract.Assert(!r.isPreview)
label := fmt.Sprintf("%s.Update(%s,%s)", r.label(), id, urn)
logging.V(7).Infof("%s executing (#olds=%v,#news=%v)", label, len(olds), len(news))
// Fetch the unconfigured provider and configure it.
provider, ok := r.GetProvider(mustNewReference(urn, UnknownID))
contract.Assertf(ok, "'Check' and 'Diff' must be called before 'Update'")
if err := provider.Configure(news); err != nil {
return nil, resource.StatusUnknown, err
}
// Publish the configured provider.
r.setProvider(mustNewReference(urn, id), provider)
return resource.PropertyMap{}, resource.StatusOK, nil
}
// Delete unregisters and unloads the provider with the given URN and ID. The provider must have been loaded when the
// registry was created (i.e. it must have been present in the state handed to NewRegistry).
func (r *Registry) Delete(urn resource.URN, id resource.ID, props resource.PropertyMap) (resource.Status, error) {
contract.Assert(!r.isPreview)
ref := mustNewReference(urn, id)
provider, has := r.deleteProvider(ref)
contract.Assert(has)
closeErr := r.host.CloseProvider(provider)
contract.IgnoreError(closeErr)
return resource.StatusOK, nil
}
func (r *Registry) Read(urn resource.URN, id resource.ID,
props resource.PropertyMap) (resource.PropertyMap, error) {
return nil, errors.New("provider resources may not be read")
}
func (r *Registry) Invoke(tok tokens.ModuleMember,
args resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
// It is the responsibility of the eval source to ensure that we never attempt an invoke using the provider
// registry.
contract.Fail()
return nil, nil, errors.New("the provider registry is not invokable")
}
func (r *Registry) GetPluginInfo() (workspace.PluginInfo, error) {
// return an error: this should not be called for the provider registry
return workspace.PluginInfo{}, errors.New("the provider registry does not report plugin info")
}
func (r *Registry) SignalCancellation() error {
// At the moment there isn't anything reasonable we can do here. In the future, it might be nice to plumb
// cancellation through the plugin loader and cancel any outstanding load requests here.
return nil
}

View file

@ -0,0 +1,731 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package providers
import (
"testing"
"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"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/workspace"
)
type testPluginHost struct {
t *testing.T
provider func(pkg tokens.Package, version *semver.Version) (plugin.Provider, error)
closeProvider func(provider plugin.Provider) error
}
func (host *testPluginHost) SignalCancellation() error {
return nil
}
func (host *testPluginHost) Close() error {
return nil
}
func (host *testPluginHost) ServerAddr() string {
host.t.Fatalf("Host RPC address not available")
return ""
}
func (host *testPluginHost) Log(sev diag.Severity, urn resource.URN, msg string, streamID int32) {
host.t.Logf("[%v] %v@%v: %v", sev, urn, streamID, msg)
}
func (host *testPluginHost) Analyzer(nm tokens.QName) (plugin.Analyzer, error) {
return nil, errors.New("unsupported")
}
func (host *testPluginHost) Provider(pkg tokens.Package, version *semver.Version) (plugin.Provider, error) {
return host.provider(pkg, version)
}
func (host *testPluginHost) CloseProvider(provider plugin.Provider) error {
return host.closeProvider(provider)
}
func (host *testPluginHost) LanguageRuntime(runtime string) (plugin.LanguageRuntime, error) {
return nil, errors.New("unsupported")
}
func (host *testPluginHost) ListPlugins() []workspace.PluginInfo {
return nil
}
func (host *testPluginHost) EnsurePlugins(plugins []workspace.PluginInfo, kinds plugin.Flags) error {
return nil
}
func (host *testPluginHost) GetRequiredPlugins(info plugin.ProgInfo,
kinds plugin.Flags) ([]workspace.PluginInfo, error) {
return nil, nil
}
type testProvider struct {
pkg tokens.Package
version semver.Version
configured bool
checkConfig func(resource.PropertyMap, resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error)
diffConfig func(resource.PropertyMap, resource.PropertyMap) (plugin.DiffResult, error)
config func(resource.PropertyMap) error
}
func (prov *testProvider) SignalCancellation() error {
return nil
}
func (prov *testProvider) Close() error {
return nil
}
func (prov *testProvider) Pkg() tokens.Package {
return prov.pkg
}
func (prov *testProvider) CheckConfig(olds,
news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return prov.checkConfig(olds, news)
}
func (prov *testProvider) DiffConfig(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
return prov.diffConfig(olds, news)
}
func (prov *testProvider) Configure(inputs resource.PropertyMap) error {
if err := prov.config(inputs); err != nil {
return err
}
prov.configured = true
return nil
}
func (prov *testProvider) Check(urn resource.URN,
olds, news resource.PropertyMap, _ bool) (resource.PropertyMap, []plugin.CheckFailure, error) {
return nil, nil, errors.New("unsupported")
}
func (prov *testProvider) Create(urn resource.URN, props resource.PropertyMap) (resource.ID,
resource.PropertyMap, resource.Status, error) {
return "", nil, resource.StatusOK, errors.New("unsupported")
}
func (prov *testProvider) Read(urn resource.URN, id resource.ID,
props resource.PropertyMap) (resource.PropertyMap, error) {
return nil, errors.New("unsupported")
}
func (prov *testProvider) Diff(urn resource.URN, id resource.ID,
olds resource.PropertyMap, news resource.PropertyMap, _ bool) (plugin.DiffResult, error) {
return plugin.DiffResult{}, errors.New("unsupported")
}
func (prov *testProvider) Update(urn resource.URN, id resource.ID,
olds resource.PropertyMap, news resource.PropertyMap) (resource.PropertyMap, resource.Status, error) {
return nil, resource.StatusOK, errors.New("unsupported")
}
func (prov *testProvider) Delete(urn resource.URN,
id resource.ID, props resource.PropertyMap) (resource.Status, error) {
return resource.StatusOK, errors.New("unsupported")
}
func (prov *testProvider) Invoke(tok tokens.ModuleMember,
args resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return nil, nil, errors.New("unsupported")
}
func (prov *testProvider) GetPluginInfo() (workspace.PluginInfo, error) {
return workspace.PluginInfo{
Name: "testProvider",
Version: &prov.version,
}, nil
}
type providerLoader struct {
pkg tokens.Package
version semver.Version
load func() (plugin.Provider, error)
}
func newPluginHost(t *testing.T, loaders []*providerLoader) plugin.Host {
return &testPluginHost{
t: t,
provider: func(pkg tokens.Package, ver *semver.Version) (plugin.Provider, error) {
var best *providerLoader
for _, l := range loaders {
if l.pkg != pkg {
continue
}
if ver != nil && l.version.LT(*ver) {
continue
}
if best == nil || l.version.GT(best.version) {
best = l
}
}
if best == nil {
return nil, nil
}
return best.load()
},
closeProvider: func(provider plugin.Provider) error {
return nil
},
}
}
func newLoader(t *testing.T, pkg, version string,
load func(tokens.Package, semver.Version) (plugin.Provider, error)) *providerLoader {
var ver semver.Version
if version != "" {
v, err := semver.ParseTolerant(version)
assert.NoError(t, err)
ver = v
}
return &providerLoader{
pkg: tokens.Package(pkg),
version: ver,
load: func() (plugin.Provider, error) {
return load(tokens.Package(pkg), ver)
},
}
}
func newSimpleLoader(t *testing.T, pkg, version string, config func(resource.PropertyMap) error) *providerLoader {
if config == nil {
config = func(resource.PropertyMap) error {
return nil
}
}
return newLoader(t, pkg, version, func(pkg tokens.Package, ver semver.Version) (plugin.Provider, error) {
return &testProvider{
pkg: tokens.Package(pkg),
version: ver,
checkConfig: func(olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return news, nil, nil
},
diffConfig: func(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
return plugin.DiffResult{}, nil
},
config: config,
}, nil
})
}
func newProviderState(pkg, name, id string, delete bool, inputs resource.PropertyMap) *resource.State {
typ := MakeProviderType(tokens.Package(pkg))
urn := resource.NewURN("test", "test", "", typ, tokens.QName(name))
if inputs == nil {
inputs = resource.PropertyMap{}
}
return &resource.State{
Type: typ,
URN: urn,
Custom: true,
Delete: delete,
ID: resource.ID(id),
Inputs: inputs,
}
}
func TestNewRegistryNoOldState(t *testing.T) {
r, err := NewRegistry(&testPluginHost{}, nil, false)
assert.NoError(t, err)
assert.NotNil(t, r)
r, err = NewRegistry(&testPluginHost{}, nil, true)
assert.NoError(t, err)
assert.NotNil(t, r)
}
func TestNewRegistryOldState(t *testing.T) {
olds := []*resource.State{
// Two providers from package A, each with a unique name and ID
newProviderState("pkgA", "a", "id1", false, nil),
newProviderState("pkgA", "b", "id2", false, nil),
// Two providers from package B, each with a unique name and ID
newProviderState("pkgB", "a", "id1", false, nil),
newProviderState("pkgB", "b", "id2", false, nil),
// Two providers from package C, both with the same name but with unique IDs and one marked for deletion
newProviderState("pkgC", "a", "id1", false, nil),
newProviderState("pkgC", "a", "id2", true, nil),
// One provider from package D with a version
newProviderState("pkgD", "a", "id1", false, resource.PropertyMap{
"version": resource.NewStringProperty("1.0.0"),
}),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
newSimpleLoader(t, "pkgB", "", nil),
newSimpleLoader(t, "pkgC", "", nil),
newSimpleLoader(t, "pkgD", "1.0.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false)
assert.NoError(t, err)
assert.NotNil(t, r)
assert.Equal(t, len(olds), len(r.providers))
for _, old := range olds {
ref, err := NewReference(old.URN, old.ID)
assert.NoError(t, err)
p, ok := r.GetProvider(ref)
assert.True(t, ok)
assert.NotNil(t, p)
assert.True(t, p.(*testProvider).configured)
assert.Equal(t, getProviderPackage(old.Type), p.Pkg())
ver, err := getProviderVersion(old.Inputs)
assert.NoError(t, err)
if ver != nil {
info, err := p.GetPluginInfo()
assert.NoError(t, err)
assert.True(t, info.Version.GTE(*ver))
}
}
}
func TestNewRegistryOldStateNoProviders(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
}
host := newPluginHost(t, []*providerLoader{})
r, err := NewRegistry(host, olds, false)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateWrongPackage(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgB", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateWrongVersion(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, resource.PropertyMap{
"version": resource.NewStringProperty("1.0.0"),
}),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "0.5.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateNoID(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateUnknownID(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", UnknownID, false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestNewRegistryOldStateDuplicates(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
newProviderState("pkgA", "a", "id1", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false)
assert.Error(t, err)
assert.Nil(t, r)
}
func TestCRUD(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
newProviderState("pkgB", "a", "id1", false, nil),
newProviderState("pkgC", "a", "id1", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
newSimpleLoader(t, "pkgB", "", nil),
newSimpleLoader(t, "pkgC", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, false)
assert.NoError(t, err)
assert.NotNil(t, r)
assert.Equal(t, len(olds), len(r.providers))
for _, old := range olds {
ref, err := NewReference(old.URN, old.ID)
assert.NoError(t, err)
p, ok := r.GetProvider(ref)
assert.True(t, ok)
assert.NotNil(t, p)
assert.Equal(t, getProviderPackage(old.Type), p.Pkg())
}
// Create a new provider for each package.
for _, l := range loaders {
typ := MakeProviderType(l.pkg)
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is not a preview, the provider should not yet be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.False(t, p.(*testProvider).configured)
// Create
id, outs, status, err := r.Create(urn, inputs)
assert.NoError(t, err)
assert.NotEqual(t, "", id)
assert.NotEqual(t, UnknownID, id)
assert.Equal(t, resource.PropertyMap{}, outs)
assert.Equal(t, resource.StatusOK, status)
p2, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.Equal(t, p, p2)
assert.True(t, p2.(*testProvider).configured)
}
// Update the existing provider for the first entry in olds.
{
urn, id := olds[0].URN, olds[0].ID
olds, news := olds[0].Inputs, olds[0].Inputs
// Fetch the old provider instance.
old, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is not a preview, the provider should not yet be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.False(t, p == old)
assert.False(t, p.(*testProvider).configured)
// Diff
diff, err := r.Diff(urn, id, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, plugin.DiffResult{}, diff)
// The old provider should still be registered.
p2, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.Equal(t, old, p2)
// Update
outs, status, err := r.Update(urn, id, olds, inputs)
assert.NoError(t, err)
assert.Equal(t, resource.PropertyMap{}, outs)
assert.Equal(t, resource.StatusOK, status)
p3, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.True(t, p3 == p)
assert.True(t, p3.(*testProvider).configured)
}
// Delete the existingv provider for the last entry in olds.
{
urn, id := olds[len(olds)-1].URN, olds[len(olds)-1].ID
// Fetch the old provider instance.
_, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
// Delete
status, err := r.Delete(urn, id, resource.PropertyMap{})
assert.NoError(t, err)
assert.Equal(t, resource.StatusOK, status)
_, ok = r.GetProvider(Reference{urn: urn, id: id})
assert.False(t, ok)
}
}
func TestCRUDPreview(t *testing.T) {
olds := []*resource.State{
newProviderState("pkgA", "a", "id1", false, nil),
newProviderState("pkgB", "a", "id1", false, nil),
newProviderState("pkgC", "a", "id1", false, nil),
newProviderState("pkgD", "a", "id1", false, nil),
}
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "", nil),
newSimpleLoader(t, "pkgB", "", nil),
newSimpleLoader(t, "pkgC", "", nil),
newLoader(t, "pkgD", "", func(pkg tokens.Package, ver semver.Version) (plugin.Provider, error) {
return &testProvider{
pkg: tokens.Package(pkg),
version: ver,
checkConfig: func(olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
return news, nil, nil
},
diffConfig: func(olds, news resource.PropertyMap) (plugin.DiffResult, error) {
// Always reuquire replacement.
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"id"}}, nil
},
config: func(inputs resource.PropertyMap) error {
return nil
},
}, nil
}),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, olds, true)
assert.NoError(t, err)
assert.NotNil(t, r)
assert.Equal(t, len(olds), len(r.providers))
for _, old := range olds {
ref, err := NewReference(old.URN, old.ID)
assert.NoError(t, err)
p, ok := r.GetProvider(ref)
assert.True(t, ok)
assert.NotNil(t, p)
assert.Equal(t, getProviderPackage(old.Type), p.Pkg())
}
// Create a new provider for each package.
for _, l := range loaders {
typ := MakeProviderType(l.pkg)
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is a preview, the provider should be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.True(t, p.(*testProvider).configured)
}
// Update the existing provider for the first entry in olds.
{
urn, id := olds[0].URN, olds[0].ID
olds, news := olds[0].Inputs, olds[0].Inputs
// Fetch the old provider instance.
old, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is a preview, the provider should be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.False(t, p == old)
assert.True(t, p.(*testProvider).configured)
// Diff
diff, err := r.Diff(urn, id, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, plugin.DiffResult{}, diff)
// The new provider should be registered.
p2, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.False(t, p2 == old)
assert.True(t, p2 == p)
}
// Replace the existing provider for the last entry in olds.
{
urn, id := olds[len(olds)-1].URN, olds[len(olds)-1].ID
olds, news := olds[len(olds)-1].Inputs, olds[len(olds)-1].Inputs
// Fetch the old provider instance.
old, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Equal(t, news, inputs)
assert.Empty(t, failures)
// Since this is a preview, the provider should be configured.
p, ok := r.GetProvider(Reference{urn: urn, id: UnknownID})
assert.True(t, ok)
assert.False(t, p == old)
assert.True(t, p.(*testProvider).configured)
// Diff
diff, err := r.Diff(urn, id, olds, news, false)
assert.NoError(t, err)
assert.True(t, diff.Replace())
// The new provider should be not be registered; the registered provider should still be the original.
p2, ok := r.GetProvider(Reference{urn: urn, id: id})
assert.True(t, ok)
assert.True(t, p2 == old)
assert.False(t, p2 == p)
}
}
func TestCRUDNoProviders(t *testing.T) {
host := newPluginHost(t, []*providerLoader{})
r, err := NewRegistry(host, []*resource.State{}, false)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.Error(t, err)
assert.Empty(t, failures)
assert.Nil(t, inputs)
}
func TestCRUDWrongPackage(t *testing.T) {
loaders := []*providerLoader{
newSimpleLoader(t, "pkgB", "", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, []*resource.State{}, false)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.Error(t, err)
assert.Empty(t, failures)
assert.Nil(t, inputs)
}
func TestCRUDWrongVersion(t *testing.T) {
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "0.5.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, []*resource.State{}, false)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{"version": resource.NewStringProperty("1.0.0")}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.Error(t, err)
assert.Empty(t, failures)
assert.Nil(t, inputs)
}
func TestCRUDBadVersionNotString(t *testing.T) {
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "1.0.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, []*resource.State{}, false)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{"version": resource.NewBoolProperty(true)}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Len(t, failures, 1)
assert.Equal(t, "version", string(failures[0].Property))
assert.Nil(t, inputs)
}
func TestCRUDBadVersion(t *testing.T) {
loaders := []*providerLoader{
newSimpleLoader(t, "pkgA", "1.0.0", nil),
}
host := newPluginHost(t, loaders)
r, err := NewRegistry(host, []*resource.State{}, false)
assert.NoError(t, err)
assert.NotNil(t, r)
typ := MakeProviderType("pkgA")
urn := resource.NewURN("test", "test", "", typ, "b")
olds, news := resource.PropertyMap{}, resource.PropertyMap{"version": resource.NewStringProperty("foo")}
// Check
inputs, failures, err := r.Check(urn, olds, news, false)
assert.NoError(t, err)
assert.Len(t, failures, 1)
assert.Equal(t, "version", string(failures[0].Property))
assert.Nil(t, inputs)
}

View file

@ -22,6 +22,7 @@ import (
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/workspace"
)
@ -62,11 +63,13 @@ func NewSnapshot(manifest Manifest, resources []*resource.State) *Snapshot {
// VerifyIntegrity checks a snapshot to ensure it is well-formed. Because of the cost of this operation,
// integrity verification is only performed on demand, and not automatically during snapshot construction.
//
// This function enforces a couple of invariants:
// 1. Parents should always come before children in the resource list
// 2. Dependents should always come before their dependencies in the resource list
// 3. For every URN in the snapshot, there must be at most one resource with that URN that is not pending deletion
// 4. The magic manifest number should change every time the snapshot is mutated
// This function verifies a number of invariants:
// 1. Provider resources must be referenceable (i.e. they must have a valid URN and ID)
// 2. A resource's provider must precede the resource in the resource list
// 3. Parents must precede children in the resource list
// 4. Dependents must precede their dependencies in the resource list
// 5. For every URN in the snapshot, there must be at most one resource with that URN that is not pending deletion
// 6. The magic manifest number should change every time the snapshot is mutated
func (snap *Snapshot) VerifyIntegrity() error {
if snap != nil {
// Ensure the magic cookie checks out.
@ -77,8 +80,27 @@ func (snap *Snapshot) VerifyIntegrity() error {
// Now check the resources. For now, we just verify that parents come before children, and that there aren't
// any duplicate URNs.
urns := make(map[resource.URN]*resource.State)
provs := make(map[providers.Reference]struct{})
for i, state := range snap.Resources {
urn := state.URN
if providers.IsProviderType(state.Type) {
ref, err := providers.NewReference(urn, state.ID)
if err != nil {
return errors.Errorf("provider %s is not referenceable: %v", urn, err)
}
provs[ref] = struct{}{}
}
if provider := state.Provider; provider != "" {
ref, err := providers.ParseReference(provider)
if err != nil {
return errors.Errorf("failed to parse provider reference for resource %s: %v", urn, err)
}
if _, has := provs[ref]; !has {
return errors.Errorf("resource %s refers to unknown provider %s", urn, ref)
}
}
if par := state.Parent; par != "" {
if _, has := urns[par]; !has {
// The parent isn't there; to give a good error message, see whether it's missing entirely, or

View file

@ -18,9 +18,17 @@ import (
"io"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
)
// A ProviderSource allows a Source to lookup provider plugins.
type ProviderSource interface {
// GetProvider fetches the provider plugin for the given reference.
GetProvider(ref providers.Reference) (plugin.Provider, bool)
}
// A Source can generate a new set of resources that the planner will process accordingly.
type Source interface {
io.Closer
@ -33,8 +41,8 @@ type Source interface {
// be assumed to reflect existing state, or whether the events should acted upon (false).
IsRefresh() bool
// Iterate begins iterating the source. Error is non-nil upon failure; otherwise, a valid iterator is returned.
Iterate(opts Options) (SourceIterator, error)
// Iterate begins iterating the source. Error is non-nil upon failure; otherwise, a valid iterator is returned.
Iterate(opts Options, providers ProviderSource) (SourceIterator, error)
}
// A SourceIterator enumerates the list of resources that a source has to offer and tracks associated state.
@ -88,6 +96,8 @@ type ReadResourceEvent interface {
Name() tokens.QName
// Type is type of the resource being read.
Type() tokens.Type
// Provider is a reference to the provider instance to use for this read.
Provider() string
// Parent is the parent resource of the resource being read.
Parent() resource.URN
// Properties is the property bag that will be passed to Read as search parameters.

View file

@ -15,15 +15,17 @@
package deploy
import (
"context"
"fmt"
"github.com/blang/semver"
pbempty "github.com/golang/protobuf/ptypes/empty"
"github.com/pkg/errors"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
@ -46,18 +48,22 @@ type EvalRunInfo struct {
// NewEvalSource returns a planning source that fetches resources by evaluating a package with a set of args and
// a confgiuration map. This evaluation is performed using the given plugin context and may optionally use the
// given plugin host (or the default, if this is nil). Note that closing the eval source also closes the host.
func NewEvalSource(plugctx *plugin.Context, runinfo *EvalRunInfo, dryRun bool) Source {
func NewEvalSource(plugctx *plugin.Context, runinfo *EvalRunInfo,
defaultProviderVersions map[tokens.Package]*semver.Version, dryRun bool) Source {
return &evalSource{
plugctx: plugctx,
runinfo: runinfo,
dryRun: dryRun,
plugctx: plugctx,
runinfo: runinfo,
defaultProviderVersions: defaultProviderVersions,
dryRun: dryRun,
}
}
type evalSource struct {
plugctx *plugin.Context // the plugin context.
runinfo *EvalRunInfo // the directives to use when running the program.
dryRun bool // true if this is a dry-run operation only.
plugctx *plugin.Context // the plugin context.
runinfo *EvalRunInfo // the directives to use when running the program.
defaultProviderVersions map[tokens.Package]*semver.Version // the default provider versions for this source.
dryRun bool // true if this is a dry-run operation only.
}
func (src *evalSource) Close() error {
@ -78,12 +84,12 @@ func (src *evalSource) Info() interface{} { return src.runinfo }
func (src *evalSource) IsRefresh() bool { return false }
// Iterate will spawn an evaluator coroutine and prepare to interact with it on subsequent calls to Next.
func (src *evalSource) Iterate(opts Options) (SourceIterator, error) {
func (src *evalSource) Iterate(opts Options, providers ProviderSource) (SourceIterator, error) {
// First, fire up a resource monitor that will watch for and record resource creation.
regChan := make(chan *registerResourceEvent)
regOutChan := make(chan *registerResourceOutputsEvent)
regReadChan := make(chan *readResourceEvent)
mon, err := newResourceMonitor(src, regChan, regOutChan, regReadChan)
mon, err := newResourceMonitor(src, providers, regChan, regOutChan, regReadChan)
if err != nil {
return nil, errors.Wrap(err, "failed to start resource monitor")
}
@ -203,28 +209,176 @@ func (iter *evalSourceIterator) forkRun(opts Options) {
}()
}
// defaultProviders manages the registration of default providers. The default provider for a package is the provider
// resource that will be used to manage resources that do not explicitly reference a provider. Default providers will
// only be registered for packages that are used by resources registered by the user's Pulumi program.
type defaultProviders struct {
versions map[tokens.Package]*semver.Version
providers map[tokens.Package]providers.Reference
config plugin.ConfigSource
requests chan defaultProviderRequest
regChan chan<- *registerResourceEvent
cancel <-chan bool
}
type defaultProviderResponse struct {
ref providers.Reference
err error
}
type defaultProviderRequest struct {
pkg tokens.Package
response chan<- defaultProviderResponse
}
// newRegisterDefaultProviderEvent creates a RegisterResourceEvent and completion channel that can be sent to the
// engine to register a default provider resource for the indicated package.
func (d *defaultProviders) newRegisterDefaultProviderEvent(
pkg tokens.Package) (*registerResourceEvent, <-chan *RegisterResult, error) {
// Attempt to get the config for the package.
cfg, err := d.config.GetPackageConfig(pkg)
if err != nil {
return nil, nil, err
}
// Create the inputs for the provider resource.
inputs := make(resource.PropertyMap)
for k, v := range cfg {
inputs[resource.PropertyKey(k.Name())] = resource.NewStringProperty(v)
}
if version := d.versions[pkg]; version != nil {
inputs["version"] = resource.NewStringProperty(version.String())
}
// Create the result channel and the event.
done := make(chan *RegisterResult)
event := &registerResourceEvent{
goal: resource.NewGoal(providers.MakeProviderType(pkg), "default", true, inputs, "", false, nil, ""),
done: done,
}
return event, done, nil
}
// handleRequest services a single default provider request. If the request is for a default provider that we have
// already loaded, we will return its reference. If the request is for a default provider that has not yet been
// loaded, we will send a register resource request to the engine, wait for it to complete, and then cache and return
// the reference of the loaded provider.
//
// Note that this function must not be called from two goroutines concurrently; it is the responsibility of d.serve()
// to ensure this.
func (d *defaultProviders) handleRequest(pkg tokens.Package) (providers.Reference, error) {
logging.V(5).Infof("handling default provider request for package %s", pkg)
ref, ok := d.providers[pkg]
if ok {
return ref, nil
}
event, done, err := d.newRegisterDefaultProviderEvent(pkg)
if err != nil {
return providers.Reference{}, err
}
select {
case d.regChan <- event:
case <-d.cancel:
return providers.Reference{}, context.Canceled
}
logging.V(5).Infof("waiting for default provider for package %s", pkg)
var result *RegisterResult
select {
case result = <-done:
case <-d.cancel:
return providers.Reference{}, context.Canceled
}
logging.V(5).Infof("registered default provider for package %s: %s", pkg, result.State.URN)
id := result.State.ID
if id == "" {
id = providers.UnknownID
}
ref, err = providers.NewReference(result.State.URN, id)
contract.Assert(err == nil)
d.providers[pkg] = ref
return ref, nil
}
// serve is the primary loop responsible for handling default provider requests.
func (d *defaultProviders) serve() {
for {
select {
case req := <-d.requests:
// Note that we do not need to handle cancellation when sending the response: every message we receive is
// guaranteed to have something waiting on the other end of the response channel.
ref, err := d.handleRequest(req.pkg)
req.response <- defaultProviderResponse{ref: ref, err: err}
case <-d.cancel:
return
}
}
}
// getDefaultProviderRef fetches the provider reference for the default provider for a particular package.
func (d *defaultProviders) getDefaultProviderRef(pkg tokens.Package) (providers.Reference, error) {
contract.Assert(pkg != "pulumi")
response := make(chan defaultProviderResponse)
select {
case d.requests <- defaultProviderRequest{pkg: pkg, response: response}:
case <-d.cancel:
return providers.Reference{}, context.Canceled
}
res := <-response
return res.ref, res.err
}
// resmon implements the pulumirpc.ResourceMonitor interface and acts as the gateway between a language runtime's
// evaluation of a program and the internal resource planning and deployment logic.
type resmon struct {
src *evalSource // the evaluation source.
regChan chan *registerResourceEvent // the channel to send resource registrations to.
regOutChan chan *registerResourceOutputsEvent // the channel to send resource output registrations to.
regReadChan chan *readResourceEvent // the channel to send resource reads to.
addr string // the address the host is listening on.
cancel chan bool // a channel that can cancel the server.
done chan error // a channel that resolves when the server completes.
src *evalSource // the evaluation source.
providers ProviderSource // the provider source itself.
defaultProviders *defaultProviders // the default provider manager.
regChan chan *registerResourceEvent // the channel to send resource registrations to.
regOutChan chan *registerResourceOutputsEvent // the channel to send resource output registrations to.
regReadChan chan *readResourceEvent // the channel to send resource reads to.
addr string // the address the host is listening on.
cancel chan bool // a channel that can cancel the server.
done chan error // a channel that resolves when the server completes.
}
// newResourceMonitor creates a new resource monitor RPC server.
func newResourceMonitor(src *evalSource, regChan chan *registerResourceEvent,
func newResourceMonitor(src *evalSource, provs ProviderSource, regChan chan *registerResourceEvent,
regOutChan chan *registerResourceOutputsEvent, regReadChan chan *readResourceEvent) (*resmon, error) {
// Create our cancellation channel.
cancel := make(chan bool)
// Create a new default provider manager.
d := &defaultProviders{
versions: src.defaultProviderVersions,
providers: make(map[tokens.Package]providers.Reference),
config: src.runinfo.Target,
requests: make(chan defaultProviderRequest),
regChan: regChan,
cancel: cancel,
}
// New up an engine RPC server.
resmon := &resmon{
src: src,
regChan: regChan,
regOutChan: regOutChan,
regReadChan: regReadChan,
cancel: make(chan bool),
src: src,
providers: provs,
defaultProviders: d,
regChan: regChan,
regOutChan: regOutChan,
regReadChan: regReadChan,
cancel: cancel,
}
// Fire up a gRPC server and start listening for incomings.
@ -241,6 +395,8 @@ func newResourceMonitor(src *evalSource, regChan chan *registerResourceEvent,
resmon.addr = fmt.Sprintf("127.0.0.1:%d", port)
resmon.done = done
go d.serve()
return resmon, nil
}
@ -255,20 +411,56 @@ func (rm *resmon) Cancel() error {
return <-rm.done
}
// Invoke performs an invocation of a member located in a resource provider.
func (rm *resmon) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error) {
// Fetch the token and load up the resource provider.
// TODO: we should be flowing version information about this request, but instead, we'll bind to the latest.
tok := tokens.ModuleMember(req.GetTok())
prov, err := rm.src.plugctx.Host.Provider(tok.Package(), nil)
if err != nil {
return nil, err
} else if prov == nil {
return nil, errors.Errorf("could not load resource provider for package '%v' from $PATH", tok.Package())
// getProviderReference fetches the provider reference for a resource, read, or invoke from the given package with the
// given unparsed provider reference. If the unparsed provider reference is empty, this function returns a reference
// to the default provider for the indicated package.
func (rm *resmon) getProviderReference(pkg tokens.Package, rawProviderRef string) (providers.Reference, error) {
if pkg == "pulumi" {
return providers.Reference{}, errors.Errorf("cannot reference internal providers")
}
if rawProviderRef != "" {
ref, err := providers.ParseReference(rawProviderRef)
if err != nil {
return providers.Reference{}, errors.Errorf("could not parse provider reference: %v", err)
}
return ref, nil
}
ref, err := rm.defaultProviders.getDefaultProviderRef(pkg)
if err != nil {
return providers.Reference{}, err
}
return ref, nil
}
// getProvider fetches the provider plugin for a resource, read, or invoke from the given package with the given
// unparsed provider reference. If the unparsed provider reference is empty, this function returns the plugin for the
// indicated package's default provider.
func (rm *resmon) getProvider(pkg tokens.Package, rawProviderRef string) (plugin.Provider, error) {
providerRef, err := rm.getProviderReference(pkg, rawProviderRef)
if err != nil {
return nil, err
}
provider, ok := rm.providers.GetProvider(providerRef)
if !ok {
return nil, errors.Errorf("unknown provider '%v'", rawProviderRef)
}
return provider, nil
}
// Invoke performs an invocation of a member located in a resource provider.
func (rm *resmon) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) (*pulumirpc.InvokeResponse, error) {
// Fetch the token and load up the resource provider if necessary.
tok := tokens.ModuleMember(req.GetTok())
prov, err := rm.getProvider(tok.Package(), req.GetProvider())
if err != nil {
return nil, err
}
// Now unpack all of the arguments and prepare to perform the invocation.
label := fmt.Sprintf("ResourceMonitor.Invoke(%s)", tok)
args, err := plugin.UnmarshalProperties(
req.GetArgs(), plugin.MarshalOptions{Label: label, KeepUnknowns: true})
if err != nil {
@ -302,15 +494,18 @@ func (rm *resmon) ReadResource(ctx context.Context,
t := tokens.Type(req.GetType())
name := tokens.QName(req.GetName())
parent := resource.URN(req.GetParent())
prov, err := rm.src.plugctx.Host.Provider(t.Package(), nil)
if err != nil {
return nil, err
} else if prov == nil {
return nil, errors.Errorf("could not load resource provider for package '%v' from $PATH", t.Package())
provider := req.GetProvider()
if !providers.IsProviderType(t) && provider == "" {
ref, err := rm.defaultProviders.getDefaultProviderRef(t.Package())
if err != nil {
return nil, err
}
provider = ref.String()
}
id := resource.ID(req.GetId())
label := fmt.Sprintf("ResourceMonitor.ReadResource(%s, %s, %s)", id, t, name)
label := fmt.Sprintf("ResourceMonitor.ReadResource(%s, %s, %s, %s)", id, t, name, provider)
var deps []resource.URN
for _, depURN := range req.GetDependencies() {
deps = append(deps, resource.URN(depURN))
@ -328,6 +523,7 @@ func (rm *resmon) ReadResource(ctx context.Context,
id: id,
name: name,
baseType: t,
provider: provider,
parent: parent,
props: props,
dependencies: deps,
@ -376,6 +572,15 @@ func (rm *resmon) RegisterResource(ctx context.Context,
parent := resource.URN(req.GetParent())
protect := req.GetProtect()
provider := req.GetProvider()
if custom && !providers.IsProviderType(t) && provider == "" {
ref, err := rm.defaultProviders.getDefaultProviderRef(t.Package())
if err != nil {
return nil, err
}
provider = ref.String()
}
dependencies := []resource.URN{}
for _, dependingURN := range req.GetDependencies() {
dependencies = append(dependencies, resource.URN(dependingURN))
@ -388,12 +593,13 @@ func (rm *resmon) RegisterResource(ctx context.Context,
}
logging.V(5).Infof(
"ResourceMonitor.RegisterResource received: t=%v, name=%v, custom=%v, #props=%v, parent=%v, protect=%v, deps=%v",
t, name, custom, len(props), parent, protect, dependencies)
"ResourceMonitor.RegisterResource received: t=%v, name=%v, custom=%v, #props=%v, parent=%v, protect=%v, "+
"provider=%v, deps=%v",
t, name, custom, len(props), parent, protect, provider, dependencies)
// Send the goal state to the engine.
step := &registerResourceEvent{
goal: resource.NewGoal(t, name, custom, props, parent, protect, dependencies),
goal: resource.NewGoal(t, name, custom, props, parent, protect, dependencies, provider),
done: make(chan *RegisterResult),
}
@ -529,6 +735,7 @@ type readResourceEvent struct {
id resource.ID
name tokens.QName
baseType tokens.Type
provider string
parent resource.URN
props resource.PropertyMap
dependencies []resource.URN
@ -542,6 +749,7 @@ func (g *readResourceEvent) event() {}
func (g *readResourceEvent) ID() resource.ID { return g.id }
func (g *readResourceEvent) Name() tokens.QName { return g.name }
func (g *readResourceEvent) Type() tokens.Type { return g.baseType }
func (g *readResourceEvent) Provider() string { return g.provider }
func (g *readResourceEvent) Parent() resource.URN { return g.parent }
func (g *readResourceEvent) Properties() resource.PropertyMap { return g.props }
func (g *readResourceEvent) Dependencies() []resource.URN { return g.dependencies }

View file

@ -0,0 +1,475 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deploy
import (
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/deploytest"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/workspace"
)
type testRegEvent struct {
goal *resource.Goal
result *RegisterResult
}
var _ RegisterResourceEvent = (*testRegEvent)(nil)
func (g *testRegEvent) event() {}
func (g *testRegEvent) Goal() *resource.Goal {
return g.goal
}
func (g *testRegEvent) Done(result *RegisterResult) {
contract.Assertf(g.result == nil, "Attempt to invoke testRegEvent.Done more than once")
g.result = result
}
func fixedProgram(steps []RegisterResourceEvent) deploytest.ProgramFunc {
return func(_ plugin.RunInfo, resmon *deploytest.ResourceMonitor) error {
for _, s := range steps {
g := s.Goal()
urn, id, outs, err := resmon.RegisterResource(g.Type, string(g.Name), g.Custom, g.Parent, g.Protect,
g.Dependencies, g.Provider, g.Properties)
if err != nil {
return err
}
s.Done(&RegisterResult{
State: resource.NewState(g.Type, urn, g.Custom, false, id, g.Properties, outs, g.Parent, g.Protect,
false, g.Dependencies, nil, g.Provider),
})
}
return nil
}
}
func newTestPluginContext(program deploytest.ProgramFunc) (*plugin.Context, error) {
sink := cmdutil.Diag()
lang := deploytest.NewLanguageRuntime(program)
host := deploytest.NewPluginHost(sink, lang)
return plugin.NewContext(sink, host, nil, nil, "", nil, nil)
}
type testProviderSource struct {
providers map[providers.Reference]plugin.Provider
m sync.RWMutex
}
func (s *testProviderSource) registerProvider(ref providers.Reference, provider plugin.Provider) {
s.m.Lock()
defer s.m.Unlock()
s.providers[ref] = provider
}
func (s *testProviderSource) GetProvider(ref providers.Reference) (plugin.Provider, bool) {
s.m.RLock()
defer s.m.RUnlock()
provider, ok := s.providers[ref]
return provider, ok
}
func newProviderEvent(pkg, name string, inputs resource.PropertyMap, parent resource.URN) RegisterResourceEvent {
if inputs == nil {
inputs = resource.PropertyMap{}
}
goal := &resource.Goal{
Type: providers.MakeProviderType(tokens.Package(pkg)),
Name: tokens.QName(name),
Custom: true,
Properties: inputs,
Parent: parent,
}
return &testRegEvent{goal: goal}
}
func TestRegisterNoDefaultProviders(t *testing.T) {
runInfo := &EvalRunInfo{
Proj: &workspace.Project{Name: "test"},
Target: &Target{Name: "test"},
}
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
var pt tokens.Type
if parent != "" {
pt = parent.Type()
}
return resource.NewURN(runInfo.Target.Name, runInfo.Proj.Name, pt, t, tokens.QName(name))
}
newProviderURN := func(pkg tokens.Package, name string, parent resource.URN) resource.URN {
return newURN(providers.MakeProviderType(pkg), name, parent)
}
componentURN := newURN("component", "component", "")
providerARef, err := providers.NewReference(newProviderURN("pkgA", "providerA", ""), "id1")
assert.NoError(t, err)
providerBRef, err := providers.NewReference(newProviderURN("pkgA", "providerB", componentURN), "id2")
assert.NoError(t, err)
providerCRef, err := providers.NewReference(newProviderURN("pkgC", "providerC", ""), "id1")
assert.NoError(t, err)
steps := []RegisterResourceEvent{
// Register a provider.
newProviderEvent("pkgA", "providerA", nil, ""),
// Register a component resource.
&testRegEvent{
goal: resource.NewGoal(componentURN.Type(), componentURN.Name(), false, resource.PropertyMap{}, "", false,
nil, ""),
},
// Register a couple resources using provider A.
&testRegEvent{
goal: resource.NewGoal("pkgA:index:typA", "res1", true, resource.PropertyMap{}, componentURN, false, nil,
providerARef.String()),
},
&testRegEvent{
goal: resource.NewGoal("pkgA:index:typA", "res2", true, resource.PropertyMap{}, componentURN, false, nil,
providerARef.String()),
},
// Register two more providers.
newProviderEvent("pkgA", "providerB", nil, ""),
newProviderEvent("pkgC", "providerC", nil, componentURN),
// Register a few resources that use the new providers.
&testRegEvent{
goal: resource.NewGoal("pkgB:index:typB", "res3", true, resource.PropertyMap{}, "", false, nil,
providerBRef.String()),
},
&testRegEvent{
goal: resource.NewGoal("pkgB:index:typC", "res4", true, resource.PropertyMap{}, "", false, nil,
providerCRef.String()),
},
}
// Create and iterate an eval source.
ctx, err := newTestPluginContext(fixedProgram(steps))
assert.NoError(t, err)
iter, err := NewEvalSource(ctx, runInfo, nil, false).Iterate(Options{}, &testProviderSource{})
assert.NoError(t, err)
processed := 0
for {
event, err := iter.Next()
assert.NoError(t, err)
if event == nil {
break
}
reg := event.(RegisterResourceEvent)
goal := reg.Goal()
if providers.IsProviderType(goal.Type) {
assert.NotEqual(t, "default", goal.Name)
}
urn := newURN(goal.Type, string(goal.Name), goal.Parent)
id := resource.ID("")
if goal.Custom {
id = "id"
}
reg.Done(&RegisterResult{
State: resource.NewState(goal.Type, urn, goal.Custom, false, id, goal.Properties, resource.PropertyMap{},
goal.Parent, goal.Protect, false, goal.Dependencies, nil, goal.Provider),
})
processed++
}
assert.Equal(t, len(steps), processed)
}
func TestRegisterDefaultProviders(t *testing.T) {
runInfo := &EvalRunInfo{
Proj: &workspace.Project{Name: "test"},
Target: &Target{Name: "test"},
}
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
var pt tokens.Type
if parent != "" {
pt = parent.Type()
}
return resource.NewURN(runInfo.Target.Name, runInfo.Proj.Name, pt, t, tokens.QName(name))
}
componentURN := newURN("component", "component", "")
steps := []RegisterResourceEvent{
// Register a component resource.
&testRegEvent{
goal: resource.NewGoal(componentURN.Type(), componentURN.Name(), false, resource.PropertyMap{}, "", false,
nil, ""),
},
// Register a couple resources from package A.
&testRegEvent{
goal: resource.NewGoal("pkgA:m:typA", "res1", true, resource.PropertyMap{}, componentURN, false, nil, ""),
},
&testRegEvent{
goal: resource.NewGoal("pkgA:m:typA", "res2", true, resource.PropertyMap{}, componentURN, false, nil, ""),
},
// Register a few resources from other packages.
&testRegEvent{
goal: resource.NewGoal("pkgB:m:typB", "res3", true, resource.PropertyMap{}, "", false, nil, ""),
},
&testRegEvent{
goal: resource.NewGoal("pkgB:m:typC", "res4", true, resource.PropertyMap{}, "", false, nil, ""),
},
}
// Create and iterate an eval source.
ctx, err := newTestPluginContext(fixedProgram(steps))
assert.NoError(t, err)
iter, err := NewEvalSource(ctx, runInfo, nil, false).Iterate(Options{}, &testProviderSource{})
assert.NoError(t, err)
processed, defaults := 0, make(map[string]struct{})
for {
event, err := iter.Next()
assert.NoError(t, err)
if event == nil {
break
}
reg := event.(RegisterResourceEvent)
goal := reg.Goal()
urn := newURN(goal.Type, string(goal.Name), goal.Parent)
id := resource.ID("")
if goal.Custom {
id = "id"
}
if providers.IsProviderType(goal.Type) {
assert.Equal(t, "default", string(goal.Name))
ref, err := providers.NewReference(urn, id)
assert.NoError(t, err)
_, ok := defaults[ref.String()]
assert.False(t, ok)
defaults[ref.String()] = struct{}{}
} else if goal.Custom {
assert.NotEqual(t, "", goal.Provider)
_, ok := defaults[goal.Provider]
assert.True(t, ok)
}
reg.Done(&RegisterResult{
State: resource.NewState(goal.Type, urn, goal.Custom, false, id, goal.Properties, resource.PropertyMap{},
goal.Parent, goal.Protect, false, goal.Dependencies, nil, goal.Provider),
})
processed++
}
assert.Equal(t, len(steps)+len(defaults), processed)
}
func TestReadInvokeNoDefaultProviders(t *testing.T) {
runInfo := &EvalRunInfo{
Proj: &workspace.Project{Name: "test"},
Target: &Target{Name: "test"},
}
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
var pt tokens.Type
if parent != "" {
pt = parent.Type()
}
return resource.NewURN(runInfo.Target.Name, runInfo.Proj.Name, pt, t, tokens.QName(name))
}
newProviderURN := func(pkg tokens.Package, name string, parent resource.URN) resource.URN {
return newURN(providers.MakeProviderType(pkg), name, parent)
}
providerARef, err := providers.NewReference(newProviderURN("pkgA", "providerA", ""), "id1")
assert.NoError(t, err)
providerBRef, err := providers.NewReference(newProviderURN("pkgA", "providerB", ""), "id2")
assert.NoError(t, err)
providerCRef, err := providers.NewReference(newProviderURN("pkgC", "providerC", ""), "id1")
assert.NoError(t, err)
invokes := int32(0)
noopProvider := &deploytest.Provider{
InvokeF: func(tokens.ModuleMember, resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
atomic.AddInt32(&invokes, 1)
return resource.PropertyMap{}, nil, nil
},
}
providerSource := &testProviderSource{
providers: map[providers.Reference]plugin.Provider{
providerARef: noopProvider,
providerBRef: noopProvider,
providerCRef: noopProvider,
},
}
expectedReads, expectedInvokes := 3, 3
program := func(_ plugin.RunInfo, resmon *deploytest.ResourceMonitor) error {
// Perform some reads and invokes with explicit provider references.
_, _, perr := resmon.ReadResource("pkgA:m:typA", "resA", "id1", "", nil, providerARef.String())
assert.NoError(t, perr)
_, _, perr = resmon.ReadResource("pkgA:m:typB", "resB", "id1", "", nil, providerBRef.String())
assert.NoError(t, perr)
_, _, perr = resmon.ReadResource("pkgC:m:typC", "resC", "id1", "", nil, providerCRef.String())
assert.NoError(t, perr)
_, _, perr = resmon.Invoke("pkgA:m:funcA", nil, providerARef.String())
assert.NoError(t, perr)
_, _, perr = resmon.Invoke("pkgA:m:funcB", nil, providerBRef.String())
assert.NoError(t, perr)
_, _, perr = resmon.Invoke("pkgC:m:funcC", nil, providerCRef.String())
assert.NoError(t, perr)
return nil
}
// Create and iterate an eval source.
ctx, err := newTestPluginContext(program)
assert.NoError(t, err)
iter, err := NewEvalSource(ctx, runInfo, nil, false).Iterate(Options{}, providerSource)
assert.NoError(t, err)
reads := 0
for {
event, err := iter.Next()
assert.NoError(t, err)
if event == nil {
break
}
read := event.(ReadResourceEvent)
urn := newURN(read.Type(), string(read.Name()), read.Parent())
read.Done(&ReadResult{
State: resource.NewState(read.Type(), urn, true, false, read.ID(), read.Properties(),
resource.PropertyMap{}, read.Parent(), false, false, read.Dependencies(), nil, read.Provider()),
})
reads++
}
assert.Equal(t, expectedReads, reads)
assert.Equal(t, expectedInvokes, int(invokes))
}
func TestReadInvokeDefaultProviders(t *testing.T) {
runInfo := &EvalRunInfo{
Proj: &workspace.Project{Name: "test"},
Target: &Target{Name: "test"},
}
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
var pt tokens.Type
if parent != "" {
pt = parent.Type()
}
return resource.NewURN(runInfo.Target.Name, runInfo.Proj.Name, pt, t, tokens.QName(name))
}
invokes := int32(0)
noopProvider := &deploytest.Provider{
InvokeF: func(tokens.ModuleMember, resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
atomic.AddInt32(&invokes, 1)
return resource.PropertyMap{}, nil, nil
},
}
expectedReads, expectedInvokes := 3, 3
program := func(_ plugin.RunInfo, resmon *deploytest.ResourceMonitor) error {
// Perform some reads and invokes with default provider references.
_, _, err := resmon.ReadResource("pkgA:m:typA", "resA", "id1", "", nil, "")
assert.NoError(t, err)
_, _, err = resmon.ReadResource("pkgA:m:typB", "resB", "id1", "", nil, "")
assert.NoError(t, err)
_, _, err = resmon.ReadResource("pkgC:m:typC", "resC", "id1", "", nil, "")
assert.NoError(t, err)
_, _, err = resmon.Invoke("pkgA:m:funcA", nil, "")
assert.NoError(t, err)
_, _, err = resmon.Invoke("pkgA:m:funcB", nil, "")
assert.NoError(t, err)
_, _, err = resmon.Invoke("pkgC:m:funcC", nil, "")
assert.NoError(t, err)
return nil
}
// Create and iterate an eval source.
ctx, err := newTestPluginContext(program)
assert.NoError(t, err)
providerSource := &testProviderSource{providers: make(map[providers.Reference]plugin.Provider)}
iter, err := NewEvalSource(ctx, runInfo, nil, false).Iterate(Options{}, providerSource)
assert.NoError(t, err)
reads, registers := 0, 0
for {
event, err := iter.Next()
assert.NoError(t, err)
if event == nil {
break
}
switch e := event.(type) {
case RegisterResourceEvent:
goal := e.Goal()
urn, id := newURN(goal.Type, string(goal.Name), goal.Parent), resource.ID("id")
assert.True(t, providers.IsProviderType(goal.Type))
assert.Equal(t, "default", string(goal.Name))
ref, err := providers.NewReference(urn, id)
assert.NoError(t, err)
_, ok := providerSource.GetProvider(ref)
assert.False(t, ok)
providerSource.registerProvider(ref, noopProvider)
e.Done(&RegisterResult{
State: resource.NewState(goal.Type, urn, goal.Custom, false, id, goal.Properties, resource.PropertyMap{},
goal.Parent, goal.Protect, false, goal.Dependencies, nil, goal.Provider),
})
registers++
case ReadResourceEvent:
urn := newURN(e.Type(), string(e.Name()), e.Parent())
e.Done(&ReadResult{
State: resource.NewState(e.Type(), urn, true, false, e.ID(), e.Properties(),
resource.PropertyMap{}, e.Parent(), false, false, e.Dependencies(), nil, e.Provider()),
})
reads++
}
}
assert.Equal(t, len(providerSource.providers), registers)
assert.Equal(t, expectedReads, int(reads))
assert.Equal(t, expectedInvokes, int(invokes))
}

View file

@ -34,7 +34,7 @@ func (src *fixedSource) Project() tokens.PackageName { return src.ctx }
func (src *fixedSource) Info() interface{} { return nil }
func (src *fixedSource) IsRefresh() bool { return false }
func (src *fixedSource) Iterate(opts Options) (SourceIterator, error) {
func (src *fixedSource) Iterate(opts Options, providers ProviderSource) (SourceIterator, error) {
return &fixedSourceIterator{
src: src,
current: -1,

View file

@ -31,7 +31,7 @@ func (src *nullSource) Project() tokens.PackageName { return "" }
func (src *nullSource) Info() interface{} { return nil }
func (src *nullSource) IsRefresh() bool { return false }
func (src *nullSource) Iterate(opts Options) (SourceIterator, error) {
func (src *nullSource) Iterate(opts Options, providers ProviderSource) (SourceIterator, error) {
return &nullSourceIterator{}, nil
}

View file

@ -18,6 +18,7 @@ import (
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/workspace"
@ -47,23 +48,28 @@ func (src *refreshSource) Project() tokens.PackageName { return src.proj.Name }
func (src *refreshSource) Info() interface{} { return nil }
func (src *refreshSource) IsRefresh() bool { return true }
func (src *refreshSource) Iterate(opts Options) (SourceIterator, error) {
func (src *refreshSource) Iterate(opts Options, provs ProviderSource) (SourceIterator, error) {
var states []*resource.State
if snap := src.target.Snapshot; snap != nil {
states = snap.Resources
}
return &refreshSourceIterator{
plugctx: src.plugctx,
states: states,
current: -1,
plugctx: src.plugctx,
target: src.target,
providers: provs,
states: states,
current: -1,
}, nil
}
// refreshSourceIterator returns state from an existing snapshot, augmented by consulting the resource provider.
type refreshSourceIterator struct {
plugctx *plugin.Context
states []*resource.State
current int
plugctx *plugin.Context
target *Target
providers ProviderSource
states []*resource.State
current int
}
func (iter *refreshSourceIterator) Close() error {
@ -89,10 +95,14 @@ func (iter *refreshSourceIterator) Next() (SourceEvent, error) {
// newRefreshGoal refreshes the state, if appropriate, and returns a new goal state.
func (iter *refreshSourceIterator) newRefreshGoal(s *resource.State) (*resource.Goal, error) {
// If this is a custom resource, go ahead and load up its plugin, and ask it to refresh the state.
if s.Custom {
provider, err := iter.plugctx.Host.Provider(s.Type.Package(), nil)
if s.Custom && !providers.IsProviderType(s.Type) {
providerRef, err := providers.ParseReference(s.Provider)
if err != nil {
return nil, errors.Wrapf(err, "fetching provider to refresh %s", s.URN)
return nil, err
}
provider, ok := iter.providers.GetProvider(providerRef)
if !ok {
return nil, errors.Errorf("unknown provider '%v' for resource '%v'", s.Provider, s.URN)
}
refreshed, err := provider.Read(s.URN, s.ID, s.Outputs)
if err != nil {
@ -102,11 +112,12 @@ func (iter *refreshSourceIterator) newRefreshGoal(s *resource.State) (*resource.
}
s = resource.NewState(
s.Type, s.URN, s.Custom, s.Delete, s.ID, s.Inputs, refreshed,
s.Parent, s.Protect, s.External, s.Dependencies, s.InitErrors)
s.Parent, s.Protect, s.External, s.Dependencies, s.InitErrors, s.Provider)
}
// Now just return the actual state as the goal state.
return resource.NewGoal(s.Type, s.URN.Name(), s.Custom, s.Outputs, s.Parent, s.Protect, s.Dependencies), nil
return resource.NewGoal(s.Type, s.URN.Name(), s.Custom, s.Outputs, s.Parent, s.Protect, s.Dependencies,
s.Provider), nil
}
type refreshSourceEvent struct {

View file

@ -0,0 +1,138 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package deploy
import (
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/deploytest"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/workspace"
)
func TestRefresh(t *testing.T) {
proj := &workspace.Project{Name: "test"}
target := &Target{Name: "test"}
newURN := func(t tokens.Type, name string, parent resource.URN) resource.URN {
var pt tokens.Type
if parent != "" {
pt = parent.Type()
}
return resource.NewURN(target.Name, proj.Name, pt, t, tokens.QName(name))
}
newProviderURN := func(pkg tokens.Package, name string, parent resource.URN) resource.URN {
return newURN(providers.MakeProviderType(pkg), name, parent)
}
componentURN := newURN("component", "component", "")
resAURN := newURN("pkgA:m:typA", "resA", "")
resBURN := newURN("pkgA:m:typB", "resB", "")
resCURN := newURN("pkgC:m:typC", "resC", "")
providerARef, err := providers.NewReference(newProviderURN("pkgA", "providerA", ""), "id1")
assert.NoError(t, err)
providerBRef, err := providers.NewReference(newProviderURN("pkgA", "providerB", componentURN), "id2")
assert.NoError(t, err)
providerCRef, err := providers.NewReference(newProviderURN("pkgC", "providerC", ""), "id1")
assert.NoError(t, err)
newProviderState := func(ref providers.Reference) *resource.State {
return &resource.State{
Type: ref.URN().Type(),
URN: ref.URN(),
Custom: true,
ID: ref.ID(),
Inputs: resource.PropertyMap{},
}
}
newState := func(urn resource.URN, id resource.ID, provider string) *resource.State {
custom := id != ""
return &resource.State{
Type: urn.Type(),
URN: urn,
Custom: custom,
ID: id,
Provider: provider,
Inputs: resource.PropertyMap{},
}
}
reads := int32(0)
noopProvider := &deploytest.Provider{
ReadF: func(resource.URN, resource.ID, resource.PropertyMap) (resource.PropertyMap, error) {
atomic.AddInt32(&reads, 1)
return resource.PropertyMap{}, nil
},
}
providerSource := &testProviderSource{
providers: map[providers.Reference]plugin.Provider{
providerARef: noopProvider,
providerBRef: noopProvider,
providerCRef: noopProvider,
},
}
olds := []*resource.State{
// One top-level provider from package A
newProviderState(providerARef),
// One component resource
newState(componentURN, "", ""),
// One nested provider from package A
newProviderState(providerBRef),
// One resource referencing provider A
newState(resAURN, "id1", providerARef.String()),
// One resource referencing provider B
newState(resBURN, "id2", providerBRef.String()),
// A top-level provider from package C
newProviderState(providerCRef),
// One resource refernecing provider C
newState(resCURN, "id3", providerCRef.String()),
}
target.Snapshot = &Snapshot{Resources: olds}
// Create and iterate a source.
iter, err := NewRefreshSource(nil, proj, target, false).Iterate(Options{}, providerSource)
assert.NoError(t, err)
processed := 0
for {
event, err := iter.Next()
assert.NoError(t, err)
if event == nil {
break
}
processed++
}
assert.Equal(t, len(olds), processed)
expectedRead := 0
for _, s := range olds {
if s.Custom && !providers.IsProviderType(s.Type) {
expectedRead++
}
}
assert.Equal(t, expectedRead, int(reads))
}

View file

@ -19,6 +19,7 @@ import (
"github.com/pulumi/pulumi/pkg/diag/colors"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
@ -31,6 +32,7 @@ type Step interface {
Op() StepOp // the operation performed by this step.
URN() resource.URN // the resource URN (for before and after).
Type() tokens.Type // the type affected by this step.
Provider() string // the provider reference for this step.
Old() *resource.State // the state of the resource before performing this step.
New() *resource.State // the state of the resource after performing this step.
Res() *resource.State // the latest state for the resource that is known (worst case, old).
@ -52,6 +54,7 @@ func NewSameStep(plan *Plan, reg RegisterResourceEvent, old *resource.State, new
contract.Assert(old != nil)
contract.Assert(old.URN != "")
contract.Assert(old.ID != "" || !old.Custom)
contract.Assert(!old.Custom || old.Provider != "" || providers.IsProviderType(old.Type))
contract.Assert(!old.Delete)
contract.Assert(new != nil)
contract.Assert(new.URN != "")
@ -68,6 +71,7 @@ func NewSameStep(plan *Plan, reg RegisterResourceEvent, old *resource.State, new
func (s *SameStep) Op() StepOp { return OpSame }
func (s *SameStep) Plan() *Plan { return s.plan }
func (s *SameStep) Type() tokens.Type { return s.old.Type }
func (s *SameStep) Provider() string { return s.old.Provider }
func (s *SameStep) URN() resource.URN { return s.old.URN }
func (s *SameStep) Old() *resource.State { return s.old }
func (s *SameStep) New() *resource.State { return s.new }
@ -101,6 +105,7 @@ func NewCreateStep(plan *Plan, reg RegisterResourceEvent, new *resource.State) S
contract.Assert(new != nil)
contract.Assert(new.URN != "")
contract.Assert(new.ID == "")
contract.Assert(!new.Custom || new.Provider != "" || providers.IsProviderType(new.Type))
contract.Assert(!new.Delete)
contract.Assert(!new.External)
return &CreateStep{
@ -120,6 +125,7 @@ func NewCreateReplacementStep(plan *Plan, reg RegisterResourceEvent,
contract.Assert(new != nil)
contract.Assert(new.URN != "")
contract.Assert(new.ID == "")
contract.Assert(!new.Custom || new.Provider != "" || providers.IsProviderType(new.Type))
contract.Assert(!new.Delete)
contract.Assert(old.Type == new.Type)
contract.Assert(!new.External)
@ -142,6 +148,7 @@ func (s *CreateStep) Op() StepOp {
}
func (s *CreateStep) Plan() *Plan { return s.plan }
func (s *CreateStep) Type() tokens.Type { return s.new.Type }
func (s *CreateStep) Provider() string { return s.new.Provider }
func (s *CreateStep) URN() resource.URN { return s.new.URN }
func (s *CreateStep) Old() *resource.State { return s.old }
func (s *CreateStep) New() *resource.State { return s.new }
@ -207,6 +214,7 @@ func NewDeleteStep(plan *Plan, old *resource.State) Step {
contract.Assert(old != nil)
contract.Assert(old.URN != "")
contract.Assert(old.ID != "" || !old.Custom)
contract.Assert(!old.Custom || old.Provider != "" || providers.IsProviderType(old.Type))
contract.Assert(!old.Delete)
return &DeleteStep{
plan: plan,
@ -218,6 +226,7 @@ func NewDeleteReplacementStep(plan *Plan, old *resource.State, pendingDelete boo
contract.Assert(old != nil)
contract.Assert(old.URN != "")
contract.Assert(old.ID != "" || !old.Custom)
contract.Assert(!old.Custom || old.Provider != "" || providers.IsProviderType(old.Type))
contract.Assert(!pendingDelete || old.Delete)
return &DeleteStep{
plan: plan,
@ -234,6 +243,7 @@ func (s *DeleteStep) Op() StepOp {
}
func (s *DeleteStep) Plan() *Plan { return s.plan }
func (s *DeleteStep) Type() tokens.Type { return s.old.Type }
func (s *DeleteStep) Provider() string { return s.old.Provider }
func (s *DeleteStep) URN() resource.URN { return s.old.URN }
func (s *DeleteStep) Old() *resource.State { return s.old }
func (s *DeleteStep) New() *resource.State { return nil }
@ -280,6 +290,7 @@ func NewUpdateStep(plan *Plan, reg RegisterResourceEvent, old *resource.State,
contract.Assert(old != nil)
contract.Assert(old.URN != "")
contract.Assert(old.ID != "" || !old.Custom)
contract.Assert(!old.Custom || old.Provider != "" || providers.IsProviderType(old.Type))
contract.Assert(!old.Delete)
contract.Assert(new != nil)
contract.Assert(new.URN != "")
@ -300,6 +311,7 @@ func NewUpdateStep(plan *Plan, reg RegisterResourceEvent, old *resource.State,
func (s *UpdateStep) Op() StepOp { return OpUpdate }
func (s *UpdateStep) Plan() *Plan { return s.plan }
func (s *UpdateStep) Type() tokens.Type { return s.old.Type }
func (s *UpdateStep) Provider() string { return s.old.Provider }
func (s *UpdateStep) URN() resource.URN { return s.old.URN }
func (s *UpdateStep) Old() *resource.State { return s.old }
func (s *UpdateStep) New() *resource.State { return s.new }
@ -384,6 +396,7 @@ func NewReplaceStep(plan *Plan, old *resource.State, new *resource.State,
func (s *ReplaceStep) Op() StepOp { return OpReplace }
func (s *ReplaceStep) Plan() *Plan { return s.plan }
func (s *ReplaceStep) Type() tokens.Type { return s.old.Type }
func (s *ReplaceStep) Provider() string { return s.old.Provider }
func (s *ReplaceStep) URN() resource.URN { return s.old.URN }
func (s *ReplaceStep) Old() *resource.State { return s.old }
func (s *ReplaceStep) New() *resource.State { return s.new }
@ -462,6 +475,7 @@ func (s *ReadStep) Op() StepOp {
func (s *ReadStep) Plan() *Plan { return s.plan }
func (s *ReadStep) Type() tokens.Type { return s.new.Type }
func (s *ReadStep) Provider() string { return s.new.Provider }
func (s *ReadStep) URN() resource.URN { return s.new.URN }
func (s *ReadStep) Old() *resource.State { return s.old }
func (s *ReadStep) New() *resource.State { return s.new }
@ -607,5 +621,16 @@ func (op StepOp) Suffix() string {
// getProvider fetches the provider for the given step.
func getProvider(s Step) (plugin.Provider, error) {
return s.Plan().Provider(s.Type().Package())
if providers.IsProviderType(s.Type()) {
return s.Plan().providers, nil
}
ref, err := providers.ParseReference(s.Provider())
if err != nil {
return nil, errors.Errorf("bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err)
}
provider, ok := s.Plan().GetProvider(ref)
if !ok {
return nil, errors.Errorf("unknown provider '%v' for resource %v", s.Provider(), s.URN())
}
return provider, nil
}

View file

@ -19,8 +19,8 @@ import (
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"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/util/logging"
)
@ -45,7 +45,7 @@ type stepGenerator struct {
// GenerateReadSteps is responsible for producing one or more steps required to service
// a ReadResourceEvent coming from the language host.
func (sg *stepGenerator) GenerateReadSteps(event ReadResourceEvent) ([]Step, error) {
urn := sg.generateURN(event.Parent(), event.Type(), event.Name())
urn := sg.plan.generateURN(event.Parent(), event.Type(), event.Name())
newState := resource.NewState(event.Type(),
urn,
true, /*custom*/
@ -57,7 +57,8 @@ func (sg *stepGenerator) GenerateReadSteps(event ReadResourceEvent) ([]Step, err
false, /*protect*/
true, /*external*/
event.Dependencies(),
nil /* initErrors */)
nil, /* initErrors */
event.Provider())
old, hasOld := sg.plan.Olds()[urn]
// If the snapshot has an old resource for this URN and it's not external, we're going
@ -104,7 +105,7 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, err
goal := event.Goal()
// generate an URN for this new resource.
urn := sg.generateURN(goal.Parent, goal.Type, goal.Name)
urn := sg.plan.generateURN(goal.Parent, goal.Type, goal.Name)
if sg.urns[urn] {
invalid = true
// TODO[pulumi/pulumi-framework#19]: improve this error message!
@ -129,8 +130,22 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, err
var prov plugin.Provider
var err error
if goal.Custom {
if prov, err = sg.provider(goal.Type); err != nil {
return nil, err
// If this resource is a provider resource, use the plan's provider registry for its CRUD operations.
// Otherwise, resolve the the resource's provider reference.
if providers.IsProviderType(goal.Type) {
prov = sg.plan.providers
} else {
contract.Assert(goal.Provider != "")
ref, refErr := providers.ParseReference(goal.Provider)
if refErr != nil {
return nil, errors.Errorf(
"bad provider reference '%v' for resource '%v': %v", goal.Provider, urn, refErr)
}
p, ok := sg.plan.GetProvider(ref)
if !ok {
return nil, errors.Errorf("unknown provider '%v' for resource '%v'", ref, urn)
}
prov = p
}
}
@ -148,7 +163,9 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, err
// If this isn't a refresh, ensure the provider is okay with this resource and fetch the inputs to pass to
// subsequent methods. If these are not inputs, we are just going to blindly store the outputs, so skip this.
if prov != nil && !refresh {
// Note that we must always run `Check` for resource providers: the provider registry uses `Check` to load the
// appropriate provider plugin.
if prov != nil && (!refresh || providers.IsProviderType(goal.Type)) {
var failures []plugin.CheckFailure
// If we are re-creating this resource because it was deleted earlier, the old inputs are now
@ -246,19 +263,27 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, err
// Case 3: hasOld
// In this case, the resource we are operating upon now exists in the old snapshot.
// It must be an update or a replace. Which operation we do depends on the provider's
// response to `Diff`. We must:
// - Check whether the update requires replacement (`Diff`)
// - If yes, create a new copy, and mark it as having been replaced.
// - If no, simply update the existing resource in place.
// It must be an update or a replace. Which operation we do depends on the the specific change made to the
// resource's properties:
// - If the resource's provider reference changed, the resource must be replaced. This behavior is founded upon
// the assumption that providers are recreated iff their configuration changed in such a way that they are no
// longer able to manage existing resources.
// - Otherwise, we invoke the resource's provider's `Diff` method. If this method indicates that the resource must
// be replaced, we do so. If it does not, we update the resource in place.
if hasOld {
contract.Assert(old != nil && old.Type == new.Type)
// Determine whether the change resulted in a diff.
diff, err := sg.diff(urn, old.ID, oldInputs, oldOutputs, inputs, outputs, props, prov, refresh,
allowUnknowns)
if err != nil {
return nil, err
var diff plugin.DiffResult
if old.Provider != new.Provider {
diff = plugin.DiffResult{Changes: plugin.DiffSome, ReplaceKeys: []resource.PropertyKey{"provider"}}
} else {
// Determine whether the change resulted in a diff.
d, diffErr := sg.diff(urn, old.ID, oldInputs, oldOutputs, inputs, outputs, props, prov, refresh,
allowUnknowns)
if diffErr != nil {
return nil, diffErr
}
diff = d
}
// Ensure that we received a sensible response.
@ -484,18 +509,7 @@ func (sg *stepGenerator) getResourcePropertyStates(urn resource.URN, goal *resou
}
return props, inputs, outputs,
resource.NewState(goal.Type, urn, goal.Custom, false, "",
inputs, outputs, goal.Parent, goal.Protect, false, goal.Dependencies, []string{})
}
func (sg *stepGenerator) generateURN(parent resource.URN, ty tokens.Type, name tokens.QName) resource.URN {
// Use the resource goal state name to produce a globally unique URN.
parentType := tokens.Type("")
if parent != "" && parent.Type() != resource.RootStackType {
// Skip empty parents and don't use the root stack type; otherwise, use the full qualified type.
parentType = parent.QualifiedType()
}
return resource.NewURN(sg.plan.Target().Name, sg.plan.source.Project(), parentType, ty, name)
inputs, outputs, goal.Parent, goal.Protect, false, goal.Dependencies, nil, goal.Provider)
}
// issueCheckErrors prints any check errors to the diagnostics sink.
@ -517,19 +531,6 @@ func (sg *stepGenerator) issueCheckErrors(new *resource.State, urn resource.URN,
return true
}
// 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 (sg *stepGenerator) provider(t tokens.Type) (plugin.Provider, error) {
pkg := t.Package()
prov, err := sg.plan.Provider(pkg)
if err != nil {
return nil, err
} else if prov == nil {
return nil, errors.Errorf("could not load resource provider for package '%v' from $PATH", pkg)
}
return prov, nil
}
func (sg *stepGenerator) Steps() int {
return len(sg.Creates()) + len(sg.Updates()) + len(sg.Replaces()) + len(sg.Deletes())
}

View file

@ -4,6 +4,7 @@ package graph
import (
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/util/contract"
)
@ -28,6 +29,22 @@ func (dg *DependencyGraph) DependingOn(res *resource.State) []*resource.State {
contract.Assert(ok)
dependentSet[res.URN] = true
isDependent := func(candidate *resource.State) bool {
if candidate.Provider != "" {
ref, err := providers.ParseReference(candidate.Provider)
contract.Assert(err == nil)
if dependentSet[ref.URN()] {
return true
}
}
for _, dependency := range candidate.Dependencies {
if dependentSet[dependency] {
return true
}
}
return false
}
// The dependency graph encoded directly within the snapshot is the reverse of
// the graph that we actually want to operate upon. Edges in the snapshot graph
// originate in a resource and go to that resource's dependencies.
@ -42,13 +59,10 @@ func (dg *DependencyGraph) DependingOn(res *resource.State) []*resource.State {
// the list. All resources that depend directly or indirectly on `res` are prepended
// onto `dependents`.
for i := cursorIndex + 1; i < len(dg.resources); i++ {
cursorResource := dg.resources[i]
for _, dependency := range cursorResource.Dependencies {
if dependentSet[dependency] {
dependents = append(dependents, cursorResource)
dependentSet[cursorResource.URN] = true
break
}
candidate := dg.resources[i]
if isDependent(candidate) {
dependents = append(dependents, candidate)
dependentSet[candidate.URN] = true
}
}

View file

@ -6,51 +6,87 @@ import (
"testing"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/stretchr/testify/assert"
)
func NewResource(name string, deps ...resource.URN) *resource.State {
func NewProviderResource(pkg, name, id string, deps ...resource.URN) *resource.State {
t := providers.MakeProviderType(tokens.Package(pkg))
return &resource.State{
Type: tokens.Type("test"),
URN: resource.URN(name),
Inputs: make(resource.PropertyMap),
Outputs: make(resource.PropertyMap),
Type: t,
URN: resource.NewURN("test", "test", "", t, tokens.QName(name)),
ID: resource.ID(id),
Inputs: resource.PropertyMap{},
Outputs: resource.PropertyMap{},
Dependencies: deps,
}
}
func NewResource(name string, provider *resource.State, deps ...resource.URN) *resource.State {
prov := ""
if provider != nil {
p, err := providers.NewReference(provider.URN, provider.ID)
if err != nil {
panic(err)
}
prov = p.String()
}
t := tokens.Type("test:test:test")
return &resource.State{
Type: t,
URN: resource.NewURN("test", "test", "", t, tokens.QName(name)),
Inputs: resource.PropertyMap{},
Outputs: resource.PropertyMap{},
Dependencies: deps,
Provider: prov,
}
}
func TestBasicGraph(t *testing.T) {
a := NewResource("a")
b := NewResource("b", a.URN)
c := NewResource("c", a.URN)
d := NewResource("d", b.URN)
pA := NewProviderResource("test", "pA", "0")
a := NewResource("a", pA)
b := NewResource("b", pA, a.URN)
pB := NewProviderResource("test", "pB", "1", a.URN, b.URN)
c := NewResource("c", pB, a.URN)
d := NewResource("d", nil, b.URN)
dg := NewDependencyGraph([]*resource.State{
pA,
a,
b,
pB,
c,
d,
})
assert.Equal(t, []*resource.State{
b, c, d,
a, b, pB, c, d,
}, dg.DependingOn(pA))
assert.Equal(t, []*resource.State{
b, pB, c, d,
}, dg.DependingOn(a))
assert.Equal(t, []*resource.State{
d,
pB, c, d,
}, dg.DependingOn(b))
assert.Equal(t, []*resource.State{
c,
}, dg.DependingOn(pB))
assert.Nil(t, dg.DependingOn(c))
assert.Nil(t, dg.DependingOn(d))
}
// Tests that we don't add the same node to the DependingOn set twice.
func TestGraphNoDuplicates(t *testing.T) {
a := NewResource("a")
b := NewResource("b", a.URN)
c := NewResource("c", a.URN)
d := NewResource("d", b.URN, c.URN)
a := NewResource("a", nil)
b := NewResource("b", nil, a.URN)
c := NewResource("c", nil, a.URN)
d := NewResource("d", nil, b.URN, c.URN)
dg := NewDependencyGraph([]*resource.State{
a,

View file

@ -23,7 +23,6 @@ import (
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/pulumi/pulumi/pkg/util/contract"
@ -43,9 +42,11 @@ type Host interface {
// Analyzer fetches the analyzer with a given name, possibly lazily allocating the plugins for it. If an analyzer
// could not be found, or an error occurred while creating it, a non-nil error is returned.
Analyzer(nm tokens.QName) (Analyzer, error)
// Provider fetches the provider for a given package, lazily allocating it if necessary. If a provider for this
// package could not be found, or an error occurs while creating it, a non-nil error is returned.
// Provider loads a new copy of the provider for a given package. If a provider for this package could not be
// found, or an error occurs while creating it, a non-nil error is returned.
Provider(pkg tokens.Package, version *semver.Version) (Provider, error)
// CloseProvider closes the given provider plugin and deregisters it from this host.
CloseProvider(provider Provider) error
// LanguageRuntime fetches the language runtime plugin for a given language, lazily allocating if necessary. If
// an implementation of this language runtime wasn't found, on an error occurs, a non-nil error is returned.
LanguageRuntime(runtime string) (LanguageRuntime, error)
@ -81,14 +82,15 @@ type Events interface {
func NewDefaultHost(ctx *Context, config ConfigSource, events Events,
runtimeOptions map[string]interface{}) (Host, error) {
host := &defaultHost{
ctx: ctx,
config: config,
events: events,
runtimeOptions: runtimeOptions,
analyzerPlugins: make(map[tokens.QName]*analyzerPlugin),
languagePlugins: make(map[string]*languagePlugin),
resourcePlugins: make(map[tokens.Package]*resourcePlugin),
loadRequests: make(chan pluginLoadRequest),
ctx: ctx,
config: config,
events: events,
runtimeOptions: runtimeOptions,
analyzerPlugins: make(map[tokens.QName]*analyzerPlugin),
languagePlugins: make(map[string]*languagePlugin),
resourcePlugins: make(map[Provider]*resourcePlugin),
reportedResourcePlugins: make(map[string]struct{}),
loadRequests: make(chan pluginLoadRequest),
}
// Fire up a gRPC server to listen for requests. This acts as a RPC interface that plugins can use
@ -115,16 +117,17 @@ type pluginLoadRequest struct {
}
type defaultHost struct {
ctx *Context // the shared context for this host.
config ConfigSource // the source for provider configuration parameters.
events Events // optional callbacks for plugin load events
runtimeOptions map[string]interface{} // options to pass to the language plugins.
analyzerPlugins map[tokens.QName]*analyzerPlugin // a cache of analyzer plugins and their processes.
languagePlugins map[string]*languagePlugin // a cache of language plugins and their processes.
resourcePlugins map[tokens.Package]*resourcePlugin // a cache of resource plugins and their processes.
plugins []workspace.PluginInfo // a list of plugins allocated by this host.
loadRequests chan pluginLoadRequest // a channel used to satisfy plugin load requests.
server *hostServer // the server's RPC machinery.
ctx *Context // the shared context for this host.
config ConfigSource // the source for provider configuration parameters.
events Events // optional callbacks for plugin load events
runtimeOptions map[string]interface{} // options to pass to the language plugins.
analyzerPlugins map[tokens.QName]*analyzerPlugin // a cache of analyzer plugins and their processes.
languagePlugins map[string]*languagePlugin // a cache of language plugins and their processes.
resourcePlugins map[Provider]*resourcePlugin // the set of loaded resource plugins.
reportedResourcePlugins map[string]struct{} // the set of unique resource plugins we'll report.
plugins []workspace.PluginInfo // a list of plugins allocated by this host.
loadRequests chan pluginLoadRequest // a channel used to satisfy plugin load requests.
server *hostServer // the server's RPC machinery.
}
var _ Host = (*defaultHost)(nil)
@ -204,27 +207,7 @@ func (host *defaultHost) Analyzer(name tokens.QName) (Analyzer, error) {
func (host *defaultHost) Provider(pkg tokens.Package, version *semver.Version) (Provider, error) {
plugin, err := host.loadPlugin(func() (interface{}, error) {
// First see if we already loaded this plugin.
if plug, has := host.resourcePlugins[pkg]; has {
contract.Assert(plug != nil)
// Make sure the versions match.
if version != nil {
if plug.Info.Version == nil {
return nil,
errors.Errorf("resource plugin version %s requested, but an unknown version was found",
version.String())
} else if !plug.Info.Version.GTE(*version) {
return nil,
errors.Errorf("resource plugin version %s requested, but version %s was found",
version.String(), plug.Info.Version.String())
}
}
return plug.Plugin, nil
}
// If not, try to load and bind to a plugin.
// Try to load and bind to a plugin.
plug, err := NewProvider(host, host.ctx, pkg, version)
if err == nil && plug != nil {
info, infoerr := plug.GetPluginInfo()
@ -247,23 +230,19 @@ func (host *defaultHost) Provider(pkg tokens.Package, version *semver.Version) (
}
}
// Configure the provider. If no configuration source is present, assume no configuration. We do this here
// because resource providers must be configured exactly once before any method besides Configure is called.
providerConfig := make(map[config.Key]string)
if host.config != nil {
providerConfig, err = host.config.GetPackageConfig(pkg)
if err != nil {
return nil, errors.Wrapf(err, "failed to fetch configuration for pkg '%v' resource provider", pkg)
}
// Record the result and add the plugin's info to our list of loaded plugins if it's the first copy of its
// kind.
key := info.Name
if info.Version != nil {
key += info.Version.String()
}
if err = plug.Configure(providerConfig); err != nil {
return nil, errors.Wrapf(err, "failed to configure pkg '%v' resource provider", pkg)
_, alreadyReported := host.reportedResourcePlugins[key]
if !alreadyReported {
host.reportedResourcePlugins[key] = struct{}{}
host.plugins = append(host.plugins, info)
}
// Memoize the result.
host.plugins = append(host.plugins, info)
host.resourcePlugins[pkg] = &resourcePlugin{Plugin: plug, Info: info}
if host.events != nil {
host.resourcePlugins[plug] = &resourcePlugin{Plugin: plug, Info: info}
if host.events != nil && !alreadyReported {
if eventerr := host.events.OnPluginLoad(info); eventerr != nil {
return nil, errors.Wrapf(eventerr, "failed to perform plugin load callback")
}
@ -411,6 +390,18 @@ func (host *defaultHost) SignalCancellation() error {
return result
}
func (host *defaultHost) CloseProvider(provider Provider) error {
// NOTE: we're abusing loadPlugin in order to ensure proper synchronization.
_, err := host.loadPlugin(func() (interface{}, error) {
if err := provider.Close(); err != nil {
return nil, err
}
delete(host.resourcePlugins, provider)
return nil, nil
})
return err
}
func (host *defaultHost) Close() error {
// Close all plugins.
for _, plug := range host.analyzerPlugins {
@ -432,7 +423,7 @@ func (host *defaultHost) Close() error {
// Empty out all maps.
host.analyzerPlugins = make(map[tokens.QName]*analyzerPlugin)
host.languagePlugins = make(map[string]*languagePlugin)
host.resourcePlugins = make(map[tokens.Package]*resourcePlugin)
host.resourcePlugins = make(map[Provider]*resourcePlugin)
// Shut down the plugin loader.
close(host.loadRequests)

View file

@ -18,7 +18,6 @@ import (
"io"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/workspace"
)
@ -38,8 +37,14 @@ type Provider interface {
io.Closer
// Pkg fetches this provider's package.
Pkg() tokens.Package
// CheckConfig validates the configuration for this resource provider.
CheckConfig(olds, news resource.PropertyMap) (resource.PropertyMap, []CheckFailure, error)
// DiffConfig checks what impacts a hypothetical change to this provider's configuration will have on the provider.
DiffConfig(olds, news resource.PropertyMap) (DiffResult, error)
// Configure configures the resource provider with "globals" that control its behavior.
Configure(vars map[config.Key]string) error
Configure(inputs resource.PropertyMap) error
// Check validates that the given property bag is valid for a resource of the given type and returns the inputs
// that should be passed to successive calls to Diff, Create, or Update for this resource.
Check(urn resource.URN, olds, news resource.PropertyMap,

View file

@ -26,7 +26,6 @@ import (
"google.golang.org/grpc/codes"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
"github.com/pulumi/pulumi/pkg/util/logging"
@ -42,6 +41,7 @@ type provider struct {
plug *plugin // the actual plugin process wrapper.
clientRaw pulumirpc.ResourceProviderClient // the raw provider client; usually unsafe to use directly.
cfgerr error // non-nil if a configure call fails.
cfgknown bool // true if all configuration values are known.
cfgdone chan bool // closed when configuration has completed.
}
@ -79,7 +79,68 @@ func (p *provider) Pkg() tokens.Package { return p.pkg }
// label returns a base label for tracing functions.
func (p *provider) label() string {
return fmt.Sprintf("Provider[%s]", p.pkg)
return fmt.Sprintf("Provider[%s, %p]", p.pkg, p)
}
// CheckConfig validates the configuration for this resource provider.
func (p *provider) CheckConfig(olds, news resource.PropertyMap) (resource.PropertyMap, []CheckFailure, error) {
// Ensure that all config values are strings or unknowns.
var failures []CheckFailure
for k, v := range news {
if !v.IsString() && !v.IsComputed() {
failures = append(failures, CheckFailure{
Property: k,
Reason: "provider property values must be strings",
})
}
}
if len(failures) != 0 {
return nil, failures, nil
}
// If all config values check out, simply return the new values.
return news, nil, nil
}
// DiffConfig checks what impacts a hypothetical change to this provider's configuration will have on the provider.
func (p *provider) DiffConfig(olds, news resource.PropertyMap) (DiffResult, error) {
// There are two interesting scenarios with the present gRPC interface:
// 1. Configuration differences in which all properties are known
// 2. Configuration differences in which some new property is unknown.
//
// Despite the fact that in both scenarios we know that configuration has changed, they differ in whether or not
// they return a diff result that indicates that the provider should be replaced (and thus require that any
// existing resource managed by the provider are also replaced).
//
// In the first case, we return a diff result that indicates that the provider _should not_ be replaced. We may
// encounter this scenario during any update or any preview in which all properties are known. Although this
// decision is not conservative--indeed, the conservative decision would be to always require replacement of a
// provider if any input has changed--we believe that it results in the best possible user experience for providers
// that do not implement DiffConfig functionality. If we took the conservative route here, any change to a
// provider's configuration (no matter how inconsequential) would cause all of its resources to be replaced. This
// is clearly a bad experience, and differs from how things worked prior to first-class providers.
//
// In the second case, we return a diff result that indicates that the provider _should_ be replaced. This may
// occur during any preview, but will never occur during an update. This decision is conservative because we
// believe that it is unlikely that a provider property that may be unknown is very likely to be a property that
// is fundamental to the provider's ability to manage its existing resources.
//
// The different decisions we make here cause a bit of a sharp edge: a provider with unknown configuration during
// preview will appear to require replacement, but will never actually require replacement during an update, as by
// that point all of its configuration will necessarily be known.
var replaceKeys []resource.PropertyKey
for k, v := range news {
// These are ensured during Check().
contract.Assert(v.IsString() || v.IsComputed())
// As per the note above, any unknown properties require replacement.
if v.IsComputed() {
replaceKeys = append(replaceKeys, k)
}
}
return DiffResult{Changes: DiffUnknown, ReplaceKeys: replaceKeys}, nil
}
// getClient returns the client, and ensures that the target provider has been configured. This just makes it safer
@ -100,14 +161,31 @@ func (p *provider) ensureConfigured() error {
}
// Configure configures the resource provider with "globals" that control its behavior.
func (p *provider) Configure(vars map[config.Key]string) error {
func (p *provider) Configure(inputs resource.PropertyMap) error {
label := fmt.Sprintf("%s.Configure()", p.label())
logging.V(7).Infof("%s executing (#vars=%d)", label, len(vars))
logging.V(7).Infof("%s executing (#vars=%d)", label, len(inputs))
// Convert the inputs to a config map. If any are unknown, do not configure the underlying plugin: instead, leavce
// the cfgknown bit unset and carry on.
config := make(map[string]string)
for k, v := range vars {
// Pass the older spelling of a configuration key across the RPC interface, for now, to support
// providers which are on the older plan.
config[k.Namespace()+":config:"+k.Name()] = v
for k, v := range inputs {
if k == "version" {
continue
}
switch {
case v.IsComputed():
p.cfgknown = false
close(p.cfgdone)
return nil
case v.IsString():
// Pass the older spelling of a configuration key across the RPC interface, for now, to support
// providers which are on the older plan.
config[string(p.Pkg())+":config:"+string(k)] = v.StringValue()
default:
p.cfgerr = errors.Errorf("provider property values must be strings; '%v' is a %v", k, v.TypeString())
close(p.cfgdone)
return p.cfgerr
}
}
// Spawn the configure to happen in parallel. This ensures that we remain responsive elsewhere that might
@ -120,7 +198,7 @@ func (p *provider) Configure(vars map[config.Key]string) error {
err = createConfigureError(rpcError)
}
// Acquire the lock, publish the results, and notify any waiters.
p.cfgerr = err
p.cfgknown, p.cfgerr = true, err
close(p.cfgdone)
}()
@ -133,6 +211,18 @@ func (p *provider) Check(urn resource.URN,
label := fmt.Sprintf("%s.Check(%s)", p.label(), urn)
logging.V(7).Infof("%s executing (#olds=%d,#news=%d", label, len(olds), len(news))
// Get the RPC client and ensure it's configured.
client, err := p.getClient()
if err != nil {
return nil, nil, err
}
// If the configuration for this provider was not fully known--e.g. if we are doing a preview and some input
// property was sourced from another resource's output properties--don't call into the underlying provider.
if !p.cfgknown {
return news, nil, nil
}
molds, err := MarshalProperties(olds, MarshalOptions{Label: fmt.Sprintf("%s.olds", label),
KeepUnknowns: allowUnknowns})
if err != nil {
@ -144,12 +234,6 @@ func (p *provider) Check(urn resource.URN,
return nil, nil, err
}
// Get the RPC client and ensure it's configured.
client, err := p.getClient()
if err != nil {
return nil, nil, err
}
resp, err := client.Check(p.ctx.Request(), &pulumirpc.CheckRequest{
Urn: string(urn),
Olds: molds,
@ -192,6 +276,17 @@ func (p *provider) Diff(urn resource.URN, id resource.ID,
label := fmt.Sprintf("%s.Diff(%s,%s)", p.label(), urn, id)
logging.V(7).Infof("%s: executing (#olds=%d,#news=%d)", label, len(olds), len(news))
// Get the RPC client and ensure it's configured.
client, err := p.getClient()
if err != nil {
return DiffResult{}, err
}
// If this function is called, we must have complete configuration for the underlying provider. Per DiffConfig,
// any unknown input will cause the provider to be replaced, which will cause all of its resources to be replaced,
// and we do not call `Diff` for resources that are being replaced due to a change to their provider reference.
contract.Assert(p.cfgknown)
molds, err := MarshalProperties(olds, MarshalOptions{
Label: fmt.Sprintf("%s.olds", label), ElideAssetContents: true, KeepUnknowns: allowUnknowns})
if err != nil {
@ -203,12 +298,6 @@ func (p *provider) Diff(urn resource.URN, id resource.ID,
return DiffResult{}, err
}
// Get the RPC client and ensure it's configured.
client, err := p.getClient()
if err != nil {
return DiffResult{}, err
}
resp, err := client.Diff(p.ctx.Request(), &pulumirpc.DiffRequest{
Id: string(id),
Urn: string(urn),
@ -261,6 +350,9 @@ func (p *provider) Create(urn resource.URN, props resource.PropertyMap) (resourc
return "", nil, resource.StatusOK, err
}
// We should only be calling {Create,Update,Delete} if the provider is fully configured.
contract.Assert(p.cfgknown)
var id resource.ID
var liveObject *_struct.Struct
var resourceError error
@ -309,14 +401,19 @@ func (p *provider) Read(urn resource.URN, id resource.ID, props resource.Propert
label := fmt.Sprintf("%s.Read(%s,%s)", p.label(), id, urn)
logging.V(7).Infof("%s executing (#props=%v)", label, len(props))
// Marshal the input state so we can perform the RPC.
marshaled, err := MarshalProperties(props, MarshalOptions{Label: label, ElideAssetContents: true})
// Get the RPC client and ensure it's configured.
client, err := p.getClient()
if err != nil {
return nil, err
}
// Get the RPC client and ensure it's configured.
client, err := p.getClient()
// If the provider is not fully configured, return an empty bag.
if !p.cfgknown {
return resource.PropertyMap{}, nil
}
// Marshal the input state so we can perform the RPC.
marshaled, err := MarshalProperties(props, MarshalOptions{Label: label, ElideAssetContents: true})
if err != nil {
return nil, err
}
@ -379,6 +476,9 @@ func (p *provider) Update(urn resource.URN, id resource.ID,
return nil, resource.StatusOK, err
}
// We should only be calling {Create,Update,Delete} if the provider is fully configured.
contract.Assert(p.cfgknown)
var liveObject *_struct.Struct
var resourceError error
var resourceStatus = resource.StatusOK
@ -432,6 +532,9 @@ func (p *provider) Delete(urn resource.URN, id resource.ID, props resource.Prope
return resource.StatusOK, err
}
// We should only be calling {Create,Update,Delete} if the provider is fully configured.
contract.Assert(p.cfgknown)
if _, err := client.Delete(p.ctx.Request(), &pulumirpc.DeleteRequest{
Id: string(id),
Urn: string(urn),
@ -454,13 +557,18 @@ func (p *provider) Invoke(tok tokens.ModuleMember, args resource.PropertyMap) (r
label := fmt.Sprintf("%s.Invoke(%s)", p.label(), tok)
logging.V(7).Infof("%s executing (#args=%d)", label, len(args))
margs, err := MarshalProperties(args, MarshalOptions{Label: fmt.Sprintf("%s.args", label)})
// Get the RPC client and ensure it's configured.
client, err := p.getClient()
if err != nil {
return nil, nil, err
}
// Get the RPC client and ensure it's configured.
client, err := p.getClient()
// If the provider is not fully configured, return an empty property map.
if !p.cfgknown {
return resource.PropertyMap{}, nil, nil
}
margs, err := MarshalProperties(args, MarshalOptions{Label: fmt.Sprintf("%s.args", label)})
if err != nil {
return nil, nil, err
}

View file

@ -28,11 +28,12 @@ type Goal struct {
Parent URN // an optional parent URN for this resource.
Protect bool // true to protect this resource from deletion.
Dependencies []URN // dependencies of this resource object.
Provider string // the provider to use for this resource.
}
// NewGoal allocates a new resource goal state.
func NewGoal(t tokens.Type, name tokens.QName, custom bool, props PropertyMap,
parent URN, protect bool, dependencies []URN) *Goal {
parent URN, protect bool, dependencies []URN, provider string) *Goal {
return &Goal{
Type: t,
Name: name,
@ -41,5 +42,6 @@ func NewGoal(t tokens.Type, name tokens.QName, custom bool, props PropertyMap,
Parent: parent,
Protect: protect,
Dependencies: dependencies,
Provider: provider,
}
}

View file

@ -35,12 +35,13 @@ type State struct {
External bool // true if this resource is "external" to Pulumi and we don't control the lifecycle
Dependencies []URN // the resource's dependencies
InitErrors []string // the set of errors encountered in the process of initializing resource.
Provider string // the provider to use for this resource.
}
// NewState creates a new resource value from existing resource state information.
func NewState(t tokens.Type, urn URN, custom bool, del bool, id ID,
inputs PropertyMap, outputs PropertyMap, parent URN, protect bool,
external bool, dependencies []URN, initErrors []string) *State {
external bool, dependencies []URN, initErrors []string, provider string) *State {
contract.Assertf(t != "", "type was empty")
contract.Assertf(custom || id == "", "is custom or had empty ID")
contract.Assertf(inputs != nil, "inputs was non-nil")
@ -57,6 +58,7 @@ func NewState(t tokens.Type, urn URN, custom bool, del bool, id ID,
External: external,
Dependencies: dependencies,
InitErrors: initErrors,
Provider: provider,
}
}

View file

@ -178,6 +178,7 @@ func SerializeResource(res *resource.State) apitype.ResourceV2 {
External: res.External,
Dependencies: res.Dependencies,
InitErrors: res.InitErrors,
Provider: res.Provider,
}
}
@ -240,7 +241,7 @@ func DeserializeResource(res apitype.ResourceV2) (*resource.State, error) {
return resource.NewState(
res.Type, res.URN, res.Custom, res.Delete, res.ID,
inputs, outputs, res.Parent, res.Protect, res.External, res.Dependencies, res.InitErrors), nil
inputs, outputs, res.Parent, res.Protect, res.External, res.Dependencies, res.InitErrors, res.Provider), nil
}
// DeserializeProperties deserializes an entire map of deploy properties into a resource property map.

View file

@ -75,6 +75,7 @@ func TestDeploymentSerialization(t *testing.T) {
resource.URN("foo:bar:boo"),
},
[]string{},
"",
)
dep := SerializeResource(res)

26
sdk/nodejs/invoke.ts Normal file
View file

@ -0,0 +1,26 @@
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { ProviderResource } from "./resource";
/*
* InvokeOptions is a bag of options that control the behavior of a call to runtime.invoke.
*/
export interface InvokeOptions {
/**
* An optional provider to use for this invocation. If no provider is supplied, the default provider for the
* invoked function's package will be used.
*/
provider?: ProviderResource;
}

View file

@ -562,7 +562,8 @@ proto.pulumirpc.InvokeRequest.prototype.toObject = function(opt_includeInstance)
proto.pulumirpc.InvokeRequest.toObject = function(includeInstance, msg) {
var f, obj = {
tok: jspb.Message.getFieldWithDefault(msg, 1, ""),
args: (f = msg.getArgs()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f)
args: (f = msg.getArgs()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f),
provider: jspb.Message.getFieldWithDefault(msg, 3, "")
};
if (includeInstance) {
@ -608,6 +609,10 @@ proto.pulumirpc.InvokeRequest.deserializeBinaryFromReader = function(msg, reader
reader.readMessage(value,google_protobuf_struct_pb.Struct.deserializeBinaryFromReader);
msg.setArgs(value);
break;
case 3:
var value = /** @type {string} */ (reader.readString());
msg.setProvider(value);
break;
default:
reader.skipField();
break;
@ -652,6 +657,13 @@ proto.pulumirpc.InvokeRequest.serializeBinaryToWriter = function(message, writer
google_protobuf_struct_pb.Struct.serializeBinaryToWriter
);
}
f = message.getProvider();
if (f.length > 0) {
writer.writeString(
3,
f
);
}
};
@ -700,6 +712,21 @@ proto.pulumirpc.InvokeRequest.prototype.hasArgs = function() {
};
/**
* optional string provider = 3;
* @return {string}
*/
proto.pulumirpc.InvokeRequest.prototype.getProvider = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, ""));
};
/** @param {string} value */
proto.pulumirpc.InvokeRequest.prototype.setProvider = function(value) {
jspb.Message.setProto3StringField(this, 3, value);
};
/**
* Generated by JsPbCodeGenerator.

View file

@ -78,7 +78,8 @@ proto.pulumirpc.ReadResourceRequest.toObject = function(includeInstance, msg) {
name: jspb.Message.getFieldWithDefault(msg, 3, ""),
parent: jspb.Message.getFieldWithDefault(msg, 4, ""),
properties: (f = msg.getProperties()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f),
dependenciesList: jspb.Message.getRepeatedField(msg, 6)
dependenciesList: jspb.Message.getRepeatedField(msg, 6),
provider: jspb.Message.getFieldWithDefault(msg, 7, "")
};
if (includeInstance) {
@ -140,6 +141,10 @@ proto.pulumirpc.ReadResourceRequest.deserializeBinaryFromReader = function(msg,
var value = /** @type {string} */ (reader.readString());
msg.addDependencies(value);
break;
case 7:
var value = /** @type {string} */ (reader.readString());
msg.setProvider(value);
break;
default:
reader.skipField();
break;
@ -212,6 +217,13 @@ proto.pulumirpc.ReadResourceRequest.serializeBinaryToWriter = function(message,
f
);
}
f = message.getProvider();
if (f.length > 0) {
writer.writeString(
7,
f
);
}
};
@ -334,6 +346,21 @@ proto.pulumirpc.ReadResourceRequest.prototype.clearDependenciesList = function()
};
/**
* optional string provider = 7;
* @return {string}
*/
proto.pulumirpc.ReadResourceRequest.prototype.getProvider = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 7, ""));
};
/** @param {string} value */
proto.pulumirpc.ReadResourceRequest.prototype.setProvider = function(value) {
jspb.Message.setProto3StringField(this, 7, value);
};
/**
* Generated by JsPbCodeGenerator.
@ -580,7 +607,8 @@ proto.pulumirpc.RegisterResourceRequest.toObject = function(includeInstance, msg
custom: jspb.Message.getFieldWithDefault(msg, 4, false),
object: (f = msg.getObject()) && google_protobuf_struct_pb.Struct.toObject(includeInstance, f),
protect: jspb.Message.getFieldWithDefault(msg, 6, false),
dependenciesList: jspb.Message.getRepeatedField(msg, 7)
dependenciesList: jspb.Message.getRepeatedField(msg, 7),
provider: jspb.Message.getFieldWithDefault(msg, 8, "")
};
if (includeInstance) {
@ -646,6 +674,10 @@ proto.pulumirpc.RegisterResourceRequest.deserializeBinaryFromReader = function(m
var value = /** @type {string} */ (reader.readString());
msg.addDependencies(value);
break;
case 8:
var value = /** @type {string} */ (reader.readString());
msg.setProvider(value);
break;
default:
reader.skipField();
break;
@ -725,6 +757,13 @@ proto.pulumirpc.RegisterResourceRequest.serializeBinaryToWriter = function(messa
f
);
}
f = message.getProvider();
if (f.length > 0) {
writer.writeString(
8,
f
);
}
};
@ -866,6 +905,21 @@ proto.pulumirpc.RegisterResourceRequest.prototype.clearDependenciesList = functi
};
/**
* optional string provider = 8;
* @return {string}
*/
proto.pulumirpc.RegisterResourceRequest.prototype.getProvider = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 8, ""));
};
/** @param {string} value */
proto.pulumirpc.RegisterResourceRequest.prototype.setProvider = function(value) {
jspb.Message.setProto3StringField(this, 8, value);
};
/**
* Generated by JsPbCodeGenerator.

View file

@ -111,6 +111,12 @@ export interface ResourceOptions {
* When set to true, protect ensures this resource cannot be deleted.
*/
protect?: boolean;
/**
* An optional provider to use for this resource's CRUD operations. If no provider is supplied, the default
* provider for the resource's package will be used. It is an error to supply a provider to a ProviderResource or
* ComponentResource.
*/
provider?: ProviderResource;
}
/**
@ -160,6 +166,28 @@ export abstract class CustomResource extends Resource {
(<any>CustomResource).doNotCapture = true;
/**
* ProviderResource is a resource that implements CRUD operations for other custom resources. These resources are
* managed similarly to other resources, including the usual diffing and update semantics.
*/
export abstract class ProviderResource extends CustomResource {
/**
* Creates and registers a new provider resource for a particular package.
*
* @param pkg The package associated with this provider.
* @param name The _unique_ name of the provider.
* @param props The configuration to use for this provider.
* @param opts A bag of options that control this provider's behavior.
*/
constructor(pkg: string, name: string, props?: Inputs, opts?: ResourceOptions) {
if (opts && opts.provider !== undefined) {
throw new RunError("Explicit providers may not be used with provider resources");
}
super(`pulumi:providers:${pkg}`, name, props, opts);
}
}
/**
* ComponentResource is a resource that aggregates one or more other child resources into a higher
* level abstraction. The component resource itself is a resource, but does not require custom CRUD
@ -179,6 +207,10 @@ export class ComponentResource extends Resource {
* @param opts A bag of options that control this resource's behavior.
*/
constructor(t: string, name: string, props?: Inputs, opts?: ResourceOptions) {
if (opts && opts.provider !== undefined) {
throw new RunError("Explicit providers may not be used with component resources");
}
super(t, name, false, props, opts);
}

View file

@ -13,10 +13,11 @@
// limitations under the License.
import * as grpc from "grpc";
import { InvokeOptions } from "../invoke";
import * as log from "../log";
import { Inputs } from "../resource";
import { debuggablePromise } from "./debuggable";
import { deserializeProperties, serializeProperties } from "./rpc";
import { deserializeProperties, serializeProperties, unknownValue } from "./rpc";
import { excessiveDebugOutput, getMonitor, rpcKeepAlive, serialize } from "./settings";
const gstruct = require("google-protobuf/google/protobuf/struct_pb.js");
@ -27,7 +28,7 @@ const resproto = require("../proto/resource_pb.js");
* can be a bag of computed values (Ts or Promise<T>s), and the result is a Promise<any> that
* resolves when the invoke finishes.
*/
export async function invoke(tok: string, props: Inputs): Promise<any> {
export async function invoke(tok: string, props: Inputs, opts?: InvokeOptions): Promise<any> {
log.debug(`Invoking function: tok=${tok}` +
excessiveDebugOutput ? `, props=${JSON.stringify(props)}` : ``);
@ -41,9 +42,17 @@ export async function invoke(tok: string, props: Inputs): Promise<any> {
// Fetch the monitor and make an RPC request.
const monitor: any = getMonitor();
let providerRef: string | undefined;
if (opts && opts.provider !== undefined) {
const providerURN = await opts.provider.urn.promise();
const providerID = await opts.provider.id.promise() || unknownValue;
providerRef = `${providerURN}::${providerID}`;
}
const req = new resproto.InvokeRequest();
req.setTok(tok);
req.setArgs(obj);
req.setProvider(providerRef);
const resp: any = await debuggablePromise(new Promise((innerResolve, innerReject) =>
monitor.invoke(req, (err: grpc.StatusObject, innerResponse: any) => {
log.debug(`Invoke RPC finished: tok=${tok}; err: ${err}, resp: ${innerResponse}`);

View file

@ -25,6 +25,7 @@ import {
serializeProperty,
serializeResourceProperties,
transferProperties,
unknownValue,
} from "./rpc";
import { excessiveDebugOutput, getMonitor, rpcKeepAlive, serialize } from "./settings";
@ -40,6 +41,8 @@ interface ResourceResolverOperation {
resolvers: OutputResolvers;
// A parent URN, fully resolved, if any.
parentURN: URN | undefined;
// A provider reference, fully resolved, if any.
providerRef: string | undefined;
// All serialized properties, fully awaited, serialized, and ready to go.
serializedProps: Record<string, any>;
// A set of dependency URNs that this resource is dependent upon (both implicitly and explicitly).
@ -72,6 +75,7 @@ export function readResource(res: Resource, t: string, name: string, props: Inpu
req.setName(name);
req.setId(resolvedID);
req.setParent(resop.parentURN);
req.setProvider(resop.providerRef);
req.setProperties(gstruct.Struct.fromJavaScript(resop.serializedProps));
req.setDependenciesList(Array.from(resop.dependencies));
@ -121,6 +125,7 @@ export function registerResource(res: Resource, t: string, name: string, custom:
req.setCustom(custom);
req.setObject(gstruct.Struct.fromJavaScript(resop.serializedProps));
req.setProtect(opts.protect);
req.setProvider(resop.providerRef);
req.setDependenciesList(Array.from(resop.dependencies));
// Now run the operation, serializing the invocation if necessary.
@ -221,6 +226,13 @@ async function prepareResource(label: string, res: Resource, custom: boolean,
parentURN = await opts.parent.urn.promise();
}
let providerRef: string | undefined;
if (opts.provider) {
const providerURN = await opts.provider.urn.promise();
const providerID = await opts.provider.id.promise() || unknownValue;
providerRef = `${providerURN}::${providerID}`;
}
const dependencies: Set<URN> = new Set<URN>(explicitURNDeps);
for (const implicitDep of implicitDependencies) {
dependencies.add(await implicitDep.urn.promise());
@ -232,6 +244,7 @@ async function prepareResource(label: string, res: Resource, custom: boolean,
resolvers: resolvers,
serializedProps: serializedProps,
parentURN: parentURN,
providerRef: providerRef,
dependencies: dependencies,
};
}

View file

@ -273,23 +273,23 @@ var _Analyzer_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("analyzer.proto", fileDescriptor_analyzer_4d94ca02b59f2385) }
var fileDescriptor_analyzer_4d94ca02b59f2385 = []byte{
// 287 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x4f, 0x4b, 0xc3, 0x40,
0x10, 0xc5, 0x1b, 0x95, 0xda, 0x4e, 0xb5, 0xc2, 0x80, 0xb5, 0xae, 0x1e, 0x42, 0x4e, 0x39, 0x6d,
0x21, 0x22, 0x5e, 0x55, 0xfc, 0x7b, 0x93, 0x78, 0xf6, 0x90, 0x96, 0x49, 0x08, 0xa4, 0xd9, 0x75,
0xff, 0x1c, 0xe2, 0xa7, 0xf0, 0x23, 0x8b, 0xbb, 0x6b, 0x2c, 0xda, 0xdb, 0x0c, 0xef, 0xf1, 0x9b,
0x37, 0x33, 0x30, 0x2d, 0xda, 0xa2, 0xe9, 0x3e, 0x48, 0x71, 0xa9, 0x84, 0x11, 0x38, 0x96, 0xb6,
0xb1, 0xeb, 0x5a, 0xc9, 0x15, 0x3b, 0x90, 0x8d, 0xad, 0xea, 0xd6, 0x0b, 0xec, 0xac, 0x12, 0xa2,
0x6a, 0x68, 0xe1, 0xba, 0xa5, 0x2d, 0x17, 0xb4, 0x96, 0xa6, 0x0b, 0xe2, 0xf9, 0x5f, 0x51, 0x1b,
0x65, 0x57, 0xc6, 0xab, 0xc9, 0x1b, 0x4c, 0x6f, 0xfc, 0x94, 0x9c, 0xde, 0x2d, 0x69, 0x83, 0x08,
0x7b, 0xa6, 0x93, 0x34, 0x8f, 0xe2, 0x28, 0x1d, 0xe7, 0xae, 0xc6, 0x2b, 0x00, 0xa9, 0x84, 0x24,
0x65, 0x6a, 0xd2, 0xf3, 0x9d, 0x38, 0x4a, 0x27, 0xd9, 0x09, 0xf7, 0x60, 0xfe, 0x03, 0xe6, 0xaf,
0x0e, 0x9c, 0x6f, 0x58, 0x93, 0x27, 0x38, 0xea, 0xf1, 0x5a, 0x8a, 0x56, 0x13, 0x5e, 0xc2, 0xa8,
0x2c, 0xea, 0xc6, 0x2a, 0xd2, 0xf3, 0x28, 0xde, 0x4d, 0x27, 0xd9, 0x29, 0xef, 0x17, 0xe3, 0xc1,
0xfd, 0xe0, 0x1d, 0x79, 0x6f, 0x4d, 0xee, 0xfa, 0xa0, 0x41, 0x43, 0x06, 0xa3, 0x30, 0xa9, 0x0b,
0x61, 0xfb, 0x1e, 0x67, 0x30, 0x54, 0x54, 0x68, 0xd1, 0xba, 0xb0, 0xe3, 0x3c, 0x74, 0xd9, 0x67,
0x04, 0xa3, 0x80, 0x51, 0x78, 0x0b, 0xfb, 0xa1, 0xc6, 0x2d, 0x11, 0xc2, 0x3d, 0x18, 0xdb, 0x26,
0xf9, 0x5d, 0x92, 0x01, 0x5e, 0xc3, 0xe1, 0x23, 0x99, 0x17, 0xf7, 0x8d, 0xe7, 0xb6, 0x14, 0x38,
0xfb, 0x77, 0x96, 0xfb, 0xef, 0x67, 0xb0, 0xe3, 0x0d, 0xcc, 0xaf, 0x3d, 0x19, 0x2c, 0x87, 0xce,
0x78, 0xf1, 0x15, 0x00, 0x00, 0xff, 0xff, 0xff, 0x6d, 0x9c, 0x46, 0xee, 0x01, 0x00, 0x00,
// 285 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x91, 0x4f, 0x4b, 0xc3, 0x40,
0x10, 0xc5, 0x1b, 0x95, 0x9a, 0x8c, 0x5a, 0x61, 0xc0, 0x5a, 0x57, 0x0f, 0x25, 0x27, 0x4f, 0x5b,
0xa8, 0x88, 0x57, 0x15, 0xff, 0xde, 0x24, 0x9e, 0x3d, 0xa4, 0x65, 0x12, 0x02, 0x69, 0x76, 0xdd,
0x3f, 0x87, 0xf8, 0x29, 0xfc, 0xc8, 0xc6, 0xdd, 0x35, 0x16, 0xed, 0x6d, 0x86, 0xf7, 0xf8, 0xcd,
0x9b, 0x19, 0x18, 0xe5, 0x4d, 0x5e, 0xb7, 0x1f, 0xa4, 0xb8, 0x54, 0xc2, 0x08, 0x4c, 0xa4, 0xad,
0xed, 0xaa, 0x52, 0x72, 0xc9, 0xf6, 0x65, 0x6d, 0xcb, 0xaa, 0xf1, 0x02, 0x3b, 0x2d, 0x85, 0x28,
0x6b, 0x9a, 0xb9, 0x6e, 0x61, 0x8b, 0x19, 0xad, 0xa4, 0x69, 0x83, 0x78, 0xf6, 0x57, 0xd4, 0x46,
0xd9, 0xa5, 0xf1, 0x6a, 0xfa, 0x06, 0xa3, 0x1b, 0x3f, 0x25, 0xa3, 0x77, 0x4b, 0xda, 0x20, 0xc2,
0x8e, 0x69, 0x25, 0x4d, 0xa2, 0x69, 0x74, 0x9e, 0x64, 0xae, 0xc6, 0x2b, 0x80, 0xce, 0x2e, 0x49,
0x99, 0x8a, 0xf4, 0x64, 0xab, 0x53, 0xf6, 0xe6, 0xc7, 0xdc, 0x83, 0xf9, 0x0f, 0x98, 0xbf, 0x3a,
0x70, 0xb6, 0x66, 0x4d, 0x9f, 0xe0, 0xb0, 0xc7, 0x6b, 0x29, 0x1a, 0x4d, 0x78, 0x09, 0x71, 0x91,
0x57, 0xb5, 0x55, 0x1d, 0x29, 0x9a, 0x6e, 0x77, 0xa4, 0x13, 0xde, 0x2f, 0xc6, 0x83, 0xfb, 0xc1,
0x3b, 0xb2, 0xde, 0x9a, 0xde, 0xf5, 0x41, 0x83, 0x86, 0x0c, 0xe2, 0x30, 0xa9, 0x0d, 0x61, 0xfb,
0x1e, 0xc7, 0x30, 0x54, 0x94, 0x6b, 0xd1, 0xb8, 0xb0, 0x49, 0x16, 0xba, 0xf9, 0x67, 0x04, 0x71,
0xc0, 0x28, 0xbc, 0x85, 0xdd, 0x50, 0xe3, 0x86, 0x08, 0xe1, 0x1e, 0x8c, 0x6d, 0x92, 0xfc, 0x2e,
0xe9, 0x00, 0xaf, 0xe1, 0xe0, 0x91, 0xcc, 0x8b, 0xfb, 0xc6, 0x73, 0x53, 0x08, 0x1c, 0xff, 0x3b,
0xcb, 0xfd, 0xf7, 0x33, 0xd8, 0xd1, 0x1a, 0xe6, 0xd7, 0x9e, 0x0e, 0x16, 0x43, 0x67, 0xbc, 0xf8,
0x0a, 0x00, 0x00, 0xff, 0xff, 0xff, 0x6d, 0x9c, 0x46, 0xee, 0x01, 0x00, 0x00,
}

View file

@ -208,22 +208,22 @@ var _Engine_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("engine.proto", fileDescriptor_engine_fba9c4feb5d1b1dc) }
var fileDescriptor_engine_fba9c4feb5d1b1dc = []byte{
// 258 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x50, 0x4d, 0x4b, 0xc3, 0x40,
0x10, 0xed, 0x36, 0xfd, 0x48, 0xa6, 0x22, 0x61, 0xc0, 0x12, 0xe2, 0x25, 0xf4, 0x14, 0x3c, 0x6c,
0x21, 0x82, 0x07, 0x4f, 0x2a, 0xc6, 0x12, 0x08, 0x29, 0xac, 0x88, 0xe7, 0x56, 0xc7, 0x25, 0xd0,
0x64, 0x63, 0xb2, 0x2b, 0xf4, 0x1f, 0xf8, 0xb3, 0x25, 0x69, 0x1b, 0xec, 0x6d, 0xde, 0x07, 0x33,
0xf3, 0x1e, 0x5c, 0x50, 0x29, 0xf3, 0x92, 0x78, 0x55, 0x2b, 0xad, 0xd0, 0xa9, 0xcc, 0xce, 0x14,
0x79, 0x5d, 0x7d, 0xf8, 0xd7, 0x52, 0x29, 0xb9, 0xa3, 0x65, 0x27, 0x6c, 0xcd, 0xd7, 0x92, 0x8a,
0x4a, 0xef, 0x0f, 0xbe, 0xc5, 0x2f, 0x03, 0x48, 0x95, 0x14, 0xf4, 0x6d, 0xa8, 0xd1, 0x18, 0x81,
0xdd, 0xd0, 0x0f, 0xd5, 0xb9, 0xde, 0x7b, 0x2c, 0x60, 0xe1, 0x65, 0x34, 0xe7, 0xfd, 0x26, 0x9e,
0x2a, 0xf9, 0x7a, 0x54, 0x45, 0xef, 0x43, 0x0f, 0xa6, 0x05, 0x35, 0xcd, 0x46, 0x92, 0x37, 0x0c,
0x58, 0xe8, 0x88, 0x13, 0x44, 0x17, 0x2c, 0x53, 0x97, 0x9e, 0xd5, 0xb1, 0xed, 0x88, 0x3e, 0xd8,
0x8d, 0xae, 0x69, 0x53, 0x24, 0x9f, 0xde, 0x28, 0x60, 0xe1, 0x58, 0xf4, 0xf8, 0xe6, 0x1e, 0x66,
0xff, 0x0e, 0xa0, 0x03, 0xe3, 0xe7, 0xf8, 0xe9, 0x6d, 0xe5, 0x0e, 0xd0, 0x86, 0x51, 0x92, 0xbd,
0xac, 0x5d, 0x86, 0x33, 0x98, 0xbe, 0x3f, 0x8a, 0x2c, 0xc9, 0x56, 0xee, 0xb0, 0x75, 0xc4, 0x42,
0xac, 0x85, 0x6b, 0x45, 0x0f, 0x30, 0x89, 0xbb, 0xf8, 0x78, 0x07, 0x56, 0xaa, 0x24, 0x5e, 0x9d,
0xbf, 0x7d, 0xcc, 0xe7, 0xcf, 0xf9, 0xa1, 0x0c, 0x7e, 0x2a, 0x83, 0xc7, 0x6d, 0x19, 0x8b, 0xc1,
0x76, 0xd2, 0x31, 0xb7, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x1d, 0x42, 0x33, 0x47, 0x01,
0x00, 0x00,
// 257 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0xcd, 0x4b, 0xcf,
0xcc, 0x4b, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2c, 0x28, 0xcd, 0x29, 0xcd, 0xcd,
0x2c, 0x2a, 0x48, 0x96, 0x92, 0x4e, 0xcf, 0xcf, 0x4f, 0xcf, 0x49, 0xd5, 0x07, 0x4b, 0x24, 0x95,
0xa6, 0xe9, 0xa7, 0xe6, 0x16, 0x94, 0x54, 0x42, 0xd4, 0x29, 0x75, 0x30, 0x72, 0x71, 0xf9, 0xe4,
0xa7, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x19, 0x71, 0x71, 0x14, 0xa7, 0x96, 0xa5,
0x16, 0x65, 0x96, 0x54, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x19, 0x89, 0xe9, 0xc1, 0x4d, 0xd2,
0x03, 0x2a, 0x0c, 0x86, 0xca, 0x06, 0xc1, 0xd5, 0x09, 0x49, 0x70, 0xb1, 0xe7, 0xa6, 0x16, 0x17,
0x27, 0xa6, 0xa7, 0x4a, 0x30, 0x01, 0xb5, 0x70, 0x06, 0xc1, 0xb8, 0x42, 0x02, 0x5c, 0xcc, 0xa5,
0x45, 0x79, 0x12, 0xcc, 0x60, 0x51, 0x10, 0x53, 0x48, 0x0a, 0x68, 0x7e, 0x49, 0x51, 0x6a, 0x62,
0xae, 0x67, 0x8a, 0x04, 0x0b, 0x50, 0x98, 0x35, 0x08, 0xce, 0xd7, 0xb2, 0xe2, 0xe2, 0x46, 0xb2,
0x40, 0x88, 0x93, 0x8b, 0xd5, 0xc5, 0xd5, 0x29, 0xd4, 0x5d, 0x80, 0x41, 0x88, 0x83, 0x8b, 0xc5,
0xd3, 0xcf, 0xcd, 0x5f, 0x80, 0x51, 0x88, 0x9b, 0x8b, 0x3d, 0xdc, 0x31, 0xc8, 0xcf, 0xd3, 0xcf,
0x5d, 0x80, 0x09, 0xa4, 0xc2, 0x35, 0x28, 0xc8, 0x3f, 0x48, 0x80, 0xd9, 0xc8, 0x81, 0x8b, 0xcd,
0x15, 0xec, 0x7d, 0x21, 0x33, 0x2e, 0x66, 0xa0, 0x29, 0x42, 0xa2, 0xa8, 0xce, 0x86, 0xfa, 0x4f,
0x4a, 0x4c, 0x0f, 0x12, 0x18, 0x7a, 0xb0, 0xc0, 0xd0, 0x73, 0x05, 0x05, 0x86, 0x12, 0x43, 0x12,
0x1b, 0x58, 0xc4, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x1d, 0x42, 0x33, 0x47, 0x01, 0x00,
0x00,
}

View file

@ -72,7 +72,7 @@ func init() { proto.RegisterFile("errors.proto", fileDescriptor_errors_f003e48c1
var fileDescriptor_errors_f003e48c15598612 = []byte{
// 106 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2d, 0x2a, 0xca,
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2d, 0x2a, 0xca,
0x2f, 0x2a, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2c, 0x28, 0xcd, 0x29, 0xcd, 0xcd,
0x2c, 0x2a, 0x48, 0x56, 0x72, 0xe3, 0xe2, 0x72, 0x05, 0x49, 0x39, 0x27, 0x96, 0x16, 0xa7, 0x0a,
0x49, 0x70, 0xb1, 0xe7, 0xa6, 0x16, 0x17, 0x27, 0xa6, 0xa7, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70,

View file

@ -413,34 +413,34 @@ var _LanguageRuntime_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("language.proto", fileDescriptor_language_840dd930e81005a9) }
var fileDescriptor_language_840dd930e81005a9 = []byte{
// 451 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0xcf, 0x6e, 0xd4, 0x30,
0x10, 0xc6, 0x9b, 0xa6, 0xfb, 0x6f, 0x16, 0x5a, 0x64, 0xb5, 0x2b, 0x93, 0x5e, 0x42, 0x00, 0x91,
0x53, 0x2a, 0x15, 0x81, 0x28, 0x27, 0x10, 0x54, 0x15, 0x12, 0x07, 0x64, 0x1e, 0x00, 0xb9, 0xc9,
0x6c, 0x14, 0x9a, 0xd8, 0xc6, 0xb1, 0x41, 0x79, 0x4a, 0x5e, 0x85, 0x47, 0x40, 0xb1, 0x93, 0x65,
0xa1, 0x8b, 0x7a, 0xf3, 0x37, 0xf9, 0x26, 0xfe, 0xf9, 0xb3, 0x07, 0x0e, 0x6b, 0x2e, 0x4a, 0xcb,
0x4b, 0xcc, 0x94, 0x96, 0x46, 0x92, 0x85, 0xb2, 0xb5, 0x6d, 0x2a, 0xad, 0xf2, 0xe8, 0x9e, 0xaa,
0x6d, 0x59, 0x09, 0xff, 0x21, 0x3a, 0x2d, 0xa5, 0x2c, 0x6b, 0x3c, 0x73, 0xea, 0xda, 0xae, 0xcf,
0xb0, 0x51, 0xa6, 0xf3, 0x1f, 0x13, 0x0e, 0x0f, 0xaf, 0xd0, 0x30, 0xfc, 0x66, 0x2b, 0x8d, 0xc5,
0x27, 0xd7, 0xd7, 0xf6, 0x12, 0x5b, 0x43, 0x28, 0xcc, 0x94, 0x96, 0x5f, 0x31, 0x37, 0x34, 0x88,
0x83, 0x74, 0xc1, 0x46, 0x49, 0x1e, 0x40, 0xa8, 0x7e, 0x14, 0x74, 0xdf, 0x55, 0xfb, 0xe5, 0xe0,
0x2d, 0x35, 0x6f, 0x68, 0xb8, 0xf1, 0xf6, 0x32, 0xf9, 0x0c, 0xd1, 0xae, 0x2d, 0x5a, 0x25, 0x45,
0x8b, 0xe4, 0x05, 0xcc, 0x3c, 0x6d, 0x4b, 0x83, 0x38, 0x4c, 0x97, 0xe7, 0xa7, 0xd9, 0xe6, 0x20,
0x99, 0x37, 0xbf, 0x47, 0x85, 0xa2, 0x40, 0x91, 0x77, 0x6c, 0xf4, 0x26, 0x3f, 0xf7, 0x01, 0x98,
0x15, 0x77, 0x93, 0x1e, 0xc3, 0xa4, 0x35, 0x3c, 0xbf, 0x19, 0x58, 0xbd, 0x18, 0xf9, 0xc3, 0x9d,
0xfc, 0x07, 0x7f, 0xf1, 0x13, 0x02, 0x07, 0x5c, 0x97, 0x2d, 0x9d, 0xc4, 0x61, 0xba, 0x60, 0x6e,
0x4d, 0x2e, 0x60, 0x9a, 0x4b, 0xb1, 0xae, 0x4a, 0x3a, 0x75, 0xd0, 0x8f, 0xb6, 0xa0, 0xff, 0x60,
0x65, 0xef, 0x9c, 0xe7, 0x52, 0x18, 0xdd, 0xb1, 0xa1, 0x81, 0xac, 0x60, 0x5a, 0xe8, 0x8e, 0x59,
0x41, 0x67, 0x71, 0x90, 0xce, 0xd9, 0xa0, 0x48, 0x04, 0x73, 0xc5, 0x35, 0xaf, 0x6b, 0xac, 0xe9,
0x3c, 0x0e, 0xd2, 0x09, 0xdb, 0x68, 0xf2, 0x0c, 0x8e, 0x1a, 0x29, 0x2a, 0x23, 0xf5, 0x17, 0x5e,
0x14, 0x1a, 0xdb, 0x96, 0x2e, 0x1c, 0xe4, 0xe1, 0x50, 0x7e, 0xeb, 0xab, 0xd1, 0x05, 0x2c, 0xb7,
0xf6, 0xec, 0x8f, 0x79, 0x83, 0xdd, 0x10, 0x49, 0xbf, 0xec, 0xe3, 0xf8, 0xce, 0x6b, 0x8b, 0x63,
0x1c, 0x4e, 0xbc, 0xde, 0x7f, 0x15, 0x24, 0x8f, 0x61, 0xe9, 0xc8, 0x87, 0x7b, 0x39, 0x86, 0x09,
0x6a, 0x2d, 0xf5, 0xd0, 0xec, 0xc5, 0xf9, 0xaf, 0x00, 0x8e, 0x3e, 0x0e, 0xef, 0x8e, 0x59, 0x61,
0xaa, 0x06, 0x49, 0x0e, 0xe4, 0xf6, 0xfd, 0x92, 0x27, 0x5b, 0x89, 0xfc, 0xf7, 0x85, 0x45, 0x4f,
0xef, 0x70, 0x79, 0x98, 0x64, 0x8f, 0xbc, 0x84, 0xb0, 0x0f, 0xe9, 0x64, 0x67, 0xce, 0xd1, 0xea,
0xdf, 0xf2, 0xa6, 0xef, 0x0d, 0xdc, 0xbf, 0x42, 0xe3, 0xff, 0xf7, 0x41, 0xac, 0x25, 0x59, 0x65,
0x7e, 0x1c, 0xb2, 0x71, 0x1c, 0xb2, 0xcb, 0x7e, 0x1c, 0xa2, 0x93, 0x5b, 0xcf, 0xae, 0xb7, 0x27,
0x7b, 0xd7, 0x53, 0x67, 0x7c, 0xfe, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x0e, 0x86, 0xf5, 0x91, 0x70,
0x03, 0x00, 0x00,
// 449 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x93, 0xdf, 0x6e, 0xd3, 0x3e,
0x14, 0xc7, 0xd7, 0x66, 0xed, 0xda, 0xd3, 0xdf, 0x6f, 0x43, 0x16, 0xab, 0x4c, 0x76, 0x03, 0x01,
0xc4, 0xae, 0x32, 0x69, 0x08, 0xc4, 0xb8, 0x02, 0xc1, 0x84, 0x90, 0xb8, 0x40, 0xe1, 0x01, 0x90,
0x97, 0x9c, 0x46, 0x61, 0x89, 0x6d, 0x1c, 0x1b, 0xd4, 0xa7, 0xe4, 0x55, 0x78, 0x04, 0xfc, 0x2f,
0xa5, 0xb0, 0xa2, 0xdd, 0xf9, 0x7b, 0xfc, 0x3d, 0x3e, 0x1f, 0x1f, 0xfb, 0xc0, 0x61, 0xcb, 0x78,
0x6d, 0x58, 0x8d, 0xb9, 0x54, 0x42, 0x0b, 0x32, 0x97, 0xa6, 0x35, 0x5d, 0xa3, 0x64, 0x99, 0xfe,
0x27, 0x5b, 0x53, 0x37, 0x3c, 0x6c, 0xa4, 0x27, 0xb5, 0x10, 0x75, 0x8b, 0x67, 0x5e, 0x5d, 0x99,
0xd5, 0x19, 0x76, 0x52, 0xaf, 0xc3, 0x66, 0xc6, 0xe0, 0xde, 0x3b, 0xd4, 0x05, 0x7e, 0x35, 0x8d,
0xc2, 0xea, 0xa3, 0xcf, 0xeb, 0x9d, 0xc4, 0x5e, 0x13, 0x0a, 0x07, 0xd6, 0xf5, 0x05, 0x4b, 0x4d,
0x47, 0xf7, 0x47, 0xa7, 0xf3, 0x62, 0x90, 0xe4, 0x0e, 0x24, 0xf2, 0x7b, 0x45, 0xc7, 0x3e, 0xea,
0x96, 0xd1, 0x5b, 0x2b, 0xd6, 0xd1, 0x64, 0xe3, 0x75, 0x32, 0xfb, 0x04, 0xe9, 0xae, 0x12, 0xbd,
0x14, 0xbc, 0x47, 0xf2, 0xcc, 0xe6, 0x85, 0x90, 0xad, 0x91, 0x9c, 0x2e, 0xce, 0x4f, 0xf2, 0xcd,
0x45, 0xf2, 0x60, 0x7e, 0x8b, 0x12, 0x79, 0x85, 0xbc, 0x5c, 0x17, 0x83, 0x37, 0xfb, 0x31, 0x06,
0x28, 0x0c, 0xbf, 0x9d, 0xf4, 0x2e, 0x4c, 0x7a, 0xcd, 0xca, 0xeb, 0xc8, 0x1a, 0xc4, 0xc0, 0x9f,
0xec, 0xe4, 0xdf, 0xff, 0x83, 0x9f, 0x10, 0xd8, 0x67, 0xaa, 0xee, 0xe9, 0xc4, 0xe2, 0xcd, 0x0b,
0xbf, 0x26, 0x17, 0x30, 0x2d, 0x05, 0x5f, 0x35, 0x35, 0x9d, 0x7a, 0xe8, 0x07, 0x5b, 0xd0, 0xbf,
0xb1, 0xf2, 0x37, 0xde, 0x73, 0xc9, 0xb5, 0x5a, 0x17, 0x31, 0x81, 0x2c, 0x61, 0x5a, 0x59, 0x69,
0x38, 0x3d, 0xb0, 0x75, 0x66, 0x45, 0x54, 0x24, 0x85, 0x99, 0x64, 0x8a, 0xb5, 0x2d, 0xb6, 0x74,
0x66, 0x77, 0x26, 0xc5, 0x46, 0x93, 0x27, 0x70, 0xd4, 0x09, 0xde, 0x68, 0xa1, 0x3e, 0xb3, 0xaa,
0x52, 0xd8, 0xf7, 0x74, 0xee, 0x21, 0x0f, 0x63, 0xf8, 0x75, 0x88, 0xa6, 0x17, 0xb0, 0xd8, 0xaa,
0xe9, 0xae, 0x79, 0x8d, 0xeb, 0xd8, 0x12, 0xb7, 0x74, 0xed, 0xf8, 0xc6, 0x5a, 0x83, 0x43, 0x3b,
0xbc, 0x78, 0x39, 0x7e, 0x31, 0xca, 0x1e, 0xc2, 0xc2, 0x93, 0xc7, 0x77, 0xb1, 0x46, 0x54, 0x4a,
0xa8, 0x98, 0x1c, 0xc4, 0xf9, 0xcf, 0x11, 0x1c, 0x7d, 0x88, 0xff, 0xce, 0xba, 0x75, 0xd3, 0x21,
0x29, 0x81, 0xdc, 0x7c, 0x5f, 0xf2, 0x68, 0xab, 0x23, 0xff, 0xfc, 0x61, 0xe9, 0xe3, 0x5b, 0x5c,
0x01, 0x26, 0xdb, 0x23, 0xcf, 0x21, 0x71, 0x4d, 0x3a, 0xde, 0xd9, 0xe7, 0x74, 0xf9, 0x77, 0x78,
0x93, 0xf7, 0x0a, 0xfe, 0xb7, 0xe7, 0x86, 0xf3, 0xde, 0xf3, 0x95, 0x20, 0xcb, 0x3c, 0x8c, 0x43,
0x3e, 0x8c, 0x43, 0x7e, 0xe9, 0xc6, 0x21, 0x3d, 0xbe, 0xf1, 0xed, 0x9c, 0x3d, 0xdb, 0xbb, 0x9a,
0x7a, 0xe3, 0xd3, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x0e, 0x86, 0xf5, 0x91, 0x70, 0x03, 0x00,
0x00,
}

View file

@ -121,7 +121,7 @@ func init() { proto.RegisterFile("plugin.proto", fileDescriptor_plugin_6374fd953
var fileDescriptor_plugin_6374fd953f733c72 = []byte{
// 130 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xc8, 0x29, 0x4d,
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xc8, 0x29, 0x4d,
0xcf, 0xcc, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x2c, 0x28, 0xcd, 0x29, 0xcd, 0xcd,
0x2c, 0x2a, 0x48, 0x56, 0x52, 0xe3, 0xe2, 0x0a, 0x00, 0x4b, 0x79, 0xe6, 0xa5, 0xe5, 0x0b, 0x49,
0x70, 0xb1, 0x97, 0xa5, 0x16, 0x15, 0x67, 0xe6, 0xe7, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06,

View file

@ -48,7 +48,7 @@ func (x DiffResponse_DiffChanges) String() string {
return proto.EnumName(DiffResponse_DiffChanges_name, int32(x))
}
func (DiffResponse_DiffChanges) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{8, 0}
return fileDescriptor_provider_5951afc12b1894bc, []int{8, 0}
}
type ConfigureRequest struct {
@ -62,7 +62,7 @@ func (m *ConfigureRequest) Reset() { *m = ConfigureRequest{} }
func (m *ConfigureRequest) String() string { return proto.CompactTextString(m) }
func (*ConfigureRequest) ProtoMessage() {}
func (*ConfigureRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{0}
return fileDescriptor_provider_5951afc12b1894bc, []int{0}
}
func (m *ConfigureRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConfigureRequest.Unmarshal(m, b)
@ -101,7 +101,7 @@ func (m *ConfigureErrorMissingKeys) Reset() { *m = ConfigureErrorMissing
func (m *ConfigureErrorMissingKeys) String() string { return proto.CompactTextString(m) }
func (*ConfigureErrorMissingKeys) ProtoMessage() {}
func (*ConfigureErrorMissingKeys) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{1}
return fileDescriptor_provider_5951afc12b1894bc, []int{1}
}
func (m *ConfigureErrorMissingKeys) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConfigureErrorMissingKeys.Unmarshal(m, b)
@ -140,7 +140,7 @@ func (m *ConfigureErrorMissingKeys_MissingKey) Reset() { *m = ConfigureE
func (m *ConfigureErrorMissingKeys_MissingKey) String() string { return proto.CompactTextString(m) }
func (*ConfigureErrorMissingKeys_MissingKey) ProtoMessage() {}
func (*ConfigureErrorMissingKeys_MissingKey) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{1, 0}
return fileDescriptor_provider_5951afc12b1894bc, []int{1, 0}
}
func (m *ConfigureErrorMissingKeys_MissingKey) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ConfigureErrorMissingKeys_MissingKey.Unmarshal(m, b)
@ -177,6 +177,7 @@ func (m *ConfigureErrorMissingKeys_MissingKey) GetDescription() string {
type InvokeRequest struct {
Tok string `protobuf:"bytes,1,opt,name=tok" json:"tok,omitempty"`
Args *_struct.Struct `protobuf:"bytes,2,opt,name=args" json:"args,omitempty"`
Provider string `protobuf:"bytes,3,opt,name=provider" json:"provider,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -186,7 +187,7 @@ func (m *InvokeRequest) Reset() { *m = InvokeRequest{} }
func (m *InvokeRequest) String() string { return proto.CompactTextString(m) }
func (*InvokeRequest) ProtoMessage() {}
func (*InvokeRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{2}
return fileDescriptor_provider_5951afc12b1894bc, []int{2}
}
func (m *InvokeRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_InvokeRequest.Unmarshal(m, b)
@ -220,6 +221,13 @@ func (m *InvokeRequest) GetArgs() *_struct.Struct {
return nil
}
func (m *InvokeRequest) GetProvider() string {
if m != nil {
return m.Provider
}
return ""
}
type InvokeResponse struct {
Return *_struct.Struct `protobuf:"bytes,1,opt,name=return" json:"return,omitempty"`
Failures []*CheckFailure `protobuf:"bytes,2,rep,name=failures" json:"failures,omitempty"`
@ -232,7 +240,7 @@ func (m *InvokeResponse) Reset() { *m = InvokeResponse{} }
func (m *InvokeResponse) String() string { return proto.CompactTextString(m) }
func (*InvokeResponse) ProtoMessage() {}
func (*InvokeResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{3}
return fileDescriptor_provider_5951afc12b1894bc, []int{3}
}
func (m *InvokeResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_InvokeResponse.Unmarshal(m, b)
@ -279,7 +287,7 @@ func (m *CheckRequest) Reset() { *m = CheckRequest{} }
func (m *CheckRequest) String() string { return proto.CompactTextString(m) }
func (*CheckRequest) ProtoMessage() {}
func (*CheckRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{4}
return fileDescriptor_provider_5951afc12b1894bc, []int{4}
}
func (m *CheckRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckRequest.Unmarshal(m, b)
@ -332,7 +340,7 @@ func (m *CheckResponse) Reset() { *m = CheckResponse{} }
func (m *CheckResponse) String() string { return proto.CompactTextString(m) }
func (*CheckResponse) ProtoMessage() {}
func (*CheckResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{5}
return fileDescriptor_provider_5951afc12b1894bc, []int{5}
}
func (m *CheckResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckResponse.Unmarshal(m, b)
@ -378,7 +386,7 @@ func (m *CheckFailure) Reset() { *m = CheckFailure{} }
func (m *CheckFailure) String() string { return proto.CompactTextString(m) }
func (*CheckFailure) ProtoMessage() {}
func (*CheckFailure) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{6}
return fileDescriptor_provider_5951afc12b1894bc, []int{6}
}
func (m *CheckFailure) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckFailure.Unmarshal(m, b)
@ -426,7 +434,7 @@ func (m *DiffRequest) Reset() { *m = DiffRequest{} }
func (m *DiffRequest) String() string { return proto.CompactTextString(m) }
func (*DiffRequest) ProtoMessage() {}
func (*DiffRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{7}
return fileDescriptor_provider_5951afc12b1894bc, []int{7}
}
func (m *DiffRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DiffRequest.Unmarshal(m, b)
@ -488,7 +496,7 @@ func (m *DiffResponse) Reset() { *m = DiffResponse{} }
func (m *DiffResponse) String() string { return proto.CompactTextString(m) }
func (*DiffResponse) ProtoMessage() {}
func (*DiffResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{8}
return fileDescriptor_provider_5951afc12b1894bc, []int{8}
}
func (m *DiffResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DiffResponse.Unmarshal(m, b)
@ -548,7 +556,7 @@ func (m *CreateRequest) Reset() { *m = CreateRequest{} }
func (m *CreateRequest) String() string { return proto.CompactTextString(m) }
func (*CreateRequest) ProtoMessage() {}
func (*CreateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{9}
return fileDescriptor_provider_5951afc12b1894bc, []int{9}
}
func (m *CreateRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CreateRequest.Unmarshal(m, b)
@ -594,7 +602,7 @@ func (m *CreateResponse) Reset() { *m = CreateResponse{} }
func (m *CreateResponse) String() string { return proto.CompactTextString(m) }
func (*CreateResponse) ProtoMessage() {}
func (*CreateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{10}
return fileDescriptor_provider_5951afc12b1894bc, []int{10}
}
func (m *CreateResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CreateResponse.Unmarshal(m, b)
@ -641,7 +649,7 @@ func (m *ReadRequest) Reset() { *m = ReadRequest{} }
func (m *ReadRequest) String() string { return proto.CompactTextString(m) }
func (*ReadRequest) ProtoMessage() {}
func (*ReadRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{11}
return fileDescriptor_provider_5951afc12b1894bc, []int{11}
}
func (m *ReadRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadRequest.Unmarshal(m, b)
@ -694,7 +702,7 @@ func (m *ReadResponse) Reset() { *m = ReadResponse{} }
func (m *ReadResponse) String() string { return proto.CompactTextString(m) }
func (*ReadResponse) ProtoMessage() {}
func (*ReadResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{12}
return fileDescriptor_provider_5951afc12b1894bc, []int{12}
}
func (m *ReadResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadResponse.Unmarshal(m, b)
@ -742,7 +750,7 @@ func (m *UpdateRequest) Reset() { *m = UpdateRequest{} }
func (m *UpdateRequest) String() string { return proto.CompactTextString(m) }
func (*UpdateRequest) ProtoMessage() {}
func (*UpdateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{13}
return fileDescriptor_provider_5951afc12b1894bc, []int{13}
}
func (m *UpdateRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UpdateRequest.Unmarshal(m, b)
@ -801,7 +809,7 @@ func (m *UpdateResponse) Reset() { *m = UpdateResponse{} }
func (m *UpdateResponse) String() string { return proto.CompactTextString(m) }
func (*UpdateResponse) ProtoMessage() {}
func (*UpdateResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{14}
return fileDescriptor_provider_5951afc12b1894bc, []int{14}
}
func (m *UpdateResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UpdateResponse.Unmarshal(m, b)
@ -841,7 +849,7 @@ func (m *DeleteRequest) Reset() { *m = DeleteRequest{} }
func (m *DeleteRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteRequest) ProtoMessage() {}
func (*DeleteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{15}
return fileDescriptor_provider_5951afc12b1894bc, []int{15}
}
func (m *DeleteRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_DeleteRequest.Unmarshal(m, b)
@ -897,7 +905,7 @@ func (m *ErrorResourceInitFailed) Reset() { *m = ErrorResourceInitFailed
func (m *ErrorResourceInitFailed) String() string { return proto.CompactTextString(m) }
func (*ErrorResourceInitFailed) ProtoMessage() {}
func (*ErrorResourceInitFailed) Descriptor() ([]byte, []int) {
return fileDescriptor_provider_4e2b47bc2ad920ea, []int{16}
return fileDescriptor_provider_5951afc12b1894bc, []int{16}
}
func (m *ErrorResourceInitFailed) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ErrorResourceInitFailed.Unmarshal(m, b)
@ -1362,64 +1370,64 @@ var _ResourceProvider_serviceDesc = grpc.ServiceDesc{
Metadata: "provider.proto",
}
func init() { proto.RegisterFile("provider.proto", fileDescriptor_provider_4e2b47bc2ad920ea) }
func init() { proto.RegisterFile("provider.proto", fileDescriptor_provider_5951afc12b1894bc) }
var fileDescriptor_provider_4e2b47bc2ad920ea = []byte{
// 881 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x56, 0xdd, 0x6e, 0x1b, 0x45,
0x14, 0xce, 0xda, 0x89, 0x1b, 0x1f, 0xff, 0x68, 0x35, 0x40, 0xe2, 0x6e, 0xb9, 0x88, 0x96, 0x9b,
0x0a, 0xa4, 0x0d, 0x4a, 0x2f, 0x80, 0xaa, 0x15, 0x28, 0x89, 0x03, 0x56, 0x55, 0xa7, 0x6c, 0x55,
0x2a, 0xb8, 0x41, 0x9b, 0xdd, 0x63, 0x67, 0xf0, 0x66, 0x67, 0x99, 0x9d, 0x35, 0x0a, 0xe2, 0x05,
0x2a, 0xde, 0x80, 0xc7, 0xe0, 0xd9, 0x78, 0x00, 0xb4, 0x33, 0xb3, 0xeb, 0x9d, 0x38, 0xb1, 0x4d,
0x55, 0xd1, 0xbb, 0x39, 0x73, 0x7e, 0xbe, 0xf3, 0x3f, 0x03, 0xfd, 0x94, 0xb3, 0x39, 0x8d, 0x90,
0x7b, 0x29, 0x67, 0x82, 0x91, 0x76, 0x9a, 0xc7, 0xf9, 0x15, 0xe5, 0x69, 0xe8, 0x74, 0xd3, 0x38,
0x9f, 0xd2, 0x44, 0x31, 0x9c, 0x07, 0x53, 0xc6, 0xa6, 0x31, 0x1e, 0x4a, 0xea, 0x22, 0x9f, 0x1c,
0xe2, 0x55, 0x2a, 0xae, 0x35, 0xf3, 0xe3, 0x9b, 0xcc, 0x4c, 0xf0, 0x3c, 0x14, 0x8a, 0xeb, 0xfe,
0x65, 0x81, 0x7d, 0xc2, 0x92, 0x09, 0x9d, 0xe6, 0x1c, 0x7d, 0xfc, 0x35, 0xc7, 0x4c, 0x90, 0xef,
0xa0, 0x3d, 0x0f, 0x38, 0x0d, 0x2e, 0x62, 0xcc, 0x06, 0xd6, 0x41, 0xf3, 0x61, 0xe7, 0xe8, 0x53,
0xaf, 0x02, 0xf7, 0x6e, 0xca, 0x7b, 0x3f, 0x94, 0xc2, 0xc3, 0x44, 0xf0, 0x6b, 0x7f, 0xa1, 0xec,
0x3c, 0x81, 0xbe, 0xc9, 0x24, 0x36, 0x34, 0x67, 0x78, 0x3d, 0xb0, 0x0e, 0xac, 0x87, 0x6d, 0xbf,
0x38, 0x92, 0x0f, 0x61, 0x67, 0x1e, 0xc4, 0x39, 0x0e, 0x1a, 0xf2, 0x4e, 0x11, 0x8f, 0x1b, 0x5f,
0x5a, 0xee, 0xdf, 0x16, 0xdc, 0xaf, 0xc0, 0x86, 0x9c, 0x33, 0xfe, 0x9c, 0x66, 0x19, 0x4d, 0xa6,
0xcf, 0xf0, 0x3a, 0x23, 0xdf, 0x43, 0xe7, 0x6a, 0x41, 0x6a, 0x3f, 0x0f, 0x6f, 0xf3, 0xf3, 0xa6,
0xaa, 0xb7, 0x38, 0xfb, 0x75, 0x1b, 0xce, 0x31, 0xc0, 0x82, 0x45, 0x08, 0x6c, 0x27, 0xc1, 0x15,
0x6a, 0x5f, 0xe5, 0x99, 0x1c, 0x40, 0x27, 0xc2, 0x2c, 0xe4, 0x34, 0x15, 0x94, 0x25, 0xda, 0xe5,
0xfa, 0x95, 0x3b, 0x86, 0xde, 0x28, 0x99, 0xb3, 0x59, 0x95, 0x4d, 0x1b, 0x9a, 0x82, 0xcd, 0xca,
0x88, 0x05, 0x9b, 0x91, 0xcf, 0x60, 0x3b, 0xe0, 0xd3, 0x4c, 0x6a, 0x77, 0x8e, 0xf6, 0x3d, 0x55,
0x21, 0xaf, 0xac, 0x90, 0xf7, 0x52, 0x56, 0xc8, 0x97, 0x42, 0xee, 0x1c, 0xfa, 0xa5, 0xbd, 0x2c,
0x65, 0x49, 0x86, 0xe4, 0x10, 0x5a, 0x1c, 0x45, 0xce, 0x13, 0x69, 0x73, 0x85, 0x01, 0x2d, 0x46,
0x1e, 0xc1, 0xee, 0x24, 0xa0, 0x71, 0xce, 0xb1, 0xc0, 0x6c, 0x4a, 0x95, 0x5a, 0x9a, 0x2e, 0x31,
0x9c, 0x9d, 0x29, 0xbe, 0x5f, 0x09, 0xba, 0xbf, 0x43, 0x57, 0x72, 0x6a, 0x61, 0x94, 0x90, 0x6d,
0xbf, 0x38, 0x16, 0x61, 0xb0, 0x38, 0x5a, 0x1f, 0x46, 0x21, 0x54, 0x08, 0x27, 0xf8, 0x5b, 0x36,
0x68, 0xae, 0x11, 0x2e, 0x84, 0xdc, 0x1c, 0x7a, 0x1a, 0x7b, 0x11, 0x32, 0x4d, 0xd2, 0x5c, 0x64,
0x6b, 0x43, 0x56, 0x62, 0x6f, 0x17, 0xf2, 0xb1, 0x0e, 0x59, 0x73, 0x88, 0x03, 0xbb, 0x29, 0x67,
0x29, 0x72, 0x51, 0x36, 0x6c, 0x45, 0x93, 0xbd, 0xa2, 0x08, 0x41, 0x56, 0xf5, 0x80, 0xa6, 0xdc,
0x37, 0x16, 0x74, 0x4e, 0xe9, 0x64, 0x52, 0xa6, 0xad, 0x0f, 0x0d, 0x1a, 0x69, 0xed, 0x06, 0x8d,
0xca, 0x34, 0x36, 0x96, 0xd3, 0xd8, 0xfc, 0x2f, 0x69, 0xdc, 0xde, 0x24, 0x8d, 0xff, 0x58, 0xd0,
0x55, 0xbe, 0xe8, 0x34, 0x3a, 0xb0, 0xcb, 0x31, 0x8d, 0x83, 0x50, 0xcf, 0x75, 0xdb, 0xaf, 0x68,
0x32, 0x80, 0x7b, 0x99, 0x50, 0x23, 0xdf, 0x90, 0xac, 0x92, 0x24, 0x9f, 0xc3, 0x07, 0x11, 0xc6,
0x28, 0xf0, 0x18, 0x27, 0xac, 0x98, 0x7a, 0xa9, 0x21, 0xfd, 0xdd, 0xf5, 0x6f, 0x63, 0x91, 0xa7,
0x70, 0x2f, 0xbc, 0x0c, 0x92, 0x29, 0x2a, 0x47, 0xfb, 0x47, 0x9f, 0xd4, 0x92, 0x5f, 0xf7, 0x48,
0x12, 0x27, 0x4a, 0xd4, 0x2f, 0x75, 0xdc, 0xa7, 0x2a, 0x85, 0xfa, 0x9e, 0xd8, 0xd0, 0x3d, 0x1d,
0x9d, 0x9d, 0xfd, 0xfc, 0x6a, 0xfc, 0x6c, 0x7c, 0xfe, 0x7a, 0x6c, 0x6f, 0x91, 0x1e, 0xb4, 0xe5,
0xcd, 0xf8, 0x7c, 0x3c, 0xb4, 0xad, 0x8a, 0x7c, 0x79, 0xfe, 0x7c, 0x68, 0x37, 0xdc, 0x9f, 0xa0,
0x77, 0xc2, 0x31, 0x10, 0x78, 0x77, 0xeb, 0x7e, 0x01, 0xa0, 0x2b, 0x49, 0x71, 0x6d, 0x03, 0xd7,
0x44, 0xdd, 0x1f, 0xa1, 0x5f, 0xda, 0xd6, 0x39, 0xbd, 0x59, 0xe0, 0xb7, 0x36, 0x7d, 0x09, 0x1d,
0x1f, 0x83, 0x68, 0xf3, 0xc6, 0x31, 0x91, 0x9a, 0x9b, 0x23, 0xbd, 0x86, 0xae, 0x42, 0x7a, 0xd7,
0x21, 0xfc, 0x69, 0x41, 0xef, 0x55, 0x1a, 0xd5, 0x52, 0xff, 0x3e, 0xdb, 0x7f, 0x04, 0xfd, 0xd2,
0x19, 0x1d, 0xa8, 0x19, 0x98, 0xb5, 0x79, 0x60, 0xbf, 0x40, 0xef, 0x54, 0xf6, 0xf9, 0xff, 0x50,
0x9d, 0x3f, 0x60, 0x5f, 0x3e, 0x58, 0x3e, 0x66, 0x2c, 0xe7, 0x21, 0x8e, 0x12, 0x2a, 0x8a, 0x8d,
0x84, 0xd1, 0x3b, 0x2b, 0x54, 0x31, 0xec, 0x6a, 0x5f, 0x15, 0x9e, 0xc9, 0x61, 0xd7, 0xe4, 0xd1,
0x9b, 0x1d, 0xb0, 0x4b, 0xe4, 0x17, 0xfa, 0xff, 0x41, 0x8e, 0xa1, 0x5d, 0x3d, 0xa6, 0xe4, 0xc1,
0x8a, 0xaf, 0x80, 0xb3, 0xb7, 0x84, 0x3e, 0x2c, 0xfe, 0x22, 0xee, 0x16, 0xf9, 0x1a, 0x5a, 0xea,
0x1d, 0x23, 0x83, 0x9a, 0x01, 0xe3, 0xa9, 0x74, 0xee, 0xdf, 0xc2, 0x51, 0xa5, 0x73, 0xb7, 0xc8,
0x13, 0xd8, 0x91, 0xdb, 0x99, 0x2c, 0x6d, 0xf2, 0x52, 0x7d, 0xb0, 0xcc, 0xa8, 0xb4, 0xbf, 0x82,
0xed, 0x62, 0xa7, 0x90, 0xbd, 0xa5, 0x4d, 0xa4, 0x74, 0xf7, 0xef, 0xd8, 0x50, 0xca, 0x73, 0x35,
0xf3, 0x86, 0xe7, 0xc6, 0x8a, 0x31, 0x3c, 0x37, 0x17, 0x84, 0xc2, 0x2e, 0xe6, 0xcd, 0xc0, 0xae,
0x8d, 0xba, 0x81, 0x5d, 0x1f, 0x4c, 0x85, 0xad, 0x7a, 0xd8, 0xc0, 0x36, 0x66, 0xcc, 0xc0, 0x36,
0x1b, 0x5e, 0x66, 0xad, 0xa5, 0x3a, 0xd7, 0x30, 0x60, 0x34, 0xf3, 0x8a, 0xa2, 0x3d, 0x86, 0xd6,
0x49, 0x90, 0x84, 0x18, 0x93, 0x3b, 0x64, 0x56, 0xe8, 0x7e, 0x03, 0xbd, 0x6f, 0x51, 0xbc, 0x90,
0x1f, 0xd5, 0x51, 0x32, 0x61, 0x77, 0x9a, 0xf8, 0xa8, 0xe6, 0xd8, 0x42, 0xdc, 0xdd, 0xba, 0x68,
0x49, 0xc1, 0x47, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x9e, 0xbd, 0xd5, 0xcc, 0x09, 0x0b, 0x00,
0x00,
var fileDescriptor_provider_5951afc12b1894bc = []byte{
// 889 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xc4, 0x56, 0xdd, 0x8e, 0xdb, 0x44,
0x14, 0x5e, 0x27, 0xd9, 0x74, 0x73, 0xf2, 0xa3, 0x68, 0x80, 0xdd, 0x34, 0xe5, 0xa2, 0x32, 0x37,
0x15, 0x48, 0x0e, 0x4a, 0x2f, 0x80, 0xaa, 0x15, 0x28, 0xdd, 0x2c, 0x44, 0x55, 0xb3, 0xc5, 0x55,
0x59, 0xc1, 0x0d, 0xf2, 0xda, 0x93, 0xac, 0x89, 0x63, 0x9b, 0xf1, 0x38, 0x28, 0x88, 0x17, 0x58,
0xf1, 0x06, 0x3c, 0x06, 0xcf, 0xc6, 0x03, 0x30, 0x9e, 0x1f, 0xc7, 0x93, 0x6c, 0xb2, 0x61, 0xb5,
0xa2, 0x77, 0x73, 0x7c, 0x7e, 0xbe, 0xf3, 0x9d, 0x73, 0xe6, 0x8c, 0xa1, 0x15, 0x93, 0x68, 0xe1,
0x7b, 0x98, 0x58, 0xec, 0x40, 0x23, 0x54, 0x8b, 0xd3, 0x20, 0x9d, 0xfb, 0x24, 0x76, 0xbb, 0x8d,
0x38, 0x48, 0xa7, 0x7e, 0x28, 0x14, 0xdd, 0x47, 0xd3, 0x28, 0x9a, 0x06, 0xb8, 0xc7, 0xa5, 0xcb,
0x74, 0xd2, 0xc3, 0xf3, 0x98, 0x2e, 0xa5, 0xf2, 0xe3, 0x75, 0x65, 0x42, 0x49, 0xea, 0x52, 0xa1,
0x35, 0xff, 0x32, 0xa0, 0xfd, 0x32, 0x0a, 0x27, 0xfe, 0x34, 0x25, 0xd8, 0xc6, 0xbf, 0xa6, 0x38,
0xa1, 0xe8, 0x3b, 0xa8, 0x2d, 0x1c, 0xe2, 0x3b, 0x97, 0x01, 0x4e, 0x3a, 0xc6, 0xe3, 0xf2, 0x93,
0x7a, 0xff, 0x53, 0x2b, 0x07, 0xb7, 0xd6, 0xed, 0xad, 0x1f, 0x94, 0xf1, 0x30, 0xa4, 0x64, 0x69,
0xaf, 0x9c, 0xbb, 0xcf, 0xa1, 0xa5, 0x2b, 0x51, 0x1b, 0xca, 0x33, 0xbc, 0x64, 0x51, 0x8d, 0x27,
0x35, 0x3b, 0x3b, 0xa2, 0x0f, 0xe1, 0x70, 0xe1, 0x04, 0x29, 0xee, 0x94, 0xf8, 0x37, 0x21, 0x3c,
0x2b, 0x7d, 0x69, 0x98, 0x7f, 0x1b, 0xf0, 0x30, 0x07, 0x1b, 0x12, 0x12, 0x91, 0xd7, 0x7e, 0x92,
0xf8, 0xe1, 0xf4, 0x15, 0x5e, 0x26, 0xe8, 0x7b, 0xa8, 0xcf, 0x57, 0xa2, 0xcc, 0xb3, 0x77, 0x53,
0x9e, 0xeb, 0xae, 0xd6, 0xea, 0x6c, 0x17, 0x63, 0x74, 0x07, 0x00, 0x2b, 0x15, 0x42, 0x50, 0x09,
0x9d, 0x39, 0x96, 0xb9, 0xf2, 0x33, 0x7a, 0x0c, 0x75, 0x0f, 0x27, 0x2e, 0xf1, 0x63, 0xea, 0x47,
0xa1, 0x4c, 0xb9, 0xf8, 0xc9, 0xfc, 0x05, 0x9a, 0xa3, 0x70, 0x11, 0xcd, 0xf2, 0x6a, 0x32, 0xc6,
0x34, 0x9a, 0x29, 0xc6, 0xec, 0x88, 0x3e, 0x83, 0x8a, 0x43, 0xa6, 0x09, 0xf7, 0xae, 0xf7, 0x4f,
0x2c, 0xd1, 0x21, 0x4b, 0x75, 0xc8, 0x7a, 0xcb, 0x3b, 0x64, 0x73, 0x23, 0xd4, 0x85, 0x23, 0x35,
0x07, 0x9d, 0x32, 0x8f, 0x91, 0xcb, 0xe6, 0x02, 0x5a, 0x0a, 0x2b, 0x89, 0xa3, 0x30, 0xc1, 0xa8,
0x07, 0x55, 0x82, 0x69, 0x4a, 0x42, 0x8e, 0xb7, 0x23, 0xb8, 0x34, 0x43, 0x4f, 0xe1, 0x68, 0xe2,
0xf8, 0x01, 0xab, 0x52, 0x96, 0x4f, 0x99, 0xbb, 0x14, 0x4a, 0x78, 0x85, 0xdd, 0xd9, 0x99, 0xd0,
0xdb, 0xb9, 0xa1, 0xf9, 0x3b, 0x34, 0xb8, 0xa6, 0x40, 0x51, 0x41, 0x32, 0x8a, 0x59, 0x58, 0x46,
0x31, 0x0a, 0xbc, 0xdb, 0x29, 0x66, 0x46, 0x99, 0x71, 0x88, 0x7f, 0x4b, 0x38, 0xbd, 0x5d, 0xc6,
0x99, 0x91, 0x99, 0x42, 0x53, 0x62, 0xaf, 0x28, 0xfb, 0x61, 0x9c, 0xd2, 0xe4, 0x56, 0xca, 0xc2,
0xec, 0x6e, 0x94, 0x07, 0x92, 0xb2, 0xd4, 0xc8, 0xb6, 0xc4, 0x98, 0x50, 0x35, 0xcc, 0xb9, 0x8c,
0x8e, 0xb3, 0x26, 0x38, 0x49, 0x3e, 0x1f, 0x52, 0x32, 0xaf, 0x0d, 0xa8, 0x9f, 0xfa, 0x93, 0x89,
0x2a, 0x5b, 0x0b, 0x4a, 0xbe, 0x27, 0xbd, 0xd9, 0x49, 0x95, 0xb1, 0xb4, 0x59, 0xc6, 0xf2, 0x7f,
0x29, 0x63, 0x65, 0x9f, 0x32, 0xfe, 0x63, 0x40, 0x43, 0xe4, 0x22, 0xcb, 0xc8, 0x08, 0x11, 0x1c,
0x07, 0x8e, 0x2b, 0xef, 0x3c, 0x23, 0xa4, 0x64, 0xd4, 0x81, 0x07, 0x09, 0x15, 0xeb, 0xa0, 0xc4,
0x55, 0x4a, 0x44, 0x9f, 0xc3, 0x07, 0x1e, 0x0e, 0x30, 0xc5, 0x03, 0x3c, 0x89, 0xb2, 0x8d, 0xc0,
0x3d, 0x78, 0xbe, 0x47, 0xf6, 0x4d, 0x2a, 0xf4, 0x02, 0x1e, 0xb8, 0x57, 0x4e, 0x38, 0xc5, 0x22,
0xd1, 0x56, 0xff, 0x93, 0x42, 0xf1, 0x8b, 0x19, 0x71, 0xe1, 0xa5, 0x30, 0xb5, 0x95, 0x8f, 0xf9,
0x42, 0x94, 0x50, 0x7e, 0x67, 0x25, 0x6b, 0x9c, 0x8e, 0xce, 0xce, 0x7e, 0x7e, 0x37, 0x7e, 0x35,
0x3e, 0xbf, 0x18, 0xb7, 0x0f, 0x50, 0x13, 0x6a, 0xfc, 0xcb, 0xf8, 0x7c, 0x3c, 0x6c, 0x1b, 0xb9,
0xf8, 0xf6, 0xfc, 0xf5, 0xb0, 0x5d, 0x32, 0x7f, 0x62, 0xd3, 0xc3, 0xba, 0x41, 0xf1, 0xf6, 0xd1,
0xfd, 0x02, 0x40, 0x76, 0xd2, 0xc7, 0xb7, 0x0e, 0x70, 0xc1, 0xd4, 0xfc, 0x11, 0x5a, 0x2a, 0xb6,
0xac, 0xe9, 0x7a, 0x83, 0xef, 0x1c, 0xfa, 0x0a, 0xea, 0x36, 0x76, 0xbc, 0xfd, 0x07, 0x47, 0x47,
0x2a, 0xef, 0x8f, 0x74, 0x01, 0x0d, 0x81, 0x74, 0xdf, 0x14, 0xfe, 0x34, 0xa0, 0xf9, 0x2e, 0xf6,
0x0a, 0xa5, 0x7f, 0x9f, 0xe3, 0x3f, 0x82, 0x96, 0x4a, 0x46, 0x12, 0xd5, 0x89, 0x19, 0xfb, 0x13,
0x63, 0x0b, 0xff, 0x94, 0xcf, 0xf9, 0xff, 0xd0, 0x9d, 0x3f, 0xe0, 0x84, 0x3f, 0x66, 0x2c, 0xeb,
0x28, 0x25, 0x2e, 0x1e, 0x85, 0x3e, 0xcd, 0x36, 0x12, 0xf6, 0xee, 0xad, 0x51, 0xd9, 0x65, 0x17,
0xfb, 0x2a, 0xcb, 0x8c, 0x5f, 0x76, 0x29, 0xf6, 0xaf, 0x0f, 0xa1, 0xad, 0x90, 0xdf, 0xc8, 0x37,
0x08, 0x0d, 0xa0, 0x96, 0x3f, 0xb4, 0xe8, 0xd1, 0x8e, 0xdf, 0x84, 0xee, 0xf1, 0x06, 0xfa, 0x30,
0xfb, 0x4f, 0x31, 0x0f, 0xd0, 0xd7, 0x50, 0x15, 0xef, 0x18, 0xea, 0x14, 0x02, 0x68, 0xcf, 0x68,
0xf7, 0xe1, 0x0d, 0x1a, 0xd1, 0x3a, 0x16, 0xe0, 0x39, 0x1c, 0xf2, 0xed, 0x8c, 0x36, 0x36, 0xb9,
0x72, 0xef, 0x6c, 0x2a, 0x72, 0xef, 0xaf, 0xa0, 0x92, 0xed, 0x14, 0x74, 0xbc, 0xb1, 0x89, 0x84,
0xef, 0xc9, 0x96, 0x0d, 0x25, 0x32, 0x17, 0x77, 0x5e, 0xcb, 0x5c, 0x5b, 0x31, 0x5a, 0xe6, 0xfa,
0x82, 0x10, 0xd8, 0xd9, 0x7d, 0xd3, 0xb0, 0x0b, 0x57, 0x5d, 0xc3, 0x2e, 0x5e, 0x4c, 0x81, 0x2d,
0x66, 0x58, 0xc3, 0xd6, 0xee, 0x98, 0x86, 0xad, 0x0f, 0x3c, 0xaf, 0x5a, 0x55, 0x4c, 0xae, 0x16,
0x40, 0x1b, 0xe6, 0x1d, 0x4d, 0x7b, 0xc6, 0xa8, 0x3b, 0xa1, 0x8b, 0x03, 0xb4, 0xc5, 0x66, 0x87,
0xef, 0x37, 0xd0, 0xfc, 0x16, 0xd3, 0x37, 0xfc, 0x27, 0x76, 0x14, 0x4e, 0xa2, 0xad, 0x21, 0x3e,
0x2a, 0x24, 0xb6, 0x32, 0x37, 0x0f, 0x2e, 0xab, 0xdc, 0xf0, 0xe9, 0xbf, 0x01, 0x00, 0x00, 0xff,
0xff, 0x2f, 0x82, 0x27, 0xcd, 0x25, 0x0b, 0x00, 0x00,
}

View file

@ -33,6 +33,7 @@ type ReadResourceRequest struct {
Parent string `protobuf:"bytes,4,opt,name=parent" json:"parent,omitempty"`
Properties *_struct.Struct `protobuf:"bytes,5,opt,name=properties" json:"properties,omitempty"`
Dependencies []string `protobuf:"bytes,6,rep,name=dependencies" json:"dependencies,omitempty"`
Provider string `protobuf:"bytes,7,opt,name=provider" json:"provider,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -42,7 +43,7 @@ func (m *ReadResourceRequest) Reset() { *m = ReadResourceRequest{} }
func (m *ReadResourceRequest) String() string { return proto.CompactTextString(m) }
func (*ReadResourceRequest) ProtoMessage() {}
func (*ReadResourceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_773fb6366ec12ccd, []int{0}
return fileDescriptor_resource_5aa1dff965971124, []int{0}
}
func (m *ReadResourceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadResourceRequest.Unmarshal(m, b)
@ -104,6 +105,13 @@ func (m *ReadResourceRequest) GetDependencies() []string {
return nil
}
func (m *ReadResourceRequest) GetProvider() string {
if m != nil {
return m.Provider
}
return ""
}
// ReadResourceResponse contains the result of reading a resource's state.
type ReadResourceResponse struct {
Urn string `protobuf:"bytes,1,opt,name=urn" json:"urn,omitempty"`
@ -117,7 +125,7 @@ func (m *ReadResourceResponse) Reset() { *m = ReadResourceResponse{} }
func (m *ReadResourceResponse) String() string { return proto.CompactTextString(m) }
func (*ReadResourceResponse) ProtoMessage() {}
func (*ReadResourceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_773fb6366ec12ccd, []int{1}
return fileDescriptor_resource_5aa1dff965971124, []int{1}
}
func (m *ReadResourceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ReadResourceResponse.Unmarshal(m, b)
@ -160,6 +168,7 @@ type RegisterResourceRequest struct {
Object *_struct.Struct `protobuf:"bytes,5,opt,name=object" json:"object,omitempty"`
Protect bool `protobuf:"varint,6,opt,name=protect" json:"protect,omitempty"`
Dependencies []string `protobuf:"bytes,7,rep,name=dependencies" json:"dependencies,omitempty"`
Provider string `protobuf:"bytes,8,opt,name=provider" json:"provider,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -169,7 +178,7 @@ func (m *RegisterResourceRequest) Reset() { *m = RegisterResourceRequest
func (m *RegisterResourceRequest) String() string { return proto.CompactTextString(m) }
func (*RegisterResourceRequest) ProtoMessage() {}
func (*RegisterResourceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_773fb6366ec12ccd, []int{2}
return fileDescriptor_resource_5aa1dff965971124, []int{2}
}
func (m *RegisterResourceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterResourceRequest.Unmarshal(m, b)
@ -238,6 +247,13 @@ func (m *RegisterResourceRequest) GetDependencies() []string {
return nil
}
func (m *RegisterResourceRequest) GetProvider() string {
if m != nil {
return m.Provider
}
return ""
}
// RegisterResourceResponse is returned by the engine after a resource has finished being initialized. It includes the
// auto-assigned URN, the provider-assigned ID, and any other properties initialized by the engine.
type RegisterResourceResponse struct {
@ -255,7 +271,7 @@ func (m *RegisterResourceResponse) Reset() { *m = RegisterResourceRespon
func (m *RegisterResourceResponse) String() string { return proto.CompactTextString(m) }
func (*RegisterResourceResponse) ProtoMessage() {}
func (*RegisterResourceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_773fb6366ec12ccd, []int{3}
return fileDescriptor_resource_5aa1dff965971124, []int{3}
}
func (m *RegisterResourceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterResourceResponse.Unmarshal(m, b)
@ -323,7 +339,7 @@ func (m *RegisterResourceOutputsRequest) Reset() { *m = RegisterResource
func (m *RegisterResourceOutputsRequest) String() string { return proto.CompactTextString(m) }
func (*RegisterResourceOutputsRequest) ProtoMessage() {}
func (*RegisterResourceOutputsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_resource_773fb6366ec12ccd, []int{4}
return fileDescriptor_resource_5aa1dff965971124, []int{4}
}
func (m *RegisterResourceOutputsRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterResourceOutputsRequest.Unmarshal(m, b)
@ -536,38 +552,39 @@ var _ResourceMonitor_serviceDesc = grpc.ServiceDesc{
Metadata: "resource.proto",
}
func init() { proto.RegisterFile("resource.proto", fileDescriptor_resource_773fb6366ec12ccd) }
func init() { proto.RegisterFile("resource.proto", fileDescriptor_resource_5aa1dff965971124) }
var fileDescriptor_resource_773fb6366ec12ccd = []byte{
// 480 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x94, 0xbd, 0x8e, 0xd3, 0x40,
0x10, 0xc7, 0xcf, 0xf6, 0xe1, 0x90, 0xe1, 0x14, 0x4e, 0x03, 0xca, 0x19, 0x83, 0x8e, 0xc8, 0x34,
0xa1, 0x71, 0xc4, 0x51, 0x50, 0x52, 0x51, 0x50, 0x20, 0x84, 0xa9, 0x41, 0x72, 0xec, 0x21, 0x32,
0x24, 0xde, 0x65, 0x3f, 0x4e, 0xba, 0xa7, 0xe1, 0x5d, 0x78, 0x12, 0x0a, 0x1e, 0x04, 0xad, 0xd7,
0x1b, 0xe2, 0x8f, 0xfb, 0xe8, 0xe6, 0x6b, 0xc7, 0xff, 0xf9, 0xed, 0xac, 0x61, 0x26, 0x48, 0x32,
0x2d, 0x0a, 0x4a, 0xb9, 0x60, 0x8a, 0xe1, 0x94, 0xeb, 0xad, 0xde, 0x55, 0x82, 0x17, 0xf1, 0xd3,
0x0d, 0x63, 0x9b, 0x2d, 0xad, 0x9a, 0xc4, 0x5a, 0x7f, 0x5b, 0xd1, 0x8e, 0xab, 0x2b, 0x5b, 0x17,
0x3f, 0xeb, 0x27, 0xa5, 0x12, 0xba, 0x50, 0x6d, 0x76, 0xc6, 0x05, 0xbb, 0xac, 0x4a, 0x12, 0xd6,
0x4f, 0x7e, 0x7b, 0xf0, 0x28, 0xa3, 0xbc, 0xcc, 0xda, 0x8f, 0x65, 0xf4, 0x53, 0x93, 0x54, 0x38,
0x03, 0xbf, 0x2a, 0x23, 0x6f, 0xe1, 0x2d, 0xa7, 0x99, 0x5f, 0x95, 0x88, 0x70, 0xac, 0xae, 0x38,
0x45, 0x7e, 0x13, 0x69, 0x6c, 0x13, 0xab, 0xf3, 0x1d, 0x45, 0x81, 0x8d, 0x19, 0x1b, 0xe7, 0x10,
0xf2, 0x5c, 0x50, 0xad, 0xa2, 0xe3, 0x26, 0xda, 0x7a, 0xf8, 0x06, 0x80, 0x0b, 0xc6, 0x49, 0xa8,
0x8a, 0x64, 0x74, 0x6f, 0xe1, 0x2d, 0x1f, 0x5c, 0x9c, 0xa5, 0x56, 0x6a, 0xea, 0xa4, 0xa6, 0x9f,
0x1b, 0xa9, 0xd9, 0x41, 0x29, 0x26, 0x70, 0x52, 0x12, 0xa7, 0xba, 0xa4, 0xba, 0x30, 0x47, 0xc3,
0x45, 0xb0, 0x9c, 0x66, 0x9d, 0x58, 0x92, 0xc3, 0xe3, 0xee, 0x0c, 0x92, 0xb3, 0x5a, 0x12, 0x9e,
0x42, 0xa0, 0x45, 0xdd, 0x4e, 0x61, 0xcc, 0x9e, 0x0c, 0xff, 0xce, 0x32, 0x92, 0x3f, 0x1e, 0x9c,
0x65, 0xb4, 0xa9, 0xa4, 0x22, 0xd1, 0x67, 0xe5, 0xd8, 0x78, 0x23, 0x6c, 0xfc, 0x51, 0x36, 0x41,
0x87, 0xcd, 0x1c, 0xc2, 0x42, 0x4b, 0xc5, 0x76, 0x0d, 0xb3, 0xfb, 0x59, 0xeb, 0xe1, 0x0a, 0x42,
0xb6, 0xfe, 0x4e, 0x85, 0xba, 0x8d, 0x57, 0x5b, 0x86, 0x11, 0x4c, 0x4c, 0xca, 0x9c, 0x08, 0x9b,
0x4e, 0xce, 0x1d, 0x50, 0x9c, 0x8c, 0x50, 0xfc, 0xe5, 0x41, 0x34, 0x1c, 0xf1, 0x5a, 0x94, 0x76,
0x43, 0xfc, 0xfd, 0x86, 0xfc, 0x57, 0x1b, 0xdc, 0x4d, 0xed, 0x1c, 0x42, 0xa9, 0xf2, 0xf5, 0x96,
0xdc, 0xd8, 0xd6, 0x33, 0x53, 0x58, 0xcb, 0xec, 0x89, 0x91, 0xe9, 0xdc, 0x84, 0xe0, 0xbc, 0x2f,
0xf0, 0xa3, 0x56, 0x5c, 0x2b, 0xe9, 0xae, 0x62, 0x28, 0xf3, 0x15, 0x4c, 0x98, 0xad, 0xb9, 0xed,
0xba, 0x5d, 0xdd, 0xc5, 0x5f, 0x1f, 0x1e, 0xba, 0xfe, 0x1f, 0x58, 0x5d, 0x29, 0x26, 0xf0, 0x2d,
0x84, 0xef, 0xeb, 0x4b, 0xf6, 0x83, 0x30, 0x4a, 0xf7, 0x0f, 0x31, 0xb5, 0xa1, 0xf6, 0xe3, 0xf1,
0x93, 0x91, 0x8c, 0xc5, 0x97, 0x1c, 0xe1, 0x27, 0x38, 0x39, 0xdc, 0x51, 0x3c, 0x3f, 0x28, 0x1e,
0x79, 0x80, 0xf1, 0xf3, 0x6b, 0xf3, 0xfb, 0x96, 0x5f, 0xe0, 0xb4, 0x8f, 0x03, 0x93, 0xce, 0xb1,
0xd1, 0x7d, 0x8d, 0x5f, 0xdc, 0x58, 0xb3, 0x6f, 0xff, 0x75, 0xb8, 0xf1, 0x2d, 0x6d, 0x7c, 0x79,
0x43, 0x87, 0xee, 0x8d, 0xc4, 0xf3, 0x01, 0xee, 0x77, 0xe6, 0x67, 0x95, 0x1c, 0xad, 0xc3, 0x26,
0xf2, 0xfa, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5d, 0xe5, 0xc8, 0x38, 0xe9, 0x04, 0x00, 0x00,
var fileDescriptor_resource_5aa1dff965971124 = []byte{
// 491 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x94, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xc7, 0x6b, 0xbb, 0x38, 0xe9, 0x50, 0x85, 0x6a, 0x41, 0xad, 0x31, 0xa8, 0x54, 0xe6, 0x02,
0x17, 0x47, 0x94, 0x03, 0x47, 0x4e, 0x1c, 0x38, 0x20, 0x84, 0x39, 0x83, 0xe4, 0xd8, 0x43, 0x64,
0x48, 0xbc, 0xcb, 0x7e, 0x54, 0xea, 0xd3, 0xf0, 0x66, 0x9c, 0x78, 0x0c, 0x0e, 0x78, 0x77, 0xbd,
0x26, 0xfe, 0x68, 0xd3, 0x53, 0xe6, 0x6b, 0x67, 0xe7, 0xff, 0xf3, 0x6c, 0x60, 0xc1, 0x51, 0x50,
0xc5, 0x0b, 0x4c, 0x19, 0xa7, 0x92, 0x92, 0x23, 0xa6, 0x36, 0x6a, 0x5b, 0x71, 0x56, 0xc4, 0x4f,
0xd6, 0x94, 0xae, 0x37, 0xb8, 0x34, 0x89, 0x95, 0xfa, 0xb6, 0xc4, 0x2d, 0x93, 0xd7, 0xb6, 0x2e,
0x7e, 0x3a, 0x4c, 0x0a, 0xc9, 0x55, 0x21, 0xdb, 0xec, 0xa2, 0xf9, 0xb9, 0xaa, 0x4a, 0xe4, 0xd6,
0x4f, 0x7e, 0x7b, 0xf0, 0x30, 0xc3, 0xbc, 0xcc, 0xda, 0xcb, 0x32, 0xfc, 0xa9, 0x50, 0x48, 0xb2,
0x00, 0xbf, 0x2a, 0x23, 0xef, 0xc2, 0x7b, 0x71, 0x94, 0x35, 0x16, 0x21, 0x70, 0x28, 0xaf, 0x19,
0x46, 0xbe, 0x89, 0x18, 0x5b, 0xc7, 0xea, 0x7c, 0x8b, 0x51, 0x60, 0x63, 0xda, 0x26, 0xa7, 0x10,
0xb2, 0x9c, 0x63, 0x2d, 0xa3, 0x43, 0x13, 0x6d, 0x3d, 0xf2, 0x06, 0xa0, 0xb9, 0x90, 0x21, 0x97,
0x15, 0x8a, 0xe8, 0x5e, 0x93, 0xbb, 0x7f, 0x79, 0x96, 0xda, 0x51, 0x53, 0x37, 0x6a, 0xfa, 0xd9,
0x8c, 0x9a, 0xed, 0x94, 0x92, 0x04, 0x8e, 0x4b, 0x64, 0x58, 0x97, 0x58, 0x17, 0xfa, 0x68, 0x78,
0x11, 0x34, 0x6d, 0x7b, 0x31, 0x12, 0xc3, 0xdc, 0xc9, 0x8a, 0x66, 0xe6, 0xda, 0xce, 0x4f, 0x72,
0x78, 0xd4, 0xd7, 0x27, 0x18, 0xad, 0x05, 0x92, 0x13, 0x08, 0x14, 0xaf, 0x5b, 0x85, 0xda, 0x1c,
0x8c, 0xe8, 0xdf, 0x79, 0xc4, 0xe4, 0xaf, 0x07, 0x67, 0x19, 0xae, 0x2b, 0x21, 0x91, 0x0f, 0x39,
0x3a, 0x6e, 0xde, 0x04, 0x37, 0x7f, 0x92, 0x5b, 0xd0, 0xe3, 0xd6, 0xc4, 0x0b, 0x25, 0x24, 0xdd,
0x1a, 0x9e, 0xf3, 0xac, 0xf5, 0xc8, 0x12, 0x42, 0xba, 0xfa, 0x8e, 0x85, 0xdc, 0xc7, 0xb2, 0x2d,
0x23, 0x11, 0xcc, 0x74, 0x4a, 0x9f, 0x08, 0x4d, 0x27, 0xe7, 0x8e, 0x08, 0xcf, 0xf6, 0x10, 0x9e,
0x0f, 0x08, 0xff, 0xf2, 0x20, 0x1a, 0xcb, 0xbf, 0x11, 0xb3, 0xdd, 0x2c, 0xbf, 0xdb, 0xac, 0xff,
0x4a, 0x82, 0xbb, 0x29, 0x69, 0x90, 0x08, 0x99, 0xaf, 0x36, 0xe8, 0x90, 0x58, 0x4f, 0x2b, 0xb4,
0x96, 0xde, 0x2f, 0x2d, 0xc1, 0xb9, 0x09, 0xc2, 0xf9, 0x70, 0xc0, 0x8f, 0x4a, 0x32, 0x25, 0x85,
0xfb, 0x4c, 0xe3, 0x31, 0x5f, 0xc1, 0x8c, 0xda, 0x9a, 0x7d, 0xab, 0xe0, 0xea, 0x2e, 0xff, 0xf8,
0xf0, 0xc0, 0xf5, 0xff, 0x40, 0xeb, 0x4a, 0x52, 0x4e, 0xde, 0x42, 0xf8, 0xbe, 0xbe, 0xa2, 0x3f,
0x9a, 0xf1, 0xd2, 0xee, 0x01, 0xa7, 0x36, 0xd4, 0x5e, 0x1e, 0x3f, 0x9e, 0xc8, 0x58, 0x7c, 0xc9,
0x01, 0xf9, 0x04, 0xc7, 0xbb, 0xfb, 0x4b, 0xce, 0x77, 0x8a, 0x27, 0x1e, 0x6e, 0xfc, 0xec, 0xc6,
0x7c, 0xd7, 0xf2, 0x0b, 0x9c, 0x0c, 0x71, 0x90, 0xa4, 0x77, 0x6c, 0x72, 0x97, 0xe3, 0xe7, 0xb7,
0xd6, 0x74, 0xed, 0xbf, 0x8e, 0x5f, 0x43, 0x4b, 0x9b, 0xbc, 0xbc, 0xa5, 0x43, 0xff, 0x8b, 0xc4,
0xa7, 0x23, 0xdc, 0xef, 0xf4, 0x9f, 0x5c, 0x72, 0xb0, 0x0a, 0x4d, 0xe4, 0xf5, 0xbf, 0x00, 0x00,
0x00, 0xff, 0xff, 0xa3, 0x97, 0x3d, 0x93, 0x21, 0x05, 0x00, 0x00,
}

View file

@ -68,6 +68,7 @@ message ConfigureErrorMissingKeys {
message InvokeRequest {
string tok = 1; // the function token to invoke.
google.protobuf.Struct args = 2; // the arguments for the function invocation.
string provider = 3; // an optional reference to the provider to use for this invoke.
}
message InvokeResponse {

View file

@ -36,6 +36,7 @@ message ReadResourceRequest {
string parent = 4; // an optional parent URN that this child resource belongs to.
google.protobuf.Struct properties = 5; // optional state sufficient to uniquely identify the resource.
repeated string dependencies = 6; // a list of URNs that this read depends on, as observed by the language host.
string provider = 7; // an optional reference to the provider to use for this read.
}
// ReadResourceResponse contains the result of reading a resource's state.
@ -53,6 +54,7 @@ message RegisterResourceRequest {
google.protobuf.Struct object = 5; // an object produced by the interpreter/source.
bool protect = 6; // true if the resource should be marked protected.
repeated string dependencies = 7; // a list of URNs that this resource depends on, as observed by the language host.
string provider = 8; // an optional reference to the provider to manage this resource's CRUD operations.
}
// RegisterResourceResponse is returned by the engine after a resource has finished being initialized. It includes the

View file

@ -22,7 +22,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
name='provider.proto',
package='pulumirpc',
syntax='proto3',
serialized_pb=_b('\n\x0eprovider.proto\x12\tpulumirpc\x1a\x0cplugin.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\"\x83\x01\n\x10\x43onfigureRequest\x12=\n\tvariables\x18\x01 \x03(\x0b\x32*.pulumirpc.ConfigureRequest.VariablesEntry\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x92\x01\n\x19\x43onfigureErrorMissingKeys\x12\x44\n\x0bmissingKeys\x18\x01 \x03(\x0b\x32/.pulumirpc.ConfigureErrorMissingKeys.MissingKey\x1a/\n\nMissingKey\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\"C\n\rInvokeRequest\x12\x0b\n\x03tok\x18\x01 \x01(\t\x12%\n\x04\x61rgs\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"d\n\x0eInvokeResponse\x12\'\n\x06return\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12)\n\x08\x66\x61ilures\x18\x02 \x03(\x0b\x32\x17.pulumirpc.CheckFailure\"i\n\x0c\x43heckRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12%\n\x04olds\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12%\n\x04news\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"c\n\rCheckResponse\x12\'\n\x06inputs\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12)\n\x08\x66\x61ilures\x18\x02 \x03(\x0b\x32\x17.pulumirpc.CheckFailure\"0\n\x0c\x43heckFailure\x12\x10\n\x08property\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\"t\n\x0b\x44iffRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03urn\x18\x02 \x01(\t\x12%\n\x04olds\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12%\n\x04news\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xc3\x01\n\x0c\x44iffResponse\x12\x10\n\x08replaces\x18\x01 \x03(\t\x12\x0f\n\x07stables\x18\x02 \x03(\t\x12\x1b\n\x13\x64\x65leteBeforeReplace\x18\x03 \x01(\x08\x12\x34\n\x07\x63hanges\x18\x04 \x01(\x0e\x32#.pulumirpc.DiffResponse.DiffChanges\"=\n\x0b\x44iffChanges\x12\x10\n\x0c\x44IFF_UNKNOWN\x10\x00\x12\r\n\tDIFF_NONE\x10\x01\x12\r\n\tDIFF_SOME\x10\x02\"I\n\rCreateRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"I\n\x0e\x43reateResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"S\n\x0bReadRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03urn\x18\x02 \x01(\t\x12+\n\nproperties\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"G\n\x0cReadResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"v\n\rUpdateRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03urn\x18\x02 \x01(\t\x12%\n\x04olds\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12%\n\x04news\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"=\n\x0eUpdateResponse\x12+\n\nproperties\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"U\n\rDeleteRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03urn\x18\x02 \x01(\t\x12+\n\nproperties\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"c\n\x17\x45rrorResourceInitFailed\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07reasons\x18\x03 \x03(\t2\x89\x05\n\x10ResourceProvider\x12\x42\n\tConfigure\x12\x1b.pulumirpc.ConfigureRequest\x1a\x16.google.protobuf.Empty\"\x00\x12?\n\x06Invoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x12<\n\x05\x43heck\x12\x17.pulumirpc.CheckRequest\x1a\x18.pulumirpc.CheckResponse\"\x00\x12\x39\n\x04\x44iff\x12\x16.pulumirpc.DiffRequest\x1a\x17.pulumirpc.DiffResponse\"\x00\x12?\n\x06\x43reate\x12\x18.pulumirpc.CreateRequest\x1a\x19.pulumirpc.CreateResponse\"\x00\x12\x39\n\x04Read\x12\x16.pulumirpc.ReadRequest\x1a\x17.pulumirpc.ReadResponse\"\x00\x12?\n\x06Update\x12\x18.pulumirpc.UpdateRequest\x1a\x19.pulumirpc.UpdateResponse\"\x00\x12<\n\x06\x44\x65lete\x12\x18.pulumirpc.DeleteRequest\x1a\x16.google.protobuf.Empty\"\x00\x12:\n\x06\x43\x61ncel\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12@\n\rGetPluginInfo\x12\x16.google.protobuf.Empty\x1a\x15.pulumirpc.PluginInfo\"\x00\x62\x06proto3')
serialized_pb=_b('\n\x0eprovider.proto\x12\tpulumirpc\x1a\x0cplugin.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\"\x83\x01\n\x10\x43onfigureRequest\x12=\n\tvariables\x18\x01 \x03(\x0b\x32*.pulumirpc.ConfigureRequest.VariablesEntry\x1a\x30\n\x0eVariablesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x92\x01\n\x19\x43onfigureErrorMissingKeys\x12\x44\n\x0bmissingKeys\x18\x01 \x03(\x0b\x32/.pulumirpc.ConfigureErrorMissingKeys.MissingKey\x1a/\n\nMissingKey\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\"U\n\rInvokeRequest\x12\x0b\n\x03tok\x18\x01 \x01(\t\x12%\n\x04\x61rgs\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x10\n\x08provider\x18\x03 \x01(\t\"d\n\x0eInvokeResponse\x12\'\n\x06return\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12)\n\x08\x66\x61ilures\x18\x02 \x03(\x0b\x32\x17.pulumirpc.CheckFailure\"i\n\x0c\x43heckRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12%\n\x04olds\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12%\n\x04news\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"c\n\rCheckResponse\x12\'\n\x06inputs\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\x12)\n\x08\x66\x61ilures\x18\x02 \x03(\x0b\x32\x17.pulumirpc.CheckFailure\"0\n\x0c\x43heckFailure\x12\x10\n\x08property\x18\x01 \x01(\t\x12\x0e\n\x06reason\x18\x02 \x01(\t\"t\n\x0b\x44iffRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03urn\x18\x02 \x01(\t\x12%\n\x04olds\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12%\n\x04news\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xc3\x01\n\x0c\x44iffResponse\x12\x10\n\x08replaces\x18\x01 \x03(\t\x12\x0f\n\x07stables\x18\x02 \x03(\t\x12\x1b\n\x13\x64\x65leteBeforeReplace\x18\x03 \x01(\x08\x12\x34\n\x07\x63hanges\x18\x04 \x01(\x0e\x32#.pulumirpc.DiffResponse.DiffChanges\"=\n\x0b\x44iffChanges\x12\x10\n\x0c\x44IFF_UNKNOWN\x10\x00\x12\r\n\tDIFF_NONE\x10\x01\x12\r\n\tDIFF_SOME\x10\x02\"I\n\rCreateRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"I\n\x0e\x43reateResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"S\n\x0bReadRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03urn\x18\x02 \x01(\t\x12+\n\nproperties\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"G\n\x0cReadResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"v\n\rUpdateRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03urn\x18\x02 \x01(\t\x12%\n\x04olds\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12%\n\x04news\x18\x04 \x01(\x0b\x32\x17.google.protobuf.Struct\"=\n\x0eUpdateResponse\x12+\n\nproperties\x18\x01 \x01(\x0b\x32\x17.google.protobuf.Struct\"U\n\rDeleteRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03urn\x18\x02 \x01(\t\x12+\n\nproperties\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\"c\n\x17\x45rrorResourceInitFailed\x12\n\n\x02id\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07reasons\x18\x03 \x03(\t2\x89\x05\n\x10ResourceProvider\x12\x42\n\tConfigure\x12\x1b.pulumirpc.ConfigureRequest\x1a\x16.google.protobuf.Empty\"\x00\x12?\n\x06Invoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x12<\n\x05\x43heck\x12\x17.pulumirpc.CheckRequest\x1a\x18.pulumirpc.CheckResponse\"\x00\x12\x39\n\x04\x44iff\x12\x16.pulumirpc.DiffRequest\x1a\x17.pulumirpc.DiffResponse\"\x00\x12?\n\x06\x43reate\x12\x18.pulumirpc.CreateRequest\x1a\x19.pulumirpc.CreateResponse\"\x00\x12\x39\n\x04Read\x12\x16.pulumirpc.ReadRequest\x1a\x17.pulumirpc.ReadResponse\"\x00\x12?\n\x06Update\x12\x18.pulumirpc.UpdateRequest\x1a\x19.pulumirpc.UpdateResponse\"\x00\x12<\n\x06\x44\x65lete\x12\x18.pulumirpc.DeleteRequest\x1a\x16.google.protobuf.Empty\"\x00\x12:\n\x06\x43\x61ncel\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12@\n\rGetPluginInfo\x12\x16.google.protobuf.Empty\x1a\x15.pulumirpc.PluginInfo\"\x00\x62\x06proto3')
,
dependencies=[plugin__pb2.DESCRIPTOR,google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,])
@ -49,8 +49,8 @@ _DIFFRESPONSE_DIFFCHANGES = _descriptor.EnumDescriptor(
],
containing_type=None,
options=None,
serialized_start=1067,
serialized_end=1128,
serialized_start=1085,
serialized_end=1146,
)
_sym_db.RegisterEnumDescriptor(_DIFFRESPONSE_DIFFCHANGES)
@ -212,6 +212,13 @@ _INVOKEREQUEST = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='provider', full_name='pulumirpc.InvokeRequest.provider', index=2,
number=3, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
@ -225,7 +232,7 @@ _INVOKEREQUEST = _descriptor.Descriptor(
oneofs=[
],
serialized_start=385,
serialized_end=452,
serialized_end=470,
)
@ -262,8 +269,8 @@ _INVOKERESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=454,
serialized_end=554,
serialized_start=472,
serialized_end=572,
)
@ -307,8 +314,8 @@ _CHECKREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=556,
serialized_end=661,
serialized_start=574,
serialized_end=679,
)
@ -345,8 +352,8 @@ _CHECKRESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=663,
serialized_end=762,
serialized_start=681,
serialized_end=780,
)
@ -383,8 +390,8 @@ _CHECKFAILURE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=764,
serialized_end=812,
serialized_start=782,
serialized_end=830,
)
@ -435,8 +442,8 @@ _DIFFREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=814,
serialized_end=930,
serialized_start=832,
serialized_end=948,
)
@ -488,8 +495,8 @@ _DIFFRESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=933,
serialized_end=1128,
serialized_start=951,
serialized_end=1146,
)
@ -526,8 +533,8 @@ _CREATEREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1130,
serialized_end=1203,
serialized_start=1148,
serialized_end=1221,
)
@ -564,8 +571,8 @@ _CREATERESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1205,
serialized_end=1278,
serialized_start=1223,
serialized_end=1296,
)
@ -609,8 +616,8 @@ _READREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1280,
serialized_end=1363,
serialized_start=1298,
serialized_end=1381,
)
@ -647,8 +654,8 @@ _READRESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1365,
serialized_end=1436,
serialized_start=1383,
serialized_end=1454,
)
@ -699,8 +706,8 @@ _UPDATEREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1438,
serialized_end=1556,
serialized_start=1456,
serialized_end=1574,
)
@ -730,8 +737,8 @@ _UPDATERESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1558,
serialized_end=1619,
serialized_start=1576,
serialized_end=1637,
)
@ -775,8 +782,8 @@ _DELETEREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1621,
serialized_end=1706,
serialized_start=1639,
serialized_end=1724,
)
@ -820,8 +827,8 @@ _ERRORRESOURCEINITFAILED = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=1708,
serialized_end=1807,
serialized_start=1726,
serialized_end=1825,
)
_CONFIGUREREQUEST_VARIABLESENTRY.containing_type = _CONFIGUREREQUEST
@ -1012,8 +1019,8 @@ _RESOURCEPROVIDER = _descriptor.ServiceDescriptor(
file=DESCRIPTOR,
index=0,
options=None,
serialized_start=1810,
serialized_end=2459,
serialized_start=1828,
serialized_end=2477,
methods=[
_descriptor.MethodDescriptor(
name='Configure',

View file

@ -22,7 +22,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
name='resource.proto',
package='pulumirpc',
syntax='proto3',
serialized_pb=_b('\n\x0eresource.proto\x12\tpulumirpc\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x0eprovider.proto\"\x90\x01\n\x13ReadResourceRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0e\n\x06parent\x18\x04 \x01(\t\x12+\n\nproperties\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x14\n\x0c\x64\x65pendencies\x18\x06 \x03(\t\"P\n\x14ReadResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xa5\x01\n\x17RegisterResourceRequest\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06parent\x18\x03 \x01(\t\x12\x0e\n\x06\x63ustom\x18\x04 \x01(\x08\x12\'\n\x06object\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07protect\x18\x06 \x01(\x08\x12\x14\n\x0c\x64\x65pendencies\x18\x07 \x03(\t\"}\n\x18RegisterResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\'\n\x06object\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0e\n\x06stable\x18\x04 \x01(\x08\x12\x0f\n\x07stables\x18\x05 \x03(\t\"W\n\x1eRegisterResourceOutputsRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12(\n\x07outputs\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct2\xe4\x02\n\x0fResourceMonitor\x12?\n\x06Invoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x12Q\n\x0cReadResource\x12\x1e.pulumirpc.ReadResourceRequest\x1a\x1f.pulumirpc.ReadResourceResponse\"\x00\x12]\n\x10RegisterResource\x12\".pulumirpc.RegisterResourceRequest\x1a#.pulumirpc.RegisterResourceResponse\"\x00\x12^\n\x17RegisterResourceOutputs\x12).pulumirpc.RegisterResourceOutputsRequest\x1a\x16.google.protobuf.Empty\"\x00\x62\x06proto3')
serialized_pb=_b('\n\x0eresource.proto\x12\tpulumirpc\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x0eprovider.proto\"\xa2\x01\n\x13ReadResourceRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0e\n\x06parent\x18\x04 \x01(\t\x12+\n\nproperties\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x14\n\x0c\x64\x65pendencies\x18\x06 \x03(\t\x12\x10\n\x08provider\x18\x07 \x01(\t\"P\n\x14ReadResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12+\n\nproperties\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xb7\x01\n\x17RegisterResourceRequest\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06parent\x18\x03 \x01(\t\x12\x0e\n\x06\x63ustom\x18\x04 \x01(\x08\x12\'\n\x06object\x18\x05 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0f\n\x07protect\x18\x06 \x01(\x08\x12\x14\n\x0c\x64\x65pendencies\x18\x07 \x03(\t\x12\x10\n\x08provider\x18\x08 \x01(\t\"}\n\x18RegisterResourceResponse\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\x12\'\n\x06object\x18\x03 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x0e\n\x06stable\x18\x04 \x01(\x08\x12\x0f\n\x07stables\x18\x05 \x03(\t\"W\n\x1eRegisterResourceOutputsRequest\x12\x0b\n\x03urn\x18\x01 \x01(\t\x12(\n\x07outputs\x18\x02 \x01(\x0b\x32\x17.google.protobuf.Struct2\xe4\x02\n\x0fResourceMonitor\x12?\n\x06Invoke\x12\x18.pulumirpc.InvokeRequest\x1a\x19.pulumirpc.InvokeResponse\"\x00\x12Q\n\x0cReadResource\x12\x1e.pulumirpc.ReadResourceRequest\x1a\x1f.pulumirpc.ReadResourceResponse\"\x00\x12]\n\x10RegisterResource\x12\".pulumirpc.RegisterResourceRequest\x1a#.pulumirpc.RegisterResourceResponse\"\x00\x12^\n\x17RegisterResourceOutputs\x12).pulumirpc.RegisterResourceOutputsRequest\x1a\x16.google.protobuf.Empty\"\x00\x62\x06proto3')
,
dependencies=[google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_protobuf_dot_struct__pb2.DESCRIPTOR,provider__pb2.DESCRIPTOR,])
@ -78,6 +78,13 @@ _READRESOURCEREQUEST = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='provider', full_name='pulumirpc.ReadResourceRequest.provider', index=6,
number=7, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
@ -91,7 +98,7 @@ _READRESOURCEREQUEST = _descriptor.Descriptor(
oneofs=[
],
serialized_start=105,
serialized_end=249,
serialized_end=267,
)
@ -128,8 +135,8 @@ _READRESOURCERESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=251,
serialized_end=331,
serialized_start=269,
serialized_end=349,
)
@ -189,6 +196,13 @@ _REGISTERRESOURCEREQUEST = _descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
_descriptor.FieldDescriptor(
name='provider', full_name='pulumirpc.RegisterResourceRequest.provider', index=7,
number=8, type=9, cpp_type=9, label=1,
has_default_value=False, default_value=_b("").decode('utf-8'),
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None, file=DESCRIPTOR),
],
extensions=[
],
@ -201,8 +215,8 @@ _REGISTERRESOURCEREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=334,
serialized_end=499,
serialized_start=352,
serialized_end=535,
)
@ -260,8 +274,8 @@ _REGISTERRESOURCERESPONSE = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=501,
serialized_end=626,
serialized_start=537,
serialized_end=662,
)
@ -298,8 +312,8 @@ _REGISTERRESOURCEOUTPUTSREQUEST = _descriptor.Descriptor(
extension_ranges=[],
oneofs=[
],
serialized_start=628,
serialized_end=715,
serialized_start=664,
serialized_end=751,
)
_READRESOURCEREQUEST.fields_by_name['properties'].message_type = google_dot_protobuf_dot_struct__pb2._STRUCT
@ -357,8 +371,8 @@ _RESOURCEMONITOR = _descriptor.ServiceDescriptor(
file=DESCRIPTOR,
index=0,
options=None,
serialized_start=718,
serialized_end=1074,
serialized_start=754,
serialized_end=1110,
methods=[
_descriptor.MethodDescriptor(
name='Invoke',

View file

@ -12,6 +12,7 @@ import (
"github.com/pmezard/go-difflib/difflib"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/testing/integration"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/stretchr/testify/assert"
@ -29,16 +30,18 @@ func TestDiffs(t *testing.T) {
UpdateCommandlineFlags: []string{"--color=raw", "--non-interactive", "--diff"},
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.NotNil(t, stack.Deployment)
assert.Equal(t, 5, len(stack.Deployment.Resources))
assert.Equal(t, 6, len(stack.Deployment.Resources))
stackRes := stack.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stack.Deployment.Resources[1]
providerRes := stack.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stack.Deployment.Resources[2]
assert.Equal(t, "a", string(a.URN.Name()))
b := stack.Deployment.Resources[2]
b := stack.Deployment.Resources[3]
assert.Equal(t, "b", string(b.URN.Name()))
c := stack.Deployment.Resources[3]
c := stack.Deployment.Resources[4]
assert.Equal(t, "c", string(c.URN.Name()))
d := stack.Deployment.Resources[4]
d := stack.Deployment.Resources[5]
assert.Equal(t, "d", string(d.URN.Name()))
},
EditDirs: []integration.EditDir{
@ -49,16 +52,18 @@ func TestDiffs(t *testing.T) {
Verbose: true,
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.NotNil(t, stack.Deployment)
assert.Equal(t, 5, len(stack.Deployment.Resources))
assert.Equal(t, 6, len(stack.Deployment.Resources))
stackRes := stack.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stack.Deployment.Resources[1]
providerRes := stack.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stack.Deployment.Resources[2]
assert.Equal(t, "a", string(a.URN.Name()))
b := stack.Deployment.Resources[2]
b := stack.Deployment.Resources[3]
assert.Equal(t, "b", string(b.URN.Name()))
c := stack.Deployment.Resources[3]
c := stack.Deployment.Resources[4]
assert.Equal(t, "c", string(c.URN.Name()))
e := stack.Deployment.Resources[4]
e := stack.Deployment.Resources[5]
assert.Equal(t, "e", string(e.URN.Name()))
expected :=
@ -67,15 +72,17 @@ func TestDiffs(t *testing.T) {
[urn=urn:pulumi:{{.StackName}}::steps::pulumi:pulumi:Stack::steps-{{.StackName}}]</unchanged>
<added>+ pulumi-nodejs:dynamic:Resource: (create)
[urn=urn:pulumi:{{.StackName}}::steps::pulumi-nodejs:dynamic:Resource::e]
[provider=urn:pulumi:{{.StackName}}::steps::pulumi:providers:pulumi-nodejs::default::id]
__provider: "exports.handler = __f0;\n\nvar __provider_proto = {};\n__f1.prototype = __provider_proto;\n__f1.instance = __provider;\nObject.defineProperty(__provider_proto, \"constructor\", { configurable: true, writable: true, value: __f1 });\nObject.defineProperty(__provider_proto, \"diff\", { configurable: true, writable: true, value: __f2 });\nObject.defineProperty(__provider_proto, \"create\", { configurable: true, writable: true, value: __f4 });\nObject.defineProperty(__provider_proto, \"update\", { configurable: true, writable: true, value: __f5 });\nObject.defineProperty(__provider_proto, \"delete\", { configurable: true, writable: true, value: __f6 });\nObject.defineProperty(__provider_proto, \"injectFault\", { configurable: true, writable: true, value: __f7 });\nvar __provider = Object.create(__provider_proto);\n\nfunction __f1() {\n return (function() {\n with({ }) {\n\nreturn function /*constructor*/() {\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f3() {\n return (function() {\n with({ }) {\n\nreturn function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f2() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*diff*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f4() {\n return (function() {\n with({ __awaiter: __f3, currentID: 0 }) {\n\nreturn function /*create*/(inputs) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f5() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*update*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f6() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*delete*/(id, props) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f7() {\n return (function() {\n with({ }) {\n\nreturn function /*injectFault*/(error) {\n this.inject = error;\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f0() {\n return (function() {\n with({ provider: __provider }) {\n\nreturn () => provider;\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n"</added>
<removed>- pulumi-nodejs:dynamic:Resource: (delete)
[id=0]
[id=id]
[urn=urn:pulumi:{{.StackName}}::steps::pulumi-nodejs:dynamic:Resource::d]
[provider=urn:pulumi:{{.StackName}}::steps::pulumi:providers:pulumi-nodejs::default::id]
__provider: "exports.handler = __f0;\n\nvar __provider_proto = {};\n__f1.prototype = __provider_proto;\n__f1.instance = __provider;\nObject.defineProperty(__provider_proto, \"constructor\", { configurable: true, writable: true, value: __f1 });\nObject.defineProperty(__provider_proto, \"diff\", { configurable: true, writable: true, value: __f2 });\nObject.defineProperty(__provider_proto, \"create\", { configurable: true, writable: true, value: __f4 });\nObject.defineProperty(__provider_proto, \"update\", { configurable: true, writable: true, value: __f5 });\nObject.defineProperty(__provider_proto, \"delete\", { configurable: true, writable: true, value: __f6 });\nObject.defineProperty(__provider_proto, \"injectFault\", { configurable: true, writable: true, value: __f7 });\nvar __provider = Object.create(__provider_proto);\n\nfunction __f1() {\n return (function() {\n with({ }) {\n\nreturn function /*constructor*/() {\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f3() {\n return (function() {\n with({ }) {\n\nreturn function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f2() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*diff*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f4() {\n return (function() {\n with({ __awaiter: __f3, currentID: 0 }) {\n\nreturn function /*create*/(inputs) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f5() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*update*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f6() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*delete*/(id, props) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f7() {\n return (function() {\n with({ }) {\n\nreturn function /*injectFault*/(error) {\n this.inject = error;\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f0() {\n return (function() {\n with({ provider: __provider }) {\n\nreturn () => provider;\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n"</removed>
<info>info</info>: 2 changes performed:
<added>+ 1 resource created</added>
<removed>- 1 resource deleted</removed>
4 resources unchanged`, stack.StackName)
5 resources unchanged`, stack.StackName)
assertPreviewOutput(t, expected, buf.String())
@ -89,14 +96,16 @@ func TestDiffs(t *testing.T) {
Verbose: true,
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.NotNil(t, stack.Deployment)
assert.Equal(t, 4, len(stack.Deployment.Resources))
assert.Equal(t, 5, len(stack.Deployment.Resources))
stackRes := stack.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stack.Deployment.Resources[1]
providerRes := stack.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stack.Deployment.Resources[2]
assert.Equal(t, "a", string(a.URN.Name()))
c := stack.Deployment.Resources[2]
c := stack.Deployment.Resources[3]
assert.Equal(t, "c", string(c.URN.Name()))
e := stack.Deployment.Resources[3]
e := stack.Deployment.Resources[4]
assert.Equal(t, "e", string(e.URN.Name()))
expected :=
@ -104,12 +113,13 @@ func TestDiffs(t *testing.T) {
* pulumi:pulumi:Stack: (same)
[urn=urn:pulumi:{{.StackName}}::steps::pulumi:pulumi:Stack::steps-{{.StackName}}]</unchanged>
<removed>- pulumi-nodejs:dynamic:Resource: (delete)
[id=0]
[id=id]
[urn=urn:pulumi:{{.StackName}}::steps::pulumi-nodejs:dynamic:Resource::b]
[provider=urn:pulumi:{{.StackName}}::steps::pulumi:providers:pulumi-nodejs::default::id]
__provider: "exports.handler = __f0;\n\nvar __provider_proto = {};\n__f1.prototype = __provider_proto;\n__f1.instance = __provider;\nObject.defineProperty(__provider_proto, \"constructor\", { configurable: true, writable: true, value: __f1 });\nObject.defineProperty(__provider_proto, \"diff\", { configurable: true, writable: true, value: __f2 });\nObject.defineProperty(__provider_proto, \"create\", { configurable: true, writable: true, value: __f4 });\nObject.defineProperty(__provider_proto, \"update\", { configurable: true, writable: true, value: __f5 });\nObject.defineProperty(__provider_proto, \"delete\", { configurable: true, writable: true, value: __f6 });\nObject.defineProperty(__provider_proto, \"injectFault\", { configurable: true, writable: true, value: __f7 });\nvar __provider = Object.create(__provider_proto);\n\nfunction __f1() {\n return (function() {\n with({ }) {\n\nreturn function /*constructor*/() {\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f3() {\n return (function() {\n with({ }) {\n\nreturn function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f2() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*diff*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f4() {\n return (function() {\n with({ __awaiter: __f3, currentID: 0 }) {\n\nreturn function /*create*/(inputs) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f5() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*update*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f6() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*delete*/(id, props) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f7() {\n return (function() {\n with({ }) {\n\nreturn function /*injectFault*/(error) {\n this.inject = error;\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f0() {\n return (function() {\n with({ provider: __provider }) {\n\nreturn () => provider;\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n"</removed>
<info>info</info>: 1 change performed:
<removed>- 1 resource deleted</removed>
4 resources unchanged`, stack.StackName)
5 resources unchanged`, stack.StackName)
assertPreviewOutput(t, expected, buf.String())
@ -123,14 +133,16 @@ func TestDiffs(t *testing.T) {
Verbose: true,
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
assert.NotNil(t, stack.Deployment)
assert.Equal(t, 4, len(stack.Deployment.Resources))
assert.Equal(t, 5, len(stack.Deployment.Resources))
stackRes := stack.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stack.Deployment.Resources[1]
providerRes := stack.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stack.Deployment.Resources[2]
assert.Equal(t, "a", string(a.URN.Name()))
c := stack.Deployment.Resources[2]
c := stack.Deployment.Resources[3]
assert.Equal(t, "c", string(c.URN.Name()))
e := stack.Deployment.Resources[3]
e := stack.Deployment.Resources[4]
assert.Equal(t, "e", string(e.URN.Name()))
expected := fillStackName(`<unchanged>Performing changes:
@ -158,19 +170,25 @@ func TestDiffs(t *testing.T) {
* pulumi:pulumi:Stack: (same)
[urn=urn:pulumi:{{.StackName}}::steps::pulumi:pulumi:Stack::steps-{{.StackName}}]</unchanged>
<removed>- pulumi-nodejs:dynamic:Resource: (delete)
[id=0]
[id=id]
[urn=urn:pulumi:{{.StackName}}::steps::pulumi-nodejs:dynamic:Resource::e]
[provider=urn:pulumi:{{.StackName}}::steps::pulumi:providers:pulumi-nodejs::default::id]
__provider: "exports.handler = __f0;\n\nvar __provider_proto = {};\n__f1.prototype = __provider_proto;\n__f1.instance = __provider;\nObject.defineProperty(__provider_proto, \"constructor\", { configurable: true, writable: true, value: __f1 });\nObject.defineProperty(__provider_proto, \"diff\", { configurable: true, writable: true, value: __f2 });\nObject.defineProperty(__provider_proto, \"create\", { configurable: true, writable: true, value: __f4 });\nObject.defineProperty(__provider_proto, \"update\", { configurable: true, writable: true, value: __f5 });\nObject.defineProperty(__provider_proto, \"delete\", { configurable: true, writable: true, value: __f6 });\nObject.defineProperty(__provider_proto, \"injectFault\", { configurable: true, writable: true, value: __f7 });\nvar __provider = Object.create(__provider_proto);\n\nfunction __f1() {\n return (function() {\n with({ }) {\n\nreturn function /*constructor*/() {\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f3() {\n return (function() {\n with({ }) {\n\nreturn function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f2() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*diff*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f4() {\n return (function() {\n with({ __awaiter: __f3, currentID: 0 }) {\n\nreturn function /*create*/(inputs) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f5() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*update*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f6() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*delete*/(id, props) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f7() {\n return (function() {\n with({ }) {\n\nreturn function /*injectFault*/(error) {\n this.inject = error;\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f0() {\n return (function() {\n with({ provider: __provider }) {\n\nreturn () => provider;\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n"</removed>
<removed>- pulumi-nodejs:dynamic:Resource: (delete)
[id=0]
[id=id]
[urn=urn:pulumi:{{.StackName}}::steps::pulumi-nodejs:dynamic:Resource::c]
[provider=urn:pulumi:{{.StackName}}::steps::pulumi:providers:pulumi-nodejs::default::id]
__provider: "exports.handler = __f0;\n\nvar __provider_proto = {};\n__f1.prototype = __provider_proto;\n__f1.instance = __provider;\nObject.defineProperty(__provider_proto, \"constructor\", { configurable: true, writable: true, value: __f1 });\nObject.defineProperty(__provider_proto, \"diff\", { configurable: true, writable: true, value: __f2 });\nObject.defineProperty(__provider_proto, \"create\", { configurable: true, writable: true, value: __f4 });\nObject.defineProperty(__provider_proto, \"update\", { configurable: true, writable: true, value: __f5 });\nObject.defineProperty(__provider_proto, \"delete\", { configurable: true, writable: true, value: __f6 });\nObject.defineProperty(__provider_proto, \"injectFault\", { configurable: true, writable: true, value: __f7 });\nvar __provider = Object.create(__provider_proto);\n\nfunction __f1() {\n return (function() {\n with({ }) {\n\nreturn function /*constructor*/() {\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f3() {\n return (function() {\n with({ }) {\n\nreturn function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f2() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*diff*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f4() {\n return (function() {\n with({ __awaiter: __f3, currentID: 0 }) {\n\nreturn function /*create*/(inputs) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f5() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*update*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f6() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*delete*/(id, props) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f7() {\n return (function() {\n with({ }) {\n\nreturn function /*injectFault*/(error) {\n this.inject = error;\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f0() {\n return (function() {\n with({ provider: __provider }) {\n\nreturn () => provider;\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n"</removed>
<removed>- pulumi-nodejs:dynamic:Resource: (delete)
[id=0]
[id=id]
[urn=urn:pulumi:{{.StackName}}::steps::pulumi-nodejs:dynamic:Resource::a]
[provider=urn:pulumi:{{.StackName}}::steps::pulumi:providers:pulumi-nodejs::default::id]
__provider: "exports.handler = __f0;\n\nvar __provider_proto = {};\n__f1.prototype = __provider_proto;\n__f1.instance = __provider;\nObject.defineProperty(__provider_proto, \"constructor\", { configurable: true, writable: true, value: __f1 });\nObject.defineProperty(__provider_proto, \"diff\", { configurable: true, writable: true, value: __f2 });\nObject.defineProperty(__provider_proto, \"create\", { configurable: true, writable: true, value: __f4 });\nObject.defineProperty(__provider_proto, \"update\", { configurable: true, writable: true, value: __f5 });\nObject.defineProperty(__provider_proto, \"delete\", { configurable: true, writable: true, value: __f6 });\nObject.defineProperty(__provider_proto, \"injectFault\", { configurable: true, writable: true, value: __f7 });\nvar __provider = Object.create(__provider_proto);\n\nfunction __f1() {\n return (function() {\n with({ }) {\n\nreturn function /*constructor*/() {\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f3() {\n return (function() {\n with({ }) {\n\nreturn function (thisArg, _arguments, P, generator) {\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n};\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f2() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*diff*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n let replaces = [];\n if (olds.replace !== news.replace) {\n replaces.push(\"replace\");\n }\n return {\n replaces: replaces,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f4() {\n return (function() {\n with({ __awaiter: __f3, currentID: 0 }) {\n\nreturn function /*create*/(inputs) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {\n id: (currentID++).toString(),\n outs: undefined,\n };\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f5() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*update*/(id, olds, news) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n return {};\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f6() {\n return (function() {\n with({ __awaiter: __f3 }) {\n\nreturn function /*delete*/(id, props) {\n return __awaiter(this, void 0, void 0, function* () {\n if (this.inject) {\n throw this.inject;\n }\n });\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f7() {\n return (function() {\n with({ }) {\n\nreturn function /*injectFault*/(error) {\n this.inject = error;\n };\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n\nfunction __f0() {\n return (function() {\n with({ provider: __provider }) {\n\nreturn () => provider;\n\n }\n }).apply(undefined, undefined).apply(this, arguments);\n}\n"</removed>
<info>info</info>: 3 changes performed:
<removed>- 3 resources deleted</removed>
<removed>- pulumi:providers:pulumi-nodejs: (delete)
[id=id]
[urn=urn:pulumi:{{.StackName}}::steps::pulumi:providers:pulumi-nodejs::default]</removed>
<info>info</info>: 4 changes performed:
<removed>- 4 resources deleted</removed>
1 resource unchanged`, stack.StackName)
assertPreviewOutput(t, expected, buf.String())
@ -197,6 +215,12 @@ func assertPreviewOutput(t *testing.T, expected, outputWithControlSeqeunces stri
// we ran, the second is a message about updating the stack in the cloud, so we drop them.
lines = lines[2:]
// Strip any provider IDs from the output.
stripProviderID := regexp.MustCompile(`(?P<prefix>pulumi:providers:\S+::\S+::|\[id=)[0-9a-f-]+`)
for i, line := range lines {
lines[i] = stripProviderID.ReplaceAllString(line, "${prefix}id")
}
// The last two lines include a call to stack export and a blank line. Drop them as well.
lines = lines[:len(lines)-2]

View file

@ -6,6 +6,7 @@ import (
"testing"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/testing/integration"
"github.com/stretchr/testify/assert"
)
@ -25,18 +26,21 @@ func TestDoublePendingDelete(t *testing.T) {
assert.NotNil(t, stackInfo.Deployment)
// Four resources in this deployment: the root resource, A, B, and A (pending delete)
assert.Equal(t, 4, len(stackInfo.Deployment.Resources))
assert.Equal(t, 5, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
assert.Equal(t, "a", string(a.URN.Name()))
assert.False(t, a.Delete)
aCondemned := stackInfo.Deployment.Resources[2]
aCondemned := stackInfo.Deployment.Resources[3]
assert.Equal(t, "a", string(aCondemned.URN.Name()))
assert.True(t, aCondemned.Delete)
b := stackInfo.Deployment.Resources[3]
b := stackInfo.Deployment.Resources[4]
assert.Equal(t, "b", string(b.URN.Name()))
assert.False(t, b.Delete)
@ -50,22 +54,25 @@ func TestDoublePendingDelete(t *testing.T) {
// Now there are two pending deletes in the deployment.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 5, len(stackInfo.Deployment.Resources))
assert.Equal(t, 6, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
assert.Equal(t, "a", string(a.URN.Name()))
assert.False(t, a.Delete)
aCondemned := stackInfo.Deployment.Resources[2]
aCondemned := stackInfo.Deployment.Resources[3]
assert.Equal(t, "a", string(aCondemned.URN.Name()))
assert.True(t, aCondemned.Delete)
aSecondCondemned := stackInfo.Deployment.Resources[3]
aSecondCondemned := stackInfo.Deployment.Resources[4]
assert.Equal(t, "a", string(aSecondCondemned.URN.Name()))
assert.True(t, aSecondCondemned.Delete)
b := stackInfo.Deployment.Resources[4]
b := stackInfo.Deployment.Resources[5]
assert.Equal(t, "b", string(b.URN.Name()))
assert.False(t, b.Delete)
},
@ -77,14 +84,17 @@ func TestDoublePendingDelete(t *testing.T) {
// We should have cleared out all of the pending deletes now.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 3, len(stackInfo.Deployment.Resources))
assert.Equal(t, 4, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
assert.Equal(t, "a", string(a.URN.Name()))
assert.False(t, a.Delete)
b := stackInfo.Deployment.Resources[2]
b := stackInfo.Deployment.Resources[3]
assert.Equal(t, "b", string(b.URN.Name()))
assert.False(t, b.Delete)
},

View file

@ -0,0 +1,3 @@
name: first_class_providers
description: A program that uses an explicit provider.
runtime: nodejs

View file

@ -0,0 +1,39 @@
// Copyright 2016-2018, Pulumi Corporation. All rights reserved.
import * as pulumi from "@pulumi/pulumi";
class DynamicProvider extends pulumi.ProviderResource {
constructor(name: string, opts?: pulumi.ResourceOptions) {
super("pulumi-nodejs", name, {}, opts);
}
}
class Provider implements pulumi.dynamic.ResourceProvider {
public static instance = new Provider();
public create: (inputs: any) => Promise<pulumi.dynamic.CreateResult>;
constructor() {
this.create = async (inputs: any) => {
return {
id: "0",
outs: undefined,
};
};
}
}
class Resource extends pulumi.dynamic.Resource {
constructor(name: string, provider?: pulumi.ProviderResource) {
super(Provider.instance, name, {}, { provider: provider});
}
}
// Create a resource using the default dynamic provider instance.
let a = new Resource("a");
// Create an explicit instance of the dynamic provider.
let p = new DynamicProvider("p");
// Create a resource using the explicit dynamic provider instance.
let b = new Resource("b", p);

View file

@ -0,0 +1,15 @@
{
"name": "stack_project_name",
"license": "Apache-2.0",
"main": "bin/index.js",
"typings": "bin/index.d.ts",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"typescript": "^2.5.3"
},
"peerDependencies": {
"@pulumi/pulumi": "latest"
}
}

View file

@ -0,0 +1,22 @@
{
"compilerOptions": {
"outDir": "bin",
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"sourceMap": true,
"stripInternal": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true
},
"files": [
"index.ts"
]
}

View file

@ -0,0 +1,7 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
typescript@^2.5.3:
version "2.6.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"

View file

@ -12,8 +12,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/config"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
ptesting "github.com/pulumi/pulumi/pkg/testing"
"github.com/pulumi/pulumi/pkg/testing/integration"
"github.com/pulumi/pulumi/pkg/workspace"
@ -209,7 +211,7 @@ func TestStackParenting(t *testing.T) {
// with the caveat, of course, that A and F will share a common parent, the implicit stack.
assert.NotNil(t, stackInfo.Deployment)
if assert.Equal(t, 8, len(stackInfo.Deployment.Resources)) {
if assert.Equal(t, 9, len(stackInfo.Deployment.Resources)) {
stackRes := stackInfo.Deployment.Resources[0]
assert.NotNil(t, stackRes)
assert.Equal(t, resource.RootStackType, stackRes.Type)
@ -230,6 +232,9 @@ func TestStackParenting(t *testing.T) {
assert.Equal(t, urns["c"], res.Parent)
case "g":
assert.Equal(t, urns["f"], res.Parent)
case "default":
// Default providers are not parented.
assert.Equal(t, "", string(res.Parent))
default:
t.Fatalf("unexpected name %s", res.URN.Name())
}
@ -398,3 +403,57 @@ func TestConfigBasicGo(t *testing.T) {
},
})
}
// Tests an explicit provider instance.
func TestExplicitProvider(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: "explicit_provider",
Dependencies: []string{"@pulumi/pulumi"},
Quick: true,
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
assert.NotNil(t, stackInfo.Deployment)
latest := stackInfo.Deployment
// Expect one stack resource, two provider resources, and two custom resources.
assert.True(t, len(latest.Resources) == 5)
var defaultProvider *apitype.ResourceV2
var explicitProvider *apitype.ResourceV2
for _, res := range latest.Resources {
urn := res.URN
switch urn.Name() {
case "default":
assert.True(t, providers.IsProviderType(res.Type))
assert.Nil(t, defaultProvider)
prov := res
defaultProvider = &prov
case "p":
assert.True(t, providers.IsProviderType(res.Type))
assert.Nil(t, explicitProvider)
prov := res
explicitProvider = &prov
case "a":
prov, err := providers.ParseReference(res.Provider)
assert.NoError(t, err)
assert.NotNil(t, defaultProvider)
defaultRef, err := providers.NewReference(defaultProvider.URN, defaultProvider.ID)
assert.NoError(t, err)
assert.Equal(t, defaultRef.String(), prov.String())
case "b":
prov, err := providers.ParseReference(res.Provider)
assert.NoError(t, err)
assert.NotNil(t, explicitProvider)
explicitRef, err := providers.NewReference(explicitProvider.URN, explicitProvider.ID)
assert.NoError(t, err)
assert.Equal(t, explicitRef.String(), prov.String())
}
}
assert.NotNil(t, defaultProvider)
assert.NotNil(t, explicitProvider)
},
})
}

View file

@ -6,6 +6,7 @@ import (
"testing"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/testing/integration"
"github.com/stretchr/testify/assert"
)
@ -24,10 +25,13 @@ func TestPartialState(t *testing.T) {
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// The first update tries to create a resource with state 4. This fails partially.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 2, len(stackInfo.Deployment.Resources))
assert.Equal(t, 3, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
// We should still have persisted the resource and its outputs to the snapshot
assert.Equal(t, "doomed", string(a.URN.Name()))
@ -52,11 +56,13 @@ func TestPartialState(t *testing.T) {
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// Step 3 creates a resource with state 5, which succeeds.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 2, len(stackInfo.Deployment.Resources))
assert.Equal(t, 3, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[1]
a := stackInfo.Deployment.Resources[2]
assert.Equal(t, "not-doomed", string(a.URN.Name()))
assert.Equal(t, 5.0, a.Outputs["state"].(float64))
assert.Nil(t, nil)
@ -69,10 +75,13 @@ func TestPartialState(t *testing.T) {
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// Step 4 updates the resource to have state 4, which fails partially.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 2, len(stackInfo.Deployment.Resources))
assert.Equal(t, 3, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
// We should have persisted the updated resource's new outputs
// to the snapshot.

View file

@ -8,11 +8,12 @@ import (
"github.com/stretchr/testify/assert"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/testing/integration"
)
// TestProtectedResources tests some interesting operations on protected resources.
func TestSteps(t *testing.T) {
func TestProtectedResources(t *testing.T) {
integration.ProgramTest(t, &integration.ProgramTestOptions{
Dir: "step1",
Dependencies: []string{"@pulumi/pulumi"},
@ -20,10 +21,12 @@ func TestSteps(t *testing.T) {
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// A single synthetic stack and a single "eternal" resource.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 2, len(stackInfo.Deployment.Resources))
assert.Equal(t, 3, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
assert.Equal(t, "eternal", string(a.URN.Name()))
assert.True(t, a.Protect)
},
@ -34,10 +37,12 @@ func TestSteps(t *testing.T) {
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// An update to "eternal"; should still be there.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 2, len(stackInfo.Deployment.Resources))
assert.Equal(t, 3, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
assert.Equal(t, "eternal", string(a.URN.Name()))
assert.True(t, a.Protect)
},
@ -50,10 +55,12 @@ func TestSteps(t *testing.T) {
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// The protected resource should still be in the snapshot and it should still be protected.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 2, len(stackInfo.Deployment.Resources))
assert.Equal(t, 3, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
assert.Equal(t, "eternal", string(a.URN.Name()))
assert.True(t, a.Protect)
},
@ -64,10 +71,12 @@ func TestSteps(t *testing.T) {
ExtraRuntimeValidation: func(t *testing.T, stackInfo integration.RuntimeValidationStackInfo) {
// "eternal" should now be unprotected.
assert.NotNil(t, stackInfo.Deployment)
assert.Equal(t, 2, len(stackInfo.Deployment.Resources))
assert.Equal(t, 3, len(stackInfo.Deployment.Resources))
stackRes := stackInfo.Deployment.Resources[0]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
a := stackInfo.Deployment.Resources[1]
providerRes := stackInfo.Deployment.Resources[1]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
a := stackInfo.Deployment.Resources[2]
assert.Equal(t, "eternal", string(a.URN.Name()))
assert.False(t, a.Protect)
},

View file

@ -9,6 +9,7 @@ import (
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/testing/integration"
)
@ -19,15 +20,23 @@ func validateResources(t *testing.T, resources []apitype.ResourceV2, expectedNam
expectedNamesTable[n] = struct{}{}
}
// Ensure that the resource count is correct.
assert.Equal(t, len(resources), len(expectedNames)+1)
// Pull out the stack resource, which must be the first resource in the checkpoint.
stackRes := resources[0]
stackRes, resources := resources[0], resources[1:]
assert.Equal(t, resource.RootStackType, stackRes.URN.Type())
// If there are more resources than just the stack, the second resource will be the default provider.
if len(resources) > 0 {
// Pull out the single provider resource, which should be the second resource in the checkpoint.
providerRes := resources[0]
resources = resources[1:]
assert.True(t, providers.IsProviderType(providerRes.URN.Type()))
}
// Ensure that the resource count is correct.
assert.Equal(t, len(resources), len(expectedNames))
// Ensure that exactly the provided resources are in the array.
for _, res := range resources[1:] {
for _, res := range resources {
name := string(res.URN.Name())
_, ok := expectedNamesTable[name]
assert.True(t, ok)