github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/core/charm/computedbase.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/charm/v12" 11 "github.com/juju/collections/set" 12 "github.com/juju/collections/transform" 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 16 "github.com/juju/juju/core/base" 17 ) 18 19 var logger = loggo.GetLogger("juju.core.charm") 20 21 // BaseForCharm takes a requested base and a list of bases supported by a 22 // charm and returns the base which is relevant. 23 // If the requested base is empty, then the first supported base is used, 24 // otherwise the requested base is validated against the supported bases. 25 func BaseForCharm(requestedBase base.Base, supportedBases []base.Base) (base.Base, error) { 26 // Old local charm with no supported bases, use the 27 // requestedBase. If none specified error. 28 if len(supportedBases) == 0 { 29 if requestedBase.Empty() { 30 return base.Base{}, MissingBaseError 31 } 32 return requestedBase, nil 33 } 34 // Use the charm default. 35 if requestedBase.Empty() { 36 return supportedBases[0], nil 37 } 38 for _, s := range supportedBases { 39 if s.IsCompatible(requestedBase) { 40 return requestedBase, nil 41 } 42 } 43 return base.Base{}, NewUnsupportedBaseError(requestedBase, supportedBases) 44 } 45 46 // MissingBaseError is used to denote that BaseForCharm could not determine 47 // a base because a legacy charm did not declare any. 48 var MissingBaseError = errors.ConstError("charm does not define any bases") 49 50 // ComputedBases of a charm, preserving legacy behaviour. For charms prior to v2, 51 // fall back the metadata series can convert to bases 52 func ComputedBases(c charm.CharmMeta) ([]base.Base, error) { 53 manifest := c.Manifest() 54 if manifest != nil { 55 computedBases := make([]base.Base, len(manifest.Bases)) 56 for i, b := range manifest.Bases { 57 computedBase, err := base.ParseBase(b.Name, b.Channel.String()) 58 if err != nil { 59 return nil, errors.Trace(err) 60 } 61 computedBases[i] = computedBase 62 } 63 return computedBases, nil 64 } 65 if charm.MetaFormat(c) < charm.FormatV2 { 66 return transform.SliceOrErr(c.Meta().Series, func(s string) (base.Base, error) { 67 if s == base.Kubernetes.String() { 68 return base.LegacyKubernetesBase(), nil 69 } 70 return base.GetBaseFromSeries(s) 71 }) 72 } 73 return []base.Base{}, nil 74 } 75 76 // BaseIsCompatibleWithCharm returns nil if the provided charm is compatible 77 // with the provided base. Otherwise, return an UnsupportedBaseError 78 func BaseIsCompatibleWithCharm(b base.Base, c charm.CharmMeta) error { 79 supportedBases, err := ComputedBases(c) 80 if err != nil { 81 return errors.Trace(err) 82 } 83 _, err = BaseForCharm(b, supportedBases) 84 return err 85 } 86 87 // OSIsCompatibleWithCharm returns nil is any of the bases the charm supports 88 // has an os which matched the provided os. Otherwise, return a NotSupported error 89 func OSIsCompatibleWithCharm(os string, c charm.CharmMeta) error { 90 supportedBases, err := ComputedBases(c) 91 if err != nil { 92 return errors.Trace(err) 93 } 94 if len(supportedBases) == 0 { 95 return MissingBaseError 96 } 97 oses := set.NewStrings(transform.Slice(supportedBases, func(b base.Base) string { return b.OS })...) 98 if oses.Contains(os) { 99 return nil 100 } 101 osesStr := strings.Join(oses.SortedValues(), "") 102 errStr := fmt.Sprintf("OS %q not supported by charm %q, supported OSes are: %s", os, c.Meta().Name, osesStr) 103 return errors.NewNotSupported(nil, errStr) 104 }