github.com/juju/charm/v11@v11.2.0/base.go (about)

     1  // Copyright 2020 Canonical Ltd.
     2  // Licensed under the LGPLv3, see LICENCE file for details.
     3  
     4  package charm
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/collections/set"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/os/v2"
    13  	"github.com/juju/utils/v3/arch"
    14  )
    15  
    16  // Base represents an OS/Channel.
    17  // Bases can also be converted to and from a series string.
    18  type Base struct {
    19  	Name          string   `bson:"name,omitempty" json:"name,omitempty"`
    20  	Channel       Channel  `bson:"channel,omitempty" json:"channel,omitempty"`
    21  	Architectures []string `bson:"architectures,omitempty" json:"architectures,omitempty"`
    22  }
    23  
    24  // Validate returns with no error when the Base is valid.
    25  func (b Base) Validate() error {
    26  	if b.Name == "" {
    27  		return errors.NotValidf("base without name")
    28  	}
    29  
    30  	if !validOSForBase.Contains(b.Name) {
    31  		return errors.NotValidf("os %q", b.Name)
    32  	}
    33  	if b.Channel.Empty() {
    34  		return errors.NotValidf("channel")
    35  	}
    36  	for _, v := range b.Architectures {
    37  		if !arch.IsSupportedArch(v) {
    38  			return errors.NotValidf("architecture %q", v)
    39  		}
    40  	}
    41  
    42  	return nil
    43  }
    44  
    45  // String representation of the Base.
    46  func (b Base) String() string {
    47  	if b.Channel.Empty() {
    48  		panic("cannot stringify invalid base. Bases should always be validated before stringifying")
    49  	}
    50  	str := fmt.Sprintf("%s@%s", b.Name, b.Channel)
    51  	if len(b.Architectures) > 0 {
    52  		str = fmt.Sprintf("%s on %s", str, strings.Join(b.Architectures, ", "))
    53  	}
    54  	return str
    55  }
    56  
    57  // ParseBase parses a base as string in the form "os@track/risk/branch"
    58  // and an optional list of architectures
    59  func ParseBase(s string, archs ...string) (Base, error) {
    60  	var err error
    61  	base := Base{}
    62  
    63  	segments := strings.Split(s, "@")
    64  	if len(segments) != 2 {
    65  		return Base{}, errors.NotValidf("base string must contain exactly one @. %q", s)
    66  	}
    67  	base.Name = strings.ToLower(segments[0])
    68  	channelName := segments[1]
    69  
    70  	if channelName != "" {
    71  		base.Channel, err = ParseChannelNormalize(channelName)
    72  		if err != nil {
    73  			return Base{}, errors.Annotatef(err, "malformed channel in base string %q", s)
    74  		}
    75  	}
    76  
    77  	base.Architectures = make([]string, len(archs))
    78  	for i, v := range archs {
    79  		base.Architectures[i] = arch.NormaliseArch(v)
    80  	}
    81  
    82  	err = base.Validate()
    83  	if err != nil {
    84  		var a string
    85  		if len(base.Architectures) > 0 {
    86  			a = fmt.Sprintf(" with architectures %q", strings.Join(base.Architectures, ","))
    87  		}
    88  		return Base{}, errors.Annotatef(err, "invalid base string %q%s", s, a)
    89  	}
    90  	return base, nil
    91  }
    92  
    93  // validOSForBase is a string set of valid OS names for a base.
    94  var validOSForBase = set.NewStrings(
    95  	strings.ToLower(os.Ubuntu.String()),
    96  	strings.ToLower(os.CentOS.String()),
    97  	strings.ToLower(os.Windows.String()),
    98  	strings.ToLower(os.OSX.String()),
    99  	strings.ToLower(os.OpenSUSE.String()),
   100  	strings.ToLower(os.GenericLinux.String()),
   101  )