Skip to content

Commit c5eb716

Browse files
committed
Implement POSTing request bodies to the endpoint.
This adds support to the APIClient generator to generate by posting a body against an HTTP endpoint.
1 parent a39057a commit c5eb716

File tree

6 files changed

+124
-14
lines changed

6 files changed

+124
-14
lines changed

api/v1alpha1/gitopsset_types.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ type APIClientGenerator struct {
7575
Interval metav1.Duration `json:"interval"`
7676

7777
// This is the API endpoint to use.
78-
// +kubebuilder:validation:Pattern="^https://"
78+
// +kubebuilder:validation:Pattern="^(http|https)://"
7979
// +optional
8080
Endpoint string `json:"endpoint,omitempty"`
8181

@@ -99,6 +99,11 @@ type APIClientGenerator struct {
9999
//
100100
// +optional
101101
HeadersRef *HeadersReference `json:"headersRef"`
102+
103+
// Body is set as the body in a POST request.
104+
//
105+
// If set, this will configure the Method to be POST automatically.
106+
Body *apiextensionsv1.JSON `json:"body"`
102107
}
103108

104109
// HeadersReference references either a Secret or ConfigMap to be used for

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/templates.weave.works_gitopssets.yaml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ spec:
5858
description: APIClientGenerator defines a generator that queries
5959
an API endpoint and uses that to generate data.
6060
properties:
61+
body:
62+
description: "Body is set as the body in a POST request.
63+
\n If set, this will configure the Method to be POST automatically."
64+
x-kubernetes-preserve-unknown-fields: true
6165
endpoint:
6266
description: This is the API endpoint to use.
63-
pattern: ^https://
67+
pattern: ^(http|https)://
6468
type: string
6569
headersRef:
6670
description: "HeadersRef allows optional configuration of
@@ -100,6 +104,7 @@ spec:
100104
- POST
101105
type: string
102106
required:
107+
- body
103108
- interval
104109
type: object
105110
cluster:
@@ -222,9 +227,14 @@ spec:
222227
that queries an API endpoint and uses that to generate
223228
data.
224229
properties:
230+
body:
231+
description: "Body is set as the body in a POST
232+
request. \n If set, this will configure the
233+
Method to be POST automatically."
234+
x-kubernetes-preserve-unknown-fields: true
225235
endpoint:
226236
description: This is the API endpoint to use.
227-
pattern: ^https://
237+
pattern: ^(http|https)://
228238
type: string
229239
headersRef:
230240
description: "HeadersRef allows optional configuration
@@ -267,6 +277,7 @@ spec:
267277
- POST
268278
type: string
269279
required:
280+
- body
270281
- interval
271282
type: object
272283
cluster:

controllers/templates/generators/apiclient/api_client.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package apiclient
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
67
"fmt"
@@ -97,11 +98,25 @@ func (g *APIClientGenerator) Interval(sg *templatesv1.GitOpsSetGenerator) time.D
9798
}
9899

99100
func (g *APIClientGenerator) createRequest(ctx context.Context, ac *templatesv1.APIClientGenerator, namespace string) (*http.Request, error) {
100-
req, err := http.NewRequestWithContext(ctx, ac.Method, ac.Endpoint, nil)
101+
method := ac.Method
102+
if ac.Body != nil {
103+
method = http.MethodPost
104+
}
105+
106+
var body io.Reader
107+
if ac.Body != nil {
108+
body = bytes.NewReader(ac.Body.Raw)
109+
}
110+
111+
req, err := http.NewRequestWithContext(ctx, method, ac.Endpoint, body)
101112
if err != nil {
102113
return nil, err
103114
}
104115

116+
if body != nil {
117+
req.Header.Set("Content-Type", "application/json")
118+
}
119+
105120
if ac.HeadersRef != nil {
106121
if ac.HeadersRef.Kind == "Secret" {
107122
return req, addHeadersFromSecretToRequest(ctx, g.Client, req, client.ObjectKey{Name: ac.HeadersRef.Name, Namespace: namespace})

controllers/templates/generators/apiclient/api_client_test.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,17 @@ import (
1111

1212
"github.com/go-logr/logr"
1313
"github.com/google/go-cmp/cmp"
14-
templatesv1 "github.com/weaveworks/gitopssets-controller/api/v1alpha1"
15-
"github.com/weaveworks/gitopssets-controller/controllers/templates/generators"
16-
"github.com/weaveworks/gitopssets-controller/test"
1714
corev1 "k8s.io/api/core/v1"
15+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1816
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1917
"k8s.io/apimachinery/pkg/runtime"
2018
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
2119
"sigs.k8s.io/controller-runtime/pkg/client"
2220
"sigs.k8s.io/controller-runtime/pkg/client/fake"
21+
22+
templatesv1 "github.com/weaveworks/gitopssets-controller/api/v1alpha1"
23+
"github.com/weaveworks/gitopssets-controller/controllers/templates/generators"
24+
"github.com/weaveworks/gitopssets-controller/test"
2325
)
2426

2527
var _ generators.Generator = (*APIClientGenerator)(nil)
@@ -85,6 +87,20 @@ func TestGenerate(t *testing.T) {
8587
},
8688
},
8789
},
90+
{
91+
name: "simple API endpoint with POST body request",
92+
apiClient: &templatesv1.APIClientGenerator{
93+
Endpoint: ts.URL + "/api/post-body",
94+
Method: http.MethodPost,
95+
Body: &apiextensionsv1.JSON{Raw: []byte(`{"user":"demo","groups":["group1"]}`)},
96+
},
97+
want: []map[string]any{
98+
{
99+
"name": "demo",
100+
"groups": []any{"group1"},
101+
},
102+
},
103+
},
88104
{
89105
name: "api endpoint returning map with JSONPath",
90106
apiClient: &templatesv1.APIClientGenerator{
@@ -370,6 +386,33 @@ func newTestMux(t *testing.T) *http.ServeMux {
370386
writeResponse(w)
371387
})
372388

389+
mux.HandleFunc("/api/post-body", func(w http.ResponseWriter, r *http.Request) {
390+
if r.Method != http.MethodPost {
391+
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
392+
return
393+
}
394+
if r.Header.Get("Content-Type") != "application/json" {
395+
http.Error(w, "unsupported content type", http.StatusUnsupportedMediaType)
396+
return
397+
}
398+
var body map[string]any
399+
decoder := json.NewDecoder(r.Body)
400+
if err := decoder.Decode(&body); err != nil {
401+
http.Error(w, "invalid json "+err.Error(), http.StatusBadRequest)
402+
return
403+
}
404+
405+
enc := json.NewEncoder(w)
406+
if err := enc.Encode([]map[string]any{
407+
{
408+
"name": body["user"],
409+
"groups": body["groups"],
410+
},
411+
}); err != nil {
412+
t.Fatal(err)
413+
}
414+
})
415+
373416
mux.HandleFunc("/api/non-array", func(w http.ResponseWriter, r *http.Request) {
374417
enc := json.NewEncoder(w)
375418
if err := enc.Encode(map[string]any{

docs/README.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,11 @@ In this case, six different `ConfigMaps` are generated, three for the "dev-team"
131131
## Generators
132132

133133
We currently provide these generators:
134-
135-
- list
136-
- pullRequests
137-
- gitRepository
138-
- matrix
139-
- apiClient
134+
- [list](#list-generator)
135+
- [pullRequests](#pullrequests-generator)
136+
- [gitRepository](#gitrepository-generator)
137+
- [matrix](#matrix-generator)
138+
- [apiClient](#apiclient-generator)
140139

141140
### List generator
142141

@@ -537,7 +536,7 @@ Not all APIs return an array of JSON objects, sometimes it's nested within a res
537536
}
538537
```
539538
You can use JSONPath to extract the fields from this data...
540-
```
539+
```yaml
541540
apiVersion: templates.weave.works/v1alpha1
542541
kind: GitOpsSet
543542
metadata:
@@ -557,6 +556,38 @@ spec:
557556
```
558557
This will generate three maps for templates, with just the _env_ and _team_ keys.
559558

559+
#### APIClient POST body
560+
561+
Another piece of functionality in the APIClient generator is the ability to POST
562+
JSON to the API.
563+
```yaml
564+
apiVersion: templates.weave.works/v1alpha1
565+
kind: GitOpsSet
566+
metadata:
567+
labels:
568+
app.kubernetes.io/name: gitopsset
569+
app.kubernetes.io/instance: gitopsset-sample
570+
app.kubernetes.io/part-of: gitopssets-controller
571+
app.kubernetes.io/managed-by: kustomize
572+
app.kubernetes.io/created-by: gitopssets-controller
573+
name: api-client-sample
574+
spec:
575+
generators:
576+
- apiClient:
577+
interval: 5m
578+
endpoint: https://api.example.com/demo
579+
body:
580+
name: "testing"
581+
value: "testing2"
582+
```
583+
This will send a request body as JSON (Content-Type "application/json") to the
584+
server and interpret the result.
585+
586+
The JSON body sent will look like this:
587+
```json
588+
{"name":"testing","value":"testing2"}
589+
```
590+
560591
## Templating functions
561592

562593
Currently, the [Sprig](http://masterminds.github.io/sprig/) functions are available in the templating, with some functions removed[^sprig] for security reasons.

0 commit comments

Comments
 (0)