Skip to content

Commit

Permalink
feat: add ReloadDeclarativeRawConfig() for sending declarative config
Browse files Browse the repository at this point in the history
  • Loading branch information
pmalek committed Dec 15, 2022
1 parent 9a0b9ec commit 3e8fc96
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
- Add support to filling entity defaults using JSON schemas.
[#231](https://github.com/Kong/go-kong/pull/231)
- Add possibility to client to send declarative configs via `ReloadDeclarativeRawConfig()`
[#252](https://github.com/Kong/go-kong/pull/252)

## [v0.33.0]

Expand Down
2 changes: 2 additions & 0 deletions kong/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Client struct {
workspace string // Do not access directly. Use Workspace()/SetWorkspace().
workspaceLock sync.RWMutex // Synchronizes access to workspace.
common service
Configs AbstractConfigService
Consumers AbstractConsumerService
Developers AbstractDeveloperService
DeveloperRoles AbstractDeveloperRoleService
Expand Down Expand Up @@ -127,6 +128,7 @@ func NewClient(baseURL *string, client *http.Client) (*Client, error) {
kong.defaultRootURL = url.String()

kong.common.client = kong
kong.Configs = (*ConfigService)(&kong.common)
kong.Consumers = (*ConsumerService)(&kong.common)
kong.Developers = (*DeveloperService)(&kong.common)
kong.DeveloperRoles = (*DeveloperRoleService)(&kong.common)
Expand Down
65 changes: 65 additions & 0 deletions kong/config_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//nolint:lll
package kong

import (
"context"
"fmt"
"io"
)

// AbstractConfigService handles Config in Kong.
type AbstractConfigService interface {
// ReloadDeclarativeRawConfig sends out the specified config to configured Admin
// API endpoint using the provided reader which should contain the JSON
// serialized body that adheres to the configuration format specified at:
// https://docs.konghq.com/gateway/latest/production/deployment-topologies/db-less-and-declarative-config/#declarative-configuration-format
ReloadDeclarativeRawConfig(ctx context.Context, config io.Reader, checkHash bool) error
}

// ConfigService handles Config in Kong.
type ConfigService service

// ReloadDeclarativeRawConfig sends out the specified config to configured Admin
// API endpoint using the provided reader which should contain the JSON
// serialized body that adheres to the configuration format specified at:
// https://docs.konghq.com/gateway/latest/production/deployment-topologies/db-less-and-declarative-config/#declarative-configuration-format
func (c *ConfigService) ReloadDeclarativeRawConfig(
ctx context.Context,
config io.Reader,
checkHash bool,
) error {
type sendConfigParams struct {
CheckHash int `url:"check_hash"`
}
var checkHashI int
if checkHash {
checkHashI = 1
}
req, err := c.client.NewRequest("POST", "/config", sendConfigParams{CheckHash: checkHashI}, config)
if err != nil {
return fmt.Errorf("creating new HTTP request for /config: %w", err)
}

resp, err := c.client.DoRAW(ctx, req)
if err != nil {
return fmt.Errorf("failed posting new config to /config: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode < 200 || resp.StatusCode >= 400 {
b, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf(
"failed posting new config to /config: got status code %d (and failed to read the response body): %w",
resp.StatusCode, err,
)
}

return fmt.Errorf(
"failed posting new config to /config: got status code %d, body: %s",
resp.StatusCode, b,
)
}

return nil
}
84 changes: 84 additions & 0 deletions kong/config_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package kong

import (
"bytes"
"context"
"encoding/json"
"testing"

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

func TestConfigService(t *testing.T) {
RunWhenDBMode(t, "off")

tests := []struct {
name string
config Configuration
wantErr bool
}{
{
name: "basic config works",
config: Configuration{
"_format_version": "1.1",
"services": []Configuration{
{
"host": "mockbin.com",
"port": 443,
"protocol": "https",
"routes": []Configuration{
{"paths": []string{"/"}},
},
},
},
},
wantErr: false,
},
{
name: "missing _format_version fails",
config: Configuration{
"services": []Configuration{
{
"host": "mockbin.com",
"port": 443,
"protocol": "https",
"routes": []Configuration{
{"paths": []string{"/"}},
},
},
},
},
wantErr: true,
},
{
name: "invalid config fails",
config: Configuration{
"dummy_key": []Configuration{
{
"host": "mockbin.com",
"port": 443,
"protocol": "https",
},
},
},
wantErr: true,
},
}

for _, tt := range tests {
client, err := NewTestClient(nil, nil)
require.NoError(t, err)
require.NotNil(t, client)

tt := tt
t.Run("with_schema/"+tt.name, func(t *testing.T) {
ctx := context.Background()
b, err := json.Marshal(tt.config)
require.NoError(t, err)

if err := client.Configs.ReloadDeclarativeRawConfig(ctx, bytes.NewBuffer(b), true); (err != nil) != tt.wantErr {
t.Errorf("Client.SendConfig() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
22 changes: 16 additions & 6 deletions kong/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/google/go-querystring/query"
Expand All @@ -17,17 +18,26 @@ func (c *Client) NewRequestRaw(method, baseURL string, endpoint string, qs inter
return nil, fmt.Errorf("endpoint can't be nil")
}
// body to be sent in JSON
var buf []byte
var r io.Reader
if body != nil {
var err error
buf, err = json.Marshal(body)
if err != nil {
return nil, err
switch v := body.(type) {
case string:
r = bytes.NewBufferString(v)
case []byte:
r = bytes.NewBuffer(v)
case io.Reader:
r = v
default:
b, err := json.Marshal(body)
if err != nil {
return nil, err
}
r = bytes.NewBuffer(b)
}
}

// Create a new request
req, err := http.NewRequest(method, baseURL+endpoint, bytes.NewBuffer(buf))
req, err := http.NewRequest(method, baseURL+endpoint, r)
if err != nil {
return nil, err
}
Expand Down
92 changes: 92 additions & 0 deletions kong/request_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package kong

import (
"bytes"
"io"
"testing"

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

func TestNewRequestBody(t *testing.T) {
t.Run("body can be string", func(t *testing.T) {
cl, err := NewClient(nil, nil)
require.NoError(t, err)

body := `{"_format_version":"1.1","services":[{"host":"example.com","name":"foo"}]}`

req, err := cl.NewRequest("POST", "/", nil, body)
require.NoError(t, err)

b, err := io.ReadAll(req.Body)
require.NoError(t, err)

assert.Equal(t,
`{"_format_version":"1.1","services":[{"host":"example.com","name":"foo"}]}`,
string(b),
)
})

t.Run("body can be []byte", func(t *testing.T) {
cl, err := NewClient(nil, nil)
require.NoError(t, err)

body := []byte(`{"_format_version":"1.1","services":[{"host":"example.com","name":"foo"}]}`)

req, err := cl.NewRequest("POST", "/", nil, body)
require.NoError(t, err)

b, err := io.ReadAll(req.Body)
require.NoError(t, err)

assert.Equal(t,
`{"_format_version":"1.1","services":[{"host":"example.com","name":"foo"}]}`,
string(b),
)
})

t.Run("body can be a bytes.Buffer", func(t *testing.T) {
cl, err := NewClient(nil, nil)
require.NoError(t, err)

body := bytes.NewBufferString(`{"_format_version":"1.1","services":[{"host":"example.com","name":"foo"}]}`)

req, err := cl.NewRequest("POST", "/", nil, body)
require.NoError(t, err)

b, err := io.ReadAll(req.Body)
require.NoError(t, err)

assert.Equal(t,
`{"_format_version":"1.1","services":[{"host":"example.com","name":"foo"}]}`,
string(b),
)
})

t.Run("body can be a map", func(t *testing.T) {
cl, err := NewClient(nil, nil)
require.NoError(t, err)

body := map[string]any{
"_format_version": "1.1",
"services": []map[string]any{
{
"host": "example.com",
"name": "foo",
},
},
}

req, err := cl.NewRequest("POST", "/", nil, body)
require.NoError(t, err)

b, err := io.ReadAll(req.Body)
require.NoError(t, err)

assert.Equal(t,
`{"_format_version":"1.1","services":[{"host":"example.com","name":"foo"}]}`,
string(b),
)
})
}
39 changes: 39 additions & 0 deletions kong/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,42 @@ func NewTestClient(baseURL *string, client *http.Client) (*Client, error) {
}
return NewClient(baseURL, client)
}

func RunWhenDBMode(t *testing.T, dbmode string) {
client, err := NewTestClient(nil, nil)
if err != nil {
t.Error(err)
}
info, err := client.Root(defaultCtx)
if err != nil {
t.Error(err)
}

config, ok := info["configuration"]
if !ok {
t.Logf("failed to find 'configuration' config key in kong configuration")
t.Skip()
}

configuration, ok := config.(map[string]any)
if !ok {
t.Logf("'configuration' key is not a map but %T", config)
t.Skip()
}

dbConfig, ok := configuration["database"]
if !ok {
t.Logf("failed to find 'database' config key in kong confiration")
t.Skip()
}

dbMode, ok := dbConfig.(string)
if !ok {
t.Logf("'database' config key is not a string but %T", dbConfig)
t.Skip()
}

if dbMode != dbmode {
t.Skip()
}
}

0 comments on commit 3e8fc96

Please sign in to comment.