github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/gui/upgradegui_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package gui_test 5 6 import ( 7 "archive/tar" 8 "bytes" 9 "crypto/sha256" 10 "errors" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "os" 15 "os/exec" 16 "path/filepath" 17 "runtime" 18 "strings" 19 20 jc "github.com/juju/testing/checkers" 21 "github.com/juju/version" 22 gc "gopkg.in/check.v1" 23 24 "github.com/juju/juju/api" 25 "github.com/juju/juju/apiserver/params" 26 "github.com/juju/juju/cmd/juju/gui" 27 envgui "github.com/juju/juju/environs/gui" 28 "github.com/juju/juju/environs/simplestreams" 29 jujutesting "github.com/juju/juju/juju/testing" 30 coretesting "github.com/juju/juju/testing" 31 ) 32 33 type upgradeGUISuite struct { 34 jujutesting.JujuConnSuite 35 } 36 37 var _ = gc.Suite(&upgradeGUISuite{}) 38 39 // run executes the upgrade-gui command passing the given args. 40 func (s *upgradeGUISuite) run(c *gc.C, args ...string) (string, error) { 41 ctx, err := coretesting.RunCommand(c, gui.NewUpgradeGUICommand(), args...) 42 return strings.Trim(coretesting.Stderr(ctx), "\n"), err 43 } 44 45 // calledFunc is returned by the patch* methods below, and when called reports 46 // whether the corresponding patched function has been called. 47 type calledFunc func() bool 48 49 func (s *upgradeGUISuite) patchClientGUIArchives(c *gc.C, returnedVersions []params.GUIArchiveVersion, returnedErr error) calledFunc { 50 var called bool 51 f := func(client *api.Client) ([]params.GUIArchiveVersion, error) { 52 called = true 53 return returnedVersions, returnedErr 54 } 55 s.PatchValue(gui.ClientGUIArchives, f) 56 return func() bool { 57 return called 58 } 59 } 60 61 func (s *upgradeGUISuite) patchClientSelectGUIVersion(c *gc.C, expectedVers string, returnedErr error) calledFunc { 62 var called bool 63 f := func(client *api.Client, vers version.Number) error { 64 called = true 65 c.Assert(vers.String(), gc.Equals, expectedVers) 66 return returnedErr 67 } 68 s.PatchValue(gui.ClientSelectGUIVersion, f) 69 return func() bool { 70 return called 71 } 72 } 73 74 func (s *upgradeGUISuite) patchClientUploadGUIArchive(c *gc.C, expectedHash string, expectedSize int64, expectedVers string, returnedIsCurrent bool, returnedErr error) calledFunc { 75 var called bool 76 f := func(client *api.Client, r io.ReadSeeker, hash string, size int64, vers version.Number) (bool, error) { 77 called = true 78 c.Assert(hash, gc.Equals, expectedHash) 79 c.Assert(size, gc.Equals, expectedSize) 80 c.Assert(vers.String(), gc.Equals, expectedVers) 81 return returnedIsCurrent, returnedErr 82 } 83 s.PatchValue(gui.ClientUploadGUIArchive, f) 84 return func() bool { 85 return called 86 } 87 } 88 89 func (s *upgradeGUISuite) patchGUIFetchMetadata(c *gc.C, returnedMetadata []*envgui.Metadata, returnedErr error) calledFunc { 90 var called bool 91 f := func(stream string, sources ...simplestreams.DataSource) ([]*envgui.Metadata, error) { 92 called = true 93 c.Assert(stream, gc.Equals, envgui.ReleasedStream) 94 c.Assert(sources[0].Description(), gc.Equals, "gui simplestreams") 95 return returnedMetadata, returnedErr 96 } 97 s.PatchValue(gui.GUIFetchMetadata, f) 98 return func() bool { 99 return called 100 } 101 } 102 103 var upgradeGUIInputErrorsTests = []struct { 104 about string 105 args []string 106 expectedError string 107 }{{ 108 about: "too many arguments", 109 args: []string{"bad", "wolf"}, 110 expectedError: `unrecognized args: \["bad" "wolf"\]`, 111 }, { 112 about: "listing and upgrading", 113 args: []string{"bad", "--list"}, 114 expectedError: "cannot provide arguments if --list is provided", 115 }, { 116 about: "archive path not found", 117 args: []string{"no-such-file"}, 118 expectedError: `invalid GUI release version or local path "no-such-file"`, 119 }} 120 121 func (s *upgradeGUISuite) TestUpgradeGUIInputErrors(c *gc.C) { 122 for i, test := range upgradeGUIInputErrorsTests { 123 c.Logf("\n%d: %s", i, test.about) 124 _, err := s.run(c, test.args...) 125 c.Assert(err, gc.ErrorMatches, test.expectedError) 126 } 127 } 128 129 func (s *upgradeGUISuite) TestUpgradeGUIListSuccess(c *gc.C) { 130 s.patchGUIFetchMetadata(c, []*envgui.Metadata{{ 131 Version: version.MustParse("2.2.0"), 132 }, { 133 Version: version.MustParse("2.1.1"), 134 }, { 135 Version: version.MustParse("2.1.0"), 136 }}, nil) 137 uploadCalled := s.patchClientUploadGUIArchive(c, "", 0, "", false, nil) 138 selectCalled := s.patchClientSelectGUIVersion(c, "", nil) 139 140 // Run the command to list available Juju GUI archive versions. 141 out, err := s.run(c, "--list") 142 c.Assert(err, jc.ErrorIsNil) 143 c.Assert(out, gc.Equals, "2.2.0\n2.1.1\n2.1.0") 144 145 // No uploads or switches are preformed. 146 c.Assert(uploadCalled(), jc.IsFalse) 147 c.Assert(selectCalled(), jc.IsFalse) 148 } 149 150 func (s *upgradeGUISuite) TestUpgradeGUIListNoReleases(c *gc.C) { 151 s.patchGUIFetchMetadata(c, nil, nil) 152 out, err := s.run(c, "--list") 153 c.Assert(err, gc.ErrorMatches, "cannot list Juju GUI release versions: no available Juju GUI archives found") 154 c.Assert(out, gc.Equals, "") 155 } 156 157 func (s *upgradeGUISuite) TestUpgradeGUIListError(c *gc.C) { 158 s.patchGUIFetchMetadata(c, nil, errors.New("bad wolf")) 159 out, err := s.run(c, "--list") 160 c.Assert(err, gc.ErrorMatches, "cannot list Juju GUI release versions: cannot retrieve Juju GUI archive info: bad wolf") 161 c.Assert(out, gc.Equals, "") 162 } 163 164 func (s *upgradeGUISuite) TestUpgradeGUIFileError(c *gc.C) { 165 path, _, _ := saveGUIArchive(c, "2.0.0") 166 err := os.Chmod(path, 0000) 167 c.Assert(err, jc.ErrorIsNil) 168 defer os.Chmod(path, 0600) 169 out, err := s.run(c, path) 170 c.Assert(err, gc.ErrorMatches, "cannot open GUI archive: .*") 171 c.Assert(out, gc.Equals, "") 172 } 173 174 func (s *upgradeGUISuite) TestUpgradeGUIArchiveVersionNotValid(c *gc.C) { 175 path, _, _ := saveGUIArchive(c, "bad-wolf") 176 out, err := s.run(c, path) 177 c.Assert(err, gc.ErrorMatches, `cannot upgrade Juju GUI using ".*": invalid version "bad-wolf" in archive`) 178 c.Assert(out, gc.Equals, "") 179 } 180 181 func (s *upgradeGUISuite) TestUpgradeGUIArchiveVersionNotFound(c *gc.C) { 182 path, _, _ := saveGUIArchive(c, "") 183 out, err := s.run(c, path) 184 c.Assert(err, gc.ErrorMatches, `cannot upgrade Juju GUI using ".*": cannot find Juju GUI version in archive`) 185 c.Assert(out, gc.Equals, "") 186 } 187 188 func (s *upgradeGUISuite) TestUpgradeGUIGUIArchivesError(c *gc.C) { 189 path, _, _ := saveGUIArchive(c, "2.1.0") 190 s.patchClientGUIArchives(c, nil, errors.New("bad wolf")) 191 out, err := s.run(c, path) 192 c.Assert(err, gc.ErrorMatches, "cannot retrieve GUI versions from the controller: bad wolf") 193 c.Assert(out, gc.Equals, "") 194 } 195 196 func (s *upgradeGUISuite) TestUpgradeGUIUploadGUIArchiveError(c *gc.C) { 197 path, hash, size := saveGUIArchive(c, "2.2.0") 198 s.patchClientGUIArchives(c, nil, nil) 199 s.patchClientUploadGUIArchive(c, hash, size, "2.2.0", false, errors.New("bad wolf")) 200 out, err := s.run(c, path) 201 c.Assert(err, gc.ErrorMatches, "cannot upload Juju GUI: bad wolf") 202 c.Assert(out, gc.Equals, "fetching Juju GUI archive\nuploading Juju GUI 2.2.0") 203 } 204 205 func (s *upgradeGUISuite) TestUpgradeGUISelectGUIVersionError(c *gc.C) { 206 path, hash, size := saveGUIArchive(c, "2.3.0") 207 s.patchClientGUIArchives(c, nil, nil) 208 s.patchClientUploadGUIArchive(c, hash, size, "2.3.0", false, nil) 209 s.patchClientSelectGUIVersion(c, "2.3.0", errors.New("bad wolf")) 210 out, err := s.run(c, path) 211 c.Assert(err, gc.ErrorMatches, "cannot switch to new Juju GUI version: bad wolf") 212 c.Assert(out, gc.Equals, "fetching Juju GUI archive\nuploading Juju GUI 2.3.0\nupload completed") 213 } 214 215 func (s *upgradeGUISuite) TestUpgradeGUIFromSimplestreamsReleaseErrors(c *gc.C) { 216 tests := []struct { 217 about string 218 arg string 219 returnedMetadata []*envgui.Metadata 220 returnedErr error 221 expectedErr string 222 }{{ 223 about: "last release: no releases found", 224 expectedErr: "cannot upgrade to most recent release: no available Juju GUI archives found", 225 }, { 226 about: "specific release: no releases found", 227 arg: "2.0.42", 228 expectedErr: "cannot upgrade to release 2.0.42: no available Juju GUI archives found", 229 }, { 230 about: "last release: error while fetching releases list", 231 returnedErr: errors.New("bad wolf"), 232 expectedErr: "cannot upgrade to most recent release: cannot retrieve Juju GUI archive info: bad wolf", 233 }, { 234 about: "specific release: error while fetching releases list", 235 arg: "2.0.47", 236 returnedErr: errors.New("bad wolf"), 237 expectedErr: "cannot upgrade to release 2.0.47: cannot retrieve Juju GUI archive info: bad wolf", 238 }, { 239 about: "last release: error while opening the remote release resource", 240 returnedMetadata: []*envgui.Metadata{ 241 makeGUIMetadata(c, "2.2.0", "exterminate"), 242 makeGUIMetadata(c, "2.1.0", ""), 243 }, 244 expectedErr: `cannot open Juju GUI archive at "https://1.2.3.4/path/to/gui/2.2.0": exterminate`, 245 }, { 246 about: "specific release: error while opening the remote release resource", 247 arg: "2.1.0", 248 returnedMetadata: []*envgui.Metadata{ 249 makeGUIMetadata(c, "2.2.0", ""), 250 makeGUIMetadata(c, "2.1.0", "boo"), 251 makeGUIMetadata(c, "2.0.0", ""), 252 }, 253 expectedErr: `cannot open Juju GUI archive at "https://1.2.3.4/path/to/gui/2.1.0": boo`, 254 }, { 255 about: "specific release: not found in available releases", 256 arg: "2.1.0", 257 returnedMetadata: []*envgui.Metadata{ 258 makeGUIMetadata(c, "2.2.0", ""), 259 makeGUIMetadata(c, "2.0.0", ""), 260 }, 261 expectedErr: "Juju GUI release version 2.1.0 not found", 262 }} 263 264 for i, test := range tests { 265 c.Logf("\n%d: %s", i, test.about) 266 267 s.patchGUIFetchMetadata(c, test.returnedMetadata, test.returnedErr) 268 out, err := s.run(c, test.arg) 269 c.Assert(err, gc.ErrorMatches, test.expectedErr) 270 c.Assert(out, gc.Equals, "") 271 } 272 } 273 274 func (s *upgradeGUISuite) TestUpgradeGUISuccess(c *gc.C) { 275 tests := []struct { 276 // about describes the test. 277 about string 278 // returnedMetadata holds metadata information returned by simplestreams. 279 returnedMetadata *envgui.Metadata 280 // archiveVersion is the version of the archive to be uploaded. 281 archiveVersion string 282 // existingVersions is a function returning a list of GUI archive versions 283 // already included in the controller. 284 existingVersions func(hash string) []params.GUIArchiveVersion 285 // opened holds whether Juju GUI metadata information in simplestreams 286 // has been opened. 287 opened bool 288 // uploaded holds whether the archive has been actually uploaded. If an 289 // archive with the same hash and version is already present in the 290 // controller, the upload is not performed again. 291 uploaded bool 292 // selected holds whether a new GUI version must be selected. If the upload 293 // upgraded the currently served version there is no need to perform 294 // the API call to switch GUI version. 295 selected bool 296 // expectedOutput holds the expected upgrade-gui command output. 297 expectedOutput string 298 }{{ 299 about: "archive: first archive", 300 archiveVersion: "2.0.0", 301 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.0\nupload completed\nJuju GUI switched to version 2.0.0", 302 uploaded: true, 303 selected: true, 304 }, { 305 about: "archive: new archive", 306 archiveVersion: "2.1.0", 307 existingVersions: func(hash string) []params.GUIArchiveVersion { 308 return []params.GUIArchiveVersion{{ 309 Version: version.MustParse("1.0.0"), 310 SHA256: "hash-1", 311 Current: true, 312 }} 313 }, 314 uploaded: true, 315 selected: true, 316 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.1.0\nupload completed\nJuju GUI switched to version 2.1.0", 317 }, { 318 about: "archive: new archive, existing non-current version", 319 archiveVersion: "2.0.42", 320 existingVersions: func(hash string) []params.GUIArchiveVersion { 321 return []params.GUIArchiveVersion{{ 322 Version: version.MustParse("2.0.42"), 323 SHA256: "hash-42", 324 Current: false, 325 }, { 326 Version: version.MustParse("2.0.47"), 327 SHA256: "hash-47", 328 Current: true, 329 }} 330 }, 331 uploaded: true, 332 selected: true, 333 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.42\nupload completed\nJuju GUI switched to version 2.0.42", 334 }, { 335 about: "archive: new archive, existing current version", 336 archiveVersion: "2.0.47", 337 existingVersions: func(hash string) []params.GUIArchiveVersion { 338 return []params.GUIArchiveVersion{{ 339 Version: version.MustParse("2.0.47"), 340 SHA256: "hash-47", 341 Current: true, 342 }} 343 }, 344 uploaded: true, 345 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.47\nupload completed\nJuju GUI at version 2.0.47", 346 }, { 347 about: "archive: existing archive, existing non-current version", 348 archiveVersion: "2.0.42", 349 existingVersions: func(hash string) []params.GUIArchiveVersion { 350 return []params.GUIArchiveVersion{{ 351 Version: version.MustParse("2.0.42"), 352 SHA256: hash, 353 Current: false, 354 }, { 355 Version: version.MustParse("2.0.47"), 356 SHA256: "hash-47", 357 Current: true, 358 }} 359 }, 360 selected: true, 361 expectedOutput: "Juju GUI switched to version 2.0.42", 362 }, { 363 about: "archive: existing archive, existing current version", 364 archiveVersion: "1.47.0", 365 existingVersions: func(hash string) []params.GUIArchiveVersion { 366 return []params.GUIArchiveVersion{{ 367 Version: version.MustParse("1.47.0"), 368 SHA256: hash, 369 Current: true, 370 }} 371 }, 372 expectedOutput: "Juju GUI at version 1.47.0", 373 }, { 374 about: "archive: existing archive, different existing version", 375 archiveVersion: "2.0.42", 376 existingVersions: func(hash string) []params.GUIArchiveVersion { 377 return []params.GUIArchiveVersion{{ 378 Version: version.MustParse("2.0.42"), 379 SHA256: "hash-42", 380 Current: false, 381 }, { 382 Version: version.MustParse("2.0.47"), 383 SHA256: hash, 384 Current: true, 385 }} 386 }, 387 uploaded: true, 388 selected: true, 389 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.42\nupload completed\nJuju GUI switched to version 2.0.42", 390 }, { 391 about: "stream: first archive", 392 archiveVersion: "2.0.0", 393 returnedMetadata: makeGUIMetadata(c, "2.0.0", ""), 394 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.0\nupload completed\nJuju GUI switched to version 2.0.0", 395 opened: true, 396 uploaded: true, 397 selected: true, 398 }, { 399 about: "stream: new archive", 400 archiveVersion: "2.1.0", 401 returnedMetadata: makeGUIMetadata(c, "2.1.0", ""), 402 existingVersions: func(hash string) []params.GUIArchiveVersion { 403 return []params.GUIArchiveVersion{{ 404 Version: version.MustParse("1.0.0"), 405 SHA256: "hash-1", 406 Current: true, 407 }} 408 }, 409 opened: true, 410 uploaded: true, 411 selected: true, 412 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.1.0\nupload completed\nJuju GUI switched to version 2.1.0", 413 }, { 414 about: "stream: new archive, existing non-current version", 415 archiveVersion: "2.0.42", 416 returnedMetadata: makeGUIMetadata(c, "2.0.42", ""), 417 existingVersions: func(hash string) []params.GUIArchiveVersion { 418 return []params.GUIArchiveVersion{{ 419 Version: version.MustParse("2.0.42"), 420 SHA256: "hash-42", 421 Current: false, 422 }, { 423 Version: version.MustParse("2.0.47"), 424 SHA256: "hash-47", 425 Current: true, 426 }} 427 }, 428 opened: true, 429 uploaded: true, 430 selected: true, 431 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.42\nupload completed\nJuju GUI switched to version 2.0.42", 432 }, { 433 about: "stream: new archive, existing current version", 434 archiveVersion: "2.0.47", 435 returnedMetadata: makeGUIMetadata(c, "2.0.47", ""), 436 existingVersions: func(hash string) []params.GUIArchiveVersion { 437 return []params.GUIArchiveVersion{{ 438 Version: version.MustParse("2.0.47"), 439 SHA256: "hash-47", 440 Current: true, 441 }} 442 }, 443 opened: true, 444 uploaded: true, 445 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.47\nupload completed\nJuju GUI at version 2.0.47", 446 }, { 447 about: "stream: existing archive, existing non-current version", 448 archiveVersion: "2.0.42", 449 returnedMetadata: makeGUIMetadata(c, "2.0.42", ""), 450 existingVersions: func(hash string) []params.GUIArchiveVersion { 451 return []params.GUIArchiveVersion{{ 452 Version: version.MustParse("2.0.42"), 453 SHA256: hash, 454 Current: false, 455 }, { 456 Version: version.MustParse("2.0.47"), 457 SHA256: "hash-47", 458 Current: true, 459 }} 460 }, 461 opened: true, 462 selected: true, 463 expectedOutput: "Juju GUI switched to version 2.0.42", 464 }, { 465 about: "stream: existing archive, existing current version", 466 archiveVersion: "1.47.0", 467 returnedMetadata: makeGUIMetadata(c, "1.47.0", ""), 468 existingVersions: func(hash string) []params.GUIArchiveVersion { 469 return []params.GUIArchiveVersion{{ 470 Version: version.MustParse("1.47.0"), 471 SHA256: hash, 472 Current: true, 473 }} 474 }, 475 opened: true, 476 expectedOutput: "Juju GUI at version 1.47.0", 477 }, { 478 about: "stream: existing archive, different existing version", 479 archiveVersion: "2.0.42", 480 returnedMetadata: makeGUIMetadata(c, "2.0.42", ""), 481 existingVersions: func(hash string) []params.GUIArchiveVersion { 482 return []params.GUIArchiveVersion{{ 483 Version: version.MustParse("2.0.42"), 484 SHA256: "hash-42", 485 Current: false, 486 }, { 487 Version: version.MustParse("2.0.47"), 488 SHA256: hash, 489 Current: true, 490 }} 491 }, 492 opened: true, 493 uploaded: true, 494 selected: true, 495 expectedOutput: "fetching Juju GUI archive\nuploading Juju GUI 2.0.42\nupload completed\nJuju GUI switched to version 2.0.42", 496 }} 497 498 for i, test := range tests { 499 c.Logf("\n%d: %s", i, test.about) 500 501 var arg string 502 var hash string 503 var size int64 504 505 if test.returnedMetadata == nil { 506 // Create an fake Juju GUI local archive. 507 arg, hash, size = saveGUIArchive(c, test.archiveVersion) 508 } else { 509 // User the remote metadata information. 510 arg = test.returnedMetadata.Version.String() 511 hash = test.returnedMetadata.SHA256 512 size = test.returnedMetadata.Size 513 } 514 515 // Patch the call to get simplestreams metadata information. 516 fetchMetadataCalled := s.patchGUIFetchMetadata(c, []*envgui.Metadata{test.returnedMetadata}, nil) 517 518 // Patch the call to get existing archive versions. 519 var existingVersions []params.GUIArchiveVersion 520 if test.existingVersions != nil { 521 existingVersions = test.existingVersions(hash) 522 } 523 guiArchivesCalled := s.patchClientGUIArchives(c, existingVersions, nil) 524 525 // Patch the other calls to the controller. 526 uploadGUIArchiveCalled := s.patchClientUploadGUIArchive(c, hash, size, test.archiveVersion, !test.selected, nil) 527 selectGUIVersionCalled := s.patchClientSelectGUIVersion(c, test.archiveVersion, nil) 528 529 // Run the command. 530 out, err := s.run(c, arg) 531 c.Assert(err, jc.ErrorIsNil) 532 c.Assert(out, gc.Equals, test.expectedOutput) 533 c.Assert(guiArchivesCalled(), jc.IsTrue) 534 c.Assert(fetchMetadataCalled(), gc.Equals, test.opened) 535 c.Assert(uploadGUIArchiveCalled(), gc.Equals, test.uploaded) 536 c.Assert(selectGUIVersionCalled(), gc.Equals, test.selected) 537 } 538 } 539 540 func (s *upgradeGUISuite) TestUpgradeGUIIntegration(c *gc.C) { 541 // Prepare a GUI archive. 542 path, hash, size := saveGUIArchive(c, "2.42.0") 543 544 // Upload the archive from command line. 545 out, err := s.run(c, path) 546 c.Assert(err, jc.ErrorIsNil) 547 c.Assert(out, gc.Equals, "fetching Juju GUI archive\nuploading Juju GUI 2.42.0\nupload completed\nJuju GUI switched to version 2.42.0") 548 549 // Check that the archive is present in the GUI storage server side. 550 storage, err := s.State.GUIStorage() 551 c.Assert(err, jc.ErrorIsNil) 552 defer storage.Close() 553 metadata, err := storage.Metadata("2.42.0") 554 c.Assert(err, jc.ErrorIsNil) 555 c.Assert(metadata.SHA256, gc.Equals, hash) 556 c.Assert(metadata.Size, gc.Equals, size) 557 558 // Check that the uploaded version has been set as the current one. 559 vers, err := s.State.GUIVersion() 560 c.Assert(err, jc.ErrorIsNil) 561 c.Assert(vers.String(), gc.Equals, "2.42.0") 562 } 563 564 // makeGUIArchive creates a Juju GUI tar.bz2 archive in memory, and returns a 565 // reader for the archive, its SHA256 hash and size. 566 func makeGUIArchive(c *gc.C, vers string) (r io.Reader, hash string, size int64) { 567 if runtime.GOOS == "windows" { 568 c.Skip("bzip2 command not available") 569 } 570 cmd := exec.Command("bzip2", "--compress", "--stdout", "--fast") 571 572 stdin, err := cmd.StdinPipe() 573 c.Assert(err, jc.ErrorIsNil) 574 stdout, err := cmd.StdoutPipe() 575 c.Assert(err, jc.ErrorIsNil) 576 577 err = cmd.Start() 578 c.Assert(err, jc.ErrorIsNil) 579 580 tw := tar.NewWriter(stdin) 581 if vers != "" { 582 err = tw.WriteHeader(&tar.Header{ 583 Name: filepath.Join("jujugui-"+vers, "jujugui"), 584 Mode: 0700, 585 Typeflag: tar.TypeDir, 586 }) 587 c.Assert(err, jc.ErrorIsNil) 588 } 589 err = tw.Close() 590 c.Assert(err, jc.ErrorIsNil) 591 err = stdin.Close() 592 c.Assert(err, jc.ErrorIsNil) 593 594 h := sha256.New() 595 r = io.TeeReader(stdout, h) 596 b, err := ioutil.ReadAll(r) 597 c.Assert(err, jc.ErrorIsNil) 598 599 err = cmd.Wait() 600 c.Assert(err, jc.ErrorIsNil) 601 602 return bytes.NewReader(b), fmt.Sprintf("%x", h.Sum(nil)), int64(len(b)) 603 } 604 605 // saveGUIArchive creates a Juju GUI tar.bz2 archive with the given version on 606 // disk, and return its path, SHA256 hash and size. 607 func saveGUIArchive(c *gc.C, vers string) (path, hash string, size int64) { 608 r, hash, size := makeGUIArchive(c, vers) 609 path = filepath.Join(c.MkDir(), "gui.tar.bz2") 610 data, err := ioutil.ReadAll(r) 611 c.Assert(err, jc.ErrorIsNil) 612 err = ioutil.WriteFile(path, data, 0600) 613 c.Assert(err, jc.ErrorIsNil) 614 return path, hash, size 615 } 616 617 // makeGUIMetadata creates and return a Juju GUI archive metadata with the 618 // given version. If fetchError is not empty, trying to fetch the corresponding 619 // archive will return the given error. 620 func makeGUIMetadata(c *gc.C, vers, fetchError string) *envgui.Metadata { 621 path, hash, size := saveGUIArchive(c, vers) 622 metaPath := "/path/to/gui/" + vers 623 return &envgui.Metadata{ 624 Version: version.MustParse(vers), 625 SHA256: hash, 626 Size: size, 627 Path: metaPath, 628 FullPath: "https://1.2.3.4" + metaPath, 629 Source: &dataSource{ 630 DataSource: envgui.NewDataSource("htpps://1.2.3.4"), 631 metaPath: metaPath, 632 path: path, 633 fetchError: fetchError, 634 c: c, 635 }, 636 } 637 } 638 639 // datasource implements simplestreams.DataSource and overrides the Fetch 640 // method for testing purposes. 641 type dataSource struct { 642 simplestreams.DataSource 643 644 metaPath string 645 path string 646 fetchError string 647 c *gc.C 648 } 649 650 // Fetch implements simplestreams.DataSource. 651 func (ds *dataSource) Fetch(path string) (io.ReadCloser, string, error) { 652 ds.c.Assert(path, gc.Equals, ds.metaPath) 653 if ds.fetchError != "" { 654 return nil, "", errors.New(ds.fetchError) 655 } 656 f, err := os.Open(ds.path) 657 ds.c.Assert(err, jc.ErrorIsNil) 658 return f, "", nil 659 }