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  }