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 }