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 )