Skip to content

Commit 4e40da8

Browse files
feat: integrate oas specification getting from configmap (#10)
1 parent d64571b commit 4e40da8

File tree

5 files changed

+116
-13
lines changed

5 files changed

+116
-13
lines changed

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/lucasepe/httplib v0.2.2
99
github.com/pb33f/libopenapi v0.16.8
1010
github.com/rs/zerolog v1.32.0
11+
k8s.io/api v0.31.1
1112
k8s.io/apimachinery v0.31.1
1213
k8s.io/client-go v0.31.1
1314
sigs.k8s.io/controller-runtime v0.19.1
@@ -41,6 +42,7 @@ require (
4142
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4243
github.com/modern-go/reflect2 v1.0.2 // indirect
4344
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
45+
github.com/pkg/errors v0.9.1 // indirect
4446
github.com/spf13/pflag v1.0.5 // indirect
4547
github.com/twmb/murmur3 v1.1.8 // indirect
4648
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
@@ -57,10 +59,10 @@ require (
5759
golang.org/x/text v0.17.0 // indirect
5860
golang.org/x/time v0.7.0 // indirect
5961
google.golang.org/protobuf v1.34.2 // indirect
62+
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
6063
gopkg.in/inf.v0 v0.9.1 // indirect
6164
gopkg.in/yaml.v2 v2.4.0 // indirect
6265
gopkg.in/yaml.v3 v3.0.1 // indirect
63-
k8s.io/api v0.31.1 // indirect
6466
k8s.io/gengo v0.0.0-20240911193312-2b36238f13e9 // indirect
6567
k8s.io/klog/v2 v2.130.1 // indirect
6668
k8s.io/kube-openapi v0.0.0-20240822171749-76de80e0abd9 // indirect

internal/client/clienttools.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package restclient
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
7+
"net/http"
68
"net/url"
79
"os"
810
"path"
@@ -16,6 +18,7 @@ import (
1618
"github.com/pb33f/libopenapi/datamodel/high/base"
1719
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
1820
orderedmap "github.com/pb33f/libopenapi/orderedmap"
21+
"k8s.io/client-go/dynamic"
1922
)
2023

2124
type APICallType string
@@ -241,14 +244,20 @@ func (u *UnstructuredClient) RequestedParams(httpMethod string, path string) (pa
241244
}
242245

243246
// BuildClient is a function that builds partial client from a swagger file.
244-
func BuildClient(swaggerPath string) (*UnstructuredClient, error) {
247+
func BuildClient(ctx context.Context, kubeclient dynamic.Interface, swaggerPath string) (*UnstructuredClient, error) {
245248
basePath := "/tmp/rest-dynamic-controller"
246249
err := os.MkdirAll(basePath, 0755)
247250
defer os.RemoveAll(basePath)
248251
if err != nil {
249252
return nil, fmt.Errorf("failed to create directory: %w", err)
250253
}
251-
err = fgetter.GetFile(filepath.Join(basePath, filepath.Base(swaggerPath)), swaggerPath, nil)
254+
255+
fgetter := &fgetter.Filegetter{
256+
Client: http.DefaultClient,
257+
KubeClient: kubeclient,
258+
}
259+
260+
err = fgetter.GetFile(ctx, filepath.Join(basePath, filepath.Base(swaggerPath)), swaggerPath, nil)
252261
if err != nil {
253262
return nil, fmt.Errorf("failed to download file: %w", err)
254263
}

internal/restResources/restResources.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (h *handler) Observe(ctx context.Context, mg *unstructured.Unstructured) (c
8686
return controller.ExternalObservation{}, err
8787
}
8888

89-
cli, err := restclient.BuildClient(clientInfo.URL)
89+
cli, err := restclient.BuildClient(ctx, h.dynamicClient, clientInfo.URL)
9090
if err != nil {
9191
log.Debug("Building REST client", "error", err)
9292
return controller.ExternalObservation{}, err
@@ -258,7 +258,7 @@ func (h *handler) Create(ctx context.Context, mg *unstructured.Unstructured) err
258258
return err
259259
}
260260

261-
cli, err := restclient.BuildClient(clientInfo.URL)
261+
cli, err := restclient.BuildClient(ctx, h.dynamicClient, clientInfo.URL)
262262
if err != nil {
263263
log.Debug("Building REST client", "error", err)
264264
return err
@@ -328,7 +328,7 @@ func (h *handler) Update(ctx context.Context, mg *unstructured.Unstructured) err
328328
return err
329329
}
330330

331-
cli, err := restclient.BuildClient(clientInfo.URL)
331+
cli, err := restclient.BuildClient(ctx, h.dynamicClient, clientInfo.URL)
332332
if err != nil {
333333
log.Debug("Building REST client", "error", err)
334334
return err
@@ -415,7 +415,7 @@ func (h *handler) Delete(ctx context.Context, mg *unstructured.Unstructured) err
415415
return err
416416
}
417417

418-
cli, err := restclient.BuildClient(clientInfo.URL)
418+
cli, err := restclient.BuildClient(ctx, h.dynamicClient, clientInfo.URL)
419419
if err != nil {
420420
log.Debug("Building REST client", "error", err)
421421
return err

internal/tools/filegetter/filegetter.go

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package filegetter
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"net/http"
78
"os"
89
"strings"
10+
11+
v1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/runtime"
14+
"k8s.io/apimachinery/pkg/runtime/schema"
15+
"k8s.io/client-go/dynamic"
916
)
1017

1118
// AuthType represents the type of authentication
@@ -25,16 +32,22 @@ type AuthConfig struct {
2532
Token string
2633
}
2734

35+
type Filegetter struct {
36+
Client *http.Client
37+
KubeClient dynamic.Interface
38+
}
39+
2840
// GetFile gets a file from a source and writes it to a destination.
29-
func GetFile(dst string, src string, auth *AuthConfig) error {
41+
func (cli *Filegetter) GetFile(ctx context.Context, dst string, src string, auth *AuthConfig) error {
3042
var reader io.Reader
3143
var err error
3244

45+
if cli.Client == nil || cli.KubeClient == nil {
46+
return fmt.Errorf("http client or kube client not set")
47+
}
48+
3349
// Check if the source is a URL or a local file
3450
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
35-
// Create a new HTTP client
36-
client := &http.Client{}
37-
3851
// Create a new request
3952
req, err := http.NewRequest("GET", src, nil)
4053
if err != nil {
@@ -52,7 +65,7 @@ func GetFile(dst string, src string, auth *AuthConfig) error {
5265
}
5366

5467
// Send the request
55-
resp, err := client.Do(req)
68+
resp, err := cli.Client.Do(req)
5669
if err != nil {
5770
return fmt.Errorf("error downloading file: %v", err)
5871
}
@@ -63,6 +76,41 @@ func GetFile(dst string, src string, auth *AuthConfig) error {
6376
}
6477

6578
reader = resp.Body
79+
} else if strings.HasPrefix(src, "configmap://") {
80+
configmapString := strings.TrimPrefix(src, "configmap://")
81+
configmapParts := strings.Split(configmapString, "/")
82+
if len(configmapParts) != 3 {
83+
return fmt.Errorf("invalid configmap source: %s - must be formatted as configmap://<namespace>/<name>/<key>", src)
84+
}
85+
namespace := configmapParts[0]
86+
name := configmapParts[1]
87+
key := configmapParts[2]
88+
89+
// Get the configmap name and key
90+
91+
var cm v1.ConfigMap
92+
93+
uns, err := cli.KubeClient.Resource(schema.GroupVersionResource{
94+
Group: "",
95+
Version: "v1",
96+
Resource: "configmaps",
97+
}).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
98+
if err != nil {
99+
return fmt.Errorf("error getting configmap: %v", err)
100+
}
101+
102+
runtime.DefaultUnstructuredConverter.FromUnstructured(uns.Object, &cm)
103+
104+
if err != nil {
105+
return fmt.Errorf("error getting configmap: %v", err)
106+
}
107+
108+
data, ok := cm.Data[key]
109+
if !ok {
110+
return fmt.Errorf("key not found in configmap: %s", key)
111+
}
112+
113+
reader = strings.NewReader(data)
66114
} else {
67115
// Open local file
68116
file, err := os.Open(src)

internal/tools/filegetter/filegetter_test.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
package filegetter
22

33
import (
4+
"context"
45
"fmt"
56
"net/http"
67
"net/http/httptest"
78
"os"
89
"path/filepath"
910
"testing"
11+
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/client-go/dynamic"
14+
15+
corev1 "k8s.io/api/core/v1"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
18+
"k8s.io/client-go/dynamic/fake"
1019
)
1120

1221
func TestGetFile(t *testing.T) {
@@ -17,6 +26,10 @@ func TestGetFile(t *testing.T) {
1726
}
1827
defer os.RemoveAll(tempDir)
1928

29+
var kubeClient dynamic.Interface
30+
scheme := runtime.NewScheme()
31+
corev1.AddToScheme(scheme)
32+
kubeClient = fake.NewSimpleDynamicClient(scheme)
2033
// Test cases
2134
testCases := []struct {
2235
name string
@@ -124,6 +137,32 @@ func TestGetFile(t *testing.T) {
124137
setup: func() string { return "" },
125138
validate: func(string) bool { return true },
126139
},
140+
{
141+
name: "ConfigMap source",
142+
src: "configmap://default/test-configmap/test-key",
143+
auth: nil,
144+
expectError: false,
145+
setup: func() string {
146+
scheme := runtime.NewScheme()
147+
corev1.AddToScheme(scheme)
148+
149+
kubeClient = fake.NewSimpleDynamicClient(scheme, &corev1.ConfigMap{
150+
ObjectMeta: metav1.ObjectMeta{
151+
Name: "test-configmap",
152+
Namespace: "default",
153+
},
154+
Data: map[string]string{
155+
"test-key": "configmap content",
156+
},
157+
})
158+
159+
return "configmap://default/test-configmap/test-key"
160+
},
161+
validate: func(dst string) bool {
162+
content, err := os.ReadFile(dst)
163+
return err == nil && string(content) == "configmap content"
164+
},
165+
},
127166
}
128167

129168
for _, tc := range testCases {
@@ -134,7 +173,12 @@ func TestGetFile(t *testing.T) {
134173
}
135174
dst := filepath.Join(tempDir, "destination.txt")
136175

137-
err := GetFile(dst, tc.src, tc.auth)
176+
filegetter := &Filegetter{
177+
Client: http.DefaultClient,
178+
KubeClient: kubeClient,
179+
}
180+
181+
err := filegetter.GetFile(context.Background(), dst, tc.src, tc.auth)
138182

139183
fmt.Println("source:", tc.src)
140184

0 commit comments

Comments
 (0)