2018-05-22 19:43:36 +00: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.
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
Overhaul resources, planning, and environments
This change, part of pulumi/lumi#90, overhauls quite a bit of the
core resource, planning, environments, and related areas.
The biggest amount of movement comes from the splitting of pkg/resource
into multiple sub-packages. This results in:
- pkg/resource: just the core resource data structures.
- pkg/resource/deployment: all planning and deployment logic.
- pkg/resource/environment: all environment, configuration, and
serialized checkpoint structures and logic.
- pkg/resource/plugin: all dynamically loaded analyzer and
provider logic, including the actual loading and RPC mechanisms.
This also splits the resource abstraction up. We now have:
- resource.Resource: a shared interface.
- resource.Object: a resource that is connected to a live object
that will periodically observe mutations due to ongoing
evaluation of computations. Snapshots of its state may be
taken; however, this is purely a "pre-planning" abstraction.
- resource.State: a snapshot of a resource's state that is frozen.
In other words, it is no longer connected to a live object.
This is what will store provider outputs (ID and properties),
and is what may be serialized into a deployment record.
The branch is in a half-baked state as of this change; more changes
are to come...
2017-06-08 23:37:40 +00:00
|
|
|
package plugin
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
|
|
|
import (
|
2020-12-23 21:25:48 +00:00
|
|
|
"context"
|
2020-02-21 20:55:27 +00:00
|
|
|
"encoding/json"
|
2023-12-12 12:19:42 +00:00
|
|
|
"errors"
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
"fmt"
|
2019-10-22 07:20:26 +00:00
|
|
|
"io"
|
2020-06-09 23:42:53 +00:00
|
|
|
"os"
|
2022-11-01 15:15:09 +00:00
|
|
|
"strconv"
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
"strings"
|
|
|
|
|
2018-02-06 17:57:32 +00:00
|
|
|
"github.com/blang/semver"
|
2021-06-17 21:46:05 +00:00
|
|
|
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
2018-04-04 17:08:17 +00:00
|
|
|
multierror "github.com/hashicorp/go-multierror"
|
2021-06-17 21:46:05 +00:00
|
|
|
"github.com/opentracing/opentracing-go"
|
2022-11-01 15:15:09 +00:00
|
|
|
"google.golang.org/grpc"
|
2018-03-09 23:43:16 +00:00
|
|
|
"google.golang.org/grpc/codes"
|
2023-01-11 19:54:31 +00:00
|
|
|
"google.golang.org/grpc/credentials/insecure"
|
2024-01-17 09:35:20 +00:00
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
2024-04-25 17:30:30 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
2023-11-15 14:53:12 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/promise"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
Move assets and archives to their own package (#15157)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
This PR is motivated by https://github.com/pulumi/pulumi/pull/15145.
`resource.*` should be built on top of `property.Value`,[^1] which means
that `resource`
needs to be able to import `property.Value`, and so `property` cannot
import
`resource`. Since Assets and Archives are both types of properties, they
must be moved out
of `resource`.
[^1]: For example:
https://github.com/pulumi/pulumi/blob/a1d686227cd7e3c70c51bd772450cb0cd57c1479/sdk/go/common/resource/resource_state.go#L35-L36
## Open Question
This PR moves them to their own sub-folders in `resource`. Should
`asset` and `archive`
live somewhere more high level, like `sdk/go/property/{asset,archive}`?
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-01-25 20:39:31 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/archive"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/asset"
|
2023-06-28 16:02:04 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
2021-08-11 00:44:15 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
|
2022-11-01 15:15:09 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil/rpcerror"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
)
|
|
|
|
|
2019-05-23 20:28:23 +00:00
|
|
|
// The `Type()` for the NodeJS dynamic provider. Logically, this is the same as calling
|
|
|
|
// providers.MakeProviderType(tokens.Package("pulumi-nodejs")), but does not depend on the providers package
|
|
|
|
// (a direct dependency would cause a cyclic import issue.
|
|
|
|
//
|
2019-05-29 18:53:10 +00:00
|
|
|
// This is needed because we have to handle some buggy behavior that previous versions of this provider implemented.
|
2019-05-23 20:28:23 +00:00
|
|
|
const nodejsDynamicProviderType = "pulumi:providers:pulumi-nodejs"
|
|
|
|
|
2019-05-29 18:53:10 +00:00
|
|
|
// The `Type()` for the Kubernetes provider. Logically, this is the same as calling
|
|
|
|
// providers.MakeProviderType(tokens.Package("kubernetes")), but does not depend on the providers package
|
|
|
|
// (a direct dependency would cause a cyclic import issue.
|
|
|
|
//
|
|
|
|
// This is needed because we have to handle some buggy behavior that previous versions of this provider implemented.
|
|
|
|
const kubernetesProviderType = "pulumi:providers:kubernetes"
|
|
|
|
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
// provider reflects a resource plugin, loaded dynamically for a single package.
|
|
|
|
type provider struct {
|
2024-05-31 00:28:48 +00:00
|
|
|
NotForwardCompatibleProvider
|
|
|
|
|
2020-10-09 20:13:55 +00:00
|
|
|
ctx *Context // a plugin context for caching, etc.
|
|
|
|
pkg tokens.Package // the Pulumi package containing this provider's resources.
|
|
|
|
plug *plugin // the actual plugin process wrapper.
|
|
|
|
clientRaw pulumirpc.ResourceProviderClient // the raw provider client; usually unsafe to use directly.
|
|
|
|
disableProviderPreview bool // true if previews for Create and Update are disabled.
|
2021-08-11 00:44:15 +00:00
|
|
|
legacyPreview bool // enables legacy behavior for unconfigured provider previews.
|
2023-01-25 19:54:31 +00:00
|
|
|
|
2023-11-15 14:53:12 +00:00
|
|
|
configSource *promise.CompletionSource[pluginConfig] // the source for the provider's configuration.
|
2023-01-25 19:54:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// pluginConfig holds the configuration of the provider
|
|
|
|
// as specified by the Configure call.
|
|
|
|
type pluginConfig struct {
|
|
|
|
known bool // true if all configuration values are known.
|
|
|
|
|
|
|
|
acceptSecrets bool // true if this plugin accepts strongly-typed secrets.
|
|
|
|
acceptResources bool // true if this plugin accepts strongly-typed resource refs.
|
|
|
|
acceptOutputs bool // true if this plugin accepts output values.
|
|
|
|
supportsPreview bool // true if this plugin supports previews for Create and Update.
|
|
|
|
}
|
|
|
|
|
2024-03-04 21:54:05 +00:00
|
|
|
// Checks PULUMI_DEBUG_PROVIDERS environment variable for any overrides for the provider identified
|
|
|
|
// by pkg. If the user has requested to attach to a live provider, returns the port number from the
|
|
|
|
// env var. For example, `PULUMI_DEBUG_PROVIDERS=aws:12345,gcp:678` will result in 12345 for aws.
|
|
|
|
func GetProviderAttachPort(pkg tokens.Package) (*int, error) {
|
2022-04-19 11:41:18 +00:00
|
|
|
var optAttach string
|
2024-03-04 21:54:05 +00:00
|
|
|
|
2022-04-19 11:41:18 +00:00
|
|
|
if providersEnvVar, has := os.LookupEnv("PULUMI_DEBUG_PROVIDERS"); has {
|
|
|
|
for _, provider := range strings.Split(providersEnvVar, ",") {
|
|
|
|
parts := strings.SplitN(provider, ":", 2)
|
2022-03-17 12:22:56 +00:00
|
|
|
|
2022-04-19 11:41:18 +00:00
|
|
|
if parts[0] == pkg.String() {
|
|
|
|
optAttach = parts[1]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-06-09 23:42:53 +00:00
|
|
|
}
|
|
|
|
|
2024-03-04 21:54:05 +00:00
|
|
|
if optAttach == "" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
port, err := strconv.Atoi(optAttach)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Expected a numeric port, got %s in PULUMI_DEBUG_PROVIDERS: %w",
|
|
|
|
optAttach, err)
|
|
|
|
}
|
|
|
|
return &port, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewProvider attempts to bind to a given package's resource plugin and then creates a gRPC connection to it. If the
|
|
|
|
// plugin could not be found, or an error occurs while creating the child process, an error is returned.
|
|
|
|
func NewProvider(host Host, ctx *Context, pkg tokens.Package, version *semver.Version,
|
|
|
|
options map[string]interface{}, disableProviderPreview bool, jsonConfig string,
|
|
|
|
) (Provider, error) {
|
|
|
|
// See if this is a provider we just want to attach to
|
|
|
|
var plug *plugin
|
|
|
|
|
|
|
|
attachPort, err := GetProviderAttachPort(pkg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-04-19 11:41:18 +00:00
|
|
|
prefix := fmt.Sprintf("%v (resource)", pkg)
|
|
|
|
|
2024-03-04 21:54:05 +00:00
|
|
|
if attachPort != nil {
|
|
|
|
port := *attachPort
|
2022-11-01 15:15:09 +00:00
|
|
|
|
|
|
|
conn, err := dialPlugin(port, pkg.String(), prefix, providerPluginDialOptions(ctx, pkg, ""))
|
2022-04-19 11:41:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Done; store the connection and return the plugin info.
|
|
|
|
plug = &plugin{
|
|
|
|
Conn: conn,
|
|
|
|
// Nothing to kill
|
|
|
|
Kill: func() error { return nil },
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Load the plugin's path by using the standard workspace logic.
|
2023-08-07 12:15:57 +00:00
|
|
|
path, err := workspace.GetPluginPath(ctx.Diag,
|
2024-04-25 17:30:30 +00:00
|
|
|
apitype.ResourcePlugin, strings.ReplaceAll(string(pkg), tokens.QNameDelimiter, "_"),
|
2022-07-22 13:17:43 +00:00
|
|
|
version, host.GetProjectPlugins())
|
2022-04-19 11:41:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(path != "", "unexpected empty path for plugin %s", pkg)
|
2022-04-19 11:41:18 +00:00
|
|
|
|
2022-10-04 08:58:01 +00:00
|
|
|
// Runtime options are passed as environment variables to the provider, this is _currently_ used by
|
|
|
|
// dynamic providers to do things like lookup the virtual environment to use.
|
2022-04-19 11:41:18 +00:00
|
|
|
env := os.Environ()
|
|
|
|
for k, v := range options {
|
|
|
|
env = append(env, fmt.Sprintf("PULUMI_RUNTIME_%s=%v", strings.ToUpper(k), v))
|
|
|
|
}
|
2023-03-31 10:22:50 +00:00
|
|
|
if jsonConfig != "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
env = append(env, "PULUMI_CONFIG="+jsonConfig)
|
2023-03-31 10:22:50 +00:00
|
|
|
}
|
2022-04-19 11:41:18 +00:00
|
|
|
plug, err = newPlugin(ctx, ctx.Pwd, path, prefix,
|
2024-04-25 17:30:30 +00:00
|
|
|
apitype.ResourcePlugin, []string{host.ServerAddr()}, env, providerPluginDialOptions(ctx, pkg, ""))
|
2022-04-19 11:41:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
2022-04-19 11:41:18 +00:00
|
|
|
|
2018-02-06 15:20:04 +00:00
|
|
|
contract.Assertf(plug != nil, "unexpected nil resource plugin for %s", pkg)
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
2021-08-11 00:44:15 +00:00
|
|
|
legacyPreview := cmdutil.IsTruthy(os.Getenv("PULUMI_LEGACY_PROVIDER_PREVIEW"))
|
|
|
|
|
2022-04-19 11:41:18 +00:00
|
|
|
p := &provider{
|
2020-10-22 20:25:05 +00:00
|
|
|
ctx: ctx,
|
|
|
|
pkg: pkg,
|
|
|
|
plug: plug,
|
|
|
|
clientRaw: pulumirpc.NewResourceProviderClient(plug.Conn),
|
|
|
|
disableProviderPreview: disableProviderPreview,
|
2021-08-11 00:44:15 +00:00
|
|
|
legacyPreview: legacyPreview,
|
2023-11-15 14:53:12 +00:00
|
|
|
configSource: &promise.CompletionSource[pluginConfig]{},
|
2022-04-19 11:41:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If we just attached (i.e. plugin bin is nil) we need to call attach
|
|
|
|
if plug.Bin == "" {
|
|
|
|
err := p.Attach(host.ServerAddr())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return p, nil
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2022-11-01 15:15:09 +00:00
|
|
|
func providerPluginDialOptions(ctx *Context, pkg tokens.Package, path string) []grpc.DialOption {
|
|
|
|
dialOpts := append(
|
|
|
|
rpcutil.OpenTracingInterceptorDialOptions(otgrpc.SpanDecorator(decorateProviderSpans)),
|
2023-01-11 19:54:31 +00:00
|
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
2022-11-01 15:15:09 +00:00
|
|
|
rpcutil.GrpcChannelOptions(),
|
|
|
|
)
|
|
|
|
|
|
|
|
if ctx.DialOptions != nil {
|
|
|
|
metadata := map[string]interface{}{
|
|
|
|
"mode": "client",
|
|
|
|
"kind": "resource",
|
|
|
|
}
|
|
|
|
if pkg != "" {
|
|
|
|
metadata["name"] = pkg.String()
|
|
|
|
}
|
|
|
|
if path != "" {
|
|
|
|
metadata["path"] = path
|
|
|
|
}
|
|
|
|
dialOpts = append(dialOpts, ctx.DialOptions(metadata)...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return dialOpts
|
|
|
|
}
|
|
|
|
|
2022-10-09 14:58:33 +00:00
|
|
|
// NewProviderFromPath creates a new provider by loading the plugin binary located at `path`.
|
2022-09-14 14:23:03 +00:00
|
|
|
func NewProviderFromPath(host Host, ctx *Context, path string) (Provider, error) {
|
|
|
|
env := os.Environ()
|
2022-11-01 15:15:09 +00:00
|
|
|
|
2022-09-14 14:23:03 +00:00
|
|
|
plug, err := newPlugin(ctx, ctx.Pwd, path, "",
|
2024-04-25 17:30:30 +00:00
|
|
|
apitype.ResourcePlugin, []string{host.ServerAddr()}, env, providerPluginDialOptions(ctx, "", path))
|
2022-09-14 14:23:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
contract.Assertf(plug != nil, "unexpected nil resource plugin at %q", path)
|
|
|
|
|
|
|
|
legacyPreview := cmdutil.IsTruthy(os.Getenv("PULUMI_LEGACY_PROVIDER_PREVIEW"))
|
|
|
|
|
|
|
|
p := &provider{
|
|
|
|
ctx: ctx,
|
|
|
|
plug: plug,
|
|
|
|
clientRaw: pulumirpc.NewResourceProviderClient(plug.Conn),
|
|
|
|
legacyPreview: legacyPreview,
|
2023-11-15 14:53:12 +00:00
|
|
|
configSource: &promise.CompletionSource[pluginConfig]{},
|
2022-09-14 14:23:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If we just attached (i.e. plugin bin is nil) we need to call attach
|
|
|
|
if plug.Bin == "" {
|
|
|
|
err := p.Attach(host.ServerAddr())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
func NewProviderWithClient(ctx *Context, pkg tokens.Package, client pulumirpc.ResourceProviderClient,
|
2023-03-03 16:36:39 +00:00
|
|
|
disableProviderPreview bool,
|
|
|
|
) Provider {
|
2020-12-23 21:25:48 +00:00
|
|
|
return &provider{
|
|
|
|
ctx: ctx,
|
|
|
|
pkg: pkg,
|
|
|
|
clientRaw: client,
|
|
|
|
disableProviderPreview: disableProviderPreview,
|
2023-11-15 14:53:12 +00:00
|
|
|
configSource: &promise.CompletionSource[pluginConfig]{},
|
2020-12-23 21:25:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
func (p *provider) Pkg() tokens.Package { return p.pkg }
|
|
|
|
|
2017-12-15 15:22:49 +00:00
|
|
|
// label returns a base label for tracing functions.
|
|
|
|
func (p *provider) label() 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 00:50:29 +00:00
|
|
|
return fmt.Sprintf("Provider[%s, %p]", p.pkg, p)
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
func (p *provider) requestContext() context.Context {
|
|
|
|
if p.ctx == nil {
|
|
|
|
return context.Background()
|
|
|
|
}
|
|
|
|
return p.ctx.Request()
|
|
|
|
}
|
|
|
|
|
2019-05-29 18:53:10 +00:00
|
|
|
// isDiffCheckConfigLogicallyUnimplemented returns true when an rpcerror.Error should be treated as if it was an error
|
|
|
|
// due to a rpc being unimplemented. Due to past mistakes, different providers returned "Unimplemented" in a variaity of
|
|
|
|
// different ways that don't always result in an Uimplemented error code.
|
|
|
|
func isDiffCheckConfigLogicallyUnimplemented(err *rpcerror.Error, providerType tokens.Type) bool {
|
|
|
|
switch string(providerType) {
|
|
|
|
// The NodeJS dynamic provider implementation incorrectly returned an empty message instead of properly implementing
|
|
|
|
// Diff/CheckConfig. This gets turned into a error with type: "Internal".
|
|
|
|
case nodejsDynamicProviderType:
|
|
|
|
if err.Code() == codes.Internal {
|
|
|
|
logging.V(8).Infof("treating error %s as unimplemented error", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// The Kubernetes provider returned an "Unimplmeneted" message, but it did so by returning a status from a different
|
|
|
|
// package that the provider was expected. That caused the error to be wrapped with an "Unknown" error.
|
|
|
|
case kubernetesProviderType:
|
|
|
|
if err.Code() == codes.Unknown && strings.Contains(err.Message(), "Unimplemented") {
|
|
|
|
logging.V(8).Infof("treating error %s as unimplemented error", err)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-05-31 00:28:48 +00:00
|
|
|
func (p *provider) Parameterize(ctx context.Context, request ParameterizeRequest) (ParameterizeResponse, error) {
|
Abstract plugin.Parameterize away from the gRPC interface (#16283)
`plugin.Provider` exists to provide a low-level abstraction *on top of*
the gRPC interface. It should not expose the gRPC types directly. The
key diff in this commit is:
```patch
- Parameterize(
- ctx context.Context, req *pulumirpc.ParameterizeRequest,
- ) (*pulumirpc.ParameterizeResponse, error)
+ Parameterize(parameters ParameterizeParameters) (string, *semver.Version, error)
```
```patch
+type ParameterizeParameters interface {
+ isParameterizeParameters()
+}
+
+type (
+ ParameterizeArgs struct {
+ Args []string
+ }
+
+ ParameterizeValue struct {
+ Name string
+ Version *semver.Version
+ // Value must be one of:
+ // - nil
+ // - bool
+ // - int, int32, int64
+ // - uint, uint32, uint64
+ // - float32, float64
+ // - string
+ // - []byte
+ // - map[string]interface{}
+ // - []interface{}
+ Value any
+ }
+)
+
+func (ParameterizeArgs) isParameterizeParameters() {}
+func (ParameterizeValue) isParameterizeParameters() {}
```
This is the new interface exposed in `plugin`. The rest of the PR is
simply complying with the new interface.
While this change is technically breaking (since it was released in
v3.116.0), its a heavily experimental feature and the providers team (my
team) is the biggest consumer of `plugin`. Before this PR,
`Parameterize` was the *only* method that dirrectly exposed the gRPC
interface it was "abstracting".
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
## Checklist
- [X] I have run `make tidy` to update any new dependencies
- [X] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [X] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-05-30 03:17:49 +00:00
|
|
|
var params pulumirpc.ParameterizeRequest
|
2024-05-31 00:28:48 +00:00
|
|
|
switch p := request.Parameters.(type) {
|
Abstract plugin.Parameterize away from the gRPC interface (#16283)
`plugin.Provider` exists to provide a low-level abstraction *on top of*
the gRPC interface. It should not expose the gRPC types directly. The
key diff in this commit is:
```patch
- Parameterize(
- ctx context.Context, req *pulumirpc.ParameterizeRequest,
- ) (*pulumirpc.ParameterizeResponse, error)
+ Parameterize(parameters ParameterizeParameters) (string, *semver.Version, error)
```
```patch
+type ParameterizeParameters interface {
+ isParameterizeParameters()
+}
+
+type (
+ ParameterizeArgs struct {
+ Args []string
+ }
+
+ ParameterizeValue struct {
+ Name string
+ Version *semver.Version
+ // Value must be one of:
+ // - nil
+ // - bool
+ // - int, int32, int64
+ // - uint, uint32, uint64
+ // - float32, float64
+ // - string
+ // - []byte
+ // - map[string]interface{}
+ // - []interface{}
+ Value any
+ }
+)
+
+func (ParameterizeArgs) isParameterizeParameters() {}
+func (ParameterizeValue) isParameterizeParameters() {}
```
This is the new interface exposed in `plugin`. The rest of the PR is
simply complying with the new interface.
While this change is technically breaking (since it was released in
v3.116.0), its a heavily experimental feature and the providers team (my
team) is the biggest consumer of `plugin`. Before this PR,
`Parameterize` was the *only* method that dirrectly exposed the gRPC
interface it was "abstracting".
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
## Checklist
- [X] I have run `make tidy` to update any new dependencies
- [X] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [X] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-05-30 03:17:49 +00:00
|
|
|
case ParameterizeArgs:
|
|
|
|
params.Parameters = &pulumirpc.ParameterizeRequest_Args{
|
|
|
|
Args: &pulumirpc.ParameterizeRequest_ParametersArgs{
|
|
|
|
Args: p.Args,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
case ParameterizeValue:
|
|
|
|
var version string
|
|
|
|
if p.Version != nil {
|
|
|
|
version = p.Version.String()
|
|
|
|
}
|
|
|
|
value, err := structpb.NewValue(p.Value)
|
|
|
|
if err != nil {
|
2024-05-31 00:28:48 +00:00
|
|
|
return ParameterizeResponse{}, err
|
Abstract plugin.Parameterize away from the gRPC interface (#16283)
`plugin.Provider` exists to provide a low-level abstraction *on top of*
the gRPC interface. It should not expose the gRPC types directly. The
key diff in this commit is:
```patch
- Parameterize(
- ctx context.Context, req *pulumirpc.ParameterizeRequest,
- ) (*pulumirpc.ParameterizeResponse, error)
+ Parameterize(parameters ParameterizeParameters) (string, *semver.Version, error)
```
```patch
+type ParameterizeParameters interface {
+ isParameterizeParameters()
+}
+
+type (
+ ParameterizeArgs struct {
+ Args []string
+ }
+
+ ParameterizeValue struct {
+ Name string
+ Version *semver.Version
+ // Value must be one of:
+ // - nil
+ // - bool
+ // - int, int32, int64
+ // - uint, uint32, uint64
+ // - float32, float64
+ // - string
+ // - []byte
+ // - map[string]interface{}
+ // - []interface{}
+ Value any
+ }
+)
+
+func (ParameterizeArgs) isParameterizeParameters() {}
+func (ParameterizeValue) isParameterizeParameters() {}
```
This is the new interface exposed in `plugin`. The rest of the PR is
simply complying with the new interface.
While this change is technically breaking (since it was released in
v3.116.0), its a heavily experimental feature and the providers team (my
team) is the biggest consumer of `plugin`. Before this PR,
`Parameterize` was the *only* method that dirrectly exposed the gRPC
interface it was "abstracting".
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
## Checklist
- [X] I have run `make tidy` to update any new dependencies
- [X] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [X] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-05-30 03:17:49 +00:00
|
|
|
}
|
|
|
|
params.Parameters = &pulumirpc.ParameterizeRequest_Value{
|
|
|
|
Value: &pulumirpc.ParameterizeRequest_ParametersValue{
|
|
|
|
Name: p.Name,
|
|
|
|
Version: version,
|
|
|
|
Value: value,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
case nil:
|
|
|
|
// No args present. That should be Ok.
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Impossible - type is constrained to ParameterizeArgs or ParameterizeValue, found %T", p))
|
|
|
|
}
|
|
|
|
resp, err := p.clientRaw.Parameterize(p.requestContext(), ¶ms)
|
2024-05-15 16:22:39 +00:00
|
|
|
if err != nil {
|
2024-05-31 00:28:48 +00:00
|
|
|
return ParameterizeResponse{}, err
|
Abstract plugin.Parameterize away from the gRPC interface (#16283)
`plugin.Provider` exists to provide a low-level abstraction *on top of*
the gRPC interface. It should not expose the gRPC types directly. The
key diff in this commit is:
```patch
- Parameterize(
- ctx context.Context, req *pulumirpc.ParameterizeRequest,
- ) (*pulumirpc.ParameterizeResponse, error)
+ Parameterize(parameters ParameterizeParameters) (string, *semver.Version, error)
```
```patch
+type ParameterizeParameters interface {
+ isParameterizeParameters()
+}
+
+type (
+ ParameterizeArgs struct {
+ Args []string
+ }
+
+ ParameterizeValue struct {
+ Name string
+ Version *semver.Version
+ // Value must be one of:
+ // - nil
+ // - bool
+ // - int, int32, int64
+ // - uint, uint32, uint64
+ // - float32, float64
+ // - string
+ // - []byte
+ // - map[string]interface{}
+ // - []interface{}
+ Value any
+ }
+)
+
+func (ParameterizeArgs) isParameterizeParameters() {}
+func (ParameterizeValue) isParameterizeParameters() {}
```
This is the new interface exposed in `plugin`. The rest of the PR is
simply complying with the new interface.
While this change is technically breaking (since it was released in
v3.116.0), its a heavily experimental feature and the providers team (my
team) is the biggest consumer of `plugin`. Before this PR,
`Parameterize` was the *only* method that dirrectly exposed the gRPC
interface it was "abstracting".
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
## Checklist
- [X] I have run `make tidy` to update any new dependencies
- [X] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [X] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-05-30 03:17:49 +00:00
|
|
|
}
|
|
|
|
var version *semver.Version
|
|
|
|
if resp.Version != "" {
|
2024-05-31 00:28:48 +00:00
|
|
|
v, err := semver.Parse(resp.Version)
|
Abstract plugin.Parameterize away from the gRPC interface (#16283)
`plugin.Provider` exists to provide a low-level abstraction *on top of*
the gRPC interface. It should not expose the gRPC types directly. The
key diff in this commit is:
```patch
- Parameterize(
- ctx context.Context, req *pulumirpc.ParameterizeRequest,
- ) (*pulumirpc.ParameterizeResponse, error)
+ Parameterize(parameters ParameterizeParameters) (string, *semver.Version, error)
```
```patch
+type ParameterizeParameters interface {
+ isParameterizeParameters()
+}
+
+type (
+ ParameterizeArgs struct {
+ Args []string
+ }
+
+ ParameterizeValue struct {
+ Name string
+ Version *semver.Version
+ // Value must be one of:
+ // - nil
+ // - bool
+ // - int, int32, int64
+ // - uint, uint32, uint64
+ // - float32, float64
+ // - string
+ // - []byte
+ // - map[string]interface{}
+ // - []interface{}
+ Value any
+ }
+)
+
+func (ParameterizeArgs) isParameterizeParameters() {}
+func (ParameterizeValue) isParameterizeParameters() {}
```
This is the new interface exposed in `plugin`. The rest of the PR is
simply complying with the new interface.
While this change is technically breaking (since it was released in
v3.116.0), its a heavily experimental feature and the providers team (my
team) is the biggest consumer of `plugin`. Before this PR,
`Parameterize` was the *only* method that dirrectly exposed the gRPC
interface it was "abstracting".
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
## Checklist
- [X] I have run `make tidy` to update any new dependencies
- [X] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [X] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-05-30 03:17:49 +00:00
|
|
|
if err != nil {
|
2024-05-31 00:28:48 +00:00
|
|
|
return ParameterizeResponse{}, err
|
Abstract plugin.Parameterize away from the gRPC interface (#16283)
`plugin.Provider` exists to provide a low-level abstraction *on top of*
the gRPC interface. It should not expose the gRPC types directly. The
key diff in this commit is:
```patch
- Parameterize(
- ctx context.Context, req *pulumirpc.ParameterizeRequest,
- ) (*pulumirpc.ParameterizeResponse, error)
+ Parameterize(parameters ParameterizeParameters) (string, *semver.Version, error)
```
```patch
+type ParameterizeParameters interface {
+ isParameterizeParameters()
+}
+
+type (
+ ParameterizeArgs struct {
+ Args []string
+ }
+
+ ParameterizeValue struct {
+ Name string
+ Version *semver.Version
+ // Value must be one of:
+ // - nil
+ // - bool
+ // - int, int32, int64
+ // - uint, uint32, uint64
+ // - float32, float64
+ // - string
+ // - []byte
+ // - map[string]interface{}
+ // - []interface{}
+ Value any
+ }
+)
+
+func (ParameterizeArgs) isParameterizeParameters() {}
+func (ParameterizeValue) isParameterizeParameters() {}
```
This is the new interface exposed in `plugin`. The rest of the PR is
simply complying with the new interface.
While this change is technically breaking (since it was released in
v3.116.0), its a heavily experimental feature and the providers team (my
team) is the biggest consumer of `plugin`. Before this PR,
`Parameterize` was the *only* method that dirrectly exposed the gRPC
interface it was "abstracting".
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
## Checklist
- [X] I have run `make tidy` to update any new dependencies
- [X] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [X] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-05-30 03:17:49 +00:00
|
|
|
}
|
|
|
|
version = &v
|
2024-05-15 16:22:39 +00:00
|
|
|
}
|
2024-05-31 00:28:48 +00:00
|
|
|
return ParameterizeResponse{Name: resp.Name, Version: version}, err
|
2024-05-15 16:22:39 +00:00
|
|
|
}
|
|
|
|
|
2020-02-28 00:10:47 +00:00
|
|
|
// GetSchema fetches the schema for this resource provider, if any.
|
2024-05-15 16:22:39 +00:00
|
|
|
func (p *provider) GetSchema(request GetSchemaRequest) ([]byte, error) {
|
|
|
|
var subpackageVersion string
|
|
|
|
if request.SubpackageVersion != nil {
|
|
|
|
subpackageVersion = request.SubpackageVersion.String()
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := p.clientRaw.GetSchema(p.requestContext(), &pulumirpc.GetSchemaRequest{
|
2024-05-15 16:22:39 +00:00
|
|
|
Version: int32(request.Version),
|
|
|
|
SubpackageName: request.SubpackageName,
|
|
|
|
SubpackageVersion: subpackageVersion,
|
2020-02-28 00:10:47 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return []byte(resp.GetSchema()), nil
|
|
|
|
}
|
|
|
|
|
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 00:50:29 +00:00
|
|
|
// CheckConfig validates the configuration for this resource provider.
|
2019-05-20 20:56:27 +00:00
|
|
|
func (p *provider) CheckConfig(urn resource.URN, olds,
|
2023-03-03 16:36:39 +00:00
|
|
|
news resource.PropertyMap, allowUnknowns bool,
|
|
|
|
) (resource.PropertyMap, []CheckFailure, error) {
|
2019-05-23 18:04:06 +00:00
|
|
|
label := fmt.Sprintf("%s.CheckConfig(%s)", p.label(), urn)
|
|
|
|
logging.V(7).Infof("%s executing (#olds=%d,#news=%d)", label, len(olds), len(news))
|
|
|
|
|
|
|
|
molds, err := MarshalProperties(olds, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".olds",
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepUnknowns: allowUnknowns,
|
2019-05-23 18:04:06 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mnews, err := MarshalProperties(news, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".news",
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepUnknowns: allowUnknowns,
|
2019-05-23 18:04:06 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := p.clientRaw.CheckConfig(p.requestContext(), &pulumirpc.CheckRequest{
|
2019-05-23 18:04:06 +00:00
|
|
|
Urn: string(urn),
|
|
|
|
Olds: molds,
|
|
|
|
News: mnews,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
2019-05-23 20:28:23 +00:00
|
|
|
code := rpcError.Code()
|
2019-05-29 18:53:10 +00:00
|
|
|
if code == codes.Unimplemented || isDiffCheckConfigLogicallyUnimplemented(rpcError, urn.Type()) {
|
2019-05-23 18:04:06 +00:00
|
|
|
// For backwards compatibility, just return the news as if the provider was okay with them.
|
2019-05-23 20:28:23 +00:00
|
|
|
logging.V(7).Infof("%s unimplemented rpc: returning news as is", label)
|
|
|
|
return news, nil, nil
|
|
|
|
}
|
2019-05-23 18:04:06 +00:00
|
|
|
logging.V(8).Infof("%s provider received rpc error `%s`: `%s`", label, rpcError.Code(),
|
|
|
|
rpcError.Message())
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal the provider inputs.
|
|
|
|
var inputs resource.PropertyMap
|
|
|
|
if ins := resp.GetInputs(); ins != nil {
|
|
|
|
inputs, err = UnmarshalProperties(ins, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".inputs",
|
2019-05-23 18:04:06 +00:00
|
|
|
KeepUnknowns: allowUnknowns,
|
|
|
|
RejectUnknowns: !allowUnknowns,
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
KeepSecrets: true,
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepResources: true,
|
2019-05-23 18:04:06 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-26 21:19:13 +00:00
|
|
|
// And now any properties that failed verification.
|
2023-06-28 16:02:04 +00:00
|
|
|
failures := slice.Prealloc[CheckFailure](len(resp.GetFailures()))
|
2020-01-26 21:19:13 +00:00
|
|
|
for _, failure := range resp.GetFailures() {
|
|
|
|
failures = append(failures, CheckFailure{resource.PropertyKey(failure.Property), failure.Reason})
|
|
|
|
}
|
|
|
|
|
2019-05-23 18:04:06 +00:00
|
|
|
// Copy over any secret annotations, since we could not pass any to the provider, and return.
|
|
|
|
annotateSecrets(inputs, news)
|
|
|
|
logging.V(7).Infof("%s success: inputs=#%d failures=#%d", label, len(inputs), len(failures))
|
|
|
|
return inputs, failures, nil
|
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 00:50:29 +00:00
|
|
|
}
|
|
|
|
|
2019-07-01 19:34:19 +00:00
|
|
|
func decodeDetailedDiff(resp *pulumirpc.DiffResponse) map[string]PropertyDiff {
|
|
|
|
if !resp.GetHasDetailedDiff() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
detailedDiff := make(map[string]PropertyDiff)
|
|
|
|
for k, v := range resp.GetDetailedDiff() {
|
|
|
|
var d DiffKind
|
|
|
|
switch v.GetKind() {
|
|
|
|
case pulumirpc.PropertyDiff_ADD:
|
|
|
|
d = DiffAdd
|
|
|
|
case pulumirpc.PropertyDiff_ADD_REPLACE:
|
|
|
|
d = DiffAddReplace
|
|
|
|
case pulumirpc.PropertyDiff_DELETE:
|
|
|
|
d = DiffDelete
|
|
|
|
case pulumirpc.PropertyDiff_DELETE_REPLACE:
|
|
|
|
d = DiffDeleteReplace
|
|
|
|
case pulumirpc.PropertyDiff_UPDATE:
|
|
|
|
d = DiffUpdate
|
|
|
|
case pulumirpc.PropertyDiff_UPDATE_REPLACE:
|
|
|
|
d = DiffUpdateReplace
|
|
|
|
default:
|
|
|
|
// Consider unknown diff kinds to be simple updates.
|
|
|
|
d = DiffUpdate
|
|
|
|
}
|
|
|
|
detailedDiff[k] = PropertyDiff{
|
|
|
|
Kind: d,
|
|
|
|
InputDiff: v.GetInputDiff(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return detailedDiff
|
|
|
|
}
|
|
|
|
|
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 00:50:29 +00:00
|
|
|
// DiffConfig checks what impacts a hypothetical change to this provider's configuration will have on the provider.
|
2023-05-29 15:41:36 +00:00
|
|
|
func (p *provider) DiffConfig(urn resource.URN, oldInputs, oldOutputs, newInputs resource.PropertyMap,
|
2023-03-03 16:36:39 +00:00
|
|
|
allowUnknowns bool, ignoreChanges []string,
|
|
|
|
) (DiffResult, error) {
|
2019-05-23 18:04:06 +00:00
|
|
|
label := fmt.Sprintf("%s.DiffConfig(%s)", p.label(), urn)
|
2023-05-29 15:41:36 +00:00
|
|
|
logging.V(7).Infof("%s: executing (#oldInputs=%d#oldOutputs=%d,#newInputs=%d)",
|
|
|
|
label, len(oldInputs), len(oldOutputs), len(newInputs))
|
|
|
|
|
|
|
|
mOldInputs, err := MarshalProperties(oldInputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".oldInputs",
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepUnknowns: true,
|
2019-05-23 18:04:06 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return DiffResult{}, err
|
|
|
|
}
|
|
|
|
|
2023-05-29 15:41:36 +00:00
|
|
|
mOldOutputs, err := MarshalProperties(oldOutputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".oldOutputs",
|
2023-05-29 15:41:36 +00:00
|
|
|
KeepUnknowns: true,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return DiffResult{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mNewInputs, err := MarshalProperties(newInputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".newInputs",
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepUnknowns: true,
|
2019-05-23 18:04:06 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return DiffResult{}, err
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := p.clientRaw.DiffConfig(p.requestContext(), &pulumirpc.DiffRequest{
|
2019-07-31 16:39:07 +00:00
|
|
|
Urn: string(urn),
|
2023-05-29 15:41:36 +00:00
|
|
|
OldInputs: mOldInputs,
|
|
|
|
Olds: mOldOutputs,
|
|
|
|
News: mNewInputs,
|
2019-07-31 16:39:07 +00:00
|
|
|
IgnoreChanges: ignoreChanges,
|
2019-05-23 18:04:06 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
2019-05-23 20:28:23 +00:00
|
|
|
code := rpcError.Code()
|
2019-05-29 18:53:10 +00:00
|
|
|
if code == codes.Unimplemented || isDiffCheckConfigLogicallyUnimplemented(rpcError, urn.Type()) {
|
2019-05-23 18:04:06 +00:00
|
|
|
logging.V(7).Infof("%s unimplemented rpc: returning DiffUnknown with no replaces", label)
|
|
|
|
// In this case, the provider plugin did not implement this and we have to provide some answer:
|
|
|
|
//
|
|
|
|
// There are two interesting scenarios with the present gRPC interface:
|
|
|
|
// 1. Configuration differences in which all properties are known
|
|
|
|
// 2. Configuration differences in which some new property is unknown.
|
|
|
|
//
|
|
|
|
// In both cases, we return a diff result that indicates that the provider _should not_ be replaced.
|
|
|
|
// Although this decision is not conservative--indeed, the conservative decision would be to always require
|
|
|
|
// replacement of a provider if any input has changed--we believe that it results in the best possible user
|
|
|
|
// experience for providers that do not implement DiffConfig functionality. If we took the conservative
|
|
|
|
// route here, any change to a provider's configuration (no matter how inconsequential) would cause all of
|
|
|
|
// its resources to be replaced. This is clearly a bad experience, and differs from how things worked prior
|
|
|
|
// to first-class providers.
|
|
|
|
return DiffResult{Changes: DiffUnknown, ReplaceKeys: nil}, nil
|
|
|
|
}
|
|
|
|
logging.V(8).Infof("%s provider received rpc error `%s`: `%s`", label, rpcError.Code(),
|
|
|
|
rpcError.Message())
|
2023-11-11 09:59:39 +00:00
|
|
|
// https://github.com/pulumi/pulumi/issues/14529: Old versions of kubernetes would error on this
|
|
|
|
// call if "kubeconfig" was set to a file. This didn't cause issues later when the same config was
|
|
|
|
// passed to Configure, and for many years silently "worked".
|
|
|
|
// https://github.com/pulumi/pulumi/pull/14436 fixed this method to start returning errors which
|
|
|
|
// exposed this issue with the kubernetes provider, new versions will be fixed to not error on
|
|
|
|
// this (https://github.com/pulumi/pulumi-kubernetes/issues/2663) but so that the CLI continues to
|
|
|
|
// work for old versions we have an explicit ignore for this one error here.
|
|
|
|
if p.pkg == "kubernetes" &&
|
|
|
|
strings.Contains(rpcError.Error(), "cannot unmarshal string into Go value of type struct") {
|
|
|
|
logging.V(8).Infof("%s ignoring error from kubernetes provider", label)
|
|
|
|
return DiffResult{Changes: DiffUnknown}, nil
|
|
|
|
}
|
|
|
|
|
2023-10-30 09:28:17 +00:00
|
|
|
return DiffResult{}, err
|
2019-05-23 18:04:06 +00:00
|
|
|
}
|
|
|
|
|
2023-06-28 16:02:04 +00:00
|
|
|
replaces := slice.Prealloc[resource.PropertyKey](len(resp.GetReplaces()))
|
2019-05-23 18:04:06 +00:00
|
|
|
for _, replace := range resp.GetReplaces() {
|
|
|
|
replaces = append(replaces, resource.PropertyKey(replace))
|
|
|
|
}
|
2023-06-28 16:02:04 +00:00
|
|
|
stables := slice.Prealloc[resource.PropertyKey](len(resp.GetStables()))
|
2019-05-23 18:04:06 +00:00
|
|
|
for _, stable := range resp.GetStables() {
|
|
|
|
stables = append(stables, resource.PropertyKey(stable))
|
|
|
|
}
|
2023-06-28 16:02:04 +00:00
|
|
|
diffs := slice.Prealloc[resource.PropertyKey](len(resp.GetDiffs()))
|
2019-05-23 18:04:06 +00:00
|
|
|
for _, diff := range resp.GetDiffs() {
|
|
|
|
diffs = append(diffs, resource.PropertyKey(diff))
|
|
|
|
}
|
|
|
|
|
|
|
|
changes := resp.GetChanges()
|
|
|
|
deleteBeforeReplace := resp.GetDeleteBeforeReplace()
|
|
|
|
logging.V(7).Infof("%s success: changes=%d #replaces=%v #stables=%v delbefrepl=%v, diffs=#%v",
|
|
|
|
label, changes, replaces, stables, deleteBeforeReplace, diffs)
|
|
|
|
|
|
|
|
return DiffResult{
|
|
|
|
Changes: DiffChanges(changes),
|
|
|
|
ReplaceKeys: replaces,
|
|
|
|
StableKeys: stables,
|
|
|
|
ChangedKeys: diffs,
|
2019-07-01 19:34:19 +00:00
|
|
|
DetailedDiff: decodeDetailedDiff(resp),
|
2019-05-23 18:04:06 +00:00
|
|
|
DeleteBeforeReplace: deleteBeforeReplace,
|
|
|
|
}, nil
|
2017-12-15 15:22:49 +00:00
|
|
|
}
|
|
|
|
|
2019-05-15 05:17:28 +00:00
|
|
|
// annotateSecrets copies the "secretness" from the ins to the outs. If there are values with the same keys for the
|
|
|
|
// outs and the ins, if they are both objects, they are transformed recursively. Otherwise, if the value in the ins
|
|
|
|
// contains a secret, the entire out value is marked as a secret. This is very close to how we project secrets
|
|
|
|
// in the programming model, with one small difference, which is how we treat the case where both are objects. In the
|
|
|
|
// programming model, we would say the entire output object is a secret. Here, we actually recur in. We do this because
|
|
|
|
// we don't want a single secret value in a rich structure to taint the entire object. Doing so would mean things like
|
|
|
|
// the entire value in the deployment would be encrypted instead of a small chunk. It also means the entire property
|
|
|
|
// would be displayed as `[secret]` in the CLI instead of a small part.
|
|
|
|
//
|
|
|
|
// NOTE: This means that for an array, if any value in the input version is a secret, the entire output array is
|
|
|
|
// marked as a secret. This is actually a very nice result, because often arrays are treated like sets by providers
|
|
|
|
// and the order may not be preserved across an operation. This means we do end up encrypting the entire array
|
|
|
|
// but that's better than accidentally leaking a value which just moved to a different location.
|
|
|
|
func annotateSecrets(outs, ins resource.PropertyMap) {
|
|
|
|
if outs == nil || ins == nil {
|
2019-04-14 04:00:48 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-05-15 05:17:28 +00:00
|
|
|
for key, inValue := range ins {
|
|
|
|
outValue, has := outs[key]
|
2019-05-14 19:56:30 +00:00
|
|
|
if !has {
|
|
|
|
continue
|
|
|
|
}
|
2019-05-15 05:17:28 +00:00
|
|
|
if outValue.IsObject() && inValue.IsObject() {
|
|
|
|
annotateSecrets(outValue.ObjectValue(), inValue.ObjectValue())
|
|
|
|
} else if !outValue.IsSecret() && inValue.ContainsSecrets() {
|
|
|
|
outs[key] = resource.MakeSecret(outValue)
|
2019-04-14 04:00:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-21 20:55:27 +00:00
|
|
|
func removeSecrets(v resource.PropertyValue) interface{} {
|
|
|
|
switch {
|
|
|
|
case v.IsNull():
|
|
|
|
return nil
|
|
|
|
case v.IsBool():
|
|
|
|
return v.BoolValue()
|
|
|
|
case v.IsNumber():
|
|
|
|
return v.NumberValue()
|
|
|
|
case v.IsString():
|
|
|
|
return v.StringValue()
|
|
|
|
case v.IsArray():
|
|
|
|
arr := []interface{}{}
|
|
|
|
for _, v := range v.ArrayValue() {
|
|
|
|
arr = append(arr, removeSecrets(v))
|
|
|
|
}
|
|
|
|
return arr
|
|
|
|
case v.IsAsset():
|
|
|
|
return v.AssetValue()
|
|
|
|
case v.IsArchive():
|
|
|
|
return v.ArchiveValue()
|
|
|
|
case v.IsComputed():
|
|
|
|
return v.Input()
|
|
|
|
case v.IsOutput():
|
|
|
|
return v.OutputValue()
|
|
|
|
case v.IsSecret():
|
|
|
|
return removeSecrets(v.SecretValue().Element)
|
|
|
|
default:
|
|
|
|
contract.Assertf(v.IsObject(), "v is not Object '%v' instead", v.TypeString())
|
|
|
|
obj := map[string]interface{}{}
|
|
|
|
for k, v := range v.ObjectValue() {
|
|
|
|
obj[string(k)] = removeSecrets(v)
|
|
|
|
}
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-06 16:47:01 +00:00
|
|
|
func traverseProperty(element resource.PropertyValue, f func(resource.PropertyValue)) {
|
|
|
|
f(element)
|
|
|
|
if element.IsSecret() {
|
|
|
|
traverseSecret(element.SecretValue(), f)
|
|
|
|
} else if element.IsObject() {
|
|
|
|
traverseMap(element.ObjectValue(), f)
|
|
|
|
} else if element.IsArray() {
|
|
|
|
traverseArray(element.ArrayValue(), f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func traverseArray(elements []resource.PropertyValue, f func(resource.PropertyValue)) {
|
|
|
|
for _, element := range elements {
|
|
|
|
traverseProperty(element, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func traverseSecret(v *resource.Secret, f func(resource.PropertyValue)) {
|
|
|
|
traverseProperty(v.Element, f)
|
|
|
|
}
|
|
|
|
|
|
|
|
func traverseMap(m resource.PropertyMap, f func(resource.PropertyValue)) {
|
|
|
|
for _, value := range m {
|
|
|
|
traverseProperty(value, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// restoreElidedAssetContents is used to restore contents of assets inside resource property maps after
|
|
|
|
// we have skipped serializing contents of assets in order to avoid sending them over the wire to resource
|
|
|
|
// providers. Mainly used in `Read` operations after we receive the live inputs from the resource provider plugin.
|
|
|
|
// Those inputs may echo back the input assets and the engine writes them out to the state. We need to make sure that
|
|
|
|
// we don't write out empty assets to the state, so we restore the asset contents from the original inputs.
|
|
|
|
func restoreElidedAssetContents(original resource.PropertyMap, transformed resource.PropertyMap) {
|
Move assets and archives to their own package (#15157)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
This PR is motivated by https://github.com/pulumi/pulumi/pull/15145.
`resource.*` should be built on top of `property.Value`,[^1] which means
that `resource`
needs to be able to import `property.Value`, and so `property` cannot
import
`resource`. Since Assets and Archives are both types of properties, they
must be moved out
of `resource`.
[^1]: For example:
https://github.com/pulumi/pulumi/blob/a1d686227cd7e3c70c51bd772450cb0cd57c1479/sdk/go/common/resource/resource_state.go#L35-L36
## Open Question
This PR moves them to their own sub-folders in `resource`. Should
`asset` and `archive`
live somewhere more high level, like `sdk/go/property/{asset,archive}`?
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-01-25 20:39:31 +00:00
|
|
|
isEmptyAsset := func(v *asset.Asset) bool {
|
2023-10-06 16:47:01 +00:00
|
|
|
return v.Text == "" && v.Path == "" && v.URI == ""
|
|
|
|
}
|
|
|
|
|
Move assets and archives to their own package (#15157)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
This PR is motivated by https://github.com/pulumi/pulumi/pull/15145.
`resource.*` should be built on top of `property.Value`,[^1] which means
that `resource`
needs to be able to import `property.Value`, and so `property` cannot
import
`resource`. Since Assets and Archives are both types of properties, they
must be moved out
of `resource`.
[^1]: For example:
https://github.com/pulumi/pulumi/blob/a1d686227cd7e3c70c51bd772450cb0cd57c1479/sdk/go/common/resource/resource_state.go#L35-L36
## Open Question
This PR moves them to their own sub-folders in `resource`. Should
`asset` and `archive`
live somewhere more high level, like `sdk/go/property/{asset,archive}`?
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-01-25 20:39:31 +00:00
|
|
|
isEmptyArchive := func(v *archive.Archive) bool {
|
2023-10-06 16:47:01 +00:00
|
|
|
return v.Path == "" && v.URI == "" && v.Assets == nil
|
|
|
|
}
|
|
|
|
|
Move assets and archives to their own package (#15157)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
This PR is motivated by https://github.com/pulumi/pulumi/pull/15145.
`resource.*` should be built on top of `property.Value`,[^1] which means
that `resource`
needs to be able to import `property.Value`, and so `property` cannot
import
`resource`. Since Assets and Archives are both types of properties, they
must be moved out
of `resource`.
[^1]: For example:
https://github.com/pulumi/pulumi/blob/a1d686227cd7e3c70c51bd772450cb0cd57c1479/sdk/go/common/resource/resource_state.go#L35-L36
## Open Question
This PR moves them to their own sub-folders in `resource`. Should
`asset` and `archive`
live somewhere more high level, like `sdk/go/property/{asset,archive}`?
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
## Checklist
- [ ] I have run `make tidy` to update any new dependencies
- [ ] I have run `make lint` to verify my code passes the lint check
- [ ] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-01-25 20:39:31 +00:00
|
|
|
originalAssets := map[string]*asset.Asset{}
|
|
|
|
originalArchives := map[string]*archive.Archive{}
|
2023-10-06 16:47:01 +00:00
|
|
|
|
|
|
|
traverseMap(original, func(value resource.PropertyValue) {
|
|
|
|
if value.IsAsset() {
|
|
|
|
originalAsset := value.AssetValue()
|
|
|
|
originalAssets[originalAsset.Hash] = originalAsset
|
|
|
|
}
|
|
|
|
|
|
|
|
if value.IsArchive() {
|
|
|
|
originalArchive := value.ArchiveValue()
|
|
|
|
originalArchives[originalArchive.Hash] = originalArchive
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
traverseMap(transformed, func(value resource.PropertyValue) {
|
|
|
|
if value.IsAsset() {
|
|
|
|
transformedAsset := value.AssetValue()
|
|
|
|
originalAsset, has := originalAssets[transformedAsset.Hash]
|
|
|
|
if has && isEmptyAsset(transformedAsset) {
|
|
|
|
transformedAsset.Sig = originalAsset.Sig
|
|
|
|
transformedAsset.Text = originalAsset.Text
|
|
|
|
transformedAsset.Path = originalAsset.Path
|
|
|
|
transformedAsset.URI = originalAsset.URI
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if value.IsArchive() {
|
|
|
|
transformedArchive := value.ArchiveValue()
|
|
|
|
originalArchive, has := originalArchives[transformedArchive.Hash]
|
|
|
|
if has && isEmptyArchive(transformedArchive) {
|
|
|
|
transformedArchive.Sig = originalArchive.Sig
|
|
|
|
transformedArchive.URI = originalArchive.URI
|
|
|
|
transformedArchive.Path = originalArchive.Path
|
|
|
|
transformedArchive.Assets = originalArchive.Assets
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-08-31 21:31:33 +00:00
|
|
|
// Configure configures the resource provider with "globals" that control its behavior.
|
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 00:50:29 +00:00
|
|
|
func (p *provider) Configure(inputs resource.PropertyMap) error {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := p.label() + ".Configure()"
|
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 00:50:29 +00:00
|
|
|
logging.V(7).Infof("%s executing (#vars=%d)", label, len(inputs))
|
|
|
|
|
2019-05-17 20:51:28 +00:00
|
|
|
// Convert the inputs to a config map. If any are unknown, do not configure the underlying plugin: instead, leave
|
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 00:50:29 +00:00
|
|
|
// the cfgknown bit unset and carry on.
|
2017-08-31 21:31:33 +00:00
|
|
|
config := make(map[string]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 00:50:29 +00:00
|
|
|
for k, v := range inputs {
|
|
|
|
if k == "version" {
|
|
|
|
continue
|
|
|
|
}
|
2020-02-21 20:55:27 +00:00
|
|
|
|
|
|
|
if v.ContainsUnknowns() {
|
2023-11-15 14:53:12 +00:00
|
|
|
p.configSource.MustFulfill(pluginConfig{
|
2023-01-25 19:54:31 +00:00
|
|
|
known: false,
|
|
|
|
acceptSecrets: false,
|
|
|
|
acceptResources: false,
|
2023-11-15 14:53:12 +00: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 00:50:29 +00:00
|
|
|
return nil
|
|
|
|
}
|
2020-02-21 20:55:27 +00:00
|
|
|
|
|
|
|
mapped := removeSecrets(v)
|
|
|
|
if _, isString := mapped.(string); !isString {
|
|
|
|
marshalled, err := json.Marshal(mapped)
|
|
|
|
if err != nil {
|
2023-12-12 12:19:42 +00:00
|
|
|
err := fmt.Errorf("marshaling configuration property '%v': %w", k, err)
|
2023-11-15 14:53:12 +00:00
|
|
|
p.configSource.MustReject(err)
|
2023-01-25 19:54:31 +00:00
|
|
|
return err
|
2020-02-21 20:55:27 +00:00
|
|
|
}
|
|
|
|
mapped = string(marshalled)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pass the older spelling of a configuration key across the RPC interface, for now, to support
|
|
|
|
// providers which are on the older plan.
|
|
|
|
config[string(p.Pkg())+":config:"+string(k)] = mapped.(string)
|
|
|
|
}
|
|
|
|
|
|
|
|
minputs, err := MarshalProperties(inputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".inputs",
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepUnknowns: true,
|
|
|
|
KeepSecrets: true,
|
|
|
|
KeepResources: true,
|
2020-02-21 20:55:27 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2023-12-12 12:19:42 +00:00
|
|
|
err := fmt.Errorf("marshaling provider inputs: %w", err)
|
2023-11-15 14:53:12 +00:00
|
|
|
p.configSource.MustReject(err)
|
2023-01-25 19:54:31 +00:00
|
|
|
return err
|
2017-08-31 21:31:33 +00:00
|
|
|
}
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
|
|
|
|
// Spawn the configure to happen in parallel. This ensures that we remain responsive elsewhere that might
|
|
|
|
// want to make forward progress, even as the configure call is happening.
|
|
|
|
go func() {
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := p.clientRaw.Configure(p.requestContext(), &pulumirpc.ConfigureRequest{
|
2023-10-13 14:12:26 +00:00
|
|
|
AcceptSecrets: true,
|
|
|
|
AcceptResources: true,
|
|
|
|
SendsOldInputs: true,
|
|
|
|
SendsOldInputsToDelete: true,
|
|
|
|
Variables: config,
|
|
|
|
Args: minputs,
|
2019-04-12 20:25:01 +00:00
|
|
|
})
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s failed: err=%v", label, rpcError.Message())
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
err = createConfigureError(rpcError)
|
2023-11-15 14:53:12 +00:00
|
|
|
p.configSource.MustReject(err)
|
|
|
|
return
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
}
|
2023-01-25 19:54:31 +00:00
|
|
|
|
2023-11-15 14:53:12 +00:00
|
|
|
p.configSource.MustFulfill(pluginConfig{
|
2023-01-25 19:54:31 +00:00
|
|
|
known: true,
|
|
|
|
acceptSecrets: resp.GetAcceptSecrets(),
|
|
|
|
acceptResources: resp.GetAcceptResources(),
|
|
|
|
supportsPreview: resp.GetSupportsPreview(),
|
|
|
|
acceptOutputs: resp.GetAcceptOutputs(),
|
2023-11-15 14:53:12 +00:00
|
|
|
})
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
}()
|
2018-04-04 17:08:17 +00:00
|
|
|
|
2017-08-31 21:31:33 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
// Check validates that the given property bag is valid for a resource of the given type.
|
2017-12-03 00:34:16 +00:00
|
|
|
func (p *provider) Check(urn resource.URN,
|
2022-01-20 11:18:54 +00:00
|
|
|
olds, news resource.PropertyMap,
|
2023-03-03 16:36:39 +00:00
|
|
|
allowUnknowns bool, randomSeed []byte,
|
|
|
|
) (resource.PropertyMap, []CheckFailure, error) {
|
2017-12-15 15:22:49 +00:00
|
|
|
label := fmt.Sprintf("%s.Check(%s)", p.label(), urn)
|
2023-06-05 08:46:25 +00:00
|
|
|
logging.V(7).Infof("%s executing (#olds=%d,#news=%d)", label, len(olds), len(news))
|
2017-12-03 00:34:16 +00:00
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
2017-12-03 00:34:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
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 00:50:29 +00:00
|
|
|
|
|
|
|
// If the configuration for this provider was not fully known--e.g. if we are doing a preview and some input
|
|
|
|
// property was sourced from another resource's output properties--don't call into the underlying provider.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.known {
|
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 00:50:29 +00:00
|
|
|
return news, nil, nil
|
|
|
|
}
|
|
|
|
|
2019-04-12 21:29:08 +00:00
|
|
|
molds, err := MarshalProperties(olds, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".olds",
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepUnknowns: allowUnknowns,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
2019-04-12 21:29:08 +00:00
|
|
|
mnews, err := MarshalProperties(news, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".news",
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepUnknowns: allowUnknowns,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := client.Check(p.requestContext(), &pulumirpc.CheckRequest{
|
2022-08-09 16:40:59 +00:00
|
|
|
Urn: string(urn),
|
|
|
|
Olds: molds,
|
|
|
|
News: mnews,
|
|
|
|
RandomSeed: randomSeed,
|
2017-09-14 23:40:44 +00:00
|
|
|
})
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
if err != nil {
|
2018-03-29 00:07:35 +00:00
|
|
|
rpcError := rpcerror.Convert(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s failed: err=%v", label, rpcError.Message())
|
2018-03-29 00:07:35 +00:00
|
|
|
return nil, nil, rpcError
|
2017-08-01 01:26:15 +00:00
|
|
|
}
|
|
|
|
|
2017-12-03 00:34:16 +00:00
|
|
|
// Unmarshal the provider inputs.
|
|
|
|
var inputs resource.PropertyMap
|
|
|
|
if ins := resp.GetInputs(); ins != nil {
|
2017-12-15 15:22:49 +00:00
|
|
|
inputs, err = UnmarshalProperties(ins, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".inputs",
|
2019-04-12 21:29:08 +00:00
|
|
|
KeepUnknowns: allowUnknowns,
|
|
|
|
RejectUnknowns: !allowUnknowns,
|
|
|
|
KeepSecrets: true,
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepResources: true,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2019-04-14 04:00:48 +00:00
|
|
|
// If we could not pass secrets to the provider, retain the secret bit on any property with the same name. This
|
|
|
|
// allows us to retain metadata about secrets in many cases, even for providers that do not understand secrets
|
|
|
|
// natively.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.acceptSecrets {
|
2019-04-14 04:00:48 +00:00
|
|
|
annotateSecrets(inputs, news)
|
|
|
|
}
|
|
|
|
|
2017-08-01 01:26:15 +00:00
|
|
|
// And now any properties that failed verification.
|
2023-06-28 16:02:04 +00:00
|
|
|
failures := slice.Prealloc[CheckFailure](len(resp.GetFailures()))
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
for _, failure := range resp.GetFailures() {
|
Overhaul resources, planning, and environments
This change, part of pulumi/lumi#90, overhauls quite a bit of the
core resource, planning, environments, and related areas.
The biggest amount of movement comes from the splitting of pkg/resource
into multiple sub-packages. This results in:
- pkg/resource: just the core resource data structures.
- pkg/resource/deployment: all planning and deployment logic.
- pkg/resource/environment: all environment, configuration, and
serialized checkpoint structures and logic.
- pkg/resource/plugin: all dynamically loaded analyzer and
provider logic, including the actual loading and RPC mechanisms.
This also splits the resource abstraction up. We now have:
- resource.Resource: a shared interface.
- resource.Object: a resource that is connected to a live object
that will periodically observe mutations due to ongoing
evaluation of computations. Snapshots of its state may be
taken; however, this is purely a "pre-planning" abstraction.
- resource.State: a snapshot of a resource's state that is frozen.
In other words, it is no longer connected to a live object.
This is what will store provider outputs (ID and properties),
and is what may be serialized into a deployment record.
The branch is in a half-baked state as of this change; more changes
are to come...
2017-06-08 23:37:40 +00:00
|
|
|
failures = append(failures, CheckFailure{resource.PropertyKey(failure.Property), failure.Reason})
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
2017-08-01 01:26:15 +00:00
|
|
|
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s success: inputs=#%d failures=#%d", label, len(inputs), len(failures))
|
2017-12-03 00:34:16 +00:00
|
|
|
return inputs, failures, nil
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2017-08-01 01:26:15 +00:00
|
|
|
// Diff checks what impacts a hypothetical update will have on the resource's properties.
|
2017-08-31 20:10:55 +00:00
|
|
|
func (p *provider) Diff(urn resource.URN, id resource.ID,
|
2023-05-29 15:41:36 +00:00
|
|
|
oldInputs, oldOutputs, newInputs resource.PropertyMap, allowUnknowns bool,
|
2023-03-03 16:36:39 +00:00
|
|
|
ignoreChanges []string,
|
|
|
|
) (DiffResult, error) {
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(urn != "", "Diff requires a URN")
|
|
|
|
contract.Assertf(id != "", "Diff requires an ID")
|
2023-05-29 15:41:36 +00:00
|
|
|
contract.Assertf(oldInputs != nil, "Diff requires old input properties")
|
2023-10-09 18:31:17 +00:00
|
|
|
contract.Assertf(newInputs != nil, "Diff requires new input properties")
|
|
|
|
contract.Assertf(oldOutputs != nil, "Diff requires old output properties")
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
2017-12-15 15:22:49 +00:00
|
|
|
label := fmt.Sprintf("%s.Diff(%s,%s)", p.label(), urn, id)
|
2023-05-29 15:41:36 +00:00
|
|
|
logging.V(7).Infof("%s: executing (#oldInputs=%d#oldOutputs=%d,#newInputs=%d)",
|
|
|
|
label, len(oldInputs), len(oldOutputs), len(newInputs))
|
2017-12-15 15:22:49 +00:00
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
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 00:50:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return DiffResult{}, err
|
|
|
|
}
|
|
|
|
|
2018-11-22 00:53:29 +00:00
|
|
|
// If the configuration for this provider was not fully known--e.g. if we are doing a preview and some input
|
|
|
|
// property was sourced from another resource's output properties--don't call into the underlying provider.
|
|
|
|
// Instead, indicate that the diff is unavailable and write a message
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.known {
|
2018-11-22 00:53:29 +00:00
|
|
|
logging.V(7).Infof("%s: cannot diff due to unknown config", label)
|
|
|
|
const message = "The provider for this resource has inputs that are not known during preview.\n" +
|
|
|
|
"This preview may not correctly represent the changes that will be applied during an update."
|
|
|
|
return DiffResult{}, DiffUnavailable(message)
|
|
|
|
}
|
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 00:50:29 +00:00
|
|
|
|
2023-05-29 15:41:36 +00:00
|
|
|
mOldInputs, err := MarshalProperties(oldInputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".oldInputs",
|
2019-04-12 21:29:08 +00:00
|
|
|
ElideAssetContents: true,
|
|
|
|
KeepUnknowns: allowUnknowns,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return DiffResult{}, err
|
|
|
|
}
|
2023-05-29 15:41:36 +00:00
|
|
|
|
|
|
|
mOldOutputs, err := MarshalProperties(oldOutputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".oldOutputs",
|
2023-05-29 15:41:36 +00:00
|
|
|
ElideAssetContents: true,
|
|
|
|
KeepUnknowns: allowUnknowns,
|
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return DiffResult{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
mNewInputs, err := MarshalProperties(newInputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".newInputs",
|
2023-05-29 15:41:36 +00:00
|
|
|
ElideAssetContents: true,
|
|
|
|
KeepUnknowns: allowUnknowns,
|
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return DiffResult{}, err
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := client.Diff(p.requestContext(), &pulumirpc.DiffRequest{
|
2019-07-31 16:39:07 +00:00
|
|
|
Id: string(id),
|
|
|
|
Urn: string(urn),
|
2023-05-29 15:41:36 +00:00
|
|
|
OldInputs: mOldInputs,
|
|
|
|
Olds: mOldOutputs,
|
|
|
|
News: mNewInputs,
|
2019-07-31 16:39:07 +00:00
|
|
|
IgnoreChanges: ignoreChanges,
|
2017-08-01 01:26:15 +00:00
|
|
|
})
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
if err != nil {
|
2018-03-29 00:07:35 +00:00
|
|
|
rpcError := rpcerror.Convert(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s failed: %v", label, rpcError.Message())
|
2018-03-29 00:07:35 +00:00
|
|
|
return DiffResult{}, rpcError
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2023-06-28 16:02:04 +00:00
|
|
|
// nil is semantically important to a lot of the pulumi system so we only pre-allocate if we have non-zero length.
|
|
|
|
replaces := slice.Prealloc[resource.PropertyKey](len(resp.GetReplaces()))
|
2017-08-01 01:26:15 +00:00
|
|
|
for _, replace := range resp.GetReplaces() {
|
|
|
|
replaces = append(replaces, resource.PropertyKey(replace))
|
|
|
|
}
|
2023-06-28 16:02:04 +00:00
|
|
|
stables := slice.Prealloc[resource.PropertyKey](len(resp.GetStables()))
|
Add a notion of stable properties
This change adds the capability for a resource provider to indicate
that, where an action carried out in response to a diff, a certain set
of properties would be "stable"; that is to say, they are guaranteed
not to change. As a result, properties may be resolved to their final
values during previewing, avoiding erroneous cascading impacts.
This avoids the ever-annoying situation I keep running into when demoing:
when adding or removing an ingress rule to a security group, we ripple
the impact through the instance, and claim it must be replaced, because
that instance depends on the security group via its name. Well, the name
is a great example of a stable property, in that it will never change, and
so this is truly unfortunate and always adds uncertainty into the demos.
Particularly since the actual update doesn't need to perform replacements.
This resolves pulumi/pulumi#330.
2017-10-04 12:22:21 +00:00
|
|
|
for _, stable := range resp.GetStables() {
|
|
|
|
stables = append(stables, resource.PropertyKey(stable))
|
|
|
|
}
|
2023-06-28 16:02:04 +00:00
|
|
|
diffs := slice.Prealloc[resource.PropertyKey](len(resp.GetDiffs()))
|
2019-03-07 00:41:19 +00:00
|
|
|
for _, diff := range resp.GetDiffs() {
|
|
|
|
diffs = append(diffs, resource.PropertyKey(diff))
|
|
|
|
}
|
|
|
|
|
2018-04-05 14:00:16 +00:00
|
|
|
changes := resp.GetChanges()
|
|
|
|
deleteBeforeReplace := resp.GetDeleteBeforeReplace()
|
2019-07-12 18:12:01 +00:00
|
|
|
logging.V(7).Infof("%s success: changes=%d #replaces=%v #stables=%v delbefrepl=%v, diffs=#%v, detaileddiff=%v",
|
|
|
|
label, changes, replaces, stables, deleteBeforeReplace, diffs, resp.GetDetailedDiff())
|
2019-03-07 00:41:19 +00:00
|
|
|
|
Add a notion of stable properties
This change adds the capability for a resource provider to indicate
that, where an action carried out in response to a diff, a certain set
of properties would be "stable"; that is to say, they are guaranteed
not to change. As a result, properties may be resolved to their final
values during previewing, avoiding erroneous cascading impacts.
This avoids the ever-annoying situation I keep running into when demoing:
when adding or removing an ingress rule to a security group, we ripple
the impact through the instance, and claim it must be replaced, because
that instance depends on the security group via its name. Well, the name
is a great example of a stable property, in that it will never change, and
so this is truly unfortunate and always adds uncertainty into the demos.
Particularly since the actual update doesn't need to perform replacements.
This resolves pulumi/pulumi#330.
2017-10-04 12:22:21 +00:00
|
|
|
return DiffResult{
|
2018-04-05 14:00:16 +00:00
|
|
|
Changes: DiffChanges(changes),
|
2017-12-10 16:37:22 +00:00
|
|
|
ReplaceKeys: replaces,
|
|
|
|
StableKeys: stables,
|
2019-03-07 00:41:19 +00:00
|
|
|
ChangedKeys: diffs,
|
2019-07-01 19:34:19 +00:00
|
|
|
DetailedDiff: decodeDetailedDiff(resp),
|
2018-04-05 14:00:16 +00:00
|
|
|
DeleteBeforeReplace: deleteBeforeReplace,
|
Add a notion of stable properties
This change adds the capability for a resource provider to indicate
that, where an action carried out in response to a diff, a certain set
of properties would be "stable"; that is to say, they are guaranteed
not to change. As a result, properties may be resolved to their final
values during previewing, avoiding erroneous cascading impacts.
This avoids the ever-annoying situation I keep running into when demoing:
when adding or removing an ingress rule to a security group, we ripple
the impact through the instance, and claim it must be replaced, because
that instance depends on the security group via its name. Well, the name
is a great example of a stable property, in that it will never change, and
so this is truly unfortunate and always adds uncertainty into the demos.
Particularly since the actual update doesn't need to perform replacements.
This resolves pulumi/pulumi#330.
2017-10-04 12:22:21 +00:00
|
|
|
}, nil
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2017-07-18 01:44:45 +00:00
|
|
|
// Create allocates a new instance of the provided resource and assigns its unique resource.ID and outputs afterwards.
|
2020-10-09 20:13:55 +00:00
|
|
|
func (p *provider) Create(urn resource.URN, props resource.PropertyMap, timeout float64, preview bool) (resource.ID,
|
2023-03-03 16:36:39 +00:00
|
|
|
resource.PropertyMap, resource.Status, error,
|
|
|
|
) {
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(urn != "", "Create requires a URN")
|
|
|
|
contract.Assertf(props != nil, "Create requires properties")
|
2017-09-14 23:40:44 +00:00
|
|
|
|
2017-12-15 15:22:49 +00:00
|
|
|
label := fmt.Sprintf("%s.Create(%s)", p.label(), urn)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s executing (#props=%v)", label, len(props))
|
2017-12-15 15:22:49 +00:00
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", nil, resource.StatusOK, err
|
|
|
|
}
|
|
|
|
|
2020-10-09 20:13:55 +00:00
|
|
|
// If this is a preview and the plugin does not support provider previews, or if the configuration for the provider
|
2021-08-11 00:44:15 +00:00
|
|
|
// is not fully known, hand back an empty property map. This will force the language SDK will to treat all properties
|
|
|
|
// as unknown, which is conservatively correct.
|
2020-10-09 20:13:55 +00:00
|
|
|
//
|
2021-08-11 00:44:15 +00:00
|
|
|
// If the provider does not support previews, return the inputs as the state. Note that this can cause problems for
|
|
|
|
// the language SDKs if there are input and state properties that share a name but expect differently-shaped values.
|
|
|
|
if preview {
|
|
|
|
// TODO: it would be great to swap the order of these if statements. This would prevent a behavioral change for
|
|
|
|
// providers that do not support provider previews, which will always return the inputs as state regardless of
|
|
|
|
// whether or not the config is known. Unfortunately, we can't, since the `supportsPreview` bit depends on the
|
|
|
|
// result of `Configure`, which we won't call if the `cfgknown` is false. It may be worth fixing this catch-22
|
|
|
|
// by extending the provider gRPC interface with a `SupportsFeature` API similar to the language monitor.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.known {
|
2021-08-11 00:44:15 +00:00
|
|
|
if p.legacyPreview {
|
|
|
|
return "", props, resource.StatusOK, nil
|
|
|
|
}
|
|
|
|
return "", resource.PropertyMap{}, resource.StatusOK, nil
|
|
|
|
}
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.supportsPreview || p.disableProviderPreview {
|
2021-08-11 00:44:15 +00:00
|
|
|
return "", props, resource.StatusOK, nil
|
|
|
|
}
|
2020-10-09 20:13:55 +00: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 00:50:29 +00:00
|
|
|
// We should only be calling {Create,Update,Delete} if the provider is fully configured.
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(pcfg.known, "Create cannot be called if the configuration is unknown")
|
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 00:50:29 +00:00
|
|
|
|
2020-10-09 20:13:55 +00:00
|
|
|
mprops, err := MarshalProperties(props, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".inputs",
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepUnknowns: preview,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2020-10-09 20:13:55 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, resource.StatusOK, err
|
|
|
|
}
|
|
|
|
|
2018-05-02 17:36:55 +00:00
|
|
|
var id resource.ID
|
2024-01-17 09:35:20 +00:00
|
|
|
var liveObject *structpb.Struct
|
2018-07-19 05:52:01 +00:00
|
|
|
var resourceError error
|
2023-03-03 16:36:39 +00:00
|
|
|
resourceStatus := resource.StatusOK
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := client.Create(p.requestContext(), &pulumirpc.CreateRequest{
|
2017-09-14 23:40:44 +00:00
|
|
|
Urn: string(urn),
|
|
|
|
Properties: mprops,
|
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 21:26:28 +00:00
|
|
|
Timeout: timeout,
|
2020-10-09 20:13:55 +00:00
|
|
|
Preview: preview,
|
2017-09-14 23:40:44 +00:00
|
|
|
})
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
if err != nil {
|
2019-03-11 20:50:00 +00:00
|
|
|
resourceStatus, id, liveObject, _, resourceError = parseError(err)
|
2018-07-06 22:17:32 +00:00
|
|
|
logging.V(7).Infof("%s failed: %v", label, resourceError)
|
2018-05-02 17:36:55 +00:00
|
|
|
|
2018-08-07 07:40:43 +00:00
|
|
|
if resourceStatus != resource.StatusPartialFailure {
|
2018-05-02 17:36:55 +00:00
|
|
|
return "", nil, resourceStatus, resourceError
|
|
|
|
}
|
2018-07-06 22:17:32 +00:00
|
|
|
// Else it's a `StatusPartialFailure`.
|
2018-05-02 17:36:55 +00:00
|
|
|
} else {
|
|
|
|
id = resource.ID(resp.GetId())
|
2018-07-06 22:17:32 +00:00
|
|
|
liveObject = resp.GetProperties()
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2020-10-09 20:13:55 +00:00
|
|
|
if id == "" && !preview {
|
2017-07-18 01:44:45 +00:00
|
|
|
return "", nil, resource.StatusUnknown,
|
2023-12-12 12:19:42 +00:00
|
|
|
fmt.Errorf("plugin for package '%v' returned empty resource.ID from create '%v'", p.pkg, urn)
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
2017-09-14 23:40:44 +00:00
|
|
|
|
2018-07-06 22:17:32 +00:00
|
|
|
outs, err := UnmarshalProperties(liveObject, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".outputs",
|
2020-12-23 21:25:48 +00:00
|
|
|
RejectUnknowns: !preview,
|
|
|
|
KeepUnknowns: preview,
|
|
|
|
KeepSecrets: true,
|
|
|
|
KeepResources: true,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
2018-07-06 22:17:32 +00:00
|
|
|
return "", nil, resourceStatus, err
|
2017-09-14 23:40:44 +00:00
|
|
|
}
|
2017-07-18 01:44:45 +00:00
|
|
|
|
2019-04-14 04:00:48 +00:00
|
|
|
// If we could not pass secrets to the provider, retain the secret bit on any property with the same name. This
|
|
|
|
// allows us to retain metadata about secrets in many cases, even for providers that do not understand secrets
|
|
|
|
// natively.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.acceptSecrets {
|
2019-04-14 04:00:48 +00:00
|
|
|
annotateSecrets(outs, props)
|
|
|
|
}
|
|
|
|
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s success: id=%s; #outs=%d", label, id, len(outs))
|
2018-05-02 17:36:55 +00:00
|
|
|
if resourceError == nil {
|
|
|
|
return id, outs, resourceStatus, nil
|
|
|
|
}
|
|
|
|
return id, outs, resourceStatus, resourceError
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2018-04-05 14:00:16 +00:00
|
|
|
// read the current live state associated with a resource. enough state must be include in the inputs to uniquely
|
|
|
|
// identify the resource; this is typically just the resource id, but may also include some properties.
|
2019-03-11 20:50:00 +00:00
|
|
|
func (p *provider) Read(urn resource.URN, id resource.ID,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs, state resource.PropertyMap,
|
|
|
|
) (ReadResult, resource.Status, error) {
|
2022-03-18 17:04:54 +00:00
|
|
|
contract.Assertf(urn != "", "Read URN was empty")
|
|
|
|
contract.Assertf(id != "", "Read ID was empty")
|
2018-04-05 14:00:16 +00:00
|
|
|
|
|
|
|
label := fmt.Sprintf("%s.Read(%s,%s)", p.label(), id, urn)
|
2019-03-11 20:50:00 +00:00
|
|
|
logging.V(7).Infof("%s executing (#inputs=%v, #state=%v)", label, len(inputs), len(state))
|
2018-04-05 14:00:16 +00:00
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
2018-04-05 14:00:16 +00:00
|
|
|
if err != nil {
|
2019-03-11 20:50:00 +00:00
|
|
|
return ReadResult{}, resource.StatusUnknown, err
|
2018-04-05 14:00:16 +00: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 00:50:29 +00:00
|
|
|
// If the provider is not fully configured, return an empty bag.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.known {
|
2019-03-11 20:50:00 +00:00
|
|
|
return ReadResult{
|
|
|
|
Outputs: resource.PropertyMap{},
|
|
|
|
Inputs: resource.PropertyMap{},
|
|
|
|
}, resource.StatusUnknown, nil
|
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 00:50:29 +00:00
|
|
|
}
|
|
|
|
|
2019-03-11 20:50:00 +00:00
|
|
|
// Marshal the resource inputs and state so we can perform the RPC.
|
2024-01-17 09:35:20 +00:00
|
|
|
var minputs *structpb.Struct
|
2019-03-11 20:50:00 +00:00
|
|
|
if inputs != nil {
|
2019-04-12 21:29:08 +00:00
|
|
|
m, err := MarshalProperties(inputs, MarshalOptions{
|
|
|
|
Label: label,
|
|
|
|
ElideAssetContents: true,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2019-03-11 20:50:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return ReadResult{}, resource.StatusUnknown, err
|
|
|
|
}
|
|
|
|
minputs = m
|
|
|
|
}
|
2019-04-12 21:29:08 +00:00
|
|
|
mstate, err := MarshalProperties(state, MarshalOptions{
|
|
|
|
Label: label,
|
|
|
|
ElideAssetContents: true,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
if err != nil {
|
2019-03-11 20:50:00 +00:00
|
|
|
return ReadResult{}, resource.StatusUnknown, err
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
}
|
|
|
|
|
2018-04-05 14:00:16 +00:00
|
|
|
// Now issue the read request over RPC, blocking until it finished.
|
2018-08-07 07:40:43 +00:00
|
|
|
var readID resource.ID
|
2024-01-17 09:35:20 +00:00
|
|
|
var liveObject *structpb.Struct
|
|
|
|
var liveInputs *structpb.Struct
|
2018-08-07 07:40:43 +00:00
|
|
|
var resourceError error
|
2023-03-03 16:36:39 +00:00
|
|
|
resourceStatus := resource.StatusOK
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := client.Read(p.requestContext(), &pulumirpc.ReadRequest{
|
2018-04-05 14:00:16 +00:00
|
|
|
Id: string(id),
|
|
|
|
Urn: string(urn),
|
2019-03-11 20:50:00 +00:00
|
|
|
Properties: mstate,
|
|
|
|
Inputs: minputs,
|
2018-04-05 14:00:16 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2019-03-11 20:50:00 +00:00
|
|
|
resourceStatus, readID, liveObject, liveInputs, resourceError = parseError(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s failed: %v", label, err)
|
2018-08-07 07:40:43 +00:00
|
|
|
|
|
|
|
if resourceStatus != resource.StatusPartialFailure {
|
2019-03-11 20:50:00 +00:00
|
|
|
return ReadResult{}, resourceStatus, resourceError
|
2018-08-07 07:40:43 +00:00
|
|
|
}
|
|
|
|
// Else it's a `StatusPartialFailure`.
|
|
|
|
} else {
|
2018-08-14 04:43:10 +00:00
|
|
|
readID = resource.ID(resp.GetId())
|
2018-08-07 07:40:43 +00:00
|
|
|
liveObject = resp.GetProperties()
|
2019-03-11 20:50:00 +00:00
|
|
|
liveInputs = resp.GetInputs()
|
2018-04-05 14:00:16 +00:00
|
|
|
}
|
|
|
|
|
2018-04-10 19:58:50 +00:00
|
|
|
// If the resource was missing, simply return a nil property map.
|
2018-08-07 07:40:43 +00:00
|
|
|
if string(readID) == "" {
|
2019-03-11 20:50:00 +00:00
|
|
|
return ReadResult{}, resourceStatus, nil
|
2018-04-10 19:58:50 +00:00
|
|
|
}
|
|
|
|
|
2018-04-05 14:00:16 +00:00
|
|
|
// Finally, unmarshal the resulting state properties and return them.
|
2019-03-11 20:50:00 +00:00
|
|
|
newState, err := UnmarshalProperties(liveObject, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".outputs",
|
2019-04-12 21:29:08 +00:00
|
|
|
RejectUnknowns: true,
|
|
|
|
KeepSecrets: true,
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepResources: true,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2018-04-05 14:00:16 +00:00
|
|
|
if err != nil {
|
2019-03-11 20:50:00 +00:00
|
|
|
return ReadResult{}, resourceStatus, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var newInputs resource.PropertyMap
|
|
|
|
if liveInputs != nil {
|
|
|
|
newInputs, err = UnmarshalProperties(liveInputs, MarshalOptions{
|
2019-04-12 21:29:08 +00:00
|
|
|
Label: label + ".inputs",
|
|
|
|
RejectUnknowns: true,
|
|
|
|
KeepSecrets: true,
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepResources: true,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2019-03-11 20:50:00 +00:00
|
|
|
if err != nil {
|
|
|
|
return ReadResult{}, resourceStatus, err
|
|
|
|
}
|
2018-04-05 14:00:16 +00:00
|
|
|
}
|
|
|
|
|
2019-04-14 04:00:48 +00:00
|
|
|
// If we could not pass secrets to the provider, retain the secret bit on any property with the same name. This
|
|
|
|
// allows us to retain metadata about secrets in many cases, even for providers that do not understand secrets
|
|
|
|
// natively.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.acceptSecrets {
|
2019-04-14 04:00:48 +00:00
|
|
|
annotateSecrets(newInputs, inputs)
|
|
|
|
annotateSecrets(newState, state)
|
|
|
|
}
|
|
|
|
|
2023-10-06 16:47:01 +00:00
|
|
|
// make sure any echoed properties restore their original asset contents if they have not changed
|
|
|
|
restoreElidedAssetContents(inputs, newInputs)
|
|
|
|
restoreElidedAssetContents(inputs, newState)
|
|
|
|
|
2019-03-11 20:50:00 +00:00
|
|
|
logging.V(7).Infof("%s success; #outs=%d, #inputs=%d", label, len(newState), len(newInputs))
|
|
|
|
return ReadResult{
|
2019-08-16 18:04:03 +00:00
|
|
|
ID: readID,
|
2019-03-11 20:50:00 +00:00
|
|
|
Outputs: newState,
|
|
|
|
Inputs: newInputs,
|
|
|
|
}, resourceStatus, resourceError
|
2018-04-05 14:00:16 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 21:09:00 +00:00
|
|
|
// Update updates an existing resource with new values.
|
2017-08-31 20:10:55 +00:00
|
|
|
func (p *provider) Update(urn resource.URN, id resource.ID,
|
2023-05-29 15:41:36 +00:00
|
|
|
oldInputs, oldOutputs, newInputs resource.PropertyMap, timeout float64,
|
2023-03-03 16:36:39 +00:00
|
|
|
ignoreChanges []string, preview bool,
|
|
|
|
) (resource.PropertyMap, resource.Status, error) {
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(urn != "", "Update requires a URN")
|
|
|
|
contract.Assertf(id != "", "Update requires an ID")
|
2023-05-29 15:41:36 +00:00
|
|
|
contract.Assertf(oldInputs != nil, "Update requires old inputs")
|
|
|
|
contract.Assertf(oldOutputs != nil, "Update requires old outputs")
|
|
|
|
contract.Assertf(newInputs != nil, "Update requires new properties")
|
2017-09-14 23:40:44 +00:00
|
|
|
|
2017-12-15 15:22:49 +00:00
|
|
|
label := fmt.Sprintf("%s.Update(%s,%s)", p.label(), id, urn)
|
2023-05-29 15:41:36 +00:00
|
|
|
logging.V(7).Infof("%s executing (#oldInputs=%v,#oldOutputs=%v,#newInputs=%v)",
|
|
|
|
label, len(oldInputs), len(oldOutputs), len(newInputs))
|
2017-12-15 15:22:49 +00:00
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
2020-10-09 20:13:55 +00:00
|
|
|
if err != nil {
|
2023-05-29 15:41:36 +00:00
|
|
|
return newInputs, resource.StatusOK, err
|
2020-10-09 20:13:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a preview and the plugin does not support provider previews, or if the configuration for the provider
|
2021-08-11 00:44:15 +00:00
|
|
|
// is not fully known, hand back an empty property map. This will force the language SDK to treat all properties
|
|
|
|
// as unknown, which is conservatively correct.
|
2020-10-09 20:13:55 +00:00
|
|
|
//
|
2021-08-11 00:44:15 +00:00
|
|
|
// If the provider does not support previews, return the inputs as the state. Note that this can cause problems for
|
|
|
|
// the language SDKs if there are input and state properties that share a name but expect differently-shaped values.
|
|
|
|
if preview {
|
|
|
|
// TODO: it would be great to swap the order of these if statements. This would prevent a behavioral change for
|
|
|
|
// providers that do not support provider previews, which will always return the inputs as state regardless of
|
|
|
|
// whether or not the config is known. Unfortunately, we can't, since the `supportsPreview` bit depends on the
|
|
|
|
// result of `Configure`, which we won't call if the `cfgknown` is false. It may be worth fixing this catch-22
|
|
|
|
// by extending the provider gRPC interface with a `SupportsFeature` API similar to the language monitor.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.known {
|
2021-08-11 00:44:15 +00:00
|
|
|
if p.legacyPreview {
|
2023-05-29 15:41:36 +00:00
|
|
|
return newInputs, resource.StatusOK, nil
|
2021-08-11 00:44:15 +00:00
|
|
|
}
|
|
|
|
return resource.PropertyMap{}, resource.StatusOK, nil
|
|
|
|
}
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.supportsPreview || p.disableProviderPreview {
|
2023-05-29 15:41:36 +00:00
|
|
|
return newInputs, resource.StatusOK, nil
|
2021-08-11 00:44:15 +00:00
|
|
|
}
|
2020-10-09 20:13:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// We should only be calling {Create,Update,Delete} if the provider is fully configured.
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(pcfg.known, "Update cannot be called if the configuration is unknown")
|
2020-10-09 20:13:55 +00:00
|
|
|
|
2023-05-29 15:41:36 +00:00
|
|
|
mOldInputs, err := MarshalProperties(oldInputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".oldInputs",
|
2019-04-12 21:29:08 +00:00
|
|
|
ElideAssetContents: true,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, resource.StatusOK, err
|
|
|
|
}
|
2023-05-29 15:41:36 +00:00
|
|
|
mOldOutputs, err := MarshalProperties(oldOutputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".oldOutputs",
|
2023-05-29 15:41:36 +00:00
|
|
|
ElideAssetContents: true,
|
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, resource.StatusOK, err
|
|
|
|
}
|
|
|
|
mNewInputs, err := MarshalProperties(newInputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".newInputs",
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepUnknowns: preview,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, resource.StatusOK, err
|
|
|
|
}
|
|
|
|
|
2024-01-17 09:35:20 +00:00
|
|
|
var liveObject *structpb.Struct
|
2018-07-19 05:52:01 +00:00
|
|
|
var resourceError error
|
2023-03-03 16:36:39 +00:00
|
|
|
resourceStatus := resource.StatusOK
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := client.Update(p.requestContext(), &pulumirpc.UpdateRequest{
|
2019-07-31 16:39:07 +00:00
|
|
|
Id: string(id),
|
|
|
|
Urn: string(urn),
|
2023-05-29 15:41:36 +00:00
|
|
|
Olds: mOldOutputs,
|
|
|
|
News: mNewInputs,
|
2019-07-31 16:39:07 +00:00
|
|
|
Timeout: timeout,
|
|
|
|
IgnoreChanges: ignoreChanges,
|
2020-10-13 19:02:19 +00:00
|
|
|
Preview: preview,
|
2023-05-29 15:41:36 +00:00
|
|
|
OldInputs: mOldInputs,
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
})
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
if err != nil {
|
2019-03-11 20:50:00 +00:00
|
|
|
resourceStatus, _, liveObject, _, resourceError = parseError(err)
|
2018-05-02 17:36:55 +00:00
|
|
|
logging.V(7).Infof("%s failed: %v", label, resourceError)
|
|
|
|
|
2018-08-07 07:40:43 +00:00
|
|
|
if resourceStatus != resource.StatusPartialFailure {
|
2018-05-02 17:36:55 +00:00
|
|
|
return nil, resourceStatus, resourceError
|
|
|
|
}
|
2018-07-06 22:17:32 +00:00
|
|
|
// Else it's a `StatusPartialFailure`.
|
2018-05-02 17:36:55 +00:00
|
|
|
} else {
|
2018-07-06 22:17:32 +00:00
|
|
|
liveObject = resp.GetProperties()
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
2017-09-14 23:40:44 +00:00
|
|
|
|
2018-07-06 22:17:32 +00:00
|
|
|
outs, err := UnmarshalProperties(liveObject, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".outputs",
|
2020-12-23 21:25:48 +00:00
|
|
|
RejectUnknowns: !preview,
|
|
|
|
KeepUnknowns: preview,
|
2019-04-12 21:29:08 +00:00
|
|
|
KeepSecrets: true,
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepResources: true,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
2018-07-06 22:17:32 +00:00
|
|
|
return nil, resourceStatus, err
|
2017-09-14 23:40:44 +00:00
|
|
|
}
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
2019-04-14 04:00:48 +00:00
|
|
|
// If we could not pass secrets to the provider, retain the secret bit on any property with the same name. This
|
|
|
|
// allows us to retain metadata about secrets in many cases, even for providers that do not understand secrets
|
|
|
|
// natively.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.acceptSecrets {
|
2023-05-29 15:41:36 +00:00
|
|
|
annotateSecrets(outs, newInputs)
|
2019-04-14 04:00:48 +00:00
|
|
|
}
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s success; #outs=%d", label, len(outs))
|
2018-05-02 17:36:55 +00:00
|
|
|
if resourceError == nil {
|
|
|
|
return outs, resourceStatus, nil
|
|
|
|
}
|
|
|
|
return outs, resourceStatus, resourceError
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete tears down an existing resource.
|
2023-10-13 14:12:26 +00:00
|
|
|
func (p *provider) Delete(urn resource.URN, id resource.ID, oldInputs, oldOutputs resource.PropertyMap,
|
2023-03-03 16:36:39 +00:00
|
|
|
timeout float64,
|
|
|
|
) (resource.Status, error) {
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(urn != "", "Delete requires a URN")
|
|
|
|
contract.Assertf(id != "", "Delete requires an ID")
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
2017-12-15 15:22:49 +00:00
|
|
|
label := fmt.Sprintf("%s.Delete(%s,%s)", p.label(), urn, id)
|
2023-10-13 14:12:26 +00:00
|
|
|
logging.V(7).Infof("%s executing (#inputs=%d, #outputs=%d)", label, len(oldInputs), len(oldOutputs))
|
2017-12-15 15:22:49 +00:00
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
2023-01-25 00:02:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return resource.StatusOK, err
|
|
|
|
}
|
|
|
|
|
2023-07-25 07:50:06 +00:00
|
|
|
// We should never call delete at preview time, so we should never see unknowns here
|
|
|
|
contract.Assertf(pcfg.known, "Delete cannot be called if the configuration is unknown")
|
|
|
|
|
2023-10-13 14:12:26 +00:00
|
|
|
minputs, err := MarshalProperties(oldInputs, MarshalOptions{
|
|
|
|
Label: label,
|
|
|
|
ElideAssetContents: true,
|
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return resource.StatusOK, err
|
|
|
|
}
|
|
|
|
|
|
|
|
moutputs, err := MarshalProperties(oldOutputs, MarshalOptions{
|
2019-04-12 21:29:08 +00:00
|
|
|
Label: label,
|
|
|
|
ElideAssetContents: true,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return resource.StatusOK, err
|
|
|
|
}
|
|
|
|
|
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 00:50:29 +00:00
|
|
|
// We should only be calling {Create,Update,Delete} if the provider is fully configured.
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(pcfg.known, "Delete cannot be called if the configuration is unknown")
|
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 00:50:29 +00:00
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
if _, err := client.Delete(p.requestContext(), &pulumirpc.DeleteRequest{
|
2017-07-19 14:57:22 +00:00
|
|
|
Id: string(id),
|
2017-08-31 20:10:55 +00:00
|
|
|
Urn: string(urn),
|
2023-10-13 14:12:26 +00:00
|
|
|
Properties: moutputs,
|
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 21:26:28 +00:00
|
|
|
Timeout: timeout,
|
2023-10-13 14:12:26 +00:00
|
|
|
OldInputs: minputs,
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
}); err != nil {
|
2018-03-09 23:43:16 +00:00
|
|
|
resourceStatus, rpcErr := resourceStateAndError(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s failed: %v", label, rpcErr)
|
2018-03-09 23:43:16 +00:00
|
|
|
return resourceStatus, rpcErr
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s success", label)
|
Overhaul resources, planning, and environments
This change, part of pulumi/lumi#90, overhauls quite a bit of the
core resource, planning, environments, and related areas.
The biggest amount of movement comes from the splitting of pkg/resource
into multiple sub-packages. This results in:
- pkg/resource: just the core resource data structures.
- pkg/resource/deployment: all planning and deployment logic.
- pkg/resource/environment: all environment, configuration, and
serialized checkpoint structures and logic.
- pkg/resource/plugin: all dynamically loaded analyzer and
provider logic, including the actual loading and RPC mechanisms.
This also splits the resource abstraction up. We now have:
- resource.Resource: a shared interface.
- resource.Object: a resource that is connected to a live object
that will periodically observe mutations due to ongoing
evaluation of computations. Snapshots of its state may be
taken; however, this is purely a "pre-planning" abstraction.
- resource.State: a snapshot of a resource's state that is frozen.
In other words, it is no longer connected to a live object.
This is what will store provider outputs (ID and properties),
and is what may be serialized into a deployment record.
The branch is in a half-baked state as of this change; more changes
are to come...
2017-06-08 23:37:40 +00:00
|
|
|
return resource.StatusOK, nil
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
// Construct creates a new component resource from the given type, name, parent, options, and inputs, and returns
|
|
|
|
// its URN and outputs.
|
2023-11-20 08:59:00 +00:00
|
|
|
func (p *provider) Construct(info ConstructInfo, typ tokens.Type, name string, parent resource.URN,
|
2023-03-03 16:36:39 +00:00
|
|
|
inputs resource.PropertyMap, options ConstructOptions,
|
|
|
|
) (ConstructResult, error) {
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(typ != "", "Construct requires a type")
|
|
|
|
contract.Assertf(name != "", "Construct requires a name")
|
|
|
|
contract.Assertf(inputs != nil, "Construct requires input properties")
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
|
|
|
|
label := fmt.Sprintf("%s.Construct(%s, %s, %s)", p.label(), typ, name, parent)
|
|
|
|
logging.V(7).Infof("%s executing (#inputs=%v)", label, len(inputs))
|
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return ConstructResult{}, err
|
|
|
|
}
|
|
|
|
|
2023-07-25 07:56:37 +00:00
|
|
|
// If the provider is not fully configured, we need to error. We can't support unknown URNs but if the
|
|
|
|
// provider isn't configured we can't call into it to get the URN.
|
|
|
|
if !pcfg.known {
|
2023-12-12 12:19:42 +00:00
|
|
|
return ConstructResult{}, errors.New("cannot construct components if the provider is configured with unknown values")
|
2023-07-25 07:56:37 +00:00
|
|
|
}
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.acceptSecrets {
|
2023-12-12 12:19:42 +00:00
|
|
|
return ConstructResult{}, errors.New("plugins that can construct components must support secrets")
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal the input properties.
|
|
|
|
minputs, err := MarshalProperties(inputs, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".inputs",
|
2020-11-23 19:15:10 +00:00
|
|
|
KeepUnknowns: true,
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2021-09-15 21:16:00 +00:00
|
|
|
// To initially scope the use of this new feature, we only keep output values for
|
|
|
|
// Construct and Call (when the client accepts them).
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepOutputValues: pcfg.acceptOutputs,
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return ConstructResult{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal the aliases.
|
2022-09-22 17:13:55 +00:00
|
|
|
aliasURNs := make([]string, len(options.Aliases))
|
|
|
|
for i, alias := range options.Aliases {
|
|
|
|
aliasURNs[i] = string(alias.URN)
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal the dependencies.
|
|
|
|
dependencies := make([]string, len(options.Dependencies))
|
|
|
|
for i, dep := range options.Dependencies {
|
|
|
|
dependencies[i] = string(dep)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal the property dependencies.
|
|
|
|
inputDependencies := map[string]*pulumirpc.ConstructRequest_PropertyDependencies{}
|
|
|
|
for name, dependencies := range options.PropertyDependencies {
|
|
|
|
urns := make([]string, len(dependencies))
|
|
|
|
for i, urn := range dependencies {
|
|
|
|
urns[i] = string(urn)
|
|
|
|
}
|
|
|
|
inputDependencies[string(name)] = &pulumirpc.ConstructRequest_PropertyDependencies{Urns: urns}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal the config.
|
|
|
|
config := map[string]string{}
|
|
|
|
for k, v := range info.Config {
|
|
|
|
config[k.String()] = v
|
|
|
|
}
|
2021-06-24 22:38:01 +00:00
|
|
|
configSecretKeys := []string{}
|
|
|
|
for _, k := range info.ConfigSecretKeys {
|
|
|
|
configSecretKeys = append(configSecretKeys, k.String())
|
|
|
|
}
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
|
2023-04-14 21:42:22 +00:00
|
|
|
req := &pulumirpc.ConstructRequest{
|
|
|
|
Project: info.Project,
|
|
|
|
Stack: info.Stack,
|
|
|
|
Config: config,
|
|
|
|
ConfigSecretKeys: configSecretKeys,
|
|
|
|
DryRun: info.DryRun,
|
|
|
|
Parallel: int32(info.Parallel),
|
|
|
|
MonitorEndpoint: info.MonitorAddress,
|
|
|
|
Type: string(typ),
|
2023-11-20 08:59:00 +00:00
|
|
|
Name: name,
|
2023-04-14 21:42:22 +00:00
|
|
|
Parent: string(parent),
|
|
|
|
Inputs: minputs,
|
|
|
|
Protect: options.Protect,
|
|
|
|
Providers: options.Providers,
|
|
|
|
InputDependencies: inputDependencies,
|
|
|
|
Aliases: aliasURNs,
|
|
|
|
Dependencies: dependencies,
|
|
|
|
AdditionalSecretOutputs: options.AdditionalSecretOutputs,
|
|
|
|
DeletedWith: string(options.DeletedWith),
|
|
|
|
DeleteBeforeReplace: options.DeleteBeforeReplace,
|
|
|
|
IgnoreChanges: options.IgnoreChanges,
|
|
|
|
ReplaceOnChanges: options.ReplaceOnChanges,
|
|
|
|
RetainOnDelete: options.RetainOnDelete,
|
2024-02-14 08:15:24 +00:00
|
|
|
AcceptsOutputValues: true,
|
2023-04-14 21:42:22 +00:00
|
|
|
}
|
|
|
|
if ct := options.CustomTimeouts; ct != nil {
|
|
|
|
req.CustomTimeouts = &pulumirpc.ConstructRequest_CustomTimeouts{
|
|
|
|
Create: ct.Create,
|
|
|
|
Update: ct.Update,
|
|
|
|
Delete: ct.Delete,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := client.Construct(p.requestContext(), req)
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return ConstructResult{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
outputs, err := UnmarshalProperties(resp.GetState(), MarshalOptions{
|
2024-02-14 08:15:24 +00:00
|
|
|
Label: label + ".outputs",
|
|
|
|
KeepUnknowns: info.DryRun,
|
|
|
|
KeepSecrets: true,
|
|
|
|
KeepResources: true,
|
|
|
|
KeepOutputValues: true,
|
Initial support for remote component construction. (#5280)
These changes add initial support for the construction of remote
components. For now, this support is limited to the NodeJS SDK;
follow-up changes will implement support for the other SDKs.
Remote components are component resources that are constructed and
managed by plugins rather than by Pulumi programs. In this sense, they
are a bit like cloud resources, and are supported by the same
distribution and plugin loading mechanisms and described by the same
schema system.
The construction of a remote component is initiated by a
`RegisterResourceRequest` with the new `remote` field set to `true`.
When the resource monitor receives such a request, it loads the plugin
that implements the component resource and calls the `Construct`
method added to the resource provider interface as part of these
changes. This method accepts the information necessary to construct the
component and its children: the component's name, type, resource
options, inputs, and input dependencies. It is responsible for
dispatching to the appropriate component factory to create the
component, then returning its URN, resolved output properties, and
output property dependencies. The dependency information is necessary to
support features such as delete-before-replace, which rely on precise
dependency information for custom resources.
These changes also add initial support for more conveniently
implementing resource providers in NodeJS. The interface used to
implement such a provider is similar to the dynamic provider interface
(and may be unified with that interface in the future).
An example of a NodeJS program constructing a remote component resource
also implemented in NodeJS can be found in
`tests/construct_component/nodejs`.
This is the core of #2430.
2020-09-08 02:33:55 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return ConstructResult{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
outputDependencies := map[resource.PropertyKey][]resource.URN{}
|
|
|
|
for k, rpcDeps := range resp.GetStateDependencies() {
|
|
|
|
urns := make([]resource.URN, len(rpcDeps.Urns))
|
|
|
|
for i, d := range rpcDeps.Urns {
|
|
|
|
urns[i] = resource.URN(d)
|
|
|
|
}
|
|
|
|
outputDependencies[resource.PropertyKey(k)] = urns
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.V(7).Infof("%s success: #outputs=%d", label, len(outputs))
|
|
|
|
return ConstructResult{
|
|
|
|
URN: resource.URN(resp.GetUrn()),
|
|
|
|
Outputs: outputs,
|
|
|
|
OutputDependencies: outputDependencies,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-09-20 00:23:10 +00:00
|
|
|
// Invoke dynamically executes a built-in function in the provider.
|
|
|
|
func (p *provider) Invoke(tok tokens.ModuleMember, args resource.PropertyMap) (resource.PropertyMap,
|
2023-03-03 16:36:39 +00:00
|
|
|
[]CheckFailure, error,
|
|
|
|
) {
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(tok != "", "Invoke requires a token")
|
2017-09-20 00:23:10 +00:00
|
|
|
|
2017-12-15 15:22:49 +00:00
|
|
|
label := fmt.Sprintf("%s.Invoke(%s)", p.label(), tok)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s executing (#args=%d)", label, len(args))
|
2017-12-15 15:22:49 +00:00
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
2017-09-20 00:23:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
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 00:50:29 +00:00
|
|
|
// If the provider is not fully configured, return an empty property map.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.known {
|
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 00:50:29 +00:00
|
|
|
return resource.PropertyMap{}, nil, nil
|
|
|
|
}
|
|
|
|
|
2019-04-12 21:29:08 +00:00
|
|
|
margs, err := MarshalProperties(args, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".args",
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
resp, err := client.Invoke(p.requestContext(), &pulumirpc.InvokeRequest{
|
2022-04-14 09:59:46 +00:00
|
|
|
Tok: string(tok),
|
|
|
|
Args: margs,
|
2020-12-10 18:21:05 +00:00
|
|
|
})
|
2017-09-20 00:23:10 +00:00
|
|
|
if err != nil {
|
2018-06-07 18:21:38 +00:00
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(7).Infof("%s failed: %v", label, rpcError.Message())
|
|
|
|
return nil, nil, rpcError
|
2017-09-20 00:23:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal any return values.
|
2017-12-15 15:22:49 +00:00
|
|
|
ret, err := UnmarshalProperties(resp.GetReturn(), MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".returns",
|
2019-04-12 21:29:08 +00:00
|
|
|
RejectUnknowns: true,
|
|
|
|
KeepSecrets: true,
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepResources: true,
|
2019-04-12 21:29:08 +00:00
|
|
|
})
|
2017-09-20 00:23:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// And now any properties that failed verification.
|
2023-06-28 16:02:04 +00:00
|
|
|
failures := slice.Prealloc[CheckFailure](len(resp.GetFailures()))
|
2017-09-20 00:23:10 +00:00
|
|
|
for _, failure := range resp.GetFailures() {
|
|
|
|
failures = append(failures, CheckFailure{resource.PropertyKey(failure.Property), failure.Reason})
|
|
|
|
}
|
|
|
|
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s success (#ret=%d,#failures=%d) success", label, len(ret), len(failures))
|
2017-09-20 00:23:10 +00:00
|
|
|
return ret, failures, nil
|
|
|
|
}
|
|
|
|
|
2019-10-22 07:20:26 +00:00
|
|
|
// StreamInvoke dynamically executes a built-in function in the provider, which returns a stream of
|
|
|
|
// responses.
|
|
|
|
func (p *provider) StreamInvoke(
|
|
|
|
tok tokens.ModuleMember,
|
|
|
|
args resource.PropertyMap,
|
2023-03-03 16:36:39 +00:00
|
|
|
onNext func(resource.PropertyMap) error,
|
|
|
|
) ([]CheckFailure, error) {
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(tok != "", "StreamInvoke requires a token")
|
2019-10-22 07:20:26 +00:00
|
|
|
|
|
|
|
label := fmt.Sprintf("%s.StreamInvoke(%s)", p.label(), tok)
|
|
|
|
logging.V(7).Infof("%s executing (#args=%d)", label, len(args))
|
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
2019-10-22 07:20:26 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the provider is not fully configured, return an empty property map.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.known {
|
2019-10-22 07:20:26 +00:00
|
|
|
return nil, onNext(resource.PropertyMap{})
|
|
|
|
}
|
|
|
|
|
|
|
|
margs, err := MarshalProperties(args, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".args",
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepSecrets: pcfg.acceptSecrets,
|
|
|
|
KeepResources: pcfg.acceptResources,
|
2019-10-22 07:20:26 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
streamClient, err := client.StreamInvoke(
|
2020-12-23 21:25:48 +00:00
|
|
|
p.requestContext(), &pulumirpc.InvokeRequest{
|
2022-04-14 09:59:46 +00:00
|
|
|
Tok: string(tok),
|
|
|
|
Args: margs,
|
2020-12-10 18:21:05 +00:00
|
|
|
})
|
2019-10-22 07:20:26 +00:00
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(7).Infof("%s failed: %v", label, rpcError.Message())
|
|
|
|
return nil, rpcError
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
in, err := streamClient.Recv()
|
|
|
|
if err == io.EOF {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal response.
|
|
|
|
ret, err := UnmarshalProperties(in.GetReturn(), MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".returns",
|
2019-10-22 07:20:26 +00:00
|
|
|
RejectUnknowns: true,
|
|
|
|
KeepSecrets: true,
|
2020-10-27 17:12:12 +00:00
|
|
|
KeepResources: true,
|
2019-10-22 07:20:26 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check properties that failed verification.
|
|
|
|
var failures []CheckFailure
|
|
|
|
for _, failure := range in.GetFailures() {
|
|
|
|
failures = append(failures, CheckFailure{resource.PropertyKey(failure.Property), failure.Reason})
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(failures) > 0 {
|
|
|
|
return failures, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send stream message back to whoever is consuming the stream.
|
|
|
|
if err := onNext(ret); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-30 14:48:56 +00:00
|
|
|
// Call dynamically executes a method in the provider associated with a component resource.
|
|
|
|
func (p *provider) Call(tok tokens.ModuleMember, args resource.PropertyMap, info CallInfo,
|
2023-03-03 16:36:39 +00:00
|
|
|
options CallOptions,
|
|
|
|
) (CallResult, error) {
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(tok != "", "Call requires a token")
|
2021-06-30 14:48:56 +00:00
|
|
|
|
|
|
|
label := fmt.Sprintf("%s.Call(%s)", p.label(), tok)
|
|
|
|
logging.V(7).Infof("%s executing (#args=%d)", label, len(args))
|
|
|
|
|
2023-01-25 19:54:31 +00:00
|
|
|
// Ensure that the plugin is configured.
|
|
|
|
client := p.clientRaw
|
2023-11-15 14:53:12 +00:00
|
|
|
pcfg, err := p.configSource.Promise().Result(context.Background())
|
2021-06-30 14:48:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return CallResult{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the provider is not fully configured, return an empty property map.
|
2023-01-25 19:54:31 +00:00
|
|
|
if !pcfg.known {
|
2021-06-30 14:48:56 +00:00
|
|
|
return CallResult{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
margs, err := MarshalProperties(args, MarshalOptions{
|
2023-12-12 12:19:42 +00:00
|
|
|
Label: label + ".args",
|
2021-06-30 14:48:56 +00:00
|
|
|
KeepUnknowns: true,
|
|
|
|
KeepSecrets: true,
|
|
|
|
KeepResources: true,
|
2021-09-15 21:16:00 +00:00
|
|
|
// To initially scope the use of this new feature, we only keep output values for
|
|
|
|
// Construct and Call (when the client accepts them).
|
2023-01-25 19:54:31 +00:00
|
|
|
KeepOutputValues: pcfg.acceptOutputs,
|
2021-06-30 14:48:56 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return CallResult{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal the arg dependencies.
|
|
|
|
argDependencies := map[string]*pulumirpc.CallRequest_ArgumentDependencies{}
|
|
|
|
for name, dependencies := range options.ArgDependencies {
|
|
|
|
urns := make([]string, len(dependencies))
|
|
|
|
for i, urn := range dependencies {
|
|
|
|
urns[i] = string(urn)
|
|
|
|
}
|
|
|
|
argDependencies[string(name)] = &pulumirpc.CallRequest_ArgumentDependencies{Urns: urns}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Marshal the config.
|
|
|
|
config := map[string]string{}
|
|
|
|
for k, v := range info.Config {
|
|
|
|
config[k.String()] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := client.Call(p.requestContext(), &pulumirpc.CallRequest{
|
2024-02-14 08:15:24 +00:00
|
|
|
Tok: string(tok),
|
|
|
|
Args: margs,
|
|
|
|
ArgDependencies: argDependencies,
|
|
|
|
Project: info.Project,
|
|
|
|
Stack: info.Stack,
|
|
|
|
Config: config,
|
|
|
|
DryRun: info.DryRun,
|
|
|
|
Parallel: int32(info.Parallel),
|
|
|
|
MonitorEndpoint: info.MonitorAddress,
|
|
|
|
AcceptsOutputValues: true,
|
2021-06-30 14:48:56 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(7).Infof("%s failed: %v", label, rpcError.Message())
|
|
|
|
return CallResult{}, rpcError
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal any return values.
|
|
|
|
ret, err := UnmarshalProperties(resp.GetReturn(), MarshalOptions{
|
2024-02-14 08:15:24 +00:00
|
|
|
Label: label + ".returns",
|
|
|
|
KeepUnknowns: info.DryRun,
|
|
|
|
KeepSecrets: true,
|
|
|
|
KeepResources: true,
|
|
|
|
KeepOutputValues: true,
|
2021-06-30 14:48:56 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return CallResult{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
returnDependencies := map[resource.PropertyKey][]resource.URN{}
|
|
|
|
for k, rpcDeps := range resp.GetReturnDependencies() {
|
|
|
|
urns := make([]resource.URN, len(rpcDeps.Urns))
|
|
|
|
for i, d := range rpcDeps.Urns {
|
|
|
|
urns[i] = resource.URN(d)
|
|
|
|
}
|
|
|
|
returnDependencies[resource.PropertyKey(k)] = urns
|
|
|
|
}
|
|
|
|
|
|
|
|
// And now any properties that failed verification.
|
2023-06-28 16:02:04 +00:00
|
|
|
failures := slice.Prealloc[CheckFailure](len(resp.GetFailures()))
|
2021-06-30 14:48:56 +00:00
|
|
|
for _, failure := range resp.GetFailures() {
|
|
|
|
failures = append(failures, CheckFailure{resource.PropertyKey(failure.Property), failure.Reason})
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.V(7).Infof("%s success (#ret=%d,#failures=%d) success", label, len(ret), len(failures))
|
|
|
|
return CallResult{Return: ret, ReturnDependencies: returnDependencies, Failures: failures}, nil
|
|
|
|
}
|
|
|
|
|
2017-12-01 21:50:32 +00:00
|
|
|
// GetPluginInfo returns this plugin's information.
|
2018-02-06 17:57:32 +00:00
|
|
|
func (p *provider) GetPluginInfo() (workspace.PluginInfo, error) {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := p.label() + ".GetPluginInfo()"
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s executing", label)
|
Run Configure calls in parallel (#1321)
As of this change, the engine will run all Configure calls in parallel.
This improves startup performance, since otherwise, we would block
waiting for all plugins to be configured before proceeding to run a
program. Emperically, this is about 1.5-2s for AWS programs, and
manifests as a delay between the purple "Previewing update of stack"
being printed, and the corresponding grey "Previewing update" message.
This is done simply by using a Goroutine for Configure, and making sure
to synchronize on all actual CRUD operations. I toyed with using double
checked locking to eliminate lock acquisitions -- something we may want
to consider as we add more fine-grained parallelism -- however, I've
kept it simple to avoid all the otherwise implied memory model woes.
I made the judgment call that GetPluginInfo may proceed before
Configure has settled. (Otherwise, we'd immediately call it and block
after loading the plugin, obviating the parallelism benefits.) I also
made the judgment call to do this in the engine, after flip flopping
several times about possibly making it a provider's own decision.
2018-05-04 21:29:47 +00:00
|
|
|
|
|
|
|
// Calling GetPluginInfo happens immediately after loading, and does not require configuration to proceed.
|
|
|
|
// Thus, we access the clientRaw property, rather than calling getClient.
|
2024-01-17 09:35:20 +00:00
|
|
|
resp, err := p.clientRaw.GetPluginInfo(p.requestContext(), &emptypb.Empty{})
|
2017-12-01 21:50:32 +00:00
|
|
|
if err != nil {
|
2018-03-29 00:07:35 +00:00
|
|
|
rpcError := rpcerror.Convert(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s failed: err=%v", label, rpcError.Message())
|
2018-03-29 00:07:35 +00:00
|
|
|
return workspace.PluginInfo{}, rpcError
|
2017-12-01 21:50:32 +00:00
|
|
|
}
|
2018-02-06 17:57:32 +00:00
|
|
|
|
|
|
|
var version *semver.Version
|
|
|
|
if v := resp.Version; v != "" {
|
|
|
|
sv, err := semver.ParseTolerant(v)
|
|
|
|
if err != nil {
|
|
|
|
return workspace.PluginInfo{}, err
|
|
|
|
}
|
|
|
|
version = &sv
|
|
|
|
}
|
|
|
|
|
2020-12-23 21:25:48 +00:00
|
|
|
path := ""
|
|
|
|
if p.plug != nil {
|
|
|
|
path = p.plug.Bin
|
|
|
|
}
|
|
|
|
|
2023-06-02 21:06:57 +00:00
|
|
|
logging.V(7).Infof("%s success (#version=%v) success", label, version)
|
2018-02-06 17:57:32 +00:00
|
|
|
return workspace.PluginInfo{
|
2018-02-21 18:32:31 +00:00
|
|
|
Name: string(p.pkg),
|
2020-12-23 21:25:48 +00:00
|
|
|
Path: path,
|
2024-04-25 17:30:30 +00:00
|
|
|
Kind: apitype.ResourcePlugin,
|
2018-02-06 17:57:32 +00:00
|
|
|
Version: version,
|
2017-12-01 21:50:32 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2022-04-19 11:41:18 +00:00
|
|
|
// Attach attaches this plugin to the engine
|
|
|
|
func (p *provider) Attach(address string) error {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := p.label() + ".Attach()"
|
2022-04-19 11:41:18 +00:00
|
|
|
logging.V(7).Infof("%s executing", label)
|
|
|
|
|
|
|
|
// Calling Attach happens immediately after loading, and does not require configuration to proceed.
|
|
|
|
// Thus, we access the clientRaw property, rather than calling getClient.
|
|
|
|
_, err := p.clientRaw.Attach(p.requestContext(), &pulumirpc.PluginAttach{Address: address})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(7).Infof("%s failed: err=%v", label, rpcError.Message())
|
|
|
|
return rpcError
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-07-12 04:20:26 +00:00
|
|
|
func (p *provider) SignalCancellation() error {
|
2024-01-17 09:35:20 +00:00
|
|
|
_, err := p.clientRaw.Cancel(p.requestContext(), &emptypb.Empty{})
|
2018-07-12 04:20:26 +00:00
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(8).Infof("provider received rpc error `%s`: `%s`", rpcError.Code(),
|
|
|
|
rpcError.Message())
|
turn on the golangci-lint exhaustive linter (#15028)
Turn on the golangci-lint exhaustive linter. This is the first step
towards catching more missing cases during development rather than
in tests, or in production.
This might be best reviewed commit-by-commit, as the first commit turns
on the linter with the `default-signifies-exhaustive: true` option set,
which requires a lot less changes in the current codebase.
I think it's probably worth doing the second commit as well, as that
will get us the real benefits, even though we end up with a little bit
more churn. However it means all the `switch` statements are covered,
which isn't the case after the first commit, since we do have a lot of
`default` statements that just call `assert.Fail`.
Fixes #14601
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-01-17 16:50:41 +00:00
|
|
|
if rpcError.Code() == codes.Unimplemented {
|
2018-07-12 04:20:26 +00:00
|
|
|
// For backwards compatibility, do nothing if it's not implemented.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
// Close tears down the underlying plugin RPC connection and process.
|
|
|
|
func (p *provider) Close() error {
|
2020-12-23 21:25:48 +00:00
|
|
|
if p.plug == nil {
|
|
|
|
return nil
|
|
|
|
}
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
return p.plug.Close()
|
|
|
|
}
|
2018-03-09 23:43:16 +00:00
|
|
|
|
2018-04-04 17:08:17 +00:00
|
|
|
// createConfigureError creates a nice error message from an RPC error that
|
|
|
|
// originated from `Configure`.
|
|
|
|
//
|
|
|
|
// If we requested that a resource configure itself but omitted required configuration
|
|
|
|
// variables, resource providers will respond with a list of missing variables and their descriptions.
|
|
|
|
// If that is what occurred, we'll use that information here to construct a nice error message.
|
|
|
|
func createConfigureError(rpcerr *rpcerror.Error) error {
|
|
|
|
var err error
|
|
|
|
for _, detail := range rpcerr.Details() {
|
|
|
|
if missingKeys, ok := detail.(*pulumirpc.ConfigureErrorMissingKeys); ok {
|
|
|
|
for _, missingKey := range missingKeys.MissingKeys {
|
|
|
|
singleError := fmt.Errorf("missing required configuration key \"%s\": %s\n"+
|
|
|
|
"Set a value using the command `pulumi config set %s <value>`.",
|
|
|
|
missingKey.Name, missingKey.Description, missingKey.Name)
|
|
|
|
err = multierror.Append(err, singleError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return rpcerr
|
|
|
|
}
|
|
|
|
|
2018-03-09 23:43:16 +00:00
|
|
|
// resourceStateAndError interprets an error obtained from a gRPC endpoint.
|
|
|
|
//
|
|
|
|
// gRPC gives us a `status.Status` structure as an `error` whenever our
|
|
|
|
// gRPC servers serve up an error. Each `status.Status` contains a code
|
|
|
|
// and a message. Based on the error code given to us, we can understand
|
|
|
|
// the state of our system and if our resource status is truly unknown.
|
|
|
|
//
|
|
|
|
// In general, our resource state is only really unknown if the server
|
|
|
|
// had an internal error, in which case it will serve one of `codes.Internal`,
|
|
|
|
// `codes.DataLoss`, or `codes.Unknown` to us.
|
2018-05-02 17:36:55 +00:00
|
|
|
func resourceStateAndError(err error) (resource.Status, *rpcerror.Error) {
|
2018-03-29 00:07:35 +00:00
|
|
|
rpcError := rpcerror.Convert(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(8).Infof("provider received rpc error `%s`: `%s`", rpcError.Code(), rpcError.Message())
|
turn on the golangci-lint exhaustive linter (#15028)
Turn on the golangci-lint exhaustive linter. This is the first step
towards catching more missing cases during development rather than
in tests, or in production.
This might be best reviewed commit-by-commit, as the first commit turns
on the linter with the `default-signifies-exhaustive: true` option set,
which requires a lot less changes in the current codebase.
I think it's probably worth doing the second commit as well, as that
will get us the real benefits, even though we end up with a little bit
more churn. However it means all the `switch` statements are covered,
which isn't the case after the first commit, since we do have a lot of
`default` statements that just call `assert.Fail`.
Fixes #14601
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-01-17 16:50:41 +00:00
|
|
|
//nolint:exhaustive // We want to handle only some error codes specially
|
2018-03-29 00:07:35 +00:00
|
|
|
switch rpcError.Code() {
|
|
|
|
case codes.Internal, codes.DataLoss, codes.Unknown:
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(8).Infof("rpc error kind `%s` may not be recoverable", rpcError.Code())
|
2018-03-29 00:07:35 +00:00
|
|
|
return resource.StatusUnknown, rpcError
|
|
|
|
}
|
|
|
|
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(8).Infof("rpc error kind `%s` is well-understood and recoverable", rpcError.Code())
|
2018-03-29 00:07:35 +00:00
|
|
|
return resource.StatusOK, rpcError
|
2018-03-09 23:43:16 +00:00
|
|
|
}
|
2018-07-06 22:17:32 +00:00
|
|
|
|
|
|
|
// parseError parses a gRPC error into a set of values that represent the state of a resource. They
|
|
|
|
// are: (1) the `resourceStatus`, indicating the last known state (e.g., `StatusOK`, representing
|
|
|
|
// success, `StatusUnknown`, representing internal failure); (2) the `*rpcerror.Error`, our internal
|
|
|
|
// representation for RPC errors; and optionally (3) `liveObject`, containing the last known live
|
|
|
|
// version of the object that has successfully created but failed to initialize (e.g., because the
|
|
|
|
// object was created, but app code is continually crashing and the resource never achieves
|
|
|
|
// liveness).
|
|
|
|
func parseError(err error) (
|
2024-01-17 09:35:20 +00:00
|
|
|
resourceStatus resource.Status, id resource.ID, liveInputs, liveObject *structpb.Struct, resourceErr error,
|
2018-07-06 22:17:32 +00:00
|
|
|
) {
|
2018-07-19 05:52:01 +00:00
|
|
|
var responseErr *rpcerror.Error
|
|
|
|
resourceStatus, responseErr = resourceStateAndError(err)
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(responseErr != nil, "resourceStateAndError must never return a nil error")
|
2018-07-06 22:17:32 +00:00
|
|
|
|
|
|
|
// If resource was successfully created but failed to initialize, the error will be packed
|
|
|
|
// with the live properties of the object.
|
2018-07-19 05:52:01 +00:00
|
|
|
resourceErr = responseErr
|
|
|
|
for _, detail := range responseErr.Details() {
|
2018-07-06 22:17:32 +00:00
|
|
|
if initErr, ok := detail.(*pulumirpc.ErrorResourceInitFailed); ok {
|
2018-07-07 01:03:42 +00:00
|
|
|
id = resource.ID(initErr.GetId())
|
2018-07-06 22:17:32 +00:00
|
|
|
liveObject = initErr.GetProperties()
|
2019-03-11 20:50:00 +00:00
|
|
|
liveInputs = initErr.GetInputs()
|
2018-07-06 22:17:32 +00:00
|
|
|
resourceStatus = resource.StatusPartialFailure
|
2018-07-19 05:52:01 +00:00
|
|
|
resourceErr = &InitError{Reasons: initErr.Reasons}
|
2018-07-06 22:17:32 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-11 20:50:00 +00:00
|
|
|
return resourceStatus, id, liveObject, liveInputs, resourceErr
|
2018-07-19 05:52:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// InitError represents a failure to initialize a resource, i.e., the resource has been successfully
|
|
|
|
// created, but it has failed to initialize.
|
|
|
|
type InitError struct {
|
|
|
|
Reasons []string
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ error = (*InitError)(nil)
|
|
|
|
|
|
|
|
func (ie *InitError) Error() string {
|
|
|
|
var err error
|
|
|
|
for _, reason := range ie.Reasons {
|
|
|
|
err = multierror.Append(err, errors.New(reason))
|
|
|
|
}
|
|
|
|
return err.Error()
|
2018-07-06 22:17:32 +00:00
|
|
|
}
|
2021-06-17 21:46:05 +00:00
|
|
|
|
|
|
|
func decorateSpanWithType(span opentracing.Span, urn string) {
|
|
|
|
if urn := resource.URN(urn); urn.IsValid() {
|
|
|
|
span.SetTag("pulumi-decorator", urn.Type())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func decorateProviderSpans(span opentracing.Span, method string, req, resp interface{}, grpcError error) {
|
|
|
|
if req == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch method {
|
|
|
|
case "/pulumirpc.ResourceProvider/Check", "/pulumirpc.ResourceProvider/CheckConfig":
|
|
|
|
decorateSpanWithType(span, req.(*pulumirpc.CheckRequest).Urn)
|
|
|
|
case "/pulumirpc.ResourceProvider/Diff", "/pulumirpc.ResourceProvider/DiffConfig":
|
|
|
|
decorateSpanWithType(span, req.(*pulumirpc.DiffRequest).Urn)
|
|
|
|
case "/pulumirpc.ResourceProvider/Create":
|
|
|
|
decorateSpanWithType(span, req.(*pulumirpc.CreateRequest).Urn)
|
|
|
|
case "/pulumirpc.ResourceProvider/Update":
|
|
|
|
decorateSpanWithType(span, req.(*pulumirpc.UpdateRequest).Urn)
|
|
|
|
case "/pulumirpc.ResourceProvider/Delete":
|
|
|
|
decorateSpanWithType(span, req.(*pulumirpc.DeleteRequest).Urn)
|
|
|
|
case "/pulumirpc.ResourceProvider/Invoke":
|
|
|
|
span.SetTag("pulumi-decorator", req.(*pulumirpc.InvokeRequest).Tok)
|
|
|
|
}
|
|
|
|
}
|
2022-11-16 17:22:47 +00:00
|
|
|
|
|
|
|
// GetMapping fetches the conversion mapping (if any) for this resource provider.
|
2023-09-21 11:45:07 +00:00
|
|
|
func (p *provider) GetMapping(key, provider string) ([]byte, string, error) {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := p.label() + ".GetMapping"
|
2023-09-21 11:45:07 +00:00
|
|
|
logging.V(7).Infof("%s executing: key=%s, provider=%s", label, key, provider)
|
2023-06-05 08:46:25 +00:00
|
|
|
|
2022-12-01 23:03:25 +00:00
|
|
|
resp, err := p.clientRaw.GetMapping(p.requestContext(), &pulumirpc.GetMappingRequest{
|
2023-09-21 11:45:07 +00:00
|
|
|
Key: key,
|
|
|
|
Provider: provider,
|
2022-12-01 23:03:25 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
code := rpcError.Code()
|
|
|
|
if code == codes.Unimplemented {
|
|
|
|
// For backwards compatibility, just return nothing as if the provider didn't have a mapping for
|
|
|
|
// the given key
|
2023-06-05 08:46:25 +00:00
|
|
|
logging.V(7).Infof("%s unimplemented", label)
|
2022-12-01 23:03:25 +00:00
|
|
|
return nil, "", nil
|
|
|
|
}
|
2023-06-05 08:46:25 +00:00
|
|
|
logging.V(7).Infof("%s failed: %v", label, rpcError)
|
2022-12-01 23:03:25 +00:00
|
|
|
return nil, "", err
|
|
|
|
}
|
2023-06-05 08:46:25 +00:00
|
|
|
|
|
|
|
logging.V(7).Infof("%s success: data=#%d provider=%s", label, len(resp.Data), resp.Provider)
|
2022-12-01 23:03:25 +00:00
|
|
|
return resp.Data, resp.Provider, nil
|
2022-11-16 17:22:47 +00:00
|
|
|
}
|
2023-09-21 11:45:07 +00:00
|
|
|
|
|
|
|
func (p *provider) GetMappings(key string) ([]string, error) {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := p.label() + ".GetMappings"
|
2023-09-21 11:45:07 +00:00
|
|
|
logging.V(7).Infof("%s executing: key=%s", label, key)
|
|
|
|
|
|
|
|
resp, err := p.clientRaw.GetMappings(p.requestContext(), &pulumirpc.GetMappingsRequest{
|
|
|
|
Key: key,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
code := rpcError.Code()
|
|
|
|
if code == codes.Unimplemented {
|
|
|
|
// For backwards compatibility just return nil to indicate unimplemented.
|
|
|
|
logging.V(7).Infof("%s unimplemented", label)
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
logging.V(7).Infof("%s failed: %v", label, rpcError)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.V(7).Infof("%s success: providers=%v", label, resp.Providers)
|
|
|
|
// Ensure we don't return nil here because we use it as an "unimplemented" flag elsewhere in the system
|
|
|
|
if resp.Providers == nil {
|
|
|
|
resp.Providers = []string{}
|
|
|
|
}
|
|
|
|
return resp.Providers, nil
|
|
|
|
}
|