github.com/juju/charm/v11@v11.2.0/manifest.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "io" 8 "io/ioutil" 9 10 "github.com/juju/errors" 11 "github.com/juju/schema" 12 "github.com/juju/utils/v3/arch" 13 "gopkg.in/yaml.v2" 14 ) 15 16 // Manifest represents the recording of the building of the charm or bundle. 17 // The manifest file should represent the metadata.yaml, but a lot more 18 // information. 19 type Manifest struct { 20 Bases []Base `yaml:"bases"` 21 } 22 23 // Validate checks the manifest to ensure there are no empty names, nor channels, 24 // and that architectures are supported. 25 func (m *Manifest) Validate() error { 26 for _, b := range m.Bases { 27 if err := b.Validate(); err != nil { 28 return errors.Annotate(err, "validating manifest") 29 } 30 } 31 return nil 32 } 33 34 func (m *Manifest) UnmarshalYAML(f func(interface{}) error) error { 35 raw := make(map[interface{}]interface{}) 36 err := f(&raw) 37 if err != nil { 38 return err 39 } 40 41 v, err := schema.List(baseSchema).Coerce(raw["bases"], nil) 42 if err != nil { 43 return errors.Annotatef(err, "coerce") 44 } 45 46 newV, ok := v.([]interface{}) 47 if !ok { 48 return errors.Annotatef(err, "converting") 49 } 50 bases, err := parseBases(newV) 51 if err != nil { 52 return err 53 } 54 55 *m = Manifest{Bases: bases} 56 return nil 57 } 58 59 func parseBases(input interface{}) ([]Base, error) { 60 var err error 61 if input == nil { 62 return nil, nil 63 } 64 var res []Base 65 for _, v := range input.([]interface{}) { 66 var base Base 67 baseMap := v.(map[string]interface{}) 68 if value, ok := baseMap["name"]; ok { 69 base.Name = value.(string) 70 } 71 if value, ok := baseMap["channel"]; ok { 72 base.Channel, err = ParseChannelNormalize(value.(string)) 73 if err != nil { 74 return nil, errors.Annotatef(err, "parsing channel %q", value.(string)) 75 } 76 } 77 base.Architectures = parseArchitectureList(baseMap["architectures"]) 78 err = base.Validate() 79 if err != nil { 80 return nil, errors.Trace(err) 81 } 82 res = append(res, base) 83 } 84 return res, nil 85 } 86 87 // ReadManifest reads in a Manifest from a charm's manifest.yaml. Some of 88 // validation is done when unmarshalling the manifest, including 89 // verification that the base.Name is a supported operating system. Full 90 // validation done by calling Validate(). 91 func ReadManifest(r io.Reader) (*Manifest, error) { 92 data, err := ioutil.ReadAll(r) 93 if err != nil { 94 return nil, err 95 } 96 var manifest *Manifest 97 if err := yaml.Unmarshal(data, &manifest); err != nil { 98 return nil, errors.Annotatef(err, "manifest") 99 } 100 if manifest == nil { 101 return nil, errors.Annotatef(err, "invalid base in manifest") 102 } 103 return manifest, nil 104 } 105 106 var baseSchema = schema.FieldMap( 107 schema.Fields{ 108 "name": schema.String(), 109 "channel": schema.String(), 110 "architectures": schema.List(schema.String()), 111 }, schema.Defaults{ 112 "name": schema.Omit, 113 "channel": schema.Omit, 114 "architectures": schema.Omit, 115 }) 116 117 func parseArchitectureList(list interface{}) []string { 118 if list == nil { 119 return nil 120 } 121 slice := list.([]interface{}) 122 result := make([]string, 0, len(slice)) 123 for _, elem := range slice { 124 result = append(result, arch.NormaliseArch(elem.(string))) 125 } 126 return result 127 }