gopkg.in/juju/charm.v6-unstable@v6.0.0-20171026192109-50d0c219b496/meta_test.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package charm_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "strings" 14 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/version" 17 gc "gopkg.in/check.v1" 18 "gopkg.in/yaml.v2" 19 yamlv2 "gopkg.in/yaml.v2" 20 21 "gopkg.in/juju/charm.v6-unstable" 22 "gopkg.in/juju/charm.v6-unstable/resource" 23 ) 24 25 func repoMeta(c *gc.C, name string) io.Reader { 26 charmDir := charmDirPath(c, name) 27 file, err := os.Open(filepath.Join(charmDir, "metadata.yaml")) 28 c.Assert(err, gc.IsNil) 29 defer file.Close() 30 data, err := ioutil.ReadAll(file) 31 c.Assert(err, gc.IsNil) 32 return bytes.NewReader(data) 33 } 34 35 type MetaSuite struct{} 36 37 var _ = gc.Suite(&MetaSuite{}) 38 39 func (s *MetaSuite) TestReadMetaVersion1(c *gc.C) { 40 meta, err := charm.ReadMeta(repoMeta(c, "dummy")) 41 c.Assert(err, gc.IsNil) 42 c.Assert(meta.Name, gc.Equals, "dummy") 43 c.Assert(meta.Summary, gc.Equals, "That's a dummy charm.") 44 c.Assert(meta.Description, gc.Equals, 45 "This is a longer description which\npotentially contains multiple lines.\n") 46 c.Assert(meta.Subordinate, gc.Equals, false) 47 } 48 49 func (s *MetaSuite) TestReadMetaVersion2(c *gc.C) { 50 // This checks that we can accept a charm with the 51 // obsolete "format" field, even though we ignore it. 52 meta, err := charm.ReadMeta(repoMeta(c, "format2")) 53 c.Assert(err, gc.IsNil) 54 c.Assert(meta.Name, gc.Equals, "format2") 55 c.Assert(meta.Categories, gc.HasLen, 0) 56 c.Assert(meta.Terms, gc.HasLen, 0) 57 } 58 59 func (s *MetaSuite) TestValidTermFormat(c *gc.C) { 60 valid := []string{ 61 "foobar", 62 "foobar/27", 63 "foo/003", 64 "owner/foobar/27", 65 "owner/foobar", 66 "owner/foo-bar", 67 "own-er/foobar", 68 "ibm/j9-jvm/2", 69 "cs:foobar/27", 70 "cs:foobar", 71 } 72 73 invalid := []string{ 74 "/", 75 "/1", 76 "//", 77 "//2", 78 "27", 79 "owner/foo/foobar", 80 "@les/term/1", 81 "own_er/foobar", 82 } 83 84 for i, s := range valid { 85 c.Logf("valid test %d: %s", i, s) 86 meta := charm.Meta{Terms: []string{s}} 87 err := meta.Check() 88 c.Check(err, jc.ErrorIsNil) 89 } 90 91 for i, s := range invalid { 92 c.Logf("invalid test %d: %s", i, s) 93 meta := charm.Meta{Terms: []string{s}} 94 err := meta.Check() 95 c.Check(err, gc.NotNil) 96 } 97 } 98 99 func (s *MetaSuite) TestTermStringRoundTrip(c *gc.C) { 100 terms := []string{ 101 "foobar", 102 "foobar/27", 103 "owner/foobar/27", 104 "owner/foobar", 105 "owner/foo-bar", 106 "own-er/foobar", 107 "ibm/j9-jvm/2", 108 "cs:foobar/27", 109 } 110 for i, term := range terms { 111 c.Logf("test %d: %s", i, term) 112 id, err := charm.ParseTerm(term) 113 c.Check(err, gc.IsNil) 114 c.Check(id.String(), gc.Equals, term) 115 } 116 } 117 118 func (s *MetaSuite) TestCheckTerms(c *gc.C) { 119 tests := []struct { 120 about string 121 terms []string 122 expectError string 123 }{{ 124 about: "valid terms", 125 terms: []string{"term/1", "term/2", "term-without-revision", "tt/2"}, 126 }, { 127 about: "revision not a number", 128 terms: []string{"term/1", "term/a"}, 129 expectError: `wrong term name format "a"`, 130 }, { 131 about: "negative revision", 132 terms: []string{"term/-1"}, 133 expectError: "negative term revision", 134 }, { 135 about: "wrong format", 136 terms: []string{"term/1", "foobar/term/abc/1"}, 137 expectError: `unknown term id format "foobar/term/abc/1"`, 138 }, { 139 about: "term with owner", 140 terms: []string{"term/1", "term/abc/1"}, 141 }, { 142 about: "term with owner no rev", 143 terms: []string{"term/1", "term/abc"}, 144 }, { 145 about: "term may not contain spaces", 146 terms: []string{"term/1", "term about a term"}, 147 expectError: `wrong term name format "term about a term"`, 148 }, { 149 about: "term name must start with lowercase letter", 150 terms: []string{"Term/1"}, 151 expectError: `wrong term name format "Term"`, 152 }, { 153 about: "term name cannot contain capital letters", 154 terms: []string{"owner/foO-Bar"}, 155 expectError: `wrong term name format "foO-Bar"`, 156 }, { 157 about: "term name cannot contain underscores, that's what dashes are for", 158 terms: []string{"owner/foo_bar"}, 159 expectError: `wrong term name format "foo_bar"`, 160 }, { 161 about: "term name can't end with a dash", 162 terms: []string{"o-/1"}, 163 expectError: `wrong term name format "o-"`, 164 }, { 165 about: "term name can't contain consecutive dashes", 166 terms: []string{"o-oo--ooo---o/1"}, 167 expectError: `wrong term name format "o-oo--ooo---o"`, 168 }, { 169 about: "term name more than a single char", 170 terms: []string{"z/1"}, 171 expectError: `wrong term name format "z"`, 172 }, { 173 about: "term name match the regexp", 174 terms: []string{"term_123-23aAf/1"}, 175 expectError: `wrong term name format "term_123-23aAf"`, 176 }, 177 } 178 for i, test := range tests { 179 c.Logf("running test %v: %v", i, test.about) 180 meta := charm.Meta{Terms: test.terms} 181 err := meta.Check() 182 if test.expectError == "" { 183 c.Check(err, jc.ErrorIsNil) 184 } else { 185 c.Check(err, gc.ErrorMatches, test.expectError) 186 } 187 } 188 } 189 190 func (s *MetaSuite) TestParseTerms(c *gc.C) { 191 tests := []struct { 192 about string 193 term string 194 expectError string 195 expectTerm charm.TermsId 196 }{{ 197 about: "valid term", 198 term: "term/1", 199 expectTerm: charm.TermsId{"", "", "term", 1}, 200 }, { 201 about: "valid term no revision", 202 term: "term", 203 expectTerm: charm.TermsId{"", "", "term", 0}, 204 }, { 205 about: "revision not a number", 206 term: "term/a", 207 expectError: `wrong term name format "a"`, 208 }, { 209 about: "negative revision", 210 term: "term/-1", 211 expectError: "negative term revision", 212 }, { 213 about: "bad revision", 214 term: "owner/term/12a", 215 expectError: `invalid revision number "12a" strconv.Atoi: parsing "12a": invalid syntax`, 216 }, { 217 about: "wrong format", 218 term: "foobar/term/abc/1", 219 expectError: `unknown term id format "foobar/term/abc/1"`, 220 }, { 221 about: "term with owner", 222 term: "term/abc/1", 223 expectTerm: charm.TermsId{"", "term", "abc", 1}, 224 }, { 225 about: "term with owner no rev", 226 term: "term/abc", 227 expectTerm: charm.TermsId{"", "term", "abc", 0}, 228 }, { 229 about: "term may not contain spaces", 230 term: "term about a term", 231 expectError: `wrong term name format "term about a term"`, 232 }, { 233 about: "term name must not start with a number", 234 term: "1Term/1", 235 expectError: `wrong term name format "1Term"`, 236 }, { 237 about: "full term with tenant", 238 term: "tenant:owner/term/1", 239 expectTerm: charm.TermsId{"tenant", "owner", "term", 1}, 240 }, { 241 about: "bad tenant", 242 term: "tenant::owner/term/1", 243 expectError: `wrong owner format ":owner"`, 244 }, { 245 about: "ownerless term with tenant", 246 term: "tenant:term/1", 247 expectTerm: charm.TermsId{"tenant", "", "term", 1}, 248 }, { 249 about: "ownerless revisionless term with tenant", 250 term: "tenant:term", 251 expectTerm: charm.TermsId{"tenant", "", "term", 0}, 252 }, { 253 about: "owner/term with tenant", 254 term: "tenant:owner/term", 255 expectTerm: charm.TermsId{"tenant", "owner", "term", 0}, 256 }, { 257 about: "term with tenant", 258 term: "tenant:term", 259 expectTerm: charm.TermsId{"tenant", "", "term", 0}, 260 }} 261 for i, test := range tests { 262 c.Logf("running test %v: %v", i, test.about) 263 term, err := charm.ParseTerm(test.term) 264 if test.expectError == "" { 265 c.Check(err, jc.ErrorIsNil) 266 c.Check(term, gc.DeepEquals, &test.expectTerm) 267 } else { 268 c.Check(err, gc.ErrorMatches, test.expectError) 269 c.Check(term, gc.IsNil) 270 } 271 } 272 } 273 274 func (s *MetaSuite) TestReadCategory(c *gc.C) { 275 meta, err := charm.ReadMeta(repoMeta(c, "category")) 276 c.Assert(err, gc.IsNil) 277 c.Assert(meta.Categories, jc.DeepEquals, []string{"database"}) 278 } 279 280 func (s *MetaSuite) TestReadTerms(c *gc.C) { 281 meta, err := charm.ReadMeta(repoMeta(c, "terms")) 282 c.Assert(err, jc.ErrorIsNil) 283 err = meta.Check() 284 c.Assert(err, jc.ErrorIsNil) 285 c.Assert(meta.Terms, jc.DeepEquals, []string{"term1/1", "term2", "owner/term3/1"}) 286 } 287 288 var metaDataWithInvalidTermsId = ` 289 name: terms 290 summary: "Sample charm with terms and conditions" 291 description: | 292 That's a boring charm that requires certain terms. 293 terms: ["!!!/abc"] 294 ` 295 296 func (s *MetaSuite) TestReadInvalidTerms(c *gc.C) { 297 reader := strings.NewReader(metaDataWithInvalidTermsId) 298 _, err := charm.ReadMeta(reader) 299 c.Assert(err, gc.ErrorMatches, `wrong owner format "!!!"`) 300 } 301 302 func (s *MetaSuite) TestReadTags(c *gc.C) { 303 meta, err := charm.ReadMeta(repoMeta(c, "category")) 304 c.Assert(err, gc.IsNil) 305 c.Assert(meta.Tags, jc.DeepEquals, []string{"openstack", "storage"}) 306 } 307 308 func (s *MetaSuite) TestSubordinate(c *gc.C) { 309 meta, err := charm.ReadMeta(repoMeta(c, "logging")) 310 c.Assert(err, gc.IsNil) 311 c.Assert(meta.Subordinate, gc.Equals, true) 312 } 313 314 func (s *MetaSuite) TestSubordinateWithoutContainerRelation(c *gc.C) { 315 r := repoMeta(c, "dummy") 316 hackYaml := ReadYaml(r) 317 hackYaml["subordinate"] = true 318 _, err := charm.ReadMeta(hackYaml.Reader()) 319 c.Assert(err, gc.ErrorMatches, "subordinate charm \"dummy\" lacks \"requires\" relation with container scope") 320 } 321 322 func (s *MetaSuite) TestScopeConstraint(c *gc.C) { 323 meta, err := charm.ReadMeta(repoMeta(c, "logging")) 324 c.Assert(err, gc.IsNil) 325 c.Assert(meta.Provides["logging-client"].Scope, gc.Equals, charm.ScopeGlobal) 326 c.Assert(meta.Requires["logging-directory"].Scope, gc.Equals, charm.ScopeContainer) 327 c.Assert(meta.Subordinate, gc.Equals, true) 328 } 329 330 func (s *MetaSuite) TestParseMetaRelations(c *gc.C) { 331 meta, err := charm.ReadMeta(repoMeta(c, "mysql")) 332 c.Assert(err, gc.IsNil) 333 c.Assert(meta.Provides["server"], gc.Equals, charm.Relation{ 334 Name: "server", 335 Role: charm.RoleProvider, 336 Interface: "mysql", 337 Scope: charm.ScopeGlobal, 338 }) 339 c.Assert(meta.Requires, gc.IsNil) 340 c.Assert(meta.Peers, gc.IsNil) 341 342 meta, err = charm.ReadMeta(repoMeta(c, "riak")) 343 c.Assert(err, gc.IsNil) 344 c.Assert(meta.Provides["endpoint"], gc.Equals, charm.Relation{ 345 Name: "endpoint", 346 Role: charm.RoleProvider, 347 Interface: "http", 348 Scope: charm.ScopeGlobal, 349 }) 350 c.Assert(meta.Provides["admin"], gc.Equals, charm.Relation{ 351 Name: "admin", 352 Role: charm.RoleProvider, 353 Interface: "http", 354 Scope: charm.ScopeGlobal, 355 }) 356 c.Assert(meta.Peers["ring"], gc.Equals, charm.Relation{ 357 Name: "ring", 358 Role: charm.RolePeer, 359 Interface: "riak", 360 Limit: 1, 361 Scope: charm.ScopeGlobal, 362 }) 363 c.Assert(meta.Requires, gc.IsNil) 364 365 meta, err = charm.ReadMeta(repoMeta(c, "terracotta")) 366 c.Assert(err, gc.IsNil) 367 c.Assert(meta.Provides["dso"], gc.Equals, charm.Relation{ 368 Name: "dso", 369 Role: charm.RoleProvider, 370 Interface: "terracotta", 371 Optional: true, 372 Scope: charm.ScopeGlobal, 373 }) 374 c.Assert(meta.Peers["server-array"], gc.Equals, charm.Relation{ 375 Name: "server-array", 376 Role: charm.RolePeer, 377 Interface: "terracotta-server", 378 Limit: 1, 379 Scope: charm.ScopeGlobal, 380 }) 381 c.Assert(meta.Requires, gc.IsNil) 382 383 meta, err = charm.ReadMeta(repoMeta(c, "wordpress")) 384 c.Assert(err, gc.IsNil) 385 c.Assert(meta.Provides["url"], gc.Equals, charm.Relation{ 386 Name: "url", 387 Role: charm.RoleProvider, 388 Interface: "http", 389 Scope: charm.ScopeGlobal, 390 }) 391 c.Assert(meta.Requires["db"], gc.Equals, charm.Relation{ 392 Name: "db", 393 Role: charm.RoleRequirer, 394 Interface: "mysql", 395 Limit: 1, 396 Scope: charm.ScopeGlobal, 397 }) 398 c.Assert(meta.Requires["cache"], gc.Equals, charm.Relation{ 399 Name: "cache", 400 Role: charm.RoleRequirer, 401 Interface: "varnish", 402 Limit: 2, 403 Optional: true, 404 Scope: charm.ScopeGlobal, 405 }) 406 c.Assert(meta.Peers, gc.IsNil) 407 } 408 409 func (s *MetaSuite) TestCombinedRelations(c *gc.C) { 410 meta, err := charm.ReadMeta(repoMeta(c, "riak")) 411 c.Assert(err, gc.IsNil) 412 combinedRelations := meta.CombinedRelations() 413 expectedLength := len(meta.Provides) + len(meta.Requires) + len(meta.Peers) 414 c.Assert(combinedRelations, gc.HasLen, expectedLength) 415 c.Assert(combinedRelations, jc.DeepEquals, map[string]charm.Relation{ 416 "endpoint": { 417 Name: "endpoint", 418 Role: charm.RoleProvider, 419 Interface: "http", 420 Scope: charm.ScopeGlobal, 421 }, 422 "admin": { 423 Name: "admin", 424 Role: charm.RoleProvider, 425 Interface: "http", 426 Scope: charm.ScopeGlobal, 427 }, 428 "ring": { 429 Name: "ring", 430 Role: charm.RolePeer, 431 Interface: "riak", 432 Limit: 1, 433 Scope: charm.ScopeGlobal, 434 }, 435 }) 436 } 437 438 var relationsConstraintsTests = []struct { 439 rels string 440 err string 441 }{ 442 { 443 "provides:\n foo: ping\nrequires:\n foo: pong", 444 `charm "a" using a duplicated relation name: "foo"`, 445 }, { 446 "requires:\n foo: ping\npeers:\n foo: pong", 447 `charm "a" using a duplicated relation name: "foo"`, 448 }, { 449 "peers:\n foo: ping\nprovides:\n foo: pong", 450 `charm "a" using a duplicated relation name: "foo"`, 451 }, { 452 "provides:\n juju: blob", 453 `charm "a" using a reserved relation name: "juju"`, 454 }, { 455 "requires:\n juju: blob", 456 `charm "a" using a reserved relation name: "juju"`, 457 }, { 458 "peers:\n juju: blob", 459 `charm "a" using a reserved relation name: "juju"`, 460 }, { 461 "provides:\n juju-snap: blub", 462 `charm "a" using a reserved relation name: "juju-snap"`, 463 }, { 464 "requires:\n juju-crackle: blub", 465 `charm "a" using a reserved relation name: "juju-crackle"`, 466 }, { 467 "peers:\n juju-pop: blub", 468 `charm "a" using a reserved relation name: "juju-pop"`, 469 }, { 470 "provides:\n innocuous: juju", 471 `charm "a" relation "innocuous" using a reserved interface: "juju"`, 472 }, { 473 "peers:\n innocuous: juju", 474 `charm "a" relation "innocuous" using a reserved interface: "juju"`, 475 }, { 476 "provides:\n innocuous: juju-snap", 477 `charm "a" relation "innocuous" using a reserved interface: "juju-snap"`, 478 }, { 479 "peers:\n innocuous: juju-snap", 480 `charm "a" relation "innocuous" using a reserved interface: "juju-snap"`, 481 }, 482 } 483 484 func (s *MetaSuite) TestRelationsConstraints(c *gc.C) { 485 check := func(s, e string) { 486 meta, err := charm.ReadMeta(strings.NewReader(s)) 487 if e != "" { 488 c.Assert(err, gc.ErrorMatches, e) 489 c.Assert(meta, gc.IsNil) 490 } else { 491 c.Assert(err, gc.IsNil) 492 c.Assert(meta, gc.NotNil) 493 } 494 } 495 prefix := "name: a\nsummary: b\ndescription: c\n" 496 for i, t := range relationsConstraintsTests { 497 c.Logf("test %d", i) 498 check(prefix+t.rels, t.err) 499 check(prefix+"subordinate: true\n"+t.rels, t.err) 500 } 501 // The juju-* namespace is accessible to container-scoped require 502 // relations on subordinate charms. 503 check(prefix+` 504 subordinate: true 505 requires: 506 juju-info: 507 interface: juju-info 508 scope: container`, "") 509 // The juju-* interfaces are allowed on any require relation. 510 check(prefix+` 511 requires: 512 innocuous: juju-info`, "") 513 } 514 515 // dummyMetadata contains a minimally valid charm metadata.yaml 516 // for testing valid and invalid series. 517 const dummyMetadata = "name: a\nsummary: b\ndescription: c" 518 519 func (s *MetaSuite) TestSeries(c *gc.C) { 520 // series not specified 521 meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata)) 522 c.Assert(err, gc.IsNil) 523 c.Check(meta.Series, gc.HasLen, 0) 524 charmMeta := fmt.Sprintf("%s\nseries:", dummyMetadata) 525 for _, seriesName := range []string{"precise", "trusty", "plan9"} { 526 charmMeta = fmt.Sprintf("%s\n - %s", charmMeta, seriesName) 527 } 528 meta, err = charm.ReadMeta(strings.NewReader(charmMeta)) 529 c.Assert(err, gc.IsNil) 530 c.Assert(meta.Series, gc.DeepEquals, []string{"precise", "trusty", "plan9"}) 531 } 532 533 func (s *MetaSuite) TestInvalidSeries(c *gc.C) { 534 for _, seriesName := range []string{"pre-c1se", "pre^cise", "cp/m", "OpenVMS"} { 535 _, err := charm.ReadMeta(strings.NewReader( 536 fmt.Sprintf("%s\nseries:\n - %s\n", dummyMetadata, seriesName))) 537 c.Assert(err, gc.NotNil) 538 c.Check(err, gc.ErrorMatches, `charm "a" declares invalid series: .*`) 539 } 540 } 541 542 func (s *MetaSuite) TestMinJujuVersion(c *gc.C) { 543 // series not specified 544 meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata)) 545 c.Assert(err, gc.IsNil) 546 c.Check(meta.Series, gc.HasLen, 0) 547 charmMeta := fmt.Sprintf("%s\nmin-juju-version: ", dummyMetadata) 548 vals := []version.Number{ 549 {Major: 1, Minor: 25}, 550 {Major: 1, Minor: 25, Tag: "alpha"}, 551 {Major: 1, Minor: 25, Patch: 1}, 552 } 553 for _, ver := range vals { 554 val := charmMeta + ver.String() 555 meta, err = charm.ReadMeta(strings.NewReader(val)) 556 c.Assert(err, gc.IsNil) 557 c.Assert(meta.MinJujuVersion, gc.Equals, ver) 558 } 559 } 560 561 func (s *MetaSuite) TestInvalidMinJujuVersion(c *gc.C) { 562 _, err := charm.ReadMeta(strings.NewReader(dummyMetadata + "\nmin-juju-version: invalid-version")) 563 564 c.Check(err, gc.ErrorMatches, `invalid min-juju-version: invalid version "invalid-version"`) 565 } 566 567 func (s *MetaSuite) TestNoMinJujuVersion(c *gc.C) { 568 meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata)) 569 c.Assert(err, jc.ErrorIsNil) 570 c.Check(meta.MinJujuVersion, gc.Equals, version.Zero) 571 } 572 573 func (s *MetaSuite) TestCheckMismatchedRelationName(c *gc.C) { 574 // This Check case cannot be covered by the above 575 // TestRelationsConstraints tests. 576 meta := charm.Meta{ 577 Name: "foo", 578 Provides: map[string]charm.Relation{ 579 "foo": { 580 Name: "foo", 581 Role: charm.RolePeer, 582 Interface: "x", 583 Limit: 1, 584 Scope: charm.ScopeGlobal, 585 }, 586 }, 587 } 588 err := meta.Check() 589 c.Assert(err, gc.ErrorMatches, `charm "foo" has mismatched role "peer"; expected "provider"`) 590 } 591 592 func (s *MetaSuite) TestCheckMismatchedRole(c *gc.C) { 593 // This Check case cannot be covered by the above 594 // TestRelationsConstraints tests. 595 meta := charm.Meta{ 596 Name: "foo", 597 Provides: map[string]charm.Relation{ 598 "foo": { 599 Role: charm.RolePeer, 600 Interface: "foo", 601 Limit: 1, 602 Scope: charm.ScopeGlobal, 603 }, 604 }, 605 } 606 err := meta.Check() 607 c.Assert(err, gc.ErrorMatches, `charm "foo" has mismatched relation name ""; expected "foo"`) 608 } 609 610 func (s *MetaSuite) TestCheckMismatchedExtraBindingName(c *gc.C) { 611 meta := charm.Meta{ 612 Name: "foo", 613 ExtraBindings: map[string]charm.ExtraBinding{ 614 "foo": {Name: "bar"}, 615 }, 616 } 617 err := meta.Check() 618 c.Assert(err, gc.ErrorMatches, `charm "foo" has invalid extra bindings: mismatched extra binding name: got "bar", expected "foo"`) 619 } 620 621 func (s *MetaSuite) TestCheckEmptyNameKeyOrEmptyExtraBindingName(c *gc.C) { 622 meta := charm.Meta{ 623 Name: "foo", 624 ExtraBindings: map[string]charm.ExtraBinding{"": {Name: "bar"}}, 625 } 626 err := meta.Check() 627 expectedError := `charm "foo" has invalid extra bindings: missing binding name` 628 c.Assert(err, gc.ErrorMatches, expectedError) 629 630 meta.ExtraBindings = map[string]charm.ExtraBinding{"bar": {Name: ""}} 631 err = meta.Check() 632 c.Assert(err, gc.ErrorMatches, expectedError) 633 } 634 635 // Test rewriting of a given interface specification into long form. 636 // 637 // InterfaceExpander uses `coerce` to do one of two things: 638 // 639 // - Rewrite shorthand to the long form used for actual storage 640 // - Fills in defaults, including a configurable `limit` 641 // 642 // This test ensures test coverage on each of these branches, along 643 // with ensuring the conversion object properly raises SchemaError 644 // exceptions on invalid data. 645 func (s *MetaSuite) TestIfaceExpander(c *gc.C) { 646 e := charm.IfaceExpander(nil) 647 648 path := []string{"<pa", "th>"} 649 650 // Shorthand is properly rewritten 651 v, err := e.Coerce("http", path) 652 c.Assert(err, gc.IsNil) 653 c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": false, "scope": string(charm.ScopeGlobal)}) 654 655 // Defaults are properly applied 656 v, err = e.Coerce(map[string]interface{}{"interface": "http"}, path) 657 c.Assert(err, gc.IsNil) 658 c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": false, "scope": string(charm.ScopeGlobal)}) 659 660 v, err = e.Coerce(map[string]interface{}{"interface": "http", "limit": 2}, path) 661 c.Assert(err, gc.IsNil) 662 c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": int64(2), "optional": false, "scope": string(charm.ScopeGlobal)}) 663 664 v, err = e.Coerce(map[string]interface{}{"interface": "http", "optional": true}, path) 665 c.Assert(err, gc.IsNil) 666 c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": true, "scope": string(charm.ScopeGlobal)}) 667 668 // Invalid data raises an error. 669 v, err = e.Coerce(42, path) 670 c.Assert(err, gc.ErrorMatches, `<path>: expected map, got int\(42\)`) 671 672 v, err = e.Coerce(map[string]interface{}{"interface": "http", "optional": nil}, path) 673 c.Assert(err, gc.ErrorMatches, "<path>.optional: expected bool, got nothing") 674 675 v, err = e.Coerce(map[string]interface{}{"interface": "http", "limit": "none, really"}, path) 676 c.Assert(err, gc.ErrorMatches, "<path>.limit: unexpected value.*") 677 678 // Can change default limit 679 e = charm.IfaceExpander(1) 680 v, err = e.Coerce(map[string]interface{}{"interface": "http"}, path) 681 c.Assert(err, gc.IsNil) 682 c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": int64(1), "optional": false, "scope": string(charm.ScopeGlobal)}) 683 } 684 685 func (s *MetaSuite) TestMetaHooks(c *gc.C) { 686 meta, err := charm.ReadMeta(repoMeta(c, "wordpress")) 687 c.Assert(err, gc.IsNil) 688 hooks := meta.Hooks() 689 expectedHooks := map[string]bool{ 690 "install": true, 691 "start": true, 692 "config-changed": true, 693 "upgrade-charm": true, 694 "stop": true, 695 "collect-metrics": true, 696 "meter-status-changed": true, 697 "leader-elected": true, 698 "leader-deposed": true, 699 "leader-settings-changed": true, 700 "update-status": true, 701 "cache-relation-joined": true, 702 "cache-relation-changed": true, 703 "cache-relation-departed": true, 704 "cache-relation-broken": true, 705 "db-relation-joined": true, 706 "db-relation-changed": true, 707 "db-relation-departed": true, 708 "db-relation-broken": true, 709 "logging-dir-relation-joined": true, 710 "logging-dir-relation-changed": true, 711 "logging-dir-relation-departed": true, 712 "logging-dir-relation-broken": true, 713 "monitoring-port-relation-joined": true, 714 "monitoring-port-relation-changed": true, 715 "monitoring-port-relation-departed": true, 716 "monitoring-port-relation-broken": true, 717 "url-relation-joined": true, 718 "url-relation-changed": true, 719 "url-relation-departed": true, 720 "url-relation-broken": true, 721 } 722 c.Assert(hooks, jc.DeepEquals, expectedHooks) 723 } 724 725 func (s *MetaSuite) TestCodecRoundTripEmpty(c *gc.C) { 726 for i, codec := range codecs { 727 c.Logf("codec %d", i) 728 empty_input := charm.Meta{} 729 data, err := codec.Marshal(empty_input) 730 c.Assert(err, gc.IsNil) 731 var empty_output charm.Meta 732 err = codec.Unmarshal(data, &empty_output) 733 c.Assert(err, gc.IsNil) 734 c.Assert(empty_input, jc.DeepEquals, empty_output) 735 } 736 } 737 738 func (s *MetaSuite) TestCodecRoundTrip(c *gc.C) { 739 var input = charm.Meta{ 740 Name: "Foo", 741 Summary: "Bar", 742 Description: "Baz", 743 Subordinate: true, 744 Provides: map[string]charm.Relation{ 745 "qux": { 746 Name: "qux", 747 Role: charm.RoleProvider, 748 Interface: "quxx", 749 Optional: true, 750 Limit: 42, 751 Scope: charm.ScopeGlobal, 752 }, 753 }, 754 Requires: map[string]charm.Relation{ 755 "frob": { 756 Name: "frob", 757 Role: charm.RoleRequirer, 758 Interface: "quxx", 759 Optional: true, 760 Limit: 42, 761 Scope: charm.ScopeContainer, 762 }, 763 }, 764 Peers: map[string]charm.Relation{ 765 "arble": { 766 Name: "arble", 767 Role: charm.RolePeer, 768 Interface: "quxx", 769 Optional: true, 770 Limit: 42, 771 Scope: charm.ScopeGlobal, 772 }, 773 }, 774 ExtraBindings: map[string]charm.ExtraBinding{ 775 "b1": {Name: "b1"}, 776 "b2": {Name: "b2"}, 777 }, 778 Categories: []string{"quxxxx", "quxxxxx"}, 779 Tags: []string{"openstack", "storage"}, 780 Terms: []string{"test-term/1", "test-term/2"}, 781 } 782 for i, codec := range codecs { 783 c.Logf("codec %d", i) 784 data, err := codec.Marshal(input) 785 c.Assert(err, gc.IsNil) 786 var output charm.Meta 787 err = codec.Unmarshal(data, &output) 788 c.Assert(err, gc.IsNil) 789 c.Assert(output, jc.DeepEquals, input, gc.Commentf("data: %q", data)) 790 } 791 } 792 793 var implementedByTests = []struct { 794 ifce string 795 name string 796 role charm.RelationRole 797 scope charm.RelationScope 798 match bool 799 implicit bool 800 }{ 801 {"ifce-pro", "pro", charm.RoleProvider, charm.ScopeGlobal, true, false}, 802 {"blah", "pro", charm.RoleProvider, charm.ScopeGlobal, false, false}, 803 {"ifce-pro", "blah", charm.RoleProvider, charm.ScopeGlobal, false, false}, 804 {"ifce-pro", "pro", charm.RoleRequirer, charm.ScopeGlobal, false, false}, 805 {"ifce-pro", "pro", charm.RoleProvider, charm.ScopeContainer, true, false}, 806 807 {"juju-info", "juju-info", charm.RoleProvider, charm.ScopeGlobal, true, true}, 808 {"blah", "juju-info", charm.RoleProvider, charm.ScopeGlobal, false, false}, 809 {"juju-info", "blah", charm.RoleProvider, charm.ScopeGlobal, false, false}, 810 {"juju-info", "juju-info", charm.RoleRequirer, charm.ScopeGlobal, false, false}, 811 {"juju-info", "juju-info", charm.RoleProvider, charm.ScopeContainer, true, true}, 812 813 {"ifce-req", "req", charm.RoleRequirer, charm.ScopeGlobal, true, false}, 814 {"blah", "req", charm.RoleRequirer, charm.ScopeGlobal, false, false}, 815 {"ifce-req", "blah", charm.RoleRequirer, charm.ScopeGlobal, false, false}, 816 {"ifce-req", "req", charm.RolePeer, charm.ScopeGlobal, false, false}, 817 {"ifce-req", "req", charm.RoleRequirer, charm.ScopeContainer, true, false}, 818 819 {"juju-info", "info", charm.RoleRequirer, charm.ScopeContainer, true, false}, 820 {"blah", "info", charm.RoleRequirer, charm.ScopeContainer, false, false}, 821 {"juju-info", "blah", charm.RoleRequirer, charm.ScopeContainer, false, false}, 822 {"juju-info", "info", charm.RolePeer, charm.ScopeContainer, false, false}, 823 {"juju-info", "info", charm.RoleRequirer, charm.ScopeGlobal, false, false}, 824 825 {"ifce-peer", "peer", charm.RolePeer, charm.ScopeGlobal, true, false}, 826 {"blah", "peer", charm.RolePeer, charm.ScopeGlobal, false, false}, 827 {"ifce-peer", "blah", charm.RolePeer, charm.ScopeGlobal, false, false}, 828 {"ifce-peer", "peer", charm.RoleProvider, charm.ScopeGlobal, false, false}, 829 {"ifce-peer", "peer", charm.RolePeer, charm.ScopeContainer, true, false}, 830 } 831 832 func (s *MetaSuite) TestImplementedBy(c *gc.C) { 833 for i, t := range implementedByTests { 834 c.Logf("test %d", i) 835 r := charm.Relation{ 836 Interface: t.ifce, 837 Name: t.name, 838 Role: t.role, 839 Scope: t.scope, 840 } 841 c.Assert(r.ImplementedBy(&dummyCharm{}), gc.Equals, t.match) 842 c.Assert(r.IsImplicit(), gc.Equals, t.implicit) 843 } 844 } 845 846 var metaYAMLMarshalTests = []struct { 847 about string 848 yaml string 849 }{{ 850 about: "minimal charm", 851 yaml: ` 852 name: minimal 853 description: d 854 summary: s 855 `, 856 }, { 857 about: "charm with lots of stuff", 858 yaml: ` 859 name: big 860 description: d 861 summary: s 862 subordinate: true 863 provides: 864 provideSimple: someinterface 865 provideLessSimple: 866 interface: anotherinterface 867 optional: true 868 scope: container 869 limit: 3 870 requires: 871 requireSimple: someinterface 872 requireLessSimple: 873 interface: anotherinterface 874 optional: true 875 scope: container 876 limit: 3 877 peers: 878 peerSimple: someinterface 879 peerLessSimple: 880 interface: peery 881 optional: true 882 extra-bindings: 883 extraBar: 884 extraFoo1: 885 categories: [c1, c1] 886 tags: [t1, t2] 887 series: 888 - someseries 889 resources: 890 foo: 891 description: 'a description' 892 filename: 'x.zip' 893 bar: 894 filename: 'y.tgz' 895 type: file 896 `, 897 }} 898 899 func (s *MetaSuite) TestYAMLMarshal(c *gc.C) { 900 for i, test := range metaYAMLMarshalTests { 901 c.Logf("test %d: %s", i, test.about) 902 ch, err := charm.ReadMeta(strings.NewReader(test.yaml)) 903 c.Assert(err, gc.IsNil) 904 gotYAML, err := yaml.Marshal(ch) 905 c.Assert(err, gc.IsNil) 906 gotCh, err := charm.ReadMeta(bytes.NewReader(gotYAML)) 907 c.Assert(err, gc.IsNil) 908 c.Assert(gotCh, jc.DeepEquals, ch) 909 } 910 } 911 912 func (s *MetaSuite) TestYAMLMarshalV2(c *gc.C) { 913 for i, test := range metaYAMLMarshalTests { 914 c.Logf("test %d: %s", i, test.about) 915 ch, err := charm.ReadMeta(strings.NewReader(test.yaml)) 916 c.Assert(err, gc.IsNil) 917 gotYAML, err := yamlv2.Marshal(ch) 918 c.Assert(err, gc.IsNil) 919 gotCh, err := charm.ReadMeta(bytes.NewReader(gotYAML)) 920 c.Assert(err, gc.IsNil) 921 c.Assert(gotCh, jc.DeepEquals, ch) 922 } 923 } 924 925 func (s *MetaSuite) TestYAMLMarshalSimpleRelationOrExtraBinding(c *gc.C) { 926 // Check that a simple relation / extra-binding gets marshaled as a string. 927 chYAML := ` 928 name: minimal 929 description: d 930 summary: s 931 provides: 932 server: http 933 requires: 934 client: http 935 peers: 936 me: http 937 extra-bindings: 938 foo: 939 ` 940 ch, err := charm.ReadMeta(strings.NewReader(chYAML)) 941 c.Assert(err, gc.IsNil) 942 gotYAML, err := yaml.Marshal(ch) 943 c.Assert(err, gc.IsNil) 944 945 var x interface{} 946 err = yaml.Unmarshal(gotYAML, &x) 947 c.Assert(err, gc.IsNil) 948 c.Assert(x, jc.DeepEquals, map[interface{}]interface{}{ 949 "name": "minimal", 950 "description": "d", 951 "summary": "s", 952 "provides": map[interface{}]interface{}{ 953 "server": "http", 954 }, 955 "requires": map[interface{}]interface{}{ 956 "client": "http", 957 }, 958 "peers": map[interface{}]interface{}{ 959 "me": "http", 960 }, 961 "extra-bindings": map[interface{}]interface{}{ 962 "foo": nil, 963 }, 964 }) 965 } 966 967 func (s *MetaSuite) TestStorage(c *gc.C) { 968 // "type" is the only required attribute for storage. 969 meta, err := charm.ReadMeta(strings.NewReader(` 970 name: a 971 summary: b 972 description: c 973 storage: 974 store0: 975 description: woo tee bix 976 type: block 977 store1: 978 type: filesystem 979 `)) 980 c.Assert(err, gc.IsNil) 981 c.Assert(meta.Storage, gc.DeepEquals, map[string]charm.Storage{ 982 "store0": { 983 Name: "store0", 984 Description: "woo tee bix", 985 Type: charm.StorageBlock, 986 CountMin: 1, // singleton 987 CountMax: 1, 988 }, 989 "store1": { 990 Name: "store1", 991 Type: charm.StorageFilesystem, 992 CountMin: 1, // singleton 993 CountMax: 1, 994 }, 995 }) 996 } 997 998 func (s *MetaSuite) TestStorageErrors(c *gc.C) { 999 prefix := ` 1000 name: a 1001 summary: b 1002 description: c 1003 storage: 1004 store-bad: 1005 `[1:] 1006 1007 type test struct { 1008 desc string 1009 yaml string 1010 err string 1011 } 1012 1013 tests := []test{{ 1014 desc: "type is required", 1015 yaml: " required: false", 1016 err: "metadata: storage.store-bad.type: unexpected value <nil>", 1017 }, { 1018 desc: "range must be an integer, or integer range (1)", 1019 yaml: " type: filesystem\n multiple:\n range: woat", 1020 err: `metadata: storage.store-bad.multiple.range: value "woat" does not match 'm', 'm-n', or 'm\+'`, 1021 }, { 1022 desc: "range must be an integer, or integer range (2)", 1023 yaml: " type: filesystem\n multiple:\n range: 0-abc", 1024 err: `metadata: storage.store-bad.multiple.range: value "0-abc" does not match 'm', 'm-n', or 'm\+'`, 1025 }, { 1026 desc: "range must be non-negative", 1027 yaml: " type: filesystem\n multiple:\n range: -1", 1028 err: `metadata: storage.store-bad.multiple.range: invalid count -1`, 1029 }, { 1030 desc: "range must be positive", 1031 yaml: " type: filesystem\n multiple:\n range: 0", 1032 err: `metadata: storage.store-bad.multiple.range: invalid count 0`, 1033 }, { 1034 desc: "location cannot be specified for block type storage", 1035 yaml: " type: block\n location: /dev/sdc", 1036 err: `charm "a" storage "store-bad": location may not be specified for "type: block"`, 1037 }, { 1038 desc: "minimum size must parse correctly", 1039 yaml: " type: block\n minimum-size: foo", 1040 err: `metadata: expected a non-negative number, got "foo"`, 1041 }, { 1042 desc: "minimum size must have valid suffix", 1043 yaml: " type: block\n minimum-size: 10Q", 1044 err: `metadata: invalid multiplier suffix "Q", expected one of MGTPEZY`, 1045 }, { 1046 desc: "properties must contain valid values", 1047 yaml: " type: block\n properties: [transient, foo]", 1048 err: `metadata: .* unexpected value "foo"`, 1049 }} 1050 1051 for i, test := range tests { 1052 c.Logf("test %d: %s", i, test.desc) 1053 c.Logf("\n%s\n", prefix+test.yaml) 1054 _, err := charm.ReadMeta(strings.NewReader(prefix + test.yaml)) 1055 c.Assert(err, gc.ErrorMatches, test.err) 1056 } 1057 } 1058 1059 func (s *MetaSuite) TestStorageCount(c *gc.C) { 1060 testStorageCount := func(count string, min, max int) { 1061 meta, err := charm.ReadMeta(strings.NewReader(fmt.Sprintf(` 1062 name: a 1063 summary: b 1064 description: c 1065 storage: 1066 store0: 1067 type: filesystem 1068 multiple: 1069 range: %s 1070 `, count))) 1071 c.Assert(err, gc.IsNil) 1072 store := meta.Storage["store0"] 1073 c.Assert(store, gc.NotNil) 1074 c.Assert(store.CountMin, gc.Equals, min) 1075 c.Assert(store.CountMax, gc.Equals, max) 1076 } 1077 testStorageCount("1", 1, 1) 1078 testStorageCount("0-1", 0, 1) 1079 testStorageCount("1-1", 1, 1) 1080 testStorageCount("1+", 1, -1) 1081 // n- is equivalent to n+ 1082 testStorageCount("1-", 1, -1) 1083 } 1084 1085 func (s *MetaSuite) TestStorageLocation(c *gc.C) { 1086 meta, err := charm.ReadMeta(strings.NewReader(` 1087 name: a 1088 summary: b 1089 description: c 1090 storage: 1091 store0: 1092 type: filesystem 1093 location: /var/lib/things 1094 `)) 1095 c.Assert(err, gc.IsNil) 1096 store := meta.Storage["store0"] 1097 c.Assert(store, gc.NotNil) 1098 c.Assert(store.Location, gc.Equals, "/var/lib/things") 1099 } 1100 1101 func (s *MetaSuite) TestStorageMinimumSize(c *gc.C) { 1102 meta, err := charm.ReadMeta(strings.NewReader(` 1103 name: a 1104 summary: b 1105 description: c 1106 storage: 1107 store0: 1108 type: filesystem 1109 minimum-size: 10G 1110 `)) 1111 c.Assert(err, gc.IsNil) 1112 store := meta.Storage["store0"] 1113 c.Assert(store, gc.NotNil) 1114 c.Assert(store.MinimumSize, gc.Equals, uint64(10*1024)) 1115 } 1116 1117 func (s *MetaSuite) TestStorageProperties(c *gc.C) { 1118 meta, err := charm.ReadMeta(strings.NewReader(` 1119 name: a 1120 summary: b 1121 description: c 1122 storage: 1123 store0: 1124 type: filesystem 1125 properties: [transient] 1126 `)) 1127 c.Assert(err, gc.IsNil) 1128 store := meta.Storage["store0"] 1129 c.Assert(store, gc.NotNil) 1130 c.Assert(store.Properties, jc.SameContents, []string{"transient"}) 1131 } 1132 1133 func (s *MetaSuite) TestExtraBindings(c *gc.C) { 1134 meta, err := charm.ReadMeta(strings.NewReader(` 1135 name: a 1136 summary: b 1137 description: c 1138 extra-bindings: 1139 endpoint-1: 1140 foo: 1141 bar-42: 1142 `)) 1143 c.Assert(err, gc.IsNil) 1144 c.Assert(meta.ExtraBindings, gc.DeepEquals, map[string]charm.ExtraBinding{ 1145 "endpoint-1": { 1146 Name: "endpoint-1", 1147 }, 1148 "foo": { 1149 Name: "foo", 1150 }, 1151 "bar-42": { 1152 Name: "bar-42", 1153 }, 1154 }) 1155 } 1156 1157 func (s *MetaSuite) TestExtraBindingsEmptyMapError(c *gc.C) { 1158 meta, err := charm.ReadMeta(strings.NewReader(` 1159 name: a 1160 summary: b 1161 description: c 1162 extra-bindings: 1163 `)) 1164 c.Assert(err, gc.ErrorMatches, "metadata: extra-bindings: expected map, got nothing") 1165 c.Assert(meta, gc.IsNil) 1166 } 1167 1168 func (s *MetaSuite) TestExtraBindingsNonEmptyValueError(c *gc.C) { 1169 meta, err := charm.ReadMeta(strings.NewReader(` 1170 name: a 1171 summary: b 1172 description: c 1173 extra-bindings: 1174 foo: 42 1175 `)) 1176 c.Assert(err, gc.ErrorMatches, `metadata: extra-bindings.foo: expected empty value, got int\(42\)`) 1177 c.Assert(meta, gc.IsNil) 1178 } 1179 1180 func (s *MetaSuite) TestExtraBindingsEmptyNameError(c *gc.C) { 1181 meta, err := charm.ReadMeta(strings.NewReader(` 1182 name: a 1183 summary: b 1184 description: c 1185 extra-bindings: 1186 "": 1187 `)) 1188 c.Assert(err, gc.ErrorMatches, `metadata: extra-bindings: expected non-empty binding name, got string\(""\)`) 1189 c.Assert(meta, gc.IsNil) 1190 } 1191 1192 func (s *MetaSuite) TestPayloadClasses(c *gc.C) { 1193 meta, err := charm.ReadMeta(strings.NewReader(` 1194 name: a 1195 summary: b 1196 description: c 1197 payloads: 1198 monitor: 1199 type: docker 1200 kvm-guest: 1201 type: kvm 1202 `)) 1203 c.Assert(err, gc.IsNil) 1204 1205 c.Check(meta.PayloadClasses, jc.DeepEquals, map[string]charm.PayloadClass{ 1206 "monitor": charm.PayloadClass{ 1207 Name: "monitor", 1208 Type: "docker", 1209 }, 1210 "kvm-guest": charm.PayloadClass{ 1211 Name: "kvm-guest", 1212 Type: "kvm", 1213 }, 1214 }) 1215 } 1216 1217 func (s *MetaSuite) TestResources(c *gc.C) { 1218 meta, err := charm.ReadMeta(strings.NewReader(` 1219 name: a 1220 summary: b 1221 description: c 1222 resources: 1223 resource-name: 1224 type: file 1225 filename: filename.tgz 1226 description: "One line that is useful when operators need to push it." 1227 other-resource: 1228 type: file 1229 filename: other.zip 1230 `)) 1231 c.Assert(err, gc.IsNil) 1232 1233 c.Check(meta.Resources, jc.DeepEquals, map[string]resource.Meta{ 1234 "resource-name": resource.Meta{ 1235 Name: "resource-name", 1236 Type: resource.TypeFile, 1237 Path: "filename.tgz", 1238 Description: "One line that is useful when operators need to push it.", 1239 }, 1240 "other-resource": resource.Meta{ 1241 Name: "other-resource", 1242 Type: resource.TypeFile, 1243 Path: "other.zip", 1244 }, 1245 }) 1246 } 1247 1248 func (s *MetaSuite) TestParseResourceMetaOkay(c *gc.C) { 1249 name := "my-resource" 1250 data := map[string]interface{}{ 1251 "type": "file", 1252 "filename": "filename.tgz", 1253 "description": "One line that is useful when operators need to push it.", 1254 } 1255 res, err := charm.ParseResourceMeta(name, data) 1256 c.Assert(err, jc.ErrorIsNil) 1257 1258 c.Check(res, jc.DeepEquals, resource.Meta{ 1259 Name: "my-resource", 1260 Type: resource.TypeFile, 1261 Path: "filename.tgz", 1262 Description: "One line that is useful when operators need to push it.", 1263 }) 1264 } 1265 1266 func (s *MetaSuite) TestParseResourceMetaMissingName(c *gc.C) { 1267 name := "" 1268 data := map[string]interface{}{ 1269 "type": "file", 1270 "filename": "filename.tgz", 1271 "description": "One line that is useful when operators need to push it.", 1272 } 1273 res, err := charm.ParseResourceMeta(name, data) 1274 c.Assert(err, jc.ErrorIsNil) 1275 1276 c.Check(res, jc.DeepEquals, resource.Meta{ 1277 Name: "", 1278 Type: resource.TypeFile, 1279 Path: "filename.tgz", 1280 Description: "One line that is useful when operators need to push it.", 1281 }) 1282 } 1283 1284 func (s *MetaSuite) TestParseResourceMetaMissingType(c *gc.C) { 1285 name := "my-resource" 1286 data := map[string]interface{}{ 1287 "filename": "filename.tgz", 1288 "description": "One line that is useful when operators need to push it.", 1289 } 1290 res, err := charm.ParseResourceMeta(name, data) 1291 c.Assert(err, jc.ErrorIsNil) 1292 1293 c.Check(res, jc.DeepEquals, resource.Meta{ 1294 Name: "my-resource", 1295 // Type is the zero value. 1296 Path: "filename.tgz", 1297 Description: "One line that is useful when operators need to push it.", 1298 }) 1299 } 1300 1301 func (s *MetaSuite) TestParseResourceMetaEmptyType(c *gc.C) { 1302 name := "my-resource" 1303 data := map[string]interface{}{ 1304 "type": "", 1305 "filename": "filename.tgz", 1306 "description": "One line that is useful when operators need to push it.", 1307 } 1308 _, err := charm.ParseResourceMeta(name, data) 1309 1310 c.Check(err, gc.ErrorMatches, `unsupported resource type .*`) 1311 } 1312 1313 func (s *MetaSuite) TestParseResourceMetaUnknownType(c *gc.C) { 1314 name := "my-resource" 1315 data := map[string]interface{}{ 1316 "type": "spam", 1317 "filename": "filename.tgz", 1318 "description": "One line that is useful when operators need to push it.", 1319 } 1320 _, err := charm.ParseResourceMeta(name, data) 1321 1322 c.Check(err, gc.ErrorMatches, `unsupported resource type .*`) 1323 } 1324 1325 func (s *MetaSuite) TestParseResourceMetaMissingPath(c *gc.C) { 1326 name := "my-resource" 1327 data := map[string]interface{}{ 1328 "type": "file", 1329 "description": "One line that is useful when operators need to push it.", 1330 } 1331 res, err := charm.ParseResourceMeta(name, data) 1332 c.Assert(err, jc.ErrorIsNil) 1333 1334 c.Check(res, jc.DeepEquals, resource.Meta{ 1335 Name: "my-resource", 1336 Type: resource.TypeFile, 1337 Path: "", 1338 Description: "One line that is useful when operators need to push it.", 1339 }) 1340 } 1341 1342 func (s *MetaSuite) TestParseResourceMetaMissingComment(c *gc.C) { 1343 name := "my-resource" 1344 data := map[string]interface{}{ 1345 "type": "file", 1346 "filename": "filename.tgz", 1347 } 1348 res, err := charm.ParseResourceMeta(name, data) 1349 c.Assert(err, jc.ErrorIsNil) 1350 1351 c.Check(res, jc.DeepEquals, resource.Meta{ 1352 Name: "my-resource", 1353 Type: resource.TypeFile, 1354 Path: "filename.tgz", 1355 Description: "", 1356 }) 1357 } 1358 1359 func (s *MetaSuite) TestParseResourceMetaEmpty(c *gc.C) { 1360 name := "my-resource" 1361 data := make(map[string]interface{}) 1362 res, err := charm.ParseResourceMeta(name, data) 1363 c.Assert(err, jc.ErrorIsNil) 1364 1365 c.Check(res, jc.DeepEquals, resource.Meta{ 1366 Name: "my-resource", 1367 }) 1368 } 1369 1370 func (s *MetaSuite) TestParseResourceMetaNil(c *gc.C) { 1371 name := "my-resource" 1372 var data map[string]interface{} 1373 res, err := charm.ParseResourceMeta(name, data) 1374 c.Assert(err, jc.ErrorIsNil) 1375 1376 c.Check(res, jc.DeepEquals, resource.Meta{ 1377 Name: "my-resource", 1378 }) 1379 } 1380 1381 type dummyCharm struct{} 1382 1383 func (c *dummyCharm) Config() *charm.Config { 1384 panic("unused") 1385 } 1386 1387 func (c *dummyCharm) Metrics() *charm.Metrics { 1388 panic("unused") 1389 } 1390 1391 func (c *dummyCharm) Actions() *charm.Actions { 1392 panic("unused") 1393 } 1394 1395 func (c *dummyCharm) Revision() int { 1396 panic("unused") 1397 } 1398 1399 func (c *dummyCharm) Meta() *charm.Meta { 1400 return &charm.Meta{ 1401 Provides: map[string]charm.Relation{ 1402 "pro": {Interface: "ifce-pro", Scope: charm.ScopeGlobal}, 1403 }, 1404 Requires: map[string]charm.Relation{ 1405 "req": {Interface: "ifce-req", Scope: charm.ScopeGlobal}, 1406 "info": {Interface: "juju-info", Scope: charm.ScopeContainer}, 1407 }, 1408 Peers: map[string]charm.Relation{ 1409 "peer": {Interface: "ifce-peer", Scope: charm.ScopeGlobal}, 1410 }, 1411 } 1412 }