github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/charm/repository/charmhub_test.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package repository 5 6 import ( 7 "context" 8 "fmt" 9 "net/url" 10 "time" 11 12 "github.com/juju/charm/v12" 13 charmresource "github.com/juju/charm/v12/resource" 14 "github.com/juju/collections/set" 15 "github.com/juju/errors" 16 "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/utils/v3/hash" 19 "go.uber.org/mock/gomock" 20 gc "gopkg.in/check.v1" 21 22 "github.com/juju/juju/charmhub" 23 "github.com/juju/juju/charmhub/transport" 24 "github.com/juju/juju/core/arch" 25 corecharm "github.com/juju/juju/core/charm" 26 "github.com/juju/juju/core/charm/repository/mocks" 27 ) 28 29 var ( 30 expRefreshFields = set.NewStrings( 31 "download", "id", "license", "name", "publisher", "resources", 32 "revision", "summary", "type", "version", "bases", "config-yaml", 33 "metadata-yaml", 34 ).SortedValues() 35 ) 36 37 type charmHubRepositorySuite struct { 38 testing.IsolationSuite 39 40 client *mocks.MockCharmHubClient 41 logger *mocks.MockLogger 42 } 43 44 var _ = gc.Suite(&charmHubRepositorySuite{}) 45 46 func (s *charmHubRepositorySuite) TestResolveForDeploy(c *gc.C) { 47 defer s.setupMocks(c).Finish() 48 s.expectCharmRefreshInstallOneFromChannel(c) 49 // The origin.ID should never be saved to the origin during 50 // ResolveWithPreferredChannel. That is done during the file 51 // download only. 52 s.testResolve(c, "") 53 } 54 55 func (s *charmHubRepositorySuite) TestResolveForUpgrade(c *gc.C) { 56 defer s.setupMocks(c).Finish() 57 cfg, err := charmhub.RefreshOne("instance-key", "charmCHARMcharmCHARMcharmCHARM01", 16, "latest/stable", charmhub.RefreshBase{ 58 Architecture: arch.DefaultArchitecture, 59 }) 60 c.Assert(err, jc.ErrorIsNil) 61 s.expectCharmRefresh(c, cfg) 62 // If the origin has an ID, ensure it's kept thru the call 63 // to ResolveWithPreferredChannel. 64 s.testResolve(c, "charmCHARMcharmCHARMcharmCHARM01") 65 } 66 67 func (s *charmHubRepositorySuite) testResolve(c *gc.C, id string) { 68 curl := charm.MustParseURL("ch:wordpress") 69 rev := 16 70 channel := corecharm.MustParseChannel("latest/stable") 71 origin := corecharm.Origin{ 72 Source: "charm-hub", 73 ID: id, 74 Revision: &rev, 75 Platform: corecharm.Platform{ 76 Architecture: arch.DefaultArchitecture, 77 OS: "ubuntu", 78 Channel: "20.04", 79 }, 80 Channel: &channel, 81 } 82 if id != "" { 83 origin.InstanceKey = "instance-key" 84 } 85 86 obtainedCurl, obtainedOrigin, obtainedBases, err := s.newClient().ResolveWithPreferredChannel("wordpress", origin) 87 c.Assert(err, jc.ErrorIsNil) 88 89 curl.Revision = rev 90 91 origin.Type = "charm" 92 origin.Revision = &curl.Revision 93 origin.Platform.Architecture = arch.DefaultArchitecture 94 origin.Platform.OS = "ubuntu" 95 origin.Platform.Channel = "20.04" 96 97 expected := s.expectedCURL(curl, 16, arch.DefaultArchitecture, "focal") 98 99 c.Assert(obtainedCurl, jc.DeepEquals, expected) 100 c.Assert(obtainedOrigin, jc.DeepEquals, origin) 101 c.Assert(obtainedBases, jc.SameContents, []corecharm.Platform{{OS: "ubuntu", Channel: "20.04", Architecture: "amd64"}}) 102 } 103 104 func (s *charmHubRepositorySuite) TestResolveFillsInEmptyTrack(c *gc.C) { 105 defer s.setupMocks(c).Finish() 106 s.expectCharmRefreshInstallOneFromChannel(c) 107 108 channel := corecharm.MustParseChannel("stable") 109 origin := corecharm.Origin{ 110 Source: "charm-hub", 111 Platform: corecharm.Platform{ 112 Architecture: arch.DefaultArchitecture, 113 OS: "ubuntu", 114 Channel: "20.04", 115 }, 116 Channel: &channel, 117 } 118 _, obtainedOrigin, _, err := s.newClient().ResolveWithPreferredChannel("wordpress", origin) 119 c.Assert(err, jc.ErrorIsNil) 120 c.Assert(obtainedOrigin.Channel.Track, gc.Equals, "latest") 121 } 122 123 func (s *charmHubRepositorySuite) TestResolveWithChannel(c *gc.C) { 124 defer s.setupMocks(c).Finish() 125 s.expectCharmRefreshInstallOneFromChannel(c) 126 127 curl := charm.MustParseURL("ch:wordpress") 128 origin := corecharm.Origin{ 129 Source: "charm-hub", 130 Platform: corecharm.Platform{ 131 Architecture: arch.DefaultArchitecture, 132 OS: "ubuntu", 133 Channel: "20.04", 134 }, 135 Channel: &charm.Channel{ 136 Track: "latest", 137 Risk: "stable", 138 }, 139 } 140 141 obtainedCurl, obtainedOrigin, obtainedBases, err := s.newClient().ResolveWithPreferredChannel("wordpress", origin) 142 c.Assert(err, jc.ErrorIsNil) 143 144 curl.Revision = 16 145 146 origin.Type = "charm" 147 origin.Revision = &curl.Revision 148 origin.Channel = &charm.Channel{ 149 Track: "latest", 150 Risk: "stable", 151 } 152 origin.Platform.Architecture = arch.DefaultArchitecture 153 origin.Platform.OS = "ubuntu" 154 origin.Platform.Channel = "20.04" 155 156 expected := s.expectedCURL(curl, 16, arch.DefaultArchitecture, "focal") 157 158 c.Assert(obtainedCurl, jc.DeepEquals, expected) 159 c.Assert(obtainedOrigin, jc.DeepEquals, origin) 160 c.Assert(obtainedBases, jc.SameContents, []corecharm.Platform{{OS: "ubuntu", Channel: "20.04", Architecture: "amd64"}}) 161 } 162 163 func (s *charmHubRepositorySuite) TestResolveWithoutBase(c *gc.C) { 164 defer s.setupMocks(c).Finish() 165 s.expectCharmRefreshInstallOneFromChannel(c) 166 167 curl := charm.MustParseURL("ch:wordpress") 168 origin := corecharm.Origin{ 169 Source: "charm-hub", 170 Platform: corecharm.Platform{ 171 Architecture: arch.DefaultArchitecture, 172 }, 173 } 174 175 obtainedCurl, obtainedOrigin, obtainedBases, err := s.newClient().ResolveWithPreferredChannel("wordpress", origin) 176 c.Assert(err, jc.ErrorIsNil) 177 178 curl.Revision = 16 179 180 origin.Type = "charm" 181 origin.Revision = &curl.Revision 182 origin.Channel = &charm.Channel{ 183 Track: "latest", 184 Risk: "stable", 185 } 186 origin.Platform.Architecture = arch.DefaultArchitecture 187 188 expected := s.expectedCURL(curl, 16, arch.DefaultArchitecture, "") 189 190 c.Assert(obtainedCurl, jc.DeepEquals, expected) 191 c.Assert(obtainedOrigin, jc.DeepEquals, origin) 192 c.Assert(obtainedBases, jc.SameContents, []corecharm.Platform{}) 193 } 194 195 func (s *charmHubRepositorySuite) TestResolveForDeployWithRevisionSuccess(c *gc.C) { 196 defer s.setupMocks(c).Finish() 197 s.expectCharmRefreshInstallOneByRevisionResources(c) 198 199 revision := 16 200 curl := charm.MustParseURL("ch:wordpress") 201 origin := corecharm.Origin{ 202 Source: "charm-hub", 203 Platform: corecharm.Platform{ 204 Architecture: arch.DefaultArchitecture, 205 OS: "ubuntu", 206 Channel: "20.04", 207 }, 208 Revision: &revision, 209 Channel: &charm.Channel{ 210 Track: "latest", 211 Risk: "stable", 212 }, 213 } 214 arg := corecharm.CharmID{URL: curl, Origin: origin} 215 216 obtainedData, err := s.newClient().ResolveForDeploy(arg) 217 c.Assert(err, jc.ErrorIsNil) 218 219 curl.Revision = revision 220 221 expectedOrigin := origin 222 expectedOrigin.Type = "charm" 223 expectedOrigin.Revision = &revision 224 225 expected := s.expectedCURL(curl, 16, arch.DefaultArchitecture, "focal") 226 227 c.Assert(obtainedData.URL, jc.DeepEquals, expected) 228 c.Assert(obtainedData.EssentialMetadata.ResolvedOrigin, jc.DeepEquals, expectedOrigin) 229 } 230 231 func (s *charmHubRepositorySuite) TestResolveForDeploySuccessChooseBase(c *gc.C) { 232 defer s.setupMocks(c).Finish() 233 s.expectedRefreshInvalidPlatformError(c) 234 s.expectCharmRefreshInstallOneFromChannelFullBase(c) 235 236 curl := charm.MustParseURL("ch:wordpress") 237 origin := corecharm.Origin{ 238 Source: "charm-hub", 239 Platform: corecharm.Platform{ 240 Architecture: arch.DefaultArchitecture, 241 }, 242 Channel: &charm.Channel{ 243 Track: "latest", 244 Risk: "stable", 245 }, 246 } 247 arg := corecharm.CharmID{URL: curl, Origin: origin} 248 249 obtainedData, err := s.newClient().ResolveForDeploy(arg) 250 c.Assert(err, jc.ErrorIsNil) 251 252 curl.Revision = 16 253 254 expectedOrigin := origin 255 expectedOrigin.Type = "charm" 256 expectedOrigin.Revision = &curl.Revision 257 expectedOrigin.Platform.OS = "ubuntu" 258 expectedOrigin.Platform.Channel = "20.04" 259 260 expected := s.expectedCURL(curl, 16, arch.DefaultArchitecture, "focal") 261 262 c.Assert(obtainedData.URL, jc.DeepEquals, expected) 263 c.Assert(obtainedData.EssentialMetadata.ResolvedOrigin, jc.DeepEquals, expectedOrigin) 264 c.Assert(obtainedData.Resources, gc.HasLen, 1) 265 foundResource := obtainedData.Resources["wal-e"] 266 c.Assert(foundResource.Name, gc.Equals, "wal-e") 267 c.Assert(foundResource.Path, gc.Equals, "wal-e.snap") 268 c.Assert(foundResource.Revision, gc.Equals, 5) 269 } 270 func (s *charmHubRepositorySuite) TestResolveWithBundles(c *gc.C) { 271 defer s.setupMocks(c).Finish() 272 s.expectBundleRefresh(c) 273 274 curl := charm.MustParseURL("ch:core-kubernetes") 275 origin := corecharm.Origin{ 276 Source: "charm-hub", 277 Platform: corecharm.Platform{ 278 Architecture: arch.DefaultArchitecture, 279 }, 280 } 281 282 obtainedCurl, obtainedOrigin, obtainedBases, err := s.newClient().ResolveWithPreferredChannel("core-kubernetes", origin) 283 c.Assert(err, jc.ErrorIsNil) 284 285 curl.Revision = 17 286 287 origin.Type = "bundle" 288 origin.Revision = &curl.Revision 289 origin.Channel = &charm.Channel{ 290 Track: "latest", 291 Risk: "stable", 292 } 293 origin.Platform.Architecture = arch.DefaultArchitecture 294 295 expected := s.expectedCURL(curl, 17, arch.DefaultArchitecture, "") 296 297 c.Assert(obtainedCurl, jc.DeepEquals, expected) 298 c.Assert(obtainedOrigin, jc.DeepEquals, origin) 299 c.Assert(obtainedBases, jc.SameContents, []corecharm.Platform{}) 300 } 301 302 func (s *charmHubRepositorySuite) TestResolveInvalidPlatformError(c *gc.C) { 303 defer s.setupMocks(c).Finish() 304 s.expectedRefreshInvalidPlatformError(c) 305 s.expectCharmRefreshInstallOneFromChannel(c) 306 307 curl := charm.MustParseURL("ch:wordpress") 308 origin := corecharm.Origin{ 309 Source: "charm-hub", 310 Platform: corecharm.Platform{ 311 Architecture: arch.DefaultArchitecture, 312 }, 313 } 314 315 obtainedCurl, obtainedOrigin, obtainedBases, err := s.newClient().ResolveWithPreferredChannel("wordpress", origin) 316 c.Assert(err, jc.ErrorIsNil) 317 318 curl.Revision = 16 319 320 origin.Type = "charm" 321 origin.Revision = &curl.Revision 322 origin.Channel = &charm.Channel{ 323 Track: "latest", 324 Risk: "stable", 325 } 326 origin.Platform.Architecture = arch.DefaultArchitecture 327 origin.Platform.OS = "ubuntu" 328 origin.Platform.Channel = "20.04" 329 330 expected := s.expectedCURL(curl, 16, arch.DefaultArchitecture, "focal") 331 332 c.Assert(obtainedCurl, jc.DeepEquals, expected) 333 c.Assert(obtainedOrigin, jc.DeepEquals, origin) 334 c.Assert(obtainedBases, jc.SameContents, []corecharm.Platform{{OS: "ubuntu", Channel: "20.04", Architecture: "amd64"}}) 335 } 336 337 func (s *charmHubRepositorySuite) TestResolveRevisionNotFoundErrorWithNoSeries(c *gc.C) { 338 defer s.setupMocks(c).Finish() 339 s.expectedRefreshRevisionNotFoundError(c) 340 341 origin := corecharm.Origin{ 342 Source: "charm-hub", 343 Platform: corecharm.Platform{ 344 Architecture: arch.DefaultArchitecture, 345 }, 346 } 347 348 _, _, _, err := s.newClient().ResolveWithPreferredChannel("wordpress", origin) 349 c.Assert(err, gc.ErrorMatches, 350 `(?m)selecting releases: charm or bundle not found in the charm's default channel, base "amd64" 351 available releases are: 352 channel "latest/stable": available bases are: ubuntu@20.04`) 353 } 354 355 func (s *charmHubRepositorySuite) TestResolveRevisionNotFoundError(c *gc.C) { 356 defer s.setupMocks(c).Finish() 357 s.expectedRefreshRevisionNotFoundError(c) 358 359 origin := corecharm.Origin{ 360 Source: "charm-hub", 361 Platform: corecharm.Platform{ 362 Architecture: arch.DefaultArchitecture, 363 OS: "ubuntu", 364 Channel: "18.04", 365 }, 366 } 367 368 repo := NewCharmHubRepository(s.logger, s.client) 369 _, _, _, err := repo.ResolveWithPreferredChannel("wordpress", origin) 370 c.Assert(err, gc.ErrorMatches, 371 `(?m)selecting releases: charm or bundle not found in the charm's default channel, base "amd64/ubuntu/18.04" 372 available releases are: 373 channel "latest/stable": available bases are: ubuntu@20.04`) 374 } 375 376 func (s *charmHubRepositorySuite) TestDownloadCharm(c *gc.C) { 377 defer s.setupMocks(c).Finish() 378 379 requestedOrigin := corecharm.Origin{ 380 Source: "charm-hub", 381 Platform: corecharm.Platform{ 382 Architecture: arch.DefaultArchitecture, 383 OS: "ubuntu", 384 Channel: "20.04", 385 }, 386 Channel: &charm.Channel{ 387 Track: "latest", 388 Risk: "stable", 389 }, 390 } 391 resolvedOrigin := corecharm.Origin{ 392 Source: "charm-hub", 393 ID: "charmCHARMcharmCHARMcharmCHARM01", 394 Hash: "SHA256 hash", 395 Platform: corecharm.Platform{ 396 Architecture: arch.DefaultArchitecture, 397 OS: "ubuntu", 398 Channel: "20.04", 399 }, 400 Channel: &charm.Channel{ 401 Track: "latest", 402 Risk: "stable", 403 }, 404 } 405 406 resolvedURL, err := url.Parse("ch:amd64/focal/wordpress-42") 407 c.Assert(err, jc.ErrorIsNil) 408 resolvedArchive := new(charm.CharmArchive) 409 410 s.expectCharmRefreshInstallOneFromChannel(c) 411 s.client.EXPECT().DownloadAndRead(context.TODO(), resolvedURL, "/tmp/foo").Return(resolvedArchive, nil) 412 413 gotArchive, gotOrigin, err := s.newClient().DownloadCharm("wordpress", requestedOrigin, "/tmp/foo") 414 c.Assert(err, jc.ErrorIsNil) 415 c.Assert(gotArchive, gc.Equals, resolvedArchive) // note: we are using gc.Equals to check the pointers here. 416 c.Assert(gotOrigin, gc.DeepEquals, resolvedOrigin) 417 } 418 419 func (s *charmHubRepositorySuite) TestGetDownloadURL(c *gc.C) { 420 defer s.setupMocks(c).Finish() 421 422 requestedOrigin := corecharm.Origin{ 423 Source: "charm-hub", 424 Platform: corecharm.Platform{ 425 Architecture: arch.DefaultArchitecture, 426 OS: "ubuntu", 427 Channel: "20.04", 428 }, 429 Channel: &charm.Channel{ 430 Track: "latest", 431 Risk: "stable", 432 }, 433 } 434 resolvedOrigin := corecharm.Origin{ 435 Source: "charm-hub", 436 ID: "charmCHARMcharmCHARMcharmCHARM01", 437 Hash: "SHA256 hash", 438 Platform: corecharm.Platform{ 439 Architecture: arch.DefaultArchitecture, 440 OS: "ubuntu", 441 Channel: "20.04", 442 }, 443 Channel: &charm.Channel{ 444 Track: "latest", 445 Risk: "stable", 446 }, 447 } 448 449 resolvedURL, err := url.Parse("ch:amd64/focal/wordpress-42") 450 c.Assert(err, jc.ErrorIsNil) 451 452 s.expectCharmRefreshInstallOneFromChannel(c) 453 454 gotURL, gotOrigin, err := s.newClient().GetDownloadURL("wordpress", requestedOrigin) 455 c.Assert(err, jc.ErrorIsNil) 456 c.Assert(gotURL, gc.DeepEquals, resolvedURL) 457 c.Assert(gotOrigin, gc.DeepEquals, resolvedOrigin) 458 } 459 460 func (s *charmHubRepositorySuite) TestGetEssentialMetadata(c *gc.C) { 461 defer s.setupMocks(c).Finish() 462 463 requestedOrigin := corecharm.Origin{ 464 Source: "charm-hub", 465 Platform: corecharm.Platform{ 466 Architecture: arch.DefaultArchitecture, 467 OS: "ubuntu", 468 Channel: "20.04", 469 }, 470 Channel: &charm.Channel{ 471 Track: "latest", 472 Risk: "stable", 473 }, 474 } 475 476 s.expectCharmRefreshInstallOneFromChannel(c) // resolve the origin 477 s.expectCharmRefreshInstallOneFromChannel(c) // refresh and get metadata 478 479 got, err := s.newClient().GetEssentialMetadata(corecharm.MetadataRequest{ 480 CharmName: "wordpress", 481 Origin: requestedOrigin, 482 }) 483 484 c.Assert(err, jc.ErrorIsNil) 485 c.Assert(got, gc.HasLen, 1) 486 c.Assert(got[0].Meta.Name, gc.Equals, "wordpress") 487 c.Assert(got[0].Config.Options["blog-title"], gc.Not(gc.IsNil)) 488 c.Assert(got[0].Manifest.Bases, gc.HasLen, 1) 489 c.Assert(got[0].ResolvedOrigin.ID, gc.Equals, "", gc.Commentf("ID is only added after charm download")) 490 c.Assert(got[0].ResolvedOrigin.Hash, gc.Equals, "", gc.Commentf("Hash is only added after charm download")) 491 } 492 493 func (s *charmHubRepositorySuite) TestResolveResources(c *gc.C) { 494 defer s.setupMocks(c).Finish() 495 s.expectRefresh(true) 496 s.expectListResourceRevisions(2) 497 498 result, err := s.newClient().ResolveResources([]charmresource.Resource{{ 499 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 500 Origin: charmresource.OriginUpload, 501 Revision: 1, 502 Fingerprint: fp(c), 503 Size: 0, 504 }, { 505 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 506 Origin: charmresource.OriginStore, 507 Revision: 2, 508 Fingerprint: fp(c), 509 Size: 0, 510 }}, charmID()) 511 c.Assert(err, jc.ErrorIsNil) 512 c.Assert(result, gc.DeepEquals, []charmresource.Resource{{ 513 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 514 Origin: charmresource.OriginUpload, 515 Revision: 1, 516 Fingerprint: fp(c), 517 Size: 0, 518 }, { 519 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 520 Origin: charmresource.OriginStore, 521 Revision: 2, 522 Fingerprint: fp(c), 523 Size: 0, 524 }}) 525 } 526 527 func (s *charmHubRepositorySuite) TestResolveResourcesFromStore(c *gc.C) { 528 defer s.setupMocks(c).Finish() 529 s.expectRefresh(false) 530 s.expectListResourceRevisions(1) 531 532 id := charmID() 533 id.Origin.ID = "" 534 result, err := s.newClient().ResolveResources([]charmresource.Resource{{ 535 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 536 Origin: charmresource.OriginStore, 537 Revision: 1, 538 Size: 0, 539 }}, id) 540 c.Assert(err, jc.ErrorIsNil) 541 c.Assert(result, gc.DeepEquals, []charmresource.Resource{{ 542 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 543 Origin: charmresource.OriginStore, 544 Revision: 1, 545 Fingerprint: fp(c), 546 Size: 0, 547 }}) 548 } 549 550 func (s *charmHubRepositorySuite) TestResolveResourcesFromStoreNoRevision(c *gc.C) { 551 defer s.setupMocks(c).Finish() 552 s.expectRefreshWithRevision(1, true) 553 554 result, err := s.newClient().ResolveResources([]charmresource.Resource{{ 555 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 556 Origin: charmresource.OriginStore, 557 Revision: -1, 558 Size: 0, 559 }}, charmID()) 560 c.Assert(err, jc.ErrorIsNil) 561 c.Assert(result, gc.DeepEquals, []charmresource.Resource{{ 562 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 563 Origin: charmresource.OriginStore, 564 Revision: 1, 565 Fingerprint: fp(c), 566 Size: 0, 567 }}) 568 } 569 570 func (s *charmHubRepositorySuite) TestResolveResourcesNoMatchingRevision(c *gc.C) { 571 defer s.setupMocks(c).Finish() 572 s.expectRefresh(true) 573 s.expectRefreshWithRevision(99, true) 574 s.expectListResourceRevisions(3) 575 576 _, err := s.newClient().ResolveResources([]charmresource.Resource{{ 577 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 578 Origin: charmresource.OriginStore, 579 Revision: 1, 580 Size: 0, 581 }}, charmID()) 582 c.Assert(err, gc.ErrorMatches, `charm resource "wal-e" at revision 1 not found`) 583 } 584 585 func (s *charmHubRepositorySuite) TestResolveResourcesUpload(c *gc.C) { 586 defer s.setupMocks(c).Finish() 587 s.expectRefresh(false) 588 589 id := charmID() 590 id.Origin.ID = "" 591 result, err := s.newClient().ResolveResources([]charmresource.Resource{{ 592 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 593 Origin: charmresource.OriginUpload, 594 Revision: 3, 595 Fingerprint: charmresource.Fingerprint{ 596 Fingerprint: hash.Fingerprint{}}, 597 Size: 0, 598 }}, id) 599 c.Assert(err, jc.ErrorIsNil) 600 c.Assert(result, gc.DeepEquals, []charmresource.Resource{{ 601 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 602 Origin: charmresource.OriginUpload, 603 Revision: 3, 604 Fingerprint: charmresource.Fingerprint{ 605 Fingerprint: hash.Fingerprint{}}, 606 Size: 0, 607 }}) 608 } 609 610 func (s *charmHubRepositorySuite) TestResourceInfo(c *gc.C) { 611 defer s.setupMocks(c).Finish() 612 s.expectRefreshWithRevision(25, false) 613 614 curl := charm.MustParseURL("ch:amd64/focal/ubuntu-19") 615 rev := curl.Revision 616 channel := corecharm.MustParseChannel("stable") 617 origin := corecharm.Origin{ 618 Source: corecharm.CharmHub, 619 Type: "charm", 620 Revision: &rev, 621 Channel: &channel, 622 Platform: corecharm.Platform{ 623 OS: "ubuntu", 624 Channel: "20.04", 625 Architecture: "amd64", 626 }, 627 } 628 629 result, err := s.newClient().resourceInfo(curl, origin, "wal-e", 25) 630 c.Assert(err, jc.ErrorIsNil) 631 c.Assert(result, gc.DeepEquals, charmresource.Resource{ 632 Meta: charmresource.Meta{Name: "wal-e", Type: 1, Path: "wal-e.snap", Description: "WAL-E Snap Package"}, 633 Origin: charmresource.OriginStore, 634 Revision: 25, 635 Fingerprint: fp(c), 636 Size: 0, 637 }) 638 } 639 640 func (s *charmHubRepositorySuite) expectCharmRefreshInstallOneFromChannel(c *gc.C) { 641 cfg, err := charmhub.InstallOneFromChannel("wordpress", "latest/stable", charmhub.RefreshBase{ 642 Architecture: arch.DefaultArchitecture, 643 }) 644 c.Assert(err, jc.ErrorIsNil) 645 s.expectCharmRefresh(c, cfg) 646 } 647 648 func (s *charmHubRepositorySuite) expectCharmRefresh(c *gc.C, cfg charmhub.RefreshConfig) { 649 s.client.EXPECT().Refresh(gomock.Any(), RefreshConfigMatcher{c: c, Config: cfg}).DoAndReturn(func(ctx context.Context, cfg charmhub.RefreshConfig) ([]transport.RefreshResponse, error) { 650 id := charmhub.ExtractConfigInstanceKey(cfg) 651 652 return []transport.RefreshResponse{{ 653 ID: "charmCHARMcharmCHARMcharmCHARM01", 654 InstanceKey: id, 655 Entity: transport.RefreshEntity{ 656 Type: transport.CharmType, 657 ID: "charmCHARMcharmCHARMcharmCHARM01", 658 Name: "wordpress", 659 Revision: 16, 660 Download: transport.Download{ 661 HashSHA256: "SHA256 hash", 662 HashSHA384: "SHA384 hash", 663 Size: 42, 664 URL: "ch:amd64/focal/wordpress-42", 665 }, 666 // 667 Bases: []transport.Base{ 668 { 669 Name: "ubuntu", 670 Architecture: "amd64", 671 Channel: "20.04", 672 }, 673 }, 674 MetadataYAML: ` 675 name: wordpress 676 summary: Blog engine 677 description: Blog engine 678 `[1:], 679 ConfigYAML: ` 680 options: 681 blog-title: {default: My Title, description: A descriptive title used for the blog., type: string} 682 `[1:], 683 }, 684 EffectiveChannel: "latest/stable", 685 }}, nil 686 }) 687 } 688 689 func (s *charmHubRepositorySuite) expectBundleRefresh(c *gc.C) { 690 cfg, err := charmhub.InstallOneFromChannel("core-kubernetes", "latest/stable", charmhub.RefreshBase{ 691 Architecture: arch.DefaultArchitecture, 692 }) 693 c.Assert(err, jc.ErrorIsNil) 694 s.client.EXPECT().Refresh(gomock.Any(), RefreshConfigMatcher{c: c, Config: cfg}).DoAndReturn(func(ctx context.Context, cfg charmhub.RefreshConfig) ([]transport.RefreshResponse, error) { 695 id := charmhub.ExtractConfigInstanceKey(cfg) 696 697 return []transport.RefreshResponse{{ 698 ID: "bundleBUNDLEbundleBUNDLE01", 699 InstanceKey: id, 700 Entity: transport.RefreshEntity{ 701 Type: transport.BundleType, 702 ID: "bundleBUNDLEbundleBUNDLE01", 703 Name: "core-kubernetes", 704 Revision: 17, 705 }, 706 EffectiveChannel: "latest/stable", 707 }}, nil 708 }) 709 } 710 711 func (s *charmHubRepositorySuite) expectedRefreshInvalidPlatformError(c *gc.C) { 712 s.client.EXPECT().Refresh(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, cfg charmhub.RefreshConfig) ([]transport.RefreshResponse, error) { 713 id := charmhub.ExtractConfigInstanceKey(cfg) 714 715 return []transport.RefreshResponse{{ 716 ID: "charmCHARMcharmCHARMcharmCHARM01", 717 InstanceKey: id, 718 Error: &transport.APIError{ 719 Code: transport.ErrorCodeInvalidCharmBase, 720 Message: "invalid charm platform", 721 Extra: transport.APIErrorExtra{ 722 DefaultBases: []transport.Base{{ 723 Architecture: "amd64", 724 Name: "ubuntu", 725 Channel: "20.04", 726 }}, 727 }, 728 }, 729 }}, nil 730 }) 731 } 732 733 func (s *charmHubRepositorySuite) expectedRefreshRevisionNotFoundError(c *gc.C) { 734 s.client.EXPECT().Refresh(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, cfg charmhub.RefreshConfig) ([]transport.RefreshResponse, error) { 735 id := charmhub.ExtractConfigInstanceKey(cfg) 736 737 return []transport.RefreshResponse{{ 738 ID: "charmCHARMcharmCHARMcharmCHARM01", 739 InstanceKey: id, 740 Error: &transport.APIError{ 741 Code: transport.ErrorCodeRevisionNotFound, 742 Message: "revision not found", 743 Extra: transport.APIErrorExtra{ 744 Releases: []transport.Release{{ 745 Base: transport.Base{ 746 Architecture: "amd64", 747 Name: "ubuntu", 748 Channel: "20.04", 749 }, 750 Channel: "stable", 751 }}, 752 }, 753 }, 754 }}, nil 755 }) 756 } 757 758 func (s *charmHubRepositorySuite) expectCharmRefreshInstallOneFromChannelFullBase(c *gc.C) { 759 cfg, err := charmhub.InstallOneFromChannel("wordpress", "latest/stable", charmhub.RefreshBase{ 760 Architecture: arch.DefaultArchitecture, Name: "ubuntu", Channel: "20.04", 761 }) 762 c.Assert(err, jc.ErrorIsNil) 763 s.expectCharmRefreshFullWithResources(c, cfg) 764 } 765 766 func (s *charmHubRepositorySuite) expectCharmRefreshInstallOneByRevisionResources(c *gc.C) { 767 cfg, err := charmhub.InstallOneFromRevision("wordpress", 16) 768 c.Assert(err, jc.ErrorIsNil) 769 s.expectCharmRefresh(c, cfg) 770 } 771 772 func (s *charmHubRepositorySuite) expectCharmRefreshFullWithResources(c *gc.C, cfg charmhub.RefreshConfig) { 773 s.client.EXPECT().Refresh(gomock.Any(), RefreshConfigMatcher{c: c, Config: cfg}).DoAndReturn(func(ctx context.Context, cfg charmhub.RefreshConfig) ([]transport.RefreshResponse, error) { 774 id := charmhub.ExtractConfigInstanceKey(cfg) 775 return []transport.RefreshResponse{{ 776 ID: "charmCHARMcharmCHARMcharmCHARM01", 777 InstanceKey: id, 778 Entity: transport.RefreshEntity{ 779 Type: transport.CharmType, 780 ID: "charmCHARMcharmCHARMcharmCHARM01", 781 Name: "wordpress", 782 Revision: 16, 783 Download: transport.Download{ 784 HashSHA256: "SHA256 hash", 785 HashSHA384: "SHA384 hash", 786 Size: 42, 787 URL: "ch:amd64/focal/wordpress-42", 788 }, 789 // 790 Bases: []transport.Base{ 791 { 792 Name: "ubuntu", 793 Architecture: "amd64", 794 Channel: "20.04", 795 }, 796 }, 797 MetadataYAML: ` 798 name: wordpress 799 summary: Blog engine 800 description: Blog engine 801 `[1:], 802 ConfigYAML: ` 803 options: 804 blog-title: {default: My Title, description: A descriptive title used for the blog., type: string} 805 `[1:], 806 Resources: []transport.ResourceRevision{ 807 resourceRevision(5), 808 }, 809 }, 810 EffectiveChannel: "latest/stable", 811 }}, nil 812 }) 813 } 814 815 func (s *charmHubRepositorySuite) setupMocks(c *gc.C) *gomock.Controller { 816 ctrl := gomock.NewController(c) 817 s.client = mocks.NewMockCharmHubClient(ctrl) 818 s.logger = mocks.NewMockLogger(ctrl) 819 s.logger.EXPECT().Tracef(gomock.Any(), gomock.Any()).AnyTimes() 820 return ctrl 821 } 822 823 func (s *charmHubRepositorySuite) expectedCURL(curl *charm.URL, revision int, arch string, series string) *charm.URL { 824 return curl.WithRevision(revision).WithArchitecture(arch).WithSeries(series) 825 } 826 827 func (s *charmHubRepositorySuite) newClient() *CharmHubRepository { 828 return NewCharmHubRepository(s.logger, s.client) 829 } 830 831 func (s *charmHubRepositorySuite) expectRefresh(id bool) { 832 s.expectRefreshWithRevision(0, id) 833 } 834 835 func (s *charmHubRepositorySuite) expectRefreshWithRevision(rev int, id bool) { 836 resp := []transport.RefreshResponse{ 837 { 838 Entity: transport.RefreshEntity{ 839 CreatedAt: time.Date(2020, 7, 7, 9, 39, 44, 132000000, time.UTC), 840 Download: transport.Download{HashSHA256: "c97e1efc5367d2fdcfdf29f4a2243b13765cc9cbdfad19627a29ac903c01ae63", Size: 5487460, URL: "https://api.staging.charmhub.io/api/v1/charms/download/jmeJLrjWpJX9OglKSeUHCwgyaCNuoQjD_208.charm"}, 841 ID: "jmeJLrjWpJX9OglKSeUHCwgyaCNuoQjD", 842 Name: "ubuntu", 843 Resources: []transport.ResourceRevision{ 844 resourceRevision(rev), 845 }, 846 Revision: 19, 847 Summary: "PostgreSQL object-relational SQL database (supported version)", 848 Version: "208", 849 }, 850 EffectiveChannel: "latest/stable", 851 Error: (*transport.APIError)(nil), 852 Name: "postgresql", 853 Result: "download", 854 }, 855 } 856 s.client.EXPECT().Refresh(gomock.Any(), charmhubConfigMatcher{id: id}).Return(resp, nil) 857 } 858 859 // charmhubConfigMatcher matches only the charm IDs and revisions of a 860 // charmhub.RefreshMany config. 861 type charmhubConfigMatcher struct { 862 id bool 863 } 864 865 func (m charmhubConfigMatcher) Matches(x interface{}) bool { 866 config, ok := x.(charmhub.RefreshConfig) 867 if !ok { 868 return false 869 } 870 h, err := config.Build() 871 if err != nil { 872 return false 873 } 874 if m.id && h.Actions[0].ID != nil && *h.Actions[0].ID == "meshuggah" { 875 return true 876 } 877 if !m.id && h.Actions[0].Name != nil && *h.Actions[0].Name == "ubuntu" { 878 return true 879 } 880 return false 881 } 882 883 func (m charmhubConfigMatcher) String() string { 884 if m.id { 885 return "match id" 886 } 887 return "match name" 888 } 889 890 func (s *charmHubRepositorySuite) expectListResourceRevisions(rev int) { 891 resp := []transport.ResourceRevision{ 892 resourceRevision(rev), 893 } 894 s.client.EXPECT().ListResourceRevisions(gomock.Any(), gomock.Any(), gomock.Any()).Return(resp, nil) 895 } 896 897 func fp(c *gc.C) charmresource.Fingerprint { 898 fp, err := charmresource.ParseFingerprint("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b") 899 c.Assert(err, jc.ErrorIsNil) 900 return fp 901 } 902 903 func charmID() corecharm.CharmID { 904 curl := charm.MustParseURL("ubuntu") 905 channel, _ := charm.ParseChannel("stable") 906 return corecharm.CharmID{ 907 URL: curl, 908 Origin: corecharm.Origin{ 909 ID: "meshuggah", 910 Source: corecharm.CharmHub, 911 Channel: &channel, 912 Platform: corecharm.Platform{ 913 OS: "ubuntu", 914 Channel: "20.04", 915 Architecture: "amd64", 916 }, 917 }} 918 } 919 920 func resourceRevision(rev int) transport.ResourceRevision { 921 return transport.ResourceRevision{ 922 Download: transport.Download{ 923 HashSHA384: "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", 924 Size: 0, 925 URL: "https://api.staging.charmhub.io/api/v1/resources/download/charm_jmeJLrjWpJX9OglKSeUHCwgyaCNuoQjD.wal-e_0", 926 }, 927 Name: "wal-e", 928 Revision: rev, 929 Type: "file", 930 Filename: "wal-e.snap", 931 Description: "WAL-E Snap Package", 932 } 933 } 934 935 // RefreshConfigMatcher is required so that we do check somethings going into 936 // the refresh method. As instanceKey is private we don't know what it is until 937 // it's called. 938 type RefreshConfigMatcher struct { 939 c *gc.C 940 Config charmhub.RefreshConfig 941 } 942 943 func (m RefreshConfigMatcher) Matches(x interface{}) bool { 944 rc, ok := x.(charmhub.RefreshConfig) 945 if !ok { 946 return false 947 } 948 949 cb, err := m.Config.Build() 950 m.c.Assert(err, jc.ErrorIsNil) 951 952 rcb, err := rc.Build() 953 m.c.Assert(err, jc.ErrorIsNil) 954 m.c.Assert(len(cb.Actions), gc.Equals, len(rcb.Actions)) 955 956 if cb.Actions[0].ID == nil && rcb.Actions[0].ID == nil { 957 return true 958 } 959 return cb.Actions[0].ID != nil && rcb.Actions[0].ID != nil && *cb.Actions[0].ID == *rcb.Actions[0].ID 960 } 961 962 func (RefreshConfigMatcher) String() string { 963 return "is refresh config" 964 } 965 966 type refreshConfigSuite struct { 967 testing.IsolationSuite 968 } 969 970 var _ = gc.Suite(&refreshConfigSuite{}) 971 972 func (refreshConfigSuite) TestRefreshByChannel(c *gc.C) { 973 name := "wordpress" 974 platform := corecharm.MustParsePlatform("amd64/ubuntu/focal") 975 channel := corecharm.MustParseChannel("latest/stable").Normalize() 976 origin := corecharm.Origin{ 977 Platform: platform, 978 Channel: &channel, 979 } 980 981 cfg, err := refreshConfig(name, origin) 982 c.Assert(err, jc.ErrorIsNil) 983 984 ch := channel.String() 985 instanceKey := charmhub.ExtractConfigInstanceKey(cfg) 986 987 build, err := cfg.Build() 988 c.Assert(err, jc.ErrorIsNil) 989 c.Assert(build, gc.DeepEquals, transport.RefreshRequest{ 990 Actions: []transport.RefreshRequestAction{{ 991 Action: "install", 992 InstanceKey: instanceKey, 993 Name: &name, 994 Channel: &ch, 995 Base: &transport.Base{ 996 Name: "ubuntu", 997 Channel: "20.04", 998 Architecture: "amd64", 999 }, 1000 }}, 1001 Context: []transport.RefreshRequestContext{}, 1002 Fields: expRefreshFields, 1003 }) 1004 } 1005 1006 func (refreshConfigSuite) TestRefreshByChannelVersion(c *gc.C) { 1007 name := "wordpress" 1008 // 'mistakenly' include a risk in the platform 1009 platform := corecharm.MustParsePlatform("amd64/ubuntu/20.10/stable") 1010 channel := corecharm.MustParseChannel("latest/stable").Normalize() 1011 origin := corecharm.Origin{ 1012 Platform: platform, 1013 Channel: &channel, 1014 } 1015 1016 cfg, err := refreshConfig(name, origin) 1017 c.Assert(err, jc.ErrorIsNil) 1018 1019 ch := channel.String() 1020 instanceKey := charmhub.ExtractConfigInstanceKey(cfg) 1021 1022 build, err := cfg.Build() 1023 c.Assert(err, jc.ErrorIsNil) 1024 c.Assert(build, gc.DeepEquals, transport.RefreshRequest{ 1025 Actions: []transport.RefreshRequestAction{{ 1026 Action: "install", 1027 InstanceKey: instanceKey, 1028 Name: &name, 1029 Channel: &ch, 1030 Base: &transport.Base{ 1031 Name: "ubuntu", 1032 Channel: "20.10", 1033 Architecture: "amd64", 1034 }, 1035 }}, 1036 Context: []transport.RefreshRequestContext{}, 1037 Fields: expRefreshFields, 1038 }) 1039 } 1040 1041 func (refreshConfigSuite) TestRefreshByRevision(c *gc.C) { 1042 revision := 1 1043 name := "wordpress" 1044 platform := corecharm.MustParsePlatform("amd64/ubuntu/focal") 1045 origin := corecharm.Origin{ 1046 Platform: platform, 1047 Revision: &revision, 1048 } 1049 1050 cfg, err := refreshConfig(name, origin) 1051 c.Assert(err, jc.ErrorIsNil) 1052 1053 instanceKey := charmhub.ExtractConfigInstanceKey(cfg) 1054 1055 build, err := cfg.Build() 1056 c.Assert(err, jc.ErrorIsNil) 1057 c.Assert(build, gc.DeepEquals, transport.RefreshRequest{ 1058 Actions: []transport.RefreshRequestAction{{ 1059 Action: "install", 1060 InstanceKey: instanceKey, 1061 Name: &name, 1062 Revision: &revision, 1063 }}, 1064 Context: []transport.RefreshRequestContext{}, 1065 Fields: expRefreshFields, 1066 }) 1067 } 1068 1069 func (refreshConfigSuite) TestRefreshByID(c *gc.C) { 1070 id := "aaabbbccc" 1071 revision := 1 1072 platform := corecharm.MustParsePlatform("amd64/ubuntu/focal") 1073 channel := corecharm.MustParseChannel("stable") 1074 origin := corecharm.Origin{ 1075 Type: transport.CharmType.String(), 1076 ID: id, 1077 Platform: platform, 1078 Revision: &revision, 1079 Channel: &channel, 1080 InstanceKey: "instance-key", 1081 } 1082 1083 cfg, err := refreshConfig("wordpress", origin) 1084 c.Assert(err, jc.ErrorIsNil) 1085 1086 instanceKey := charmhub.ExtractConfigInstanceKey(cfg) 1087 1088 build, err := cfg.Build() 1089 c.Assert(err, jc.ErrorIsNil) 1090 c.Assert(build, gc.DeepEquals, transport.RefreshRequest{ 1091 Actions: []transport.RefreshRequestAction{{ 1092 Action: "refresh", 1093 InstanceKey: instanceKey, 1094 ID: &id, 1095 }}, 1096 Context: []transport.RefreshRequestContext{{ 1097 InstanceKey: instanceKey, 1098 ID: id, 1099 Revision: revision, 1100 Base: transport.Base{ 1101 Name: "ubuntu", 1102 Channel: "20.04", 1103 Architecture: "amd64", 1104 }, 1105 TrackingChannel: channel.String(), 1106 }}, 1107 Fields: expRefreshFields, 1108 }) 1109 } 1110 1111 type selectNextBaseSuite struct { 1112 testing.IsolationSuite 1113 logger *mocks.MockLogger 1114 } 1115 1116 var _ = gc.Suite(&selectNextBaseSuite{}) 1117 1118 func (*selectNextBaseSuite) TestSelectNextBaseWithNoBases(c *gc.C) { 1119 repo := new(CharmHubRepository) 1120 _, err := repo.selectNextBases(nil, corecharm.Origin{}) 1121 c.Assert(err, gc.ErrorMatches, `no bases available`) 1122 } 1123 1124 func (*selectNextBaseSuite) TestSelectNextBaseWithInvalidBases(c *gc.C) { 1125 repo := new(CharmHubRepository) 1126 _, err := repo.selectNextBases([]transport.Base{{ 1127 Architecture: "all", 1128 }}, corecharm.Origin{ 1129 Platform: corecharm.Platform{ 1130 Architecture: "amd64", 1131 }, 1132 }) 1133 c.Assert(err, gc.ErrorMatches, `bases matching architecture "amd64" not found`) 1134 } 1135 1136 func (*selectNextBaseSuite) TestSelectNextBaseWithInvalidBaseChannel(c *gc.C) { 1137 repo := new(CharmHubRepository) 1138 _, err := repo.selectNextBases([]transport.Base{{ 1139 Architecture: "amd64", 1140 }}, corecharm.Origin{ 1141 Platform: corecharm.Platform{ 1142 Architecture: "amd64", 1143 OS: "ubuntu", 1144 }, 1145 }) 1146 c.Assert(errors.IsNotValid(err), jc.IsTrue) 1147 } 1148 1149 func (*selectNextBaseSuite) TestSelectNextBaseWithInvalidOS(c *gc.C) { 1150 repo := new(CharmHubRepository) 1151 _, err := repo.selectNextBases([]transport.Base{{ 1152 Architecture: "amd64", 1153 }}, corecharm.Origin{ 1154 Platform: corecharm.Platform{ 1155 Architecture: "amd64", 1156 OS: "ubuntu", 1157 }, 1158 }) 1159 c.Assert(errors.IsNotValid(err), jc.IsTrue) 1160 } 1161 1162 func (*selectNextBaseSuite) TestSelectNextBaseWithValidBases(c *gc.C) { 1163 repo := new(CharmHubRepository) 1164 platform, err := repo.selectNextBases([]transport.Base{{ 1165 Architecture: "amd64", 1166 Name: "ubuntu", 1167 Channel: "20.04", 1168 }}, corecharm.Origin{ 1169 Platform: corecharm.Platform{ 1170 Architecture: "amd64", 1171 OS: "ubuntu", 1172 Channel: "20.04", 1173 }, 1174 }) 1175 c.Assert(err, jc.ErrorIsNil) 1176 c.Assert(platform, gc.DeepEquals, []corecharm.Platform{{ 1177 Architecture: "amd64", 1178 OS: "ubuntu", 1179 Channel: "20.04", 1180 }}) 1181 } 1182 1183 func (*selectNextBaseSuite) TestSelectNextBaseWithValidBasesWithSeries(c *gc.C) { 1184 repo := new(CharmHubRepository) 1185 platform, err := repo.selectNextBases([]transport.Base{{ 1186 Architecture: "amd64", 1187 Name: "ubuntu", 1188 Channel: "focal", 1189 }, { 1190 Architecture: "amd64", 1191 Name: "ubuntu", 1192 Channel: "20.04", 1193 }}, corecharm.Origin{ 1194 Platform: corecharm.Platform{ 1195 Architecture: "amd64", 1196 OS: "ubuntu", 1197 Channel: "20.04", 1198 }, 1199 }) 1200 c.Assert(err, jc.ErrorIsNil) 1201 c.Assert(platform, gc.DeepEquals, []corecharm.Platform{{ 1202 Architecture: "amd64", 1203 OS: "ubuntu", 1204 Channel: "20.04", 1205 }}) 1206 } 1207 1208 func (*selectNextBaseSuite) TestSelectNextBaseWithCentosBase(c *gc.C) { 1209 repo := new(CharmHubRepository) 1210 platform, err := repo.selectNextBases([]transport.Base{{ 1211 Architecture: "amd64", 1212 Name: "centos", 1213 Channel: "7", 1214 }}, corecharm.Origin{ 1215 Platform: corecharm.Platform{ 1216 Architecture: "amd64", 1217 OS: "ubuntu", 1218 Channel: "20.04", 1219 }, 1220 }) 1221 c.Assert(err, jc.ErrorIsNil) 1222 c.Assert(platform, gc.DeepEquals, []corecharm.Platform{{ 1223 Architecture: "amd64", 1224 OS: "centos", 1225 Channel: "7", 1226 }}) 1227 } 1228 1229 func (*selectNextBaseSuite) TestSelectNextBasesFromReleasesNoReleasesError(c *gc.C) { 1230 channel := corecharm.MustParseChannel("stable/foo") 1231 repo := new(CharmHubRepository) 1232 err := repo.handleRevisionNotFound([]transport.Release{}, corecharm.Origin{ 1233 Channel: &channel, 1234 }) 1235 c.Assert(err, gc.ErrorMatches, `no releases available`) 1236 } 1237 1238 func (*selectNextBaseSuite) TestSelectNextBasesFromReleasesAmbiguousMatchError(c *gc.C) { 1239 channel := corecharm.MustParseChannel("stable/foo") 1240 repo := new(CharmHubRepository) 1241 err := repo.handleRevisionNotFound([]transport.Release{ 1242 {}, 1243 }, corecharm.Origin{ 1244 Channel: &channel, 1245 }) 1246 c.Assert(err, gc.ErrorMatches, fmt.Sprintf(`ambiguous arch and series with channel %q. specify both arch and series along with channel`, channel.String())) 1247 } 1248 1249 func (s *selectNextBaseSuite) TestSelectNextBasesFromReleasesSuggestionError(c *gc.C) { 1250 defer s.setupMocks(c).Finish() 1251 repo := NewCharmHubRepository(s.logger, nil) 1252 1253 channel := corecharm.MustParseChannel("stable") 1254 err := repo.handleRevisionNotFound([]transport.Release{{ 1255 Base: transport.Base{ 1256 Name: "os", 1257 Channel: "series", 1258 Architecture: "arch", 1259 }, 1260 Channel: "stable", 1261 }}, corecharm.Origin{ 1262 Channel: &channel, 1263 }) 1264 c.Assert(err, gc.ErrorMatches, `charm or bundle not found for channel "stable", base ""`) 1265 } 1266 1267 func (s *selectNextBaseSuite) TestSelectNextBasesFromReleasesSuggestion(c *gc.C) { 1268 defer s.setupMocks(c).Finish() 1269 repo := NewCharmHubRepository(s.logger, nil) 1270 err := repo.handleRevisionNotFound([]transport.Release{{ 1271 Base: transport.Base{ 1272 Name: "ubuntu", 1273 Channel: "20.04", 1274 Architecture: "arch", 1275 }, 1276 Channel: "stable", 1277 }}, corecharm.Origin{ 1278 Platform: corecharm.Platform{ 1279 Architecture: "arch", 1280 }, 1281 }) 1282 c.Assert(err, gc.ErrorMatches, 1283 `charm or bundle not found in the charm's default channel, base "arch" 1284 available releases are: 1285 channel "latest/stable": available bases are: ubuntu@20.04`) 1286 } 1287 1288 func (s *selectNextBaseSuite) setupMocks(c *gc.C) *gomock.Controller { 1289 ctrl := gomock.NewController(c) 1290 s.logger = mocks.NewMockLogger(ctrl) 1291 s.logger.EXPECT().Errorf(gomock.Any(), gomock.Any()).AnyTimes() 1292 s.logger.EXPECT().Tracef(gomock.Any(), gomock.Any()).AnyTimes() 1293 return ctrl 1294 } 1295 1296 type composeSuggestionsSuite struct { 1297 testing.IsolationSuite 1298 logger *mocks.MockLogger 1299 } 1300 1301 var _ = gc.Suite(&composeSuggestionsSuite{}) 1302 1303 func (s *composeSuggestionsSuite) TestNoReleases(c *gc.C) { 1304 defer s.setupMocks(c).Finish() 1305 repo := NewCharmHubRepository(s.logger, nil) 1306 suggestions := repo.composeSuggestions([]transport.Release{}, corecharm.Origin{}) 1307 c.Assert(suggestions, gc.DeepEquals, []string(nil)) 1308 } 1309 1310 func (s *composeSuggestionsSuite) TestNoMatchingArch(c *gc.C) { 1311 defer s.setupMocks(c).Finish() 1312 repo := NewCharmHubRepository(s.logger, nil) 1313 suggestions := repo.composeSuggestions([]transport.Release{{ 1314 Base: transport.Base{ 1315 Name: "os", 1316 Channel: "series", 1317 Architecture: "arch", 1318 }, 1319 Channel: "stable", 1320 }}, corecharm.Origin{}) 1321 c.Assert(suggestions, gc.DeepEquals, []string(nil)) 1322 } 1323 1324 func (s *composeSuggestionsSuite) TestSuggestion(c *gc.C) { 1325 defer s.setupMocks(c).Finish() 1326 repo := NewCharmHubRepository(s.logger, nil) 1327 suggestions := repo.composeSuggestions([]transport.Release{{ 1328 Base: transport.Base{ 1329 Name: "ubuntu", 1330 Channel: "20.04", 1331 Architecture: "arch", 1332 }, 1333 Channel: "stable", 1334 }}, corecharm.Origin{ 1335 Platform: corecharm.Platform{ 1336 Architecture: "arch", 1337 }, 1338 }) 1339 c.Assert(suggestions, gc.DeepEquals, []string{ 1340 `channel "latest/stable": available bases are: ubuntu@20.04`, 1341 }) 1342 } 1343 1344 func (s *composeSuggestionsSuite) TestSuggestionWithRisk(c *gc.C) { 1345 defer s.setupMocks(c).Finish() 1346 repo := NewCharmHubRepository(s.logger, nil) 1347 suggestions := repo.composeSuggestions([]transport.Release{{ 1348 Base: transport.Base{ 1349 Name: "ubuntu", 1350 Channel: "20.04/stable", 1351 Architecture: "arch", 1352 }, 1353 Channel: "stable", 1354 }}, corecharm.Origin{ 1355 Platform: corecharm.Platform{ 1356 Architecture: "arch", 1357 }, 1358 }) 1359 c.Assert(suggestions, gc.DeepEquals, []string{ 1360 `channel "latest/stable": available bases are: ubuntu@20.04`, 1361 }) 1362 } 1363 1364 func (s *composeSuggestionsSuite) TestMultipleSuggestion(c *gc.C) { 1365 defer s.setupMocks(c).Finish() 1366 repo := NewCharmHubRepository(s.logger, nil) 1367 suggestions := repo.composeSuggestions([]transport.Release{{ 1368 Base: transport.Base{ 1369 Name: "ubuntu", 1370 Channel: "20.04", 1371 Architecture: "c", 1372 }, 1373 Channel: "stable", 1374 }, { 1375 Base: transport.Base{ 1376 Name: "ubuntu", 1377 Channel: "18.04", 1378 Architecture: "c", 1379 }, 1380 Channel: "stable", 1381 }, { 1382 Base: transport.Base{ 1383 Name: "ubuntu", 1384 Channel: "18.04", 1385 Architecture: "all", 1386 }, 1387 Channel: "2.0/stable", 1388 }, { 1389 Base: transport.Base{ 1390 Name: "g", 1391 Channel: "h", 1392 Architecture: "i", 1393 }, 1394 Channel: "stable", 1395 }}, corecharm.Origin{ 1396 Platform: corecharm.Platform{ 1397 Architecture: "c", 1398 }, 1399 }) 1400 c.Assert(suggestions, jc.SameContents, []string{ 1401 `channel "latest/stable": available bases are: ubuntu@20.04, ubuntu@18.04`, 1402 `channel "2.0/stable": available bases are: ubuntu@18.04`, 1403 }) 1404 } 1405 1406 func (s *composeSuggestionsSuite) TestCentosSuggestion(c *gc.C) { 1407 defer s.setupMocks(c).Finish() 1408 repo := NewCharmHubRepository(s.logger, nil) 1409 suggestions := repo.composeSuggestions([]transport.Release{{ 1410 Base: transport.Base{ 1411 Name: "centos", 1412 Channel: "7", 1413 Architecture: "c", 1414 }, 1415 Channel: "stable", 1416 }}, corecharm.Origin{ 1417 Platform: corecharm.Platform{ 1418 Architecture: "c", 1419 }, 1420 }) 1421 c.Assert(suggestions, gc.DeepEquals, []string{ 1422 `channel "latest/stable": available bases are: centos@7`, 1423 }) 1424 } 1425 1426 func (s *composeSuggestionsSuite) setupMocks(c *gc.C) *gomock.Controller { 1427 ctrl := gomock.NewController(c) 1428 s.logger = mocks.NewMockLogger(ctrl) 1429 s.logger.EXPECT().Errorf(gomock.Any(), gomock.Any()).AnyTimes() 1430 s.logger.EXPECT().Tracef(gomock.Any(), gomock.Any()).AnyTimes() 1431 return ctrl 1432 }