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  }