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 }