github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/deploy/import.go (about)

     1  // Copyright 2016-2020, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package deploy
    16  
    17  import (
    18  	"context"
    19  	cryptorand "crypto/rand"
    20  	"fmt"
    21  	"sort"
    22  
    23  	"github.com/blang/semver"
    24  	"github.com/pulumi/pulumi/pkg/v3/codegen/schema"
    25  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
    26  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    27  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
    28  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    29  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
    31  )
    32  
    33  // An Import specifies a resource to import.
    34  type Import struct {
    35  	Type              tokens.Type     // The type token for the resource. Required.
    36  	Name              tokens.QName    // The name of the resource. Required.
    37  	ID                resource.ID     // The ID of the resource. Required.
    38  	Parent            resource.URN    // The parent of the resource, if any.
    39  	Provider          resource.URN    // The specific provider to use for the resource, if any.
    40  	Version           *semver.Version // The provider version to use for the resource, if any.
    41  	PluginDownloadURL string          // The provider PluginDownloadURL to use for the resource, if any.
    42  	Protect           bool            // Whether to mark the resource as protected after import
    43  	Properties        []string        // Which properties to include (Defaults to required properties)
    44  }
    45  
    46  // ImportOptions controls the import process.
    47  type ImportOptions struct {
    48  	Events   Events // an optional events callback interface.
    49  	Parallel int    // the degree of parallelism for resource operations (<=1 for serial).
    50  }
    51  
    52  // NewImportDeployment creates a new import deployment from a resource snapshot plus a set of resources to import.
    53  //
    54  // From the old and new states, it understands how to orchestrate an evaluation and analyze the resulting resources.
    55  // The deployment may be used to simply inspect a series of operations, or actually perform them; these operations are
    56  // generated based on analysis of the old and new states.  If a resource exists in new, but not old, for example, it
    57  // results in a create; if it exists in both, but is different, it results in an update; and so on and so forth.
    58  //
    59  // Note that a deployment uses internal concurrency and parallelism in various ways, so it must be closed if for some
    60  // reason it isn't carried out to its final conclusion. This will result in cancellation and reclamation of resources.
    61  func NewImportDeployment(ctx *plugin.Context, target *Target, projectName tokens.PackageName, imports []Import,
    62  	preview bool) (*Deployment, error) {
    63  
    64  	contract.Assert(ctx != nil)
    65  	contract.Assert(target != nil)
    66  
    67  	prev := target.Snapshot
    68  	source := NewErrorSource(projectName)
    69  	if err := migrateProviders(target, prev, source); err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	// Produce a map of all old resources for fast access.
    74  	oldResources, olds, err := buildResourceMap(prev, preview)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	// Create a goal map for the deployment.
    80  	newGoals := &goalMap{}
    81  
    82  	builtins := newBuiltinProvider(nil, nil)
    83  
    84  	// Create a new provider registry.
    85  	reg, err := providers.NewRegistry(ctx.Host, oldResources, preview, builtins)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	// Return the prepared deployment.
    91  	return &Deployment{
    92  		ctx:          ctx,
    93  		target:       target,
    94  		prev:         prev,
    95  		olds:         olds,
    96  		goals:        newGoals,
    97  		imports:      imports,
    98  		isImport:     true,
    99  		schemaLoader: schema.NewPluginLoader(ctx.Host),
   100  		source:       NewErrorSource(projectName),
   101  		preview:      preview,
   102  		providers:    reg,
   103  		newPlans:     newResourcePlan(target.Config),
   104  	}, nil
   105  }
   106  
   107  type noopEvent int
   108  
   109  func (noopEvent) event()                      {}
   110  func (noopEvent) Goal() *resource.Goal        { return nil }
   111  func (noopEvent) Done(result *RegisterResult) {}
   112  
   113  type noopOutputsEvent resource.URN
   114  
   115  func (noopOutputsEvent) event()                        {}
   116  func (e noopOutputsEvent) URN() resource.URN           { return resource.URN(e) }
   117  func (noopOutputsEvent) Outputs() resource.PropertyMap { return resource.PropertyMap{} }
   118  func (noopOutputsEvent) Done()                         {}
   119  
   120  type importer struct {
   121  	deployment *Deployment
   122  	executor   *stepExecutor
   123  	preview    bool
   124  }
   125  
   126  func (i *importer) executeSerial(ctx context.Context, steps ...Step) bool {
   127  	return i.wait(ctx, i.executor.ExecuteSerial(steps))
   128  }
   129  
   130  func (i *importer) executeParallel(ctx context.Context, steps ...Step) bool {
   131  	return i.wait(ctx, i.executor.ExecuteParallel(steps))
   132  }
   133  
   134  func (i *importer) wait(ctx context.Context, token completionToken) bool {
   135  	token.Wait(ctx)
   136  	return ctx.Err() == nil && !i.executor.Errored()
   137  }
   138  
   139  func (i *importer) registerExistingResources(ctx context.Context) bool {
   140  	if i != nil && i.deployment != nil && i.deployment.prev != nil {
   141  		// Issue same steps per existing resource to make sure that they are recorded in the snapshot.
   142  		// We issue these steps serially s.t. the resources remain in the order in which they appear in the state.
   143  		for _, r := range i.deployment.prev.Resources {
   144  			if r.Delete {
   145  				continue
   146  			}
   147  
   148  			new := *r
   149  			new.ID = ""
   150  			if !i.executeSerial(ctx, NewSameStep(i.deployment, noopEvent(0), r, &new)) {
   151  				return false
   152  			}
   153  		}
   154  	}
   155  	return true
   156  }
   157  
   158  func (i *importer) getOrCreateStackResource(ctx context.Context) (resource.URN, bool, bool) {
   159  	// Get or create the root resource.
   160  	if i.deployment.prev != nil {
   161  		for _, res := range i.deployment.prev.Resources {
   162  			if res.Type == resource.RootStackType {
   163  				return res.URN, false, true
   164  			}
   165  		}
   166  	}
   167  
   168  	projectName, stackName := i.deployment.source.Project(), i.deployment.target.Name
   169  	typ, name := resource.RootStackType, fmt.Sprintf("%s-%s", projectName, stackName)
   170  	urn := resource.NewURN(stackName.Q(), projectName, "", typ, tokens.QName(name))
   171  	state := resource.NewState(typ, urn, false, false, "", resource.PropertyMap{}, nil, "", false, false, nil, nil, "",
   172  		nil, false, nil, nil, nil, "", false, "")
   173  	// TODO(seqnum) should stacks be created with 1? When do they ever get recreated/replaced?
   174  	if !i.executeSerial(ctx, NewCreateStep(i.deployment, noopEvent(0), state)) {
   175  		return "", false, false
   176  	}
   177  	return urn, true, true
   178  }
   179  
   180  func (i *importer) registerProviders(ctx context.Context) (map[resource.URN]string, result.Result, bool) {
   181  	urnToReference := map[resource.URN]string{}
   182  
   183  	// Determine which default providers are not present in the state. If all default providers are accounted for,
   184  	// we're done.
   185  	//
   186  	// NOTE: what if the configuration for an existing default provider has changed? If it has, we should diff it and
   187  	// replace it appropriately or we should not use the ambient config at all.
   188  	var defaultProviderRequests []providers.ProviderRequest
   189  	defaultProviders := map[resource.URN]struct{}{}
   190  	for _, imp := range i.deployment.imports {
   191  		if imp.Provider != "" {
   192  			// If the provider for this import exists, map its URN to its provider reference. If it does not exist,
   193  			// the import step will issue an appropriate error or errors.
   194  			ref := string(imp.Provider)
   195  			if state, ok := i.deployment.olds[imp.Provider]; ok {
   196  				r, err := providers.NewReference(imp.Provider, state.ID)
   197  				contract.AssertNoError(err)
   198  				ref = r.String()
   199  			}
   200  			urnToReference[imp.Provider] = ref
   201  			continue
   202  		}
   203  
   204  		if imp.Type.Package() == "" {
   205  			return nil, result.Error("incorrect package type specified"), false
   206  		}
   207  		req := providers.NewProviderRequest(imp.Version, imp.Type.Package(), imp.PluginDownloadURL)
   208  		typ, name := providers.MakeProviderType(req.Package()), req.Name()
   209  		urn := i.deployment.generateURN("", typ, name)
   210  		if state, ok := i.deployment.olds[urn]; ok {
   211  			ref, err := providers.NewReference(urn, state.ID)
   212  			contract.AssertNoError(err)
   213  			urnToReference[urn] = ref.String()
   214  			continue
   215  		}
   216  		if _, ok := defaultProviders[urn]; ok {
   217  			continue
   218  		}
   219  
   220  		defaultProviderRequests = append(defaultProviderRequests, req)
   221  		defaultProviders[urn] = struct{}{}
   222  	}
   223  	if len(defaultProviderRequests) == 0 {
   224  		return urnToReference, nil, true
   225  	}
   226  
   227  	steps := make([]Step, len(defaultProviderRequests))
   228  	sort.Slice(defaultProviderRequests, func(i, j int) bool {
   229  		return defaultProviderRequests[i].String() < defaultProviderRequests[j].String()
   230  	})
   231  	for idx, req := range defaultProviderRequests {
   232  		if req.Package() == "" {
   233  			return nil, result.Error("incorrect package type specified"), false
   234  		}
   235  
   236  		typ, name := providers.MakeProviderType(req.Package()), req.Name()
   237  		urn := i.deployment.generateURN("", typ, name)
   238  
   239  		// Fetch, prepare, and check the configuration for this provider.
   240  		inputs, err := i.deployment.target.GetPackageConfig(req.Package())
   241  		if err != nil {
   242  			return nil, result.Errorf("failed to fetch provider config: %v", err), false
   243  		}
   244  
   245  		// Calculate the inputs for the provider using the ambient config.
   246  		if v := req.Version(); v != nil {
   247  			providers.SetProviderVersion(inputs, v)
   248  		}
   249  		if url := req.PluginDownloadURL(); url != "" {
   250  			providers.SetProviderURL(inputs, url)
   251  		}
   252  		inputs, failures, err := i.deployment.providers.Check(urn, nil, inputs, false, nil)
   253  		if err != nil {
   254  			return nil, result.Errorf("failed to validate provider config: %v", err), false
   255  		}
   256  
   257  		state := resource.NewState(typ, urn, true, false, "", inputs, nil, "", false, false, nil, nil, "", nil, false,
   258  			nil, nil, nil, "", false, "")
   259  		// TODO(seqnum) should default providers be created with 1? When do they ever get recreated/replaced?
   260  		if issueCheckErrors(i.deployment, state, urn, failures) {
   261  			return nil, nil, false
   262  		}
   263  
   264  		steps[idx] = NewCreateStep(i.deployment, noopEvent(0), state)
   265  	}
   266  
   267  	// Issue the create steps.
   268  	if !i.executeParallel(ctx, steps...) {
   269  		return nil, nil, false
   270  	}
   271  
   272  	// Update the URN to reference map.
   273  	for _, s := range steps {
   274  		res := s.Res()
   275  		id := res.ID
   276  		if i.preview {
   277  			id = providers.UnknownID
   278  		}
   279  		ref, err := providers.NewReference(res.URN, id)
   280  		contract.AssertNoError(err)
   281  		urnToReference[res.URN] = ref.String()
   282  	}
   283  
   284  	return urnToReference, nil, true
   285  }
   286  
   287  func (i *importer) importResources(ctx context.Context) result.Result {
   288  	contract.Assert(len(i.deployment.imports) != 0)
   289  
   290  	if !i.registerExistingResources(ctx) {
   291  		return nil
   292  	}
   293  
   294  	stackURN, createdStack, ok := i.getOrCreateStackResource(ctx)
   295  	if !ok {
   296  		return nil
   297  	}
   298  
   299  	urnToReference, res, ok := i.registerProviders(ctx)
   300  	if !ok {
   301  		return res
   302  	}
   303  
   304  	// Create a step per resource to import and execute them in parallel. If there are duplicates, fail the import.
   305  	urns := map[resource.URN]struct{}{}
   306  	steps := make([]Step, 0, len(i.deployment.imports))
   307  	for _, imp := range i.deployment.imports {
   308  		parent := imp.Parent
   309  		if parent == "" {
   310  			parent = stackURN
   311  		}
   312  		urn := i.deployment.generateURN(parent, imp.Type, imp.Name)
   313  
   314  		// Check for duplicate imports.
   315  		if _, has := urns[urn]; has {
   316  			return result.Errorf("duplicate import '%v' of type '%v'", imp.Name, imp.Type)
   317  		}
   318  		urns[urn] = struct{}{}
   319  
   320  		// If the resource already exists and the ID matches the ID to import, skip this resource. If the ID does
   321  		// not match, the step itself will issue an error.
   322  		if old, ok := i.deployment.olds[urn]; ok {
   323  			oldID := old.ID
   324  			if old.ImportID != "" {
   325  				oldID = old.ImportID
   326  			}
   327  			if oldID == imp.ID {
   328  				continue
   329  			}
   330  		}
   331  
   332  		providerURN := imp.Provider
   333  		if providerURN == "" {
   334  			req := providers.NewProviderRequest(imp.Version, imp.Type.Package(), imp.PluginDownloadURL)
   335  			typ, name := providers.MakeProviderType(req.Package()), req.Name()
   336  			providerURN = i.deployment.generateURN("", typ, name)
   337  		}
   338  
   339  		// Fetch the provider reference for this import. All provider URNs should be mapped.
   340  		provider, ok := urnToReference[providerURN]
   341  		contract.Assert(ok)
   342  
   343  		// If we have a plan for this resource we need to feed the saved seed to Check to remove non-determinism
   344  		var randomSeed []byte
   345  		if i.deployment.plan != nil {
   346  			if resourcePlan, ok := i.deployment.plan.ResourcePlans[urn]; ok {
   347  				randomSeed = resourcePlan.Seed
   348  			}
   349  		} else {
   350  			randomSeed = make([]byte, 32)
   351  			n, err := cryptorand.Read(randomSeed)
   352  			contract.AssertNoError(err)
   353  			contract.Assert(n == len(randomSeed))
   354  		}
   355  
   356  		// Create the new desired state. Note that the resource is protected.
   357  		new := resource.NewState(urn.Type(), urn, true, false, imp.ID, resource.PropertyMap{}, nil, parent, imp.Protect,
   358  			false, nil, nil, provider, nil, false, nil, nil, nil, "", false, "")
   359  		steps = append(steps, newImportDeploymentStep(i.deployment, new, randomSeed))
   360  	}
   361  
   362  	if !i.executeParallel(ctx, steps...) {
   363  		return nil
   364  	}
   365  
   366  	if createdStack {
   367  		i.executor.ExecuteRegisterResourceOutputs(noopOutputsEvent(stackURN))
   368  	}
   369  
   370  	return nil
   371  }