2020-10-14 11:51:53 +00:00
|
|
|
// Copyright 2016-2020, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package deploy
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-08-09 16:40:59 +00:00
|
|
|
cryptorand "crypto/rand"
|
2023-12-12 12:19:42 +00:00
|
|
|
"errors"
|
2020-10-14 11:51:53 +00:00
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
"github.com/blang/semver"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
|
|
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
|
2024-04-09 10:56:25 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/util/gsync"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
|
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"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
2020-10-14 11:51:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// An Import specifies a resource to import.
|
|
|
|
type Import struct {
|
2023-09-11 15:54:07 +00:00
|
|
|
Type tokens.Type // The type token for the resource. Required.
|
2023-11-20 08:59:00 +00:00
|
|
|
Name string // The name of the resource. Required.
|
2023-09-11 15:54:07 +00:00
|
|
|
ID resource.ID // The ID of the resource. Required.
|
|
|
|
Parent resource.URN // The parent of the resource, if any.
|
|
|
|
Provider resource.URN // The specific provider to use for the resource, if any.
|
|
|
|
Version *semver.Version // The provider version to use for the resource, if any.
|
|
|
|
PluginDownloadURL string // The provider PluginDownloadURL to use for the resource, if any.
|
|
|
|
PluginChecksums map[string][]byte // The provider checksums to use for the resource, if any.
|
|
|
|
Protect bool // Whether to mark the resource as protected after import
|
|
|
|
Properties []string // Which properties to include (Defaults to required properties)
|
2023-11-13 17:58:35 +00:00
|
|
|
|
|
|
|
// True if this import should create an empty component resource. ID must not be set if this is used.
|
|
|
|
Component bool
|
|
|
|
// True if this is a remote component resource. Component must be true if this is true.
|
|
|
|
Remote bool
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ImportOptions controls the import process.
|
|
|
|
type ImportOptions struct {
|
|
|
|
Events Events // an optional events callback interface.
|
|
|
|
Parallel int // the degree of parallelism for resource operations (<=1 for serial).
|
|
|
|
}
|
|
|
|
|
2020-11-18 17:47:52 +00:00
|
|
|
// NewImportDeployment creates a new import deployment from a resource snapshot plus a set of resources to import.
|
2020-10-14 11:51:53 +00:00
|
|
|
//
|
|
|
|
// From the old and new states, it understands how to orchestrate an evaluation and analyze the resulting resources.
|
2020-11-18 17:47:52 +00:00
|
|
|
// The deployment may be used to simply inspect a series of operations, or actually perform them; these operations are
|
2020-10-14 11:51:53 +00:00
|
|
|
// generated based on analysis of the old and new states. If a resource exists in new, but not old, for example, it
|
|
|
|
// results in a create; if it exists in both, but is different, it results in an update; and so on and so forth.
|
|
|
|
//
|
2020-11-18 17:47:52 +00:00
|
|
|
// Note that a deployment uses internal concurrency and parallelism in various ways, so it must be closed if for some
|
|
|
|
// reason it isn't carried out to its final conclusion. This will result in cancellation and reclamation of resources.
|
|
|
|
func NewImportDeployment(ctx *plugin.Context, target *Target, projectName tokens.PackageName, imports []Import,
|
2023-03-03 16:36:39 +00:00
|
|
|
preview bool,
|
|
|
|
) (*Deployment, error) {
|
2023-02-17 20:05:48 +00:00
|
|
|
contract.Requiref(ctx != nil, "ctx", "must not be nil")
|
|
|
|
contract.Requiref(target != nil, "target", "must not be nil")
|
2020-10-14 11:51:53 +00:00
|
|
|
|
|
|
|
prev := target.Snapshot
|
|
|
|
source := NewErrorSource(projectName)
|
|
|
|
if err := migrateProviders(target, prev, source); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Produce a map of all old resources for fast access.
|
2023-04-12 09:35:20 +00:00
|
|
|
_, olds, err := buildResourceMap(prev, preview)
|
2020-10-14 11:51:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-12-22 19:04:49 +00:00
|
|
|
// Create a goal map for the deployment.
|
2024-04-09 10:56:25 +00:00
|
|
|
newGoals := &gsync.Map[resource.URN, *resource.Goal]{}
|
2020-12-22 19:04:49 +00:00
|
|
|
|
2023-12-03 08:46:37 +00:00
|
|
|
builtins := newBuiltinProvider(nil, nil, ctx.Diag)
|
2020-11-11 05:11:30 +00:00
|
|
|
|
2020-10-14 11:51:53 +00:00
|
|
|
// Create a new provider registry.
|
2023-04-12 09:35:20 +00:00
|
|
|
reg := providers.NewRegistry(ctx.Host, preview, builtins)
|
2020-10-14 11:51:53 +00:00
|
|
|
|
2020-11-18 17:47:52 +00:00
|
|
|
// Return the prepared deployment.
|
|
|
|
return &Deployment{
|
2020-10-14 11:51:53 +00:00
|
|
|
ctx: ctx,
|
|
|
|
target: target,
|
|
|
|
prev: prev,
|
|
|
|
olds: olds,
|
2020-12-22 19:04:49 +00:00
|
|
|
goals: newGoals,
|
2020-10-14 11:51:53 +00:00
|
|
|
imports: imports,
|
|
|
|
isImport: true,
|
|
|
|
schemaLoader: schema.NewPluginLoader(ctx.Host),
|
|
|
|
source: NewErrorSource(projectName),
|
|
|
|
preview: preview,
|
|
|
|
providers: reg,
|
2022-01-31 10:31:51 +00:00
|
|
|
newPlans: newResourcePlan(target.Config),
|
2024-04-09 10:56:25 +00:00
|
|
|
news: &gsync.Map[resource.URN, *resource.State]{},
|
2020-10-14 11:51:53 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type noopEvent int
|
|
|
|
|
|
|
|
func (noopEvent) event() {}
|
|
|
|
func (noopEvent) Goal() *resource.Goal { return nil }
|
|
|
|
func (noopEvent) Done(result *RegisterResult) {}
|
|
|
|
|
|
|
|
type noopOutputsEvent resource.URN
|
|
|
|
|
|
|
|
func (noopOutputsEvent) event() {}
|
|
|
|
func (e noopOutputsEvent) URN() resource.URN { return resource.URN(e) }
|
|
|
|
func (noopOutputsEvent) Outputs() resource.PropertyMap { return resource.PropertyMap{} }
|
|
|
|
func (noopOutputsEvent) Done() {}
|
|
|
|
|
|
|
|
type importer struct {
|
2020-11-18 17:47:52 +00:00
|
|
|
deployment *Deployment
|
|
|
|
executor *stepExecutor
|
|
|
|
preview bool
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *importer) executeSerial(ctx context.Context, steps ...Step) bool {
|
|
|
|
return i.wait(ctx, i.executor.ExecuteSerial(steps))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *importer) executeParallel(ctx context.Context, steps ...Step) bool {
|
|
|
|
return i.wait(ctx, i.executor.ExecuteParallel(steps))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *importer) wait(ctx context.Context, token completionToken) bool {
|
|
|
|
token.Wait(ctx)
|
2023-10-17 06:47:32 +00:00
|
|
|
return ctx.Err() == nil && i.executor.Errored() == nil
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *importer) registerExistingResources(ctx context.Context) bool {
|
2020-12-10 13:31:45 +00:00
|
|
|
if i != nil && i.deployment != nil && i.deployment.prev != nil {
|
|
|
|
// Issue same steps per existing resource to make sure that they are recorded in the snapshot.
|
|
|
|
// We issue these steps serially s.t. the resources remain in the order in which they appear in the state.
|
|
|
|
for _, r := range i.deployment.prev.Resources {
|
|
|
|
if r.Delete {
|
|
|
|
continue
|
|
|
|
}
|
2020-10-14 11:51:53 +00:00
|
|
|
|
2023-11-10 13:31:11 +00:00
|
|
|
// Clear the ID because Same asserts that the new state has no ID.
|
2024-05-09 16:15:41 +00:00
|
|
|
new := r.Copy()
|
2020-12-10 13:31:45 +00:00
|
|
|
new.ID = ""
|
2023-11-10 13:31:11 +00:00
|
|
|
// Set a dummy goal so the resource is tracked as managed.
|
2024-04-09 10:56:25 +00:00
|
|
|
i.deployment.goals.Store(r.URN, &resource.Goal{})
|
2024-05-09 16:15:41 +00:00
|
|
|
if !i.executeSerial(ctx, NewSameStep(i.deployment, noopEvent(0), r, new)) {
|
2020-12-10 13:31:45 +00:00
|
|
|
return false
|
|
|
|
}
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *importer) getOrCreateStackResource(ctx context.Context) (resource.URN, bool, bool) {
|
|
|
|
// Get or create the root resource.
|
2020-11-18 17:47:52 +00:00
|
|
|
if i.deployment.prev != nil {
|
|
|
|
for _, res := range i.deployment.prev.Resources {
|
2023-12-04 10:36:51 +00:00
|
|
|
if res.Type == resource.RootStackType && res.Parent == "" {
|
2020-10-14 11:51:53 +00:00
|
|
|
return res.URN, false, true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-18 17:47:52 +00:00
|
|
|
projectName, stackName := i.deployment.source.Project(), i.deployment.target.Name
|
2020-10-14 11:51:53 +00:00
|
|
|
typ, name := resource.RootStackType, fmt.Sprintf("%s-%s", projectName, stackName)
|
2023-11-20 08:59:00 +00:00
|
|
|
urn := resource.NewURN(stackName.Q(), projectName, "", typ, name)
|
2020-10-14 11:51:53 +00:00
|
|
|
state := resource.NewState(typ, urn, false, false, "", resource.PropertyMap{}, nil, "", false, false, nil, nil, "",
|
[engine] Add support for source positions
These changes add support for passing source position information in
gRPC metadata and recording the source position that corresponds to a
resource registration in the statefile.
Enabling source position information in the resource model can provide
substantial benefits, including but not limited to:
- Better errors from the Pulumi CLI
- Go-to-defintion for resources in state
- Editor integration for errors, etc. from `pulumi preview`
Source positions are (file, line) or (file, line, column) tuples
represented as URIs. The line and column are stored in the fragment
portion of the URI as "line(,column)?". The scheme of the URI and the
form of its path component depends on the context in which it is
generated or used:
- During an active update, the URI's scheme is `file` and paths are
absolute filesystem paths. This allows consumers to easily access
arbitrary files that are available on the host.
- In a statefile, the URI's scheme is `project` and paths are relative
to the project root. This allows consumers to resolve source positions
relative to the project file in different contexts irrespective of the
location of the project itself (e.g. given a project-relative path and
the URL of the project's root on GitHub, one can build a GitHub URL for
the source position).
During an update, source position information may be attached to gRPC
calls as "source-position" metadata. This allows arbitrary calls to be
associated with source positions without changes to their protobuf
payloads. Modifying the protobuf payloads is also a viable approach, but
is somewhat more invasive than attaching metadata, and requires changes
to every call signature.
Source positions should reflect the position in user code that initiated
a resource model operation (e.g. the source position passed with
`RegisterResource` for `pet` in the example above should be the source
position in `index.ts`, _not_ the source position in the Pulumi SDK). In
general, the Pulumi SDK should be able to infer the source position of
the resource registration, as the relationship between a resource
registration and its corresponding user code should be static per SDK.
Source positions in state files will be stored as a new `registeredAt`
property on each resource. This property is optional.
2023-06-29 18:41:19 +00:00
|
|
|
nil, false, nil, nil, nil, "", false, "", nil, nil, "")
|
2022-01-20 11:18:54 +00:00
|
|
|
// TODO(seqnum) should stacks be created with 1? When do they ever get recreated/replaced?
|
2020-11-18 17:47:52 +00:00
|
|
|
if !i.executeSerial(ctx, NewCreateStep(i.deployment, noopEvent(0), state)) {
|
2020-10-14 11:51:53 +00:00
|
|
|
return "", false, false
|
|
|
|
}
|
|
|
|
return urn, true, true
|
|
|
|
}
|
|
|
|
|
2023-10-01 14:21:32 +00:00
|
|
|
func (i *importer) registerProviders(ctx context.Context) (map[resource.URN]string, bool, error) {
|
2020-10-14 11:51:53 +00:00
|
|
|
urnToReference := map[resource.URN]string{}
|
|
|
|
|
|
|
|
// Determine which default providers are not present in the state. If all default providers are accounted for,
|
|
|
|
// we're done.
|
|
|
|
//
|
|
|
|
// NOTE: what if the configuration for an existing default provider has changed? If it has, we should diff it and
|
|
|
|
// replace it appropriately or we should not use the ambient config at all.
|
2023-06-28 16:02:04 +00:00
|
|
|
defaultProviderRequests := slice.Prealloc[providers.ProviderRequest](len(i.deployment.imports))
|
2020-10-14 11:51:53 +00:00
|
|
|
defaultProviders := map[resource.URN]struct{}{}
|
2020-11-18 17:47:52 +00:00
|
|
|
for _, imp := range i.deployment.imports {
|
2023-11-13 17:58:35 +00:00
|
|
|
if imp.Component && !imp.Remote {
|
|
|
|
// Skip local component resources, they don't have providers.
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-10-14 11:51:53 +00:00
|
|
|
if imp.Provider != "" {
|
|
|
|
// If the provider for this import exists, map its URN to its provider reference. If it does not exist,
|
|
|
|
// the import step will issue an appropriate error or errors.
|
|
|
|
ref := string(imp.Provider)
|
2020-11-18 17:47:52 +00:00
|
|
|
if state, ok := i.deployment.olds[imp.Provider]; ok {
|
2020-10-14 11:51:53 +00:00
|
|
|
r, err := providers.NewReference(imp.Provider, state.ID)
|
2023-02-17 20:05:48 +00:00
|
|
|
contract.AssertNoErrorf(err,
|
|
|
|
"could not create provider reference with URN %q and ID %q", imp.Provider, state.ID)
|
2020-10-14 11:51:53 +00:00
|
|
|
ref = r.String()
|
|
|
|
}
|
|
|
|
urnToReference[imp.Provider] = ref
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-11-24 17:14:09 +00:00
|
|
|
if imp.Type.Package() == "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
return nil, false, errors.New("incorrect package type specified")
|
2020-11-24 17:14:09 +00:00
|
|
|
}
|
2023-09-11 15:54:07 +00:00
|
|
|
req := providers.NewProviderRequest(imp.Version, imp.Type.Package(), imp.PluginDownloadURL, imp.PluginChecksums)
|
2020-10-14 11:51:53 +00:00
|
|
|
typ, name := providers.MakeProviderType(req.Package()), req.Name()
|
2020-11-18 17:47:52 +00:00
|
|
|
urn := i.deployment.generateURN("", typ, name)
|
|
|
|
if state, ok := i.deployment.olds[urn]; ok {
|
2020-10-14 11:51:53 +00:00
|
|
|
ref, err := providers.NewReference(urn, state.ID)
|
2023-02-17 20:05:48 +00:00
|
|
|
contract.AssertNoErrorf(err,
|
|
|
|
"could not create provider reference with URN %q and ID %q", urn, state.ID)
|
2020-10-14 11:51:53 +00:00
|
|
|
urnToReference[urn] = ref.String()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if _, ok := defaultProviders[urn]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultProviderRequests = append(defaultProviderRequests, req)
|
|
|
|
defaultProviders[urn] = struct{}{}
|
|
|
|
}
|
|
|
|
if len(defaultProviderRequests) == 0 {
|
2023-10-01 14:21:32 +00:00
|
|
|
return urnToReference, true, nil
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
steps := make([]Step, len(defaultProviderRequests))
|
|
|
|
sort.Slice(defaultProviderRequests, func(i, j int) bool {
|
|
|
|
return defaultProviderRequests[i].String() < defaultProviderRequests[j].String()
|
|
|
|
})
|
|
|
|
for idx, req := range defaultProviderRequests {
|
2020-11-24 17:14:09 +00:00
|
|
|
if req.Package() == "" {
|
2023-12-12 12:19:42 +00:00
|
|
|
return nil, false, errors.New("incorrect package type specified")
|
2020-11-24 17:14:09 +00:00
|
|
|
}
|
|
|
|
|
2020-10-14 11:51:53 +00:00
|
|
|
typ, name := providers.MakeProviderType(req.Package()), req.Name()
|
2020-11-18 17:47:52 +00:00
|
|
|
urn := i.deployment.generateURN("", typ, name)
|
2020-10-14 11:51:53 +00:00
|
|
|
|
|
|
|
// Fetch, prepare, and check the configuration for this provider.
|
2020-11-18 17:47:52 +00:00
|
|
|
inputs, err := i.deployment.target.GetPackageConfig(req.Package())
|
2020-10-14 11:51:53 +00:00
|
|
|
if err != nil {
|
2023-10-01 14:21:32 +00:00
|
|
|
return nil, false, fmt.Errorf("failed to fetch provider config: %w", err)
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the inputs for the provider using the ambient config.
|
|
|
|
if v := req.Version(); v != nil {
|
2021-12-17 22:52:01 +00:00
|
|
|
providers.SetProviderVersion(inputs, v)
|
|
|
|
}
|
|
|
|
if url := req.PluginDownloadURL(); url != "" {
|
|
|
|
providers.SetProviderURL(inputs, url)
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
2023-09-11 15:54:07 +00:00
|
|
|
if checksums := req.PluginChecksums(); checksums != nil {
|
|
|
|
providers.SetProviderChecksums(inputs, checksums)
|
|
|
|
}
|
2022-07-25 11:08:03 +00:00
|
|
|
inputs, failures, err := i.deployment.providers.Check(urn, nil, inputs, false, nil)
|
2020-10-14 11:51:53 +00:00
|
|
|
if err != nil {
|
2023-10-01 14:21:32 +00:00
|
|
|
return nil, false, fmt.Errorf("failed to validate provider config: %w", err)
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
state := resource.NewState(typ, urn, true, false, "", inputs, nil, "", false, false, nil, nil, "", nil, false,
|
[engine] Add support for source positions
These changes add support for passing source position information in
gRPC metadata and recording the source position that corresponds to a
resource registration in the statefile.
Enabling source position information in the resource model can provide
substantial benefits, including but not limited to:
- Better errors from the Pulumi CLI
- Go-to-defintion for resources in state
- Editor integration for errors, etc. from `pulumi preview`
Source positions are (file, line) or (file, line, column) tuples
represented as URIs. The line and column are stored in the fragment
portion of the URI as "line(,column)?". The scheme of the URI and the
form of its path component depends on the context in which it is
generated or used:
- During an active update, the URI's scheme is `file` and paths are
absolute filesystem paths. This allows consumers to easily access
arbitrary files that are available on the host.
- In a statefile, the URI's scheme is `project` and paths are relative
to the project root. This allows consumers to resolve source positions
relative to the project file in different contexts irrespective of the
location of the project itself (e.g. given a project-relative path and
the URL of the project's root on GitHub, one can build a GitHub URL for
the source position).
During an update, source position information may be attached to gRPC
calls as "source-position" metadata. This allows arbitrary calls to be
associated with source positions without changes to their protobuf
payloads. Modifying the protobuf payloads is also a viable approach, but
is somewhat more invasive than attaching metadata, and requires changes
to every call signature.
Source positions should reflect the position in user code that initiated
a resource model operation (e.g. the source position passed with
`RegisterResource` for `pet` in the example above should be the source
position in `index.ts`, _not_ the source position in the Pulumi SDK). In
general, the Pulumi SDK should be able to infer the source position of
the resource registration, as the relationship between a resource
registration and its corresponding user code should be static per SDK.
Source positions in state files will be stored as a new `registeredAt`
property on each resource. This property is optional.
2023-06-29 18:41:19 +00:00
|
|
|
nil, nil, nil, "", false, "", nil, nil, "")
|
2022-01-20 11:18:54 +00:00
|
|
|
// TODO(seqnum) should default providers be created with 1? When do they ever get recreated/replaced?
|
2020-11-18 17:47:52 +00:00
|
|
|
if issueCheckErrors(i.deployment, state, urn, failures) {
|
2023-10-01 14:21:32 +00:00
|
|
|
return nil, false, nil
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
2023-11-10 13:31:11 +00:00
|
|
|
// Set a dummy goal so the resource is tracked as managed.
|
2024-04-09 10:56:25 +00:00
|
|
|
i.deployment.goals.Store(urn, &resource.Goal{})
|
2020-11-18 17:47:52 +00:00
|
|
|
steps[idx] = NewCreateStep(i.deployment, noopEvent(0), state)
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Issue the create steps.
|
|
|
|
if !i.executeParallel(ctx, steps...) {
|
2023-10-01 14:21:32 +00:00
|
|
|
return nil, false, nil
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update the URN to reference map.
|
|
|
|
for _, s := range steps {
|
|
|
|
res := s.Res()
|
|
|
|
id := res.ID
|
|
|
|
if i.preview {
|
|
|
|
id = providers.UnknownID
|
|
|
|
}
|
|
|
|
ref, err := providers.NewReference(res.URN, id)
|
2023-02-17 20:05:48 +00:00
|
|
|
contract.AssertNoErrorf(err, "could not create provider reference with URN %q and ID %q", res.URN, id)
|
2020-10-14 11:51:53 +00:00
|
|
|
urnToReference[res.URN] = ref.String()
|
|
|
|
}
|
|
|
|
|
2023-10-01 14:21:32 +00:00
|
|
|
return urnToReference, true, nil
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
2023-10-01 14:21:32 +00:00
|
|
|
func (i *importer) importResources(ctx context.Context) error {
|
2023-02-17 20:05:48 +00:00
|
|
|
contract.Assertf(len(i.deployment.imports) != 0, "no resources to import")
|
2020-10-14 11:51:53 +00:00
|
|
|
|
|
|
|
if !i.registerExistingResources(ctx) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
stackURN, createdStack, ok := i.getOrCreateStackResource(ctx)
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-01 14:21:32 +00:00
|
|
|
urnToReference, ok, err := i.registerProviders(ctx)
|
2020-10-14 11:51:53 +00:00
|
|
|
if !ok {
|
2023-10-01 14:21:32 +00:00
|
|
|
return err
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
2023-11-10 13:31:11 +00:00
|
|
|
// Create a step per resource to import and execute them in parallel batches which don't depend on each other.
|
|
|
|
// If there are duplicates, fail the import.
|
2020-10-14 11:51:53 +00:00
|
|
|
urns := map[resource.URN]struct{}{}
|
2023-06-28 16:02:04 +00:00
|
|
|
steps := slice.Prealloc[Step](len(i.deployment.imports))
|
2020-11-18 17:47:52 +00:00
|
|
|
for _, imp := range i.deployment.imports {
|
2020-10-14 11:51:53 +00:00
|
|
|
parent := imp.Parent
|
|
|
|
if parent == "" {
|
|
|
|
parent = stackURN
|
|
|
|
}
|
2020-11-18 17:47:52 +00:00
|
|
|
urn := i.deployment.generateURN(parent, imp.Type, imp.Name)
|
2020-10-14 11:51:53 +00:00
|
|
|
|
|
|
|
// Check for duplicate imports.
|
|
|
|
if _, has := urns[urn]; has {
|
2023-10-01 14:21:32 +00:00
|
|
|
return fmt.Errorf("duplicate import '%v' of type '%v'", imp.Name, imp.Type)
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
urns[urn] = struct{}{}
|
|
|
|
|
2023-11-10 13:31:11 +00:00
|
|
|
// If the resource already exists and the ID matches the ID to import, then Same this resource. If the ID does
|
2020-10-14 11:51:53 +00:00
|
|
|
// not match, the step itself will issue an error.
|
2020-11-18 17:47:52 +00:00
|
|
|
if old, ok := i.deployment.olds[urn]; ok {
|
2020-10-14 11:51:53 +00:00
|
|
|
oldID := old.ID
|
|
|
|
if old.ImportID != "" {
|
|
|
|
oldID = old.ImportID
|
|
|
|
}
|
|
|
|
if oldID == imp.ID {
|
2023-11-10 13:31:11 +00:00
|
|
|
// Clear the ID because Same asserts that the new state has no ID.
|
2024-05-09 16:15:41 +00:00
|
|
|
new := old.Copy()
|
2023-11-10 13:31:11 +00:00
|
|
|
new.ID = ""
|
|
|
|
// Set a dummy goal so the resource is tracked as managed.
|
2024-04-09 10:56:25 +00:00
|
|
|
i.deployment.goals.Store(old.URN, &resource.Goal{})
|
2024-05-09 16:15:41 +00:00
|
|
|
steps = append(steps, NewSameStep(i.deployment, noopEvent(0), old, new))
|
2020-10-14 11:51:53 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-13 17:58:35 +00:00
|
|
|
// If the resource already exists and the ID matches the ID to import, then Same this resource. If the ID does
|
|
|
|
// not match, the step itself will issue an error.
|
|
|
|
if old, ok := i.deployment.olds[urn]; ok {
|
|
|
|
oldID := old.ID
|
|
|
|
if old.ImportID != "" {
|
|
|
|
oldID = old.ImportID
|
|
|
|
}
|
|
|
|
if oldID == imp.ID {
|
|
|
|
// Clear the ID because Same asserts that the new state has no ID.
|
2024-05-09 16:15:41 +00:00
|
|
|
new := old.Copy()
|
2023-11-13 17:58:35 +00:00
|
|
|
new.ID = ""
|
|
|
|
// Set a dummy goal so the resource is tracked as managed.
|
2024-04-09 10:56:25 +00:00
|
|
|
i.deployment.goals.Store(old.URN, &resource.Goal{})
|
2024-05-09 16:15:41 +00:00
|
|
|
steps = append(steps, NewSameStep(i.deployment, noopEvent(0), old, new))
|
2023-11-13 17:58:35 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-14 11:51:53 +00:00
|
|
|
providerURN := imp.Provider
|
2023-11-13 17:58:35 +00:00
|
|
|
if providerURN == "" && (!imp.Component || imp.Remote) {
|
2023-09-11 15:54:07 +00:00
|
|
|
req := providers.NewProviderRequest(imp.Version, imp.Type.Package(), imp.PluginDownloadURL, imp.PluginChecksums)
|
2020-10-14 11:51:53 +00:00
|
|
|
typ, name := providers.MakeProviderType(req.Package()), req.Name()
|
2020-11-18 17:47:52 +00:00
|
|
|
providerURN = i.deployment.generateURN("", typ, name)
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
2023-11-13 17:58:35 +00:00
|
|
|
var provider string
|
|
|
|
if providerURN != "" {
|
|
|
|
// Fetch the provider reference for this import. All provider URNs should be mapped.
|
|
|
|
provider, ok = urnToReference[providerURN]
|
|
|
|
contract.Assertf(ok, "provider reference for URN %v not found", providerURN)
|
2022-08-09 16:40:59 +00:00
|
|
|
}
|
|
|
|
|
2023-11-13 17:58:35 +00:00
|
|
|
// Create the new desired state. Note that the resource is protected. Provider might be "" at this point.
|
|
|
|
new := resource.NewState(
|
|
|
|
urn.Type(), urn, !imp.Component, false, imp.ID, resource.PropertyMap{}, nil, parent, imp.Protect,
|
[engine] Add support for source positions
These changes add support for passing source position information in
gRPC metadata and recording the source position that corresponds to a
resource registration in the statefile.
Enabling source position information in the resource model can provide
substantial benefits, including but not limited to:
- Better errors from the Pulumi CLI
- Go-to-defintion for resources in state
- Editor integration for errors, etc. from `pulumi preview`
Source positions are (file, line) or (file, line, column) tuples
represented as URIs. The line and column are stored in the fragment
portion of the URI as "line(,column)?". The scheme of the URI and the
form of its path component depends on the context in which it is
generated or used:
- During an active update, the URI's scheme is `file` and paths are
absolute filesystem paths. This allows consumers to easily access
arbitrary files that are available on the host.
- In a statefile, the URI's scheme is `project` and paths are relative
to the project root. This allows consumers to resolve source positions
relative to the project file in different contexts irrespective of the
location of the project itself (e.g. given a project-relative path and
the URL of the project's root on GitHub, one can build a GitHub URL for
the source position).
During an update, source position information may be attached to gRPC
calls as "source-position" metadata. This allows arbitrary calls to be
associated with source positions without changes to their protobuf
payloads. Modifying the protobuf payloads is also a viable approach, but
is somewhat more invasive than attaching metadata, and requires changes
to every call signature.
Source positions should reflect the position in user code that initiated
a resource model operation (e.g. the source position passed with
`RegisterResource` for `pet` in the example above should be the source
position in `index.ts`, _not_ the source position in the Pulumi SDK). In
general, the Pulumi SDK should be able to infer the source position of
the resource registration, as the relationship between a resource
registration and its corresponding user code should be static per SDK.
Source positions in state files will be stored as a new `registeredAt`
property on each resource. This property is optional.
2023-06-29 18:41:19 +00:00
|
|
|
false, nil, nil, provider, nil, false, nil, nil, nil, "", false, "", nil, nil, "")
|
2023-11-10 13:31:11 +00:00
|
|
|
// Set a dummy goal so the resource is tracked as managed.
|
2024-04-09 10:56:25 +00:00
|
|
|
i.deployment.goals.Store(urn, &resource.Goal{})
|
2023-11-13 17:58:35 +00:00
|
|
|
|
|
|
|
if imp.Component {
|
|
|
|
if imp.Remote {
|
|
|
|
contract.Assertf(ok, "provider reference for URN %v not found", providerURN)
|
|
|
|
}
|
|
|
|
|
|
|
|
steps = append(steps, newImportDeploymentStep(i.deployment, new, nil))
|
|
|
|
} else {
|
|
|
|
contract.Assertf(ok, "provider reference for URN %v not found", providerURN)
|
|
|
|
|
|
|
|
// If we have a plan for this resource we need to feed the saved seed to Check to remove non-determinism
|
|
|
|
var randomSeed []byte
|
|
|
|
if i.deployment.plan != nil {
|
|
|
|
if resourcePlan, ok := i.deployment.plan.ResourcePlans[urn]; ok {
|
|
|
|
randomSeed = resourcePlan.Seed
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
randomSeed = make([]byte, 32)
|
|
|
|
n, err := cryptorand.Read(randomSeed)
|
|
|
|
contract.AssertNoErrorf(err, "could not read random bytes")
|
|
|
|
contract.Assertf(n == len(randomSeed), "read %d random bytes, expected %d", n, len(randomSeed))
|
|
|
|
}
|
|
|
|
|
|
|
|
steps = append(steps, newImportDeploymentStep(i.deployment, new, randomSeed))
|
|
|
|
}
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
2023-11-10 13:31:11 +00:00
|
|
|
// We've created all the steps above but we need to execute them in parallel batches which don't depend on each other
|
|
|
|
for len(urns) > 0 {
|
|
|
|
// Find all the steps that can be executed in parallel. `urns` is a map of every resource we still
|
|
|
|
// need to import so if we need a resource from that map we can't yet build this resource.
|
|
|
|
parallelSteps := []Step{}
|
|
|
|
for _, step := range steps {
|
|
|
|
// If we've already done this step don't do it again
|
|
|
|
if _, ok := urns[step.New().URN]; !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the step has no dependencies (we actually only need to look at parent), it can be executed in parallel
|
|
|
|
if _, ok := urns[step.New().Parent]; !ok {
|
|
|
|
parallelSteps = append(parallelSteps, step)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove all the urns we're about to import
|
|
|
|
for _, step := range parallelSteps {
|
|
|
|
delete(urns, step.New().URN)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !i.executeParallel(ctx, parallelSteps...) {
|
|
|
|
return nil
|
|
|
|
}
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if createdStack {
|
2023-10-01 14:21:32 +00:00
|
|
|
return i.executor.ExecuteRegisterResourceOutputs(noopOutputsEvent(stackURN))
|
2020-10-14 11:51:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|