github.com/juju/charm/v11@v11.2.0/charm.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "fmt" 8 "os" 9 "strings" 10 11 "github.com/juju/collections/set" 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 ) 15 16 var logger = loggo.GetLogger("juju.charm") 17 18 // CharmMeta describes methods that inform charm operation. 19 type CharmMeta interface { 20 Meta() *Meta 21 Manifest() *Manifest 22 } 23 24 // The Charm interface is implemented by any type that 25 // may be handled as a charm. 26 type Charm interface { 27 CharmMeta 28 Config() *Config 29 Metrics() *Metrics 30 Actions() *Actions 31 Revision() int 32 } 33 34 // ReadCharm reads a Charm from path, which can point to either a charm archive or a 35 // charm directory. 36 func ReadCharm(path string) (charm Charm, err error) { 37 info, err := os.Stat(path) 38 if err != nil { 39 return nil, errors.Trace(err) 40 } 41 if info.IsDir() { 42 charm, err = ReadCharmDir(path) 43 } else { 44 charm, err = ReadCharmArchive(path) 45 } 46 if err != nil { 47 return nil, errors.Trace(err) 48 } 49 50 return charm, errors.Trace(CheckMeta(charm)) 51 } 52 53 // FormatSelectionReason represents the reason for a format version selection. 54 type FormatSelectionReason = string 55 56 const ( 57 // SelectionManifest states that it found a manifest. 58 SelectionManifest FormatSelectionReason = "manifest" 59 // SelectionBases states that there was at least 1 base. 60 SelectionBases FormatSelectionReason = "bases" 61 // SelectionSeries states that there was at least 1 series. 62 SelectionSeries FormatSelectionReason = "series" 63 // SelectionContainers states that there was at least 1 container. 64 SelectionContainers FormatSelectionReason = "containers" 65 ) 66 67 var ( 68 // formatV2Set defines what in reality is a v2 metadata. 69 formatV2Set = set.NewStrings(SelectionBases, SelectionContainers) 70 ) 71 72 // MetaFormatReasons returns the format and why the selection was done. We can 73 // then inspect the reasons to understand the reasoning. 74 func MetaFormatReasons(ch CharmMeta) (Format, []FormatSelectionReason) { 75 manifest := ch.Manifest() 76 77 // To better inform users of why a metadata selection was preferred over 78 // another, we deduce why a format is selected over another. 79 reasons := set.NewStrings() 80 if manifest != nil { 81 reasons.Add(SelectionManifest) 82 if len(manifest.Bases) > 0 { 83 reasons.Add(SelectionBases) 84 } 85 } 86 if len(ch.Meta().Series) > 0 { 87 reasons.Add(SelectionSeries) 88 } 89 90 // To be a format v1 you can have no series with no bases or containers, or 91 // just have a series slice. 92 format := FormatV1 93 if !reasons.Contains(SelectionSeries) && reasons.Intersection(formatV2Set).Size() > 0 { 94 format = FormatV2 95 } 96 97 return format, reasons.SortedValues() 98 } 99 100 // MetaFormat returns the underlying format from checking the charm for the 101 // right values. 102 func MetaFormat(ch CharmMeta) Format { 103 format, _ := MetaFormatReasons(ch) 104 return format 105 } 106 107 // CheckMeta determines the version of the metadata used by this charm, 108 // then checks that it is valid as appropriate. 109 func CheckMeta(ch CharmMeta) error { 110 format, reasons := MetaFormatReasons(ch) 111 return ch.Meta().Check(format, reasons...) 112 } 113 114 // SeriesForCharm takes a requested series and a list of series supported by a 115 // charm and returns the series which is relevant. 116 // If the requested series is empty, then the first supported series is used, 117 // otherwise the requested series is validated against the supported series. 118 func SeriesForCharm(requestedSeries string, supportedSeries []string) (string, error) { 119 // Old charm with no supported series. 120 if len(supportedSeries) == 0 { 121 if requestedSeries == "" { 122 return "", errMissingSeries 123 } 124 return requestedSeries, nil 125 } 126 // Use the charm default. 127 if requestedSeries == "" { 128 return supportedSeries[0], nil 129 } 130 for _, s := range supportedSeries { 131 if s == requestedSeries { 132 return requestedSeries, nil 133 } 134 } 135 return "", &unsupportedSeriesError{requestedSeries, supportedSeries} 136 } 137 138 // errMissingSeries is used to denote that SeriesForCharm could not determine 139 // a series because a legacy charm did not declare any. 140 var errMissingSeries = fmt.Errorf("series not specified and charm does not define any") 141 142 // IsMissingSeriesError returns true if err is an errMissingSeries. 143 func IsMissingSeriesError(err error) bool { 144 return err == errMissingSeries 145 } 146 147 // UnsupportedSeriesError represents an error indicating that the requested series 148 // is not supported by the charm. 149 type unsupportedSeriesError struct { 150 requestedSeries string 151 supportedSeries []string 152 } 153 154 func (e *unsupportedSeriesError) Error() string { 155 return fmt.Sprintf( 156 "series %q not supported by charm, supported series are: %s", 157 e.requestedSeries, strings.Join(e.supportedSeries, ","), 158 ) 159 } 160 161 // NewUnsupportedSeriesError returns an error indicating that the requested series 162 // is not supported by a charm. 163 func NewUnsupportedSeriesError(requestedSeries string, supportedSeries []string) error { 164 return &unsupportedSeriesError{requestedSeries, supportedSeries} 165 } 166 167 // IsUnsupportedSeriesError returns true if err is an UnsupportedSeriesError. 168 func IsUnsupportedSeriesError(err error) bool { 169 _, ok := err.(*unsupportedSeriesError) 170 return ok 171 }