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  }