github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/edit/operations_test.go (about) 1 // Copyright 2016-2022, 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 edit 16 17 import ( 18 "testing" 19 "time" 20 21 "github.com/pulumi/pulumi/pkg/v3/secrets/b64" 22 23 "github.com/pulumi/pulumi/pkg/v3/resource/deploy" 24 "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" 25 "github.com/pulumi/pulumi/pkg/v3/version" 26 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 27 "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" 28 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 ) 32 33 func NewResource(name string, provider *resource.State, deps ...resource.URN) *resource.State { 34 prov := "" 35 if provider != nil { 36 p, err := providers.NewReference(provider.URN, provider.ID) 37 if err != nil { 38 panic(err) 39 } 40 prov = p.String() 41 } 42 43 t := tokens.Type("a:b:c") 44 return &resource.State{ 45 Type: t, 46 URN: resource.NewURN("test", "test", "", t, tokens.QName(name)), 47 Inputs: resource.PropertyMap{}, 48 Outputs: resource.PropertyMap{}, 49 Dependencies: deps, 50 Provider: prov, 51 } 52 } 53 54 func NewProviderResource(pkg, name, id string, deps ...resource.URN) *resource.State { 55 t := providers.MakeProviderType(tokens.Package(pkg)) 56 return &resource.State{ 57 Type: t, 58 URN: resource.NewURN("test", "test", "", t, tokens.QName(name)), 59 ID: resource.ID(id), 60 Inputs: resource.PropertyMap{}, 61 Outputs: resource.PropertyMap{}, 62 Dependencies: deps, 63 } 64 } 65 66 func NewSnapshot(resources []*resource.State) *deploy.Snapshot { 67 return deploy.NewSnapshot(deploy.Manifest{ 68 Time: time.Now(), 69 Version: version.Version, 70 Plugins: nil, 71 }, b64.NewBase64SecretsManager(), resources, nil) 72 } 73 74 func TestDeletion(t *testing.T) { 75 t.Parallel() 76 77 pA := NewProviderResource("a", "p1", "0") 78 a := NewResource("a", pA) 79 b := NewResource("b", pA) 80 c := NewResource("c", pA) 81 snap := NewSnapshot([]*resource.State{ 82 pA, 83 a, 84 b, 85 c, 86 }) 87 88 err := DeleteResource(snap, b, nil, false) 89 assert.NoError(t, err) 90 assert.Len(t, snap.Resources, 3) 91 assert.Equal(t, []*resource.State{pA, a, c}, snap.Resources) 92 } 93 94 func TestDeletingDependencies(t *testing.T) { 95 t.Parallel() 96 97 pA := NewProviderResource("a", "p1", "0") 98 a := NewResource("a", pA) 99 b := NewResource("b", pA) 100 c := NewResource("c", pA, a.URN) 101 d := NewResource("d", pA, c.URN) 102 snap := NewSnapshot([]*resource.State{ 103 pA, a, b, c, d, 104 }) 105 106 err := DeleteResource(snap, a, nil, true) 107 require.NoError(t, err) 108 109 assert.Equal(t, snap.Resources, []*resource.State{pA, b}) 110 } 111 112 func TestFailedDeletionProviderDependency(t *testing.T) { 113 t.Parallel() 114 115 pA := NewProviderResource("a", "p1", "0") 116 a := NewResource("a", pA) 117 b := NewResource("b", pA) 118 c := NewResource("c", pA) 119 snap := NewSnapshot([]*resource.State{ 120 pA, 121 a, 122 b, 123 c, 124 }) 125 126 err := DeleteResource(snap, pA, nil, false) 127 assert.Error(t, err) 128 depErr, ok := err.(ResourceHasDependenciesError) 129 if !assert.True(t, ok) { 130 t.FailNow() 131 } 132 133 assert.Contains(t, depErr.Dependencies, a) 134 assert.Contains(t, depErr.Dependencies, b) 135 assert.Contains(t, depErr.Dependencies, c) 136 assert.Len(t, snap.Resources, 4) 137 assert.Equal(t, []*resource.State{pA, a, b, c}, snap.Resources) 138 } 139 140 func TestFailedDeletionRegularDependency(t *testing.T) { 141 t.Parallel() 142 143 pA := NewProviderResource("a", "p1", "0") 144 a := NewResource("a", pA) 145 b := NewResource("b", pA, a.URN) 146 c := NewResource("c", pA) 147 snap := NewSnapshot([]*resource.State{ 148 pA, 149 a, 150 b, 151 c, 152 }) 153 154 err := DeleteResource(snap, a, nil, false) 155 assert.Error(t, err) 156 depErr, ok := err.(ResourceHasDependenciesError) 157 if !assert.True(t, ok) { 158 t.FailNow() 159 } 160 161 assert.NotContains(t, depErr.Dependencies, pA) 162 assert.NotContains(t, depErr.Dependencies, a) 163 assert.Contains(t, depErr.Dependencies, b) 164 assert.NotContains(t, depErr.Dependencies, c) 165 assert.Len(t, snap.Resources, 4) 166 assert.Equal(t, []*resource.State{pA, a, b, c}, snap.Resources) 167 } 168 169 func TestFailedDeletionProtected(t *testing.T) { 170 t.Parallel() 171 172 pA := NewProviderResource("a", "p1", "0") 173 a := NewResource("a", pA) 174 a.Protect = true 175 snap := NewSnapshot([]*resource.State{ 176 pA, 177 a, 178 }) 179 180 err := DeleteResource(snap, a, nil, false) 181 assert.Error(t, err) 182 _, ok := err.(ResourceProtectedError) 183 assert.True(t, ok) 184 } 185 186 func TestDeleteProtected(t *testing.T) { 187 t.Parallel() 188 189 tests := []struct { 190 name string 191 test func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) 192 }{ 193 { 194 "root-protected", 195 func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) { 196 a.Protect = true 197 protectedCount := 0 198 err := DeleteResource(snap, a, func(s *resource.State) error { 199 s.Protect = false 200 protectedCount++ 201 return nil 202 }, false) 203 assert.NoError(t, err) 204 assert.Equal(t, protectedCount, 1) 205 assert.Equal(t, snap.Resources, []*resource.State{pA, b, c}) 206 }, 207 }, 208 { 209 "root-and-branch", 210 func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) { 211 a.Protect = true 212 b.Protect = true 213 c.Protect = true 214 protectedCount := 0 215 err := DeleteResource(snap, b, func(s *resource.State) error { 216 s.Protect = false 217 protectedCount++ 218 return nil 219 }, true) 220 assert.NoError(t, err) 221 // 2 because we only plan to delete b and c. a is protected but not 222 // scheduled for deletion, so we don't call the onProtect handler. 223 assert.Equal(t, protectedCount, 2) 224 assert.Equal(t, snap.Resources, []*resource.State{pA, a}) 225 226 }, 227 }, 228 { 229 "branch", 230 func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) { 231 b.Protect = true 232 c.Protect = true 233 protectedCount := 0 234 err := DeleteResource(snap, c, func(s *resource.State) error { 235 s.Protect = false 236 protectedCount++ 237 return nil 238 }, false) 239 assert.NoError(t, err) 240 assert.Equal(t, protectedCount, 1) 241 assert.Equal(t, snap.Resources, []*resource.State{pA, a, b}) 242 }, 243 }, 244 { 245 "no-permission-root", 246 func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) { 247 c.Protect = true 248 err := DeleteResource(snap, c, nil, false).(ResourceProtectedError) 249 assert.Equal(t, ResourceProtectedError{ 250 Condemned: c, 251 }, err) 252 }, 253 }, 254 { 255 "no-permission-branch", 256 func(t *testing.T, pA, a, b, c *resource.State, snap *deploy.Snapshot) { 257 c.Protect = true 258 err := DeleteResource(snap, b, nil, true).(ResourceProtectedError) 259 assert.Equal(t, ResourceProtectedError{ 260 Condemned: c, 261 }, err) 262 }, 263 }, 264 } 265 for _, tt := range tests { 266 tt := tt 267 t.Run(tt.name, func(t *testing.T) { 268 t.Parallel() 269 pA := NewProviderResource("a", "p1", "0") 270 a := NewResource("a", pA) 271 b := NewResource("b", pA) 272 c := NewResource("c", pA, b.URN) 273 snap := NewSnapshot([]*resource.State{ 274 pA, 275 a, 276 b, 277 c, 278 }) 279 280 tt.test(t, pA, a, b, c, snap) 281 }) 282 } 283 } 284 285 func TestFailedDeletionParentDependency(t *testing.T) { 286 t.Parallel() 287 288 pA := NewProviderResource("a", "p1", "0") 289 a := NewResource("a", pA) 290 b := NewResource("b", pA) 291 b.Parent = a.URN 292 c := NewResource("c", pA) 293 c.Parent = a.URN 294 snap := NewSnapshot([]*resource.State{ 295 pA, 296 a, 297 b, 298 c, 299 }) 300 301 err := DeleteResource(snap, a, nil, false) 302 assert.Error(t, err) 303 depErr, ok := err.(ResourceHasDependenciesError) 304 if !assert.True(t, ok) { 305 t.FailNow() 306 } 307 308 assert.NotContains(t, depErr.Dependencies, pA) 309 assert.NotContains(t, depErr.Dependencies, a) 310 assert.Contains(t, depErr.Dependencies, b) 311 assert.Contains(t, depErr.Dependencies, c) 312 assert.Len(t, snap.Resources, 4) 313 assert.Equal(t, []*resource.State{pA, a, b, c}, snap.Resources) 314 } 315 316 func TestUnprotectResource(t *testing.T) { 317 t.Parallel() 318 319 pA := NewProviderResource("a", "p1", "0") 320 a := NewResource("a", pA) 321 a.Protect = true 322 b := NewResource("b", pA) 323 c := NewResource("c", pA) 324 snap := NewSnapshot([]*resource.State{ 325 pA, 326 a, 327 b, 328 c, 329 }) 330 331 err := UnprotectResource(snap, a) 332 assert.NoError(t, err) 333 assert.Len(t, snap.Resources, 4) 334 assert.Equal(t, []*resource.State{pA, a, b, c}, snap.Resources) 335 assert.False(t, a.Protect) 336 } 337 338 func TestLocateResourceNotFound(t *testing.T) { 339 t.Parallel() 340 341 pA := NewProviderResource("a", "p1", "0") 342 a := NewResource("a", pA) 343 b := NewResource("b", pA) 344 c := NewResource("c", pA) 345 snap := NewSnapshot([]*resource.State{ 346 pA, 347 a, 348 b, 349 c, 350 }) 351 352 ty := tokens.Type("a:b:c") 353 urn := resource.NewURN("test", "test", "", ty, "not-present") 354 resList := LocateResource(snap, urn) 355 assert.Nil(t, resList) 356 } 357 358 func TestLocateResourceAmbiguous(t *testing.T) { 359 t.Parallel() 360 361 pA := NewProviderResource("a", "p1", "0") 362 a := NewResource("a", pA) 363 b := NewResource("b", pA) 364 aPending := NewResource("a", pA) 365 aPending.Delete = true 366 snap := NewSnapshot([]*resource.State{ 367 pA, 368 a, 369 b, 370 aPending, 371 }) 372 373 resList := LocateResource(snap, a.URN) 374 assert.Len(t, resList, 2) 375 assert.Contains(t, resList, a) 376 assert.Contains(t, resList, aPending) 377 assert.NotContains(t, resList, pA) 378 assert.NotContains(t, resList, b) 379 } 380 381 func TestLocateResourceExact(t *testing.T) { 382 t.Parallel() 383 384 pA := NewProviderResource("a", "p1", "0") 385 a := NewResource("a", pA) 386 b := NewResource("b", pA) 387 c := NewResource("c", pA) 388 snap := NewSnapshot([]*resource.State{ 389 pA, 390 a, 391 b, 392 c, 393 }) 394 395 resList := LocateResource(snap, a.URN) 396 assert.Len(t, resList, 1) 397 assert.Contains(t, resList, a) 398 } 399 400 func TestRenameStack(t *testing.T) { 401 t.Parallel() 402 403 pA := NewProviderResource("a", "p1", "0") 404 a := NewResource("a", pA) 405 b := NewResource("b", pA) 406 c := NewResource("c", pA) 407 snap := NewSnapshot([]*resource.State{ 408 pA, 409 a, 410 b, 411 c, 412 }) 413 414 // Baseline. Can locate resource A. 415 resList := LocateResource(snap, a.URN) 416 assert.Len(t, resList, 1) 417 assert.Contains(t, resList, a) 418 if t.Failed() { 419 t.Fatal("Unable to find expected resource in initial snapshot.") 420 } 421 baselineResourceURN := resList[0].URN 422 423 // The stack name and project are hard-coded in NewResource(...) 424 assert.EqualValues(t, "test", baselineResourceURN.Stack()) 425 assert.EqualValues(t, "test", baselineResourceURN.Project()) 426 427 // Rename just the stack. 428 //nolint:paralleltest // uses shared stack 429 t.Run("JustTheStack", func(t *testing.T) { 430 err := RenameStack(snap, tokens.Name("new-stack"), tokens.PackageName("")) 431 if err != nil { 432 t.Fatalf("Error renaming stack: %v", err) 433 } 434 435 // Confirm the previous resource by URN isn't found. 436 assert.Len(t, LocateResource(snap, baselineResourceURN), 0) 437 438 // Confirm the resource has been renamed. 439 updatedResourceURN := resource.NewURN( 440 tokens.QName("new-stack"), 441 "test", // project name stayed the same 442 "" /*parent type*/, baselineResourceURN.Type(), 443 baselineResourceURN.Name()) 444 assert.Len(t, LocateResource(snap, updatedResourceURN), 1) 445 }) 446 447 // Rename the stack and project. 448 //nolint:paralleltest // uses shared stack 449 t.Run("StackAndProject", func(t *testing.T) { 450 err := RenameStack(snap, tokens.Name("new-stack2"), tokens.PackageName("new-project")) 451 if err != nil { 452 t.Fatalf("Error renaming stack: %v", err) 453 } 454 455 // Lookup the resource by URN, with both stack and project updated. 456 updatedResourceURN := resource.NewURN( 457 tokens.QName("new-stack2"), 458 "new-project", 459 "" /*parent type*/, baselineResourceURN.Type(), 460 baselineResourceURN.Name()) 461 assert.Len(t, LocateResource(snap, updatedResourceURN), 1) 462 }) 463 }