github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/gce_image_publish/publish/publish_test.go (about) 1 // Copyright 2017 Google Inc. All Rights Reserved. 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 publish 16 17 import ( 18 "context" 19 "reflect" 20 "testing" 21 "time" 22 23 daisy "github.com/GoogleCloudPlatform/compute-daisy" 24 "github.com/kylelemons/godebug/pretty" 25 computeAlpha "google.golang.org/api/compute/v0.alpha" 26 "google.golang.org/api/compute/v1" 27 ) 28 29 func TestPublishImage(t *testing.T) { 30 now := time.Now() 31 fakeInitialState := &computeAlpha.InitialStateConfig{ 32 Dbs: []*computeAlpha.FileContentBuffer{ 33 { 34 Content: "abc", 35 FileType: "BIN", 36 }, 37 }, 38 Dbxs: []*computeAlpha.FileContentBuffer{ 39 { 40 Content: "abc", 41 FileType: "X509", 42 }, 43 }, 44 NullFields: []string{"Keks", "Pk"}, 45 } 46 47 tests := []struct { 48 desc string 49 p *Publish 50 img *Image 51 pubImgs []*computeAlpha.Image 52 skipDup bool 53 replace bool 54 noRoot bool 55 wantCI *daisy.CreateImages 56 wantDI *daisy.DeprecateImages 57 wantErr bool 58 }{ 59 { 60 desc: "normal case", 61 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 62 img: &Image{ 63 Prefix: "foo", 64 Family: "foo-family", 65 GuestOsFeatures: []string{"foo-feature", "bar-feature"}, 66 ObsoleteDate: &now, 67 RolloutPolicy: &computeAlpha.RolloutPolicy{ 68 DefaultRolloutTime: now.Format(time.RFC3339), 69 }, 70 }, 71 pubImgs: []*computeAlpha.Image{ 72 {Name: "bar-2", Family: "bar-family"}, 73 {Name: "foo-2", Family: "foo-family"}, 74 { 75 Name: "foo-1", 76 Family: "foo-family", 77 Deprecated: &computeAlpha.DeprecationStatus{ 78 State: "DEPRECATED", 79 StateOverride: &computeAlpha.RolloutPolicy{ 80 DefaultRolloutTime: now.Format(time.RFC3339), 81 }, 82 }, 83 }, 84 { 85 Name: "bar-1", 86 Family: "bar-family", 87 Deprecated: &computeAlpha.DeprecationStatus{ 88 State: "DEPRECATED", 89 StateOverride: &computeAlpha.RolloutPolicy{ 90 DefaultRolloutTime: now.Format(time.RFC3339), 91 }, 92 }, 93 }, 94 }, 95 wantCI: &daisy.CreateImages{ 96 ImagesAlpha: []*daisy.ImageAlpha{ 97 { 98 ImageBase: daisy.ImageBase{Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-3"}}, 99 Image: computeAlpha.Image{ 100 Name: "foo-3", Family: "foo-family", 101 SourceImage: "projects/bar-project/global/images/foo-3", 102 RolloutOverride: &computeAlpha.RolloutPolicy{ 103 DefaultRolloutTime: now.Format(time.RFC3339), 104 }, 105 Deprecated: &computeAlpha.DeprecationStatus{ 106 State: "ACTIVE", 107 Obsolete: now.Format(time.RFC3339), 108 }, 109 }, 110 GuestOsFeatures: []string{"foo-feature", "bar-feature"}, 111 }, 112 }, 113 }, 114 wantDI: &daisy.DeprecateImages{ 115 { 116 Image: "foo-2", 117 Project: "foo-project", 118 DeprecationStatusAlpha: computeAlpha.DeprecationStatus{ 119 State: "DEPRECATED", 120 Replacement: "https://www.googleapis.com/compute/v1/projects/foo-project/global/images/foo-3", 121 StateOverride: &computeAlpha.RolloutPolicy{ 122 DefaultRolloutTime: now.Format(time.RFC3339), 123 }, 124 }, 125 }, 126 }, 127 }, 128 { 129 desc: "multiple images to deprecate", 130 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 131 img: &Image{ 132 Prefix: "foo", 133 Family: "foo-family", 134 RolloutPolicy: &computeAlpha.RolloutPolicy{ 135 DefaultRolloutTime: now.Format(time.RFC3339), 136 }, 137 }, 138 pubImgs: []*computeAlpha.Image{ 139 {Name: "bar-2", Family: "bar-family"}, 140 {Name: "foo-2", Family: "foo-family"}, 141 {Name: "foo-1", Family: "foo-family"}, 142 {Name: "bar-1", Family: "bar-family"}, 143 }, 144 wantCI: &daisy.CreateImages{ 145 ImagesAlpha: []*daisy.ImageAlpha{ 146 { 147 ImageBase: daisy.ImageBase{Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-3"}}, 148 Image: computeAlpha.Image{ 149 Name: "foo-3", 150 Family: "foo-family", 151 SourceImage: "projects/bar-project/global/images/foo-3", 152 RolloutOverride: &computeAlpha.RolloutPolicy{ 153 DefaultRolloutTime: now.Format(time.RFC3339), 154 }, 155 }, 156 }, 157 }, 158 }, 159 wantDI: &daisy.DeprecateImages{ 160 { 161 Image: "foo-2", 162 Project: "foo-project", 163 DeprecationStatusAlpha: computeAlpha.DeprecationStatus{ 164 State: "DEPRECATED", 165 Replacement: "https://www.googleapis.com/compute/v1/projects/foo-project/global/images/foo-3", 166 StateOverride: &computeAlpha.RolloutPolicy{ 167 DefaultRolloutTime: now.Format(time.RFC3339), 168 }, 169 }, 170 }, 171 { 172 Image: "foo-1", 173 Project: "foo-project", 174 DeprecationStatusAlpha: computeAlpha.DeprecationStatus{ 175 State: "DEPRECATED", 176 Replacement: "https://www.googleapis.com/compute/v1/projects/foo-project/global/images/foo-3", 177 StateOverride: &computeAlpha.RolloutPolicy{ 178 DefaultRolloutTime: now.Format(time.RFC3339), 179 }, 180 }, 181 }, 182 }, 183 }, 184 { 185 desc: "GCSPath case", 186 p: &Publish{SourceGCSPath: "gs://bar-project-path", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 187 img: &Image{Prefix: "foo", Family: "foo-family"}, 188 pubImgs: []*computeAlpha.Image{}, 189 wantCI: &daisy.CreateImages{ 190 ImagesAlpha: []*daisy.ImageAlpha{ 191 { 192 ImageBase: daisy.ImageBase{Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-3"}}, 193 Image: computeAlpha.Image{ 194 Name: "foo-3", 195 Family: "foo-family", 196 RawDisk: &computeAlpha.ImageRawDisk{Source: "gs://bar-project-path/foo-3/root.tar.gz"}, 197 }, 198 }, 199 }, 200 }, 201 }, 202 { 203 desc: "GCSPath with noRoot case", 204 p: &Publish{SourceGCSPath: "gs://bar-project-path", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 205 img: &Image{Prefix: "foo", Family: "foo-family"}, 206 pubImgs: []*computeAlpha.Image{}, 207 noRoot: true, 208 wantCI: &daisy.CreateImages{ 209 ImagesAlpha: []*daisy.ImageAlpha{ 210 { 211 ImageBase: daisy.ImageBase{Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-3"}}, 212 Image: computeAlpha.Image{ 213 Name: "foo-3", 214 Family: "foo-family", 215 RawDisk: &computeAlpha.ImageRawDisk{Source: "gs://bar-project-path/foo-3.tar.gz"}, 216 }, 217 }, 218 }, 219 }, 220 }, 221 { 222 desc: "both SourceGCSPath and SourceProject set", 223 p: &Publish{SourceGCSPath: "gs://bar-project-path", SourceProject: "bar-project"}, 224 img: &Image{}, 225 wantErr: true, 226 }, 227 { 228 desc: "neither SourceGCSPath and SourceProject set", 229 p: &Publish{}, 230 img: &Image{}, 231 wantErr: true, 232 }, 233 { 234 desc: "image already exists", 235 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 236 img: &Image{Prefix: "foo", Family: "foo-family", GuestOsFeatures: []string{"foo-feature"}}, 237 pubImgs: []*computeAlpha.Image{{Name: "foo-3", Family: "foo-family"}}, 238 wantErr: true, 239 }, 240 { 241 desc: "image already exists, skipDup set", 242 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 243 img: &Image{ 244 Prefix: "foo", 245 Family: "foo-family", 246 GuestOsFeatures: []string{"foo-feature"}, 247 }, 248 pubImgs: []*computeAlpha.Image{ 249 {Name: "foo-3", Family: "bar-family"}, 250 {Name: "foo-2", Family: "foo-family"}, 251 }, 252 skipDup: true, 253 wantDI: &daisy.DeprecateImages{ 254 { 255 Image: "foo-2", Project: "foo-project", 256 DeprecationStatusAlpha: computeAlpha.DeprecationStatus{ 257 State: "DEPRECATED", 258 Replacement: "https://www.googleapis.com/compute/v1/projects/foo-project/global/images/foo-3", 259 }, 260 }, 261 }, 262 }, 263 { 264 desc: "image already exists, replace set", 265 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 266 img: &Image{ 267 Prefix: "foo", 268 Family: "foo-family", 269 RolloutPolicy: &computeAlpha.RolloutPolicy{ 270 DefaultRolloutTime: now.Format(time.RFC3339), 271 }, 272 }, 273 pubImgs: []*computeAlpha.Image{ 274 {Name: "foo-3", Family: "bar-family"}, 275 {Name: "foo-2", Family: "foo-family"}, 276 }, 277 replace: true, 278 wantCI: &daisy.CreateImages{ 279 ImagesAlpha: []*daisy.ImageAlpha{ 280 { 281 ImageBase: daisy.ImageBase{ 282 OverWrite: true, 283 Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-3"}, 284 }, 285 Image: computeAlpha.Image{ 286 Name: "foo-3", 287 Family: "foo-family", 288 SourceImage: "projects/bar-project/global/images/foo-3", 289 RolloutOverride: &computeAlpha.RolloutPolicy{ 290 DefaultRolloutTime: now.Format(time.RFC3339), 291 }, 292 }, 293 }, 294 }, 295 }, 296 wantDI: &daisy.DeprecateImages{ 297 { 298 Image: "foo-2", 299 Project: "foo-project", 300 DeprecationStatusAlpha: computeAlpha.DeprecationStatus{ 301 State: "DEPRECATED", 302 Replacement: "https://www.googleapis.com/compute/v1/projects/foo-project/global/images/foo-3", 303 StateOverride: &computeAlpha.RolloutPolicy{ 304 DefaultRolloutTime: now.Format(time.RFC3339), 305 }, 306 }, 307 }, 308 }, 309 }, 310 { 311 desc: "new image from src, without version", 312 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project"}, 313 img: &Image{Prefix: "foo-x", Family: "foo-family", GuestOsFeatures: []string{"foo-feature", "bar-feature"}}, 314 pubImgs: []*computeAlpha.Image{ 315 {Name: "bar-x", Family: "bar-family"}, 316 }, 317 wantCI: &daisy.CreateImages{ 318 ImagesAlpha: []*daisy.ImageAlpha{ 319 { 320 ImageBase: daisy.ImageBase{Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-x"}}, 321 Image: computeAlpha.Image{ 322 Name: "foo-x", 323 Family: "foo-family", 324 SourceImage: "projects/bar-project/global/images/foo-x", 325 }, 326 GuestOsFeatures: []string{"foo-feature", "bar-feature"}}, 327 }, 328 }, 329 }, 330 { 331 desc: "no image family, don't deprecate", 332 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 333 img: &Image{Prefix: "foo", Family: "foo-family"}, 334 pubImgs: []*computeAlpha.Image{ 335 {Name: "foo-2", Family: ""}, 336 {Name: "foo-1", Family: "", Deprecated: &computeAlpha.DeprecationStatus{State: "DEPRECATED"}}, 337 }, 338 wantCI: &daisy.CreateImages{ 339 ImagesAlpha: []*daisy.ImageAlpha{ 340 { 341 ImageBase: daisy.ImageBase{Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-3"}}, 342 Image: computeAlpha.Image{ 343 Name: "foo-3", 344 Family: "foo-family", 345 SourceImage: "projects/bar-project/global/images/foo-3", 346 }, 347 }, 348 }, 349 }, 350 }, 351 { 352 desc: "ignore license validation if forbidden", 353 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 354 img: &Image{Prefix: "foo", Family: "foo-family", GuestOsFeatures: []string{"foo-feature"}, IgnoreLicenseValidationIfForbidden: true}, 355 pubImgs: []*computeAlpha.Image{}, 356 wantCI: &daisy.CreateImages{ 357 ImagesAlpha: []*daisy.ImageAlpha{ 358 { 359 ImageBase: daisy.ImageBase{ 360 Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-3"}, 361 IgnoreLicenseValidationIfForbidden: true, 362 }, 363 Image: computeAlpha.Image{ 364 Name: "foo-3", 365 Family: "foo-family", 366 SourceImage: "projects/bar-project/global/images/foo-3", 367 }, 368 GuestOsFeatures: []string{"foo-feature"}, 369 }, 370 }, 371 }, 372 }, 373 { 374 desc: "don't ignore license validation if forbidden", 375 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project", sourceVersion: "3", publishVersion: "3"}, 376 img: &Image{Prefix: "foo", Family: "foo-family", GuestOsFeatures: []string{"foo-feature"}, IgnoreLicenseValidationIfForbidden: false}, 377 pubImgs: []*computeAlpha.Image{}, 378 wantCI: &daisy.CreateImages{ 379 ImagesAlpha: []*daisy.ImageAlpha{ 380 { 381 ImageBase: daisy.ImageBase{ 382 Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-3"}, 383 IgnoreLicenseValidationIfForbidden: false, 384 }, 385 Image: computeAlpha.Image{ 386 Name: "foo-3", 387 Family: "foo-family", 388 SourceImage: "projects/bar-project/global/images/foo-3", 389 }, 390 GuestOsFeatures: []string{"foo-feature"}, 391 }, 392 }, 393 }, 394 }, 395 { 396 desc: "new image from src, with ShieldedInstanceInitialState", 397 p: &Publish{SourceProject: "bar-project", PublishProject: "foo-project"}, 398 img: &Image{Prefix: "foo-x", Family: "foo-family", ShieldedInstanceInitialState: fakeInitialState}, 399 pubImgs: []*computeAlpha.Image{}, 400 wantCI: &daisy.CreateImages{ 401 ImagesAlpha: []*daisy.ImageAlpha{ 402 { 403 ImageBase: daisy.ImageBase{Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "foo-x"}}, 404 Image: computeAlpha.Image{ 405 Name: "foo-x", 406 Family: "foo-family", 407 SourceImage: "projects/bar-project/global/images/foo-x", 408 ShieldedInstanceInitialState: fakeInitialState, 409 }, 410 }, 411 }, 412 }, 413 }, 414 } 415 416 for _, tt := range tests { 417 dr, di, _, err := publishImage(tt.p, tt.img, tt.pubImgs, tt.skipDup, tt.replace, tt.noRoot) 418 if tt.wantErr && err != nil { 419 continue 420 } 421 if !tt.wantErr && err != nil { 422 t.Errorf("%s: error from publishImage(): %v", tt.desc, err) 423 continue 424 } else if tt.wantErr && err == nil { 425 t.Errorf("%s: did not get expected error from publishImage()", tt.desc) 426 } 427 428 if diff := pretty.Compare(dr, tt.wantCI); diff != "" { 429 t.Errorf("%s: returned CreateImages does not match expectation: (-got +want)\n%s", tt.desc, diff) 430 } 431 if diff := pretty.Compare(di, tt.wantDI); diff != "" { 432 t.Errorf("%s: returned DeprecateImages does not match expectation: (-got +want)\n%s", tt.desc, diff) 433 } 434 } 435 } 436 437 func TestRollbackImage(t *testing.T) { 438 tests := []struct { 439 desc string 440 p *Publish 441 img *Image 442 pubImgs []*computeAlpha.Image 443 wantDR *daisy.DeleteResources 444 wantDI *daisy.DeprecateImages 445 }{ 446 { 447 "normal case", 448 &Publish{PublishProject: "foo-project", publishVersion: "3"}, 449 &Image{Prefix: "foo", Family: "foo-family"}, 450 []*computeAlpha.Image{ 451 {Name: "bar-3", Family: "bar-family"}, 452 {Name: "foo-3", Family: "foo-family"}, 453 {Name: "bar-2", Family: "bar-family", Deprecated: &computeAlpha.DeprecationStatus{State: "DEPRECATED"}}, 454 {Name: "foo-2", Family: "foo-family", Deprecated: &computeAlpha.DeprecationStatus{State: "DEPRECATED"}}, 455 {Name: "foo-1", Family: "foo-family", Deprecated: &computeAlpha.DeprecationStatus{State: "DEPRECATED"}}, 456 {Name: "bar-1", Family: "bar-family", Deprecated: &computeAlpha.DeprecationStatus{State: "DEPRECATED"}}, 457 }, 458 &daisy.DeleteResources{Images: []string{"projects/foo-project/global/images/foo-3"}}, 459 &daisy.DeprecateImages{{Image: "foo-2", Project: "foo-project", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "ACTIVE"}}}, 460 }, 461 { 462 "no image to undeprecate", 463 &Publish{PublishProject: "foo-project", publishVersion: "3"}, 464 &Image{Prefix: "foo", Family: "foo-family"}, 465 []*computeAlpha.Image{ 466 {Name: "bar-3", Family: "bar-family"}, 467 {Name: "foo-3", Family: "foo-family"}, 468 {Name: "bar-2", Family: "bar-family", Deprecated: &computeAlpha.DeprecationStatus{State: "DEPRECATED"}}, 469 {Name: "bar-1", Family: "bar-family", Deprecated: &computeAlpha.DeprecationStatus{State: "DEPRECATED"}}, 470 }, 471 &daisy.DeleteResources{Images: []string{"projects/foo-project/global/images/foo-3"}}, 472 &daisy.DeprecateImages{}, 473 }, 474 { 475 "image DNE", 476 &Publish{PublishProject: "foo-project", publishVersion: "1"}, 477 &Image{Prefix: "foo", Family: "foo-family"}, 478 []*computeAlpha.Image{ 479 {Name: "bar-1", Family: "bar-family"}, 480 }, 481 nil, 482 nil, 483 }, 484 } 485 for _, tt := range tests { 486 dr, di := rollbackImage(tt.p, tt.img, tt.pubImgs) 487 if diff := pretty.Compare(dr, tt.wantDR); diff != "" { 488 t.Errorf("%s: returned DeleteResources does not match expectation: (-got +want)\n%s", tt.desc, diff) 489 } 490 if diff := pretty.Compare(di, tt.wantDI); diff != "" { 491 t.Errorf("%s: returned DeprecateImages does not match expectation: (-got +want)\n%s", tt.desc, diff) 492 } 493 } 494 495 } 496 497 func TestPopulateSteps(t *testing.T) { 498 // This scenario is a bit contrived as there's no way you will get 499 // DeleteResources steps and CreateImages steps in the same workflow, 500 // but this simplifies the test data. 501 got := daisy.New() 502 err := populateSteps( 503 got, 504 "foo", 505 //&daisy.CreateImages{{Image: computeAlpha.Image{Name: "create-image"}}}, 506 &daisy.CreateImages{ImagesAlpha: []*daisy.ImageAlpha{{Image: computeAlpha.Image{Name: "create-image"}}}}, 507 508 &daisy.DeprecateImages{{Image: "deprecate-image"}}, 509 &daisy.DeleteResources{Images: []string{"delete-image"}}, 510 ) 511 if err != nil { 512 t.Fatal(err) 513 } 514 got.Cancel = nil 515 516 want := &daisy.Workflow{ 517 Steps: map[string]*daisy.Step{ 518 "delete-foo": {DeleteResources: &daisy.DeleteResources{Images: []string{"delete-image"}}}, 519 "deprecate-foo": {DeprecateImages: &daisy.DeprecateImages{{Image: "deprecate-image"}}}, 520 "publish-foo": {Timeout: "1h", CreateImages: &daisy.CreateImages{ImagesAlpha: []*daisy.ImageAlpha{{Image: computeAlpha.Image{Name: "create-image"}}}}}, 521 }, 522 Dependencies: map[string][]string{ 523 "delete-foo": {"publish-foo", "deprecate-foo"}, 524 "deprecate-foo": {"publish-foo"}, 525 }, 526 DefaultTimeout: "10m", 527 } 528 529 if diff := (&pretty.Config{Diffable: true, Formatter: pretty.DefaultFormatter}).Compare(got, want); diff != "" { 530 t.Errorf("-got +want\n%s", diff) 531 } 532 533 } 534 535 func TestPopulateWorkflow(t *testing.T) { 536 now := time.Now() 537 got := daisy.New() 538 p := &Publish{ 539 SourceProject: "foo-project", 540 PublishProject: "foo-project", 541 publishVersion: "pv", 542 sourceVersion: "sv", 543 Images: []*Image{ 544 { 545 Prefix: "test", 546 Family: "test-family", 547 RolloutPolicy: createRollOut([]*compute.Zone{ 548 {Name: "us-central1-a", Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central1"}, 549 {Name: "us-central1-b", Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central1"}, 550 {Name: "us-central1-c", Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central1"}, 551 }, now, 1), 552 }, 553 }, 554 } 555 err := p.populateWorkflow( 556 context.Background(), 557 got, 558 []*computeAlpha.Image{ 559 {Name: "test-old", Family: "test-family"}, 560 }, 561 p.Images[0], 562 false, 563 false, 564 false, 565 false, 566 ) 567 if err != nil { 568 t.Fatal(err) 569 } 570 got.Cancel = nil 571 572 wantrp := computeAlpha.RolloutPolicy{DefaultRolloutTime: now.Add(time.Minute * 2).Format(time.RFC3339)} 573 wantrp.LocationRolloutPolicies = make(map[string]string) 574 wantrp.LocationRolloutPolicies["zones/us-central1-a"] = now.Format(time.RFC3339) 575 wantrp.LocationRolloutPolicies["zones/us-central1-b"] = now.Add(time.Minute).Format(time.RFC3339) 576 wantrp.LocationRolloutPolicies["zones/us-central1-c"] = now.Add(time.Minute * 2).Format(time.RFC3339) 577 578 want := &daisy.Workflow{ 579 Steps: map[string]*daisy.Step{ 580 "publish-test": {Timeout: "1h", CreateImages: &daisy.CreateImages{ 581 ImagesAlpha: []*daisy.ImageAlpha{ 582 { 583 ImageBase: daisy.ImageBase{Resource: daisy.Resource{Project: "foo-project", NoCleanup: true, RealName: "test-pv"}}, 584 Image: computeAlpha.Image{ 585 Name: "test-pv", 586 Family: "test-family", 587 SourceImage: "projects/foo-project/global/images/test-sv", 588 RolloutOverride: &wantrp, 589 }, 590 }, 591 }, 592 }}, 593 "deprecate-test": {DeprecateImages: &daisy.DeprecateImages{ 594 {Project: "foo-project", Image: "test-old", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "DEPRECATED", Replacement: "https://www.googleapis.com/compute/v1/projects/foo-project/global/images/test-pv", StateOverride: &wantrp}}}, 595 }, 596 }, 597 Dependencies: map[string][]string{ 598 "deprecate-test": {"publish-test"}, 599 }, 600 DefaultTimeout: "10m", 601 } 602 603 if diff := (&pretty.Config{Diffable: true, Formatter: pretty.DefaultFormatter}).Compare(got, want); diff != "" { 604 t.Errorf("-got +want\n%s", diff) 605 } 606 607 } 608 609 func TestCreatePrintOut(t *testing.T) { 610 tests := []struct { 611 name string 612 args *daisy.CreateImages 613 want []string 614 }{ 615 {"empty", nil, nil}, 616 { 617 "one image", 618 &daisy.CreateImages{ImagesAlpha: []*daisy.ImageAlpha{{Image: computeAlpha.Image{Name: "foo", Description: "bar"}}}}, 619 []string{"foo: (bar)"}, 620 }, 621 {"two images", &daisy.CreateImages{ImagesAlpha: []*daisy.ImageAlpha{ 622 {Image: computeAlpha.Image{Name: "foo1", Description: "bar1"}}, 623 {Image: computeAlpha.Image{Name: "foo2", Description: "bar2"}}, 624 }, 625 }, 626 []string{"foo1: (bar1)", "foo2: (bar2)"}, 627 }, 628 } 629 for _, tt := range tests { 630 t.Run(tt.name, func(t *testing.T) { 631 p := &Publish{} 632 p.createPrintOut(tt.args) 633 if !reflect.DeepEqual(p.toCreate, tt.want) { 634 t.Errorf("createPrintOut() got = %v, want %v", p.toCreate, tt.want) 635 } 636 }) 637 } 638 } 639 640 func TestDeletePrintOut(t *testing.T) { 641 tests := []struct { 642 name string 643 args *daisy.DeleteResources 644 want []string 645 }{ 646 {"empty", nil, nil}, 647 {"not an image", &daisy.DeleteResources{Disks: []string{"foo"}}, nil}, 648 {"one image", &daisy.DeleteResources{Images: []string{"foo"}}, []string{"foo"}}, 649 {"two images", &daisy.DeleteResources{Images: []string{"foo", "bar"}}, []string{"foo", "bar"}}, 650 } 651 for _, tt := range tests { 652 t.Run(tt.name, func(t *testing.T) { 653 p := &Publish{} 654 p.deletePrintOut(tt.args) 655 if !reflect.DeepEqual(p.toDelete, tt.want) { 656 t.Errorf("deletePrintOut() got = %v, want %v", p.toDelete, tt.want) 657 } 658 }) 659 } 660 } 661 662 func TestDeprecatePrintOut(t *testing.T) { 663 tests := []struct { 664 name string 665 args *daisy.DeprecateImages 666 toDeprecate []string 667 toObsolete []string 668 toUndeprecate []string 669 }{ 670 {"empty", nil, nil, nil, nil}, 671 {"unknown state", &daisy.DeprecateImages{&daisy.DeprecateImage{Image: "foo", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "foo"}}}, nil, nil, nil}, 672 {"only DEPRECATED", &daisy.DeprecateImages{&daisy.DeprecateImage{Image: "foo", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "DEPRECATED", StateOverride: &computeAlpha.RolloutPolicy{DefaultRolloutTime: time.Now().Format(time.RFC3339)}}}}, []string{"foo"}, nil, nil}, 673 {"only OBSOLETE", &daisy.DeprecateImages{&daisy.DeprecateImage{Image: "foo", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "OBSOLETE"}}}, nil, []string{"foo"}, nil}, 674 {"only un-deprecated", &daisy.DeprecateImages{&daisy.DeprecateImage{Image: "foo", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "ACTIVE"}}}, nil, nil, []string{"foo"}}, 675 {"all three", &daisy.DeprecateImages{ 676 &daisy.DeprecateImage{Image: "foo", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "DEPRECATED"}}, 677 &daisy.DeprecateImage{Image: "bar", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "OBSOLETE"}}, 678 &daisy.DeprecateImage{Image: "baz", DeprecationStatusAlpha: computeAlpha.DeprecationStatus{State: "ACTIVE"}}}, 679 []string{"foo"}, []string{"bar"}, []string{"baz"}, 680 }, 681 } 682 for _, tt := range tests { 683 t.Run(tt.name, func(t *testing.T) { 684 p := &Publish{} 685 p.deprecatePrintOut(tt.args) 686 if !reflect.DeepEqual(p.toDeprecate, tt.toDeprecate) { 687 t.Errorf("deprecatePrintOut() toDeprecate got = %v, want %v", p.toDeprecate, tt.toDeprecate) 688 } 689 if !reflect.DeepEqual(p.toObsolete, tt.toObsolete) { 690 t.Errorf("deprecatePrintOut() toObsolete got = %v, want %v", p.toObsolete, tt.toObsolete) 691 } 692 if !reflect.DeepEqual(p.toUndeprecate, tt.toUndeprecate) { 693 t.Errorf("deprecatePrintOut() toUndeprecate got = %v, want %v", p.toUndeprecate, tt.toUndeprecate) 694 } 695 }) 696 } 697 } 698 699 func TestCreateRollOut(t *testing.T) { 700 startTime := time.Now().Round(time.Second) 701 tests := []struct { 702 desc string 703 zones []*compute.Zone 704 rolloutStartTime time.Time 705 rolloutRate int 706 wantRollout computeAlpha.RolloutPolicy 707 }{ 708 { 709 desc: "3 regions, each region has a different number of zones.", 710 zones: []*compute.Zone{ 711 { 712 Name: "us-central1-a", 713 Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central1", 714 }, 715 { 716 Name: "us-central1-b", 717 Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central1", 718 }, 719 { 720 Name: "us-central2-a", 721 Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central2", 722 }, 723 { 724 Name: "us-central2-c", 725 Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central2", 726 }, 727 { 728 Name: "us-central2-b", 729 Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central2", 730 }, 731 { 732 Name: "us-central3-a", 733 Region: "https://www.googleapis.com/compute/v1/projects/projectname/regions/us-central2", 734 }, 735 }, 736 rolloutStartTime: startTime, 737 rolloutRate: 5, 738 wantRollout: computeAlpha.RolloutPolicy{ 739 DefaultRolloutTime: startTime.Format(time.RFC3339), 740 LocationRolloutPolicies: map[string]string{ 741 "us-central1-a": startTime.Format(time.RFC3339), 742 "us-central2-a": startTime.Add(5 * time.Minute).Format(time.RFC3339), 743 "us-central3-a": startTime.Add(10 * time.Minute).Format(time.RFC3339), 744 "us-central1-b": startTime.Add(15 * time.Minute).Format(time.RFC3339), 745 "us-central2-b": startTime.Add(20 * time.Minute).Format(time.RFC3339), 746 "us-central2-c": startTime.Add(25 * time.Minute).Format(time.RFC3339), 747 }, 748 }, 749 }, 750 } 751 for _, tt := range tests { 752 t.Run(tt.desc, func(t *testing.T) { 753 rollout := createRollOut(tt.zones, tt.rolloutStartTime, tt.rolloutRate) 754 755 if reflect.DeepEqual(rollout, tt.wantRollout) { 756 t.Errorf("unexpected rollout got = %s, want = %s", rollout, tt.wantRollout) 757 } 758 }) 759 } 760 } 761 762 func TestCreatePublishWithFile(t *testing.T) { 763 tests := []struct { 764 name string 765 path string 766 wantErr bool 767 }{ 768 {"no valid path", "", true}, 769 {"pass with valid path", "../../../daisy_workflows/build-publish/debian/debian_10.publish.json", false}, 770 } 771 for _, tt := range tests { 772 t.Run(tt.name, func(t *testing.T) { 773 _, err := CreatePublish("", "", "", "", "", "", "", tt.path, map[string]string{}, map[string][]*computeAlpha.Image{}) 774 if err != nil && !tt.wantErr { 775 t.Errorf("CreatePublish() called with path %s: got error %v", tt.path, err) 776 } 777 if tt.wantErr && err == nil { 778 t.Errorf("CreatePublish() called with path %s: did not get expected error", tt.path) 779 } 780 }) 781 } 782 } 783 784 func TestCreatePublishWithTemplate(t *testing.T) { 785 tests := []struct { 786 name string 787 template string 788 wantErr bool 789 }{ 790 {"pass template", `{"WorkProject": "blah"}`, false}, 791 {"pass with invalid template", "{", true}, 792 } 793 for _, tt := range tests { 794 t.Run(tt.name, func(t *testing.T) { 795 _, err := CreatePublishWithTemplate("", "", "", "", "", "", "", tt.template, map[string]string{}, map[string][]*computeAlpha.Image{}) 796 if err != nil && !tt.wantErr { 797 t.Errorf("CreatePublishWithTemplate() called with template %s: got error %v", tt.template, err) 798 } 799 if tt.wantErr && err == nil { 800 t.Errorf("CreatePublishWithTemplate() called with template %s: did not get expected error", tt.template) 801 } 802 }) 803 } 804 }