diff --git a/Dockerfile b/Dockerfile index defb64c8586..6ce9a7dfa38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN go mod vendor RUN go mod tidy ARG TEST="true" RUN if [ "$TEST" != "false" ]; then ./validate.sh ; fi -RUN go build -mod=vendor . +RUN go build -mod=vendor -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//'` -X github.com/prebid/prebid-server/version.Rev=`git rev-parse HEAD`" . FROM ubuntu:18.04 AS release LABEL maintainer="hans.hjort@xandr.com" diff --git a/endpoints/version.go b/endpoints/version.go index d3cda6b6e3c..514791e20b8 100644 --- a/endpoints/version.go +++ b/endpoints/version.go @@ -8,18 +8,23 @@ import ( ) type versionModel struct { + Version string `json:"version"` Revision string `json:"revision"` } -// NewVersionEndpoint returns the latest commit sha1 from which the binary was built -func NewVersionEndpoint(version string) http.HandlerFunc { +// NewVersionEndpoint returns the latest git tag as the version and commit hash as the revision from which the binary was built +func NewVersionEndpoint(version string, revision string) http.HandlerFunc { if version == "" { version = "not-set" } + if revision == "" { + revision = "not-set" + } return func(w http.ResponseWriter, _ *http.Request) { jsonOutput, err := json.Marshal(versionModel{ - Revision: version, + Version: version, + Revision: revision, }) if err != nil { glog.Errorf("/version Critical error when trying to marshal versionModel: %v", err) diff --git a/endpoints/version_test.go b/endpoints/version_test.go index 1d21a89f841..ecddf532e8f 100644 --- a/endpoints/version_test.go +++ b/endpoints/version_test.go @@ -11,17 +11,18 @@ import ( func TestVersion(t *testing.T) { // Setup: var testCases = []struct { - input string + version string + revision string expected string }{ - {"", `{"revision": "not-set"}`}, - {"abc", `{"revision": "abc"}`}, - {"d6cd1e2bd19e03a81132a23b2025920577f84e37", `{"revision": "d6cd1e2bd19e03a81132a23b2025920577f84e37"}`}, + {"", "", `{"version":"not-set","revision":"not-set"}`}, + {"abc", "def", `{"version":"abc","revision" :"def"}`}, + {"1.2.3", "d6cd1e2bd19e03a81132a23b2025920577f84e37", `{"version":"1.2.3","revision":"d6cd1e2bd19e03a81132a23b2025920577f84e37"}`}, } for _, tc := range testCases { - handler := NewVersionEndpoint(tc.input) + handler := NewVersionEndpoint(tc.version, tc.revision) w := httptest.NewRecorder() // Execute: @@ -29,7 +30,11 @@ func TestVersion(t *testing.T) { // Verify: var result, expected versionModel - err := json.NewDecoder(w.Body).Decode(&result) + responseBodyBytes, err := ioutil.ReadAll(w.Body) + if err != nil { + t.Errorf("Error reading response body bytes: %s", err) + } + err = json.Unmarshal(responseBodyBytes, &result) if err != nil { t.Errorf("Bad response body. Expected: %s, got an error %s", tc.expected, err) } @@ -40,7 +45,6 @@ func TestVersion(t *testing.T) { } if !reflect.DeepEqual(expected, result) { - responseBodyBytes, _ := ioutil.ReadAll(w.Body) responseBodyString := string(responseBodyBytes) t.Errorf("Bad response body. Expected: %s, got %s", tc.expected, responseBodyString) } diff --git a/exchange/bidder.go b/exchange/bidder.go index 895cff936bd..28be854c264 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -15,6 +15,7 @@ import ( "github.com/golang/glog" "github.com/prebid/prebid-server/config/util" "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/version" nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" @@ -138,17 +139,17 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B } return nil, errs } + xPrebidHeader := buildXPrebidHeader(request, version.Ver) - if reqInfo.GlobalPrivacyControlHeader == "1" { - for i := 0; i < len(reqData); i++ { - if reqData[i].Headers != nil { - reqHeader := reqData[i].Headers.Clone() - reqHeader.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader) - reqData[i].Headers = reqHeader - } else { - reqData[i].Headers = http.Header{} - reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader) - } + for i := 0; i < len(reqData); i++ { + if reqData[i].Headers != nil { + reqData[i].Headers = reqData[i].Headers.Clone() + } else { + reqData[i].Headers = http.Header{} + } + reqData[i].Headers.Add("X-Prebid", xPrebidHeader) + if reqInfo.GlobalPrivacyControlHeader == "1" { + reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader) } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index a31a195c512..1591d353bb0 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -26,6 +26,7 @@ import ( "github.com/prebid/prebid-server/metrics" metricsConfig "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -145,6 +146,12 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { server := httptest.NewServer(mockHandler(200, "getBody", "responseJson")) defer server.Close() + oldVer := version.Ver + version.Ver = "test-version" + defer func() { + version.Ver = oldVer + }() + requestHeaders := http.Header{} requestHeaders.Add("Content-Type", "application/json") requestHeaders.Add("Authorization", "anySecret") @@ -173,7 +180,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"Content-Type": {"application/json"}}, + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"pbs-go/test-version"}}, ResponseBody: "responseJson", Status: 200, }, @@ -214,7 +221,7 @@ func TestSetGPCHeader(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "Sec-Gpc": {"1"}}, + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"pbs-go/unknown"}, "Sec-Gpc": {"1"}}, ResponseBody: "responseJson", Status: 200, }, @@ -252,7 +259,7 @@ func TestSetGPCHeaderNil(t *testing.T) { { Uri: server.URL, RequestBody: "requestJson", - RequestHeaders: map[string][]string{"Sec-Gpc": {"1"}}, + RequestHeaders: map[string][]string{"X-Prebid": {"pbs-go/unknown"}, "Sec-Gpc": {"1"}}, ResponseBody: "responseJson", Status: 200, }, diff --git a/exchange/utils.go b/exchange/utils.go index f33f1b9199c..1277cffd85f 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/rand" + "strings" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/go-gdpr/vendorconsent" @@ -700,3 +701,39 @@ func getExtBidAdjustmentFactors(requestExt *openrtb_ext.ExtRequest) map[string]f } return bidAdjustmentFactors } + +func buildXPrebidHeader(bidRequest *openrtb2.BidRequest, version string) string { + req := &openrtb_ext.RequestWrapper{BidRequest: bidRequest} + + sb := &strings.Builder{} + writeNameVersionRecord(sb, "pbs-go", version) + + if reqExt, err := req.GetRequestExt(); err == nil && reqExt != nil { + if prebidExt := reqExt.GetPrebid(); prebidExt != nil { + if channel := prebidExt.Channel; channel != nil { + writeNameVersionRecord(sb, channel.Name, channel.Version) + } + } + } + if appExt, err := req.GetAppExt(); err == nil && appExt != nil { + if prebidExt := appExt.GetPrebid(); prebidExt != nil { + writeNameVersionRecord(sb, prebidExt.Source, prebidExt.Version) + } + } + return sb.String() +} + +func writeNameVersionRecord(sb *strings.Builder, name, version string) { + if name == "" { + return + } + if version == "" { + version = "unknown" + } + if sb.Len() != 0 { + sb.WriteString(",") + } + sb.WriteString(name) + sb.WriteString("/") + sb.WriteString(version) +} diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 81b0b9bd3c0..6ce4b28ac33 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -2228,3 +2228,117 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { assert.Equal(t, &requestExpected, test.request, test.description+":request") } } + +func TestBuildXPrebidHeader(t *testing.T) { + testCases := []struct { + description string + version string + requestExt *openrtb_ext.ExtRequest + requestAppExt *openrtb_ext.ExtApp + result string + }{ + { + description: "No versions", + version: "", + result: "pbs-go/unknown", + }, + { + description: "pbs", + version: "test-version", + result: "pbs-go/test-version", + }, + { + description: "prebid.js", + version: "test-version", + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Channel: &openrtb_ext.ExtRequestPrebidChannel{ + Name: "pbjs", + Version: "test-pbjs-version", + }, + }, + }, + result: "pbs-go/test-version,pbjs/test-pbjs-version", + }, + { + description: "unknown prebid.js", + version: "test-version", + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Channel: &openrtb_ext.ExtRequestPrebidChannel{ + Name: "pbjs", + }, + }, + }, + result: "pbs-go/test-version,pbjs/unknown", + }, + { + description: "channel without a name", + version: "test-version", + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Channel: &openrtb_ext.ExtRequestPrebidChannel{ + Version: "test-version", + }, + }, + }, + result: "pbs-go/test-version", + }, + { + description: "prebid-mobile", + version: "test-version", + requestAppExt: &openrtb_ext.ExtApp{ + Prebid: openrtb_ext.ExtAppPrebid{ + Source: "prebid-mobile", + Version: "test-prebid-mobile-version", + }, + }, + result: "pbs-go/test-version,prebid-mobile/test-prebid-mobile-version", + }, + { + description: "app ext without a source", + version: "test-version", + requestAppExt: &openrtb_ext.ExtApp{ + Prebid: openrtb_ext.ExtAppPrebid{ + Version: "test-version", + }, + }, + result: "pbs-go/test-version", + }, + { + description: "Version found in both req.Ext and req.App.Ext", + version: "test-version", + requestExt: &openrtb_ext.ExtRequest{ + Prebid: openrtb_ext.ExtRequestPrebid{ + Channel: &openrtb_ext.ExtRequestPrebidChannel{ + Name: "pbjs", + Version: "test-pbjs-version", + }, + }, + }, + requestAppExt: &openrtb_ext.ExtApp{ + Prebid: openrtb_ext.ExtAppPrebid{ + Source: "prebid-mobile", + Version: "test-prebid-mobile-version", + }, + }, + result: "pbs-go/test-version,pbjs/test-pbjs-version,prebid-mobile/test-prebid-mobile-version", + }, + } + + for _, test := range testCases { + req := &openrtb2.BidRequest{} + if test.requestExt != nil { + reqExt, err := json.Marshal(test.requestExt) + assert.NoError(t, err, test.description+":err marshalling reqExt") + req.Ext = reqExt + } + if test.requestAppExt != nil { + reqAppExt, err := json.Marshal(test.requestAppExt) + assert.NoError(t, err, test.description+":err marshalling reqAppExt") + req.App = &openrtb2.App{Ext: reqAppExt} + } + result := buildXPrebidHeader(req, test.version) + assert.Equal(t, test.result, result, test.description+":result") + } +} diff --git a/main.go b/main.go index 5c5a2a462e0..6087c3d69dd 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,6 @@ import ( "github.com/spf13/viper" ) -// Rev holds binary revision string -// Set manually at build time using: -// go build -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -// Populated automatically at build / releases -// `gox -os="linux" -arch="386" -output="{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...;` -// See issue #559 -var Rev string - func init() { rand.Seed(time.Now().UnixNano()) } @@ -37,7 +29,7 @@ func main() { glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err) } - err = serve(Rev, cfg) + err = serve(cfg) if err != nil { glog.Exitf("prebid-server failed: %v", err) } @@ -51,7 +43,7 @@ func loadConfig() (*config.Configuration, error) { return config.New(v) } -func serve(revision string, cfg *config.Configuration) error { +func serve(cfg *config.Configuration) error { fetchingInterval := time.Duration(cfg.CurrencyConverter.FetchIntervalSeconds) * time.Second staleRatesThreshold := time.Duration(cfg.CurrencyConverter.StaleRatesSeconds) * time.Second currencyConverter := currency.NewRateConverter(&http.Client{}, cfg.CurrencyConverter.FetchURL, staleRatesThreshold) @@ -67,7 +59,7 @@ func serve(revision string, cfg *config.Configuration) error { pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) corsRouter := router.SupportCORS(r) - server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(revision, currencyConverter, fetchingInterval), r.MetricsEngine) + server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(currencyConverter, fetchingInterval), r.MetricsEngine) r.Shutdown() return nil diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 606874f196a..bf875123ea1 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -29,6 +29,7 @@ type ExtRequestPrebid struct { Aliases map[string]string `json:"aliases,omitempty"` BidAdjustmentFactors map[string]float64 `json:"bidadjustmentfactors,omitempty"` Cache *ExtRequestPrebidCache `json:"cache,omitempty"` + Channel *ExtRequestPrebidChannel `json:"channel,omitempty"` Data *ExtRequestPrebidData `json:"data,omitempty"` Debug bool `json:"debug,omitempty"` Events json.RawMessage `json:"events,omitempty"` @@ -80,6 +81,12 @@ type SourceExt struct { SChain ExtRequestPrebidSChainSChain `json:"schain"` } +// ExtRequestPrebidChannel defines the contract for bidrequest.ext.prebid.channel +type ExtRequestPrebidChannel struct { + Name string `json:"name"` + Version string `json:"version"` +} + // ExtRequestPrebidCache defines the contract for bidrequest.ext.prebid.cache type ExtRequestPrebidCache struct { Bids *ExtRequestPrebidCacheBids `json:"bids"` diff --git a/router/admin.go b/router/admin.go index b66bf55a5d6..29cdbbe5e23 100644 --- a/router/admin.go +++ b/router/admin.go @@ -7,9 +7,10 @@ import ( "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/endpoints" + "github.com/prebid/prebid-server/version" ) -func Admin(revision string, rateConverter *currency.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux { +func Admin(rateConverter *currency.RateConverter, rateConverterFetchingInterval time.Duration) *http.ServeMux { // Add endpoints to the admin server // Making sure to add pprof routes mux := http.NewServeMux() @@ -21,6 +22,6 @@ func Admin(revision string, rateConverter *currency.RateConverter, rateConverter mux.HandleFunc("/debug/pprof/trace", pprof.Trace) // Register prebid-server defined admin handlers mux.HandleFunc("/currency/rates", endpoints.NewCurrencyRatesEndpoint(rateConverter, rateConverterFetchingInterval)) - mux.HandleFunc("/version", endpoints.NewVersionEndpoint(revision)) + mux.HandleFunc("/version", endpoints.NewVersionEndpoint(version.Ver, version.Rev)) return mux } diff --git a/version/version.go b/version/version.go new file mode 100644 index 00000000000..e93102763fa --- /dev/null +++ b/version/version.go @@ -0,0 +1,14 @@ +package version + +// Ver holds the version derived from the latest git tag +// Populated using: +// go build -ldflags "-X github.com/prebid/prebid-server/version.Ver=`git describe --tags | sed 's/^v//`" +// Populated automatically at build / releases in the Docker image +var Ver string + +// Rev holds binary revision string +// Populated using: +// go build -ldflags "-X github.com/prebid/prebid-server/version.Rev=`git rev-parse --short HEAD`" +// Populated automatically at build / releases in the Docker image +// See issue #559 +var Rev string