diff --git a/Gopkg.lock b/Gopkg.lock index dc4a3818..8fd113a9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -22,6 +22,14 @@ revision = "ab2d9a74b97eda058004c8deef80ac624432f408" version = "v1.0.0" +[[projects]] + digest = "1:141cc9fc6279592458b304038bd16a05ef477d125c6dad281216345a11746fd7" + name = "github.com/gofrs/uuid" + packages = ["."] + pruneopts = "" + revision = "6b08a5c5172ba18946672b49749cde22873dd7c2" + version = "v3.2.0" + [[projects]] digest = "1:a25a2c5ae694b01713fb6cd03c3b1ac1ccc1902b9f0a922680a88ec254f968e1" name = "github.com/google/uuid" @@ -194,6 +202,7 @@ "code.cloudfoundry.org/lager", "code.cloudfoundry.org/lager/lagertest", "github.com/drewolson/testflight", + "github.com/gofrs/uuid", "github.com/gorilla/mux", "github.com/onsi/ginkgo", "github.com/onsi/ginkgo/extensions/table", diff --git a/api.go b/api.go index ab730675..96301ee2 100644 --- a/api.go +++ b/api.go @@ -38,6 +38,7 @@ func New(serviceBroker ServiceBroker, logger lager.Logger, brokerCredentials Bro authMiddleware := auth.NewWrapper(brokerCredentials.Username, brokerCredentials.Password).Wrap apiVersionMiddleware := middlewares.APIVersionMiddleware{LoggerFactory: logger} + router.Use(middlewares.AddCorrelationIDToContext) router.Use(authMiddleware) router.Use(middlewares.AddOriginatingIdentityToContext) router.Use(apiVersionMiddleware.ValidateAPIVersionHdr) diff --git a/api_test.go b/api_test.go index 6bed0878..3c126366 100644 --- a/api_test.go +++ b/api_test.go @@ -26,6 +26,8 @@ import ( "net/url" "strings" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "code.cloudfoundry.org/lager/lagertest" "github.com/drewolson/testflight" @@ -109,6 +111,13 @@ var _ = Describe("Service Broker API", func() { return recorder } + It("has a X-Correlation-ID header", func() { + response := makeRequest() + + header := response.Header().Get("X-Correlation-ID") + Expect(header).Should(Not(BeNil())) + }) + It("has a Content-Type header", func() { response := makeRequest() @@ -2326,4 +2335,54 @@ var _ = Describe("Service Broker API", func() { }) }) }) + + Describe("CorrelationIDHeader", func() { + + var ( + fakeServiceBroker *fakes.AutoFakeServiceBroker + req *http.Request + testServer *httptest.Server + ) + + BeforeEach(func() { + fakeServiceBroker = new(fakes.AutoFakeServiceBroker) + brokerAPI = brokerapi.New(fakeServiceBroker, brokerLogger, credentials) + + testServer = httptest.NewServer(brokerAPI) + var err error + req, err = http.NewRequest("GET", testServer.URL+"/v2/catalog", nil) + Expect(err).NotTo(HaveOccurred()) + req.Header.Add("X-Broker-API-Version", "2.14") + req.SetBasicAuth(credentials.Username, credentials.Password) + }) + + AfterEach(func() { + testServer.Close() + }) + + When("X-Correlation-ID is passed", func() { + It("Adds correlation id to the context", func() { + const correlationID = "fake-correlation-id" + req.Header.Add("X-Correlation-ID", correlationID) + + _, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") + ctx := fakeServiceBroker.ServicesArgsForCall(0) + Expect(ctx.Value(middlewares.CorrelationIDKey)).To(Equal(correlationID)) + + }) + }) + When("X-Correlation-ID is not passed", func() { + It("Generates correlation id and adds it to the context", func() { + _, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") + ctx := fakeServiceBroker.ServicesArgsForCall(0) + Expect(ctx.Value(middlewares.CorrelationIDKey)).To(Not(BeNil())) + }) + }) + }) }) diff --git a/handlers/api_handler.go b/handlers/api_handler.go index 607827bf..ea7d0937 100644 --- a/handlers/api_handler.go +++ b/handlers/api_handler.go @@ -16,7 +16,6 @@ const ( serviceIdMissingKey = "service-id-missing" planIdMissingKey = "plan-id-missing" unknownErrorKey = "unknown-error" - apiVersionInvalidKey = "broker-api-version-invalid" bindingIDLogKey = "binding-id" ) diff --git a/handlers/bind.go b/handlers/bind.go index a24710d2..83453d93 100644 --- a/handlers/bind.go +++ b/handlers/bind.go @@ -4,6 +4,8 @@ import ( "encoding/json" "net/http" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain" @@ -20,9 +22,12 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { instanceID := vars["instance_id"] bindingID := vars["binding_id"] + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(bindLogKey, lager.Data{ - instanceIDLogKey: instanceID, - bindingIDLogKey: bindingID, + instanceIDLogKey: instanceID, + bindingIDLogKey: bindingID, + middlewares.CorrelationIDKey: correlationID, }) version := getAPIVersion(req) diff --git a/handlers/deprovision.go b/handlers/deprovision.go index 56024482..13260316 100644 --- a/handlers/deprovision.go +++ b/handlers/deprovision.go @@ -3,6 +3,8 @@ package handlers import ( "net/http" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain" @@ -14,8 +16,12 @@ const deprovisionLogKey = "deprovision" func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) instanceID := vars["instance_id"] + + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(deprovisionLogKey, lager.Data{ - instanceIDLogKey: instanceID, + instanceIDLogKey: instanceID, + middlewares.CorrelationIDKey: correlationID, }) details := domain.DeprovisionDetails{ diff --git a/handlers/get_binding.go b/handlers/get_binding.go index 24912de5..4ac27239 100644 --- a/handlers/get_binding.go +++ b/handlers/get_binding.go @@ -4,6 +4,8 @@ import ( "errors" "net/http" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain/apiresponses" @@ -16,9 +18,12 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { instanceID := vars["instance_id"] bindingID := vars["binding_id"] + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(getBindLogKey, lager.Data{ - instanceIDLogKey: instanceID, - bindingIDLogKey: bindingID, + instanceIDLogKey: instanceID, + bindingIDLogKey: bindingID, + middlewares.CorrelationIDKey: correlationID, }) version := getAPIVersion(req) @@ -27,7 +32,7 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ Description: err.Error(), }) - logger.Error(apiVersionInvalidKey, err) + logger.Error(middlewares.ApiVersionInvalidKey, err) return } diff --git a/handlers/get_instance.go b/handlers/get_instance.go index 0271ecd3..ce9dce7e 100644 --- a/handlers/get_instance.go +++ b/handlers/get_instance.go @@ -4,6 +4,8 @@ import ( "errors" "net/http" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain/apiresponses" @@ -15,8 +17,11 @@ func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) instanceID := vars["instance_id"] + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(getInstanceLogKey, lager.Data{ - instanceIDLogKey: instanceID, + instanceIDLogKey: instanceID, + middlewares.CorrelationIDKey: correlationID, }) version := getAPIVersion(req) @@ -25,7 +30,7 @@ func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ Description: err.Error(), }) - logger.Error(apiVersionInvalidKey, err) + logger.Error(middlewares.ApiVersionInvalidKey, err) return } diff --git a/handlers/last_binding_operation.go b/handlers/last_binding_operation.go index e45ac81a..3b0c1b1e 100644 --- a/handlers/last_binding_operation.go +++ b/handlers/last_binding_operation.go @@ -4,6 +4,8 @@ import ( "errors" "net/http" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain" @@ -22,8 +24,11 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques OperationData: req.FormValue("operation"), } + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(lastBindingOperationLogKey, lager.Data{ - instanceIDLogKey: instanceID, + instanceIDLogKey: instanceID, + middlewares.CorrelationIDKey: correlationID, }) version := getAPIVersion(req) @@ -32,7 +37,7 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ Description: err.Error(), }) - logger.Error(apiVersionInvalidKey, err) + logger.Error(middlewares.ApiVersionInvalidKey, err) return } diff --git a/handlers/last_binding_operation_test.go b/handlers/last_binding_operation_test.go index 5e313981..e10f353b 100644 --- a/handlers/last_binding_operation_test.go +++ b/handlers/last_binding_operation_test.go @@ -1,9 +1,15 @@ package handlers_test import ( - "code.cloudfoundry.org/lager" + "context" "encoding/json" "fmt" + "net/http" + "net/url" + + "github.com/pivotal-cf/brokerapi/middlewares" + + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -13,8 +19,6 @@ import ( "github.com/pivotal-cf/brokerapi/handlers" "github.com/pivotal-cf/brokerapi/handlers/fakes" "github.com/pkg/errors" - "net/http" - "net/url" ) var _ = Describe("LastBindingOperation", func() { @@ -135,5 +139,7 @@ func newRequest(instanceID, bindingID, planID, serviceID, operation string) *htt request.Form.Add("service_id", serviceID) request.Form.Add("operation", operation) + newCtx := context.WithValue(request.Context(), middlewares.CorrelationIDKey, "fake-correlation-id") + request = request.WithContext(newCtx) return request } diff --git a/handlers/last_operation.go b/handlers/last_operation.go index 27f62f15..f994aa97 100644 --- a/handlers/last_operation.go +++ b/handlers/last_operation.go @@ -3,6 +3,8 @@ package handlers import ( "net/http" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain" @@ -20,8 +22,11 @@ func (h APIHandler) LastOperation(w http.ResponseWriter, req *http.Request) { OperationData: req.FormValue("operation"), } + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(lastOperationLogKey, lager.Data{ - instanceIDLogKey: instanceID, + instanceIDLogKey: instanceID, + middlewares.CorrelationIDKey: correlationID, }) logger.Info("starting-check-for-operation") diff --git a/handlers/provision.go b/handlers/provision.go index 9a5e4b23..ef290f27 100644 --- a/handlers/provision.go +++ b/handlers/provision.go @@ -4,6 +4,8 @@ import ( "encoding/json" "net/http" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain" @@ -24,8 +26,11 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) instanceID := vars["instance_id"] + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(provisionLogKey, lager.Data{ - instanceIDLogKey: instanceID, + instanceIDLogKey: instanceID, + middlewares.CorrelationIDKey: correlationID, }) var details domain.ProvisionDetails diff --git a/handlers/unbind.go b/handlers/unbind.go index 4c81cc5f..18518651 100644 --- a/handlers/unbind.go +++ b/handlers/unbind.go @@ -8,6 +8,7 @@ import ( "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain" "github.com/pivotal-cf/brokerapi/domain/apiresponses" + "github.com/pivotal-cf/brokerapi/middlewares" ) const unbindLogKey = "unbind" @@ -17,9 +18,12 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { instanceID := vars["instance_id"] bindingID := vars["binding_id"] + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(unbindLogKey, lager.Data{ - instanceIDLogKey: instanceID, - bindingIDLogKey: bindingID, + instanceIDLogKey: instanceID, + bindingIDLogKey: bindingID, + middlewares.CorrelationIDKey: correlationID, }) version := getAPIVersion(req) @@ -29,7 +33,7 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { h.respond(w, http.StatusUnprocessableEntity, apiresponses.ErrorResponse{ Description: err.Error(), }) - logger.Error(apiVersionInvalidKey, err) + logger.Error(middlewares.ApiVersionInvalidKey, err) return } details := domain.UnbindDetails{ diff --git a/handlers/update.go b/handlers/update.go index b617e46c..809a4501 100644 --- a/handlers/update.go +++ b/handlers/update.go @@ -5,6 +5,8 @@ import ( "net/http" "strconv" + "github.com/pivotal-cf/brokerapi/middlewares" + "code.cloudfoundry.org/lager" "github.com/gorilla/mux" "github.com/pivotal-cf/brokerapi/domain" @@ -17,8 +19,11 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { vars := mux.Vars(req) instanceID := vars["instance_id"] + correlationID := req.Context().Value(middlewares.CorrelationIDKey).(string) + logger := h.logger.Session(updateLogKey, lager.Data{ - instanceIDLogKey: instanceID, + instanceIDLogKey: instanceID, + middlewares.CorrelationIDKey: correlationID, }) var details domain.UpdateDetails diff --git a/middlewares/api_version_header.go b/middlewares/api_version_header.go index efe56e28..f1a38460 100644 --- a/middlewares/api_version_header.go +++ b/middlewares/api_version_header.go @@ -24,7 +24,7 @@ import ( "code.cloudfoundry.org/lager" ) -const apiVersionInvalidKey = "broker-api-version-invalid" +const ApiVersionInvalidKey = "broker-api-version-invalid" type APIVersionMiddleware struct { LoggerFactory lager.Logger @@ -40,7 +40,7 @@ func (m APIVersionMiddleware) ValidateAPIVersionHdr(next http.Handler) http.Hand err := checkBrokerAPIVersionHdr(req) if err != nil { - logger.Error(apiVersionInvalidKey, err) + logger.Error(ApiVersionInvalidKey, err) w.Header().Set("Content-type", "application/json") diff --git a/middlewares/correlation_id_header.go b/middlewares/correlation_id_header.go new file mode 100644 index 00000000..c6374f6f --- /dev/null +++ b/middlewares/correlation_id_header.go @@ -0,0 +1,47 @@ +package middlewares + +import ( + "context" + "net/http" + + "github.com/gofrs/uuid" +) + +const CorrelationIDKey = "correlation-id" + +var correlationIDHeaders = []string{"X-Correlation-ID", "X-CorrelationID", "X-ForRequest-ID", "X-Request-ID", "X-Vcap-Request-Id"} + +func AddCorrelationIDToContext(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + var correlationID, headerName string + var found bool + + for _, header := range correlationIDHeaders { + headerValue := req.Header.Get(header) + if headerValue != "" { + correlationID = headerValue + headerName = header + found = true + break + } + } + + if !found { + correlationID = generateCorrelationID() + headerName = correlationIDHeaders[0] + } + + w.Header().Set(headerName, correlationID) + newCtx := context.WithValue(req.Context(), CorrelationIDKey, correlationID) + next.ServeHTTP(w, req.WithContext(newCtx)) + }) +} + +func generateCorrelationID() string { + uuids, err := uuid.NewV4() + if err != nil { + return "" + } + + return uuids.String() +}