Skip to content
This repository was archived by the owner on Aug 28, 2025. It is now read-only.

Commit b09ad57

Browse files
authored
feat: allowing the configuration of the virtual workspace url directly (#204)
* feat: allowing the configuration of the virtual workspace url directly * test: fixing tests
1 parent e6b3f14 commit b09ad57

File tree

5 files changed

+26
-246
lines changed

5 files changed

+26
-246
lines changed

cmd/listener.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package cmd
22

33
import (
44
"crypto/tls"
5-
"github.com/openmfp/kubernetes-graphql-gateway/listener/discoveryclient"
6-
"k8s.io/client-go/discovery"
75
"os"
86

7+
"k8s.io/client-go/discovery"
8+
9+
"github.com/openmfp/kubernetes-graphql-gateway/listener/discoveryclient"
10+
911
kcpapis "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
1012
kcpcore "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
1113
kcptenancy "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1"
@@ -135,15 +137,7 @@ var listenCmd = &cobra.Command{
135137
OpenAPIDefinitionsPath: appCfg.OpenApiDefinitionsPath,
136138
}
137139

138-
reconciler, err := kcp.NewReconciler(
139-
ctx,
140-
log,
141-
appCfg,
142-
reconcilerOpts,
143-
discoveryInterface,
144-
kcp.PreReconcile,
145-
discoveryclient.NewFactory,
146-
)
140+
reconciler, err := kcp.NewReconciler(appCfg, reconcilerOpts, restCfg, discoveryInterface, kcp.PreReconcile, discoveryclient.NewFactory)
147141

148142
if err != nil {
149143
setupLog.Error(err, "unable to instantiate reconciler")

listener/kcp/reconciler_factory.go

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package kcp
33
import (
44
"context"
55
"errors"
6-
"github.com/openmfp/golang-commons/logger"
76

87
"k8s.io/apimachinery/pkg/api/meta"
98
"k8s.io/apimachinery/pkg/runtime"
@@ -49,20 +48,15 @@ type ReconcilerOpts struct {
4948
OpenAPIDefinitionsPath string
5049
}
5150

52-
func NewReconciler(
53-
ctx context.Context,
54-
log *logger.Logger,
55-
appCfg config.Config,
56-
opts ReconcilerOpts,
51+
func NewReconciler(appCfg config.Config, opts ReconcilerOpts, restcfg *rest.Config,
5752
discoveryInterface discovery.DiscoveryInterface,
5853
preReconcileFunc func(cr *apischema.CRDResolver, io workspacefile.IOHandler) error,
59-
discoverFactory func(cfg *rest.Config) (*discoveryclient.FactoryProvider, error),
60-
) (CustomReconciler, error) {
54+
discoverFactory func(cfg *rest.Config) (*discoveryclient.FactoryProvider, error)) (CustomReconciler, error) {
6155
if !appCfg.EnableKcp {
6256
return newStandardReconciler(opts, discoveryInterface, preReconcileFunc)
6357
}
6458

65-
return newKcpReconciler(ctx, log, appCfg, opts, discoverFactory)
59+
return newKcpReconciler(opts, restcfg, discoverFactory)
6660
}
6761

6862
func newStandardReconciler(
@@ -120,13 +114,7 @@ func PreReconcile(
120114
return nil
121115
}
122116

123-
func newKcpReconciler(
124-
ctx context.Context,
125-
log *logger.Logger,
126-
appCfg config.Config,
127-
opts ReconcilerOpts,
128-
newDiscoveryFactoryFunc func(cfg *rest.Config) (*discoveryclient.FactoryProvider, error),
129-
) (CustomReconciler, error) {
117+
func newKcpReconciler(opts ReconcilerOpts, restcfg *rest.Config, newDiscoveryFactoryFunc func(cfg *rest.Config) (*discoveryclient.FactoryProvider, error)) (CustomReconciler, error) {
130118
ioHandler, err := workspacefile.NewIOHandler(opts.OpenAPIDefinitionsPath)
131119
if err != nil {
132120
return nil, errors.Join(ErrCreateIOHandler, err)
@@ -137,12 +125,7 @@ func newKcpReconciler(
137125
return nil, errors.Join(ErrCreatePathResolver, err)
138126
}
139127

140-
virtualWorkspaceCfg, err := virtualWorkspaceConfigFromCfg(ctx, log, appCfg, opts.Config, opts.Client)
141-
if err != nil {
142-
return nil, errors.Join(ErrGetVWConfig, err)
143-
}
144-
145-
df, err := newDiscoveryFactoryFunc(virtualWorkspaceCfg)
128+
df, err := newDiscoveryFactoryFunc(restcfg)
146129
if err != nil {
147130
return nil, errors.Join(ErrCreateDiscoveryClient, err)
148131
}

listener/kcp/reconciler_factory_test.go

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package kcp
22

33
import (
4-
"context"
54
"errors"
6-
"github.com/openmfp/golang-commons/logger"
5+
"path"
6+
"testing"
7+
78
"github.com/openmfp/kubernetes-graphql-gateway/common/config"
89
"github.com/openmfp/kubernetes-graphql-gateway/listener/clusterpath"
910
"github.com/openmfp/kubernetes-graphql-gateway/listener/kcp/mocks"
10-
"github.com/stretchr/testify/require"
11-
"path"
12-
"testing"
1311

1412
kcpapis "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
1513
"github.com/stretchr/testify/assert"
@@ -73,9 +71,6 @@ func TestNewReconciler(t *testing.T) {
7371
},
7472
}
7573

76-
log, err := logger.New(logger.DefaultConfig())
77-
require.NoError(t, err)
78-
7974
for name, tc := range tests {
8075
scheme := runtime.NewScheme()
8176
assert.NoError(t, kcpapis.AddToScheme(scheme))
@@ -99,25 +94,18 @@ func TestNewReconciler(t *testing.T) {
9994
},
10095
}...).Build()
10196

102-
reconciler, err := NewReconciler(
103-
context.Background(),
104-
log,
105-
appCfg,
106-
ReconcilerOpts{
107-
Config: tc.cfg,
108-
Scheme: scheme,
109-
Client: fakeClient,
110-
OpenAPIDefinitionsPath: tc.definitionsPath,
111-
},
112-
&mocks.MockDiscoveryInterface{},
113-
func(cr *apischema.CRDResolver, io workspacefile.IOHandler) error {
114-
return nil
115-
},
116-
func(cfg *rest.Config) (*discoveryclient.FactoryProvider, error) {
117-
return &discoveryclient.FactoryProvider{
118-
Config: cfg,
119-
}, nil
120-
})
97+
reconciler, err := NewReconciler(appCfg, ReconcilerOpts{
98+
Config: tc.cfg,
99+
Scheme: scheme,
100+
Client: fakeClient,
101+
OpenAPIDefinitionsPath: tc.definitionsPath,
102+
}, tc.cfg, &mocks.MockDiscoveryInterface{}, func(cr *apischema.CRDResolver, io workspacefile.IOHandler) error {
103+
return nil
104+
}, func(cfg *rest.Config) (*discoveryclient.FactoryProvider, error) {
105+
return &discoveryclient.FactoryProvider{
106+
Config: cfg,
107+
}, nil
108+
})
121109

122110
if tc.err != nil {
123111
assert.EqualError(t, err, tc.err.Error())

listener/kcp/workspace_config.go

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,15 @@
11
package kcp
22

33
import (
4-
"context"
54
"errors"
6-
kcptenancy "github.com/kcp-dev/kcp/sdk/apis/tenancy/v1alpha1"
7-
"github.com/openmfp/golang-commons/logger"
85
"net/url"
96
"strings"
10-
"time"
11-
12-
kcpapis "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
13-
"k8s.io/client-go/rest"
14-
"sigs.k8s.io/controller-runtime/pkg/client"
15-
16-
"github.com/openmfp/kubernetes-graphql-gateway/common/config"
177
)
188

199
var (
20-
ErrTimeoutFetchingAPIExport = errors.New("timeout fetching APIExport")
21-
ErrFailedToGetAPIExport = errors.New("failed to get APIExport")
22-
ErrNoVirtualURLsFound = errors.New("no virtual URLs found for APIExport")
23-
ErrEmptyVirtualWorkspaceURL = errors.New("empty URL in virtual workspace for APIExport")
24-
ErrInvalidURL = errors.New("invalid URL format")
10+
ErrInvalidURL = errors.New("invalid URL format")
2511
)
2612

27-
func virtualWorkspaceConfigFromCfg(
28-
ctx context.Context,
29-
log *logger.Logger,
30-
appCfg config.Config,
31-
restCfg *rest.Config,
32-
clt client.Client,
33-
) (*rest.Config, error) {
34-
timeOutDuration := 10 * time.Second
35-
ctx, cancelFn := context.WithTimeout(ctx, timeOutDuration)
36-
defer cancelFn()
37-
38-
var apiExport kcpapis.APIExport
39-
key := client.ObjectKey{
40-
Namespace: appCfg.ApiExportWorkspace,
41-
Name: appCfg.ApiExportName,
42-
}
43-
if err := clt.Get(ctx, key, &apiExport); err != nil {
44-
// if this is not a local development, we must have kubernetes.graphql.gateway apiexport
45-
if !appCfg.LocalDevelopment {
46-
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
47-
return nil, errors.Join(ErrTimeoutFetchingAPIExport, err)
48-
}
49-
return nil, errors.Join(ErrFailedToGetAPIExport, err)
50-
}
51-
52-
// otherwise fallback to the default APIExport, but live ApiBinding watching will not work
53-
if err = clt.Get(ctx, client.ObjectKey{Name: kcptenancy.SchemeGroupVersion.Group}, &apiExport); err != nil {
54-
return nil, errors.Join(ErrFailedToGetAPIExport, err)
55-
}
56-
57-
log.Warn().Str("apiexport", appCfg.ApiExportName).Msg("failed to find ApiExport, listener will not watch ApiBinding changes in realtime")
58-
}
59-
60-
if len(apiExport.Status.VirtualWorkspaces) == 0 { // nolint: staticcheck
61-
return nil, ErrNoVirtualURLsFound
62-
}
63-
64-
virtualWorkspaceURL := apiExport.Status.VirtualWorkspaces[0].URL // nolint: staticcheck
65-
if virtualWorkspaceURL == "" {
66-
return nil, ErrEmptyVirtualWorkspaceURL
67-
}
68-
69-
internalVirtualWorkspaceURL, err := combineBaseURLAndPath(restCfg.Host, virtualWorkspaceURL)
70-
if err != nil {
71-
return nil, err
72-
}
73-
74-
restCfg.Host = internalVirtualWorkspaceURL
75-
76-
return restCfg, nil
77-
}
78-
7913
func combineBaseURLAndPath(baseURLStr, pathURLStr string) (string, error) {
8014
baseURL, err := url.Parse(baseURLStr)
8115
if err != nil {

listener/kcp/workspace_config_test.go

Lines changed: 0 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,12 @@
11
package kcp
22

33
import (
4-
"context"
54
"errors"
6-
"github.com/openmfp/golang-commons/logger"
7-
"github.com/openmfp/kubernetes-graphql-gateway/common/config"
8-
"github.com/stretchr/testify/require"
95
"testing"
106

117
"github.com/stretchr/testify/assert"
12-
"k8s.io/apimachinery/pkg/runtime"
13-
"k8s.io/client-go/rest"
14-
"sigs.k8s.io/controller-runtime/pkg/client"
15-
"sigs.k8s.io/controller-runtime/pkg/client/fake"
16-
17-
kcpapis "github.com/kcp-dev/kcp/sdk/apis/apis/v1alpha1"
18-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
198
)
209

21-
func TestVirtualWorkspaceConfigFromCfg(t *testing.T) {
22-
scheme := runtime.NewScheme()
23-
assert.NoError(t, kcpapis.AddToScheme(scheme))
24-
25-
tests := map[string]struct {
26-
clientObjects func(appCfg *config.Config) []client.Object
27-
err error
28-
}{
29-
"successful_configuration_update": {
30-
clientObjects: func(appCfg *config.Config) []client.Object {
31-
return []client.Object{
32-
&kcpapis.APIExport{
33-
ObjectMeta: metav1.ObjectMeta{
34-
Namespace: appCfg.ApiExportWorkspace,
35-
Name: appCfg.ApiExportName,
36-
},
37-
Status: kcpapis.APIExportStatus{
38-
VirtualWorkspaces: []kcpapis.VirtualWorkspace{
39-
{URL: "https://192.168.1.13:6443/services/apiexport/root/tenancy.kcp.io"},
40-
},
41-
},
42-
},
43-
}
44-
},
45-
},
46-
"error_retrieving_APIExport": {
47-
err: errors.Join(ErrFailedToGetAPIExport, errors.New("apiexports.apis.kcp.io \"tenancy.kcp.io\" not found")),
48-
},
49-
"empty_virtual_workspace_list": {
50-
clientObjects: func(appCfg *config.Config) []client.Object {
51-
return []client.Object{
52-
&kcpapis.APIExport{
53-
ObjectMeta: metav1.ObjectMeta{
54-
Namespace: appCfg.ApiExportWorkspace,
55-
Name: appCfg.ApiExportName,
56-
},
57-
},
58-
}
59-
},
60-
err: ErrNoVirtualURLsFound,
61-
},
62-
"empty_virtual_workspace_url": {
63-
clientObjects: func(appCfg *config.Config) []client.Object {
64-
return []client.Object{
65-
&kcpapis.APIExport{
66-
ObjectMeta: metav1.ObjectMeta{
67-
Namespace: appCfg.ApiExportWorkspace,
68-
Name: appCfg.ApiExportName,
69-
},
70-
Status: kcpapis.APIExportStatus{
71-
VirtualWorkspaces: []kcpapis.VirtualWorkspace{
72-
{URL: ""},
73-
},
74-
},
75-
},
76-
}
77-
},
78-
err: ErrEmptyVirtualWorkspaceURL,
79-
},
80-
"wrong_url_in_virtual_ws": {
81-
clientObjects: func(appCfg *config.Config) []client.Object {
82-
return []client.Object{
83-
&kcpapis.APIExport{
84-
ObjectMeta: metav1.ObjectMeta{
85-
Namespace: appCfg.ApiExportWorkspace,
86-
Name: appCfg.ApiExportName,
87-
},
88-
Status: kcpapis.APIExportStatus{
89-
VirtualWorkspaces: []kcpapis.VirtualWorkspace{
90-
{URL: "ht@tp://bad_url"},
91-
},
92-
},
93-
},
94-
}
95-
},
96-
err: errors.Join(ErrInvalidURL, errors.New("parse \"ht@tp://bad_url\": first path segment in URL cannot contain colon")),
97-
},
98-
}
99-
100-
log, err := logger.New(logger.DefaultConfig())
101-
require.NoError(t, err)
102-
103-
for name, tc := range tests {
104-
t.Run(name, func(t *testing.T) {
105-
appCfg, err := config.NewFromEnv()
106-
assert.NoError(t, err)
107-
appCfg.LocalDevelopment = true
108-
109-
fakeClientBuilder := fake.NewClientBuilder().WithScheme(scheme)
110-
if tc.clientObjects != nil {
111-
fakeClientBuilder.WithObjects(tc.clientObjects(&appCfg)...)
112-
}
113-
fakeClient := fakeClientBuilder.Build()
114-
115-
resultCfg, err := virtualWorkspaceConfigFromCfg(context.Background(), log, appCfg, &rest.Config{Host: validAPIServerHost}, fakeClient)
116-
117-
if tc.err != nil {
118-
// here it fails
119-
assert.EqualError(t, err, tc.err.Error())
120-
assert.Nil(t, resultCfg)
121-
} else {
122-
assert.NoError(t, err)
123-
assert.Equal(t, tc.clientObjects(&appCfg)[0].(*kcpapis.APIExport).Status.VirtualWorkspaces[0].URL, resultCfg.Host) // nolint: staticcheck
124-
}
125-
})
126-
}
127-
}
128-
12910
func TestCombineBaseURLAndPath(t *testing.T) {
13011
tests := []struct {
13112
name string

0 commit comments

Comments
 (0)