github.com/juju/charm/v11@v11.2.0/channel.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/errors" 11 ) 12 13 // Risk describes the type of risk in a current channel. 14 type Risk string 15 16 const ( 17 Stable Risk = "stable" 18 Candidate Risk = "candidate" 19 Beta Risk = "beta" 20 Edge Risk = "edge" 21 ) 22 23 // Risks is a list of the available channel risks. 24 var Risks = []Risk{ 25 Stable, 26 Candidate, 27 Beta, 28 Edge, 29 } 30 31 func isRisk(potential string) bool { 32 for _, risk := range Risks { 33 if potential == string(risk) { 34 return true 35 } 36 } 37 return false 38 } 39 40 // Channel identifies and describes completely a store channel. 41 // 42 // A channel consists of, and is subdivided by, tracks, risk-levels and 43 // branches: 44 // - Tracks enable snap developers to publish multiple supported releases of 45 // their application under the same snap name. 46 // - Risk-levels represent a progressive potential trade-off between stability 47 // and new features. 48 // - Branches are _optional_ and hold temporary releases intended to help with 49 // bug-fixing. 50 // 51 // The complete channel name can be structured as three distinct parts separated 52 // by slashes: 53 // 54 // <track>/<risk>/<branch> 55 // 56 type Channel struct { 57 Track string `json:"track,omitempty"` 58 Risk Risk `json:"risk,omitempty"` 59 Branch string `json:"branch,omitempty"` 60 } 61 62 // MakeChannel creates a core charm Channel from a set of component parts. 63 func MakeChannel(track, risk, branch string) (Channel, error) { 64 if !isRisk(risk) { 65 return Channel{}, errors.NotValidf("risk %q", risk) 66 } 67 return Channel{ 68 Track: track, 69 Risk: Risk(risk), 70 Branch: branch, 71 }, nil 72 } 73 74 // MakePermissiveChannel creates a normalized core charm channel which 75 // never fails. It assumes that the risk has been prechecked. 76 func MakePermissiveChannel(track, risk, branch string) Channel { 77 ch := Channel{ 78 Track: track, 79 Risk: Risk(risk), 80 Branch: branch, 81 } 82 return ch.Normalize() 83 } 84 85 // ParseChannel parses a string representing a store channel. 86 func ParseChannel(s string) (Channel, error) { 87 if s == "" { 88 return Channel{}, errors.NotValidf("empty channel") 89 } 90 91 p := strings.Split(s, "/") 92 93 var risk, track, branch *string 94 switch len(p) { 95 case 1: 96 if isRisk(p[0]) { 97 risk = &p[0] 98 } else { 99 track = &p[0] 100 } 101 case 2: 102 if isRisk(p[0]) { 103 risk, branch = &p[0], &p[1] 104 } else { 105 track, risk = &p[0], &p[1] 106 } 107 case 3: 108 track, risk, branch = &p[0], &p[1], &p[2] 109 default: 110 return Channel{}, errors.Errorf("channel is malformed and has too many components %q", s) 111 } 112 113 ch := Channel{} 114 115 if risk != nil { 116 if !isRisk(*risk) { 117 return Channel{}, errors.NotValidf("risk in channel %q", s) 118 } 119 // We can lift this into a risk, as we've validated prior to this to 120 // ensure it's a valid risk. 121 ch.Risk = Risk(*risk) 122 } 123 if track != nil { 124 if *track == "" { 125 return Channel{}, errors.NotValidf("track in channel %q", s) 126 } 127 ch.Track = *track 128 } 129 if branch != nil { 130 if *branch == "" { 131 return Channel{}, errors.NotValidf("branch in channel %q", s) 132 } 133 ch.Branch = *branch 134 } 135 return ch, nil 136 } 137 138 // ParseChannelNormalize parses a string representing a store channel. 139 // The returned channel's track, risk and name are normalized. 140 func ParseChannelNormalize(s string) (Channel, error) { 141 ch, err := ParseChannel(s) 142 if err != nil { 143 return Channel{}, errors.Trace(err) 144 } 145 return ch.Normalize(), nil 146 } 147 148 // Normalize the channel with normalized track, risk and names. 149 func (ch Channel) Normalize() Channel { 150 track := ch.Track 151 152 risk := ch.Risk 153 if risk == "" { 154 risk = "stable" 155 } 156 157 return Channel{ 158 Track: track, 159 Risk: risk, 160 Branch: ch.Branch, 161 } 162 } 163 164 // Empty returns true if all it's components are empty. 165 func (ch Channel) Empty() bool { 166 return ch.Track == "" && ch.Risk == "" && ch.Branch == "" 167 } 168 169 func (ch Channel) String() string { 170 path := string(ch.Risk) 171 if track := ch.Track; track != "" { 172 path = fmt.Sprintf("%s/%s", track, path) 173 } 174 if branch := ch.Branch; branch != "" { 175 path = fmt.Sprintf("%s/%s", path, branch) 176 } 177 178 return path 179 }