github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/base/channel.go (about) 1 // Copyright 2022 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package base 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 an os channel. 41 // 42 // A channel consists of, and is subdivided by, tracks and risk-levels: 43 // - Tracks represents the version of the os, eg "22.04". 44 // - Risk-levels represent a progressive potential trade-off between stability 45 // and new features. 46 // 47 // The complete channel name can be structured as three distinct parts separated 48 // by slashes: 49 // 50 // <track>/<risk> 51 type Channel struct { 52 Track string `json:"track,omitempty"` 53 Risk Risk `json:"risk,omitempty"` 54 } 55 56 // MakeDefaultChannel creates a normalized channel for 57 // the specified track with a default risk of "stable". 58 func MakeDefaultChannel(track string) Channel { 59 ch := Channel{ 60 Track: track, 61 } 62 return ch.Normalize() 63 } 64 65 // ParseChannel parses a string representing a channel. 66 func ParseChannel(s string) (Channel, error) { 67 if s == "" { 68 return Channel{}, errors.NotValidf("empty channel") 69 } 70 71 p := strings.Split(s, "/") 72 73 var risk, track *string 74 switch len(p) { 75 case 1: 76 track = &p[0] 77 case 2: 78 track, risk = &p[0], &p[1] 79 default: 80 return Channel{}, errors.Errorf("channel is malformed and has too many components %q", s) 81 } 82 83 ch := Channel{} 84 85 if risk != nil { 86 if !isRisk(*risk) { 87 return Channel{}, errors.NotValidf("risk in channel %q", s) 88 } 89 // We can lift this into a risk, as we've validated prior to this to 90 // ensure it's a valid risk. 91 ch.Risk = Risk(*risk) 92 } 93 if track != nil { 94 if *track == "" { 95 return Channel{}, errors.NotValidf("track in channel %q", s) 96 } 97 ch.Track = *track 98 } 99 return ch, nil 100 } 101 102 // ParseChannelNormalize parses a string representing a store channel. 103 // The returned channel's track, risk and name are normalized. 104 func ParseChannelNormalize(s string) (Channel, error) { 105 ch, err := ParseChannel(s) 106 if err != nil { 107 return Channel{}, errors.Trace(err) 108 } 109 return ch.Normalize(), nil 110 } 111 112 // Normalize the channel with normalized track, risk and names. 113 func (ch Channel) Normalize() Channel { 114 track := ch.Track 115 116 risk := ch.Risk 117 if risk == "" && track != "kubernetes" { 118 risk = "stable" 119 } 120 121 return Channel{ 122 Track: track, 123 Risk: risk, 124 } 125 } 126 127 // Empty returns true if all it's components are empty. 128 func (ch Channel) Empty() bool { 129 return ch.Track == "" && ch.Risk == "" 130 } 131 132 func (ch Channel) String() string { 133 path := ch.Track 134 if risk := ch.Risk; risk != "" { 135 path = fmt.Sprintf("%s/%s", path, risk) 136 } 137 return path 138 } 139 140 func (ch Channel) DisplayString() string { 141 track, risk := ch.Track, ch.Risk 142 if risk == Stable && track != "kubernetes" { 143 risk = "" 144 } 145 if risk == "" { 146 return track 147 } 148 return fmt.Sprintf("%s/%s", track, risk) 149 }