github.com/rigado/snapd@v2.42.5-go-mod+incompatible/store/errors.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package store 21 22 import ( 23 "errors" 24 "fmt" 25 "net/url" 26 "sort" 27 "strings" 28 29 "github.com/snapcore/snapd/snap/channel" 30 "github.com/snapcore/snapd/strutil" 31 ) 32 33 var ( 34 // ErrBadQuery is returned from Find when the query has special characters in strange places. 35 ErrBadQuery = errors.New("bad query") 36 37 // ErrSnapNotFound is returned when a snap can not be found 38 ErrSnapNotFound = errors.New("snap not found") 39 40 // ErrUnauthenticated is returned when authentication is needed to complete the query 41 ErrUnauthenticated = errors.New("you need to log in first") 42 43 // ErrAuthenticationNeeds2fa is returned if the authentication needs 2factor 44 ErrAuthenticationNeeds2fa = errors.New("two factor authentication required") 45 46 // Err2faFailed is returned when 2fa failed (e.g., a bad token was given) 47 Err2faFailed = errors.New("two factor authentication failed") 48 49 // ErrInvalidCredentials is returned on login error 50 // It can also be returned when refreshing the discharge 51 // macaroon if the user has changed their password. 52 ErrInvalidCredentials = errors.New("invalid credentials") 53 54 // ErrTOSNotAccepted is returned when the user has not accepted the store's terms of service. 55 ErrTOSNotAccepted = errors.New("terms of service not accepted") 56 57 // ErrNoPaymentMethods is returned when the user has no valid payment methods associated with their account. 58 ErrNoPaymentMethods = errors.New("no payment methods") 59 60 // ErrPaymentDeclined is returned when the user's payment method was declined by the upstream payment provider. 61 ErrPaymentDeclined = errors.New("payment declined") 62 63 // ErrLocalSnap is returned when an operation that only applies to snaps that come from a store was attempted on a local snap. 64 ErrLocalSnap = errors.New("cannot perform operation on local snap") 65 66 // ErrNoUpdateAvailable is returned when an update is attempetd for a snap that has no update available. 67 ErrNoUpdateAvailable = errors.New("snap has no updates available") 68 ) 69 70 // RevisionNotAvailableError is returned when an install is attempted for a snap but the/a revision is not available (given install constraints). 71 type RevisionNotAvailableError struct { 72 Action string 73 Channel string 74 Releases []channel.Channel 75 } 76 77 func (e *RevisionNotAvailableError) Error() string { 78 return "no snap revision available as specified" 79 } 80 81 // DownloadError represents a download error 82 type DownloadError struct { 83 Code int 84 URL *url.URL 85 } 86 87 func (e *DownloadError) Error() string { 88 return fmt.Sprintf("received an unexpected http response code (%v) when trying to download %s", e.Code, e.URL) 89 } 90 91 // PasswordPolicyError is returned in a few corner cases, most notably 92 // when the password has been force-reset. 93 type PasswordPolicyError map[string]stringList 94 95 func (e PasswordPolicyError) Error() string { 96 var msg string 97 98 if reason, ok := e["reason"]; ok && len(reason) == 1 { 99 msg = reason[0] 100 if location, ok := e["location"]; ok && len(location) == 1 { 101 msg += "\nTo address this, go to: " + location[0] + "\n" 102 } 103 } else { 104 for k, vs := range e { 105 msg += fmt.Sprintf("%s: %s\n", k, strings.Join(vs, " ")) 106 } 107 } 108 109 return msg 110 } 111 112 // InvalidAuthDataError signals that the authentication data didn't pass validation. 113 type InvalidAuthDataError map[string]stringList 114 115 func (e InvalidAuthDataError) Error() string { 116 var es []string 117 for _, v := range e { 118 es = append(es, v...) 119 } 120 // XXX: confirm with server people that extra args are all 121 // full sentences (with periods and capitalization) 122 // (empirically this checks out) 123 return strings.Join(es, " ") 124 } 125 126 // SnapActionError conveys errors that were reported on otherwise overall successful snap action (install/refresh) request. 127 type SnapActionError struct { 128 // NoResults is set if the there were no results in the response 129 NoResults bool 130 // Refresh errors by snap name. 131 Refresh map[string]error 132 // Install errors by snap name. 133 Install map[string]error 134 // Download errors by snap name. 135 Download map[string]error 136 // Other errors. 137 Other []error 138 } 139 140 func (e SnapActionError) Error() string { 141 nRefresh := len(e.Refresh) 142 nInstall := len(e.Install) 143 nDownload := len(e.Download) 144 nOther := len(e.Other) 145 146 // single error 147 switch nRefresh + nInstall + nDownload + nOther { 148 case 0: 149 if e.NoResults { 150 // this is an atypical result 151 return "no install/refresh information results from the store" 152 } 153 case 1: 154 if nOther == 0 { 155 var op string 156 var errs map[string]error 157 switch { 158 case nRefresh > 0: 159 op = "refresh" 160 errs = e.Refresh 161 case nInstall > 0: 162 op = "install" 163 errs = e.Install 164 case nDownload > 0: 165 op = "download" 166 errs = e.Download 167 } 168 for name, e := range errs { 169 return fmt.Sprintf("cannot %s snap %q: %v", op, name, e) 170 } 171 } else { 172 return fmt.Sprintf("cannot refresh, install, or download: %v", e.Other[0]) 173 } 174 } 175 176 header := "cannot refresh, install, or download:" 177 if nOther == 0 { 178 // at least one of nDownload, nInstall, or nRefresh is > 0 179 switch { 180 case nDownload == 0 && nRefresh == 0: 181 header = "cannot install:" 182 case nDownload == 0 && nInstall == 0: 183 header = "cannot refresh:" 184 case nRefresh == 0 && nInstall == 0: 185 header = "cannot download:" 186 case nDownload == 0: 187 header = "cannot refresh or install:" 188 case nInstall == 0: 189 header = "cannot refresh or download:" 190 case nRefresh == 0: 191 header = "cannot install or download:" 192 } 193 } 194 195 // reverse the "snap->error" map to "error->snap", as the 196 // common case is that all snaps fail with the same error 197 // (e.g. "no refresh available") 198 errToSnaps := map[string][]string{} 199 errKeys := []string{} // poorman's ordered map 200 201 for _, m := range []map[string]error{e.Refresh, e.Install, e.Download} { 202 for snapName, err := range m { 203 k := err.Error() 204 v, ok := errToSnaps[k] 205 if !ok { 206 errKeys = append(errKeys, k) 207 } 208 errToSnaps[k] = append(v, snapName) 209 } 210 } 211 212 es := make([]string, 1, 1+len(errToSnaps)+nOther) 213 es[0] = header 214 for _, k := range errKeys { 215 sort.Strings(errToSnaps[k]) 216 es = append(es, fmt.Sprintf("%s: %s", k, strutil.Quoted(errToSnaps[k]))) 217 } 218 219 for _, e := range e.Other { 220 es = append(es, e.Error()) 221 } 222 223 if len(es) == 2 { 224 // header + 1 reason 225 return strings.Join(es, " ") 226 } 227 228 return strings.Join(es, "\n") 229 } 230 231 // Authorization soft-expiry errors that get handled automatically. 232 var ( 233 errUserAuthorizationNeedsRefresh = errors.New("soft-expired user authorization needs refresh") 234 errDeviceAuthorizationNeedsRefresh = errors.New("soft-expired device authorization needs refresh") 235 ) 236 237 func translateSnapActionError(action, snapChannel, code, message string, releases []snapRelease) error { 238 switch code { 239 case "revision-not-found": 240 e := &RevisionNotAvailableError{ 241 Action: action, 242 Channel: snapChannel, 243 } 244 if len(releases) != 0 { 245 parsedReleases := make([]channel.Channel, len(releases)) 246 for i := 0; i < len(releases); i++ { 247 var err error 248 parsedReleases[i], err = channel.Parse(releases[i].Channel, releases[i].Architecture) 249 if err != nil { 250 // shouldn't happen, return error without Releases 251 return e 252 } 253 } 254 e.Releases = parsedReleases 255 } 256 return e 257 case "id-not-found", "name-not-found": 258 return ErrSnapNotFound 259 case "user-authorization-needs-refresh": 260 return errUserAuthorizationNeedsRefresh 261 case "device-authorization-needs-refresh": 262 return errDeviceAuthorizationNeedsRefresh 263 default: 264 return fmt.Errorf("%v", message) 265 } 266 }