Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Add elastic-api-version support #2677

Merged
merged 15 commits into from
Jun 12, 2023
Merged
81 changes: 81 additions & 0 deletions internal/pkg/api/apiVersion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package api

import (
"errors"
"fmt"
"net/http"
"regexp"
"strings"
)

var (
ErrInvalidAPIVersionFormat = errors.New("invalid version format")
ErrUnsupportedAPIVersion = errors.New("version is not supported")
)

const (
ElasticAPIVersionHeader = "elastic-api-version"
nchaulet marked this conversation as resolved.
Show resolved Hide resolved
DefaultVersion = "2023-06-01"
)

var SupportedVersions = []string{DefaultVersion}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if there is a better place to list the versions we support and the default version.


var isValidVersionRegex = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}$`)

type apiVersion struct {
supportedVersions []string
defaultVersion string
}

func NewAPIVersion() *apiVersion {
return &apiVersion{
supportedVersions: SupportedVersions,
defaultVersion: DefaultVersion,
}
}

func (a *apiVersion) validateVersionFormat(version string) (string, error) {
nchaulet marked this conversation as resolved.
Show resolved Hide resolved
if !isValidVersionRegex.MatchString(version) {
return version, fmt.Errorf("received \"%s\", expected a valid date string formatted as YYYY-MM-DD. %w", version, ErrUnsupportedAPIVersion)
nchaulet marked this conversation as resolved.
Show resolved Hide resolved
}

return version, nil
}

func (a *apiVersion) isVersionSupported(version string) bool {
for _, vers := range a.supportedVersions {
if vers == version {
return true
}
}
return false
}

func (a *apiVersion) middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

headerValue := r.Header.Get(ElasticAPIVersionHeader)
if headerValue != "" {
_, err := a.validateVersionFormat(headerValue)
if err != nil {
ErrorResp(w, r, err)
return
}

if !a.isVersionSupported(headerValue) {
w.Header().Add(ElasticAPIVersionHeader, a.defaultVersion)
jsoriano marked this conversation as resolved.
Show resolved Hide resolved
ErrorResp(w, r, fmt.Errorf("received \"%s\", is not supported. supported versions are: %s %w", headerValue, strings.Join(a.supportedVersions, ", "), ErrInvalidAPIVersionFormat))
nchaulet marked this conversation as resolved.
Show resolved Hide resolved
return
}
w.Header().Add(ElasticAPIVersionHeader, headerValue)
} else {
w.Header().Add(ElasticAPIVersionHeader, a.defaultVersion)
}

next.ServeHTTP(w, r)
})
}
80 changes: 80 additions & 0 deletions internal/pkg/api/apiVersion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package api

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAPIVersion_middleware(t *testing.T) {
supportedVersions := []string{"2022-01-01", "2022-02-01", "2022-03-01"}
defaultVersion := "2022-03-01"

tests := []struct {
name string
requestAPIVersionHeader string
expectRespStatus string
expectRespAPIVersionHeader string
}{
{
name: "with a misformatted elastic-api-version header",
requestAPIVersionHeader: "iamnotvalid",
expectRespStatus: "400 Bad Request",
},
{
name: "with an invalid elastic-api-version header",
requestAPIVersionHeader: "1990-01-01",
expectRespStatus: "400 Bad Request",
},
{
name: "with a valid elastic-api-version header",
requestAPIVersionHeader: "2022-02-01",
expectRespAPIVersionHeader: "2022-02-01",
expectRespStatus: "200 OK",
},
{
name: "without elastic-api-version header",
expectRespAPIVersionHeader: "2022-03-01",
expectRespStatus: "200 OK",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {

apiVersion := apiVersion{
supportedVersions: supportedVersions,
defaultVersion: defaultVersion,
}

resp := httptest.NewRecorder()

req := httptest.NewRequest("GET", "/api/test", nil)

nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})

if tc.requestAPIVersionHeader != "" {
req.Header.Set(ElasticAPIVersionHeader, tc.requestAPIVersionHeader)
}

apiVersion.middleware(nextHandler).ServeHTTP(resp, req)

respResult := resp.Result()
defer respResult.Body.Close()

if tc.expectRespStatus != "" {
assert.Equal(t, tc.expectRespStatus, respResult.Status)
}

if tc.expectRespAPIVersionHeader != "" {
assert.Equal(t, tc.expectRespAPIVersionHeader, resp.Header().Get(ElasticAPIVersionHeader))
}
})
}
}
19 changes: 19 additions & 0 deletions internal/pkg/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,25 @@ func NewHTTPErrResp(err error) HTTPErrResp {
zerolog.InfoLevel,
},
},
// Version
{
ErrInvalidAPIVersionFormat,
HTTPErrResp{
http.StatusBadRequest,
"ErrInvalidAPIVersionFormat",
"",
zerolog.InfoLevel,
},
},
{
ErrUnsupportedAPIVersion,
HTTPErrResp{
http.StatusBadRequest,
"ErrUnsupportedAPIVersion",
"",
zerolog.InfoLevel,
},
},
}

for _, e := range errTable {
Expand Down