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  }