github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/charm/origin.go (about) 1 // Copyright 2020 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/errors" 12 13 corebase "github.com/juju/juju/core/base" 14 ) 15 16 // Source represents the source of the charm. 17 type Source string 18 19 // Matches attempts to match a string to a given source. 20 func (c Source) Matches(o string) bool { 21 return string(c) == o 22 } 23 24 func (c Source) String() string { 25 return string(c) 26 } 27 28 const ( 29 // Local represents a local charm. 30 Local Source = "local" 31 // CharmHub represents a charm from the new charmHub. 32 CharmHub Source = "charm-hub" 33 ) 34 35 // Origin holds the original source of a charm. Information about where the 36 // charm was installed from (charm-hub, charm-store, local) and any additional 37 // information we can utilise when making modelling decisions for upgrading or 38 // changing. 39 type Origin struct { 40 Source Source 41 Type string 42 ID string 43 Hash string 44 45 // Users can request a revision to be installed instead of a channel, so 46 // we should model that correctly here. 47 Revision *int 48 Channel *charm.Channel 49 Platform Platform 50 51 // InstanceKey is an optional unique string associated with the application. 52 // To assist with keeping KPI data in charmhub, it must be the same for every 53 // charmhub Refresh action for the Refresh api endpoint related to an 54 // application. For all other actions, a random uuid will used when the request 55 // is sent. Create with the charmhub.CreateInstanceKey method. LP: 1944582 56 InstanceKey string 57 } 58 59 // Platform describes the platform used to install the charm with. 60 type Platform struct { 61 Architecture string 62 OS string 63 Channel string 64 } 65 66 // MustParsePlatform parses a given string or returns a panic. 67 func MustParsePlatform(s string) Platform { 68 p, err := ParsePlatformNormalize(s) 69 if err != nil { 70 panic(err) 71 } 72 return p 73 } 74 75 // ParsePlatform parses a string representing a store platform. 76 // Serialized version of platform can be expected to conform to the following: 77 // 78 // 1. Architecture is mandatory. 79 // 2. OS is optional and can be dropped. Release is mandatory if OS wants 80 // to be displayed. 81 // 3. Release is also optional. 82 // 83 // To indicate something is missing `unknown` can be used in place. 84 // 85 // Examples: 86 // 87 // 1. `<arch>/<os>/<channel>` 88 // 2. `<arch>` 89 // 3. `<arch>/<series>` 90 // 4. `<arch>/unknown/<series>` 91 func ParsePlatform(s string) (Platform, error) { 92 if s == "" { 93 return Platform{}, errors.BadRequestf("platform cannot be empty") 94 } 95 96 p := strings.Split(s, "/") 97 98 var arch, os, channel *string 99 switch len(p) { 100 case 1: 101 arch = &p[0] 102 case 2: 103 arch = &p[0] 104 channel = &p[1] 105 case 3: 106 arch, os, channel = &p[0], &p[1], &p[2] 107 case 4: 108 arch, os, channel = &p[0], &p[1], strptr(fmt.Sprintf("%s/%s", p[2], p[3])) 109 default: 110 return Platform{}, errors.Errorf("platform is malformed and has too many components %q", s) 111 } 112 113 platform := Platform{} 114 if arch != nil { 115 if *arch == "" { 116 return Platform{}, errors.NotValidf("architecture in platform %q", s) 117 } 118 platform.Architecture = *arch 119 } 120 if os != nil { 121 if *os == "" { 122 return Platform{}, errors.NotValidf("os in platform %q", s) 123 } 124 platform.OS = *os 125 } 126 if channel != nil { 127 if *channel == "" { 128 return Platform{}, errors.NotValidf("channel in platform %q", s) 129 } 130 if *channel != "unknown" { 131 // Channel might be a series, eg "jammy" or an os version, eg "22.04". 132 // We are transitioning away from series but still need to support it. 133 // If an os version is specified, os is mandatory. 134 series := *channel 135 vers, err := corebase.SeriesVersion(series) 136 if err == nil { 137 osType, _ := corebase.GetOSFromSeries(series) 138 platform.OS = strings.ToLower(osType.String()) 139 *channel = vers 140 } else if platform.OS == "" { 141 return Platform{}, errors.NotValidf("channel without os name in platform %q", s) 142 } 143 platform.Channel = *channel 144 } 145 } 146 147 return platform, nil 148 } 149 150 func strptr(s string) *string { 151 return &s 152 } 153 154 // ParsePlatformNormalize parses a string presenting a store platform. 155 // The returned platform's architecture, os and series are normalized. 156 func ParsePlatformNormalize(s string) (Platform, error) { 157 platform, err := ParsePlatform(s) 158 if err != nil { 159 return Platform{}, errors.Trace(err) 160 } 161 return platform.Normalize(), nil 162 } 163 164 // Normalize the platform with normalized architecture, os and channel. 165 func (p Platform) Normalize() Platform { 166 os := p.OS 167 if os == "unknown" { 168 os = "" 169 } 170 171 channel := p.Channel 172 if channel == "unknown" { 173 os = "" 174 channel = "" 175 } 176 177 return Platform{ 178 Architecture: p.Architecture, 179 OS: os, 180 Channel: channel, 181 } 182 } 183 184 func (p Platform) String() string { 185 path := p.Architecture 186 if os := p.OS; os != "" { 187 path = fmt.Sprintf("%s/%s", path, os) 188 } 189 if channel := p.Channel; channel != "" { 190 path = fmt.Sprintf("%s/%s", path, channel) 191 } 192 193 return path 194 }