github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/core/charm/charmpath.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package charm
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/juju/charm/v12"
    13  	"github.com/juju/errors"
    14  
    15  	"github.com/juju/juju/core/base"
    16  )
    17  
    18  // NewCharmAtPath returns the charm represented by this path,
    19  // and a URL that describes it. If the base is empty,
    20  // the charm's default base is used, if any.
    21  // Otherwise, the base is validated against those the
    22  // charm declares it supports.
    23  func NewCharmAtPath(path string, b base.Base) (charm.Charm, *charm.URL, error) {
    24  	return NewCharmAtPathForceBase(path, b, false)
    25  }
    26  
    27  // NewCharmAtPathForceBase returns the charm represented by this path,
    28  // and a URL that describes it. If the base is empty,
    29  // the charm's default base is used, if any.
    30  // Otherwise, the base is validated against those the
    31  // charm declares it supports. If force is true, then any
    32  // base validation errors are ignored and the requested
    33  // base is used regardless.
    34  func NewCharmAtPathForceBase(path string, b base.Base, force bool) (charm.Charm, *charm.URL, error) {
    35  	if path == "" {
    36  		return nil, nil, errors.New("empty charm path")
    37  	}
    38  	_, err := os.Stat(path)
    39  	if isNotExistsError(err) {
    40  		return nil, nil, os.ErrNotExist
    41  	} else if err == nil && !isValidCharmOrBundlePath(path) {
    42  		return nil, nil, InvalidPath(path)
    43  	}
    44  	ch, err := charm.ReadCharm(path)
    45  	if err != nil {
    46  		if isNotExistsError(err) {
    47  			return nil, nil, CharmNotFound(path)
    48  		}
    49  		return nil, nil, err
    50  	}
    51  	name := ch.Meta().Name
    52  
    53  	baseToUse, err := charmBase(b, force, ch)
    54  	if err != nil {
    55  		return nil, nil, err
    56  	}
    57  	seriesToUse, err := base.GetSeriesFromBase(baseToUse)
    58  	if err != nil {
    59  		return nil, nil, err
    60  	}
    61  
    62  	url := &charm.URL{
    63  		Schema:   "local",
    64  		Name:     name,
    65  		Series:   seriesToUse,
    66  		Revision: ch.Revision(),
    67  	}
    68  	return ch, url, nil
    69  }
    70  
    71  func charmBase(b base.Base, force bool, cm charm.CharmMeta) (base.Base, error) {
    72  	if force && !b.Empty() {
    73  		return b, nil
    74  	}
    75  	computedBases, err := ComputedBases(cm)
    76  	logger.Tracef("base %q, %v", b, computedBases)
    77  	if err != nil {
    78  		return base.Base{}, err
    79  	}
    80  	if len(computedBases) == 0 {
    81  		if b.Empty() {
    82  			return base.Base{}, errors.New("base not specified and charm does not define any")
    83  		} else {
    84  			// old style charm with no base in metadata.
    85  			return b, nil
    86  		}
    87  	}
    88  	if !b.Empty() {
    89  		for _, computedBase := range computedBases {
    90  			if b == computedBase {
    91  				return b, nil
    92  			}
    93  		}
    94  		return base.Base{}, NewUnsupportedBaseError(b, computedBases)
    95  	}
    96  	if len(computedBases) > 0 {
    97  		return computedBases[0], nil
    98  	}
    99  	return base.Base{}, errors.Errorf("base not specified and charm does not define any")
   100  }
   101  
   102  func isNotExistsError(err error) bool {
   103  	if os.IsNotExist(errors.Cause(err)) {
   104  		return true
   105  	}
   106  	// On Windows, we get a path error due to a GetFileAttributesEx syscall.
   107  	// To avoid being too proscriptive, we'll simply check for the error
   108  	// type and not any content.
   109  	if _, ok := err.(*os.PathError); ok {
   110  		return true
   111  	}
   112  	return false
   113  }
   114  
   115  func isValidCharmOrBundlePath(path string) bool {
   116  	//Exclude relative paths.
   117  	return strings.HasPrefix(path, ".") || filepath.IsAbs(path)
   118  }
   119  
   120  // CharmNotFound returns an error indicating that the
   121  // charm at the specified URL does not exist.
   122  func CharmNotFound(url string) error {
   123  	return errors.NewNotFound(nil, "charm not found: "+url)
   124  }
   125  
   126  // InvalidPath returns an invalidPathError.
   127  func InvalidPath(path string) error {
   128  	return &invalidPathError{path}
   129  }
   130  
   131  // invalidPathError represents an error indicating that the requested
   132  // charm or bundle path is not valid as a charm or bundle path.
   133  type invalidPathError struct {
   134  	path string
   135  }
   136  
   137  func (e *invalidPathError) Error() string {
   138  	return fmt.Sprintf("path %q can not be a relative path", e.path)
   139  }
   140  
   141  func IsInvalidPathError(err error) bool {
   142  	_, ok := err.(*invalidPathError)
   143  	return ok
   144  }
   145  
   146  // UnsupportedSeriesError represents an error indicating that the requested series
   147  // is not supported by the charm.
   148  type unsupportedSeriesError struct {
   149  	requestedSeries string
   150  	supportedSeries []string
   151  }
   152  
   153  func (e *unsupportedSeriesError) Error() string {
   154  	return fmt.Sprintf(
   155  		"series %q not supported by charm, the charm supported series are: %s",
   156  		e.requestedSeries, strings.Join(e.supportedSeries, ","),
   157  	)
   158  }
   159  
   160  // NewUnsupportedSeriesError returns an error indicating that the requested series
   161  // is not supported by a charm.
   162  func NewUnsupportedSeriesError(requestedSeries string, supportedSeries []string) error {
   163  	return &unsupportedSeriesError{requestedSeries, supportedSeries}
   164  }
   165  
   166  // IsUnsupportedSeriesError returns true if err is an UnsupportedSeriesError.
   167  func IsUnsupportedSeriesError(err error) bool {
   168  	_, ok := err.(*unsupportedSeriesError)
   169  	return ok
   170  }
   171  
   172  // unsupportedBaseError represents an error indicating that the requested base
   173  // is not supported by the charm.
   174  type unsupportedBaseError struct {
   175  	requestedBase  base.Base
   176  	supportedBases []base.Base
   177  }
   178  
   179  func (e *unsupportedBaseError) Error() string {
   180  	return fmt.Sprintf(
   181  		"base %q not supported by charm, the charm supported bases are: %s",
   182  		e.requestedBase.DisplayString(), printBases(e.supportedBases),
   183  	)
   184  }
   185  
   186  // NewUnsupportedBaseError returns an error indicating that the requested series
   187  // is not supported by a charm.
   188  func NewUnsupportedBaseError(requestedBase base.Base, supportedBases []base.Base) error {
   189  	return &unsupportedBaseError{requestedBase, supportedBases}
   190  }
   191  
   192  // IsUnsupportedBaseError returns true if err is an UnsupportedSeriesError.
   193  func IsUnsupportedBaseError(err error) bool {
   194  	_, ok := err.(*unsupportedBaseError)
   195  	return ok
   196  }