~bigbes/core-go

20be483e4e7e137d820dc4d33049a9d851fa1b64 — Conrad Hoffmann 9 months ago e22c3e1
Don't uppercase error messages
M auth/bearer.go => auth/bearer.go +1 -1
@@ 141,7 141,7 @@ func (g *Grants) Has(grant string, mode string) bool {
	}

	if mode != RO && mode != RW {
		panic(fmt.Errorf("Invalid access mode"))
		panic(fmt.Errorf("invalid access mode"))
	}
	if g.ReadOnly && mode == RW {
		return false

M auth/middleware.go => auth/middleware.go +9 -9
@@ 96,7 96,7 @@ func (authctx *AuthContext) Access(scope, kind string) error {
		}
		return nil
	default:
		panic(fmt.Errorf("Unknown auth method %q for access check", authctx.AuthMethod))
		panic(fmt.Errorf("unknown auth method %q for access check", authctx.AuthMethod))
	}
}



@@ 169,7 169,7 @@ func authForOAuthClient(ctx context.Context, clientUUID string) (*AuthContext, e
			if err := rows.Err(); err != nil {
				panic(err)
			}
			panic(fmt.Errorf("Multiple matching user accounts; invariant broken"))
			panic(fmt.Errorf("multiple matching user accounts; invariant broken"))
		}
		return nil
	}); err != nil {


@@ 178,7 178,7 @@ func authForOAuthClient(ctx context.Context, clientUUID string) (*AuthContext, e

	if auth.UserType == USER_TYPE_SUSPENDED {
		return nil, fmt.Errorf(
			"Account suspended with the following notice: %s\nContact support",
			"account suspended with the following notice: %s\nContact support",
			*auth.SuspensionNotice)
	}



@@ 245,7 245,7 @@ func internalAuth(payload []byte, w http.ResponseWriter, r *http.Request, next h
	}
	ip := net.ParseIP(host)
	if ip == nil {
		panic(fmt.Errorf("Unable to parse remote address"))
		panic(fmt.Errorf("unable to parse remote address"))
	}
	if !config.IsInternalIP(ip) {
		authError(w, fmt.Sprintf("Invalid source IP %s for internal auth", ip), http.StatusUnauthorized)


@@ 316,7 316,7 @@ func internalAuth(payload []byte, w http.ResponseWriter, r *http.Request, next h

func FetchMetaProfile(ctx context.Context, username string, user *AuthContext) error {
	if config.ServiceName(ctx) == "meta.sr.ht" {
		panic(fmt.Errorf("Cannot fetch profile from ourselves"))
		panic(fmt.Errorf("cannot fetch profile from ourselves"))
	}

	query := client.GraphQLQuery{


@@ 393,7 393,7 @@ func FetchMetaProfile(ctx context.Context, username string, user *AuthContext) e
			&user.Username, &user.Email, &user.UserType, &user.URL,
			&user.Location, &user.Bio, &user.SuspensionNotice); err != nil {
			if err == sql.ErrNoRows {
				panic(fmt.Errorf("Failed to upsert user record from meta.sr.ht"))
				panic(fmt.Errorf("failed to upsert user record from meta.sr.ht"))
			}
			return err
		}


@@ 460,7 460,7 @@ func LookupUser(ctx context.Context, username string, user *AuthContext) error {
			if err = rows.Err(); err != nil {
				return err
			}
			panic(fmt.Errorf("Multiple users of the same username; invariant broken"))
			panic(fmt.Errorf("multiple users of the same username; invariant broken"))
		}
		return nil
	})


@@ 573,7 573,7 @@ func WebhookAuth(ctx context.Context, auth *AuthContext,
	tokenHash [64]byte, rawGrants string, clientID *string,
	expires *time.Time) (context.Context, error) {
	if expires != nil && time.Now().UTC().After(*expires) {
		return nil, fmt.Errorf("The authentication token used to create this webhook has expired")
		return nil, fmt.Errorf("the authentication token used to create this webhook has expired")
	}

	grants, err := DecodeGrants(ctx, rawGrants)


@@ 643,7 643,7 @@ func Middleware(conf ini.File, apiconf string) func(http.Handler) http.Handler {
func ForContext(ctx context.Context) *AuthContext {
	raw, ok := ctx.Value(userCtxKey).(*AuthContext)
	if !ok {
		panic(fmt.Errorf("Invalid authentication context"))
		panic(fmt.Errorf("invalid authentication context"))
	}
	return raw
}

M config/config.go => config/config.go +1 -1
@@ 132,7 132,7 @@ func GetAPI(conf ini.File, svc string, external bool) string {
		}
	}

	panic(fmt.Errorf("No suitable origin configured for requested API"))
	panic(fmt.Errorf("no suitable origin configured for requested API"))
}

const DefaultQueueSize = 512

M config/middleware.go => config/middleware.go +2 -2
@@ 35,7 35,7 @@ func Context(ctx context.Context, conf ini.File, service string) context.Context
func ForContext(ctx context.Context) ini.File {
	raw, ok := ctx.Value(configCtxKey).(ini.File)
	if !ok {
		panic(errors.New("Invalid config context"))
		panic(errors.New("invalid config context"))
	}
	return raw
}


@@ 43,7 43,7 @@ func ForContext(ctx context.Context) ini.File {
func ServiceName(ctx context.Context) string {
	raw, ok := ctx.Value(serviceCtxKey).(*string)
	if !ok {
		panic(errors.New("Invalid service config context"))
		panic(errors.New("invalid service config context"))
	}
	return *raw
}

M crypto/crypto.go => crypto/crypto.go +2 -2
@@ 71,7 71,7 @@ func DecryptWithoutExpiration(payload []byte) []byte {

func DecryptWithExpiration(payload []byte, expiry time.Duration) []byte {
	if expiry == 0 {
		panic(fmt.Errorf("DecryptWithExpiration given expiration of zero. Use DecryptWithoutExpiration if you really meant it."))
		panic(fmt.Errorf("DecryptWithExpiration given expiration of zero - use DecryptWithoutExpiration if you really meant it"))
	}
	return fernet.VerifyAndDecrypt(payload, expiry, []*fernet.Key{fernetKey})
}


@@ 95,7 95,7 @@ func SignWebhook(payload []byte) (string, string) {
	var nonceSeed [8]byte
	_, err := rand.Read(nonceSeed[:])
	if err != nil {
		panic(fmt.Errorf("Failed to generate nonce: %w", err))
		panic(fmt.Errorf("failed to generate nonce: %w", err))
	}

	nonce := hex.EncodeToString(nonceSeed[:])

M database/middleware.go => database/middleware.go +3 -3
@@ 32,7 32,7 @@ func Context(ctx context.Context, db *sql.DB) context.Context {
func ForContext(ctx context.Context) (*sql.Conn, error) {
	raw, ok := ctx.Value(dbCtxKey).(*sql.DB)
	if !ok {
		panic(errors.New("Invalid database context"))
		panic(errors.New("invalid database context"))
	}
	return raw.Conn(ctx)
}


@@ 40,7 40,7 @@ func ForContext(ctx context.Context) (*sql.Conn, error) {
func DBForContext(ctx context.Context) *sql.DB {
	raw, ok := ctx.Value(dbCtxKey).(*sql.DB)
	if !ok {
		panic(errors.New("Invalid database context"))
		panic(errors.New("invalid database context"))
	}
	return raw
}


@@ 72,7 72,7 @@ func WithTx(ctx context.Context, opts *sql.TxOptions, fn func(tx *sql.Tx) error)
	}
	if txErr != nil && txErr != sql.ErrTxDone {
		if err != nil {
			panic(fmt.Errorf("Transaction error: %v\nClosure error: %v", txErr, err))
			panic(fmt.Errorf("transaction error: %v\nClosure error: %v", txErr, err))
		} else {
			panic(txErr)
		}

M database/sq.go => database/sq.go +2 -2
@@ 100,7 100,7 @@ func Select(ctx context.Context, cols ...any) sq.SelectBuilder {
				q = q.Columns(Columns(ctx, col)...)
			}
		default:
			panic(fmt.Errorf("Unknown selectable type %T", col))
			panic(fmt.Errorf("unknown selectable type %T", col))
		}
	}
	return q


@@ 111,7 111,7 @@ func SelectAll(m Model) sq.SelectBuilder {
	mf.buildCache()
	var cols []string
	for col, fields := range mf.bySQL {
		for _, _ = range fields {
		for range fields {
			cols = append(cols, WithAlias(m.Alias(), col))
		}
	}

M email/send.go => email/send.go +7 -7
@@ 37,14 37,14 @@ func mailSetup(ctx context.Context) (*smtp.Client, *mail.Address, error) {
	}
	mailconf.port, err = strconv.Atoi(portStr)
	if err != nil {
		panic(fmt.Errorf("Unable to parse [mail]smtp-port (must be integer)"))
		panic(fmt.Errorf("unable to parse [mail]smtp-port (must be integer)"))
	}

	if mailconf.host, ok = conf.Get("mail", "smtp-host"); !ok {
		panic(fmt.Errorf("Missing SMTP configuration options [smtp-host]"))
		panic(fmt.Errorf("missing SMTP configuration options [smtp-host]"))
	}
	if mailconf.from, ok = conf.Get("mail", "smtp-from"); !ok {
		panic(fmt.Errorf("Missing SMTP configuration options [smtp-from]"))
		panic(fmt.Errorf("missing SMTP configuration options [smtp-from]"))
	}

	sender, err := mail.ParseAddress(mailconf.from)


@@ 57,7 57,7 @@ func mailSetup(ctx context.Context) (*smtp.Client, *mail.Address, error) {
		case "starttls", "tls", "insecure":
			mailconf.enctype = enctype
		default:
			panic(fmt.Errorf("Invalid SMTP configuration value for [smtp-encryption]"))
			panic(fmt.Errorf("invalid SMTP configuration value for [smtp-encryption]"))
		}
	}



@@ 66,15 66,15 @@ func mailSetup(ctx context.Context) (*smtp.Client, *mail.Address, error) {
		case "none", "plain":
			mailconf.authtype = authtype
		default:
			panic(fmt.Errorf("Invalid SMTP configuration value for [smtp-auth]"))
			panic(fmt.Errorf("invalid SMTP configuration value for [smtp-auth]"))
		}
	}
	if mailconf.authtype == "plain" {
		if mailconf.user, ok = conf.Get("mail", "smtp-user"); !ok {
			panic(fmt.Errorf("Missing SMTP configuration options [smtp-user]"))
			panic(fmt.Errorf("missing SMTP configuration options [smtp-user]"))
		}
		if mailconf.pass, ok = conf.Get("mail", "smtp-password"); !ok {
			panic(fmt.Errorf("Missing SMTP configuration options [smtp-password]"))
			panic(fmt.Errorf("missing SMTP configuration options [smtp-password]"))
		}
	}


M email/worker.go => email/worker.go +11 -11
@@ 68,7 68,7 @@ func prepareEncrypted(rcptKey *string, header mail.Header,
		return nil, err
	}
	if len(keyring) != 1 {
		return nil, errors.New("Expected user PGP key to contain one key")
		return nil, errors.New("expected user PGP key to contain one key")
	}
	rcptEntity := keyring[0]



@@ 77,7 77,7 @@ func prepareEncrypted(rcptKey *string, header mail.Header,
	// occur once it is written to.
	_, ok := rcptEntity.EncryptionKey(time.Now())
	if !ok {
		return nil, fmt.Errorf("No valid encryption key found (expired?)")
		return nil, fmt.Errorf("no valid encryption key found (expired?)")
	}

	return pgpmail.Encrypt(buf, header.Header.Header,


@@ 222,19 222,19 @@ func (q *Queue) CanPGPSign() bool {
func NewQueue(conf ini.File) *Queue {
	smtpFrom, ok := conf.Get("mail", "smtp-from")
	if !ok {
		panic(fmt.Errorf("Expected [mail]smtp-from in config"))
		panic(fmt.Errorf("expected [mail]smtp-from in config"))
	}
	ownerName, ok := conf.Get("sr.ht", "owner-name")
	if !ok {
		panic(fmt.Errorf("Expected [sr.ht]owner-name in config"))
		panic(fmt.Errorf("expected [sr.ht]owner-name in config"))
	}
	ownerEmail, ok := conf.Get("sr.ht", "owner-email")
	if !ok {
		panic(fmt.Errorf("Expected [sr.ht]owner-email in config"))
		panic(fmt.Errorf("expected [sr.ht]owner-email in config"))
	}
	addr, err := mail.ParseAddress(smtpFrom)
	if err != nil {
		panic(fmt.Errorf("Invalid [mail]smtp-from: %s", err.Error()))
		panic(fmt.Errorf("invalid [mail]smtp-from: %s", err.Error()))
	}
	ownerAddr := &mail.Address{
		Name:    ownerName,


@@ 245,20 245,20 @@ func NewQueue(conf ini.File) *Queue {
	if privKeyPath, ok := conf.Get("mail", "pgp-privkey"); ok {
		privKeyFile, err := os.Open(privKeyPath)
		if err != nil {
			panic(fmt.Errorf("Failed to open [mail]pgp-privkey: %v", err))
			panic(fmt.Errorf("failed to open [mail]pgp-privkey: %v", err))
		}
		defer privKeyFile.Close()

		keyring, err := openpgp.ReadArmoredKeyRing(privKeyFile)
		if err != nil {
			panic(fmt.Errorf("Failed to read PGP key ring from [mail]pgp-privkey: %v", err))
			panic(fmt.Errorf("failed to read PGP key ring from [mail]pgp-privkey: %v", err))
		}
		if len(keyring) != 1 {
			panic(fmt.Errorf("Expected [mail]pgp-privkey to contain one key"))
			panic(fmt.Errorf("expected [mail]pgp-privkey to contain one key"))
		}
		entity = keyring[0]
		if entity.PrivateKey == nil || entity.PrivateKey.Encrypted {
			panic(fmt.Errorf("Failed to load [mail]pgp-privkey for email signature"))
			panic(fmt.Errorf("failed to load [mail]pgp-privkey for email signature"))
		}
	}



@@ 276,7 276,7 @@ func NewQueue(conf ini.File) *Queue {
func ForContext(ctx context.Context) *workerContext {
	q, ok := ctx.Value(emailCtxKey).(*workerContext)
	if !ok {
		panic(errors.New("No email worker for this context"))
		panic(errors.New("no email worker for this context"))
	}
	return q
}

M model/cursor.go => model/cursor.go +1 -1
@@ 22,7 22,7 @@ func (cur *Cursor) UnmarshalGQL(v any) error {
	}
	plain := crypto.DecryptWithoutExpiration([]byte(enc))
	if plain == nil {
		return fmt.Errorf("Invalid cursor")
		return fmt.Errorf("invalid cursor")
	}
	err := json.Unmarshal(plain, cur)
	if err != nil {

M objects/middleware.go => objects/middleware.go +1 -1
@@ 38,7 38,7 @@ func Context(ctx context.Context, client *s3.Client) context.Context {
func ForContext(ctx context.Context) *s3.Client {
	raw, ok := ctx.Value(s3CtxKey).(*s3.Client)
	if !ok {
		panic(fmt.Errorf("Invalid S3 context"))
		panic(fmt.Errorf("invalid S3 context"))
	}
	return raw
}

M redis/middleware.go => redis/middleware.go +1 -1
@@ 31,7 31,7 @@ func Context(ctx context.Context, client redis.UniversalClient) context.Context 
func ForContext(ctx context.Context) redis.UniversalClient {
	raw, ok := ctx.Value(redisCtxKey).(redis.UniversalClient)
	if !ok {
		panic(fmt.Errorf("Invalid redis context"))
		panic(fmt.Errorf("invalid redis context"))
	}
	return raw
}

M server/directives.go => server/directives.go +5 -5
@@ 13,7 13,7 @@ func Admin(ctx context.Context, obj any,
	next graphql.Resolver) (any, error) {

	if auth.ForContext(ctx).UserType != auth.USER_TYPE_ADMIN {
		return nil, fmt.Errorf("Access denied")
		return nil, fmt.Errorf("access denied")
	}

	return next(ctx)


@@ 23,7 23,7 @@ func AnonInternal(ctx context.Context, obj any,
	next graphql.Resolver) (any, error) {

	if auth.ForContext(ctx).AuthMethod != auth.AUTH_ANON_INTERNAL {
		return nil, fmt.Errorf("Anonymous internal auth access denied")
		return nil, fmt.Errorf("anonymous internal auth access denied")
	}

	return next(ctx)


@@ 33,7 33,7 @@ func Internal(ctx context.Context, obj any,
	next graphql.Resolver) (any, error) {

	if auth.ForContext(ctx).AuthMethod != auth.AUTH_INTERNAL {
		return nil, fmt.Errorf("Internal auth access denied")
		return nil, fmt.Errorf("internal auth access denied")
	}

	return next(ctx)


@@ 48,12 48,12 @@ func Private(ctx context.Context, obj any,
		return next(ctx)
	case auth.AUTH_OAUTH2:
		if user.BearerToken.ClientID != "" {
			return nil, fmt.Errorf("Private auth access denied")
			return nil, fmt.Errorf("private auth access denied")
		}
		return next(ctx)
	}

	return nil, fmt.Errorf("Private auth access denied")
	return nil, fmt.Errorf("private auth access denied")
}

func Access(ctx context.Context, obj any, next graphql.Resolver,

M server/server.go => server/server.go +3 -3
@@ 294,7 294,7 @@ func (server *Server) WithDefaultMiddleware() *Server {
			if net.ParseIP(addr) == nil {
				addr, _, err = net.SplitHostPort(addr)
				if err != nil {
					panic(fmt.Errorf("Invalid remote address: %s", r.RemoteAddr))
					panic(fmt.Errorf("invalid remote address: %s", r.RemoteAddr))
				}
			}
			ctx := context.WithValue(r.Context(), serverCtxKey, server)


@@ 312,7 312,7 @@ func (server *Server) WithDefaultMiddleware() *Server {
func RemoteAddr(ctx context.Context) string {
	raw, ok := ctx.Value(remoteAddrCtxKey).(string)
	if !ok {
		panic(fmt.Errorf("Invalid authentication context"))
		panic(fmt.Errorf("invalid authentication context"))
	}
	return raw
}


@@ 320,7 320,7 @@ func RemoteAddr(ctx context.Context) string {
func ForContext(ctx context.Context) *Server {
	raw, ok := ctx.Value(serverCtxKey).(*Server)
	if !ok {
		panic(fmt.Errorf("Invalid server context"))
		panic(fmt.Errorf("invalid server context"))
	}
	return raw
}

M valid/valid.go => valid/valid.go +4 -4
@@ 64,7 64,7 @@ func (valid *Validation) Ok() bool {
// validation with.
func (valid *Validation) Optional(name string, fn func(i any)) {
	if valid.input == nil {
		panic(fmt.Errorf("Attempted to validate fields without input"))
		panic(fmt.Errorf("attempted to validate fields without input"))
	}
	if o, ok := valid.input[name]; ok {
		if o == nil {


@@ 80,7 80,7 @@ func (valid *Validation) Optional(name string, fn func(i any)) {
// called with the string for the user to conduct further validation with.
func (valid *Validation) OptionalString(name string, fn func(s string)) {
	if valid.input == nil {
		panic(fmt.Errorf("Attempted to validate fields without input"))
		panic(fmt.Errorf("attempted to validate fields without input"))
	}
	if o, ok := valid.input[name]; ok {
		if o == nil {


@@ 109,7 109,7 @@ func (valid *Validation) OptionalString(name string, fn func(s string)) {
// validation with.
func (valid *Validation) NullableString(name string, fn func(s *string)) {
	if valid.input == nil {
		panic(fmt.Errorf("Attempted to validate fields without input"))
		panic(fmt.Errorf("attempted to validate fields without input"))
	}
	if o, ok := valid.input[name]; ok {
		var val *string


@@ 136,7 136,7 @@ func (valid *Validation) NullableString(name string, fn func(s *string)) {
// called with the boolean for the user to conduct further validation with.
func (valid *Validation) OptionalBool(name string, fn func(b bool)) {
	if valid.input == nil {
		panic(fmt.Errorf("Attempted to validate fields without input"))
		panic(fmt.Errorf("attempted to validate fields without input"))
	}
	if o, ok := valid.input[name]; ok {
		if o == nil {

M webhooks/config.go => webhooks/config.go +3 -3
@@ 50,7 50,7 @@ func NewAuthConfig(ctx context.Context) (AuthConfig, error) {
		}, nil
	case auth.AUTH_COOKIE:
		// TODO: Should this work?
		return AuthConfig{}, fmt.Errorf("Native webhooks are not supported with web authentication")
		return AuthConfig{}, fmt.Errorf("native webhooks are not supported with web authentication")
	case auth.AUTH_INTERNAL:
		nodeID := config.ServiceName(ctx)
		return AuthConfig{


@@ 58,9 58,9 @@ func NewAuthConfig(ctx context.Context) (AuthConfig, error) {
			NodeID:     &nodeID,
		}, nil
	case auth.AUTH_WEBHOOK:
		panic(fmt.Errorf("Recursive webhook auth is not supported"))
		panic(fmt.Errorf("recursive webhook auth is not supported"))
	}
	panic(fmt.Errorf("Unreachable"))
	panic(fmt.Errorf("unreachable"))
}

// Returns an SQL expression to filter webhooks for the authenticated user.

M webhooks/context.go => webhooks/context.go +3 -3
@@ 40,7 40,7 @@ func Context(ctx context.Context, payload any) context.Context {
func Payload(ctx context.Context) (any, error) {
	payload := ctx.Value(payloadContextKey)
	if payload == nil {
		return nil, errors.New("Cannot use this resolver without an active webhook context")
		return nil, errors.New("cannot use this resolver without an active webhook context")
	}
	return payload, nil
}


@@ 81,7 81,7 @@ func (webhook *WebhookContext) Exec(ctx context.Context,
			panic(err)
		}
	default:
		panic(fmt.Errorf("Unsupported authentication context for webhook"))
		panic(fmt.Errorf("unsupported authentication context for webhook"))
	}

	exec := executor.New(schema)


@@ 134,7 134,7 @@ func Validate(schema graphql.ExecutableSchema, query string) error {
	ctx := graphql.StartOperationTrace(context.TODO())
	_, errors := exec.CreateOperationContext(ctx, &params)
	if errors != nil {
		return fmt.Errorf("Error validating webhook query: %s", errors.Error())
		return fmt.Errorf("error validating webhook query: %s", errors.Error())
	}
	return nil
}

M webhooks/legacy.go => webhooks/legacy.go +2 -2
@@ 228,7 228,7 @@ func deliverPayload(ctx context.Context, name, url string,
	reader := io.LimitReader(resp.Body, 65536) // No more than 64 KiB
	body, err := io.ReadAll(reader)
	if err != nil {
		return fmt.Errorf("Error reading response body: %v: %w",
		return fmt.Errorf("error reading response body: %v: %w",
			err, work.ErrDoNotReattempt)
	}



@@ 255,7 255,7 @@ func deliverPayload(ctx context.Context, name, url string,
		resp.StatusCode == http.StatusServiceUnavailable ||
		resp.StatusCode == http.StatusGatewayTimeout {
		// Retry
		return fmt.Errorf("Server returned status %d: %s",
		return fmt.Errorf("server returned status %d: %s",
			resp.StatusCode, resp.Status)
	}


M webhooks/middleware.go => webhooks/middleware.go +2 -2
@@ 21,7 21,7 @@ func Middleware(queue *WebhookQueue) func(next http.Handler) http.Handler {
func ForContext(ctx context.Context) *WebhookQueue {
	queue, ok := ctx.Value(ctxKey).(*WebhookQueue)
	if !ok {
		panic(fmt.Errorf("No webhook queue for this context"))
		panic(fmt.Errorf("no webhook queue for this context"))
	}
	return queue
}


@@ 41,7 41,7 @@ func LegacyMiddleware(queue *LegacyQueue) func(next http.Handler) http.Handler {
func LegacyForContext(ctx context.Context) *LegacyQueue {
	queue, ok := ctx.Value(legacyCtxKey).(*LegacyQueue)
	if !ok {
		panic(fmt.Errorf("No legacy webhook queue for this context"))
		panic(fmt.Errorf("no legacy webhook queue for this context"))
	}
	return queue
}

M webhooks/queue.go => webhooks/queue.go +2 -2
@@ 242,7 242,7 @@ func (queue *WebhookQueue) deliverPayload(ctx context.Context,
	reader := io.LimitReader(resp.Body, 262144) // No more than 256 KiB
	body, err := io.ReadAll(reader)
	if err != nil {
		return fmt.Errorf("Error reading response body: %v: %w",
		return fmt.Errorf("error reading response body: %v: %w",
			err, work.ErrDoNotReattempt)
	}



@@ 267,7 267,7 @@ func (queue *WebhookQueue) deliverPayload(ctx context.Context,
		resp.StatusCode == http.StatusServiceUnavailable ||
		resp.StatusCode == http.StatusGatewayTimeout {
		// Retry
		return fmt.Errorf("Server returned status %d: %s",
		return fmt.Errorf("server returned status %d: %s",
			resp.StatusCode, resp.Status)
	}