Merge pull request #831 from stevvooe/add-driver-health-check
Provide simple storage driver health check
This commit is contained in:
commit
528442c015
15 changed files with 202 additions and 72 deletions
|
@ -50,7 +50,10 @@ func main() {
|
||||||
ErrorDescriptors []errcode.ErrorDescriptor
|
ErrorDescriptors []errcode.ErrorDescriptor
|
||||||
}{
|
}{
|
||||||
RouteDescriptors: v2.APIDescriptor.RouteDescriptors,
|
RouteDescriptors: v2.APIDescriptor.RouteDescriptors,
|
||||||
ErrorDescriptors: errcode.GetErrorCodeGroup("registry.api.v2"),
|
ErrorDescriptors: append(errcode.GetErrorCodeGroup("registry.api.v2"),
|
||||||
|
// The following are part of the specification but provided by errcode default.
|
||||||
|
errcode.ErrorCodeUnauthorized.Descriptor(),
|
||||||
|
errcode.ErrorCodeUnsupported.Descriptor()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tmpl.Execute(os.Stdout, data); err != nil {
|
if err := tmpl.Execute(os.Stdout, data); err != nil {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"github.com/bugsnag/bugsnag-go"
|
"github.com/bugsnag/bugsnag-go"
|
||||||
"github.com/docker/distribution/configuration"
|
"github.com/docker/distribution/configuration"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
_ "github.com/docker/distribution/health"
|
"github.com/docker/distribution/health"
|
||||||
_ "github.com/docker/distribution/registry/auth/htpasswd"
|
_ "github.com/docker/distribution/registry/auth/htpasswd"
|
||||||
_ "github.com/docker/distribution/registry/auth/silly"
|
_ "github.com/docker/distribution/registry/auth/silly"
|
||||||
_ "github.com/docker/distribution/registry/auth/token"
|
_ "github.com/docker/distribution/registry/auth/token"
|
||||||
|
@ -70,8 +70,10 @@ func main() {
|
||||||
uuid.Loggerf = context.GetLogger(ctx).Warnf
|
uuid.Loggerf = context.GetLogger(ctx).Warnf
|
||||||
|
|
||||||
app := handlers.NewApp(ctx, *config)
|
app := handlers.NewApp(ctx, *config)
|
||||||
|
app.RegisterHealthChecks()
|
||||||
handler := configureReporting(app)
|
handler := configureReporting(app)
|
||||||
handler = panicHandler(handler)
|
handler = panicHandler(handler)
|
||||||
|
handler = health.Handler(handler)
|
||||||
handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler)
|
handler = gorhandlers.CombinedLoggingHandler(os.Stdout, handler)
|
||||||
|
|
||||||
if config.HTTP.Debug.Addr != "" {
|
if config.HTTP.Debug.Addr != "" {
|
||||||
|
|
|
@ -2249,7 +2249,7 @@ The following headers will be returned with the response:
|
||||||
|
|
||||||
|Name|Description|
|
|Name|Description|
|
||||||
|----|-----------|
|
|----|-----------|
|
||||||
|`Content-Length`|Zero|
|
|`Content-Length`|0|
|
||||||
|`Docker-Content-Digest`|Digest of the targeted content for the request.|
|
|`Docker-Content-Digest`|Digest of the targeted content for the request.|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,13 @@ package health
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -140,7 +144,7 @@ func PeriodicThresholdChecker(check Checker, period time.Duration, threshold int
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckStatus returns a map with all the current health check errors
|
// CheckStatus returns a map with all the current health check errors
|
||||||
func CheckStatus() map[string]string {
|
func CheckStatus() map[string]string { // TODO(stevvooe) this needs a proper type
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
defer mutex.RUnlock()
|
defer mutex.RUnlock()
|
||||||
statusKeys := make(map[string]string)
|
statusKeys := make(map[string]string)
|
||||||
|
@ -174,13 +178,13 @@ func RegisterFunc(name string, check func() error) {
|
||||||
|
|
||||||
// RegisterPeriodicFunc allows the convenience of registering a PeriodicChecker
|
// RegisterPeriodicFunc allows the convenience of registering a PeriodicChecker
|
||||||
// from an arbitrary func() error
|
// from an arbitrary func() error
|
||||||
func RegisterPeriodicFunc(name string, check func() error, period time.Duration) {
|
func RegisterPeriodicFunc(name string, period time.Duration, check CheckFunc) {
|
||||||
Register(name, PeriodicChecker(CheckFunc(check), period))
|
Register(name, PeriodicChecker(CheckFunc(check), period))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterPeriodicThresholdFunc allows the convenience of registering a
|
// RegisterPeriodicThresholdFunc allows the convenience of registering a
|
||||||
// PeriodicChecker from an arbitrary func() error
|
// PeriodicChecker from an arbitrary func() error
|
||||||
func RegisterPeriodicThresholdFunc(name string, check func() error, period time.Duration, threshold int) {
|
func RegisterPeriodicThresholdFunc(name string, period time.Duration, threshold int, check CheckFunc) {
|
||||||
Register(name, PeriodicThresholdChecker(CheckFunc(check), period, threshold))
|
Register(name, PeriodicThresholdChecker(CheckFunc(check), period, threshold))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,25 +193,61 @@ func RegisterPeriodicThresholdFunc(name string, check func() error, period time.
|
||||||
// Returns 503 if any Error status exists, 200 otherwise
|
// Returns 503 if any Error status exists, 200 otherwise
|
||||||
func StatusHandler(w http.ResponseWriter, r *http.Request) {
|
func StatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == "GET" {
|
if r.Method == "GET" {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
checks := CheckStatus()
|
||||||
checksStatus := CheckStatus()
|
status := http.StatusOK
|
||||||
// If there is an error, return 503
|
|
||||||
if len(checksStatus) != 0 {
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
|
||||||
}
|
|
||||||
encoder := json.NewEncoder(w)
|
|
||||||
err := encoder.Encode(checksStatus)
|
|
||||||
|
|
||||||
// Parsing of the JSON failed. Returning generic error message
|
// If there is an error, return 503
|
||||||
if err != nil {
|
if len(checks) != 0 {
|
||||||
encoder.Encode(struct {
|
status = http.StatusServiceUnavailable
|
||||||
ServerError string `json:"server_error"`
|
|
||||||
}{
|
|
||||||
ServerError: "Could not parse error message",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statusResponse(w, r, status, checks)
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns a handler that will return 503 response code if the health
|
||||||
|
// checks have failed. If everything is okay with the health checks, the
|
||||||
|
// handler will pass through to the provided handler. Use this handler to
|
||||||
|
// disable a web application when the health checks fail.
|
||||||
|
func Handler(handler http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
checks := CheckStatus()
|
||||||
|
if len(checks) != 0 {
|
||||||
|
errcode.ServeJSON(w, errcode.ErrorCodeUnavailable.
|
||||||
|
WithDetail("health check failed: please see /debug/health"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r) // pass through
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// statusResponse completes the request with a response describing the health
|
||||||
|
// of the service.
|
||||||
|
func statusResponse(w http.ResponseWriter, r *http.Request, status int, checks map[string]string) {
|
||||||
|
p, err := json.Marshal(checks)
|
||||||
|
if err != nil {
|
||||||
|
context.GetLogger(context.Background()).Errorf("error serializing health status: %v", err)
|
||||||
|
p, err = json.Marshal(struct {
|
||||||
|
ServerError string `json:"server_error"`
|
||||||
|
}{
|
||||||
|
ServerError: "Could not parse error message",
|
||||||
|
})
|
||||||
|
status = http.StatusInternalServerError
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
context.GetLogger(context.Background()).Errorf("error serializing health status failure message: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.Header().Set("Content-Length", fmt.Sprint(len(p)))
|
||||||
|
w.WriteHeader(status)
|
||||||
|
if _, err := w.Write(p); err != nil {
|
||||||
|
context.GetLogger(context.Background()).Errorf("error writing health status response body: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package health
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -45,3 +46,62 @@ func TestReturns503IfThereAreErrorChecks(t *testing.T) {
|
||||||
t.Errorf("Did not get a 503.")
|
t.Errorf("Did not get a 503.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestHealthHandler ensures that our handler implementation correct protects
|
||||||
|
// the web application when things aren't so healthy.
|
||||||
|
func TestHealthHandler(t *testing.T) {
|
||||||
|
// clear out existing checks.
|
||||||
|
registeredChecks = make(map[string]Checker)
|
||||||
|
|
||||||
|
// protect an http server
|
||||||
|
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}))
|
||||||
|
|
||||||
|
// wrap it in our health handler
|
||||||
|
handler = Handler(handler)
|
||||||
|
|
||||||
|
// use this swap check status
|
||||||
|
updater := NewStatusUpdater()
|
||||||
|
Register("test_check", updater)
|
||||||
|
|
||||||
|
// now, create a test server
|
||||||
|
server := httptest.NewServer(handler)
|
||||||
|
|
||||||
|
checkUp := func(t *testing.T, message string) {
|
||||||
|
resp, err := http.Get(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting success status: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusNoContent {
|
||||||
|
t.Fatalf("unexpected response code from server when %s: %d != %d", message, resp.StatusCode, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
// NOTE(stevvooe): we really don't care about the body -- the format is
|
||||||
|
// not standardized or supported, yet.
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDown := func(t *testing.T, message string) {
|
||||||
|
resp, err := http.Get(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting down status: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusServiceUnavailable {
|
||||||
|
t.Fatalf("unexpected response code from server when %s: %d != %d", message, resp.StatusCode, http.StatusServiceUnavailable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// server should be up
|
||||||
|
checkUp(t, "initial health check")
|
||||||
|
|
||||||
|
// now, we fail the health check
|
||||||
|
updater.Update(fmt.Errorf("the server is now out of commission"))
|
||||||
|
checkDown(t, "server should be down") // should be down
|
||||||
|
|
||||||
|
// bring server back up
|
||||||
|
updater.Update(nil)
|
||||||
|
checkUp(t, "when server is back up") // now we should be back up.
|
||||||
|
}
|
||||||
|
|
|
@ -13,15 +13,45 @@ var (
|
||||||
groupToDescriptors = map[string][]ErrorDescriptor{}
|
groupToDescriptors = map[string][]ErrorDescriptor{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrorCodeUnknown is a generic error that can be used as a last
|
var (
|
||||||
// resort if there is no situation-specific error message that can be used
|
// ErrorCodeUnknown is a generic error that can be used as a last
|
||||||
var ErrorCodeUnknown = Register("errcode", ErrorDescriptor{
|
// resort if there is no situation-specific error message that can be used
|
||||||
Value: "UNKNOWN",
|
ErrorCodeUnknown = Register("errcode", ErrorDescriptor{
|
||||||
Message: "unknown error",
|
Value: "UNKNOWN",
|
||||||
Description: `Generic error returned when the error does not have an
|
Message: "unknown error",
|
||||||
|
Description: `Generic error returned when the error does not have an
|
||||||
API classification.`,
|
API classification.`,
|
||||||
HTTPStatusCode: http.StatusInternalServerError,
|
HTTPStatusCode: http.StatusInternalServerError,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ErrorCodeUnsupported is returned when an operation is not supported.
|
||||||
|
ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "UNSUPPORTED",
|
||||||
|
Message: "The operation is unsupported.",
|
||||||
|
Description: `The operation was unsupported due to a missing
|
||||||
|
implementation or invalid set of parameters.`,
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
})
|
||||||
|
|
||||||
|
// ErrorCodeUnauthorized is returned if a request is not authorized.
|
||||||
|
ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "UNAUTHORIZED",
|
||||||
|
Message: "access to the requested resource is not authorized",
|
||||||
|
Description: `The access controller denied access for the operation on
|
||||||
|
a resource. Often this will be accompanied by a 401 Unauthorized
|
||||||
|
response status.`,
|
||||||
|
HTTPStatusCode: http.StatusUnauthorized,
|
||||||
|
})
|
||||||
|
|
||||||
|
// ErrorCodeUnavailable provides a common error to report unavialability
|
||||||
|
// of a service or endpoint.
|
||||||
|
ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "UNAVAILABLE",
|
||||||
|
Message: "service unavailable",
|
||||||
|
Description: "Returned when a service is not available",
|
||||||
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
var nextCode = 1000
|
var nextCode = 1000
|
||||||
var registerLock sync.Mutex
|
var registerLock sync.Mutex
|
||||||
|
|
|
@ -124,7 +124,7 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
Body: BodyDescriptor{
|
Body: BodyDescriptor{
|
||||||
ContentType: "application/json; charset=utf-8",
|
ContentType: "application/json; charset=utf-8",
|
||||||
|
@ -145,7 +145,7 @@ var (
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
Body: BodyDescriptor{
|
Body: BodyDescriptor{
|
||||||
ContentType: "application/json; charset=utf-8",
|
ContentType: "application/json; charset=utf-8",
|
||||||
|
@ -374,7 +374,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
Format: errorsBody,
|
Format: errorsBody,
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -451,7 +451,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
Format: errorsBody,
|
Format: errorsBody,
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -506,7 +506,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
Format: errorsBody,
|
Format: errorsBody,
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -568,7 +568,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
Format: errorsBody,
|
Format: errorsBody,
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -645,7 +645,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
Format: errorsBody,
|
Format: errorsBody,
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -682,7 +682,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
Body: BodyDescriptor{
|
Body: BodyDescriptor{
|
||||||
ContentType: "application/json; charset=utf-8",
|
ContentType: "application/json; charset=utf-8",
|
||||||
|
@ -737,7 +737,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnauthorized,
|
errcode.ErrorCodeUnauthorized,
|
||||||
},
|
},
|
||||||
Body: BodyDescriptor{
|
Body: BodyDescriptor{
|
||||||
ContentType: "application/json; charset=utf-8",
|
ContentType: "application/json; charset=utf-8",
|
||||||
|
@ -974,7 +974,7 @@ var routeDescriptors = []RouteDescriptor{
|
||||||
Format: errorsBody,
|
Format: errorsBody,
|
||||||
},
|
},
|
||||||
ErrorCodes: []errcode.ErrorCode{
|
ErrorCodes: []errcode.ErrorCode{
|
||||||
ErrorCodeUnsupported,
|
errcode.ErrorCodeUnsupported,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,24 +9,6 @@ import (
|
||||||
const errGroup = "registry.api.v2"
|
const errGroup = "registry.api.v2"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrorCodeUnsupported is returned when an operation is not supported.
|
|
||||||
ErrorCodeUnsupported = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
|
||||||
Value: "UNSUPPORTED",
|
|
||||||
Message: "The operation is unsupported.",
|
|
||||||
Description: `The operation was unsupported due to a missing
|
|
||||||
implementation or invalid set of parameters.`,
|
|
||||||
})
|
|
||||||
|
|
||||||
// ErrorCodeUnauthorized is returned if a request is not authorized.
|
|
||||||
ErrorCodeUnauthorized = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
|
||||||
Value: "UNAUTHORIZED",
|
|
||||||
Message: "access to the requested resource is not authorized",
|
|
||||||
Description: `The access controller denied access for the operation on
|
|
||||||
a resource. Often this will be accompanied by a 401 Unauthorized
|
|
||||||
response status.`,
|
|
||||||
HTTPStatusCode: http.StatusUnauthorized,
|
|
||||||
})
|
|
||||||
|
|
||||||
// ErrorCodeDigestInvalid is returned when uploading a blob if the
|
// ErrorCodeDigestInvalid is returned when uploading a blob if the
|
||||||
// provided digest does not match the blob contents.
|
// provided digest does not match the blob contents.
|
||||||
ErrorCodeDigestInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
ErrorCodeDigestInvalid = errcode.Register(errGroup, errcode.ErrorDescriptor{
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnexpectedHTTPStatusError is returned when an unexpected HTTP status is
|
// UnexpectedHTTPStatusError is returned when an unexpected HTTP status is
|
||||||
|
@ -52,7 +51,7 @@ func handleErrorResponse(resp *http.Response) error {
|
||||||
if resp.StatusCode == 401 {
|
if resp.StatusCode == 401 {
|
||||||
err := parseHTTPErrorResponse(resp.Body)
|
err := parseHTTPErrorResponse(resp.Body)
|
||||||
if uErr, ok := err.(*UnexpectedHTTPResponseError); ok {
|
if uErr, ok := err.(*UnexpectedHTTPResponseError); ok {
|
||||||
return v2.ErrorCodeUnauthorized.WithDetail(uErr.Response)
|
return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
|
||||||
"github.com/docker/distribution/testutil"
|
"github.com/docker/distribution/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -782,10 +781,10 @@ func TestManifestUnauthorized(t *testing.T) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Unexpected error type: %#v", err)
|
t.Fatalf("Unexpected error type: %#v", err)
|
||||||
}
|
}
|
||||||
if v2Err.Code != v2.ErrorCodeUnauthorized {
|
if v2Err.Code != errcode.ErrorCodeUnauthorized {
|
||||||
t.Fatalf("Unexpected error code: %s", v2Err.Code.String())
|
t.Fatalf("Unexpected error code: %s", v2Err.Code.String())
|
||||||
}
|
}
|
||||||
if expected := v2.ErrorCodeUnauthorized.Message(); v2Err.Message != expected {
|
if expected := errcode.ErrorCodeUnauthorized.Message(); v2Err.Message != expected {
|
||||||
t.Fatalf("Unexpected message value: %q, expected %q", v2Err.Message, expected)
|
t.Fatalf("Unexpected message value: %q, expected %q", v2Err.Message, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/configuration"
|
"github.com/docker/distribution/configuration"
|
||||||
ctxu "github.com/docker/distribution/context"
|
ctxu "github.com/docker/distribution/context"
|
||||||
|
"github.com/docker/distribution/health"
|
||||||
"github.com/docker/distribution/notifications"
|
"github.com/docker/distribution/notifications"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
|
@ -203,6 +204,20 @@ func NewApp(ctx context.Context, configuration configuration.Configuration) *App
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterHealthChecks is an awful hack to defer health check registration
|
||||||
|
// control to callers. This should only ever be called once per registry
|
||||||
|
// process, typically in a main function. The correct way would be register
|
||||||
|
// health checks outside of app, since multiple apps may exist in the same
|
||||||
|
// process. Because the configuration and app are tightly coupled,
|
||||||
|
// implementing this properly will require a refactor. This method may panic
|
||||||
|
// if called twice in the same process.
|
||||||
|
func (app *App) RegisterHealthChecks() {
|
||||||
|
health.RegisterPeriodicThresholdFunc("storagedriver_"+app.Config.Storage.Type(), 10*time.Second, 3, func() error {
|
||||||
|
_, err := app.driver.List(app, "/") // "/" should always exist
|
||||||
|
return err // any error will be treated as failure
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// register a handler with the application, by route name. The handler will be
|
// register a handler with the application, by route name. The handler will be
|
||||||
// passed through the application filters and context will be constructed at
|
// passed through the application filters and context will be constructed at
|
||||||
// request time.
|
// request time.
|
||||||
|
@ -560,7 +575,7 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
|
||||||
// base route is accessed. This section prevents us from making
|
// base route is accessed. This section prevents us from making
|
||||||
// that mistake elsewhere in the code, allowing any operation to
|
// that mistake elsewhere in the code, allowing any operation to
|
||||||
// proceed.
|
// proceed.
|
||||||
if err := errcode.ServeJSON(w, v2.ErrorCodeUnauthorized); err != nil {
|
if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized); err != nil {
|
||||||
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("forbidden: no repository name")
|
return fmt.Errorf("forbidden: no repository name")
|
||||||
|
@ -575,7 +590,7 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
|
||||||
// Add the appropriate WWW-Auth header
|
// Add the appropriate WWW-Auth header
|
||||||
err.SetHeaders(w)
|
err.SetHeaders(w)
|
||||||
|
|
||||||
if err := errcode.ServeJSON(w, v2.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil {
|
if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil {
|
||||||
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -205,8 +205,8 @@ func TestNewApp(t *testing.T) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("not an ErrorCoder: %#v", errs[0])
|
t.Fatalf("not an ErrorCoder: %#v", errs[0])
|
||||||
}
|
}
|
||||||
if err2.ErrorCode() != v2.ErrorCodeUnauthorized {
|
if err2.ErrorCode() != errcode.ErrorCodeUnauthorized {
|
||||||
t.Fatalf("unexpected error code: %v != %v", err2.ErrorCode(), v2.ErrorCodeUnauthorized)
|
t.Fatalf("unexpected error code: %v != %v", err2.ErrorCode(), errcode.ErrorCodeUnauthorized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ func (bh *blobHandler) DeleteBlob(w http.ResponseWriter, r *http.Request) {
|
||||||
bh.Errors = append(bh.Errors, v2.ErrorCodeBlobUnknown)
|
bh.Errors = append(bh.Errors, v2.ErrorCodeBlobUnknown)
|
||||||
case distribution.ErrUnsupported:
|
case distribution.ErrUnsupported:
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
bh.Errors = append(bh.Errors, v2.ErrorCodeUnsupported)
|
bh.Errors = append(bh.Errors, errcode.ErrorCodeUnsupported)
|
||||||
default:
|
default:
|
||||||
bh.Errors = append(bh.Errors, errcode.ErrorCodeUnknown)
|
bh.Errors = append(bh.Errors, errcode.ErrorCodeUnknown)
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,7 +213,7 @@ func (imh *imageManifestHandler) DeleteImageManifest(w http.ResponseWriter, r *h
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
case distribution.ErrUnsupported:
|
case distribution.ErrUnsupported:
|
||||||
imh.Errors = append(imh.Errors, v2.ErrorCodeUnsupported)
|
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnsupported)
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
default:
|
default:
|
||||||
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown)
|
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/docker/distribution/registry/client"
|
"github.com/docker/distribution/registry/client"
|
||||||
"github.com/docker/distribution/registry/proxy/scheduler"
|
"github.com/docker/distribution/registry/proxy/scheduler"
|
||||||
)
|
)
|
||||||
|
@ -147,9 +147,9 @@ func manifestDigest(sm *manifest.SignedManifest) (digest.Digest, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pms proxyManifestStore) Put(manifest *manifest.SignedManifest) error {
|
func (pms proxyManifestStore) Put(manifest *manifest.SignedManifest) error {
|
||||||
return v2.ErrorCodeUnsupported
|
return errcode.ErrorCodeUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pms proxyManifestStore) Delete(dgst digest.Digest) error {
|
func (pms proxyManifestStore) Delete(dgst digest.Digest) error {
|
||||||
return v2.ErrorCodeUnsupported
|
return errcode.ErrorCodeUnsupported
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue