Skip to content

Commit 089a1a3

Browse files
authored
feat: multi cluster support (#240)
feat: multi-cluster listener & gateway support - Implemented multi-cluster support for the listener and gateway. - Introduced condition management and strategy pattern in reconcilers. - Integrated golang-commons lifecycle. - Removed singlecluster mode and reconciler factory. - Added RoundTripperFactory and moved config builder to common package. Signed-off-by: Artem Shcherbatiuk <[email protected]> On-behalf-of: @SAP [email protected]
1 parent 7604dbb commit 089a1a3

File tree

111 files changed

+10504
-2695
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+10504
-2695
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ coverage.html
2424
*.swp
2525
*.swo
2626
*~
27+
*.log
2728

2829
# MacOS
2930
**/.DS_Store
@@ -40,4 +41,5 @@ coverage.html
4041
go.work
4142

4243
# binary files
43-
main
44+
main
45+
kubernetes-graphql-gateway

.mockery.yaml

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@ with-expecter: true
44
packages:
55
k8s.io/client-go/discovery:
66
config:
7-
dir: listener/kcp/mocks
7+
dir: listener/reconciler/kcp/mocks
88
outpkg: mocks
99
interfaces:
1010
DiscoveryInterface:
1111

1212
k8s.io/apimachinery/pkg/api/meta:
1313
config:
14-
dir: listener/kcp/mocks
14+
dir: listener/reconciler/kcp/mocks
1515
outpkg: mocks
1616
interfaces:
1717
RESTMapper:
1818

1919
sigs.k8s.io/controller-runtime/pkg/client:
2020
config:
21-
dir: gateway/resolver/mocks
21+
dir: common/mocks
2222
outpkg: mocks
2323
interfaces:
2424
WithWatch:
@@ -31,45 +31,46 @@ packages:
3131
interfaces:
3232
RoundTripper:
3333

34-
github.com/openmfp/kubernetes-graphql-gateway/listener/workspacefile:
34+
github.com/openmfp/kubernetes-graphql-gateway/listener/pkg/workspacefile:
3535
config:
36-
dir: listener/workspacefile/mocks
36+
dir: listener/pkg/workspacefile/mocks
3737
outpkg: mocks
3838
interfaces:
3939
IOHandler:
4040

41-
github.com/openmfp/kubernetes-graphql-gateway/listener/discoveryclient:
41+
github.com/openmfp/kubernetes-graphql-gateway/listener/reconciler/kcp/discoveryclient:
4242
config:
43-
dir: listener/discoveryclient/mocks
43+
dir: listener/reconciler/kcp/discoveryclient/mocks
4444
outpkg: mocks
4545
interfaces:
4646
Factory:
4747

48-
github.com/openmfp/kubernetes-graphql-gateway/listener/apischema:
48+
github.com/openmfp/kubernetes-graphql-gateway/listener/pkg/apischema:
4949
config:
50-
dir: listener/apischema/mocks
50+
dir: listener/pkg/apischema/mocks
5151
outpkg: mocks
5252
interfaces:
5353
Resolver:
5454

55-
github.com/openmfp/kubernetes-graphql-gateway/listener/clusterpath:
55+
github.com/openmfp/kubernetes-graphql-gateway/listener/reconciler/kcp/clusterpath:
5656
config:
57-
dir: listener/clusterpath/mocks
57+
dir: listener/reconciler/kcp/clusterpath/mocks
5858
outpkg: mocks
5959
interfaces:
6060
Resolver:
6161

62-
github.com/openmfp/kubernetes-graphql-gateway/listener/controller:
62+
k8s.io/client-go/openapi:
6363
config:
64-
dir: listener/controller/mocks
64+
dir: listener/pkg/apischema/mocks
6565
outpkg: mocks
6666
interfaces:
67-
CRDResolver:
67+
GroupVersion:
68+
Client:
6869

69-
k8s.io/client-go/openapi:
70+
github.com/openmfp/kubernetes-graphql-gateway/gateway/manager:
7071
config:
71-
dir: listener/apischema/mocks
72+
dir: gateway/manager/mocks
7273
outpkg: mocks
7374
interfaces:
74-
GroupVersion:
75-
Client:
75+
ClusterManager:
76+
SchemaWatcher:

.testcoverage.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ exclude:
44
- cmd
55
- tests
66
- common/config/config.go
7-
- mocks
7+
- mocks
8+
- common/apis/*
9+
# remove it later:
10+
- listener/reconciler/clusteraccess/subroutines.go
11+
- listener/reconciler/singlecluster

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,38 @@ This repository contains two main components:
1313
- [Listener](./docs/listener.md): watches a cluster and stores its openAPI spec in a directory.
1414
- [Gateway](./docs/gateway.md): exposes the openAPI spec as a GraphQL endpoints.
1515

16+
## MultiCluster Support
17+
18+
The system supports three modes of operation:
19+
20+
1. **Single Cluster** (`ENABLE_KCP=false`, `MULTICLUSTER=false`): Gateway connects to the same cluster as the listener
21+
2. **KCP Mode** (`ENABLE_KCP=true`): Designed for KCP-based multi-cluster scenarios
22+
3. **MultiCluster Mode** (`ENABLE_KCP=false`, `MULTICLUSTER=true`): Gateway connects to multiple external clusters via ClusterAccess resources
23+
24+
### MultiCluster with ClusterAccess
25+
26+
In MultiCluster mode, the system uses ClusterAccess resources to store kubeconfig data and connection information. The listener processes these resources and embeds connection metadata into schema files, which the gateway then uses to establish cluster-specific connections.
27+
28+
For complete setup instructions, see:
29+
- [ClusterAccess documentation](./docs/clusteraccess.md) - Manual setup
30+
- [MultiCluster Kubeconfig Flow](./docs/multicluster-kubeconfig-flow.md) - Detailed flow explanation
31+
32+
### Quick Setup Scripts
33+
34+
```bash
35+
# Create ClusterAccess with secure token authentication
36+
./scripts/create-clusteraccess.sh --target-kubeconfig ~/.kube/prod-config
37+
38+
# Test end-to-end integration
39+
./scripts/test-clusteraccess-integration.sh
40+
```
41+
42+
### Gateway Requirements
43+
44+
- **Single Cluster Mode**: Requires KUBECONFIG to connect to the local cluster
45+
- **KCP Mode**: Requires KUBECONFIG to connect to KCP management cluster
46+
- **MultiCluster Mode**: Does NOT require KUBECONFIG - gets all connection info from schema files
47+
1648
## Authorization
1749

1850
All information about authorization can be found in the [authorization](./docs/authorization.md) section.

Taskfile.yml

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ vars:
66
ENVTEST_K8S_VERSION: "1.30.0" # to get latest version run $(pwd)/bin/setup-envtest list
77
ENVTEST_VERSION: "release-0.20" # https://github.com/kubernetes-sigs/controller-runtime/releases
88
MOCKERY_VERSION: v2.52.3 # https://github.com/vektra/mockery/releases
9+
CONTROLLER_GEN_VERSION: v0.18.0 # https://github.com/kubernetes-sigs/controller-tools/releases
910
tasks:
1011
## Setup
1112
setup:mockery:
@@ -16,6 +17,10 @@ tasks:
1617
internal: true
1718
cmds:
1819
- test -s {{.LOCAL_BIN}}/setup-envtest || GOBIN=$(pwd)/{{.LOCAL_BIN}} go install sigs.k8s.io/controller-runtime/tools/setup-envtest@{{.ENVTEST_VERSION}}
20+
setup:controller-gen:
21+
internal: true
22+
cmds:
23+
- test -s {{.LOCAL_BIN}}/controller-gen || GOBIN=$(pwd)/{{.LOCAL_BIN}} go install sigs.k8s.io/controller-tools/cmd/controller-gen@{{.CONTROLLER_GEN_VERSION}}
1924
update:crd:
2025
desc: "Download the latest CRD from OpenMFP"
2126
cmds:
@@ -31,6 +36,25 @@ tasks:
3136
cmds:
3237
- test -s {{.LOCAL_BIN}}/go-test-coverage || GOBIN=$(pwd)/{{.LOCAL_BIN}} go install github.com/vladopajic/go-test-coverage/v2@latest
3338

39+
## Code Generation
40+
generate:crd:
41+
desc: "Generate CRD manifests from Go types"
42+
deps: [setup:controller-gen]
43+
cmds:
44+
- "{{.LOCAL_BIN}}/controller-gen crd:crdVersions=v1 paths=./common/apis/v1alpha1 output:crd:artifacts:config=config/crd"
45+
- echo "CRD manifests generated successfully in config/crd/"
46+
generate:deepcopy:
47+
desc: "Generate deepcopy methods for API types"
48+
deps: [setup:controller-gen]
49+
cmds:
50+
- "{{.LOCAL_BIN}}/controller-gen object paths=./common/apis/v1alpha1"
51+
- echo "Deepcopy methods generated successfully"
52+
generate:
53+
desc: "Generate all CRD-related files (manifests + deepcopy methods)"
54+
deps: [generate:crd, generate:deepcopy]
55+
cmds:
56+
- echo "All CRD generation completed successfully!"
57+
3458
## Development
3559
mockery:
3660
deps: [ setup:mockery ]
@@ -62,7 +86,7 @@ tasks:
6286
vars:
6387
ADDITIONAL_COMMAND_ARGS: -coverprofile=./cover.out -covermode=atomic -coverpkg=./...
6488
cover:
65-
deps: [ setup:envtest, setup:go-test-coverage ]
89+
deps: [ setup:envtest, update:crd, setup:go-test-coverage ]
6690
cmds:
6791
- task: envtest
6892
vars:
@@ -75,16 +99,36 @@ tasks:
7599
- go tool cover -html=cover.out -o coverage.html
76100
- open coverage.html || xdg-open coverage.html || start coverage.html
77101
validate:
102+
desc: "Run all validation checks including code generation, linting, and testing"
78103
cmds:
104+
- task: generate
79105
- task: mockery
80106
- task: lint
81107
- task: test
82108

83109

84110
gateway:
85-
cmds:
111+
desc: "Start the GraphQL gateway server (kills existing process on port 8080 if needed)"
112+
cmds:
113+
- |
114+
# Check if port 8080 is in use and kill the process if found
115+
PID=$(lsof -ti:8080 2>/dev/null || echo "")
116+
if [ ! -z "$PID" ]; then
117+
echo "Found existing process $PID on port 8080, killing it..."
118+
kill $PID 2>/dev/null || true
119+
sleep 2
120+
fi
86121
- go run main.go gateway
87122

88123
listener:
89-
cmds:
124+
desc: "Start the listener server (kills existing process on port 8090 if needed)"
125+
cmds:
126+
- |
127+
# Check if port 8090 is in use and kill the process if found
128+
PID=$(lsof -ti:8090 2>/dev/null || echo "")
129+
if [ ! -z "$PID" ]; then
130+
echo "Found existing process $PID on port 8090, killing it..."
131+
kill $PID 2>/dev/null || true
132+
sleep 2
133+
fi
90134
- go run main.go listener

cmd/gateway.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
"github.com/prometheus/client_golang/prometheus/promhttp"
1414
"github.com/spf13/cobra"
1515
ctrl "sigs.k8s.io/controller-runtime"
16-
restCfg "sigs.k8s.io/controller-runtime/pkg/client/config"
17-
"sigs.k8s.io/controller-runtime/pkg/log/zap"
1816

1917
"github.com/openmfp/golang-commons/logger"
2018

@@ -48,12 +46,12 @@ var gatewayCmd = &cobra.Command{
4846
defer openmfpcontext.Recover(log)
4947
}
5048

51-
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
49+
ctrl.SetLogger(log.Logr())
5250

53-
// Get Kubernetes restCfg
54-
restCfg, err := restCfg.GetConfig()
51+
gatewayInstance, err := manager.NewGateway(log, appCfg)
5552
if err != nil {
56-
log.Fatal().Err(err).Msg("Error getting Kubernetes restCfg, exiting")
53+
log.Error().Err(err).Msg("Error creating gateway")
54+
return fmt.Errorf("failed to create gateway: %w", err)
5755
}
5856

5957
// Initialize tracing provider
@@ -76,15 +74,14 @@ var gatewayCmd = &cobra.Command{
7674
}
7775
}()
7876

79-
// Initialize Manager
80-
managerInstance, err := manager.NewManager(log, restCfg, appCfg)
81-
if err != nil {
82-
log.Error().Err(err).Msg("Error creating manager")
83-
return fmt.Errorf("failed to create manager: %w", err)
84-
}
77+
defer func() {
78+
if err := providerShutdown(ctx); err != nil {
79+
log.Fatal().Err(err).Msg("failed to shutdown TracerProvider")
80+
}
81+
}()
8582

8683
// Set up HTTP handler
87-
http.Handle("/", managerInstance)
84+
http.Handle("/", gatewayInstance)
8885

8986
// Replace the /metrics endpoint handler
9087
http.Handle("/metrics", promhttp.Handler())
@@ -113,6 +110,10 @@ var gatewayCmd = &cobra.Command{
113110
log.Fatal().Err(err).Msg("HTTP server shutdown failed")
114111
}
115112

113+
if err := gatewayInstance.Close(); err != nil {
114+
log.Error().Err(err).Msg("Error closing gateway services")
115+
}
116+
116117
// Call the shutdown cleanup
117118
shutdown()
118119

0 commit comments

Comments
 (0)