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

Expose Kong capabilities through a function in the client #48

Merged
merged 30 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
159812c
Expose Kong capabilities through a function in the client
mmorel-35 Apr 26, 2021
5332e3a
test rename
mmorel-35 Apr 26, 2021
75e00b9
Lint magic number for number of parts in a version
mmorel-35 Apr 26, 2021
2cf7990
use kong.workspace instead of workspace
mmorel-35 Apr 26, 2021
7ef3826
constant definition and calculate version once
mmorel-35 Apr 26, 2021
fb84377
add workspace as a parameter for NewTestClient
mmorel-35 Apr 27, 2021
d74066e
missing parameter
mmorel-35 Apr 27, 2021
1eebf94
Remove workspace from constructor, replaced by a setter
mmorel-35 Apr 28, 2021
7df7605
Add Portal and RBAC to the struct to use them in tests
mmorel-35 Apr 28, 2021
c6f166a
Merge branch 'local/upstream/main' into feature/kongVersion
mmorel-35 Apr 28, 2021
4b80d52
Turn RBAC field into a boolean
mmorel-35 Apr 28, 2021
a7c92af
simplify boolean expression
mmorel-35 Apr 28, 2021
708c13b
rbac test
mmorel-35 Apr 28, 2021
37ce7b7
restore currentVersion
mmorel-35 Apr 28, 2021
34bb575
Add individual field per authentification type
mmorel-35 Apr 29, 2021
b1cc2de
remove workspace from client and test /kong before call to /
mmorel-35 Apr 30, 2021
8b2d94c
lint
mmorel-35 Apr 30, 2021
7b483c7
check properties existence
mmorel-35 Apr 30, 2021
2319b87
cast
mmorel-35 Apr 30, 2021
c4e1582
lint
mmorel-35 Apr 30, 2021
c5a9d25
remove capabilities and follow preconisations
mmorel-35 May 12, 2021
a2c9056
remove Kong and getKong
mmorel-35 May 12, 2021
dc4ebc6
restablish root
mmorel-35 May 12, 2021
8f1df42
lint
mmorel-35 May 12, 2021
c152f23
fix documentation
mmorel-35 May 12, 2021
2c04d9d
Add comments
mmorel-35 May 12, 2021
3991d73
lint and comment
mmorel-35 May 12, 2021
4019f28
Update kong/utils.go
mmorel-35 May 12, 2021
f04a05f
Update kong/utils.go
mmorel-35 May 12, 2021
3505ee2
check presence for version in the map
mmorel-35 May 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions kong/kong.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http/httputil"
"net/url"
"os"
"path"

"github.com/pkg/errors"

Expand All @@ -31,6 +32,7 @@ var (
type Client struct {
client *http.Client
baseURL string
workspace string
common service
Consumers AbstractConsumerService
Developers AbstractDeveloperService
Expand Down Expand Up @@ -85,7 +87,7 @@ type Status struct {
}

// NewClient returns a Client which talks to Admin API of Kong
func NewClient(baseURL *string, client *http.Client) (*Client, error) {
func NewClient(baseURL *string, workspace string, client *http.Client) (*Client, error) {
if client == nil {
client = http.DefaultClient
}
Expand All @@ -101,6 +103,11 @@ func NewClient(baseURL *string, client *http.Client) (*Client, error) {
if err != nil {
return nil, errors.Wrap(err, "parsing URL")
}
kong.workspace = workspace
if kong.hasWorkspace() {
url.Path = path.Join(url.Path, kong.workspace)
}

kong.baseURL = url.String()

kong.common.client = kong
Expand Down Expand Up @@ -147,6 +154,10 @@ func NewClient(baseURL *string, client *http.Client) (*Client, error) {
return kong, nil
}

func (c *Client) hasWorkspace() bool {
return len(c.workspace) > 0
}

// Do executes a HTTP request and returns a response
func (c *Client) Do(ctx context.Context, req *http.Request,
v interface{}) (*Response, error) {
Expand Down Expand Up @@ -264,9 +275,13 @@ func (c *Client) Status(ctx context.Context) (*Status, error) {
}

// Root returns the response of GET request on root of
// Admin API (GET /).
// Admin API (GET / by default or GET /kong when the client has workspace defined)
func (c *Client) Root(ctx context.Context) (map[string]interface{}, error) {
req, err := c.NewRequest("GET", "/", nil, nil)
queryPath := "/"
if c.hasWorkspace() {
queryPath = queryPath + "kong"
}
req, err := c.NewRequest("GET", queryPath, nil, nil)
if err != nil {
return nil, err
}
Expand All @@ -278,3 +293,11 @@ func (c *Client) Root(ctx context.Context) (map[string]interface{}, error) {
}
return root, nil
}

func (c *Client) Kong(ctx context.Context) (*Kong, error) {
root, err := c.Root(ctx)
if err != nil {
return nil, err
}
return getKong(root)
}
4 changes: 2 additions & 2 deletions kong/kong_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ func NewTestClient(baseURL *string, client *http.Client) (*Client, error) {
},
rt: defaultTransport,
}
return NewClient(baseURL, c)
return NewClient(baseURL, "", c)
}
return NewClient(baseURL, client)
return NewClient(baseURL, "", client)
}

type TestWorkspace struct {
Expand Down
10 changes: 10 additions & 0 deletions kong/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import (
"strings"
)

type Kong struct {
Version string
Enterprise bool
Database string
Credentials struct {
mmorel-35 marked this conversation as resolved.
Show resolved Hide resolved
hasTagSupport bool
minVersion string
mmorel-35 marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Service represents a Service in Kong.
// Read https://getkong.org/docs/0.13.x/admin-api/#Service-object
// +k8s:deepcopy-gen=true
Expand Down
44 changes: 44 additions & 0 deletions kong/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@ package kong

import (
"bytes"
"fmt"
"net/http"
"regexp"
"strings"

"github.com/blang/semver/v4"
)

var (
mmorel-35 marked this conversation as resolved.
Show resolved Hide resolved
versionParts = 4
kong140Version = semver.MustParse("1.4.0")
)

// String returns pointer to s.
Expand Down Expand Up @@ -91,3 +100,38 @@ func HTTPClientWithHeaders(client *http.Client,
}
return res
}

func cleanSemVer(v string) (semver.Version, error) {
// fix enterprise edition semver adding patch number
// fix enterprise edition version with dash
// fix bad version formats like 0.13.0preview1
re := regexp.MustCompile(`(\d+\.\d+)(?:[\.-](\d+))?(?:\-?(.+)$|$)`)
m := re.FindStringSubmatch(v)
if len(m) != versionParts {
return semver.Version{}, fmt.Errorf("Unknown Kong version : '%v'", v)
}
if m[2] == "" {
m[2] = "0"
}
if m[3] != "" {
m[3] = "-" + strings.Replace(m[3], "enterprise-edition", "enterprise", 1)
m[3] = strings.Replace(m[3], ".", "", -1)
}
v = fmt.Sprintf("%s.%s%s", m[1], m[2], m[3])
return semver.Make(v)
}

func getKong(root map[string]interface{}) (*Kong, error) {
version := root["version"].(string)
semVer, err := cleanSemVer(version)
Copy link
Member

Choose a reason for hiding this comment

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

Let's not do this here since it is a lossy conversion.
The user of the library can interpret the version if needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

How about providing both the cleaned semver and the original string with all our extra content? Elsewhere we're likely going to be performing semver comparisons for the most part, and I think it makes sense to have a standard conversion rather than each user implementing their own cleanSemVer() variant.

Copy link
Member

@hbagdi hbagdi May 10, 2021

Choose a reason for hiding this comment

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

I think a better approach would be:

info, err := client.Root() // most powerful
version := utils.versionFromInfo(info) // package-level global function to fetch version from the info map
semanticVersion := utils.ParseSemanticVersion(version) // package-level function

This makes the code cleaner and keeps each function responsible for one and only one thing.

if err != nil {
return nil, err
}
kong := new(Kong)
kong.Version = semVer.String()
kong.Enterprise = strings.Contains(version, "enterprise")
kong.Database = root["configuration"].(map[string]interface{})["database"].(string)
kong.Credentials.minVersion = kong140Version.String()
kong.Credentials.hasTagSupport = semVer.GTE(kong140Version)
return kong, nil
}
92 changes: 90 additions & 2 deletions kong/utils_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package kong

import (
"testing"

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

func TestStringArrayToString(t *testing.T) {
Expand Down Expand Up @@ -52,3 +52,91 @@ func TestStringSlice(t *testing.T) {
assert.Equal("foo", *arrp[0])
assert.Equal("bar", *arrp[1])
}

func TestFixVersion(t *testing.T) {
validVersions := map[string]string{
"0.14.1": "0.14.1",
"0.14.2rc": "0.14.2-rc",
"0.14.2rc1": "0.14.2-rc1",
"0.14.2preview": "0.14.2-preview",
"0.14.2preview1": "0.14.2-preview1",
"0.33-enterprise-edition": "0.33.0-enterprise",
"0.33-1-enterprise-edition": "0.33.1-enterprise",
"1.3.0.0-enterprise-edition-lite": "1.3.0-0-enterprise-lite",
"1.3.0.0-enterprise-lite": "1.3.0-0-enterprise-lite",
}
for inputVersion, expectedVersion := range validVersions {
v, err := cleanSemVer(inputVersion)
if err != nil {
t.Errorf("error converting %s: %v", inputVersion, err)
} else if v.String() != expectedVersion {
t.Errorf("converting %s, expecting %s, getting %s", inputVersion, expectedVersion, v.String())
}
}

invalidVersions := []string{
"",
"0-1-1",
}
for _, inputVersion := range invalidVersions {
_, err := cleanSemVer(inputVersion)
if err == nil {
t.Errorf("expecting error converting %s, getting no errors", inputVersion)
}
}
}

func Test_getKong(t *testing.T) {

kongWithoutCredentialsSupport := new(Kong)
kongWithoutCredentialsSupport.Credentials.minVersion = "1.4.0"
kongWithoutCredentialsSupport.Credentials.hasTagSupport = false

kongWithCredentialsSupport := new(Kong)
kongWithCredentialsSupport.Credentials.minVersion = "1.4.0"
kongWithCredentialsSupport.Credentials.hasTagSupport = true

tests := []struct {
name string
root map[string]interface{}
expected *Kong
}{
{
root: map[string]interface{}{
"version": "0.33-1-enterprise-edition",
"configuration": map[string]interface{}{
"database": "off",
},
},
expected: &Kong{
Version: "0.33.1-enterprise",
Enterprise: true,
Database: "off",
Credentials: kongWithoutCredentialsSupport.Credentials,
},
},
{
root: map[string]interface{}{
"version": "2.3.2.0",
"configuration": map[string]interface{}{
"database": "cassandra",
},
},
expected: &Kong{
Version: "2.3.2-0",
Enterprise: false,
Database: "cassandra",
Credentials: kongWithCredentialsSupport.Credentials,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, _ := getKong(tt.root)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("Expected %v, but is %v", tt.expected, result)
}
})
}

mmorel-35 marked this conversation as resolved.
Show resolved Hide resolved
}