Skip to content

Commit

Permalink
[Fleet] Add elastic-api-version support (#2677)
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet committed Jun 12, 2023
1 parent d5babde commit ce791de
Show file tree
Hide file tree
Showing 8 changed files with 654 additions and 143 deletions.
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"
DefaultVersion = "2023-06-01"
)

var SupportedVersions = []string{DefaultVersion}

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) error {
if !isValidVersionRegex.MatchString(version) {
return fmt.Errorf("received \"%s\", expected a valid date string formatted as YYYY-MM-DD. %w", version, ErrInvalidAPIVersionFormat)
}

return 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)
ErrorResp(w, r, fmt.Errorf("received %q, is not supported. supported versions are: [%s] %w", headerValue, strings.Join(a.supportedVersions, ", "), ErrUnsupportedAPIVersion))
return
}
w.Header().Add(ElasticAPIVersionHeader, headerValue)
} else {
w.Header().Add(ElasticAPIVersionHeader, a.defaultVersion)
}

next.ServeHTTP(w, r)
})
}
93 changes: 93 additions & 0 deletions internal/pkg/api/apiVersion_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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 (
"encoding/json"
"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
expectErr string
}{
{
name: "with a misformatted Elastic-Api-Version header",
requestAPIVersionHeader: "iamnotvalid",
expectRespStatus: "400 Bad Request",
expectErr: "ErrInvalidAPIVersionFormat",
},
{
name: "with an invalid Elastic-Api-Version header",
requestAPIVersionHeader: "1990-01-01",
expectRespStatus: "400 Bad Request",
expectErr: "ErrUnsupportedAPIVersion",
},
{
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))
}

if tc.expectErr != "" {
errorResp := &Error{}
dec := json.NewDecoder(resp.Body)
err := dec.Decode(&errorResp)

assert.NoError(t, err)
assert.Equal(t, tc.expectErr, errorResp.Error)
}
})
}
}
19 changes: 19 additions & 0 deletions internal/pkg/api/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,25 @@ func NewHTTPErrResp(err error) HTTPErrResp {
zerolog.InfoLevel,
},
},
// Version
{
ErrInvalidAPIVersionFormat,
HTTPErrResp{
http.StatusBadRequest,
"ErrInvalidAPIVersionFormat",
"",
zerolog.InfoLevel,
},
},
{
ErrUnsupportedAPIVersion,
HTTPErrResp{
http.StatusBadRequest,
"ErrUnsupportedAPIVersion",
"",
zerolog.InfoLevel,
},
},
// file
{
delivery.ErrNoFile,
Expand Down

0 comments on commit ce791de

Please sign in to comment.