Skip to content

Commit 2860059

Browse files
onematchfoxkevholditch
authored andcommitted
Add support for Targets (kevholditch#35)
* Implemented support for targets Implemented methods listed under https://docs.konghq.com/1.0.x/admin-api/#target-object barring List All Targets * go-imports * Upgrade Kong to 1.0.1 * Fix "DNS issues" on health status test Replace foo.com:443 with www.example.com:80 Extended interval and number of checks before status change to stop kong interfering in simple test case * Kong 1.0.2 * Give Kong time to create balancer and healthchecks * Not just random sleep Slight improvement on test "hack". Sleep until we see that Kong reports that healthchecks are on for a target before we test the ability to mark the target as healthy/unhealthy * Target health test Check for DNS_ERROR as well as HEALTHCHECKS_OFF when waiting Adjust to use overload with ids when setting health * Commented test Sadly had to resort to this... Left existing code there, perhaps someone else feels like figuring out a way to test this in a more robust manner. * go-imports
1 parent 843b09e commit 2860059

6 files changed

+472
-2
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ services:
77
- docker
88
env:
99
matrix:
10-
- KONG_VERSION=1.0.0
10+
- KONG_VERSION=1.0.2

README.md

+39
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,45 @@ updateUpstreamRequest := &gokong.UpstreamRequest{
593593
updatedUpstream, err := gokong.NewClient(gokong.NewDefaultConfig()).Upstreams().UpdateByName("test-upstream", updateUpstreamRequest)
594594
```
595595

596+
## Targets
597+
Create a target for an upstream ([for more information on the Target Fields see the Kong documentation](https://getkong.org/docs/0.13.x/admin-api/#upstream-objects)):
598+
```go
599+
targetRequest := &TargetRequest{
600+
Target: "foo.com:443",
601+
Weight: 100,
602+
}
603+
createdTarget, err := gokong.NewClient(gokong.NewDefaultConfig()).Targets().CreateFromUpstreamId("upstreamId", targetRequest)
604+
```
605+
606+
List all targets for an upstream
607+
```go
608+
targets, err := gokong.NewClient(gokong.NewDefaultConfig()).Targets().GetTargetsFromUpstreamId("upstreamId")
609+
```
610+
611+
Delete a target from an upstream
612+
```go
613+
targets, err := gokong.NewClient(gokong.NewDefaultConfig()).Targets().DeleteFromUpstreamById("upstreamId")
614+
```
615+
616+
Set target as healthy
617+
```go
618+
targets, err := gokong.NewClient(gokong.NewDefaultConfig()).Targets().SetTargetFromUpstreamByIdAsHealthy("upstreamId")
619+
```
620+
621+
Set target as unhealthy
622+
```go
623+
targets, err := gokong.NewClient(gokong.NewDefaultConfig()).Targets().SetTargetFromUpstreamByIdAsUnhealthy("upstreamId")
624+
```
625+
626+
List all targets for an upstream (including health status)
627+
```go
628+
targets, err := gokong.NewClient(gokong.NewDefaultConfig()).Targets().GetTargetsWithHealthFromUpstreamName(upstreamId)
629+
```
630+
631+
**Notes**: Target methods listed above are overloaded in the same fashion as other objects exposed by this library. For parameters:
632+
- upstream - either name of id can be used
633+
- target - either id or target name (host:port) can be used
634+
596635
# Contributing
597636
I would love to get contributions to the project so please feel free to submit a PR. To setup your dev station you need go and docker installed.
598637

client.go

+6
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,9 @@ func (kongAdminClient *KongAdminClient) Services() *ServiceClient {
137137
config: kongAdminClient.config,
138138
}
139139
}
140+
141+
func (kongAdminClient *KongAdminClient) Targets() *TargetClient {
142+
return &TargetClient{
143+
config: kongAdminClient.config,
144+
}
145+
}

client_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/stretchr/testify/assert"
1515
)
1616

17-
const defaultKongVersion = "1.0.0"
17+
const defaultKongVersion = "1.0.2"
1818
const kong401Server = "KONG_401_SERVER"
1919

2020
func Test_Newclient(t *testing.T) {

targets.go

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package gokong
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
type TargetClient struct {
9+
config *Config
10+
}
11+
12+
type TargetRequest struct {
13+
Target string `json:"target"`
14+
Weight int `json:"weight"`
15+
}
16+
17+
type Target struct {
18+
Id *string `json:"id,omitempty"`
19+
CreatedAt *float32 `json:"created_at"`
20+
Target *string `json:"target"`
21+
Weight *int `json:"weight"`
22+
Upstream *Id `json:"upstream"`
23+
Health *string `json:"health"`
24+
}
25+
26+
type Targets struct {
27+
Data []*Target `json:"data"`
28+
Total int `json:"total,omitempty"`
29+
Next string `json:"next,omitempty"`
30+
NodeId string `json:"node_id,omitempty"`
31+
}
32+
33+
const TargetsPath = "/upstreams/%s/targets"
34+
35+
func (targetClient *TargetClient) CreateFromUpstreamName(name string, targetRequest *TargetRequest) (*Target, error) {
36+
return targetClient.CreateFromUpstreamId(name, targetRequest)
37+
}
38+
39+
func (targetClient *TargetClient) CreateFromUpstreamId(id string, targetRequest *TargetRequest) (*Target, error) {
40+
r, body, errs := newPost(targetClient.config, targetClient.config.HostAddress+fmt.Sprintf(TargetsPath, id)).Send(targetRequest).End()
41+
if errs != nil {
42+
return nil, fmt.Errorf("could not register the target, error: %v", errs)
43+
}
44+
45+
if r.StatusCode == 401 || r.StatusCode == 403 {
46+
return nil, fmt.Errorf("not authorised, message from kong: %s", body)
47+
}
48+
49+
createdTarget := &Target{}
50+
err := json.Unmarshal([]byte(body), createdTarget)
51+
if err != nil {
52+
return nil, fmt.Errorf("could not parse target get response, error: %v", err)
53+
}
54+
55+
if createdTarget.Id == nil {
56+
return nil, fmt.Errorf("could not register the target, error: %v", body)
57+
}
58+
59+
return createdTarget, nil
60+
}
61+
62+
func (targetClient *TargetClient) GetTargetsFromUpstreamName(name string) ([]*Target, error) {
63+
return targetClient.GetTargetsFromUpstreamId(name)
64+
}
65+
66+
func (targetClient *TargetClient) GetTargetsFromUpstreamId(id string) ([]*Target, error) {
67+
targets := []*Target{}
68+
data := &Targets{}
69+
70+
for {
71+
r, body, errs := newGet(targetClient.config, targetClient.config.HostAddress+fmt.Sprintf(TargetsPath, id)).End()
72+
if errs != nil {
73+
return nil, fmt.Errorf("could not get targets, error: %v", errs)
74+
}
75+
76+
if r.StatusCode == 401 || r.StatusCode == 403 {
77+
return nil, fmt.Errorf("not authorised, message from kong: %s", body)
78+
}
79+
80+
if r.StatusCode == 404 {
81+
return nil, fmt.Errorf("non existent upstream: %s", id)
82+
}
83+
84+
err := json.Unmarshal([]byte(body), data)
85+
if err != nil {
86+
return nil, fmt.Errorf("could not parse target get response, error: %v", err)
87+
}
88+
89+
targets = append(targets, data.Data...)
90+
91+
if data.Next == "" {
92+
break
93+
}
94+
}
95+
return targets, nil
96+
}
97+
98+
func (targetClient *TargetClient) DeleteFromUpstreamByHostPort(upstreamNameOrId string, hostPort string) error {
99+
return targetClient.DeleteFromUpstreamById(upstreamNameOrId, hostPort)
100+
}
101+
102+
func (targetClient *TargetClient) DeleteFromUpstreamById(upstreamNameOrId string, id string) error {
103+
r, body, errs := newDelete(targetClient.config, targetClient.config.HostAddress+fmt.Sprintf(TargetsPath, upstreamNameOrId)+fmt.Sprintf("/%s", id)).End()
104+
if errs != nil {
105+
return fmt.Errorf("could not delete the target, result: %v error: %v", r, errs)
106+
}
107+
108+
if r.StatusCode == 401 || r.StatusCode == 403 {
109+
return fmt.Errorf("not authorised, message from kong: %s", body)
110+
}
111+
112+
if r.StatusCode != 204 {
113+
return fmt.Errorf("Received unexpected response status code: %d. Body: %s", r.StatusCode, body)
114+
}
115+
116+
return nil
117+
}
118+
119+
func (targetClient *TargetClient) SetTargetFromUpstreamByHostPortAsHealthy(upstreamNameOrId string, hostPort string) error {
120+
return targetClient.SetTargetFromUpstreamByIdAsHealthy(upstreamNameOrId, hostPort)
121+
}
122+
123+
func (targetClient *TargetClient) SetTargetFromUpstreamByIdAsHealthy(upstreamNameOrId string, id string) error {
124+
r, body, errs := newPost(targetClient.config, targetClient.config.HostAddress+fmt.Sprintf(TargetsPath, upstreamNameOrId)+fmt.Sprintf("/%s/healthy", id)).Send("").End()
125+
if errs != nil {
126+
return fmt.Errorf("could not set the target as healthy, result: %v error: %v", r, errs)
127+
}
128+
129+
if r.StatusCode == 401 || r.StatusCode == 403 {
130+
return fmt.Errorf("not authorised, message from kong: %s", body)
131+
}
132+
133+
if r.StatusCode != 204 {
134+
return fmt.Errorf("Received unexpected response status code: %d. Body: %s", r.StatusCode, body)
135+
}
136+
137+
return nil
138+
}
139+
140+
func (targetClient *TargetClient) SetTargetFromUpstreamByHostPortAsUnhealthy(upstreamNameOrId string, hostPort string) error {
141+
return targetClient.SetTargetFromUpstreamByIdAsUnhealthy(upstreamNameOrId, hostPort)
142+
}
143+
144+
func (targetClient *TargetClient) SetTargetFromUpstreamByIdAsUnhealthy(upstreamNameOrId string, id string) error {
145+
r, body, errs := newPost(targetClient.config, targetClient.config.HostAddress+fmt.Sprintf(TargetsPath, upstreamNameOrId)+fmt.Sprintf("/%s/unhealthy", id)).Send("").End()
146+
if errs != nil {
147+
return fmt.Errorf("could not set the target as unhealthy, result: %v error: %v", r, errs)
148+
}
149+
150+
if r.StatusCode == 401 || r.StatusCode == 403 {
151+
return fmt.Errorf("not authorised, message from kong: %s", body)
152+
}
153+
154+
if r.StatusCode != 204 {
155+
return fmt.Errorf("Received unexpected response status code: %d. Body: %s", r.StatusCode, body)
156+
}
157+
158+
return nil
159+
}
160+
161+
func (targetClient *TargetClient) GetTargetsWithHealthFromUpstreamName(name string) ([]*Target, error) {
162+
return targetClient.GetTargetsWithHealthFromUpstreamId(name)
163+
}
164+
165+
func (targetClient *TargetClient) GetTargetsWithHealthFromUpstreamId(id string) ([]*Target, error) {
166+
targets := []*Target{}
167+
data := &Targets{}
168+
169+
for {
170+
r, body, errs := newGet(targetClient.config, targetClient.config.HostAddress+fmt.Sprintf("/upstreams/%s/health", id)).End()
171+
if errs != nil {
172+
return nil, fmt.Errorf("could not get targets, error: %v", errs)
173+
}
174+
175+
if r.StatusCode == 401 || r.StatusCode == 403 {
176+
return nil, fmt.Errorf("not authorised, message from kong: %s", body)
177+
}
178+
179+
if r.StatusCode == 404 {
180+
return nil, fmt.Errorf("non existent upstream: %s", id)
181+
}
182+
183+
err := json.Unmarshal([]byte(body), data)
184+
if err != nil {
185+
return nil, fmt.Errorf("could not parse target get response, error: %v", err)
186+
}
187+
188+
targets = append(targets, data.Data...)
189+
190+
if data.Next == "" {
191+
break
192+
}
193+
}
194+
return targets, nil
195+
}
196+
197+
// TODO: Implement List all Targets - https://docs.konghq.com/1.0.x/admin-api/#list-all-targets
198+
// Note: JSON returned by this method has slightly different structure to the standard Get request. This method retruns "upstream_id": "07131005-ba30-4204-a29f-0927d53257b4" instead of "upstream": {"id":"127dfc88-ed57-45bf-b77a-a9d3a152ad31"},
199+
200+
// TODO: Implement other methods available by /targets paths
201+
// https://docs.konghq.com/1.0.x/admin-api/#update-or-create-upstream
202+
// https://docs.konghq.com/1.0.x/admin-api/#delete-upstream

0 commit comments

Comments
 (0)