github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/resource/deploy_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resource 5 6 import ( 7 "bytes" 8 "io" 9 "io/ioutil" 10 "os" 11 "path" 12 13 "github.com/juju/errors" 14 "github.com/juju/juju/core/resources" 15 "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/juju/charm.v6" 19 charmresource "gopkg.in/juju/charm.v6/resource" 20 "gopkg.in/macaroon.v2-unstable" 21 22 "github.com/juju/juju/charmstore" 23 ) 24 25 type DeploySuite struct { 26 testing.IsolationSuite 27 28 stub *testing.Stub 29 } 30 31 var _ = gc.Suite(&DeploySuite{}) 32 33 func (s *DeploySuite) SetUpTest(c *gc.C) { 34 s.IsolationSuite.SetUpTest(c) 35 36 s.stub = &testing.Stub{} 37 } 38 39 func (s DeploySuite) TestDeployResourcesWithoutFiles(c *gc.C) { 40 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 41 cURL := charm.MustParseURL("cs:~a-user/trusty/spam-5") 42 chID := charmstore.CharmID{ 43 URL: cURL, 44 } 45 csMac := &macaroon.Macaroon{} 46 resources := map[string]charmresource.Meta{ 47 "store-tarball": { 48 Name: "store-tarball", 49 Type: charmresource.TypeFile, 50 Path: "store.tgz", 51 }, 52 "store-zip": { 53 Name: "store-zip", 54 Type: charmresource.TypeFile, 55 Path: "store.zip", 56 }, 57 } 58 59 ids, err := DeployResources(DeployResourcesArgs{ 60 ApplicationID: "mysql", 61 CharmID: chID, 62 CharmStoreMacaroon: csMac, 63 ResourceValues: nil, 64 Client: deps, 65 ResourcesMeta: resources, 66 }) 67 c.Assert(err, jc.ErrorIsNil) 68 69 c.Check(ids, gc.DeepEquals, map[string]string{ 70 "store-tarball": "id-store-tarball", 71 "store-zip": "id-store-zip", 72 }) 73 74 s.stub.CheckCallNames(c, "AddPendingResources") 75 s.stub.CheckCall(c, 0, "AddPendingResources", "mysql", chID, csMac, []charmresource.Resource{{ 76 Meta: resources["store-tarball"], 77 Origin: charmresource.OriginStore, 78 Revision: -1, 79 }, { 80 Meta: resources["store-zip"], 81 Origin: charmresource.OriginStore, 82 Revision: -1, 83 }}) 84 } 85 86 func (s DeploySuite) TestUploadFilesOnly(c *gc.C) { 87 deps := uploadDeps{s.stub, rsc{bytes.NewBufferString("file contents")}} 88 cURL := charm.MustParseURL("cs:~a-user/trusty/spam-5") 89 chID := charmstore.CharmID{ 90 URL: cURL, 91 } 92 csMac := &macaroon.Macaroon{} 93 du := deployUploader{ 94 applicationID: "mysql", 95 chID: chID, 96 csMac: csMac, 97 client: deps, 98 resources: map[string]charmresource.Meta{ 99 "upload": { 100 Name: "upload", 101 Type: charmresource.TypeFile, 102 Path: "upload", 103 }, 104 "store": { 105 Name: "store", 106 Type: charmresource.TypeFile, 107 Path: "store", 108 }, 109 }, 110 osOpen: deps.Open, 111 osStat: deps.Stat, 112 } 113 114 files := map[string]string{ 115 "upload": "foobar.txt", 116 } 117 revisions := map[string]int{} 118 ids, err := du.upload(files, revisions) 119 c.Assert(err, jc.ErrorIsNil) 120 c.Check(ids, gc.DeepEquals, map[string]string{ 121 "upload": "id-upload", 122 "store": "id-store", 123 }) 124 125 s.stub.CheckCallNames(c, "Stat", "AddPendingResources", "Open", "UploadPendingResource") 126 expectedStore := []charmresource.Resource{ 127 { 128 Meta: du.resources["store"], 129 Origin: charmresource.OriginStore, 130 Revision: -1, 131 }, 132 } 133 s.stub.CheckCall(c, 1, "AddPendingResources", "mysql", chID, csMac, expectedStore) 134 s.stub.CheckCall(c, 2, "Open", "foobar.txt") 135 136 expectedUpload := charmresource.Resource{ 137 Meta: du.resources["upload"], 138 Origin: charmresource.OriginUpload, 139 } 140 s.stub.CheckCall(c, 3, "UploadPendingResource", "mysql", expectedUpload, "foobar.txt", "file contents") 141 } 142 143 func (s DeploySuite) TestUploadRevisionsOnly(c *gc.C) { 144 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 145 cURL := charm.MustParseURL("cs:~a-user/trusty/spam-5") 146 chID := charmstore.CharmID{ 147 URL: cURL, 148 } 149 csMac := &macaroon.Macaroon{} 150 du := deployUploader{ 151 applicationID: "mysql", 152 chID: chID, 153 csMac: csMac, 154 client: deps, 155 resources: map[string]charmresource.Meta{ 156 "upload": { 157 Name: "upload", 158 Type: charmresource.TypeFile, 159 Path: "upload", 160 }, 161 "store": { 162 Name: "store", 163 Type: charmresource.TypeFile, 164 Path: "store", 165 }, 166 }, 167 osOpen: deps.Open, 168 osStat: deps.Stat, 169 } 170 171 files := map[string]string{} 172 revisions := map[string]int{ 173 "store": 3, 174 } 175 ids, err := du.upload(files, revisions) 176 c.Assert(err, jc.ErrorIsNil) 177 c.Check(ids, gc.DeepEquals, map[string]string{ 178 "upload": "id-upload", 179 "store": "id-store", 180 }) 181 182 s.stub.CheckCallNames(c, "AddPendingResources") 183 expectedStore := []charmresource.Resource{{ 184 Meta: du.resources["store"], 185 Origin: charmresource.OriginStore, 186 Revision: 3, 187 }, { 188 Meta: du.resources["upload"], 189 Origin: charmresource.OriginStore, 190 Revision: -1, 191 }} 192 s.stub.CheckCall(c, 0, "AddPendingResources", "mysql", chID, csMac, expectedStore) 193 } 194 195 func (s DeploySuite) TestUploadFilesAndRevisions(c *gc.C) { 196 deps := uploadDeps{s.stub, rsc{bytes.NewBufferString("file contents")}} 197 cURL := charm.MustParseURL("cs:~a-user/trusty/spam-5") 198 chID := charmstore.CharmID{ 199 URL: cURL, 200 } 201 csMac := &macaroon.Macaroon{} 202 du := deployUploader{ 203 applicationID: "mysql", 204 chID: chID, 205 csMac: csMac, 206 client: deps, 207 resources: map[string]charmresource.Meta{ 208 "upload": { 209 Name: "upload", 210 Type: charmresource.TypeFile, 211 Path: "upload", 212 }, 213 "store": { 214 Name: "store", 215 Type: charmresource.TypeFile, 216 Path: "store", 217 }, 218 }, 219 osOpen: deps.Open, 220 osStat: deps.Stat, 221 } 222 223 files := map[string]string{ 224 "upload": "foobar.txt", 225 } 226 revisions := map[string]int{ 227 "store": 3, 228 } 229 ids, err := du.upload(files, revisions) 230 c.Assert(err, jc.ErrorIsNil) 231 c.Check(ids, gc.DeepEquals, map[string]string{ 232 "upload": "id-upload", 233 "store": "id-store", 234 }) 235 236 s.stub.CheckCallNames(c, "Stat", "AddPendingResources", "Open", "UploadPendingResource") 237 expectedStore := []charmresource.Resource{ 238 { 239 Meta: du.resources["store"], 240 Origin: charmresource.OriginStore, 241 Revision: 3, 242 }, 243 } 244 s.stub.CheckCall(c, 1, "AddPendingResources", "mysql", chID, csMac, expectedStore) 245 s.stub.CheckCall(c, 2, "Open", "foobar.txt") 246 247 expectedUpload := charmresource.Resource{ 248 Meta: du.resources["upload"], 249 Origin: charmresource.OriginUpload, 250 } 251 s.stub.CheckCall(c, 3, "UploadPendingResource", "mysql", expectedUpload, "foobar.txt", "file contents") 252 } 253 254 func (s DeploySuite) TestUploadUnexpectedResourceFile(c *gc.C) { 255 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 256 du := deployUploader{ 257 applicationID: "mysql", 258 client: deps, 259 resources: map[string]charmresource.Meta{ 260 "res1": { 261 Name: "res1", 262 Type: charmresource.TypeFile, 263 Path: "path", 264 }, 265 }, 266 osOpen: deps.Open, 267 osStat: deps.Stat, 268 } 269 270 files := map[string]string{"some bad resource": "foobar.txt"} 271 revisions := map[string]int{} 272 _, err := du.upload(files, revisions) 273 c.Check(err, gc.ErrorMatches, `unrecognized resource "some bad resource"`) 274 275 s.stub.CheckNoCalls(c) 276 } 277 278 func (s DeploySuite) TestUploadUnexpectedResourceRevision(c *gc.C) { 279 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 280 du := deployUploader{ 281 applicationID: "mysql", 282 client: deps, 283 resources: map[string]charmresource.Meta{ 284 "res1": { 285 Name: "res1", 286 Type: charmresource.TypeFile, 287 Path: "path", 288 }, 289 }, 290 osOpen: deps.Open, 291 osStat: deps.Stat, 292 } 293 294 files := map[string]string{} 295 revisions := map[string]int{"some bad resource": 2} 296 _, err := du.upload(files, revisions) 297 c.Check(err, gc.ErrorMatches, `unrecognized resource "some bad resource"`) 298 299 s.stub.CheckNoCalls(c) 300 } 301 302 func (s DeploySuite) TestMissingResource(c *gc.C) { 303 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 304 du := deployUploader{ 305 applicationID: "mysql", 306 client: deps, 307 resources: map[string]charmresource.Meta{ 308 "res1": { 309 Name: "res1", 310 Type: charmresource.TypeFile, 311 Path: "path", 312 }, 313 }, 314 osOpen: deps.Open, 315 osStat: deps.Stat, 316 } 317 318 // set the error that will be returned by os.Stat 319 s.stub.SetErrors(os.ErrNotExist) 320 321 files := map[string]string{"res1": "foobar.txt"} 322 revisions := map[string]int{} 323 _, err := du.upload(files, revisions) 324 c.Check(err, gc.ErrorMatches, `file for resource "res1".*`) 325 c.Check(errors.Cause(err), jc.Satisfies, os.IsNotExist) 326 } 327 328 func (s DeploySuite) TestDeployDockerResourceRegistryPathString(c *gc.C) { 329 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 330 cURL := charm.MustParseURL("cs:~a-user/mysql-k8s-5") 331 chID := charmstore.CharmID{ 332 URL: cURL, 333 } 334 csMac := &macaroon.Macaroon{} 335 resourceMeta := map[string]charmresource.Meta{ 336 "mysql_image": { 337 Name: "mysql_image", 338 Type: charmresource.TypeContainerImage, 339 }, 340 } 341 342 passedResourceValues := map[string]string{ 343 "mysql_image": "mariadb:10.3.8", 344 } 345 346 du := deployUploader{ 347 applicationID: "mysql", 348 chID: chID, 349 csMac: csMac, 350 client: deps, 351 resources: resourceMeta, 352 osOpen: deps.Open, 353 osStat: deps.Stat, 354 } 355 ids, err := du.upload(passedResourceValues, map[string]int{}) 356 c.Assert(err, jc.ErrorIsNil) 357 c.Check(ids, gc.DeepEquals, map[string]string{ 358 "mysql_image": "id-mysql_image", 359 }) 360 361 expectedUpload := charmresource.Resource{ 362 Meta: resourceMeta["mysql_image"], 363 Origin: charmresource.OriginUpload, 364 } 365 366 expectedUploadData := "{\"ImageName\":\"mariadb:10.3.8\",\"Username\":\"\"}" 367 s.stub.CheckCallNames(c, "UploadPendingResource") 368 s.stub.CheckCall(c, 0, "UploadPendingResource", "mysql", expectedUpload, "mariadb:10.3.8", expectedUploadData) 369 } 370 371 func (s DeploySuite) TestDeployDockerResourceJSONFile(c *gc.C) { 372 fileContents := ` 373 { 374 "ImageName": "registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image", 375 "Username": "docker-registry", 376 "Password": "hunter2" 377 } 378 ` 379 dir := c.MkDir() 380 jsonFile := path.Join(dir, "details.json") 381 err := ioutil.WriteFile(jsonFile, []byte(fileContents), 0600) 382 c.Assert(err, jc.ErrorIsNil) 383 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 384 cURL := charm.MustParseURL("cs:~a-user/mysql-k8s-5") 385 chID := charmstore.CharmID{ 386 URL: cURL, 387 } 388 csMac := &macaroon.Macaroon{} 389 resourceMeta := map[string]charmresource.Meta{ 390 "mysql_image": { 391 Name: "mysql_image", 392 Type: charmresource.TypeContainerImage, 393 }, 394 } 395 396 passedResourceValues := map[string]string{ 397 "mysql_image": jsonFile, 398 } 399 du := deployUploader{ 400 applicationID: "mysql", 401 chID: chID, 402 csMac: csMac, 403 client: deps, 404 resources: resourceMeta, 405 osOpen: deps.Open, 406 osStat: deps.Stat, 407 } 408 ids, err := du.upload(passedResourceValues, map[string]int{}) 409 c.Assert(err, jc.ErrorIsNil) 410 c.Check(ids, gc.DeepEquals, map[string]string{ 411 "mysql_image": "id-mysql_image", 412 }) 413 414 expectedUpload := charmresource.Resource{ 415 Meta: resourceMeta["mysql_image"], 416 Origin: charmresource.OriginUpload, 417 } 418 419 expectedUploadData := "{\"ImageName\":\"registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image\",\"Username\":\"docker-registry\",\"Password\":\"hunter2\"}" 420 s.stub.CheckCallNames(c, "UploadPendingResource") 421 s.stub.CheckCall(c, 0, "UploadPendingResource", "mysql", expectedUpload, jsonFile, expectedUploadData) 422 } 423 424 func (s DeploySuite) TestDeployDockerResourceYAMLFile(c *gc.C) { 425 fileContents := ` 426 registrypath: registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image 427 username: docker-registry 428 password: hunter2 429 ` 430 dir := c.MkDir() 431 jsonFile := path.Join(dir, "details.yaml") 432 err := ioutil.WriteFile(jsonFile, []byte(fileContents), 0600) 433 c.Assert(err, jc.ErrorIsNil) 434 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 435 cURL := charm.MustParseURL("cs:~a-user/mysql-k8s-5") 436 chID := charmstore.CharmID{ 437 URL: cURL, 438 } 439 csMac := &macaroon.Macaroon{} 440 resourceMeta := map[string]charmresource.Meta{ 441 "mysql_image": { 442 Name: "mysql_image", 443 Type: charmresource.TypeContainerImage, 444 }, 445 } 446 447 passedResourceValues := map[string]string{ 448 "mysql_image": jsonFile, 449 } 450 du := deployUploader{ 451 applicationID: "mysql", 452 chID: chID, 453 csMac: csMac, 454 client: deps, 455 resources: resourceMeta, 456 osOpen: deps.Open, 457 osStat: deps.Stat, 458 } 459 ids, err := du.upload(passedResourceValues, map[string]int{}) 460 c.Assert(err, jc.ErrorIsNil) 461 c.Check(ids, gc.DeepEquals, map[string]string{ 462 "mysql_image": "id-mysql_image", 463 }) 464 465 expectedUpload := charmresource.Resource{ 466 Meta: resourceMeta["mysql_image"], 467 Origin: charmresource.OriginUpload, 468 } 469 470 expectedUploadData := "{\"ImageName\":\"registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image\",\"Username\":\"docker-registry\",\"Password\":\"hunter2\"}" 471 s.stub.CheckCallNames(c, "UploadPendingResource") 472 s.stub.CheckCall(c, 0, "UploadPendingResource", "mysql", expectedUpload, jsonFile, expectedUploadData) 473 } 474 475 func (s DeploySuite) TestUnMarshallingDockerDetails(c *gc.C) { 476 content := ` 477 registrypath: registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image 478 username: docker-registry 479 password: hunter2 480 ` 481 data := bytes.NewBufferString(content) 482 dets, err := unMarshalDockerDetails(data) 483 c.Assert(err, jc.ErrorIsNil) 484 c.Assert(dets, gc.DeepEquals, resources.DockerImageDetails{ 485 RegistryPath: "registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image", 486 Username: "docker-registry", 487 Password: "hunter2", 488 }) 489 490 content = ` 491 { 492 "ImageName": "registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image", 493 "Username": "docker-registry", 494 "Password": "hunter2" 495 } 496 ` 497 data = bytes.NewBufferString(content) 498 dets, err = unMarshalDockerDetails(data) 499 c.Assert(err, jc.ErrorIsNil) 500 c.Assert(dets, gc.DeepEquals, resources.DockerImageDetails{ 501 RegistryPath: "registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image", 502 Username: "docker-registry", 503 Password: "hunter2", 504 }) 505 506 content = ` 507 path: registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image@sha256:516f74 508 username: docker-registry 509 password: hunter2 510 ` 511 data = bytes.NewBufferString(content) 512 dets, err = unMarshalDockerDetails(data) 513 c.Assert(err, gc.ErrorMatches, "docker image path \"\" not valid") 514 } 515 516 func (s DeploySuite) TestGetDockerDetailsData(c *gc.C) { 517 result, err := getDockerDetailsData("registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image") 518 c.Assert(err, jc.ErrorIsNil) 519 c.Assert(result, gc.DeepEquals, resources.DockerImageDetails{ 520 RegistryPath: "registry.staging.jujucharms.com/wallyworld/mysql-k8s/mysql_image", 521 Username: "", 522 Password: "", 523 }) 524 525 result, err = getDockerDetailsData("/path/doesnt/exist.yaml") 526 c.Assert(err, gc.ErrorMatches, "filepath or registry path: /path/doesnt/exist.yaml not valid") 527 528 result, err = getDockerDetailsData(".invalid-reg-path") 529 c.Assert(err, gc.ErrorMatches, "filepath or registry path: .invalid-reg-path not valid") 530 531 dir := c.MkDir() 532 yamlFile := path.Join(dir, "actually-yaml-file") 533 err = ioutil.WriteFile(yamlFile, []byte("registrypath: mariadb/mariadb:10.2"), 0600) 534 c.Assert(err, jc.ErrorIsNil) 535 result, err = getDockerDetailsData(yamlFile) 536 c.Assert(err, jc.ErrorIsNil) 537 c.Assert(result, gc.DeepEquals, resources.DockerImageDetails{ 538 RegistryPath: "mariadb/mariadb:10.2", 539 Username: "", 540 Password: "", 541 }) 542 } 543 544 type uploadDeps struct { 545 stub *testing.Stub 546 ReadSeekCloser ReadSeekCloser 547 } 548 549 func (s uploadDeps) AddPendingResources(applicationID string, charmID charmstore.CharmID, csMac *macaroon.Macaroon, resources []charmresource.Resource) (ids []string, err error) { 550 charmresource.Sort(resources) 551 s.stub.AddCall("AddPendingResources", applicationID, charmID, csMac, resources) 552 if err := s.stub.NextErr(); err != nil { 553 return nil, err 554 } 555 ids = make([]string, len(resources)) 556 for i, res := range resources { 557 ids[i] = "id-" + res.Name 558 } 559 return ids, nil 560 } 561 562 func (s uploadDeps) UploadPendingResource(applicationID string, resource charmresource.Resource, filename string, r io.ReadSeeker) (id string, err error) { 563 data := new(bytes.Buffer) 564 565 // we care the right data has been passed, not the right io.ReaderSeeker pointer. 566 _, err = data.ReadFrom(r) 567 if err != nil { 568 return "", err 569 } 570 s.stub.AddCall("UploadPendingResource", applicationID, resource, filename, data.String()) 571 if err := s.stub.NextErr(); err != nil { 572 return "", err 573 } 574 return "id-" + resource.Name, nil 575 } 576 577 func (s uploadDeps) Open(name string) (ReadSeekCloser, error) { 578 s.stub.AddCall("Open", name) 579 if err := s.stub.NextErr(); err != nil { 580 return nil, err 581 } 582 return s.ReadSeekCloser, nil 583 } 584 585 func (s uploadDeps) Stat(name string) error { 586 s.stub.AddCall("Stat", name) 587 return s.stub.NextErr() 588 } 589 590 type rsc struct { 591 *bytes.Buffer 592 } 593 594 func (rsc) Close() error { 595 return nil 596 } 597 func (rsc) Seek(offset int64, whence int) (int64, error) { 598 return 0, nil 599 }