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`.
2018-08-07 02:50:29 +02:00
|
|
|
// 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.
|
|
|
|
|
2019-10-31 01:16:55 +01:00
|
|
|
// nolint: goconst
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
package engine
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-08-15 00:32:23 +02:00
|
|
|
"fmt"
|
2018-08-23 02:52:46 +02:00
|
|
|
"reflect"
|
|
|
|
"strconv"
|
2018-08-13 22:41:01 +02:00
|
|
|
"strings"
|
2018-09-11 00:18:25 +02:00
|
|
|
"sync"
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/blang/semver"
|
2018-09-11 01:48:14 +02:00
|
|
|
"github.com/mitchellh/copystructure"
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/stretchr/testify/assert"
|
2018-09-08 00:19:18 +02:00
|
|
|
"google.golang.org/grpc/codes"
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
2020-03-18 23:09:29 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/diag"
|
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/diag/colors"
|
2020-03-18 21:36:19 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/resource"
|
2020-03-18 23:03:37 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/resource/config"
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy/deploytest"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy/providers"
|
2020-03-18 22:26:24 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/resource/plugin"
|
2019-08-16 20:04:03 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/secrets"
|
2020-03-18 22:49:56 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/tokens"
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/util/cancel"
|
2020-03-18 22:40:07 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/util/contract"
|
2020-03-18 23:34:58 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/util/logging"
|
2019-03-20 00:21:50 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/util/result"
|
2020-03-18 23:37:13 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/util/rpcutil/rpcerror"
|
2020-03-18 22:35:53 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/common/workspace"
|
2019-11-12 23:20:06 +01:00
|
|
|
"github.com/pulumi/pulumi/sdk/go/pulumi"
|
2019-09-18 03:14:10 +02:00
|
|
|
|
|
|
|
combinations "github.com/mxschmitt/golang-combinations"
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type JournalEntryKind int
|
|
|
|
|
|
|
|
const (
|
|
|
|
JournalEntryBegin JournalEntryKind = 0
|
|
|
|
JournalEntrySuccess JournalEntryKind = 1
|
|
|
|
JournalEntryFailure JournalEntryKind = 2
|
|
|
|
JournalEntryOutputs JournalEntryKind = 4
|
|
|
|
)
|
|
|
|
|
|
|
|
type JournalEntry struct {
|
Refine resource replacement logic for providers (#2767)
This commit touches an intersection of a few different provider-oriented
features that combined to cause a particularly severe bug that made it
impossible for users to upgrade provider versions without seeing
replacements with their resources.
For some context, Pulumi models all providers as resources and places
them in the snapshot like any other resource. Every resource has a
reference to the provider that created it. If a Pulumi program does not
specify a particular provider to use when performing a resource
operation, the Pulumi engine injects one automatically; these are called
"default providers" and are the most common ways that users end up with
providers in their snapshot. Default providers can be identified by
their name, which is always prefixed with "default".
Recently, in an effort to make the Pulumi engine more flexible with
provider versions, it was made possible for the engine to have multiple
default providers active for a provider of a particular type, which was
previously not possible. Because a provider is identified as a tuple of
package name and version, it was difficult to find a name for these
duplicate default providers that did not cause additional problems. The
provider versioning PR gave these default providers a name that was
derived from the version of the package. This proved to be a problem,
because when users upgraded from one version of a package to another,
this changed the name of their default provider which in turn caused all
of their resources created using that provider (read: everything) to be
replaced.
To combat this, this PR introduces a rule that the engine will apply
when diffing a resource to determine whether or not it needs to be
replaced: "If a resource's provider changes, and both old and new
providers are default providers whose properties do not require
replacement, proceed as if there were no diff." This allows the engine
to gracefully recognize and recover when a resource's default provider changes
names, as long as the provider's config has not changed.
2019-06-03 21:16:31 +02:00
|
|
|
Kind JournalEntryKind
|
|
|
|
Step deploy.Step
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type Journal struct {
|
|
|
|
Entries []JournalEntry
|
|
|
|
events chan JournalEntry
|
|
|
|
cancel chan bool
|
|
|
|
done chan bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (j *Journal) Close() error {
|
|
|
|
close(j.cancel)
|
|
|
|
<-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)
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
ops, doneOps := []resource.Operation{}, make(map[*resource.State]bool)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
for _, e := range j.Entries {
|
|
|
|
logging.V(7).Infof("%v %v (%v)", e.Step.Op(), e.Step.URN(), e.Kind)
|
|
|
|
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
// Begin journal entries add pending operations to the snapshot. As we see success or failure
|
|
|
|
// entries, we'll record them in doneOps.
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
switch e.Kind {
|
|
|
|
case JournalEntryBegin:
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
switch e.Step.Op() {
|
|
|
|
case deploy.OpCreate, deploy.OpCreateReplacement:
|
|
|
|
ops = append(ops, resource.NewOperation(e.Step.New(), resource.OperationTypeCreating))
|
2019-01-31 22:48:44 +01:00
|
|
|
case deploy.OpDelete, deploy.OpDeleteReplaced, deploy.OpReadDiscard, deploy.OpDiscardReplaced:
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
ops = append(ops, resource.NewOperation(e.Step.Old(), resource.OperationTypeDeleting))
|
|
|
|
case deploy.OpRead, deploy.OpReadReplacement:
|
|
|
|
ops = append(ops, resource.NewOperation(e.Step.New(), resource.OperationTypeReading))
|
|
|
|
case deploy.OpUpdate:
|
|
|
|
ops = append(ops, resource.NewOperation(e.Step.New(), resource.OperationTypeUpdating))
|
2019-07-12 20:12:01 +02:00
|
|
|
case deploy.OpImport, deploy.OpImportReplacement:
|
|
|
|
ops = append(ops, resource.NewOperation(e.Step.New(), resource.OperationTypeImporting))
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
}
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
case JournalEntryFailure, JournalEntrySuccess:
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
switch e.Step.Op() {
|
2019-01-31 22:48:44 +01:00
|
|
|
// nolint: lll
|
2019-07-12 20:12:01 +02:00
|
|
|
case deploy.OpCreate, deploy.OpCreateReplacement, deploy.OpRead, deploy.OpReadReplacement, deploy.OpUpdate,
|
|
|
|
deploy.OpImport, deploy.OpImportReplacement:
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
doneOps[e.Step.New()] = true
|
2019-01-31 22:48:44 +01:00
|
|
|
case deploy.OpDelete, deploy.OpDeleteReplaced, deploy.OpReadDiscard, deploy.OpDiscardReplaced:
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
doneOps[e.Step.Old()] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
// Now mark resources done as necessary.
|
|
|
|
if e.Kind == JournalEntrySuccess {
|
|
|
|
switch e.Step.Op() {
|
|
|
|
case deploy.OpSame, deploy.OpUpdate:
|
|
|
|
resources = append(resources, e.Step.New())
|
2018-08-09 21:45:39 +02:00
|
|
|
dones[e.Step.Old()] = true
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
case deploy.OpCreate, deploy.OpCreateReplacement:
|
|
|
|
resources = append(resources, e.Step.New())
|
|
|
|
if old := e.Step.Old(); old != nil && old.PendingReplacement {
|
|
|
|
dones[old] = true
|
|
|
|
}
|
2019-01-31 22:48:44 +01:00
|
|
|
case deploy.OpDelete, deploy.OpDeleteReplaced, deploy.OpReadDiscard, deploy.OpDiscardReplaced:
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
if old := e.Step.Old(); !old.PendingReplacement {
|
|
|
|
dones[old] = true
|
|
|
|
}
|
|
|
|
case deploy.OpReplace:
|
|
|
|
// do nothing.
|
|
|
|
case deploy.OpRead, deploy.OpReadReplacement:
|
|
|
|
resources = append(resources, e.Step.New())
|
|
|
|
if e.Step.Old() != nil {
|
|
|
|
dones[e.Step.Old()] = true
|
|
|
|
}
|
|
|
|
case deploy.OpRemovePendingReplace:
|
Refine resource replacement logic for providers (#2767)
This commit touches an intersection of a few different provider-oriented
features that combined to cause a particularly severe bug that made it
impossible for users to upgrade provider versions without seeing
replacements with their resources.
For some context, Pulumi models all providers as resources and places
them in the snapshot like any other resource. Every resource has a
reference to the provider that created it. If a Pulumi program does not
specify a particular provider to use when performing a resource
operation, the Pulumi engine injects one automatically; these are called
"default providers" and are the most common ways that users end up with
providers in their snapshot. Default providers can be identified by
their name, which is always prefixed with "default".
Recently, in an effort to make the Pulumi engine more flexible with
provider versions, it was made possible for the engine to have multiple
default providers active for a provider of a particular type, which was
previously not possible. Because a provider is identified as a tuple of
package name and version, it was difficult to find a name for these
duplicate default providers that did not cause additional problems. The
provider versioning PR gave these default providers a name that was
derived from the version of the package. This proved to be a problem,
because when users upgraded from one version of a package to another,
this changed the name of their default provider which in turn caused all
of their resources created using that provider (read: everything) to be
replaced.
To combat this, this PR introduces a rule that the engine will apply
when diffing a resource to determine whether or not it needs to be
replaced: "If a resource's provider changes, and both old and new
providers are default providers whose properties do not require
replacement, proceed as if there were no diff." This allows the engine
to gracefully recognize and recover when a resource's default provider changes
names, as long as the provider's config has not changed.
2019-06-03 21:16:31 +02:00
|
|
|
dones[e.Step.Old()] = true
|
2019-07-12 20:12:01 +02:00
|
|
|
case deploy.OpImport, deploy.OpImportReplacement:
|
|
|
|
resources = append(resources, e.Step.New())
|
|
|
|
dones[e.Step.New()] = true
|
2018-08-09 21:45:39 +02:00
|
|
|
}
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add a list of in-flight operations to the deployment (#1759)
* Add a list of in-flight operations to the deployment
This commit augments 'DeploymentV2' with a list of operations that are
currently in flight. This information is used by the engine to keep
track of whether or not a particular deployment is in a valid state.
The SnapshotManager is responsible for inserting and removing operations
from the in-flight operation list. When the engine registers an intent
to perform an operation, SnapshotManager inserts an Operation into this
list and saves it to the snapshot. When an operation completes, the
SnapshotManager removes it from the snapshot. From this, the engine can
infer that if it ever sees a deployment with pending operations, the
Pulumi CLI must have crashed or otherwise abnormally terminated before
seeing whether or not an operation completed successfully.
To remedy this state, this commit also adds code to 'pulumi stack
import' that clears all pending operations from a deployment, as well as
code to plan generation that will reject any deployments that have
pending operations present.
At the CLI level, if we see that we are in a state where pending
operations were in-flight when the engine died, we'll issue a
human-friendly error message that indicates which resources are in a bad
state and how to recover their stack.
* CR: Multi-line string literals, renaming in-flight -> pending
* CR: Add enum to apitype for operation type, also name status -> type for clarity
* Fix the yaml type
* Fix missed renames
* Add implementation for lifecycle_test.go
* Rebase against master
2018-08-11 06:39:59 +02:00
|
|
|
// Append any pending operations.
|
|
|
|
var operations []resource.Operation
|
|
|
|
for _, op := range ops {
|
|
|
|
if !doneOps[op.Resource] {
|
|
|
|
operations = append(operations, op)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-24 21:13:00 +02:00
|
|
|
// If we have a base snapshot, copy over its secrets manager.
|
|
|
|
var secretsManager secrets.Manager
|
|
|
|
if base != nil {
|
|
|
|
secretsManager = base.SecretsManager
|
|
|
|
}
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
manifest := deploy.Manifest{}
|
|
|
|
manifest.Magic = manifest.NewMagic()
|
2019-04-24 21:13:00 +02:00
|
|
|
return deploy.NewSnapshot(manifest, secretsManager, resources, operations)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
2019-01-31 23:27:53 +01:00
|
|
|
func (j *Journal) SuccessfulSteps() []deploy.Step {
|
|
|
|
var steps []deploy.Step
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Kind == JournalEntrySuccess {
|
|
|
|
steps = append(steps, entry.Step)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return steps
|
|
|
|
}
|
|
|
|
|
|
|
|
type StepSummary struct {
|
|
|
|
Op deploy.StepOp
|
|
|
|
URN resource.URN
|
|
|
|
}
|
|
|
|
|
|
|
|
func AssertSameSteps(t *testing.T, expected []StepSummary, actual []deploy.Step) bool {
|
|
|
|
assert.Equal(t, len(expected), len(actual))
|
|
|
|
for _, exp := range expected {
|
|
|
|
act := actual[0]
|
|
|
|
actual = actual[1:]
|
|
|
|
|
|
|
|
if !assert.Equal(t, exp.Op, act.Op()) || !assert.Equal(t, exp.URN, act.URN()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
func newJournal() *Journal {
|
|
|
|
j := &Journal{
|
|
|
|
events: make(chan JournalEntry),
|
|
|
|
cancel: make(chan bool),
|
|
|
|
done: make(chan bool),
|
|
|
|
}
|
|
|
|
go func() {
|
2018-08-30 06:00:05 +02:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-j.cancel:
|
|
|
|
close(j.done)
|
|
|
|
return
|
|
|
|
case e := <-j.events:
|
|
|
|
j.Entries = append(j.Entries, e)
|
|
|
|
}
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
type TestOp func(UpdateInfo, *Context, UpdateOptions, bool) (ResourceChanges, result.Result)
|
|
|
|
type ValidateFunc func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
events []Event, res result.Result) result.Result
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
func (op TestOp) Run(project workspace.Project, target deploy.Target, opts UpdateOptions,
|
2019-03-20 00:21:50 +01:00
|
|
|
dryRun bool, backendClient deploy.BackendClient, validate ValidateFunc) (*deploy.Snapshot, result.Result) {
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
2018-11-14 22:33:35 +01:00
|
|
|
return op.RunWithContext(context.Background(), project, target, opts, dryRun, backendClient, validate)
|
2018-08-30 06:00:05 +02:00
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
func (op TestOp) RunWithContext(
|
|
|
|
callerCtx context.Context, project workspace.Project,
|
|
|
|
target deploy.Target, opts UpdateOptions, dryRun bool,
|
|
|
|
backendClient deploy.BackendClient, validate ValidateFunc) (*deploy.Snapshot, result.Result) {
|
2018-08-30 06:00:05 +02:00
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// Create an appropriate update info and context.
|
|
|
|
info := &updateInfo{project: project, target: target}
|
|
|
|
|
2018-09-11 00:18:25 +02:00
|
|
|
cancelCtx, cancelSrc := cancel.NewContext(context.Background())
|
|
|
|
done := make(chan bool)
|
|
|
|
defer close(done)
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case <-callerCtx.Done():
|
|
|
|
cancelSrc.Cancel()
|
|
|
|
case <-done:
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
events := make(chan Event)
|
|
|
|
journal := newJournal()
|
|
|
|
|
|
|
|
ctx := &Context{
|
|
|
|
Cancel: cancelCtx,
|
|
|
|
Events: events,
|
|
|
|
SnapshotManager: journal,
|
2018-11-14 22:33:35 +01:00
|
|
|
BackendClient: backendClient,
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Begin draining events.
|
2018-08-13 22:41:01 +02:00
|
|
|
var firedEvents []Event
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
go func() {
|
2018-08-13 22:41:01 +02:00
|
|
|
for e := range events {
|
|
|
|
firedEvents = append(firedEvents, e)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Run the step and its validator.
|
2019-03-20 00:21:50 +01:00
|
|
|
_, res := op(info, ctx, opts, dryRun)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
contract.IgnoreClose(journal)
|
|
|
|
|
|
|
|
if dryRun {
|
2019-03-20 00:21:50 +01:00
|
|
|
return nil, res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
if validate != nil {
|
2019-03-20 00:21:50 +01:00
|
|
|
res = validate(project, target, journal, firedEvents, res)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
snap := journal.Snap(target.Snapshot)
|
2019-03-20 00:21:50 +01:00
|
|
|
if res == nil && snap != nil {
|
|
|
|
res = result.WrapIfNonNil(snap.VerifyIntegrity())
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
2019-03-20 00:21:50 +01:00
|
|
|
return snap, res
|
2018-08-30 06:00:05 +02:00
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type TestStep struct {
|
2018-08-13 22:41:01 +02:00
|
|
|
Op TestOp
|
|
|
|
ExpectFailure bool
|
|
|
|
SkipPreview bool
|
|
|
|
Validate ValidateFunc
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type TestPlan struct {
|
2018-11-14 22:33:35 +01:00
|
|
|
Project string
|
|
|
|
Stack string
|
|
|
|
Runtime string
|
|
|
|
Config config.Map
|
|
|
|
Decrypter config.Decrypter
|
|
|
|
BackendClient deploy.BackendClient
|
|
|
|
Options UpdateOptions
|
|
|
|
Steps []TestStep
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
2018-11-05 22:36:35 +01:00
|
|
|
//nolint: goconst
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-08-30 06:00:05 +02:00
|
|
|
func (p *TestPlan) GetProject() workspace.Project {
|
|
|
|
_, projectName, runtime := p.getNames()
|
|
|
|
|
|
|
|
return workspace.Project{
|
2018-11-01 16:28:11 +01:00
|
|
|
Name: projectName,
|
|
|
|
Runtime: workspace.NewProjectRuntimeInfo(runtime, nil),
|
2018-08-30 06:00:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *TestPlan) GetTarget(snapshot *deploy.Snapshot) deploy.Target {
|
|
|
|
stack, _, _ := p.getNames()
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
cfg := p.Config
|
|
|
|
if cfg == nil {
|
|
|
|
cfg = config.Map{}
|
|
|
|
}
|
|
|
|
|
2018-08-30 06:00:05 +02:00
|
|
|
return deploy.Target{
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
Name: stack,
|
|
|
|
Config: cfg,
|
|
|
|
Decrypter: p.Decrypter,
|
|
|
|
Snapshot: snapshot,
|
|
|
|
}
|
2018-08-30 06:00:05 +02:00
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
func assertIsErrorOrBailResult(t *testing.T, res result.Result) {
|
|
|
|
assert.NotNil(t, res)
|
|
|
|
}
|
|
|
|
|
2018-08-30 06:00:05 +02:00
|
|
|
func (p *TestPlan) Run(t *testing.T, snapshot *deploy.Snapshot) *deploy.Snapshot {
|
2018-09-11 01:48:14 +02:00
|
|
|
project := p.GetProject()
|
|
|
|
snap := snapshot
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
for _, step := range p.Steps {
|
2018-09-11 01:48:14 +02:00
|
|
|
// note: it's really important that the preview and update operate on different snapshots. the engine can and
|
|
|
|
// does mutate the snapshot in-place, even in previews, and sharing a snapshot between preview and update can
|
|
|
|
// cause state changes from the preview to persist even when doing an update.
|
2018-08-13 22:41:01 +02:00
|
|
|
if !step.SkipPreview {
|
2018-09-11 01:48:14 +02:00
|
|
|
previewSnap := CloneSnapshot(t, snap)
|
|
|
|
previewTarget := p.GetTarget(previewSnap)
|
2019-03-20 00:21:50 +01:00
|
|
|
_, res := step.Op.Run(project, previewTarget, p.Options, true, p.BackendClient, step.Validate)
|
2018-08-13 22:41:01 +02:00
|
|
|
if step.ExpectFailure {
|
2019-03-20 00:21:50 +01:00
|
|
|
assertIsErrorOrBailResult(t, res)
|
2018-08-13 22:41:01 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
assert.Nil(t, res)
|
2018-08-13 22:41:01 +02:00
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
var res result.Result
|
2018-09-11 01:48:14 +02:00
|
|
|
target := p.GetTarget(snap)
|
2019-03-20 00:21:50 +01:00
|
|
|
snap, res = step.Op.Run(project, target, p.Options, false, p.BackendClient, step.Validate)
|
2018-08-13 22:41:01 +02:00
|
|
|
if step.ExpectFailure {
|
2019-03-20 00:21:50 +01:00
|
|
|
assertIsErrorOrBailResult(t, res)
|
2018-08-13 22:41:01 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-09-20 04:28:14 +02:00
|
|
|
if res != nil {
|
|
|
|
if res.IsBail() {
|
|
|
|
t.Logf("Got unexpected bail result")
|
|
|
|
t.FailNow()
|
|
|
|
} else {
|
|
|
|
t.Logf("Got unexpected error result: %v", res.Error())
|
|
|
|
t.FailNow()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
assert.Nil(t, res)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
2018-09-11 01:48:14 +02:00
|
|
|
return snap
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloneSnapshot makes a deep copy of the given snapshot and returns a pointer to the clone.
|
|
|
|
func CloneSnapshot(t *testing.T, snap *deploy.Snapshot) *deploy.Snapshot {
|
|
|
|
t.Helper()
|
|
|
|
if snap != nil {
|
|
|
|
copiedSnap := copystructure.Must(copystructure.Copy(*snap)).(deploy.Snapshot)
|
|
|
|
assert.True(t, reflect.DeepEqual(*snap, copiedSnap))
|
|
|
|
return &copiedSnap
|
|
|
|
}
|
|
|
|
|
|
|
|
return snap
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func MakeBasicLifecycleSteps(t *testing.T, resCount int) []TestStep {
|
|
|
|
return []TestStep{
|
|
|
|
// Initial update
|
|
|
|
{
|
|
|
|
Op: Update,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// 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)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
// No-op refresh
|
|
|
|
{
|
|
|
|
Op: Refresh,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
2018-08-23 02:52:46 +02:00
|
|
|
// Should see only refresh-sames.
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
for _, entry := range j.Entries {
|
2018-08-23 02:52:46 +02:00
|
|
|
assert.Equal(t, deploy.OpRefresh, entry.Step.Op())
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.(*deploy.RefreshStep).ResultOp())
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
assert.Len(t, j.Snap(target.Snapshot).Resources, resCount)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
// No-op update
|
|
|
|
{
|
|
|
|
Op: Update,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// 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)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
// No-op refresh
|
|
|
|
{
|
|
|
|
Op: Refresh,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
2018-08-23 02:52:46 +02:00
|
|
|
// Should see only referesh-sames.
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
for _, entry := range j.Entries {
|
2018-08-23 02:52:46 +02:00
|
|
|
assert.Equal(t, deploy.OpRefresh, entry.Step.Op())
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.(*deploy.RefreshStep).ResultOp())
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
assert.Len(t, j.Snap(target.Snapshot).Resources, resCount)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
// Destroy
|
|
|
|
{
|
|
|
|
Op: Destroy,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// Should see only deletes.
|
|
|
|
for _, entry := range j.Entries {
|
2019-01-31 22:48:44 +01:00
|
|
|
switch entry.Step.Op() {
|
|
|
|
case deploy.OpDelete, deploy.OpReadDiscard:
|
|
|
|
// ok
|
|
|
|
default:
|
|
|
|
assert.Fail(t, "expected OpDelete or OpReadDiscard")
|
|
|
|
}
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
assert.Len(t, j.Snap(target.Snapshot).Resources, 0)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
// No-op refresh
|
|
|
|
{
|
|
|
|
Op: Refresh,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.Len(t, j.Entries, 0)
|
|
|
|
assert.Len(t, j.Snap(target.Snapshot).Resources, 0)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEmptyProgramLifecycle(t *testing.T) {
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, _ *deploytest.ResourceMonitor) error {
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
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 {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
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 {
|
2019-07-25 20:18:40 +02:00
|
|
|
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
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 {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
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{},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
2018-08-23 02:52:46 +02:00
|
|
|
isRefresh := false
|
2019-03-20 00:21:50 +01:00
|
|
|
validate := func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// 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:
|
2018-08-23 02:52:46 +02:00
|
|
|
expect := deploy.OpSame
|
|
|
|
if isRefresh {
|
|
|
|
expect = deploy.OpRefresh
|
|
|
|
}
|
|
|
|
assert.Equal(t, expect, entry.Step.Op())
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.Len(t, j.Snap(target.Snapshot).Resources, 2)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2018-08-23 02:52:46 +02:00
|
|
|
isRefresh = true
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
p.Steps = []TestStep{{Op: Refresh, Validate: validate}}
|
|
|
|
p.Run(t, old)
|
|
|
|
|
|
|
|
// Run a single destroy step using the base snapshot.
|
2018-08-23 02:52:46 +02:00
|
|
|
isRefresh = false
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Destroy,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// 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)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
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{
|
2019-05-23 19:54:18 +02:00
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
2019-07-31 18:39:07 +02:00
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// 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 {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
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,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
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)
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
}}
|
2018-09-11 01:48:14 +02:00
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
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{
|
2019-05-23 19:54:18 +02:00
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
2019-07-31 18:39:07 +02:00
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// 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 {
|
2019-07-25 20:18:40 +02:00
|
|
|
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true,
|
|
|
|
deploytest.ResourceOptions{Inputs: providerInputs})
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
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,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
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)
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
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{
|
2019-05-23 19:54:18 +02:00
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
2019-07-31 18:39:07 +02:00
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
// 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 {
|
2019-07-25 20:18:40 +02:00
|
|
|
provURN, provID, _, err := monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true,
|
|
|
|
deploytest.ResourceOptions{Inputs: providerInputs})
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Provider: provRef.String(),
|
|
|
|
})
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
|
|
|
|
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,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
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)
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
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`.
2018-08-07 02:50:29 +02:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Resume the lifecycle with another no-op update.
|
|
|
|
p.Steps = steps[2:]
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
2018-08-07 20:01:08 +02:00
|
|
|
|
2018-11-22 01:53:29 +01:00
|
|
|
func TestSingleResourceDiffUnavailable(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
2019-07-31 18:39:07 +02:00
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
2018-11-22 01:53:29 +01:00
|
|
|
|
|
|
|
return plugin.DiffResult{}, plugin.DiffUnavailable("diff unavailable")
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
inputs := resource.PropertyMap{}
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputs,
|
|
|
|
})
|
2018-11-22 01:53:29 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Run the initial update.
|
|
|
|
project := p.GetProject()
|
2019-03-20 00:21:50 +01:00
|
|
|
snap, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.Nil(t, res)
|
2018-11-22 01:53:29 +01:00
|
|
|
|
|
|
|
// Now change the inputs to our resource and run a preview.
|
|
|
|
inputs = resource.PropertyMap{"foo": resource.NewStringProperty("bar")}
|
2019-03-20 00:21:50 +01:00
|
|
|
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, true, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, _ *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
|
2018-11-22 01:53:29 +01:00
|
|
|
found := false
|
|
|
|
for _, e := range events {
|
|
|
|
if e.Type == DiagEvent {
|
|
|
|
p := e.Payload.(DiagEventPayload)
|
|
|
|
if p.URN == resURN && p.Severity == diag.Warning && p.Message == "diff unavailable" {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, found)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-11-22 01:53:29 +01:00
|
|
|
})
|
2019-03-20 00:21:50 +01:00
|
|
|
assert.Nil(t, res)
|
2018-11-22 01:53:29 +01:00
|
|
|
}
|
|
|
|
|
2018-08-07 20:01:08 +02:00
|
|
|
func TestDestroyWithPendingDelete(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, _ *deploytest.ResourceMonitor) error {
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-07 20:01:08 +02:00
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Create an old snapshot with two copies of a resource that share a URN: one that is pending deletion and one
|
|
|
|
// that is not.
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
|
|
|
{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "1",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "0",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
Delete: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(_ workspace.Project, _ deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
2018-11-05 22:36:35 +01:00
|
|
|
// Verify that we see a DeleteReplacement for the resource with ID 0 and a Delete for the resource with
|
2018-08-07 20:01:08 +02:00
|
|
|
// ID 1.
|
|
|
|
deletedID0, deletedID1 := false, false
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
// Ignore non-terminal steps and steps that affect the injected default provider.
|
|
|
|
if entry.Kind != JournalEntrySuccess || entry.Step.URN() != resURN ||
|
|
|
|
(entry.Step.Op() != deploy.OpDelete && entry.Step.Op() != deploy.OpDeleteReplaced) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch id := entry.Step.Old().ID; id {
|
|
|
|
case "0":
|
|
|
|
assert.False(t, deletedID0)
|
|
|
|
deletedID0 = true
|
|
|
|
case "1":
|
|
|
|
assert.False(t, deletedID1)
|
|
|
|
deletedID1 = true
|
|
|
|
default:
|
|
|
|
assert.Fail(t, "unexpected resource ID %v", string(id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, deletedID0)
|
|
|
|
assert.True(t, deletedID1)
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-08-07 20:01:08 +02:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUpdateWithPendingDelete(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, nil, loaders...)
|
2018-08-07 20:01:08 +02:00
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Create an old snapshot with two copies of a resource that share a URN: one that is pending deletion and one
|
|
|
|
// that is not.
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
|
|
|
{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "1",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "0",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
Delete: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Destroy,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(_ workspace.Project, _ deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
2018-11-05 22:36:35 +01:00
|
|
|
// Verify that we see a DeleteReplacement for the resource with ID 0 and a Delete for the resource with
|
2018-08-07 20:01:08 +02:00
|
|
|
// ID 1.
|
|
|
|
deletedID0, deletedID1 := false, false
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
// Ignore non-terminal steps and steps that affect the injected default provider.
|
|
|
|
if entry.Kind != JournalEntrySuccess || entry.Step.URN() != resURN ||
|
|
|
|
(entry.Step.Op() != deploy.OpDelete && entry.Step.Op() != deploy.OpDeleteReplaced) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch id := entry.Step.Old().ID; id {
|
|
|
|
case "0":
|
|
|
|
assert.False(t, deletedID0)
|
|
|
|
deletedID0 = true
|
|
|
|
case "1":
|
|
|
|
assert.False(t, deletedID1)
|
|
|
|
deletedID1 = true
|
|
|
|
default:
|
|
|
|
assert.Fail(t, "unexpected resource ID %v", string(id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, deletedID0)
|
|
|
|
assert.True(t, deletedID1)
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-08-07 20:01:08 +02:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
2018-08-08 22:45:48 +02:00
|
|
|
|
|
|
|
func TestParallelRefresh(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a program that registers four resources, each of which depends on the resource that immediately precedes
|
|
|
|
// it.
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
resA, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2018-08-08 22:45:48 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
resB, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true, deploytest.ResourceOptions{
|
|
|
|
Dependencies: []resource.URN{resA},
|
|
|
|
})
|
2018-08-08 22:45:48 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
resC, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resC", true, deploytest.ResourceOptions{
|
|
|
|
Dependencies: []resource.URN{resB},
|
|
|
|
})
|
2018-08-08 22:45:48 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resD", true, deploytest.ResourceOptions{
|
|
|
|
Dependencies: []resource.URN{resC},
|
|
|
|
})
|
2018-08-08 22:45:48 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-08 22:45:48 +02:00
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{Parallel: 4, host: host},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
assert.Len(t, snap.Resources, 5)
|
|
|
|
assert.Equal(t, string(snap.Resources[0].URN.Name()), "default") // provider
|
|
|
|
assert.Equal(t, string(snap.Resources[1].URN.Name()), "resA")
|
|
|
|
assert.Equal(t, string(snap.Resources[2].URN.Name()), "resB")
|
|
|
|
assert.Equal(t, string(snap.Resources[3].URN.Name()), "resC")
|
|
|
|
assert.Equal(t, string(snap.Resources[4].URN.Name()), "resD")
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Refresh}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
assert.Len(t, snap.Resources, 5)
|
|
|
|
assert.Equal(t, string(snap.Resources[0].URN.Name()), "default") // provider
|
|
|
|
assert.Equal(t, string(snap.Resources[1].URN.Name()), "resA")
|
|
|
|
assert.Equal(t, string(snap.Resources[2].URN.Name()), "resB")
|
|
|
|
assert.Equal(t, string(snap.Resources[3].URN.Name()), "resC")
|
|
|
|
assert.Equal(t, string(snap.Resources[4].URN.Name()), "resD")
|
|
|
|
}
|
2018-08-09 21:45:39 +02:00
|
|
|
|
|
|
|
func TestExternalRefresh(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Our program reads a resource and exits.
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-04-17 20:25:02 +02:00
|
|
|
_, _, err := monitor.ReadResource("pkgA:m:typA", "resA", "resA-some-id", "", resource.PropertyMap{}, "", "")
|
2018-08-09 21:45:39 +02:00
|
|
|
if !assert.NoError(t, err) {
|
|
|
|
t.FailNow()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-09 21:45:39 +02:00
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{Op: Update}},
|
|
|
|
}
|
|
|
|
|
|
|
|
// The read should place "resA" in the snapshot with the "External" bit set.
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
assert.Len(t, snap.Resources, 2)
|
|
|
|
assert.Equal(t, string(snap.Resources[0].URN.Name()), "default") // provider
|
|
|
|
assert.Equal(t, string(snap.Resources[1].URN.Name()), "resA")
|
|
|
|
assert.True(t, snap.Resources[1].External)
|
|
|
|
|
|
|
|
p = &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{Op: Refresh}},
|
|
|
|
}
|
|
|
|
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
// A refresh should leave "resA" as it is in the snapshot. The External bit should still be set.
|
|
|
|
assert.Len(t, snap.Resources, 2)
|
|
|
|
assert.Equal(t, string(snap.Resources[0].URN.Name()), "default") // provider
|
|
|
|
assert.Equal(t, string(snap.Resources[1].URN.Name()), "resA")
|
|
|
|
assert.True(t, snap.Resources[1].External)
|
|
|
|
}
|
2018-08-07 09:40:43 +02:00
|
|
|
|
|
|
|
func TestRefreshInitFailure(t *testing.T) {
|
2018-09-05 23:00:28 +02:00
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
res2URN := p.NewURN("pkgA:m:typA", "resB", "")
|
|
|
|
|
|
|
|
res2Outputs := resource.PropertyMap{"foo": resource.NewStringProperty("bar")}
|
|
|
|
|
2018-08-07 09:40:43 +02:00
|
|
|
//
|
|
|
|
// Refresh will persist any initialization errors that are returned by `Read`. This provider
|
|
|
|
// will error out or not based on the value of `refreshShouldFail`.
|
|
|
|
//
|
|
|
|
refreshShouldFail := false
|
|
|
|
|
|
|
|
//
|
|
|
|
// Set up test environment to use `readFailProvider` as the underlying resource provider.
|
|
|
|
//
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(
|
2019-03-11 21:50:00 +01:00
|
|
|
urn resource.URN, id resource.ID, inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
2018-09-05 23:00:28 +02:00
|
|
|
if refreshShouldFail && urn == resURN {
|
2018-08-07 09:40:43 +02:00
|
|
|
err := &plugin.InitError{
|
|
|
|
Reasons: []string{"Refresh reports continued to fail to initialize"},
|
|
|
|
}
|
2019-03-11 21:50:00 +01:00
|
|
|
return plugin.ReadResult{Outputs: resource.PropertyMap{}}, resource.StatusPartialFailure, err
|
2018-09-05 23:00:28 +02:00
|
|
|
} else if urn == res2URN {
|
2019-03-11 21:50:00 +01:00
|
|
|
return plugin.ReadResult{Outputs: res2Outputs}, resource.StatusOK, nil
|
2018-08-07 09:40:43 +02:00
|
|
|
}
|
2019-03-11 21:50:00 +01:00
|
|
|
return plugin.ReadResult{Outputs: resource.PropertyMap{}}, resource.StatusOK, nil
|
2018-08-07 09:40:43 +02:00
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2018-08-07 09:40:43 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-07 09:40:43 +02:00
|
|
|
|
2018-09-05 23:00:28 +02:00
|
|
|
p.Options.host = host
|
2018-08-07 09:40:43 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// Create an old snapshot with a single initialization failure.
|
|
|
|
//
|
|
|
|
old := &deploy.Snapshot{
|
2018-09-05 23:00:28 +02:00
|
|
|
Resources: []*resource.State{
|
|
|
|
{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "0",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
InitErrors: []string{"Resource failed to initialize"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: res2URN.Type(),
|
|
|
|
URN: res2URN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "1",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
},
|
|
|
|
},
|
2018-08-07 09:40:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Refresh DOES NOT fail, causing the initialization error to disappear.
|
|
|
|
//
|
|
|
|
p.Steps = []TestStep{{Op: Refresh}}
|
|
|
|
snap := p.Run(t, old)
|
|
|
|
|
|
|
|
for _, resource := range snap.Resources {
|
|
|
|
switch urn := resource.URN; urn {
|
|
|
|
case provURN:
|
|
|
|
// break
|
|
|
|
case resURN:
|
2018-08-23 02:52:46 +02:00
|
|
|
assert.Empty(t, resource.InitErrors)
|
2018-09-05 23:00:28 +02:00
|
|
|
case res2URN:
|
|
|
|
assert.Equal(t, res2Outputs, resource.Outputs)
|
2018-08-07 09:40:43 +02:00
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2019-04-10 22:47:54 +02:00
|
|
|
// Refresh again, see the resource is in a partial state of failure, but the refresh operation
|
|
|
|
// DOES NOT fail. The initialization error is still persisted.
|
2018-08-07 09:40:43 +02:00
|
|
|
//
|
|
|
|
refreshShouldFail = true
|
2019-04-10 22:47:54 +02:00
|
|
|
p.Steps = []TestStep{{Op: Refresh, SkipPreview: true}}
|
2018-08-07 09:40:43 +02:00
|
|
|
snap = p.Run(t, old)
|
|
|
|
for _, resource := range snap.Resources {
|
|
|
|
switch urn := resource.URN; urn {
|
|
|
|
case provURN:
|
|
|
|
// break
|
|
|
|
case resURN:
|
|
|
|
assert.Equal(t, []string{"Refresh reports continued to fail to initialize"}, resource.InitErrors)
|
2018-09-05 23:00:28 +02:00
|
|
|
case res2URN:
|
|
|
|
assert.Equal(t, res2Outputs, resource.Outputs)
|
2018-08-07 09:40:43 +02:00
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-08-13 22:41:01 +02:00
|
|
|
|
|
|
|
// Test that ensures that we log diagnostics for resources that receive an error from Check. (Note that this
|
|
|
|
// is distinct from receiving non-error failures from Check.)
|
|
|
|
func TestCheckFailureRecord(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CheckF: func(urn resource.URN,
|
|
|
|
olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
|
|
|
return nil, nil, errors.New("oh no, check had an error")
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2018-08-13 22:41:01 +02:00
|
|
|
assert.Error(t, err)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-13 22:41:01 +02:00
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
SkipPreview: true,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
2018-08-13 22:41:01 +02:00
|
|
|
sawFailure := false
|
|
|
|
for _, evt := range evts {
|
|
|
|
if evt.Type == DiagEvent {
|
|
|
|
e := evt.Payload.(DiagEventPayload)
|
|
|
|
msg := colors.Never.Colorize(e.Message)
|
|
|
|
sawFailure = msg == "oh no, check had an error\n" && e.Severity == diag.Error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.True(t, sawFailure)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-08-13 22:41:01 +02:00
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test that checks that we emit diagnostics for properties that check says are invalid.
|
|
|
|
func TestCheckFailureInvalidPropertyRecord(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CheckF: func(urn resource.URN,
|
|
|
|
olds, news resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
|
|
|
return nil, []plugin.CheckFailure{{
|
|
|
|
Property: "someprop",
|
|
|
|
Reason: "field is not valid",
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2018-08-13 22:41:01 +02:00
|
|
|
assert.Error(t, err)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-13 22:41:01 +02:00
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
SkipPreview: true,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
2018-08-13 22:41:01 +02:00
|
|
|
sawFailure := false
|
|
|
|
for _, evt := range evts {
|
|
|
|
if evt.Type == DiagEvent {
|
|
|
|
e := evt.Payload.(DiagEventPayload)
|
|
|
|
msg := colors.Never.Colorize(e.Message)
|
|
|
|
sawFailure = strings.Contains(msg, "field is not valid") && e.Severity == diag.Error
|
|
|
|
if sawFailure {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.True(t, sawFailure)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-08-13 22:41:01 +02:00
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
}
|
2018-08-15 00:32:23 +02:00
|
|
|
|
|
|
|
// Test that tests that Refresh can detect that resources have been deleted and removes them
|
|
|
|
// from the snapshot.
|
|
|
|
func TestRefreshWithDelete(t *testing.T) {
|
|
|
|
for _, parallelFactor := range []int{1, 4} {
|
|
|
|
t.Run(fmt.Sprintf("parallel-%d", parallelFactor), func(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(
|
2019-03-11 21:50:00 +01:00
|
|
|
urn resource.URN, id resource.ID, inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
2018-08-15 00:32:23 +02:00
|
|
|
// This thing doesn't exist. Returning nil from Read should trigger
|
|
|
|
// the engine to delete it from the snapshot.
|
2019-03-11 21:50:00 +01:00
|
|
|
return plugin.ReadResult{}, resource.StatusOK, nil
|
2018-08-15 00:32:23 +02:00
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2018-08-15 00:32:23 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-15 00:32:23 +02:00
|
|
|
p := &TestPlan{Options: UpdateOptions{host: host, Parallel: parallelFactor}}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Refresh}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Refresh succeeds and records that the resource in the snapshot doesn't exist anymore
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
assert.Len(t, snap.Resources, 1)
|
|
|
|
assert.Equal(t, provURN, snap.Resources[0].URN)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2018-08-21 01:41:07 +02:00
|
|
|
|
2019-09-20 04:28:14 +02:00
|
|
|
func pickURN(t *testing.T, urns []resource.URN, names []string, target string) resource.URN {
|
|
|
|
assert.Equal(t, len(urns), len(names))
|
|
|
|
assert.Contains(t, names, target)
|
|
|
|
|
|
|
|
for i, name := range names {
|
|
|
|
if name == target {
|
|
|
|
return urns[i]
|
|
|
|
}
|
2019-09-18 03:14:10 +02:00
|
|
|
}
|
2019-09-20 04:28:14 +02:00
|
|
|
|
|
|
|
t.Fatalf("Could not find target: %v in %v", target, names)
|
|
|
|
return ""
|
2019-09-18 03:14:10 +02:00
|
|
|
}
|
|
|
|
|
2018-08-23 02:52:46 +02:00
|
|
|
// Tests that dependencies are correctly rewritten when refresh removes deleted resources.
|
|
|
|
func TestRefreshDeleteDependencies(t *testing.T) {
|
2019-09-18 03:14:10 +02:00
|
|
|
names := []string{"resA", "resB", "resC"}
|
|
|
|
|
|
|
|
// Try refreshing a stack with every combination of the three above resources as a target to
|
|
|
|
// refresh.
|
|
|
|
subsets := combinations.All(names)
|
|
|
|
|
|
|
|
// combinations.All doesn't return the empty set. So explicitly test that case (i.e. test no
|
|
|
|
// targets specified)
|
|
|
|
validateRefreshDeleteCombination(t, names, []string{})
|
|
|
|
|
|
|
|
for _, subset := range subsets {
|
|
|
|
validateRefreshDeleteCombination(t, names, subset)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateRefreshDeleteCombination(t *testing.T, names []string, targets []string) {
|
2018-08-23 02:52:46 +02:00
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
const resType = "pkgA:m:typA"
|
|
|
|
|
2019-09-18 03:14:10 +02:00
|
|
|
urnA := p.NewURN(resType, names[0], "")
|
|
|
|
urnB := p.NewURN(resType, names[1], "")
|
|
|
|
urnC := p.NewURN(resType, names[2], "")
|
|
|
|
urns := []resource.URN{urnA, urnB, urnC}
|
|
|
|
|
|
|
|
refreshTargets := []resource.URN{}
|
|
|
|
|
|
|
|
t.Logf("Refreshing targets: %v", targets)
|
|
|
|
for _, target := range targets {
|
2019-09-20 04:28:14 +02:00
|
|
|
refreshTargets = append(refreshTargets, pickURN(t, urns, names, target))
|
2019-09-18 03:14:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.RefreshTargets = refreshTargets
|
2018-08-23 02:52:46 +02:00
|
|
|
|
|
|
|
newResource := func(urn resource.URN, id resource.ID, delete bool, dependencies ...resource.URN) *resource.State {
|
|
|
|
return &resource.State{
|
|
|
|
Type: urn.Type(),
|
|
|
|
URN: urn,
|
|
|
|
Custom: true,
|
|
|
|
Delete: delete,
|
|
|
|
ID: id,
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
Dependencies: dependencies,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oldResources := []*resource.State{
|
|
|
|
newResource(urnA, "0", false),
|
|
|
|
newResource(urnB, "1", false, urnA),
|
|
|
|
newResource(urnC, "2", false, urnA, urnB),
|
|
|
|
newResource(urnA, "3", true),
|
|
|
|
newResource(urnA, "4", true),
|
|
|
|
newResource(urnC, "5", true, urnA, urnB),
|
|
|
|
}
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: oldResources,
|
|
|
|
}
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2019-03-11 21:50:00 +01:00
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
2018-08-23 02:52:46 +02:00
|
|
|
|
|
|
|
switch id {
|
|
|
|
case "0", "4":
|
|
|
|
// We want to delete resources A::0 and A::4.
|
2019-03-11 21:50:00 +01:00
|
|
|
return plugin.ReadResult{}, resource.StatusOK, nil
|
2018-08-23 02:52:46 +02:00
|
|
|
default:
|
2019-03-11 21:50:00 +01:00
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
2018-08-23 02:52:46 +02:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, nil, loaders...)
|
2018-08-23 02:52:46 +02:00
|
|
|
|
2019-09-18 03:14:10 +02:00
|
|
|
p.Steps = []TestStep{
|
|
|
|
{
|
|
|
|
Op: Refresh,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
// Should see only refreshes.
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if len(refreshTargets) > 0 {
|
|
|
|
// should only see changes to urns we explicitly asked to change
|
|
|
|
assert.Containsf(t, refreshTargets, entry.Step.URN(),
|
|
|
|
"Refreshed a resource that wasn't a target: %v", entry.Step.URN())
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, deploy.OpRefresh, entry.Step.Op())
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-08-23 02:52:46 +02:00
|
|
|
snap := p.Run(t, old)
|
|
|
|
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
|
|
|
|
for _, r := range snap.Resources {
|
|
|
|
switch urn := r.URN; urn {
|
|
|
|
case provURN:
|
|
|
|
continue
|
|
|
|
case urnA, urnB, urnC:
|
|
|
|
// break
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
|
2019-09-18 03:14:10 +02:00
|
|
|
if len(refreshTargets) == 0 || containsURN(refreshTargets, urnA) {
|
|
|
|
// 'A' was deleted, so we should see the impact downstream.
|
|
|
|
|
|
|
|
switch r.ID {
|
|
|
|
case "1":
|
|
|
|
// A::0 was deleted, so B's dependency list should be empty.
|
|
|
|
assert.Equal(t, urnB, r.URN)
|
|
|
|
assert.Empty(t, r.Dependencies)
|
|
|
|
case "2":
|
|
|
|
// A::0 was deleted, so C's dependency list should only contain B.
|
|
|
|
assert.Equal(t, urnC, r.URN)
|
|
|
|
assert.Equal(t, []resource.URN{urnB}, r.Dependencies)
|
|
|
|
case "3":
|
|
|
|
// A::3 should not have changed.
|
|
|
|
assert.Equal(t, oldResources[3], r)
|
|
|
|
case "5":
|
|
|
|
// A::4 was deleted but A::3 was still refernceable by C, so C should not have changed.
|
|
|
|
assert.Equal(t, oldResources[5], r)
|
|
|
|
default:
|
|
|
|
t.Fatalf("Unexpected changed resource when refreshing %v: %v::%v", refreshTargets, r.URN, r.ID)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// A was not deleted. So nothing should be impacted.
|
|
|
|
id, err := strconv.Atoi(r.ID.String())
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, oldResources[id], r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func containsURN(urns []resource.URN, urn resource.URN) bool {
|
|
|
|
for _, val := range urns {
|
|
|
|
if val == urn {
|
|
|
|
return true
|
2018-08-23 02:52:46 +02:00
|
|
|
}
|
|
|
|
}
|
2019-09-18 03:14:10 +02:00
|
|
|
|
|
|
|
return false
|
2018-08-23 02:52:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Tests basic refresh functionality.
|
|
|
|
func TestRefreshBasics(t *testing.T) {
|
2019-09-18 03:14:10 +02:00
|
|
|
names := []string{"resA", "resB", "resC"}
|
|
|
|
|
|
|
|
// Try refreshing a stack with every combination of the three above resources as a target to
|
|
|
|
// refresh.
|
|
|
|
subsets := combinations.All(names)
|
|
|
|
|
|
|
|
// combinations.All doesn't return the empty set. So explicitly test that case (i.e. test no
|
|
|
|
// targets specified)
|
|
|
|
validateRefreshBasicsCombination(t, names, []string{})
|
|
|
|
|
|
|
|
for _, subset := range subsets {
|
|
|
|
validateRefreshBasicsCombination(t, names, subset)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateRefreshBasicsCombination(t *testing.T, names []string, targets []string) {
|
2018-08-23 02:52:46 +02:00
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
const resType = "pkgA:m:typA"
|
|
|
|
|
2019-09-18 03:14:10 +02:00
|
|
|
urnA := p.NewURN(resType, names[0], "")
|
|
|
|
urnB := p.NewURN(resType, names[1], "")
|
|
|
|
urnC := p.NewURN(resType, names[2], "")
|
|
|
|
urns := []resource.URN{urnA, urnB, urnC}
|
|
|
|
|
|
|
|
refreshTargets := []resource.URN{}
|
|
|
|
|
|
|
|
for _, target := range targets {
|
2019-09-20 04:28:14 +02:00
|
|
|
refreshTargets = append(p.Options.RefreshTargets, pickURN(t, urns, names, target))
|
2019-09-18 03:14:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.RefreshTargets = refreshTargets
|
2018-08-23 02:52:46 +02:00
|
|
|
|
|
|
|
newResource := func(urn resource.URN, id resource.ID, delete bool, dependencies ...resource.URN) *resource.State {
|
|
|
|
return &resource.State{
|
|
|
|
Type: urn.Type(),
|
|
|
|
URN: urn,
|
|
|
|
Custom: true,
|
|
|
|
Delete: delete,
|
|
|
|
ID: id,
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
Dependencies: dependencies,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oldResources := []*resource.State{
|
|
|
|
newResource(urnA, "0", false),
|
|
|
|
newResource(urnB, "1", false, urnA),
|
|
|
|
newResource(urnC, "2", false, urnA, urnB),
|
|
|
|
newResource(urnA, "3", true),
|
|
|
|
newResource(urnA, "4", true),
|
|
|
|
newResource(urnC, "5", true, urnA, urnB),
|
|
|
|
}
|
|
|
|
|
2019-03-11 21:50:00 +01:00
|
|
|
newStates := map[resource.ID]plugin.ReadResult{
|
2018-08-23 02:52:46 +02:00
|
|
|
// A::0 and A::3 will have no changes.
|
2019-03-11 21:50:00 +01:00
|
|
|
"0": {Outputs: resource.PropertyMap{}, Inputs: resource.PropertyMap{}},
|
|
|
|
"3": {Outputs: resource.PropertyMap{}, Inputs: resource.PropertyMap{}},
|
|
|
|
|
|
|
|
// B::1 and A::4 will have changes. The latter will also have input changes.
|
|
|
|
"1": {Outputs: resource.PropertyMap{"foo": resource.NewStringProperty("bar")}, Inputs: resource.PropertyMap{}},
|
|
|
|
"4": {
|
|
|
|
Outputs: resource.PropertyMap{"baz": resource.NewStringProperty("qux")},
|
|
|
|
Inputs: resource.PropertyMap{"oof": resource.NewStringProperty("zab")},
|
|
|
|
},
|
2018-08-23 02:52:46 +02:00
|
|
|
|
|
|
|
// C::2 and C::5 will be deleted.
|
2019-03-11 21:50:00 +01:00
|
|
|
"2": {},
|
|
|
|
"5": {},
|
2018-08-23 02:52:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: oldResources,
|
|
|
|
}
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2019-03-11 21:50:00 +01:00
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
2018-08-23 02:52:46 +02:00
|
|
|
|
|
|
|
new, hasNewState := newStates[id]
|
|
|
|
assert.True(t, hasNewState)
|
|
|
|
return new, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, nil, loaders...)
|
2018-08-23 02:52:46 +02:00
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Refresh,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
2018-08-23 02:52:46 +02:00
|
|
|
// Should see only refreshes.
|
|
|
|
for _, entry := range j.Entries {
|
2019-09-18 03:14:10 +02:00
|
|
|
if len(refreshTargets) > 0 {
|
|
|
|
// should only see changes to urns we explicitly asked to change
|
|
|
|
assert.Containsf(t, refreshTargets, entry.Step.URN(),
|
|
|
|
"Refreshed a resource that wasn't a target: %v", entry.Step.URN())
|
|
|
|
}
|
|
|
|
|
2018-08-23 02:52:46 +02:00
|
|
|
assert.Equal(t, deploy.OpRefresh, entry.Step.Op())
|
|
|
|
resultOp := entry.Step.(*deploy.RefreshStep).ResultOp()
|
|
|
|
|
|
|
|
old := entry.Step.Old()
|
|
|
|
if !old.Custom || providers.IsProviderType(old.Type) {
|
|
|
|
// Component and provider resources should never change.
|
|
|
|
assert.Equal(t, deploy.OpSame, resultOp)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
expected, new := newStates[old.ID], entry.Step.New()
|
2019-03-11 21:50:00 +01:00
|
|
|
if expected.Outputs == nil {
|
2018-08-23 02:52:46 +02:00
|
|
|
// If the resource was deleted, we want the result op to be an OpDelete.
|
|
|
|
assert.Nil(t, new)
|
|
|
|
assert.Equal(t, deploy.OpDelete, resultOp)
|
|
|
|
} else {
|
|
|
|
// If there were changes to the outputs, we want the result op to be an OpUpdate. Otherwise we want
|
|
|
|
// an OpSame.
|
2019-03-11 21:50:00 +01:00
|
|
|
if reflect.DeepEqual(old.Outputs, expected.Outputs) {
|
2018-08-23 02:52:46 +02:00
|
|
|
assert.Equal(t, deploy.OpSame, resultOp)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, deploy.OpUpdate, resultOp)
|
|
|
|
}
|
|
|
|
|
2019-03-11 21:50:00 +01:00
|
|
|
// Only the inputs and outputs should have changed (if anything changed).
|
|
|
|
old.Inputs = expected.Inputs
|
|
|
|
old.Outputs = expected.Outputs
|
2018-08-23 02:52:46 +02:00
|
|
|
assert.Equal(t, old, new)
|
|
|
|
}
|
|
|
|
}
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-08-23 02:52:46 +02:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap := p.Run(t, old)
|
|
|
|
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
|
|
|
|
for _, r := range snap.Resources {
|
|
|
|
switch urn := r.URN; urn {
|
|
|
|
case provURN:
|
|
|
|
continue
|
|
|
|
case urnA, urnB, urnC:
|
|
|
|
// break
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The only resources left in the checkpoint should be those that were not deleted by the refresh.
|
|
|
|
expected := newStates[r.ID]
|
|
|
|
assert.NotNil(t, expected)
|
|
|
|
|
|
|
|
idx, err := strconv.ParseInt(string(r.ID), 0, 0)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2019-03-11 21:50:00 +01:00
|
|
|
// The new resources should be equal to the old resources + the new inputs and outputs.
|
2018-08-23 02:52:46 +02:00
|
|
|
old := oldResources[int(idx)]
|
2019-03-11 21:50:00 +01:00
|
|
|
old.Inputs = expected.Inputs
|
|
|
|
old.Outputs = expected.Outputs
|
2018-08-23 02:52:46 +02:00
|
|
|
assert.Equal(t, old, r)
|
|
|
|
}
|
2018-08-30 06:00:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Tests that an interrupted refresh leaves behind an expected state.
|
|
|
|
func TestCanceledRefresh(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
const resType = "pkgA:m:typA"
|
|
|
|
|
|
|
|
urnA := p.NewURN(resType, "resA", "")
|
|
|
|
urnB := p.NewURN(resType, "resB", "")
|
|
|
|
urnC := p.NewURN(resType, "resC", "")
|
2018-08-23 02:52:46 +02:00
|
|
|
|
2018-08-30 06:00:05 +02:00
|
|
|
newResource := func(urn resource.URN, id resource.ID, delete bool, dependencies ...resource.URN) *resource.State {
|
|
|
|
return &resource.State{
|
|
|
|
Type: urn.Type(),
|
|
|
|
URN: urn,
|
|
|
|
Custom: true,
|
|
|
|
Delete: delete,
|
|
|
|
ID: id,
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
Dependencies: dependencies,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oldResources := []*resource.State{
|
|
|
|
newResource(urnA, "0", false),
|
|
|
|
newResource(urnB, "1", false),
|
|
|
|
newResource(urnC, "2", false),
|
|
|
|
}
|
|
|
|
|
|
|
|
newStates := map[resource.ID]resource.PropertyMap{
|
|
|
|
// A::0 and B::1 will have changes; D::3 will be deleted.
|
|
|
|
"0": {"foo": resource.NewStringProperty("bar")},
|
|
|
|
"1": {"baz": resource.NewStringProperty("qux")},
|
|
|
|
"2": nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: oldResources,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a cancelable context for the refresh operation.
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
// Serialize all refreshes s.t. we can cancel after the first is issued.
|
2019-01-04 04:40:07 +01:00
|
|
|
refreshes, cancelled := make(chan resource.ID), make(chan bool)
|
2018-08-30 06:00:05 +02:00
|
|
|
go func() {
|
|
|
|
<-refreshes
|
|
|
|
cancel()
|
|
|
|
}()
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
2019-03-11 21:50:00 +01:00
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
2018-08-30 06:00:05 +02:00
|
|
|
|
2019-01-04 04:40:07 +01:00
|
|
|
refreshes <- id
|
|
|
|
<-cancelled
|
2018-08-30 06:00:05 +02:00
|
|
|
|
|
|
|
new, hasNewState := newStates[id]
|
|
|
|
assert.True(t, hasNewState)
|
2019-03-11 21:50:00 +01:00
|
|
|
return plugin.ReadResult{Outputs: new}, resource.StatusOK, nil
|
2018-08-30 06:00:05 +02:00
|
|
|
},
|
2019-01-04 04:40:07 +01:00
|
|
|
CancelF: func() error {
|
|
|
|
close(cancelled)
|
|
|
|
return nil
|
|
|
|
},
|
2018-08-30 06:00:05 +02:00
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshed := make(map[resource.ID]bool)
|
|
|
|
op := TestOp(Refresh)
|
|
|
|
options := UpdateOptions{
|
|
|
|
Parallel: 1,
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host: deploytest.NewPluginHost(nil, nil, nil, loaders...),
|
2018-08-30 06:00:05 +02:00
|
|
|
}
|
|
|
|
project, target := p.GetProject(), p.GetTarget(old)
|
2019-03-20 00:21:50 +01:00
|
|
|
validate := func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
2018-08-30 06:00:05 +02:00
|
|
|
for _, entry := range j.Entries {
|
|
|
|
assert.Equal(t, deploy.OpRefresh, entry.Step.Op())
|
|
|
|
resultOp := entry.Step.(*deploy.RefreshStep).ResultOp()
|
|
|
|
|
|
|
|
old := entry.Step.Old()
|
|
|
|
if !old.Custom || providers.IsProviderType(old.Type) {
|
|
|
|
// Component and provider resources should never change.
|
|
|
|
assert.Equal(t, deploy.OpSame, resultOp)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
refreshed[old.ID] = true
|
|
|
|
|
|
|
|
expected, new := newStates[old.ID], entry.Step.New()
|
|
|
|
if expected == nil {
|
|
|
|
// If the resource was deleted, we want the result op to be an OpDelete.
|
|
|
|
assert.Nil(t, new)
|
|
|
|
assert.Equal(t, deploy.OpDelete, resultOp)
|
|
|
|
} else {
|
|
|
|
// If there were changes to the outputs, we want the result op to be an OpUpdate. Otherwise we want
|
|
|
|
// an OpSame.
|
|
|
|
if reflect.DeepEqual(old.Outputs, expected) {
|
|
|
|
assert.Equal(t, deploy.OpSame, resultOp)
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, deploy.OpUpdate, resultOp)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only the outputs should have changed (if anything changed).
|
|
|
|
old.Outputs = expected
|
|
|
|
assert.Equal(t, old, new)
|
|
|
|
}
|
|
|
|
}
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-08-30 06:00:05 +02:00
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
snap, res := op.RunWithContext(ctx, project, target, options, false, nil, validate)
|
|
|
|
assertIsErrorOrBailResult(t, res)
|
2019-01-04 04:40:07 +01:00
|
|
|
assert.Equal(t, 1, len(refreshed))
|
2018-08-30 06:00:05 +02:00
|
|
|
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
|
|
|
|
for _, r := range snap.Resources {
|
|
|
|
switch urn := r.URN; urn {
|
|
|
|
case provURN:
|
|
|
|
continue
|
|
|
|
case urnA, urnB, urnC:
|
|
|
|
// break
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
|
|
|
|
idx, err := strconv.ParseInt(string(r.ID), 0, 0)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if refreshed[r.ID] {
|
|
|
|
// The refreshed resource should have its new state.
|
|
|
|
expected := newStates[r.ID]
|
|
|
|
if expected == nil {
|
|
|
|
assert.Fail(t, "refreshed resource was not deleted")
|
|
|
|
} else {
|
|
|
|
old := oldResources[int(idx)]
|
|
|
|
old.Outputs = expected
|
|
|
|
assert.Equal(t, old, r)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Any resources that were not refreshed should retain their original state.
|
|
|
|
old := oldResources[int(idx)]
|
|
|
|
assert.Equal(t, old, r)
|
|
|
|
}
|
|
|
|
}
|
2018-08-23 02:52:46 +02:00
|
|
|
}
|
|
|
|
|
2018-08-21 01:41:07 +02:00
|
|
|
// Tests that errors returned directly from the language host get logged by the engine.
|
|
|
|
func TestLanguageHostDiagnostics(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
errorText := "oh no"
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, _ *deploytest.ResourceMonitor) error {
|
|
|
|
// Exiting immediately with an error simulates a language exiting immediately with a non-zero exit code.
|
|
|
|
return errors.New(errorText)
|
|
|
|
})
|
|
|
|
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-21 01:41:07 +02:00
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
SkipPreview: true,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assertIsErrorOrBailResult(t, res)
|
2018-08-21 01:41:07 +02:00
|
|
|
sawExitCode := false
|
|
|
|
for _, evt := range evts {
|
|
|
|
if evt.Type == DiagEvent {
|
|
|
|
e := evt.Payload.(DiagEventPayload)
|
|
|
|
msg := colors.Never.Colorize(e.Message)
|
|
|
|
sawExitCode = strings.Contains(msg, errorText) && e.Severity == diag.Error
|
|
|
|
if sawExitCode {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.True(t, sawExitCode)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-08-21 01:41:07 +02:00
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
2018-08-23 00:32:54 +02:00
|
|
|
|
|
|
|
type brokenDecrypter struct {
|
|
|
|
ErrorMessage string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b brokenDecrypter) DecryptValue(ciphertext string) (string, error) {
|
|
|
|
return "", fmt.Errorf(b.ErrorMessage)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests that the engine presents a reasonable error message when a decrypter fails to decrypt a config value.
|
|
|
|
func TestBrokenDecrypter(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, _ *deploytest.ResourceMonitor) error {
|
|
|
|
return nil
|
|
|
|
})
|
Implement status sinks
This commit reverts most of #1853 and replaces it with functionally
identical logic, using the notion of status message-specific sinks.
In other words, where the original commit implemented ephemeral status
messages by adding an `isStatus` parameter to most of the logging
methdos in pulumi/pulumi, this implements ephemeral status messages as a
parallel logging sink, which emits _only_ ephemeral status messages.
The original commit message in that PR was:
> Allow log events to be marked "status" events
>
> This commit will introduce a field, IsStatus to LogRequest. A "status"
> logging event will be displayed in the Info column of the main
> display, but will not be printed out at the end, when resource
> operations complete.
>
> For example, for complex resource initialization, we'd like to display
> a series of intermediate results: [1/4] Service object created, for
> example. We'd like these to appear in the Info column, but not at the
> end, where they are not helpful to the user.
2018-08-31 22:12:40 +02:00
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2018-08-23 00:32:54 +02:00
|
|
|
key := config.MustMakeKey("foo", "bar")
|
|
|
|
msg := "decryption failed"
|
|
|
|
configMap := make(config.Map)
|
|
|
|
configMap[key] = config.NewSecureValue("hunter2")
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Decrypter: brokenDecrypter{ErrorMessage: msg},
|
|
|
|
Config: configMap,
|
|
|
|
Steps: []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
SkipPreview: true,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assertIsErrorOrBailResult(t, res)
|
|
|
|
decryptErr := res.Error().(DecryptError)
|
2018-08-23 00:32:54 +02:00
|
|
|
assert.Equal(t, key, decryptErr.Key)
|
|
|
|
assert.Contains(t, decryptErr.Err.Error(), msg)
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-08-23 00:32:54 +02:00
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
2018-09-08 00:19:18 +02:00
|
|
|
|
|
|
|
func TestBadResourceType(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, mon *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := mon.RegisterResource("very:bad", "resA", true)
|
2018-09-08 00:19:18 +02:00
|
|
|
assert.Error(t, err)
|
|
|
|
rpcerr, ok := rpcerror.FromError(err)
|
|
|
|
assert.True(t, ok)
|
|
|
|
assert.Equal(t, codes.InvalidArgument, rpcerr.Code())
|
|
|
|
assert.Contains(t, rpcerr.Message(), "Type 'very:bad' is not a valid type token")
|
|
|
|
|
2019-04-17 20:25:02 +02:00
|
|
|
_, _, err = mon.ReadResource("very:bad", "someResource", "someId", "", resource.PropertyMap{}, "", "")
|
2018-09-08 00:19:18 +02:00
|
|
|
assert.Error(t, err)
|
|
|
|
rpcerr, ok = rpcerror.FromError(err)
|
|
|
|
assert.True(t, ok)
|
|
|
|
assert.Equal(t, codes.InvalidArgument, rpcerr.Code())
|
|
|
|
assert.Contains(t, rpcerr.Message(), "Type 'very:bad' is not a valid type token")
|
|
|
|
|
|
|
|
// Component resources may have any format type.
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, noErr := mon.RegisterResource("a:component", "resB", false)
|
2018-09-08 00:19:18 +02:00
|
|
|
assert.NoError(t, noErr)
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, noErr = mon.RegisterResource("singlename", "resC", false)
|
2018-09-08 00:19:18 +02:00
|
|
|
assert.NoError(t, noErr)
|
|
|
|
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
SkipPreview: true,
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
2018-09-11 00:18:25 +02:00
|
|
|
|
|
|
|
// Tests that provider cancellation occurs as expected.
|
|
|
|
func TestProviderCancellation(t *testing.T) {
|
|
|
|
const resourceCount = 4
|
|
|
|
|
|
|
|
// Set up a cancelable context for the refresh operation.
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
// Wait for our resource ops, then cancel.
|
|
|
|
var ops sync.WaitGroup
|
|
|
|
ops.Add(resourceCount)
|
|
|
|
go func() {
|
|
|
|
ops.Wait()
|
|
|
|
cancel()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Set up an independent cancelable context for the provider's operations.
|
|
|
|
provCtx, provCancel := context.WithCancel(context.Background())
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN,
|
Addition of Custom Timeouts (#2885)
* Plumbing the custom timeouts from the engine to the providers
* Plumbing the CustomTimeouts through to the engine and adding test to show this
* Change the provider proto to include individual timeouts
* Plumbing the CustomTimeouts from the engine through to the Provider RPC interface
* Change how the CustomTimeouts are sent across RPC
These errors were spotted in testing. We can now see that the timeout
information is arriving in the RegisterResourceRequest
```
req=&pulumirpc.RegisterResourceRequest{
Type: "aws:s3/bucket:Bucket",
Name: "my-bucket",
Parent: "urn:pulumi:dev::aws-vpc::pulumi:pulumi:Stack::aws-vpc-dev",
Custom: true,
Object: &structpb.Struct{},
Protect: false,
Dependencies: nil,
Provider: "",
PropertyDependencies: {},
DeleteBeforeReplace: false,
Version: "",
IgnoreChanges: nil,
AcceptSecrets: true,
AdditionalSecretOutputs: nil,
Aliases: nil,
CustomTimeouts: &pulumirpc.RegisterResourceRequest_CustomTimeouts{
Create: 300,
Update: 400,
Delete: 500,
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
},
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
}
```
* Changing the design to use strings
* CHANGELOG entry to include the CustomTimeouts work
* Changing custom timeouts to be passed around the engine as converted value
We don't want to pass around strings - the user can provide it but we want
to make the engine aware of the timeout in seconds as a float64
2019-07-15 23:26:28 +02:00
|
|
|
inputs resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap,
|
|
|
|
resource.Status, error) {
|
2018-09-11 00:18:25 +02:00
|
|
|
|
|
|
|
// Inform the waiter that we've entered a provider op and wait for cancellation.
|
|
|
|
ops.Done()
|
|
|
|
<-provCtx.Done()
|
|
|
|
|
|
|
|
return resource.ID(urn.Name()), resource.PropertyMap{}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
CancelF: func() error {
|
|
|
|
provCancel()
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
done := make(chan bool)
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
errors := make([]error, resourceCount)
|
|
|
|
var resources sync.WaitGroup
|
|
|
|
resources.Add(resourceCount)
|
|
|
|
for i := 0; i < resourceCount; i++ {
|
|
|
|
go func(idx int) {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, errors[idx] = monitor.RegisterResource("pkgA:m:typA", fmt.Sprintf("res%d", idx), true)
|
2018-09-11 00:18:25 +02:00
|
|
|
resources.Done()
|
|
|
|
}(i)
|
|
|
|
}
|
|
|
|
resources.Wait()
|
|
|
|
for _, err := range errors {
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
close(done)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
op := TestOp(Update)
|
|
|
|
options := UpdateOptions{
|
|
|
|
Parallel: resourceCount,
|
|
|
|
host: deploytest.NewPluginHost(nil, nil, program, loaders...),
|
|
|
|
}
|
|
|
|
project, target := p.GetProject(), p.GetTarget(nil)
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
_, res := op.RunWithContext(ctx, project, target, options, false, nil, nil)
|
|
|
|
assertIsErrorOrBailResult(t, res)
|
2018-09-11 00:18:25 +02:00
|
|
|
|
|
|
|
// Wait for the program to finish.
|
|
|
|
<-done
|
|
|
|
}
|
2018-10-01 18:48:48 +02:00
|
|
|
|
|
|
|
// Tests that a preview works for a stack with pending operations.
|
|
|
|
func TestPreviewWithPendingOperations(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
const resType = "pkgA:m:typA"
|
|
|
|
urnA := p.NewURN(resType, "resA", "")
|
|
|
|
|
|
|
|
newResource := func(urn resource.URN, id resource.ID, delete bool, dependencies ...resource.URN) *resource.State {
|
|
|
|
return &resource.State{
|
|
|
|
Type: urn.Type(),
|
|
|
|
URN: urn,
|
|
|
|
Custom: true,
|
|
|
|
Delete: delete,
|
|
|
|
ID: id,
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
Dependencies: dependencies,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
PendingOperations: []resource.Operation{{
|
|
|
|
Resource: newResource(urnA, "0", false),
|
|
|
|
Type: resource.OperationTypeUpdating,
|
|
|
|
}},
|
|
|
|
Resources: []*resource.State{
|
|
|
|
newResource(urnA, "0", false),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2018-10-01 18:48:48 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
op := TestOp(Update)
|
|
|
|
options := UpdateOptions{host: deploytest.NewPluginHost(nil, nil, program, loaders...)}
|
|
|
|
project, target := p.GetProject(), p.GetTarget(old)
|
|
|
|
|
|
|
|
// A preview should succeed despite the pending operations.
|
2019-03-20 00:21:50 +01:00
|
|
|
_, res := op.Run(project, target, options, true, nil, nil)
|
|
|
|
assert.Nil(t, res)
|
2018-10-01 18:48:48 +02:00
|
|
|
|
|
|
|
// But an update should fail.
|
2019-03-20 00:21:50 +01:00
|
|
|
_, res = op.Run(project, target, options, false, nil, nil)
|
|
|
|
assertIsErrorOrBailResult(t, res)
|
|
|
|
assert.EqualError(t, res.Error(), deploy.PlanPendingOperationsError{}.Error())
|
2018-10-01 18:48:48 +02:00
|
|
|
}
|
2018-10-09 20:19:31 +02:00
|
|
|
|
|
|
|
// Tests that a failed partial update causes the engine to persist the resource's old inputs and new outputs.
|
|
|
|
func TestUpdatePartialFailure(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
2019-07-31 18:39:07 +02:00
|
|
|
DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
2018-10-09 20:19:31 +02:00
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
|
2019-07-31 18:39:07 +02:00
|
|
|
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
timeout float64, ignoreChanges []string) (resource.PropertyMap, resource.Status, error) {
|
2018-10-09 20:19:31 +02:00
|
|
|
outputs := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"output_prop": 42,
|
|
|
|
})
|
|
|
|
|
|
|
|
return outputs, resource.StatusPartialFailure, errors.New("update failed to apply")
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, mon *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := mon.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
2018-10-09 20:19:31 +02:00
|
|
|
"input_prop": "new inputs",
|
2019-07-25 20:18:40 +02:00
|
|
|
}),
|
|
|
|
})
|
2018-10-09 20:19:31 +02:00
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{Options: UpdateOptions{host: host}}
|
|
|
|
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
SkipPreview: true,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assertIsErrorOrBailResult(t, res)
|
2018-10-09 20:19:31 +02:00
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case resURN:
|
|
|
|
assert.Equal(t, deploy.OpUpdate, entry.Step.Op())
|
|
|
|
switch entry.Kind {
|
|
|
|
case JournalEntryBegin:
|
|
|
|
continue
|
|
|
|
case JournalEntrySuccess:
|
|
|
|
inputs := entry.Step.New().Inputs
|
|
|
|
outputs := entry.Step.New().Outputs
|
|
|
|
assert.Len(t, inputs, 1)
|
|
|
|
assert.Len(t, outputs, 1)
|
|
|
|
assert.Equal(t,
|
|
|
|
resource.NewStringProperty("old inputs"), inputs[resource.PropertyKey("input_prop")])
|
|
|
|
assert.Equal(t,
|
|
|
|
resource.NewNumberProperty(42), outputs[resource.PropertyKey("output_prop")])
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected journal operation: %d", entry.Kind)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-10-09 20:19:31 +02:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
|
|
|
{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "1",
|
|
|
|
Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"input_prop": "old inputs",
|
|
|
|
}),
|
|
|
|
Outputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"output_prop": 1,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
2018-11-14 22:33:35 +01:00
|
|
|
|
|
|
|
// Tests that the StackReference resource works as intended,
|
|
|
|
func TestStackReference(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{}
|
|
|
|
|
|
|
|
// Test that the normal lifecycle works correctly.
|
|
|
|
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, mon *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, state, err := mon.RegisterResource("pulumi:pulumi:StackReference", "other", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
2018-11-14 22:33:35 +01:00
|
|
|
"name": "other",
|
2019-07-25 20:18:40 +02:00
|
|
|
}),
|
|
|
|
})
|
2018-11-14 22:33:35 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
if !info.DryRun {
|
|
|
|
assert.Equal(t, "bar", state["outputs"].ObjectValue()["foo"].StringValue())
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
p := &TestPlan{
|
|
|
|
BackendClient: &deploytest.BackendClient{
|
|
|
|
GetStackOutputsF: func(ctx context.Context, name string) (resource.PropertyMap, error) {
|
|
|
|
switch name {
|
|
|
|
case "other":
|
|
|
|
return resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
}), nil
|
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("unknown stack \"%s\"", name)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Options: UpdateOptions{host: deploytest.NewPluginHost(nil, nil, program, loaders...)},
|
|
|
|
Steps: MakeBasicLifecycleSteps(t, 2),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
// Test that changes to `name` cause replacement.
|
|
|
|
resURN := p.NewURN("pulumi:pulumi:StackReference", "other", "")
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
|
|
|
{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "1",
|
|
|
|
Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"name": "other2",
|
|
|
|
}),
|
|
|
|
Outputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"name": "other2",
|
|
|
|
"outputs": resource.PropertyMap{},
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
SkipPreview: true,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
2018-11-14 22:33:35 +01:00
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case resURN:
|
|
|
|
switch entry.Step.Op() {
|
|
|
|
case deploy.OpCreateReplacement, deploy.OpDeleteReplaced, deploy.OpReplace:
|
|
|
|
// OK
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected journal operation: %v", entry.Step.Op())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2018-11-14 22:33:35 +01:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, old)
|
|
|
|
|
|
|
|
// Test that unknown stacks are handled appropriately.
|
|
|
|
program = deploytest.NewLanguageRuntime(func(info plugin.RunInfo, mon *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := mon.RegisterResource("pulumi:pulumi:StackReference", "other", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
2018-11-14 22:33:35 +01:00
|
|
|
"name": "rehto",
|
2019-07-25 20:18:40 +02:00
|
|
|
}),
|
|
|
|
})
|
2018-11-14 22:33:35 +01:00
|
|
|
assert.Error(t, err)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
p.Options = UpdateOptions{host: deploytest.NewPluginHost(nil, nil, program, loaders...)}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
SkipPreview: true,
|
|
|
|
}}
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
// Test that unknown properties cause errors.
|
|
|
|
program = deploytest.NewLanguageRuntime(func(info plugin.RunInfo, mon *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := mon.RegisterResource("pulumi:pulumi:StackReference", "other", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: resource.NewPropertyMapFromMap(map[string]interface{}{
|
2018-11-14 22:33:35 +01:00
|
|
|
"name": "other",
|
|
|
|
"foo": "bar",
|
2019-07-25 20:18:40 +02:00
|
|
|
}),
|
|
|
|
})
|
2018-11-14 22:33:35 +01:00
|
|
|
assert.Error(t, err)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
p.Options = UpdateOptions{host: deploytest.NewPluginHost(nil, nil, program, loaders...)}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
2018-12-18 22:25:52 +01:00
|
|
|
|
|
|
|
type channelWriter struct {
|
|
|
|
channel chan []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cw *channelWriter) Write(d []byte) (int, error) {
|
|
|
|
cw.channel <- d
|
|
|
|
return len(d), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tests that a failed plugin load correctly shuts down the host.
|
|
|
|
func TestLoadFailureShutdown(t *testing.T) {
|
|
|
|
|
|
|
|
// Note that the setup here is a bit baroque, and is intended to replicate the CLI architecture that lead to
|
|
|
|
// issue #2170. That issue--a panic on a closed channel--was caused by the intersection of several design choices:
|
|
|
|
//
|
|
|
|
// - The provider registry loads and configures the set of providers necessary for the resources currently in the
|
|
|
|
// checkpoint it is processing at plan creation time. Registry creation fails promptly if a provider plugin
|
|
|
|
// fails to load (e.g. because is binary is missing).
|
|
|
|
// - Provider configuration in the CLI's host happens asynchronously. This is meant to allow the engine to remain
|
|
|
|
// responsive while plugins configure.
|
|
|
|
// - Providers may call back into the CLI's host for logging. Callbacks are processed as long as the CLI's plugin
|
|
|
|
// context is open.
|
|
|
|
// - Log events from the CLI's host are delivered to the CLI's diagnostic streams via channels. The CLI closes
|
|
|
|
// these channels once the engine operation it initiated completes.
|
|
|
|
//
|
|
|
|
// These choices gave rise to the following situation:
|
|
|
|
// 1. The provider registry loads a provider for package A and kicks off its configuration.
|
|
|
|
// 2. The provider registry attempts to load a provider for package B. The load fails, and the provider registry
|
|
|
|
// creation call fails promptly.
|
|
|
|
// 3. The engine operation requested by the CLI fails promptly because provider registry creation failed.
|
|
|
|
// 4. The CLI shuts down its diagnostic channels.
|
|
|
|
// 5. The provider for package A calls back in to the host to log a message. The host then attempts to deliver
|
|
|
|
// the message to the CLI's diagnostic channels, causing a panic.
|
|
|
|
//
|
|
|
|
// The fix was to properly close the plugin host during step (3) s.t. the host was no longer accepting callbacks
|
|
|
|
// and would not attempt to send messages to the CLI's diagnostic channels.
|
|
|
|
//
|
|
|
|
// As such, this test attempts to replicate the CLI architecture by using one provider that configures
|
|
|
|
// asynchronously and attempts to call back into the engine and a second provider that fails to load.
|
|
|
|
|
|
|
|
release, done := make(chan bool), make(chan bool)
|
|
|
|
sinkWriter := &channelWriter{channel: make(chan []byte)}
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoaderWithHost("pkgA", semver.MustParse("1.0.0"),
|
|
|
|
func(host plugin.Host) (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ConfigureF: func(news resource.PropertyMap) error {
|
|
|
|
go func() {
|
|
|
|
<-release
|
|
|
|
host.Log(diag.Info, "", "configuring pkgA provider...", 0)
|
|
|
|
close(done)
|
|
|
|
}()
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgB", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return nil, errors.New("pkgB load failure")
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
provAURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
provBURN := p.NewProviderURN("pkgB", "default", "")
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
|
|
|
{
|
|
|
|
Type: provAURN.Type(),
|
|
|
|
URN: provAURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "0",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Type: provBURN.Type(),
|
|
|
|
URN: provBURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: "1",
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
op := TestOp(Update)
|
|
|
|
sink := diag.DefaultSink(sinkWriter, sinkWriter, diag.FormatOptions{Color: colors.Raw})
|
|
|
|
options := UpdateOptions{host: deploytest.NewPluginHost(sink, sink, program, loaders...)}
|
|
|
|
project, target := p.GetProject(), p.GetTarget(old)
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
_, res := op.Run(project, target, options, true, nil, nil)
|
|
|
|
assertIsErrorOrBailResult(t, res)
|
2018-12-18 22:25:52 +01:00
|
|
|
|
|
|
|
close(sinkWriter.channel)
|
|
|
|
close(release)
|
|
|
|
<-done
|
|
|
|
}
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
|
2019-10-01 08:41:56 +02:00
|
|
|
var complexTestDependencyGraphNames = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"}
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
|
2019-10-01 08:41:56 +02:00
|
|
|
func generateComplexTestDependencyGraph(
|
|
|
|
t *testing.T, p *TestPlan) ([]resource.URN, *deploy.Snapshot, plugin.LanguageRuntime) {
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
|
2019-10-01 08:41:56 +02:00
|
|
|
resType := tokens.Type("pkgA:m:typA")
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
type propertyDependencies map[resource.PropertyKey][]resource.URN
|
|
|
|
|
2019-10-01 08:41:56 +02:00
|
|
|
names := complexTestDependencyGraphNames
|
|
|
|
|
|
|
|
urnA := p.NewProviderURN("pkgA", names[0], "")
|
|
|
|
urnB := p.NewURN(resType, names[1], "")
|
|
|
|
urnC := p.NewProviderURN("pkgA", names[2], "")
|
|
|
|
urnD := p.NewProviderURN("pkgA", names[3], "")
|
|
|
|
urnE := p.NewURN(resType, names[4], "")
|
|
|
|
urnF := p.NewURN(resType, names[5], "")
|
|
|
|
urnG := p.NewURN(resType, names[6], "")
|
|
|
|
urnH := p.NewURN(resType, names[7], "")
|
|
|
|
urnI := p.NewURN(resType, names[8], "")
|
|
|
|
urnJ := p.NewURN(resType, names[9], "")
|
|
|
|
urnK := p.NewURN(resType, names[10], "")
|
|
|
|
urnL := p.NewURN(resType, names[11], "")
|
|
|
|
|
|
|
|
urns := []resource.URN{
|
|
|
|
urnA, urnB, urnC, urnD, urnE, urnF,
|
|
|
|
urnG, urnH, urnI, urnJ, urnK, urnL,
|
|
|
|
}
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
|
|
|
|
newResource := func(urn resource.URN, id resource.ID, provider string, dependencies []resource.URN,
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDeps propertyDependencies, outputs resource.PropertyMap) *resource.State {
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
|
|
|
|
inputs := resource.PropertyMap{}
|
|
|
|
for k := range propertyDeps {
|
|
|
|
inputs[k] = resource.NewStringProperty("foo")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &resource.State{
|
|
|
|
Type: urn.Type(),
|
|
|
|
URN: urn,
|
|
|
|
Custom: true,
|
|
|
|
Delete: false,
|
|
|
|
ID: id,
|
|
|
|
Inputs: inputs,
|
2019-07-23 22:39:21 +02:00
|
|
|
Outputs: outputs,
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
Dependencies: dependencies,
|
|
|
|
Provider: provider,
|
|
|
|
PropertyDependencies: propertyDeps,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
2019-07-23 22:39:21 +02:00
|
|
|
newResource(urnA, "0", "", nil, nil, resource.PropertyMap{"A": resource.NewStringProperty("foo")}),
|
|
|
|
newResource(urnB, "1", string(urnA)+"::0", nil, nil, nil),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
newResource(urnC, "2", "",
|
|
|
|
[]resource.URN{urnA},
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDependencies{"A": []resource.URN{urnA}},
|
|
|
|
resource.PropertyMap{"A": resource.NewStringProperty("bar")}),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
newResource(urnD, "3", "",
|
|
|
|
[]resource.URN{urnA},
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDependencies{"B": []resource.URN{urnA}}, nil),
|
|
|
|
newResource(urnE, "4", string(urnC)+"::2", nil, nil, nil),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
newResource(urnF, "5", "",
|
|
|
|
[]resource.URN{urnC},
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDependencies{"A": []resource.URN{urnC}}, nil),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
newResource(urnG, "6", "",
|
|
|
|
[]resource.URN{urnC},
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDependencies{"B": []resource.URN{urnC}}, nil),
|
|
|
|
newResource(urnH, "4", string(urnD)+"::3", nil, nil, nil),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
newResource(urnI, "5", "",
|
|
|
|
[]resource.URN{urnD},
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDependencies{"A": []resource.URN{urnD}}, nil),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
newResource(urnJ, "6", "",
|
|
|
|
[]resource.URN{urnD},
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDependencies{"B": []resource.URN{urnD}}, nil),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
newResource(urnK, "7", "",
|
|
|
|
[]resource.URN{urnF, urnG},
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDependencies{"A": []resource.URN{urnF, urnG}}, nil),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
newResource(urnL, "8", "",
|
|
|
|
[]resource.URN{urnF, urnG},
|
2019-07-23 22:39:21 +02:00
|
|
|
propertyDependencies{"B": []resource.URN{urnF, urnG}}, nil),
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
register := func(urn resource.URN, provider string, inputs resource.PropertyMap) resource.ID {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, id, _, err := monitor.RegisterResource(urn.Type(), string(urn.Name()), true, deploytest.ResourceOptions{
|
|
|
|
Provider: provider,
|
|
|
|
Inputs: inputs,
|
|
|
|
})
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return id
|
|
|
|
}
|
|
|
|
|
|
|
|
idA := register(urnA, "", resource.PropertyMap{"A": resource.NewStringProperty("bar")})
|
|
|
|
register(urnB, string(urnA)+"::"+string(idA), nil)
|
|
|
|
idC := register(urnC, "", nil)
|
|
|
|
idD := register(urnD, "", nil)
|
|
|
|
register(urnE, string(urnC)+"::"+string(idC), nil)
|
|
|
|
register(urnF, "", nil)
|
|
|
|
register(urnG, "", nil)
|
|
|
|
register(urnH, string(urnD)+"::"+string(idD), nil)
|
|
|
|
register(urnI, "", nil)
|
|
|
|
register(urnJ, "", nil)
|
|
|
|
register(urnK, "", nil)
|
|
|
|
register(urnL, "", nil)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2019-10-01 08:41:56 +02:00
|
|
|
return urns, old, program
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDeleteBeforeReplace(t *testing.T) {
|
|
|
|
// A
|
|
|
|
// _________|_________
|
|
|
|
// B C D
|
|
|
|
// ___|___ ___|___
|
|
|
|
// E F G H I J
|
|
|
|
// |__|
|
|
|
|
// K L
|
|
|
|
//
|
|
|
|
// For a given resource R in (A, C, D):
|
|
|
|
// - R will be the provider for its first dependent
|
|
|
|
// - A change to R will require that its second dependent be replaced
|
|
|
|
// - A change to R will not require that its third dependent be replaced
|
|
|
|
//
|
|
|
|
// In addition, K will have a requires-replacement property that depends on both F and G, and
|
|
|
|
// L will have a normal property that depends on both F and G.
|
|
|
|
//
|
|
|
|
// With that in mind, the following resources should require replacement: A, B, C, E, F, and K
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
urns, old, program := generateComplexTestDependencyGraph(t, p)
|
|
|
|
names := complexTestDependencyGraphNames
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{
|
|
|
|
ReplaceKeys: []resource.PropertyKey{"A"},
|
|
|
|
DeleteBeforeReplace: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"A"}}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
SkipPreview: true,
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
|
|
|
|
replaced := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Step.Op() == deploy.OpReplace {
|
|
|
|
replaced[entry.Step.URN()] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, map[resource.URN]bool{
|
2019-10-01 08:41:56 +02:00
|
|
|
pickURN(t, urns, names, "A"): true,
|
|
|
|
pickURN(t, urns, names, "B"): true,
|
|
|
|
pickURN(t, urns, names, "C"): true,
|
|
|
|
pickURN(t, urns, names, "E"): true,
|
|
|
|
pickURN(t, urns, names, "F"): true,
|
|
|
|
pickURN(t, urns, names, "K"): true,
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
}, replaced)
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPropertyDependenciesAdapter(t *testing.T) {
|
|
|
|
// Ensure that the eval source properly shims in property dependencies if none were reported (and does not if
|
|
|
|
// any were reported).
|
|
|
|
|
|
|
|
type propertyDependencies map[resource.PropertyKey][]resource.URN
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
const resType = "pkgA:m:typA"
|
|
|
|
var urnA, urnB, urnC, urnD resource.URN
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
|
|
|
|
register := func(name string, inputs resource.PropertyMap, inputDeps propertyDependencies,
|
|
|
|
dependencies []resource.URN) resource.URN {
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
urn, _, _, err := monitor.RegisterResource(resType, name, true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputs,
|
|
|
|
Dependencies: dependencies,
|
|
|
|
PropertyDeps: inputDeps,
|
|
|
|
})
|
Implement more precise delete-before-replace semantics. (#2369)
This implements the new algorithm for deciding which resources must be
deleted due to a delete-before-replace operation.
We need to compute the set of resources that may be replaced by a
change to the resource under consideration. We do this by taking the
complete set of transitive dependents on the resource under
consideration and removing any resources that would not be replaced by
changes to their dependencies. We determine whether or not a resource
may be replaced by substituting unknowns for input properties that may
change due to deletion of the resources their value depends on and
calling the resource provider's Diff method.
This is perhaps clearer when described by example. Consider the
following dependency graph:
A
__|__
B C
| _|_
D E F
In this graph, all of B, C, D, E, and F transitively depend on A. It may
be the case, however, that changes to the specific properties of any of
those resources R that would occur if a resource on the path to A were
deleted and recreated may not cause R to be replaced. For example, the
edge from B to A may be a simple dependsOn edge such that a change to
B does not actually influence any of B's input properties. In that case,
neither B nor D would need to be deleted before A could be deleted.
In order to make the above algorithm a reality, the resource monitor
interface has been updated to include a map that associates an input
property key with the list of resources that input property depends on.
Older clients of the resource monitor will leave this map empty, in
which case all input properties will be treated as depending on all
dependencies of the resource. This is probably overly conservative, but
it is less conservative than what we currently implement, and is
certainly correct.
2019-01-28 18:46:30 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return urn
|
|
|
|
}
|
|
|
|
|
|
|
|
urnA = register("A", nil, nil, nil)
|
|
|
|
urnB = register("B", nil, nil, nil)
|
|
|
|
urnC = register("C", resource.PropertyMap{
|
|
|
|
"A": resource.NewStringProperty("foo"),
|
|
|
|
"B": resource.NewStringProperty("bar"),
|
|
|
|
}, nil, []resource.URN{urnA, urnB})
|
|
|
|
urnD = register("D", resource.PropertyMap{
|
|
|
|
"A": resource.NewStringProperty("foo"),
|
|
|
|
"B": resource.NewStringProperty("bar"),
|
|
|
|
}, propertyDependencies{
|
|
|
|
"A": []resource.URN{urnB},
|
|
|
|
"B": []resource.URN{urnA, urnC},
|
|
|
|
}, []resource.URN{urnA, urnB, urnC})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{Op: Update}},
|
|
|
|
}
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch res.URN {
|
|
|
|
case urnA, urnB:
|
|
|
|
assert.Empty(t, res.Dependencies)
|
|
|
|
assert.Empty(t, res.PropertyDependencies)
|
|
|
|
case urnC:
|
|
|
|
assert.Equal(t, []resource.URN{urnA, urnB}, res.Dependencies)
|
|
|
|
assert.EqualValues(t, propertyDependencies{
|
|
|
|
"A": res.Dependencies,
|
|
|
|
"B": res.Dependencies,
|
|
|
|
}, res.PropertyDependencies)
|
|
|
|
case urnD:
|
|
|
|
assert.Equal(t, []resource.URN{urnA, urnB, urnC}, res.Dependencies)
|
|
|
|
assert.EqualValues(t, propertyDependencies{
|
|
|
|
"A": []resource.URN{urnB},
|
|
|
|
"B": []resource.URN{urnA, urnC},
|
|
|
|
}, res.PropertyDependencies)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-01-31 23:27:53 +01:00
|
|
|
|
|
|
|
func TestExplicitDeleteBeforeReplace(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
2019-08-21 00:51:02 +02:00
|
|
|
dbrDiff := false
|
2019-01-31 23:27:53 +01:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
2019-07-31 18:39:07 +02:00
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
2019-01-31 23:27:53 +01:00
|
|
|
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
2019-08-21 00:51:02 +02:00
|
|
|
return plugin.DiffResult{
|
|
|
|
ReplaceKeys: []resource.PropertyKey{"A"},
|
|
|
|
DeleteBeforeReplace: dbrDiff,
|
|
|
|
}, nil
|
2019-01-31 23:27:53 +01:00
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
const resType = "pkgA:index:typ"
|
|
|
|
|
|
|
|
inputsA := resource.NewPropertyMapFromMap(map[string]interface{}{"A": "foo"})
|
2019-08-21 00:51:02 +02:00
|
|
|
dbrValue, dbrA := true, (*bool)(nil)
|
2019-01-31 23:27:53 +01:00
|
|
|
inputsB := resource.NewPropertyMapFromMap(map[string]interface{}{"A": "foo"})
|
|
|
|
|
|
|
|
var provURN, urnA, urnB resource.URN
|
|
|
|
var provID resource.ID
|
|
|
|
var err error
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
provURN, provID, _, err = monitor.RegisterResource(providers.MakeProviderType("pkgA"), "provA", true)
|
2019-01-31 23:27:53 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
if provID == "" {
|
|
|
|
provID = providers.UnknownID
|
|
|
|
}
|
|
|
|
provRef, err := providers.NewReference(provURN, provID)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
provA := provRef.String()
|
|
|
|
|
2019-07-25 20:18:40 +02:00
|
|
|
urnA, _, _, err = monitor.RegisterResource(resType, "resA", true, deploytest.ResourceOptions{
|
|
|
|
Provider: provA,
|
|
|
|
Inputs: inputsA,
|
|
|
|
DeleteBeforeReplace: dbrA,
|
|
|
|
})
|
2019-01-31 23:27:53 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
inputDepsB := map[resource.PropertyKey][]resource.URN{"A": {urnA}}
|
2019-07-25 20:18:40 +02:00
|
|
|
urnB, _, _, err = monitor.RegisterResource(resType, "resB", true, deploytest.ResourceOptions{
|
|
|
|
Provider: provA,
|
|
|
|
Inputs: inputsB,
|
|
|
|
Dependencies: []resource.URN{urnA},
|
|
|
|
PropertyDeps: inputDepsB,
|
|
|
|
})
|
2019-01-31 23:27:53 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Change the value of resA.A. Only resA should be replaced, and the replacement should be create-before-delete.
|
|
|
|
inputsA["A"] = resource.NewStringProperty("bar")
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
2019-01-31 23:27:53 +01:00
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
|
|
{Op: deploy.OpSame, URN: urnB},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2019-01-31 23:27:53 +01:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Change the registration of resA such that it requires delete-before-replace and change the value of resA.A. Both
|
|
|
|
// resA and resB should be replaced, and the replacements should be delete-before-replace.
|
2019-08-21 00:51:02 +02:00
|
|
|
dbrA, inputsA["A"] = &dbrValue, resource.NewStringProperty("baz")
|
2019-01-31 23:27:53 +01:00
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
2019-01-31 23:27:53 +01:00
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnB},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnB},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnB},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2019-01-31 23:27:53 +01:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Change the value of resB.A. Only resB should be replaced, and the replacement should be create-before-delete.
|
|
|
|
inputsB["A"] = resource.NewStringProperty("qux")
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
2019-01-31 23:27:53 +01:00
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
|
|
{Op: deploy.OpSame, URN: urnA},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnB},
|
|
|
|
{Op: deploy.OpReplace, URN: urnB},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnB},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2019-01-31 23:27:53 +01:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Change the registration of resA such that it no longer requires delete-before-replace and change the value of
|
2019-08-21 00:51:02 +02:00
|
|
|
// resA.A. Only resA should be replaced, and the replacement should be create-before-delete.
|
|
|
|
dbrA, inputsA["A"] = nil, resource.NewStringProperty("zam")
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
|
|
{Op: deploy.OpSame, URN: urnB},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Change the diff of resA such that it requires delete-before-replace and change the value of resA.A. Both
|
|
|
|
// resA and resB should be replaced, and the replacements should be delete-before-replace.
|
|
|
|
dbrDiff, inputsA["A"] = true, resource.NewStringProperty("foo")
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnB},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnB},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnB},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Change the registration of resA such that it disables delete-before-replace and change the value of
|
|
|
|
// resA.A. Only resA should be replaced, and the replacement should be create-before-delete.
|
|
|
|
dbrA, dbrValue, inputsA["A"] = &dbrValue, false, resource.NewStringProperty("bar")
|
2019-01-31 23:27:53 +01:00
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
2019-01-31 23:27:53 +01:00
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
|
|
{Op: deploy.OpSame, URN: urnB},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
2019-03-20 00:21:50 +01:00
|
|
|
return res
|
2019-01-31 23:27:53 +01:00
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
2019-04-22 22:54:48 +02:00
|
|
|
|
|
|
|
func TestSingleResourceIgnoreChanges(t *testing.T) {
|
2019-07-31 18:39:07 +02:00
|
|
|
var expectedIgnoreChanges []string
|
|
|
|
|
2019-04-22 22:54:48 +02:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
2019-07-31 18:39:07 +02:00
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
assert.Equal(t, expectedIgnoreChanges, ignoreChanges)
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
timeout float64, ignoreChanges []string) (resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
assert.Equal(t, expectedIgnoreChanges, ignoreChanges)
|
|
|
|
return resource.PropertyMap{}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
2019-04-22 22:54:48 +02:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
updateProgramWithProps := func(snap *deploy.Snapshot, props resource.PropertyMap, ignoreChanges []string,
|
|
|
|
allowedOps []deploy.StepOp) *deploy.Snapshot {
|
2019-07-31 18:39:07 +02:00
|
|
|
expectedIgnoreChanges = ignoreChanges
|
2019-04-22 22:54:48 +02:00
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: props,
|
|
|
|
IgnoreChanges: ignoreChanges,
|
|
|
|
})
|
2019-04-22 22:54:48 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
for _, event := range events {
|
|
|
|
if event.Type == ResourcePreEvent {
|
|
|
|
payload := event.Payload.(ResourcePreEventPayload)
|
|
|
|
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p.Run(t, snap)
|
|
|
|
}
|
|
|
|
|
2019-07-31 18:39:07 +02:00
|
|
|
snap := updateProgramWithProps(nil, resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"a": 1,
|
|
|
|
"b": map[string]interface{}{
|
|
|
|
"c": "foo",
|
|
|
|
},
|
|
|
|
}), []string{"a", "b.c"}, []deploy.StepOp{deploy.OpCreate})
|
2019-04-22 22:54:48 +02:00
|
|
|
|
|
|
|
// Ensure that a change to an ignored property results in an OpSame
|
2019-07-31 18:39:07 +02:00
|
|
|
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"a": 2,
|
|
|
|
"b": map[string]interface{}{
|
|
|
|
"c": "bar",
|
|
|
|
},
|
|
|
|
}), []string{"a", "b.c"}, []deploy.StepOp{deploy.OpSame})
|
2019-04-22 22:54:48 +02:00
|
|
|
|
|
|
|
// Ensure that a change to an un-ignored property results in an OpUpdate
|
2019-07-31 18:39:07 +02:00
|
|
|
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"a": 3,
|
|
|
|
"b": map[string]interface{}{
|
|
|
|
"c": "qux",
|
|
|
|
},
|
|
|
|
}), nil, []deploy.StepOp{deploy.OpUpdate})
|
2019-04-22 22:54:48 +02:00
|
|
|
|
|
|
|
// Ensure that a removing an ignored property results in an OpSame
|
2019-07-31 18:39:07 +02:00
|
|
|
snap = updateProgramWithProps(snap, resource.PropertyMap{}, []string{"a", "b"}, []deploy.StepOp{deploy.OpSame})
|
2019-04-22 22:54:48 +02:00
|
|
|
|
|
|
|
// Ensure that a removing an un-ignored property results in an OpUpdate
|
|
|
|
snap = updateProgramWithProps(snap, resource.PropertyMap{}, nil, []deploy.StepOp{deploy.OpUpdate})
|
|
|
|
|
|
|
|
// Ensure that adding an ignored property results in an OpSame
|
2019-07-31 18:39:07 +02:00
|
|
|
snap = updateProgramWithProps(snap, resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"a": 4,
|
|
|
|
"b": map[string]interface{}{
|
|
|
|
"c": "zed",
|
|
|
|
},
|
|
|
|
}), []string{"a", "b"}, []deploy.StepOp{deploy.OpSame})
|
2019-04-22 22:54:48 +02:00
|
|
|
|
|
|
|
// Ensure that adding an un-ignored property results in an OpUpdate
|
|
|
|
_ = updateProgramWithProps(snap, resource.PropertyMap{
|
2019-07-31 18:39:07 +02:00
|
|
|
"c": resource.NewNumberProperty(4),
|
|
|
|
}, []string{"a", "b"}, []deploy.StepOp{deploy.OpUpdate})
|
2019-04-22 22:54:48 +02:00
|
|
|
}
|
2019-06-01 08:01:01 +02:00
|
|
|
|
Refine resource replacement logic for providers (#2767)
This commit touches an intersection of a few different provider-oriented
features that combined to cause a particularly severe bug that made it
impossible for users to upgrade provider versions without seeing
replacements with their resources.
For some context, Pulumi models all providers as resources and places
them in the snapshot like any other resource. Every resource has a
reference to the provider that created it. If a Pulumi program does not
specify a particular provider to use when performing a resource
operation, the Pulumi engine injects one automatically; these are called
"default providers" and are the most common ways that users end up with
providers in their snapshot. Default providers can be identified by
their name, which is always prefixed with "default".
Recently, in an effort to make the Pulumi engine more flexible with
provider versions, it was made possible for the engine to have multiple
default providers active for a provider of a particular type, which was
previously not possible. Because a provider is identified as a tuple of
package name and version, it was difficult to find a name for these
duplicate default providers that did not cause additional problems. The
provider versioning PR gave these default providers a name that was
derived from the version of the package. This proved to be a problem,
because when users upgraded from one version of a package to another,
this changed the name of their default provider which in turn caused all
of their resources created using that provider (read: everything) to be
replaced.
To combat this, this PR introduces a rule that the engine will apply
when diffing a resource to determine whether or not it needs to be
replaced: "If a resource's provider changes, and both old and new
providers are default providers whose properties do not require
replacement, proceed as if there were no diff." This allows the engine
to gracefully recognize and recover when a resource's default provider changes
names, as long as the provider's config has not changed.
2019-06-03 21:16:31 +02:00
|
|
|
// TestDefaultProviderDiff tests that the engine can gracefully recover whenever a resource's default provider changes
|
|
|
|
// and there is no diff in the provider's inputs.
|
|
|
|
func TestDefaultProviderDiff(t *testing.T) {
|
|
|
|
const resName, resBName = "resA", "resB"
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.10"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.11"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.12"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
runProgram := func(base *deploy.Snapshot, versionA, versionB string, expectedStep deploy.StepOp) *deploy.Snapshot {
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", resName, true, deploytest.ResourceOptions{
|
|
|
|
Version: versionA,
|
|
|
|
})
|
Refine resource replacement logic for providers (#2767)
This commit touches an intersection of a few different provider-oriented
features that combined to cause a particularly severe bug that made it
impossible for users to upgrade provider versions without seeing
replacements with their resources.
For some context, Pulumi models all providers as resources and places
them in the snapshot like any other resource. Every resource has a
reference to the provider that created it. If a Pulumi program does not
specify a particular provider to use when performing a resource
operation, the Pulumi engine injects one automatically; these are called
"default providers" and are the most common ways that users end up with
providers in their snapshot. Default providers can be identified by
their name, which is always prefixed with "default".
Recently, in an effort to make the Pulumi engine more flexible with
provider versions, it was made possible for the engine to have multiple
default providers active for a provider of a particular type, which was
previously not possible. Because a provider is identified as a tuple of
package name and version, it was difficult to find a name for these
duplicate default providers that did not cause additional problems. The
provider versioning PR gave these default providers a name that was
derived from the version of the package. This proved to be a problem,
because when users upgraded from one version of a package to another,
this changed the name of their default provider which in turn caused all
of their resources created using that provider (read: everything) to be
replaced.
To combat this, this PR introduces a rule that the engine will apply
when diffing a resource to determine whether or not it needs to be
replaced: "If a resource's provider changes, and both old and new
providers are default providers whose properties do not require
replacement, proceed as if there were no diff." This allows the engine
to gracefully recognize and recover when a resource's default provider changes
names, as long as the provider's config has not changed.
2019-06-03 21:16:31 +02:00
|
|
|
assert.NoError(t, err)
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", resBName, true, deploytest.ResourceOptions{
|
|
|
|
Version: versionB,
|
|
|
|
})
|
Refine resource replacement logic for providers (#2767)
This commit touches an intersection of a few different provider-oriented
features that combined to cause a particularly severe bug that made it
impossible for users to upgrade provider versions without seeing
replacements with their resources.
For some context, Pulumi models all providers as resources and places
them in the snapshot like any other resource. Every resource has a
reference to the provider that created it. If a Pulumi program does not
specify a particular provider to use when performing a resource
operation, the Pulumi engine injects one automatically; these are called
"default providers" and are the most common ways that users end up with
providers in their snapshot. Default providers can be identified by
their name, which is always prefixed with "default".
Recently, in an effort to make the Pulumi engine more flexible with
provider versions, it was made possible for the engine to have multiple
default providers active for a provider of a particular type, which was
previously not possible. Because a provider is identified as a tuple of
package name and version, it was difficult to find a name for these
duplicate default providers that did not cause additional problems. The
provider versioning PR gave these default providers a name that was
derived from the version of the package. This proved to be a problem,
because when users upgraded from one version of a package to another,
this changed the name of their default provider which in turn caused all
of their resources created using that provider (read: everything) to be
replaced.
To combat this, this PR introduces a rule that the engine will apply
when diffing a resource to determine whether or not it needs to be
replaced: "If a resource's provider changes, and both old and new
providers are default providers whose properties do not require
replacement, proceed as if there were no diff." This allows the engine
to gracefully recognize and recover when a resource's default provider changes
names, as long as the provider's config has not changed.
2019-06-03 21:16:31 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Kind != JournalEntrySuccess {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch entry.Step.URN().Name().String() {
|
|
|
|
case resName, resBName:
|
|
|
|
assert.Equal(t, expectedStep, entry.Step.Op())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p.Run(t, base)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This test simulates the upgrade scenario of old-style default providers to new-style versioned default providers.
|
|
|
|
//
|
|
|
|
// The first update creates a stack using a language host that does not report a version to the engine. As a result,
|
|
|
|
// the engine makes up a default provider for "pkgA" and calls it "default". It then creates the two resources that
|
|
|
|
// we are creating and associates them with the default provider.
|
|
|
|
snap := runProgram(nil, "", "", deploy.OpCreate)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
|
|
|
assert.Equal(t, "default", res.URN.Name().String())
|
|
|
|
case res.URN.Name().String() == resName || res.URN.Name().String() == resBName:
|
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "default", provRef.URN().Name().String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The second update switches to a language host that does report a version to the engine. As a result, the engine
|
|
|
|
// uses this version to make a new provider, with a different URN, and uses that provider to operate on resA and
|
|
|
|
// resB.
|
|
|
|
//
|
|
|
|
// Despite switching out the provider, the engine should still generate a Same step for resA. It is vital that the
|
|
|
|
// engine gracefully react to changes in the default provider in this manner. See pulumi/pulumi#2753 for what
|
|
|
|
// happens when it doesn't.
|
|
|
|
snap = runProgram(snap, "0.17.10", "0.17.10", deploy.OpSame)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
|
|
|
assert.Equal(t, "default_0_17_10", res.URN.Name().String())
|
|
|
|
case res.URN.Name().String() == resName || res.URN.Name().String() == resBName:
|
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "default_0_17_10", provRef.URN().Name().String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The third update changes the version that the language host reports to the engine. This simulates a scenario in
|
|
|
|
// which a user updates their SDK to a new version of a provider package. In order to simulate side-by-side
|
|
|
|
// packages with different versions, this update requests distinct package versions for resA and resB.
|
|
|
|
snap = runProgram(snap, "0.17.11", "0.17.12", deploy.OpSame)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
|
|
|
assert.True(t, res.URN.Name().String() == "default_0_17_11" || res.URN.Name().String() == "default_0_17_12")
|
|
|
|
case res.URN.Name().String() == resName:
|
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "default_0_17_11", provRef.URN().Name().String())
|
|
|
|
case res.URN.Name().String() == resBName:
|
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "default_0_17_12", provRef.URN().Name().String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestDefaultProviderDiffReplacement tests that, when replacing a default provider for a resource, the engine will
|
|
|
|
// replace the resource if DiffConfig on the new provider returns a diff for the provider's new state.
|
|
|
|
func TestDefaultProviderDiffReplacement(t *testing.T) {
|
|
|
|
const resName, resBName = "resA", "resB"
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.10"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
// This implementation of DiffConfig always requests replacement.
|
2019-07-31 18:39:07 +02:00
|
|
|
DiffConfigF: func(_ resource.URN, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
Refine resource replacement logic for providers (#2767)
This commit touches an intersection of a few different provider-oriented
features that combined to cause a particularly severe bug that made it
impossible for users to upgrade provider versions without seeing
replacements with their resources.
For some context, Pulumi models all providers as resources and places
them in the snapshot like any other resource. Every resource has a
reference to the provider that created it. If a Pulumi program does not
specify a particular provider to use when performing a resource
operation, the Pulumi engine injects one automatically; these are called
"default providers" and are the most common ways that users end up with
providers in their snapshot. Default providers can be identified by
their name, which is always prefixed with "default".
Recently, in an effort to make the Pulumi engine more flexible with
provider versions, it was made possible for the engine to have multiple
default providers active for a provider of a particular type, which was
previously not possible. Because a provider is identified as a tuple of
package name and version, it was difficult to find a name for these
duplicate default providers that did not cause additional problems. The
provider versioning PR gave these default providers a name that was
derived from the version of the package. This proved to be a problem,
because when users upgraded from one version of a package to another,
this changed the name of their default provider which in turn caused all
of their resources created using that provider (read: everything) to be
replaced.
To combat this, this PR introduces a rule that the engine will apply
when diffing a resource to determine whether or not it needs to be
replaced: "If a resource's provider changes, and both old and new
providers are default providers whose properties do not require
replacement, proceed as if there were no diff." This allows the engine
to gracefully recognize and recover when a resource's default provider changes
names, as long as the provider's config has not changed.
2019-06-03 21:16:31 +02:00
|
|
|
keys := []resource.PropertyKey{}
|
|
|
|
for k := range news {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
ReplaceKeys: keys,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("0.17.11"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
runProgram := func(base *deploy.Snapshot, versionA, versionB string, expectedSteps ...deploy.StepOp) *deploy.Snapshot {
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", resName, true, deploytest.ResourceOptions{
|
|
|
|
Version: versionA,
|
|
|
|
})
|
Refine resource replacement logic for providers (#2767)
This commit touches an intersection of a few different provider-oriented
features that combined to cause a particularly severe bug that made it
impossible for users to upgrade provider versions without seeing
replacements with their resources.
For some context, Pulumi models all providers as resources and places
them in the snapshot like any other resource. Every resource has a
reference to the provider that created it. If a Pulumi program does not
specify a particular provider to use when performing a resource
operation, the Pulumi engine injects one automatically; these are called
"default providers" and are the most common ways that users end up with
providers in their snapshot. Default providers can be identified by
their name, which is always prefixed with "default".
Recently, in an effort to make the Pulumi engine more flexible with
provider versions, it was made possible for the engine to have multiple
default providers active for a provider of a particular type, which was
previously not possible. Because a provider is identified as a tuple of
package name and version, it was difficult to find a name for these
duplicate default providers that did not cause additional problems. The
provider versioning PR gave these default providers a name that was
derived from the version of the package. This proved to be a problem,
because when users upgraded from one version of a package to another,
this changed the name of their default provider which in turn caused all
of their resources created using that provider (read: everything) to be
replaced.
To combat this, this PR introduces a rule that the engine will apply
when diffing a resource to determine whether or not it needs to be
replaced: "If a resource's provider changes, and both old and new
providers are default providers whose properties do not require
replacement, proceed as if there were no diff." This allows the engine
to gracefully recognize and recover when a resource's default provider changes
names, as long as the provider's config has not changed.
2019-06-03 21:16:31 +02:00
|
|
|
assert.NoError(t, err)
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", resBName, true, deploytest.ResourceOptions{
|
|
|
|
Version: versionB,
|
|
|
|
})
|
Refine resource replacement logic for providers (#2767)
This commit touches an intersection of a few different provider-oriented
features that combined to cause a particularly severe bug that made it
impossible for users to upgrade provider versions without seeing
replacements with their resources.
For some context, Pulumi models all providers as resources and places
them in the snapshot like any other resource. Every resource has a
reference to the provider that created it. If a Pulumi program does not
specify a particular provider to use when performing a resource
operation, the Pulumi engine injects one automatically; these are called
"default providers" and are the most common ways that users end up with
providers in their snapshot. Default providers can be identified by
their name, which is always prefixed with "default".
Recently, in an effort to make the Pulumi engine more flexible with
provider versions, it was made possible for the engine to have multiple
default providers active for a provider of a particular type, which was
previously not possible. Because a provider is identified as a tuple of
package name and version, it was difficult to find a name for these
duplicate default providers that did not cause additional problems. The
provider versioning PR gave these default providers a name that was
derived from the version of the package. This proved to be a problem,
because when users upgraded from one version of a package to another,
this changed the name of their default provider which in turn caused all
of their resources created using that provider (read: everything) to be
replaced.
To combat this, this PR introduces a rule that the engine will apply
when diffing a resource to determine whether or not it needs to be
replaced: "If a resource's provider changes, and both old and new
providers are default providers whose properties do not require
replacement, proceed as if there were no diff." This allows the engine
to gracefully recognize and recover when a resource's default provider changes
names, as long as the provider's config has not changed.
2019-06-03 21:16:31 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Kind != JournalEntrySuccess {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch entry.Step.URN().Name().String() {
|
|
|
|
case resName:
|
|
|
|
assert.Subset(t, expectedSteps, []deploy.StepOp{entry.Step.Op()})
|
|
|
|
case resBName:
|
|
|
|
assert.Subset(t,
|
|
|
|
[]deploy.StepOp{deploy.OpCreate, deploy.OpSame}, []deploy.StepOp{entry.Step.Op()})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p.Run(t, base)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This test simulates the upgrade scenario of default providers, except that the requested upgrade results in the
|
|
|
|
// provider getting replaced. Because of this, the engine should decide to replace resA. It should not decide to
|
|
|
|
// replace resB, as its change does not require replacement.
|
|
|
|
snap := runProgram(nil, "", "", deploy.OpCreate)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
|
|
|
assert.Equal(t, "default", res.URN.Name().String())
|
|
|
|
case res.URN.Name().String() == resName || res.URN.Name().String() == resBName:
|
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "default", provRef.URN().Name().String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Upon update, now that the language host is sending a version, DiffConfig reports that there's a diff between the
|
|
|
|
// old and new provider and so we must replace resA.
|
|
|
|
snap = runProgram(snap, "0.17.10", "0.17.11", deploy.OpCreateReplacement, deploy.OpReplace, deploy.OpDeleteReplaced)
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
switch {
|
|
|
|
case providers.IsDefaultProvider(res.URN):
|
|
|
|
assert.True(t, res.URN.Name().String() == "default_0_17_10" || res.URN.Name().String() == "default_0_17_11")
|
|
|
|
case res.URN.Name().String() == resName:
|
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "default_0_17_10", provRef.URN().Name().String())
|
|
|
|
case res.URN.Name().String() == resBName:
|
|
|
|
provRef, err := providers.ParseReference(res.Provider)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "default_0_17_11", provRef.URN().Name().String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-01 08:01:01 +02:00
|
|
|
// Resource is an abstract representation of a resource graph
|
|
|
|
type Resource struct {
|
|
|
|
t tokens.Type
|
|
|
|
name string
|
|
|
|
children []Resource
|
|
|
|
props resource.PropertyMap
|
|
|
|
aliases []resource.URN
|
|
|
|
dependencies []resource.URN
|
|
|
|
parent resource.URN
|
|
|
|
deleteBeforeReplace bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func registerResources(t *testing.T, monitor *deploytest.ResourceMonitor, resources []Resource) error {
|
|
|
|
for _, r := range resources {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource(r.t, r.name, true, deploytest.ResourceOptions{
|
|
|
|
Parent: r.parent,
|
|
|
|
Dependencies: r.dependencies,
|
|
|
|
Inputs: r.props,
|
2019-08-21 00:51:02 +02:00
|
|
|
DeleteBeforeReplace: &r.deleteBeforeReplace,
|
2019-07-25 20:18:40 +02:00
|
|
|
Aliases: r.aliases,
|
|
|
|
})
|
2019-06-01 08:01:01 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = registerResources(t, monitor, r.children)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAliases(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
// The `forcesReplacement` key forces replacement and all other keys can update in place
|
2019-07-31 18:39:07 +02:00
|
|
|
DiffF: func(res resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
2019-06-01 08:01:01 +02:00
|
|
|
replaceKeys := []resource.PropertyKey{}
|
|
|
|
old, hasOld := olds["forcesReplacement"]
|
|
|
|
new, hasNew := news["forcesReplacement"]
|
|
|
|
if hasOld && !hasNew || hasNew && !hasOld || hasOld && hasNew && old.Diff(new) != nil {
|
|
|
|
replaceKeys = append(replaceKeys, "forcesReplacement")
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{ReplaceKeys: replaceKeys}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
updateProgramWithResource := func(
|
|
|
|
snap *deploy.Snapshot, resources []Resource, allowedOps []deploy.StepOp) *deploy.Snapshot {
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
err := registerResources(t, monitor, resources)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
for _, event := range events {
|
|
|
|
if event.Type == ResourcePreEvent {
|
|
|
|
payload := event.Payload.(ResourcePreEventPayload)
|
|
|
|
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Step.Type() == "pulumi:providers:pkgA" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch entry.Kind {
|
|
|
|
case JournalEntrySuccess:
|
|
|
|
assert.Subset(t, allowedOps, []deploy.StepOp{entry.Step.Op()})
|
|
|
|
case JournalEntryFailure:
|
|
|
|
assert.Fail(t, "unexpected failure in journal")
|
|
|
|
case JournalEntryBegin:
|
|
|
|
case JournalEntryOutputs:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p.Run(t, snap)
|
|
|
|
}
|
|
|
|
|
|
|
|
snap := updateProgramWithResource(nil, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n1",
|
|
|
|
}}, []deploy.StepOp{deploy.OpCreate})
|
|
|
|
|
|
|
|
// Ensure that rename produces Same
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n2",
|
|
|
|
aliases: []resource.URN{"urn:pulumi:test::test::pkgA:index:t1::n1"},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that rename produces Same with multiple aliases
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n3",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n2",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that rename produces Same with multiple aliases (reversed)
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n3",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n2",
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that aliasing back to original name is okay
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n1",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n3",
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n2",
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that removing aliases is okay (once old names are gone from all snapshots)
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n1",
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that changing the type works
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t2",
|
|
|
|
name: "n1",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that changing the type again works
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:othermod:t3",
|
|
|
|
name: "n1",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t2::n1",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that order of aliases doesn't matter
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:othermod:t3",
|
|
|
|
name: "n1",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
"urn:pulumi:test::test::pkgA:othermod:t3::n1",
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t2::n1",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that removing aliases is okay (once old names are gone from all snapshots)
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:othermod:t3",
|
|
|
|
name: "n1",
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame})
|
|
|
|
|
|
|
|
// Ensure that changing everything (including props) leads to update not delete and re-create
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t4",
|
|
|
|
name: "n2",
|
|
|
|
props: resource.PropertyMap{
|
|
|
|
resource.PropertyKey("x"): resource.NewNumberProperty(42),
|
|
|
|
},
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:othermod:t3::n1",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpUpdate})
|
|
|
|
|
|
|
|
// Ensure that changing everything again (including props) leads to update not delete and re-create
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t5",
|
|
|
|
name: "n3",
|
|
|
|
props: resource.PropertyMap{
|
|
|
|
resource.PropertyKey("x"): resource.NewNumberProperty(1000),
|
|
|
|
},
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t4::n2",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpUpdate})
|
|
|
|
|
|
|
|
// Ensure that changing a forceNew property while also changing type and name leads to replacement not delete+create
|
|
|
|
snap = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t6",
|
|
|
|
name: "n4",
|
|
|
|
props: resource.PropertyMap{
|
|
|
|
resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1000),
|
|
|
|
},
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t5::n3",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
|
|
|
|
|
|
|
// Ensure that changing a forceNew property and deleteBeforeReplace while also changing type and name leads to
|
|
|
|
// replacement not delete+create
|
|
|
|
_ = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t7",
|
|
|
|
name: "n5",
|
|
|
|
props: resource.PropertyMap{
|
|
|
|
resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(999),
|
|
|
|
},
|
|
|
|
deleteBeforeReplace: true,
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t6::n4",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
|
|
|
|
|
|
|
// Start again - this time with two resources with depends on relationship
|
|
|
|
snap = updateProgramWithResource(nil, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n1",
|
|
|
|
props: resource.PropertyMap{
|
|
|
|
resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1),
|
|
|
|
},
|
|
|
|
deleteBeforeReplace: true,
|
|
|
|
}, {
|
|
|
|
t: "pkgA:index:t2",
|
|
|
|
name: "n2",
|
|
|
|
dependencies: []resource.URN{"urn:pulumi:test::test::pkgA:index:t1::n1"},
|
|
|
|
}}, []deploy.StepOp{deploy.OpCreate})
|
|
|
|
|
|
|
|
_ = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t1-new",
|
|
|
|
name: "n1-new",
|
|
|
|
props: resource.PropertyMap{
|
|
|
|
resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(2),
|
|
|
|
},
|
|
|
|
deleteBeforeReplace: true,
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
t: "pkgA:index:t2-new",
|
|
|
|
name: "n2-new",
|
|
|
|
dependencies: []resource.URN{"urn:pulumi:test::test::pkgA:index:t1-new::n1-new"},
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t2::n2",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame, deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
|
|
|
|
|
|
|
// Start again - this time with two resources with parent relationship
|
|
|
|
snap = updateProgramWithResource(nil, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n1",
|
|
|
|
props: resource.PropertyMap{
|
|
|
|
resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(1),
|
|
|
|
},
|
|
|
|
deleteBeforeReplace: true,
|
|
|
|
}, {
|
|
|
|
t: "pkgA:index:t2",
|
|
|
|
name: "n2",
|
|
|
|
parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1::n1"),
|
|
|
|
}}, []deploy.StepOp{deploy.OpCreate})
|
|
|
|
|
|
|
|
_ = updateProgramWithResource(snap, []Resource{{
|
|
|
|
t: "pkgA:index:t1-new",
|
|
|
|
name: "n1-new",
|
|
|
|
props: resource.PropertyMap{
|
|
|
|
resource.PropertyKey("forcesReplacement"): resource.NewNumberProperty(2),
|
|
|
|
},
|
|
|
|
deleteBeforeReplace: true,
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
t: "pkgA:index:t2-new",
|
|
|
|
name: "n2-new",
|
|
|
|
parent: resource.URN("urn:pulumi:test::test::pkgA:index:t1-new::n1-new"),
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1$pkgA:index:t2::n2",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpSame, deploy.OpReplace, deploy.OpCreateReplacement, deploy.OpDeleteReplaced})
|
2019-11-06 17:49:13 +01:00
|
|
|
|
|
|
|
// ensure failure when different resources use duplicate aliases
|
|
|
|
snap = updateProgramWithResource(nil, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n1",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
t: "pkgA:index:t2",
|
|
|
|
name: "n2",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpCreate})
|
|
|
|
|
|
|
|
err := snap.NormalizeURNReferences()
|
|
|
|
assert.Equal(t, err.Error(),
|
|
|
|
"Two resources ('urn:pulumi:test::test::pkgA:index:t1::n1'"+
|
|
|
|
" and 'urn:pulumi:test::test::pkgA:index:t2::n2') aliased to the same: 'urn:pulumi:test::test::pkgA:index:t1::n1'")
|
|
|
|
|
|
|
|
// ensure different resources can use different aliases
|
|
|
|
snap = updateProgramWithResource(nil, []Resource{{
|
|
|
|
t: "pkgA:index:t1",
|
|
|
|
name: "n1",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n1",
|
|
|
|
},
|
|
|
|
}, {
|
|
|
|
t: "pkgA:index:t2",
|
|
|
|
name: "n2",
|
|
|
|
aliases: []resource.URN{
|
|
|
|
"urn:pulumi:test::test::pkgA:index:t1::n2",
|
|
|
|
},
|
|
|
|
}}, []deploy.StepOp{deploy.OpCreate})
|
|
|
|
|
|
|
|
err = snap.NormalizeURNReferences()
|
|
|
|
assert.Nil(t, err)
|
2019-07-01 21:34:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestPersistentDiff(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
2019-07-31 18:39:07 +02:00
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
2019-07-01 21:34:19 +02:00
|
|
|
|
|
|
|
return plugin.DiffResult{Changes: plugin.DiffSome}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
inputs := resource.PropertyMap{}
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputs,
|
|
|
|
})
|
2019-07-01 21:34:19 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Run the initial update.
|
|
|
|
project := p.GetProject()
|
|
|
|
snap, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// First, make no change to the inputs and run a preview. We should see an update to the resource due to
|
|
|
|
// provider diffing.
|
|
|
|
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, true, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, _ *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
found := false
|
|
|
|
for _, e := range events {
|
|
|
|
if e.Type == ResourcePreEvent {
|
|
|
|
p := e.Payload.(ResourcePreEventPayload).Metadata
|
|
|
|
if p.URN == resURN {
|
|
|
|
assert.Equal(t, deploy.OpUpdate, p.Op)
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, found)
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// Next, enable legacy diff behavior. We should see no changes to the resource.
|
|
|
|
p.Options.UseLegacyDiff = true
|
|
|
|
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, true, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, _ *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
found := false
|
|
|
|
for _, e := range events {
|
|
|
|
if e.Type == ResourcePreEvent {
|
|
|
|
p := e.Payload.(ResourcePreEventPayload).Metadata
|
|
|
|
if p.URN == resURN {
|
|
|
|
assert.Equal(t, deploy.OpSame, p.Op)
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, found)
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDetailedDiffReplace(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
2019-07-31 18:39:07 +02:00
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
2019-07-01 21:34:19 +02:00
|
|
|
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
|
|
"prop": {Kind: plugin.DiffAddReplace},
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
2019-06-01 08:01:01 +02:00
|
|
|
|
2019-07-01 21:34:19 +02:00
|
|
|
inputs := resource.PropertyMap{}
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputs,
|
|
|
|
})
|
2019-07-01 21:34:19 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Run the initial update.
|
|
|
|
project := p.GetProject()
|
|
|
|
snap, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// First, make no change to the inputs and run a preview. We should see an update to the resource due to
|
|
|
|
// provider diffing.
|
|
|
|
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, true, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, _ *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
found := false
|
|
|
|
for _, e := range events {
|
|
|
|
if e.Type == ResourcePreEvent {
|
|
|
|
p := e.Payload.(ResourcePreEventPayload).Metadata
|
|
|
|
if p.URN == resURN && p.Op == deploy.OpReplace {
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, found)
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
2019-06-01 08:01:01 +02:00
|
|
|
}
|
2019-07-12 20:12:01 +02:00
|
|
|
|
|
|
|
func TestImport(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
2019-07-31 18:39:07 +02:00
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
2019-07-12 20:12:01 +02:00
|
|
|
|
|
|
|
if olds["foo"].DeepEquals(news["foo"]) {
|
|
|
|
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
diffKind := plugin.DiffUpdate
|
|
|
|
if news["foo"].IsString() && news["foo"].StringValue() == "replace" {
|
|
|
|
diffKind = plugin.DiffUpdateReplace
|
|
|
|
}
|
|
|
|
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
DetailedDiff: map[string]plugin.PropertyDiff{
|
|
|
|
"foo": {Kind: diffKind},
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
CreateF: func(urn resource.URN,
|
Addition of Custom Timeouts (#2885)
* Plumbing the custom timeouts from the engine to the providers
* Plumbing the CustomTimeouts through to the engine and adding test to show this
* Change the provider proto to include individual timeouts
* Plumbing the CustomTimeouts from the engine through to the Provider RPC interface
* Change how the CustomTimeouts are sent across RPC
These errors were spotted in testing. We can now see that the timeout
information is arriving in the RegisterResourceRequest
```
req=&pulumirpc.RegisterResourceRequest{
Type: "aws:s3/bucket:Bucket",
Name: "my-bucket",
Parent: "urn:pulumi:dev::aws-vpc::pulumi:pulumi:Stack::aws-vpc-dev",
Custom: true,
Object: &structpb.Struct{},
Protect: false,
Dependencies: nil,
Provider: "",
PropertyDependencies: {},
DeleteBeforeReplace: false,
Version: "",
IgnoreChanges: nil,
AcceptSecrets: true,
AdditionalSecretOutputs: nil,
Aliases: nil,
CustomTimeouts: &pulumirpc.RegisterResourceRequest_CustomTimeouts{
Create: 300,
Update: 400,
Delete: 500,
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
},
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
}
```
* Changing the design to use strings
* CHANGELOG entry to include the CustomTimeouts work
* Changing custom timeouts to be passed around the engine as converted value
We don't want to pass around strings - the user can provide it but we want
to make the engine aware of the timeout in seconds as a float64
2019-07-15 23:26:28 +02:00
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
2019-07-12 20:12:01 +02:00
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
|
|
|
|
return plugin.ReadResult{
|
|
|
|
Inputs: resource.PropertyMap{
|
|
|
|
"foo": resource.NewStringProperty("bar"),
|
|
|
|
},
|
|
|
|
Outputs: resource.PropertyMap{
|
|
|
|
"foo": resource.NewStringProperty("bar"),
|
|
|
|
},
|
|
|
|
}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
readID, importID, inputs := resource.ID(""), resource.ID("id"), resource.PropertyMap{}
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
var err error
|
|
|
|
if readID != "" {
|
|
|
|
_, _, err = monitor.ReadResource("pkgA:m:typA", "resA", readID, "", resource.PropertyMap{}, "", "")
|
|
|
|
} else {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputs,
|
|
|
|
ImportID: importID,
|
|
|
|
})
|
2019-07-12 20:12:01 +02:00
|
|
|
}
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
// Run the initial update. The import should fail due to a mismatch in inputs between the program and the
|
|
|
|
// actual resource state.
|
|
|
|
project := p.GetProject()
|
|
|
|
_, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.NotNil(t, res)
|
|
|
|
|
|
|
|
// Run a second update after fixing the inputs. The import should succeed.
|
|
|
|
inputs["foo"] = resource.NewStringProperty("bar")
|
|
|
|
snap, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
|
|
case resURN:
|
|
|
|
assert.Equal(t, deploy.OpImport, entry.Step.Op())
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.Len(t, snap.Resources, 2)
|
|
|
|
|
|
|
|
// Now, run another update. The update should succeed and there should be no diffs.
|
|
|
|
snap, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// Change a property value and run a third update. The update should succeed.
|
|
|
|
inputs["foo"] = resource.NewStringProperty("rab")
|
|
|
|
snap, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
case resURN:
|
|
|
|
assert.Equal(t, deploy.OpUpdate, entry.Step.Op())
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// Change the property value s.t. the resource requires replacement. The update should fail.
|
|
|
|
inputs["foo"] = resource.NewStringProperty("replace")
|
|
|
|
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient, nil)
|
|
|
|
assert.NotNil(t, res)
|
|
|
|
|
|
|
|
// Finally, destroy the stack. The `Delete` function should be called.
|
|
|
|
_, res = TestOp(Destroy).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN, resURN:
|
|
|
|
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// Now clear the ID to import and run an initial update to create a resource that we will import-replace.
|
|
|
|
importID, inputs["foo"] = "", resource.NewStringProperty("bar")
|
|
|
|
snap, res = TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN, resURN:
|
|
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.Len(t, snap.Resources, 2)
|
|
|
|
|
|
|
|
// Set the import ID to the same ID as the existing resource and run an update. This should produce no changes.
|
|
|
|
for _, r := range snap.Resources {
|
|
|
|
if r.URN == resURN {
|
|
|
|
importID = r.ID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
snap, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// Then set the import ID and run another update. The update should succeed and should show an import-replace and
|
|
|
|
// a delete-replaced.
|
|
|
|
importID = "id"
|
|
|
|
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
case resURN:
|
|
|
|
switch entry.Step.Op() {
|
|
|
|
case deploy.OpReplace, deploy.OpImportReplacement:
|
|
|
|
assert.Equal(t, importID, entry.Step.New().ID)
|
|
|
|
case deploy.OpDeleteReplaced:
|
|
|
|
assert.NotEqual(t, importID, entry.Step.Old().ID)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// Change the program to read a resource rather than creating one.
|
|
|
|
readID = "id"
|
|
|
|
snap, res = TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
|
|
case resURN:
|
|
|
|
assert.Equal(t, deploy.OpRead, entry.Step.Op())
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.Len(t, snap.Resources, 2)
|
|
|
|
|
|
|
|
// Now have the program import the resource. We should see an import-replace and a read-discard.
|
|
|
|
readID, importID = "", readID
|
|
|
|
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
|
|
|
|
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
switch urn := entry.Step.URN(); urn {
|
|
|
|
case provURN:
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
case resURN:
|
|
|
|
switch entry.Step.Op() {
|
|
|
|
case deploy.OpReplace, deploy.OpImportReplacement:
|
|
|
|
assert.Equal(t, importID, entry.Step.New().ID)
|
|
|
|
case deploy.OpDiscardReplaced:
|
|
|
|
assert.Equal(t, importID, entry.Step.Old().ID)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
})
|
|
|
|
assert.Nil(t, res)
|
|
|
|
}
|
Addition of Custom Timeouts (#2885)
* Plumbing the custom timeouts from the engine to the providers
* Plumbing the CustomTimeouts through to the engine and adding test to show this
* Change the provider proto to include individual timeouts
* Plumbing the CustomTimeouts from the engine through to the Provider RPC interface
* Change how the CustomTimeouts are sent across RPC
These errors were spotted in testing. We can now see that the timeout
information is arriving in the RegisterResourceRequest
```
req=&pulumirpc.RegisterResourceRequest{
Type: "aws:s3/bucket:Bucket",
Name: "my-bucket",
Parent: "urn:pulumi:dev::aws-vpc::pulumi:pulumi:Stack::aws-vpc-dev",
Custom: true,
Object: &structpb.Struct{},
Protect: false,
Dependencies: nil,
Provider: "",
PropertyDependencies: {},
DeleteBeforeReplace: false,
Version: "",
IgnoreChanges: nil,
AcceptSecrets: true,
AdditionalSecretOutputs: nil,
Aliases: nil,
CustomTimeouts: &pulumirpc.RegisterResourceRequest_CustomTimeouts{
Create: 300,
Update: 400,
Delete: 500,
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
},
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
}
```
* Changing the design to use strings
* CHANGELOG entry to include the CustomTimeouts work
* Changing custom timeouts to be passed around the engine as converted value
We don't want to pass around strings - the user can provide it but we want
to make the engine aware of the timeout in seconds as a float64
2019-07-15 23:26:28 +02:00
|
|
|
|
|
|
|
func TestCustomTimeouts(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 {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
CustomTimeouts: &resource.CustomTimeouts{
|
Addition of Custom Timeouts (#2885)
* Plumbing the custom timeouts from the engine to the providers
* Plumbing the CustomTimeouts through to the engine and adding test to show this
* Change the provider proto to include individual timeouts
* Plumbing the CustomTimeouts from the engine through to the Provider RPC interface
* Change how the CustomTimeouts are sent across RPC
These errors were spotted in testing. We can now see that the timeout
information is arriving in the RegisterResourceRequest
```
req=&pulumirpc.RegisterResourceRequest{
Type: "aws:s3/bucket:Bucket",
Name: "my-bucket",
Parent: "urn:pulumi:dev::aws-vpc::pulumi:pulumi:Stack::aws-vpc-dev",
Custom: true,
Object: &structpb.Struct{},
Protect: false,
Dependencies: nil,
Provider: "",
PropertyDependencies: {},
DeleteBeforeReplace: false,
Version: "",
IgnoreChanges: nil,
AcceptSecrets: true,
AdditionalSecretOutputs: nil,
Aliases: nil,
CustomTimeouts: &pulumirpc.RegisterResourceRequest_CustomTimeouts{
Create: 300,
Update: 400,
Delete: 500,
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
},
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
}
```
* Changing the design to use strings
* CHANGELOG entry to include the CustomTimeouts work
* Changing custom timeouts to be passed around the engine as converted value
We don't want to pass around strings - the user can provide it but we want
to make the engine aware of the timeout in seconds as a float64
2019-07-15 23:26:28 +02:00
|
|
|
Create: 60, Delete: 60, Update: 240,
|
2019-07-25 20:18:40 +02:00
|
|
|
},
|
|
|
|
})
|
Addition of Custom Timeouts (#2885)
* Plumbing the custom timeouts from the engine to the providers
* Plumbing the CustomTimeouts through to the engine and adding test to show this
* Change the provider proto to include individual timeouts
* Plumbing the CustomTimeouts from the engine through to the Provider RPC interface
* Change how the CustomTimeouts are sent across RPC
These errors were spotted in testing. We can now see that the timeout
information is arriving in the RegisterResourceRequest
```
req=&pulumirpc.RegisterResourceRequest{
Type: "aws:s3/bucket:Bucket",
Name: "my-bucket",
Parent: "urn:pulumi:dev::aws-vpc::pulumi:pulumi:Stack::aws-vpc-dev",
Custom: true,
Object: &structpb.Struct{},
Protect: false,
Dependencies: nil,
Provider: "",
PropertyDependencies: {},
DeleteBeforeReplace: false,
Version: "",
IgnoreChanges: nil,
AcceptSecrets: true,
AdditionalSecretOutputs: nil,
Aliases: nil,
CustomTimeouts: &pulumirpc.RegisterResourceRequest_CustomTimeouts{
Create: 300,
Update: 400,
Delete: 500,
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
},
XXX_NoUnkeyedLiteral: struct {}{},
XXX_unrecognized: nil,
XXX_sizecache: 0,
}
```
* Changing the design to use strings
* CHANGELOG entry to include the CustomTimeouts work
* Changing custom timeouts to be passed around the engine as converted value
We don't want to pass around strings - the user can provide it but we want
to make the engine aware of the timeout in seconds as a float64
2019-07-15 23:26:28 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
assert.Len(t, snap.Resources, 2)
|
|
|
|
assert.Equal(t, string(snap.Resources[0].URN.Name()), "default")
|
|
|
|
assert.Equal(t, string(snap.Resources[1].URN.Name()), "resA")
|
|
|
|
assert.NotNil(t, snap.Resources[1].CustomTimeouts)
|
|
|
|
assert.Equal(t, snap.Resources[1].CustomTimeouts.Create, float64(60))
|
|
|
|
assert.Equal(t, snap.Resources[1].CustomTimeouts.Update, float64(240))
|
|
|
|
assert.Equal(t, snap.Resources[1].CustomTimeouts.Delete, float64(60))
|
|
|
|
}
|
2019-07-23 22:39:21 +02:00
|
|
|
|
|
|
|
func TestProviderDiffMissingOldOutputs(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
2019-07-31 18:39:07 +02:00
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
2019-07-23 22:39:21 +02:00
|
|
|
// Always require replacement if any diff exists.
|
|
|
|
if !olds.DeepEquals(news) {
|
|
|
|
keys := []resource.PropertyKey{}
|
|
|
|
for k := range news {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{Changes: plugin.DiffSome, ReplaceKeys: keys}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
2019-07-25 20:18:40 +02:00
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
2019-07-23 22:39:21 +02:00
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, 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 initial update and refresh.
|
|
|
|
p.Steps = steps[:2]
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Delete the old provider outputs (if any) from the checkpoint, then run the no-op update.
|
|
|
|
providerURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
for _, r := range snap.Resources {
|
|
|
|
if r.URN == providerURN {
|
|
|
|
r.Outputs = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = steps[2:3]
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Change the config, delete the old provider outputs, and run an update. We expect everything to require
|
|
|
|
// replacement.
|
|
|
|
p.Config[config.MustMakeKey("pkgA", "foo")] = config.NewValue("baz")
|
|
|
|
for _, r := range snap.Resources {
|
|
|
|
if r.URN == providerURN {
|
|
|
|
r.Outputs = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
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 providerURN:
|
|
|
|
replacedProvider = true
|
|
|
|
case resURN:
|
|
|
|
replacedResource = true
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, replacedProvider)
|
|
|
|
assert.True(t, replacedResource)
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
2019-08-16 20:04:03 +02:00
|
|
|
|
|
|
|
func TestRefreshStepWillPersistUpdatedIDs(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
idBefore := resource.ID("myid")
|
|
|
|
idAfter := resource.ID("mynewid")
|
|
|
|
outputs := resource.PropertyMap{"foo": resource.NewStringProperty("bar")}
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(
|
|
|
|
urn resource.URN, id resource.ID, inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
return plugin.ReadResult{ID: idAfter, Outputs: outputs, Inputs: resource.PropertyMap{}}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", false)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p.Options.host = host
|
|
|
|
|
|
|
|
old := &deploy.Snapshot{
|
|
|
|
Resources: []*resource.State{
|
|
|
|
{
|
|
|
|
Type: resURN.Type(),
|
|
|
|
URN: resURN,
|
|
|
|
Custom: true,
|
|
|
|
ID: idBefore,
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
Outputs: outputs,
|
|
|
|
InitErrors: []string{"Resource failed to initialize"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Refresh, SkipPreview: true}}
|
|
|
|
snap := p.Run(t, old)
|
|
|
|
|
|
|
|
for _, resource := range snap.Resources {
|
|
|
|
switch urn := resource.URN; urn {
|
|
|
|
case provURN:
|
|
|
|
// break
|
|
|
|
case resURN:
|
|
|
|
assert.Empty(t, resource.InitErrors)
|
|
|
|
assert.Equal(t, idAfter, resource.ID)
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-21 19:09:02 +02:00
|
|
|
|
|
|
|
func TestMissingRead(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(_ resource.URN, _ resource.ID, _, _ resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
return plugin.ReadResult{}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Our program reads a resource and exits.
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, err := monitor.ReadResource("pkgA:m:typA", "resA", "resA-some-id", "", resource.PropertyMap{}, "", "")
|
|
|
|
assert.Error(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{Op: Update, ExpectFailure: true}},
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
2019-08-24 00:00:24 +02:00
|
|
|
|
|
|
|
func TestImportUpdatedID(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
provURN := p.NewProviderURN("pkgA", "default", "")
|
|
|
|
resURN := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
importID := resource.ID("myID")
|
|
|
|
actualID := resource.ID("myNewID")
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(
|
|
|
|
urn resource.URN, id resource.ID, inputs, state resource.PropertyMap,
|
|
|
|
) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
return plugin.ReadResult{
|
|
|
|
ID: actualID,
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, id, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", false, deploytest.ResourceOptions{
|
|
|
|
ImportID: importID,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, actualID, id)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Refresh, SkipPreview: true}}
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
for _, resource := range snap.Resources {
|
|
|
|
switch urn := resource.URN; urn {
|
|
|
|
case provURN:
|
|
|
|
// break
|
|
|
|
case resURN:
|
|
|
|
assert.Equal(t, actualID, resource.ID)
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected resource %v", urn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-20 04:28:14 +02:00
|
|
|
|
2019-11-19 05:28:25 +01:00
|
|
|
func TestDestroyTarget(t *testing.T) {
|
2019-09-20 04:28:14 +02:00
|
|
|
// Try refreshing a stack with combinations of the above resources as target to destroy.
|
2019-10-01 08:41:56 +02:00
|
|
|
subsets := combinations.All(complexTestDependencyGraphNames)
|
2019-09-20 04:28:14 +02:00
|
|
|
|
|
|
|
for _, subset := range subsets {
|
|
|
|
// limit to up to 3 resources to destroy. This keeps the test running time under
|
|
|
|
// control as it only generates a few hundred combinations instead of several thousand.
|
|
|
|
if len(subset) <= 3 {
|
2019-11-19 05:28:25 +01:00
|
|
|
destroySpecificTargets(t, subset, true, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {})
|
2019-09-20 04:28:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-19 05:28:25 +01:00
|
|
|
destroySpecificTargets(
|
|
|
|
t, []string{"A"}, true, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {
|
|
|
|
// when deleting 'A' we expect A, B, C, E, F, and K to be deleted
|
|
|
|
names := complexTestDependencyGraphNames
|
|
|
|
assert.Equal(t, map[resource.URN]bool{
|
|
|
|
pickURN(t, urns, names, "A"): true,
|
|
|
|
pickURN(t, urns, names, "B"): true,
|
|
|
|
pickURN(t, urns, names, "C"): true,
|
|
|
|
pickURN(t, urns, names, "E"): true,
|
|
|
|
pickURN(t, urns, names, "F"): true,
|
|
|
|
pickURN(t, urns, names, "K"): true,
|
|
|
|
}, deleted)
|
|
|
|
})
|
|
|
|
|
|
|
|
destroySpecificTargets(
|
|
|
|
t, []string{"A"}, false, /*targetDependents*/
|
|
|
|
func(urns []resource.URN, deleted map[resource.URN]bool) {})
|
2019-09-20 04:28:14 +02:00
|
|
|
}
|
|
|
|
|
2019-11-19 05:28:25 +01:00
|
|
|
func destroySpecificTargets(
|
|
|
|
t *testing.T, targets []string, targetDependents bool,
|
2019-09-20 04:28:14 +02:00
|
|
|
validate func(urns []resource.URN, deleted map[resource.URN]bool)) {
|
|
|
|
|
|
|
|
// A
|
|
|
|
// _________|_________
|
|
|
|
// B C D
|
|
|
|
// ___|___ ___|___
|
|
|
|
// E F G H I J
|
|
|
|
// |__|
|
|
|
|
// K L
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
2019-10-01 08:41:56 +02:00
|
|
|
urns, old, program := generateComplexTestDependencyGraph(t, p)
|
2019-09-20 04:28:14 +02:00
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{
|
|
|
|
ReplaceKeys: []resource.PropertyKey{"A"},
|
|
|
|
DeleteBeforeReplace: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"A"}}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
2019-11-19 05:28:25 +01:00
|
|
|
p.Options.TargetDependents = targetDependents
|
2019-09-20 04:28:14 +02:00
|
|
|
|
|
|
|
destroyTargets := []resource.URN{}
|
|
|
|
for _, target := range targets {
|
2019-10-01 08:41:56 +02:00
|
|
|
destroyTargets = append(destroyTargets, pickURN(t, urns, complexTestDependencyGraphNames, target))
|
2019-09-20 04:28:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.DestroyTargets = destroyTargets
|
|
|
|
t.Logf("Destroying targets: %v", destroyTargets)
|
|
|
|
|
2019-11-19 05:28:25 +01:00
|
|
|
// If we're not forcing the targets to be destroyed, then expect to get a failure here as
|
|
|
|
// we'll have downstream resources to delete that weren't specified explicitly.
|
2019-09-20 04:28:14 +02:00
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Destroy,
|
2019-11-19 05:28:25 +01:00
|
|
|
ExpectFailure: !targetDependents,
|
2019-09-20 04:28:14 +02:00
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(j.Entries) > 0)
|
|
|
|
|
|
|
|
deleted := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
|
|
|
|
deleted[entry.Step.URN()] = true
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.DestroyTargets {
|
|
|
|
assert.Contains(t, deleted, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
validate(urns, deleted)
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
Propagate inputs to outputs during preview. (#3245)
These changes restore a more-correct version of the behavior that was
disabled with #3014. The original implementation of this behavior was
done in the SDKs, which do not have access to the complete inputs for a
resource (in particular, default values filled in by the provider during
`Check` are not exposed to the SDK). This lack of information meant that
the resolved output values could disagree with the typings present in
a provider SDK. Exacerbating this problem was the fact that unknown
values were dropped entirely, causing `undefined` values to appear in
unexpected places.
By doing this in the engine and allowing unknown values to be
represented in a first-class manner in the SDK, we can attack both of
these issues.
Although this behavior is not _strictly_ consistent with respect to the
resource model--in an update, a resource's output properties will come
from its provider and may differ from its input properties--this
behavior was present in the product for a fairly long time without
significant issues. In the future, we may be able to improve the
accuracy of resource outputs during a preview by allowing the provider
to dry-run CRUD operations and return partially-known values where
possible.
These changes also introduce new APIs in the Node and Python SDKs
that work with unknown values in a first-class fashion:
- A new parameter to the `apply` function that indicates that the
callback should be run even if the result of the apply contains
unknown values
- `containsUnknowns` and `isUnknown`, which return true if a value
either contains nested unknown values or is exactly an unknown value
- The `Unknown` type, which represents unknown values
The primary use case for these APIs is to allow nested, properties with
known values to be accessed via the lifted property accessor even when
the containing property is not fully know. A common example of this
pattern is the `metadata.name` property of a Kubernetes `Namespace`
object: while other properties of the `metadata` bag may be unknown,
`name` is often known. These APIs allow `ns.metadata.name` to return a
known value in this case.
In order to avoid exposing downlevel SDKs to unknown values--a change
which could break user code by exposing it to unexpected values--a
language SDK must indicate whether or not it supports first-class
unknown values as part of each `RegisterResourceRequest`.
These changes also allow us to avoid breaking user code with the new
behavior introduced by the prior commit.
Fixes #3190.
2019-09-30 20:03:58 +02:00
|
|
|
|
2019-10-01 08:41:56 +02:00
|
|
|
func TestUpdateTarget(t *testing.T) {
|
|
|
|
// Try refreshing a stack with combinations of the above resources as target to destroy.
|
|
|
|
subsets := combinations.All(complexTestDependencyGraphNames)
|
|
|
|
|
|
|
|
for _, subset := range subsets {
|
|
|
|
// limit to up to 3 resources to destroy. This keeps the test running time under
|
|
|
|
// control as it only generates a few hundred combinations instead of several thousand.
|
|
|
|
if len(subset) <= 3 {
|
|
|
|
updateSpecificTargets(t, subset)
|
|
|
|
}
|
|
|
|
}
|
2019-10-28 19:41:43 +01:00
|
|
|
|
2019-11-19 05:28:25 +01:00
|
|
|
updateSpecificTargets(t, []string{"A"})
|
|
|
|
|
2019-10-28 19:41:43 +01:00
|
|
|
// Also update a target that doesn't exist to make sure we don't crash or otherwise go off the rails.
|
|
|
|
updateInvalidTarget(t)
|
2019-10-01 08:41:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func updateSpecificTargets(t *testing.T, targets []string) {
|
|
|
|
// A
|
|
|
|
// _________|_________
|
|
|
|
// B C D
|
|
|
|
// ___|___ ___|___
|
|
|
|
// E F G H I J
|
|
|
|
// |__|
|
|
|
|
// K L
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
urns, old, program := generateComplexTestDependencyGraph(t, p)
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
// all resources will change.
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
|
|
|
|
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
timeout float64, ignoreChanges []string) (resource.PropertyMap, resource.Status, error) {
|
|
|
|
outputs := olds.Copy()
|
|
|
|
|
|
|
|
outputs["output_prop"] = resource.NewPropertyValue(42)
|
|
|
|
return outputs, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
updateTargets := []resource.URN{}
|
|
|
|
for _, target := range targets {
|
|
|
|
updateTargets = append(updateTargets,
|
|
|
|
pickURN(t, urns, complexTestDependencyGraphNames, target))
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.UpdateTargets = updateTargets
|
|
|
|
t.Logf("Updating targets: %v", updateTargets)
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(j.Entries) > 0)
|
|
|
|
|
|
|
|
updated := make(map[resource.URN]bool)
|
|
|
|
sames := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Step.Op() == deploy.OpUpdate {
|
|
|
|
updated[entry.Step.URN()] = true
|
|
|
|
} else if entry.Step.Op() == deploy.OpSame {
|
|
|
|
sames[entry.Step.URN()] = true
|
|
|
|
} else {
|
|
|
|
assert.FailNowf(t, "", "Got a step that wasn't a same/update: %v", entry.Step.Op())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.UpdateTargets {
|
|
|
|
assert.Contains(t, updated, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.UpdateTargets {
|
|
|
|
assert.NotContains(t, sames, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
Fix a dependency graph bug during DBR. (#3329)
The dependency graph used to determine the set of resources that
depend on a resource being DBR'd is constructured from the list of
resource states present in the old snapshot. However, the dependencies
of resources that are present in both the old snapshot and the current
plan can be different, which in turn can cause the engine to make
incorrect decisions during DBR with respect to which resources need to
be replaced. For example, consider the following program:
```
var resA = new Resource("a", {dbr: "foo"});
var resB = new Resource("b", {dbr: resA.prop});
```
If this program is then changed to:
```
var resB = new Resource("b", {dbr: "<literal value of resA.prop>"});
var resA = new Resource("a", {dbr: "bar"});
```
The engine will first decide to make no changes to "b", as its input
property values have not changed. "b" has changed, however, such that it
no longer has a dependency on "a".
The engine will then decide to DBR "a". In the process, it will
determine that it first needs to delete "b", because the state for "b"
that is used when calculating "a"'s dependents does not reflect the
changes made during the plan.
To fix this issue, we rely on the observation that dependents can only
have been _removed_ from the base dependency graph: for a dependent to
have been added, it would have had to have been registered prior to the
root--a resource it depends on--which is not a valid operation. This
means that any resources that depend on the root must not yet have
been registered, which in turn implies that resources that have already
been registered must not depend on the root. Thus, we ignore these
resources if they are encountered while walking the old dependency graph
to determine the set of dependents.
2019-10-13 02:22:13 +02:00
|
|
|
|
2019-10-28 19:41:43 +01:00
|
|
|
func updateInvalidTarget(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
_, old, program := generateComplexTestDependencyGraph(t, p)
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
// all resources will change.
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
|
|
|
|
UpdateF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
timeout float64, ignoreChanges []string) (resource.PropertyMap, resource.Status, error) {
|
|
|
|
outputs := olds.Copy()
|
|
|
|
|
|
|
|
outputs["output_prop"] = resource.NewPropertyValue(42)
|
|
|
|
return outputs, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p.Options.UpdateTargets = []resource.URN{"foo"}
|
|
|
|
t.Logf("Updating invalid targets: %v", p.Options.UpdateTargets)
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
|
|
|
|
2019-11-19 05:28:25 +01:00
|
|
|
func TestCreateDuringTargetedUpdate_CreateMentionedAsTarget(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host1},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap1 := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Now, create a resource resB. This shouldn't be a problem since resB isn't referenced by anything.
|
|
|
|
program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
|
|
|
|
|
|
|
|
resA := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
resB := p.NewURN("pkgA:m:typA", "resB", "")
|
|
|
|
p.Options.host = host2
|
|
|
|
p.Options.UpdateTargets = []resource.URN{resA, resB}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(j.Entries) > 0)
|
|
|
|
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Step.URN() == resA {
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
} else if entry.Step.URN() == resB {
|
|
|
|
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateDuringTargetedUpdate_UntargetedCreateNotReferenced(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host1},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap1 := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Now, create a resource resB. This shouldn't be a problem since resB isn't referenced by anything.
|
|
|
|
program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resB", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
|
|
|
|
|
|
|
|
resA := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
|
|
|
|
p.Options.host = host2
|
|
|
|
p.Options.UpdateTargets = []resource.URN{resA}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(j.Entries) > 0)
|
|
|
|
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
// everything should be a same op here.
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateDuringTargetedUpdate_UntargetedCreateReferencedByTarget(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host1},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
p.Run(t, nil)
|
|
|
|
|
|
|
|
resA := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
resB := p.NewURN("pkgA:m:typA", "resB", "")
|
|
|
|
|
|
|
|
// Now, create a resource resB. But reference it from A. This will cause a dependency we can't
|
|
|
|
// satisfy.
|
|
|
|
program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true,
|
|
|
|
deploytest.ResourceOptions{
|
|
|
|
Dependencies: []resource.URN{resB},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
|
|
|
|
|
|
|
|
p.Options.host = host2
|
|
|
|
p.Options.UpdateTargets = []resource.URN{resA}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: true,
|
|
|
|
}}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCreateDuringTargetedUpdate_UntargetedCreateReferencedByUntargetedCreate(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program1 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host1 := deploytest.NewPluginHost(nil, nil, program1, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host1},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap1 := p.Run(t, nil)
|
|
|
|
|
|
|
|
resA := p.NewURN("pkgA:m:typA", "resA", "")
|
|
|
|
resB := p.NewURN("pkgA:m:typA", "resB", "")
|
|
|
|
|
|
|
|
// Now, create a resource resB. But reference it from A. This will cause a dependency we can't
|
|
|
|
// satisfy.
|
|
|
|
program2 := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
_, _, _, err := monitor.RegisterResource("pkgA:m:typA", "resB", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resC", true,
|
|
|
|
deploytest.ResourceOptions{
|
|
|
|
Dependencies: []resource.URN{resB},
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, _, _, err = monitor.RegisterResource("pkgA:m:typA", "resA", true)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host2 := deploytest.NewPluginHost(nil, nil, program2, loaders...)
|
|
|
|
|
|
|
|
p.Options.host = host2
|
|
|
|
p.Options.UpdateTargets = []resource.URN{resA}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(j.Entries) > 0)
|
|
|
|
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
assert.Equal(t, deploy.OpSame, entry.Step.Op())
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap1)
|
|
|
|
}
|
|
|
|
|
Fix a dependency graph bug during DBR. (#3329)
The dependency graph used to determine the set of resources that
depend on a resource being DBR'd is constructured from the list of
resource states present in the old snapshot. However, the dependencies
of resources that are present in both the old snapshot and the current
plan can be different, which in turn can cause the engine to make
incorrect decisions during DBR with respect to which resources need to
be replaced. For example, consider the following program:
```
var resA = new Resource("a", {dbr: "foo"});
var resB = new Resource("b", {dbr: resA.prop});
```
If this program is then changed to:
```
var resB = new Resource("b", {dbr: "<literal value of resA.prop>"});
var resA = new Resource("a", {dbr: "bar"});
```
The engine will first decide to make no changes to "b", as its input
property values have not changed. "b" has changed, however, such that it
no longer has a dependency on "a".
The engine will then decide to DBR "a". In the process, it will
determine that it first needs to delete "b", because the state for "b"
that is used when calculating "a"'s dependents does not reflect the
changes made during the plan.
To fix this issue, we rely on the observation that dependents can only
have been _removed_ from the base dependency graph: for a dependent to
have been added, it would have had to have been registered prior to the
root--a resource it depends on--which is not a valid operation. This
means that any resources that depend on the root must not yet have
been registered, which in turn implies that resources that have already
been registered must not depend on the root. Thus, we ignore these
resources if they are encountered while walking the old dependency graph
to determine the set of dependents.
2019-10-13 02:22:13 +02:00
|
|
|
func TestDependencyChangeDBR(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
if !olds["A"].DeepEquals(news["A"]) {
|
|
|
|
return plugin.DiffResult{
|
|
|
|
ReplaceKeys: []resource.PropertyKey{"A"},
|
|
|
|
DeleteBeforeReplace: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
if !olds["B"].DeepEquals(news["B"]) {
|
|
|
|
return plugin.DiffResult{
|
|
|
|
Changes: plugin.DiffSome,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
CreateF: func(urn resource.URN,
|
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
const resType = "pkgA:index:typ"
|
|
|
|
|
|
|
|
inputsA := resource.NewPropertyMapFromMap(map[string]interface{}{"A": "foo"})
|
|
|
|
inputsB := resource.NewPropertyMapFromMap(map[string]interface{}{"A": "foo"})
|
|
|
|
|
|
|
|
var urnA, urnB resource.URN
|
|
|
|
var err error
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
urnA, _, _, err = monitor.RegisterResource(resType, "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputsA,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
inputDepsB := map[resource.PropertyKey][]resource.URN{"A": {urnA}}
|
|
|
|
urnB, _, _, err = monitor.RegisterResource(resType, "resB", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputsB,
|
|
|
|
Dependencies: []resource.URN{urnA},
|
|
|
|
PropertyDeps: inputDepsB,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
inputsA["A"] = resource.NewStringProperty("bar")
|
|
|
|
program = deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
urnB, _, _, err = monitor.RegisterResource(resType, "resB", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputsB,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
urnA, _, _, err = monitor.RegisterResource(resType, "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: inputsA,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p.Steps = []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(j.Entries) > 0)
|
|
|
|
|
|
|
|
resBDeleted, resBSame := false, false
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Step.URN() == urnB {
|
|
|
|
switch entry.Step.Op() {
|
|
|
|
case deploy.OpDelete, deploy.OpDeleteReplaced:
|
|
|
|
resBDeleted = true
|
|
|
|
case deploy.OpSame:
|
|
|
|
resBSame = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.True(t, resBSame)
|
|
|
|
assert.False(t, resBDeleted)
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
2019-10-31 01:16:55 +01:00
|
|
|
|
|
|
|
func TestReplaceSpecificTargets(t *testing.T) {
|
|
|
|
// A
|
|
|
|
// _________|_________
|
|
|
|
// B C D
|
|
|
|
// ___|___ ___|___
|
|
|
|
// E F G H I J
|
|
|
|
// |__|
|
|
|
|
// K L
|
|
|
|
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
urns, old, program := generateComplexTestDependencyGraph(t, p)
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
|
|
|
// No resources will change.
|
|
|
|
return plugin.DiffResult{Changes: plugin.DiffNone}, nil
|
|
|
|
},
|
|
|
|
|
|
|
|
CreateF: func(urn resource.URN,
|
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
getURN := func(name string) resource.URN {
|
|
|
|
return pickURN(t, urns, complexTestDependencyGraphNames, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Options.ReplaceTargets = []resource.URN{
|
|
|
|
getURN("F"),
|
|
|
|
getURN("B"),
|
|
|
|
getURN("G"),
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
ExpectFailure: false,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.True(t, len(j.Entries) > 0)
|
|
|
|
|
|
|
|
replaced := make(map[resource.URN]bool)
|
|
|
|
sames := make(map[resource.URN]bool)
|
|
|
|
for _, entry := range j.Entries {
|
|
|
|
if entry.Step.Op() == deploy.OpReplace {
|
|
|
|
replaced[entry.Step.URN()] = true
|
|
|
|
} else if entry.Step.Op() == deploy.OpSame {
|
|
|
|
sames[entry.Step.URN()] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.ReplaceTargets {
|
|
|
|
assert.Contains(t, replaced, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, target := range p.Options.ReplaceTargets {
|
|
|
|
assert.NotContains(t, sames, target)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, old)
|
|
|
|
}
|
Propagate inputs to outputs during preview. (#3327)
These changes restore a more-correct version of the behavior that was
disabled with #3014. The original implementation of this behavior was
done in the SDKs, which do not have access to the complete inputs for a
resource (in particular, default values filled in by the provider during
`Check` are not exposed to the SDK). This lack of information meant that
the resolved output values could disagree with the typings present in
a provider SDK. Exacerbating this problem was the fact that unknown
values were dropped entirely, causing `undefined` values to appear in
unexpected places.
By doing this in the engine and allowing unknown values to be
represented in a first-class manner in the SDK, we can attack both of
these issues.
Although this behavior is not _strictly_ consistent with respect to the
resource model--in an update, a resource's output properties will come
from its provider and may differ from its input properties--this
behavior was present in the product for a fairly long time without
significant issues. In the future, we may be able to improve the
accuracy of resource outputs during a preview by allowing the provider
to dry-run CRUD operations and return partially-known values where
possible.
These changes also introduce new APIs in the Node and Python SDKs
that work with unknown values in a first-class fashion:
- A new parameter to the `apply` function that indicates that the
callback should be run even if the result of the apply contains
unknown values
- `containsUnknowns` and `isUnknown`, which return true if a value
either contains nested unknown values or is exactly an unknown value
- The `Unknown` type, which represents unknown values
The primary use case for these APIs is to allow nested, properties with
known values to be accessed via the lifted property accessor even when
the containing property is not fully know. A common example of this
pattern is the `metadata.name` property of a Kubernetes `Namespace`
object: while other properties of the `metadata` bag may be unknown,
`name` is often known. These APIs allow `ns.metadata.name` to return a
known value in this case.
In order to avoid exposing downlevel SDKs to unknown values--a change
which could break user code by exposing it to unexpected values--a
language SDK must indicate whether or not it supports first-class
unknown values as part of each `RegisterResourceRequest`.
These changes also allow us to avoid breaking user code with the new
behavior introduced by the prior commit.
Fixes #3190.
2019-11-11 21:09:34 +01:00
|
|
|
|
|
|
|
func TestPreviewInputPropagation(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN,
|
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
preview := true
|
|
|
|
program := deploytest.NewLanguageRuntime(func(_ plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
computed := interface{}(resource.Computed{Element: resource.NewStringProperty("")})
|
|
|
|
if !preview {
|
|
|
|
computed = "alpha"
|
|
|
|
}
|
|
|
|
|
|
|
|
ins := resource.NewPropertyMapFromMap(map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
"baz": map[string]interface{}{
|
|
|
|
"a": 42,
|
|
|
|
"b": computed,
|
|
|
|
},
|
|
|
|
"qux": []interface{}{
|
|
|
|
computed,
|
|
|
|
24,
|
|
|
|
},
|
|
|
|
"zed": computed,
|
|
|
|
})
|
|
|
|
|
|
|
|
_, _, state, err := monitor.RegisterResource("pkgA:m:typA", "resA", true, deploytest.ResourceOptions{
|
|
|
|
Inputs: ins,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
assert.True(t, state.DeepEquals(ins))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
|
|
|
|
project := p.GetProject()
|
|
|
|
|
|
|
|
// Run a preview. The inputs should be propagated to the outputs during the create.
|
|
|
|
preview = true
|
|
|
|
_, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, preview, p.BackendClient, nil)
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// Run an update.
|
|
|
|
preview = false
|
|
|
|
snap, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, preview, p.BackendClient, nil)
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
// Run another preview. The inputs should be propagated to the outputs during the update.
|
|
|
|
preview = true
|
|
|
|
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, preview, p.BackendClient, nil)
|
|
|
|
assert.Nil(t, res)
|
|
|
|
}
|
2019-11-12 23:20:06 +01:00
|
|
|
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
type testResource struct {
|
|
|
|
pulumi.CustomResourceState
|
|
|
|
|
|
|
|
Foo pulumi.StringOutput `pulumi:"foo"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type testResourceArgs struct {
|
|
|
|
Foo string `pulumi:"foo"`
|
|
|
|
Bar string `pulumi:"bar"`
|
|
|
|
Baz string `pulumi:"baz"`
|
|
|
|
Bang string `pulumi:"bang"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type testResourceInputs struct {
|
|
|
|
Foo pulumi.StringInput
|
|
|
|
Bar pulumi.StringInput
|
|
|
|
Baz pulumi.StringInput
|
|
|
|
Bang pulumi.StringInput
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*testResourceInputs) ElementType() reflect.Type {
|
|
|
|
return reflect.TypeOf((*testResourceArgs)(nil))
|
|
|
|
}
|
|
|
|
|
2019-11-12 23:20:06 +01:00
|
|
|
func TestSingleResourceDefaultProviderGolangLifecycle(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN,
|
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
|
|
Project: info.Project,
|
|
|
|
Stack: info.Stack,
|
|
|
|
Parallel: info.Parallel,
|
|
|
|
DryRun: info.DryRun,
|
|
|
|
MonitorAddr: info.MonitorAddress,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var resA testResource
|
|
|
|
err := ctx.RegisterResource("pkgA:m:typA", "resA", &testResourceInputs{
|
|
|
|
Foo: pulumi.String("bar"),
|
|
|
|
}, &resA)
|
2019-11-12 23:20:06 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var resB testResource
|
|
|
|
err = ctx.RegisterResource("pkgA:m:typA", "resB", &testResourceInputs{
|
|
|
|
Baz: resA.Foo.ApplyT(func(v string) string {
|
|
|
|
return v + "bar"
|
|
|
|
}).(pulumi.StringOutput),
|
|
|
|
}, &resB)
|
2019-11-12 23:20:06 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: MakeBasicLifecycleSteps(t, 4),
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
2019-11-19 01:47:19 +01:00
|
|
|
|
2020-03-02 22:59:11 +01:00
|
|
|
// Inspired by transformations_test.go.
|
|
|
|
func TestSingleResourceDefaultProviderGolangTransformations(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN,
|
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
newResource := func(ctx *pulumi.Context, name string, opts ...pulumi.ResourceOption) error {
|
|
|
|
var res testResource
|
|
|
|
return ctx.RegisterResource("pkgA:m:typA", name, &testResourceInputs{
|
|
|
|
Foo: pulumi.String("bar"),
|
|
|
|
}, &res, opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
newComponent := func(ctx *pulumi.Context, name string, opts ...pulumi.ResourceOption) error {
|
|
|
|
var res testResource
|
|
|
|
err := ctx.RegisterComponentResource("pkgA:m:typA", name, &res, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var resChild testResource
|
|
|
|
return ctx.RegisterResource("pkgA:m:typA", name+"Child", &testResourceInputs{
|
|
|
|
Foo: pulumi.String("bar"),
|
|
|
|
}, &resChild, pulumi.Parent(&res))
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
|
|
Project: info.Project,
|
|
|
|
Stack: info.Stack,
|
|
|
|
Parallel: info.Parallel,
|
|
|
|
DryRun: info.DryRun,
|
|
|
|
MonitorAddr: info.MonitorAddress,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
|
|
// Scenario #1 - apply a transformation to a CustomResource
|
|
|
|
res1Transformation := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
// TODO[pulumi/pulumi#3846] We should use a mergeOptions-style API here.
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: args.Props,
|
|
|
|
Opts: append(args.Opts, pulumi.AdditionalSecretOutputs([]string{"output"})),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.NoError(t, newResource(ctx, "res1",
|
|
|
|
pulumi.Transformations([]pulumi.ResourceTransformation{res1Transformation})))
|
|
|
|
|
|
|
|
// Scenario #2 - apply a transformation to a Component to transform its children
|
|
|
|
res2Transformation := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
if args.Name == "res2Child" {
|
|
|
|
// TODO[pulumi/pulumi#3846] We should use a mergeOptions-style API here.
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: args.Props,
|
|
|
|
Opts: append(args.Opts, pulumi.AdditionalSecretOutputs([]string{"output", "output2"})),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
assert.NoError(t, newComponent(ctx, "res2",
|
|
|
|
pulumi.Transformations([]pulumi.ResourceTransformation{res2Transformation})))
|
|
|
|
|
|
|
|
// Scenario #3 - apply a transformation to the Stack to transform all (future) resources in the stack
|
|
|
|
res3Transformation := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
// Props might be nil.
|
|
|
|
var props *testResourceInputs
|
|
|
|
if args.Props == nil {
|
|
|
|
props = &testResourceInputs{}
|
|
|
|
} else {
|
|
|
|
props = args.Props.(*testResourceInputs)
|
|
|
|
}
|
|
|
|
props.Foo = pulumi.String("baz")
|
|
|
|
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: props,
|
|
|
|
Opts: args.Opts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert.NoError(t, ctx.RegisterStackTransformation(res3Transformation))
|
|
|
|
assert.NoError(t, newResource(ctx, "res3"))
|
|
|
|
|
|
|
|
// Scenario #4 - transformations are applied in order of decreasing specificity
|
|
|
|
// 1. (not in this example) Child transformation
|
|
|
|
// 2. First parent transformation
|
|
|
|
// 3. Second parent transformation
|
|
|
|
// 4. Stack transformation
|
|
|
|
res4Transformation1 := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
if args.Name == "res4Child" {
|
|
|
|
props := args.Props.(*testResourceInputs)
|
|
|
|
props.Foo = pulumi.String("baz1")
|
|
|
|
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: props,
|
|
|
|
Opts: args.Opts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
res4Transformation2 := func(args *pulumi.ResourceTransformationArgs) *pulumi.ResourceTransformationResult {
|
|
|
|
if args.Name == "res4Child" {
|
|
|
|
props := args.Props.(*testResourceInputs)
|
|
|
|
props.Foo = pulumi.String("baz2")
|
|
|
|
|
|
|
|
return &pulumi.ResourceTransformationResult{
|
|
|
|
Props: props,
|
|
|
|
Opts: args.Opts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
assert.NoError(t, newComponent(ctx, "res4",
|
|
|
|
pulumi.Transformations([]pulumi.ResourceTransformation{res4Transformation1, res4Transformation2})))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
}
|
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
_ []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
foundRes1 := false
|
|
|
|
foundRes2 := false
|
|
|
|
foundRes2Child := false
|
|
|
|
foundRes3 := false
|
|
|
|
foundRes4Child := false
|
|
|
|
// foundRes5Child1 := false
|
|
|
|
for _, res := range j.Snap(target.Snapshot).Resources {
|
|
|
|
// "res1" has a transformation which adds additionalSecretOutputs
|
|
|
|
if res.URN.Name() == "res1" {
|
|
|
|
foundRes1 = true
|
|
|
|
assert.Equal(t, res.Type, tokens.Type("pkgA:m:typA"))
|
|
|
|
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("output"))
|
|
|
|
}
|
|
|
|
// "res2" has a transformation which adds additionalSecretOutputs to it's "child"
|
|
|
|
if res.URN.Name() == "res2" {
|
|
|
|
foundRes2 = true
|
|
|
|
assert.Equal(t, res.Type, tokens.Type("pkgA:m:typA"))
|
|
|
|
assert.NotContains(t, res.AdditionalSecretOutputs, resource.PropertyKey("output"))
|
|
|
|
}
|
|
|
|
if res.URN.Name() == "res2Child" {
|
|
|
|
foundRes2Child = true
|
|
|
|
assert.Equal(t, res.Parent.Name(), tokens.QName("res2"))
|
|
|
|
assert.Equal(t, res.Type, tokens.Type("pkgA:m:typA"))
|
|
|
|
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("output"))
|
|
|
|
assert.Contains(t, res.AdditionalSecretOutputs, resource.PropertyKey("output2"))
|
|
|
|
}
|
|
|
|
// "res3" is impacted by a global stack transformation which sets
|
|
|
|
// Foo to "baz"
|
|
|
|
if res.URN.Name() == "res3" {
|
|
|
|
foundRes3 = true
|
|
|
|
assert.Equal(t, "baz", res.Inputs["foo"].StringValue())
|
|
|
|
assert.Len(t, res.Aliases, 0)
|
|
|
|
}
|
|
|
|
// "res4" is impacted by two component parent transformations which set
|
|
|
|
// Foo to "baz1" and then "baz2" and also a global stack
|
|
|
|
// transformation which sets optionalDefault to "baz". The end
|
|
|
|
// result should be "baz".
|
|
|
|
if res.URN.Name() == "res4Child" {
|
|
|
|
foundRes4Child = true
|
|
|
|
assert.Equal(t, res.Parent.Name(), tokens.QName("res4"))
|
|
|
|
assert.Equal(t, "baz", res.Inputs["foo"].StringValue())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.True(t, foundRes1)
|
|
|
|
assert.True(t, foundRes2)
|
|
|
|
assert.True(t, foundRes2Child)
|
|
|
|
assert.True(t, foundRes3)
|
|
|
|
assert.True(t, foundRes4Child)
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
2019-11-19 01:47:19 +01:00
|
|
|
// This test validates the wiring of the IgnoreChanges prop in the go SDK.
|
|
|
|
// It doesn't attempt to validate underlying behavior.
|
|
|
|
func TestIgnoreChangesGolangLifecycle(t *testing.T) {
|
|
|
|
var expectedIgnoreChanges []string
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN,
|
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
// just verify that the IgnoreChanges prop made it through
|
|
|
|
assert.Equal(t, expectedIgnoreChanges, ignoreChanges)
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
setupAndRunProgram := func(ignoreChanges []string) *deploy.Snapshot {
|
|
|
|
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
|
|
Project: info.Project,
|
|
|
|
Stack: info.Stack,
|
|
|
|
Parallel: info.Parallel,
|
|
|
|
DryRun: info.DryRun,
|
|
|
|
MonitorAddr: info.MonitorAddress,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var res pulumi.CustomResourceState
|
|
|
|
err := ctx.RegisterResource("pkgA:m:typA", "resA", nil, &res, pulumi.IgnoreChanges(ignoreChanges))
|
2019-11-19 01:47:19 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
events []Event, res result.Result) result.Result {
|
|
|
|
for _, event := range events {
|
|
|
|
if event.Type == ResourcePreEvent {
|
|
|
|
payload := event.Payload.(ResourcePreEventPayload)
|
|
|
|
assert.Equal(t, []deploy.StepOp{deploy.OpCreate}, []deploy.StepOp{payload.Metadata.Op})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ignore changes specified
|
|
|
|
ignoreChanges := []string{"b"}
|
|
|
|
setupAndRunProgram(ignoreChanges)
|
|
|
|
|
|
|
|
// ignore changes empty
|
|
|
|
ignoreChanges = []string{}
|
|
|
|
setupAndRunProgram(ignoreChanges)
|
|
|
|
}
|
2019-11-25 23:10:06 +01:00
|
|
|
|
|
|
|
func TestExplicitDeleteBeforeReplaceGoSDK(t *testing.T) {
|
|
|
|
p := &TestPlan{}
|
|
|
|
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
DiffConfigF: func(urn resource.URN, olds, news resource.PropertyMap,
|
|
|
|
ignoreChanges []string) (plugin.DiffResult, error) {
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
if !olds["foo"].DeepEquals(news["foo"]) {
|
2019-11-25 23:10:06 +01:00
|
|
|
return plugin.DiffResult{
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
ReplaceKeys: []resource.PropertyKey{"foo"},
|
2019-11-25 23:10:06 +01:00
|
|
|
DeleteBeforeReplace: true,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
DiffF: func(urn resource.URN, id resource.ID,
|
|
|
|
olds, news resource.PropertyMap, ignoreChanges []string) (plugin.DiffResult, error) {
|
|
|
|
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
if !olds["foo"].DeepEquals(news["foo"]) {
|
|
|
|
return plugin.DiffResult{ReplaceKeys: []resource.PropertyKey{"foo"}}, nil
|
2019-11-25 23:10:06 +01:00
|
|
|
}
|
|
|
|
return plugin.DiffResult{}, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
inputsA := &testResourceInputs{Foo: pulumi.String("foo")}
|
2019-11-25 23:10:06 +01:00
|
|
|
|
|
|
|
dbrValue, dbrA := true, (*bool)(nil)
|
|
|
|
getDbr := func() bool {
|
|
|
|
if dbrA == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return *dbrA
|
|
|
|
}
|
|
|
|
|
|
|
|
var stackURN, provURN, urnA resource.URN = "urn:pulumi:test::test::pulumi:pulumi:Stack::test-test",
|
|
|
|
"urn:pulumi:test::test::pulumi:providers:pkgA::provA", "urn:pulumi:test::test::pkgA:m:typA::resA"
|
|
|
|
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
|
|
Project: info.Project,
|
|
|
|
Stack: info.Stack,
|
|
|
|
Parallel: info.Parallel,
|
|
|
|
DryRun: info.DryRun,
|
|
|
|
MonitorAddr: info.MonitorAddress,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
2020-03-02 22:59:11 +01:00
|
|
|
provider := &pulumi.ProviderResourceState{}
|
|
|
|
err := ctx.RegisterResource(string(providers.MakeProviderType("pkgA")), "provA", nil, provider)
|
2019-11-25 23:10:06 +01:00
|
|
|
assert.NoError(t, err)
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
|
|
|
|
var res pulumi.CustomResourceState
|
|
|
|
err = ctx.RegisterResource("pkgA:m:typA", "resA", inputsA, &res,
|
|
|
|
pulumi.Provider(provider), pulumi.DeleteBeforeReplace(getDbr()))
|
2019-11-25 23:10:06 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
p.Options.host = deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p.Steps = []TestStep{{Op: Update}}
|
|
|
|
snap := p.Run(t, nil)
|
|
|
|
|
|
|
|
// Change the value of resA.A. Should create before replace
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
inputsA.Foo = pulumi.String("bar")
|
2019-11-25 23:10:06 +01:00
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpSame, URN: stackURN},
|
|
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
snap = p.Run(t, snap)
|
|
|
|
|
|
|
|
// Change the registration of resA such that it requires delete-before-replace and change the value of resA.A.
|
|
|
|
// replacement should be delete-before-replace.
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
dbrA, inputsA.Foo = &dbrValue, pulumi.String("baz")
|
2019-11-25 23:10:06 +01:00
|
|
|
p.Steps = []TestStep{{
|
|
|
|
Op: Update,
|
|
|
|
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpSame, URN: stackURN},
|
|
|
|
{Op: deploy.OpSame, URN: provURN},
|
|
|
|
{Op: deploy.OpDeleteReplaced, URN: urnA},
|
|
|
|
{Op: deploy.OpReplace, URN: urnA},
|
|
|
|
{Op: deploy.OpCreateReplacement, URN: urnA},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
p.Run(t, snap)
|
|
|
|
}
|
2019-11-26 00:31:12 +01:00
|
|
|
|
|
|
|
func TestReadResourceGolangLifecycle(t *testing.T) {
|
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
return &deploytest.Provider{
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
assert.Equal(t, resource.ID("someId"), id)
|
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
var stackURN, defaultProviderURN, urnA resource.URN = "urn:pulumi:test::test::pulumi:pulumi:Stack::test-test",
|
|
|
|
"urn:pulumi:test::test::pulumi:providers:pkgA::default", "urn:pulumi:test::test::pkgA:m:typA::resA"
|
|
|
|
|
|
|
|
setupAndRunProgram := func() *deploy.Snapshot {
|
|
|
|
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
|
|
Project: info.Project,
|
|
|
|
Stack: info.Stack,
|
|
|
|
Parallel: info.Parallel,
|
|
|
|
DryRun: info.DryRun,
|
|
|
|
MonitorAddr: info.MonitorAddress,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var res pulumi.CustomResourceState
|
|
|
|
err := ctx.ReadResource("pkgA:m:typA", "resA", pulumi.ID("someId"), nil, &res)
|
2019-11-26 00:31:12 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{
|
|
|
|
{
|
|
|
|
Op: Update,
|
|
|
|
Validate: func(project workspace.Project, target deploy.Target, j *Journal,
|
|
|
|
evts []Event, res result.Result) result.Result {
|
|
|
|
|
|
|
|
assert.Nil(t, res)
|
|
|
|
|
|
|
|
AssertSameSteps(t, []StepSummary{
|
|
|
|
{Op: deploy.OpCreate, URN: stackURN},
|
|
|
|
{Op: deploy.OpCreate, URN: defaultProviderURN},
|
|
|
|
{Op: deploy.OpRead, URN: urnA},
|
|
|
|
}, j.SuccessfulSteps())
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return p.Run(t, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
setupAndRunProgram()
|
|
|
|
}
|
2019-11-26 22:23:34 +01:00
|
|
|
|
|
|
|
// ensures that RegisterResource, ReadResource (TODO https://github.com/pulumi/pulumi/issues/3562),
|
|
|
|
// and Invoke all respect the provider hierarchy
|
|
|
|
// most specific providers are used first 1. resource.provider, 2. resource.providers, 3. resource.parent.providers
|
|
|
|
func TestProviderInheritanceGolangLifecycle(t *testing.T) {
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
type invokeArgs struct {
|
|
|
|
Bang string `pulumi:"bang"`
|
|
|
|
Bar string `pulumi:"bar"`
|
|
|
|
}
|
|
|
|
|
2019-11-26 22:23:34 +01:00
|
|
|
loaders := []*deploytest.ProviderLoader{
|
|
|
|
deploytest.NewProviderLoader("pkgA", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
v := &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN,
|
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
v.InvokeF = func(tok tokens.ModuleMember,
|
|
|
|
inputs resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
|
|
|
assert.True(t, v.Config.DeepEquals(inputs))
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}),
|
|
|
|
deploytest.NewProviderLoader("pkgB", semver.MustParse("1.0.0"), func() (plugin.Provider, error) {
|
|
|
|
v := &deploytest.Provider{
|
|
|
|
CreateF: func(urn resource.URN,
|
|
|
|
news resource.PropertyMap, timeout float64) (resource.ID, resource.PropertyMap, resource.Status, error) {
|
|
|
|
|
|
|
|
return "created-id", news, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
ReadF: func(urn resource.URN, id resource.ID,
|
|
|
|
inputs, state resource.PropertyMap) (plugin.ReadResult, resource.Status, error) {
|
|
|
|
return plugin.ReadResult{Inputs: inputs, Outputs: state}, resource.StatusOK, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
v.InvokeF = func(tok tokens.ModuleMember,
|
|
|
|
inputs resource.PropertyMap) (resource.PropertyMap, []plugin.CheckFailure, error) {
|
|
|
|
assert.True(t, v.Config.DeepEquals(inputs))
|
|
|
|
return nil, nil, nil
|
|
|
|
}
|
|
|
|
return v, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
program := deploytest.NewLanguageRuntime(func(info plugin.RunInfo, monitor *deploytest.ResourceMonitor) error {
|
|
|
|
ctx, err := pulumi.NewContext(context.Background(), pulumi.RunInfo{
|
|
|
|
Project: info.Project,
|
|
|
|
Stack: info.Stack,
|
|
|
|
Parallel: info.Parallel,
|
|
|
|
DryRun: info.DryRun,
|
|
|
|
MonitorAddr: info.MonitorAddress,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return pulumi.RunWithContext(ctx, func(ctx *pulumi.Context) error {
|
|
|
|
// register a couple of providers, pass in some props that we can use to indentify it during invoke
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var providerA pulumi.ProviderResourceState
|
|
|
|
err := ctx.RegisterResource(string(providers.MakeProviderType("pkgA")), "prov1",
|
|
|
|
&testResourceInputs{
|
|
|
|
Foo: pulumi.String("1"),
|
|
|
|
}, &providerA)
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var providerB pulumi.ProviderResourceState
|
|
|
|
err = ctx.RegisterResource(string(providers.MakeProviderType("pkgB")), "prov2",
|
|
|
|
&testResourceInputs{
|
|
|
|
Bar: pulumi.String("2"),
|
|
|
|
Bang: pulumi.String(""),
|
|
|
|
}, &providerB)
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var providerBOverride pulumi.ProviderResourceState
|
|
|
|
err = ctx.RegisterResource(string(providers.MakeProviderType("pkgB")), "prov3",
|
|
|
|
&testResourceInputs{
|
|
|
|
Bar: pulumi.String(""),
|
|
|
|
Bang: pulumi.String("3"),
|
|
|
|
}, &providerBOverride)
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
parentProviders := make(map[string]pulumi.ProviderResource)
|
2020-03-02 22:59:11 +01:00
|
|
|
parentProviders["pkgA"] = &providerA
|
|
|
|
parentProviders["pkgB"] = &providerB
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
// create a parent resource that uses provider map
|
|
|
|
var parentResource pulumi.CustomResourceState
|
|
|
|
err = ctx.RegisterResource("pkgA:m:typA", "resA", nil, &parentResource, pulumi.ProviderMap(parentProviders))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
// parent uses specified provider from map
|
|
|
|
parentResultProvider := parentResource.GetProvider("pkgA:m:typA")
|
2020-03-02 22:59:11 +01:00
|
|
|
assert.Equal(t, &providerA, parentResultProvider)
|
2019-11-26 22:23:34 +01:00
|
|
|
|
|
|
|
// create a child resource
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var childResource pulumi.CustomResourceState
|
2020-03-02 22:59:11 +01:00
|
|
|
err = ctx.RegisterResource("pkgB:m:typB", "resBChild", nil, &childResource, pulumi.Parent(&parentResource))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// child uses provider value from parent
|
|
|
|
childResultProvider := childResource.GetProvider("pkgB:m:typB")
|
2020-03-02 22:59:11 +01:00
|
|
|
assert.Equal(t, &providerB, childResultProvider)
|
2019-11-26 22:23:34 +01:00
|
|
|
|
|
|
|
// create a child with a provider specified
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var childWithOverride pulumi.CustomResourceState
|
|
|
|
err = ctx.RegisterResource("pkgB:m:typB", "resBChildOverride", nil, &childWithOverride,
|
2020-03-02 22:59:11 +01:00
|
|
|
pulumi.Parent(&parentResource), pulumi.Provider(&providerBOverride))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// child uses the specified provider, and not the provider from the parent
|
|
|
|
childWithOverrideProvider := childWithOverride.GetProvider("pkgB:m:typB")
|
2020-03-02 22:59:11 +01:00
|
|
|
assert.Equal(t, &providerBOverride, childWithOverrideProvider)
|
2019-11-26 22:23:34 +01:00
|
|
|
|
|
|
|
// pass in a fake ID
|
|
|
|
testID := pulumi.ID("testID")
|
|
|
|
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
// read a resource that uses provider map
|
|
|
|
err = ctx.ReadResource("pkgA:m:typA", "readResA", testID, nil, &parentResource, pulumi.ProviderMap(parentProviders))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
// parent uses specified provider from map
|
|
|
|
parentResultProvider = parentResource.GetProvider("pkgA:m:typA")
|
2020-03-02 22:59:11 +01:00
|
|
|
assert.Equal(t, &providerA, parentResultProvider)
|
2019-11-26 22:23:34 +01:00
|
|
|
|
|
|
|
// read a child resource
|
2020-03-02 22:59:11 +01:00
|
|
|
err = ctx.ReadResource("pkgB:m:typB", "readResBChild", testID, nil, &childResource, pulumi.Parent(&parentResource))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// child uses provider value from parent
|
|
|
|
childResultProvider = childResource.GetProvider("pkgB:m:typB")
|
2020-03-02 22:59:11 +01:00
|
|
|
assert.Equal(t, &providerB, childResultProvider)
|
2019-11-26 22:23:34 +01:00
|
|
|
|
|
|
|
// read a child with a provider specified
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
err = ctx.ReadResource("pkgB:m:typB", "readResBChildOverride", testID, nil, &childWithOverride,
|
2020-03-02 22:59:11 +01:00
|
|
|
pulumi.Parent(&parentResource), pulumi.Provider(&providerBOverride))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// child uses the specified provider, and not the provider from the parent
|
|
|
|
childWithOverrideProvider = childWithOverride.GetProvider("pkgB:m:typB")
|
2020-03-02 22:59:11 +01:00
|
|
|
assert.Equal(t, &providerBOverride, childWithOverrideProvider)
|
2019-11-26 22:23:34 +01:00
|
|
|
|
|
|
|
// invoke with specific provider
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
var invokeResult struct{}
|
|
|
|
err = ctx.Invoke("pkgB:do:something", invokeArgs{
|
|
|
|
Bang: "3",
|
2020-03-02 22:59:11 +01:00
|
|
|
}, &invokeResult, pulumi.Provider(&providerBOverride))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// invoke with parent
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
err = ctx.Invoke("pkgB:do:something", invokeArgs{
|
|
|
|
Bar: "2",
|
2020-03-02 22:59:11 +01:00
|
|
|
}, &invokeResult, pulumi.Parent(&parentResource))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// invoke with parent and provider
|
Redesign the Go SDK resource/input/output system. (#3506)
The redesign is focused around providing better static typings and
improved ease-of-use for the Go SDK. Most of the redesign revolves
around three pivots:
- Strongly-typed inputs, especially for nested types
- Struct-based resource and invoke APIs
- Ease-of-use of Apply
1. Strongly-typed inputs
Input is the type of a generic input value for a Pulumi resource.
This type is used in conjunction with Output to provide polymorphism
over strongly-typed input values.
The intended pattern for nested Pulumi value types is to define an
input interface and a plain, input, and output variant of the value
type that implement the input interface.
For example, given a nested Pulumi value type with the following shape:
```
type Nested struct {
Foo int
Bar string
}
```
We would define the following:
```
var nestedType = reflect.TypeOf((*Nested)(nil)).Elem()
type NestedInput interface {
pulumi.Input
ToNestedOutput() NestedOutput
ToNestedOutputWithContext(context.Context) NestedOutput
}
type Nested struct {
Foo int `pulumi:"foo"`
Bar string `pulumi:"bar"`
}
type NestedInputValue struct {
Foo pulumi.IntInput `pulumi:"foo"`
Bar pulumi.StringInput `pulumi:"bar"`
}
func (NestedInputValue) ElementType() reflect.Type {
return nestedType
}
func (v NestedInputValue) ToNestedOutput() NestedOutput {
return pulumi.ToOutput(v).(NestedOutput)
}
func (v NestedInputValue) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return pulumi.ToOutputWithContext(ctx, v).(NestedOutput)
}
type NestedOutput struct { *pulumi.OutputState }
func (NestedOutput) ElementType() reflect.Type {
return nestedType
}
func (o NestedOutput) ToNestedOutput() NestedOutput {
return o
}
func (o NestedOutput) ToNestedOutputWithContext(ctx context.Context) NestedOutput {
return o
}
func (o NestedOutput) Foo() pulumi.IntOutput {
return o.Apply(func (v Nested) int {
return v.Foo
}).(pulumi.IntOutput)
}
func (o NestedOutput) Bar() pulumi.StringOutput {
return o.Apply(func (v Nested) string {
return v.Bar
}).(pulumi.StringOutput)
}
```
The SDK provides input and output types for primitives, arrays, and
maps.
2. Struct-based APIs
Instead of providing expected output properties in the input map passed
to {Read,Register}Resource and returning the outputs as a map, the user
now passes a pointer to a struct that implements one of the Resource
interfaces and has appropriately typed and tagged fields that represent
its output properties.
For example, given a custom resource with an int-typed output "foo" and
a string-typed output "bar", we would define the following
CustomResource type:
```
type MyResource struct {
pulumi.CustomResourceState
Foo pulumi.IntOutput `pulumi:"foo"`
Bar pulumi.StringOutput `pulumi:"bar"`
}
```
And invoke RegisterResource like so:
```
var resource MyResource
err := ctx.RegisterResource(tok, name, props, &resource, opts...)
```
Invoke arguments and results are also provided via structs, but use
plain-old Go types for their fields:
```
type MyInvokeArgs struct {
Foo int `pulumi:"foo"`
}
type MyInvokeResult struct {
Bar string `pulumi:"bar"`
}
var result MyInvokeResult
err := ctx.Invoke(tok, MyInvokeArgs{Foo: 42}, &result, opts...)
```
3. Ease-of-use of Apply
All `Apply` methods now accept an interface{} as the callback type.
The provided callback value must have one of the following signatures:
func (v T) U
func (v T) (U, error)
func (ctx context.Context, v T) U
func (ctx context.Context, v T) (U, error)
T must be assignable from the ElementType of the Output. If U is a type
that has a registered Output type, the result of the Apply will be the
corresponding Output type. Otherwise, the result of the Apply will be
AnyOutput.
Fixes https://github.com/pulumi/pulumi/issues/2149.
Fixes https://github.com/pulumi/pulumi/issues/3488.
Fixes https://github.com/pulumi/pulumi/issues/3487.
Fixes https://github.com/pulumi/pulumi-aws/issues/248.
Fixes https://github.com/pulumi/pulumi/issues/3492.
Fixes https://github.com/pulumi/pulumi/issues/3491.
Fixes https://github.com/pulumi/pulumi/issues/3562.
2020-01-18 16:08:37 +01:00
|
|
|
err = ctx.Invoke("pkgB:do:something", invokeArgs{
|
|
|
|
Bang: "3",
|
2020-03-02 22:59:11 +01:00
|
|
|
}, &invokeResult, pulumi.Parent(&parentResource), pulumi.Provider(&providerBOverride))
|
2019-11-26 22:23:34 +01:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
})
|
|
|
|
host := deploytest.NewPluginHost(nil, nil, program, loaders...)
|
|
|
|
|
|
|
|
p := &TestPlan{
|
|
|
|
Options: UpdateOptions{host: host},
|
|
|
|
Steps: []TestStep{{Op: Update}},
|
|
|
|
}
|
|
|
|
p.Run(t, nil)
|
|
|
|
}
|