Skip to content

Commit ea54208

Browse files
committed
fixed integration
On-behalf-of: @SAP [email protected] Signed-off-by: Artem Shcherbatiuk <[email protected]>
1 parent b0d5db6 commit ea54208

File tree

3 files changed

+159
-52
lines changed

3 files changed

+159
-52
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package clusteraccess
2+
3+
// This file exports internal functions for integration testing
4+
5+
// NewGenerateSchemaSubroutineForTesting creates a generateSchemaSubroutine for testing
6+
func NewGenerateSchemaSubroutineForTesting(reconciler *ClusterAccessReconciler) *generateSchemaSubroutine {
7+
return &generateSchemaSubroutine{reconciler: reconciler}
8+
}

listener/reconciler/clusteraccess/reconciler.go

Lines changed: 39 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"time"
87

98
"k8s.io/apimachinery/pkg/api/meta"
109
"k8s.io/apimachinery/pkg/runtime"
1110
"k8s.io/client-go/rest"
1211
ctrl "sigs.k8s.io/controller-runtime"
1312
"sigs.k8s.io/controller-runtime/pkg/client"
1413

14+
"github.com/openmfp/golang-commons/controller/lifecycle"
1515
"github.com/openmfp/golang-commons/logger"
1616
gatewayv1alpha1 "github.com/openmfp/kubernetes-graphql-gateway/common/apis/v1alpha1"
1717
"github.com/openmfp/kubernetes-graphql-gateway/common/config"
@@ -49,15 +49,18 @@ func NewClusterAccessReconciler(
4949
}
5050
}
5151

52-
// Check if ClusterAccess CRD is available
53-
caStatus, err := CheckClusterAccessCRDStatus(ctx, opts.Client, log)
52+
if schemaResolver == nil {
53+
schemaResolver = apischema.NewResolver(log)
54+
}
55+
56+
// Check if ClusterAccess CRD is registered
57+
crdStatus, err := CheckClusterAccessCRDStatus(ctx, opts.Client, log)
5458
if err != nil {
55-
return nil, err
59+
return nil, fmt.Errorf("failed to check ClusterAccess CRD status: %w", err)
5660
}
5761

58-
if caStatus != CRDRegistered {
59-
log.Error().Msg("Multi-cluster mode enabled but ClusterAccess CRD not available")
60-
return nil, errors.New("multi-cluster mode enabled but ClusterAccess CRD not available")
62+
if crdStatus != CRDRegistered {
63+
return nil, ErrCRDNotRegistered
6164
}
6265

6366
log.Info().Msg("ClusterAccess CRD registered, creating ClusterAccess reconciler")
@@ -85,11 +88,14 @@ func CheckClusterAccessCRDStatus(ctx context.Context, k8sClient client.Client, l
8588
// ClusterAccessReconciler handles reconciliation for ClusterAccess resources
8689
type ClusterAccessReconciler struct {
8790
client.Client
88-
Scheme *runtime.Scheme
89-
restCfg *rest.Config
90-
ioHandler workspacefile.IOHandler
91-
schemaResolver apischema.Resolver
92-
log *logger.Logger
91+
Scheme *runtime.Scheme
92+
restCfg *rest.Config
93+
ioHandler workspacefile.IOHandler
94+
schemaResolver apischema.Resolver
95+
log *logger.Logger
96+
mgr ctrl.Manager
97+
opts reconciler.ReconcilerOpts
98+
lifecycleManager *lifecycle.LifecycleManager
9399
}
94100

95101
func NewReconciler(
@@ -98,64 +104,45 @@ func NewReconciler(
98104
schemaResolver apischema.Resolver,
99105
log *logger.Logger,
100106
) (reconciler.CustomReconciler, error) {
107+
// Create standard manager
108+
mgr, err := ctrl.NewManager(opts.Config, opts.ManagerOpts)
109+
if err != nil {
110+
return nil, err
111+
}
112+
101113
r := &ClusterAccessReconciler{
102-
Client: opts.Client,
103-
Scheme: opts.Scheme,
114+
opts: opts,
104115
restCfg: opts.Config,
116+
mgr: mgr,
105117
ioHandler: ioHandler,
106118
schemaResolver: schemaResolver,
107119
log: log,
108120
}
109121

122+
// Create lifecycle manager with subroutines and condition management
123+
r.lifecycleManager = lifecycle.NewLifecycleManager(
124+
log,
125+
"cluster-access-reconciler",
126+
"cluster-access-reconciler",
127+
opts.Client,
128+
[]lifecycle.Subroutine{
129+
&generateSchemaSubroutine{reconciler: r},
130+
},
131+
).WithConditionManagement()
132+
110133
return r, nil
111134
}
112135

113136
func (r *ClusterAccessReconciler) GetManager() ctrl.Manager {
114-
// Create a simple manager for this reconciler
115-
mgr, err := ctrl.NewManager(r.restCfg, ctrl.Options{
116-
Scheme: r.Scheme,
117-
})
118-
if err != nil {
119-
r.log.Error().Err(err).Msg("Failed to create manager")
120-
return nil
121-
}
122-
return mgr
137+
return r.mgr
123138
}
124139

125140
func (r *ClusterAccessReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
126-
r.log.Info().Str("name", req.Name).Str("namespace", req.Namespace).Msg("Reconciling ClusterAccess")
127-
128-
// Get ClusterAccess object
129-
clusterAccess := &gatewayv1alpha1.ClusterAccess{}
130-
if err := r.Get(ctx, req.NamespacedName, clusterAccess); err != nil {
131-
r.log.Error().Err(err).Msg("Failed to get ClusterAccess")
132-
return ctrl.Result{}, client.IgnoreNotFound(err)
133-
}
134-
135-
// Generate schema for this cluster access
136-
if err := r.generateSchema(ctx, *clusterAccess); err != nil {
137-
r.log.Error().Err(err).Msg("Failed to generate schema")
138-
return ctrl.Result{RequeueAfter: time.Minute * 5}, err
139-
}
140-
141-
return ctrl.Result{}, nil
141+
return r.lifecycleManager.Reconcile(ctx, req, &gatewayv1alpha1.ClusterAccess{})
142142
}
143143

144144
func (r *ClusterAccessReconciler) SetupWithManager(mgr ctrl.Manager) error {
145145
return ctrl.NewControllerManagedBy(mgr).
146146
For(&gatewayv1alpha1.ClusterAccess{}).
147147
Complete(r)
148148
}
149-
150-
func (r *ClusterAccessReconciler) generateSchema(ctx context.Context, clusterAccess gatewayv1alpha1.ClusterAccess) error {
151-
r.log.Info().Str("clusterAccess", clusterAccess.Name).Msg("Starting schema generation")
152-
153-
// This is a simplified version - in the real implementation, you would:
154-
// 1. Create a discovery client using the cluster access credentials
155-
// 2. Resolve the OpenAPI schema
156-
// 3. Inject cluster metadata
157-
// 4. Write the schema file using ioHandler
158-
159-
r.log.Info().Str("clusterAccess", clusterAccess.Name).Msg("Schema generation completed")
160-
return nil
161-
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package clusteraccess
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"k8s.io/apimachinery/pkg/api/meta"
8+
"k8s.io/client-go/discovery"
9+
"k8s.io/client-go/rest"
10+
ctrl "sigs.k8s.io/controller-runtime"
11+
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
12+
13+
"github.com/openmfp/golang-commons/controller/lifecycle"
14+
commonserrors "github.com/openmfp/golang-commons/errors"
15+
gatewayv1alpha1 "github.com/openmfp/kubernetes-graphql-gateway/common/apis/v1alpha1"
16+
"github.com/openmfp/kubernetes-graphql-gateway/listener/pkg/apischema"
17+
"github.com/openmfp/kubernetes-graphql-gateway/listener/reconciler"
18+
)
19+
20+
// generateSchemaSubroutine processes ClusterAccess resources and generates schemas
21+
type generateSchemaSubroutine struct {
22+
reconciler *ClusterAccessReconciler
23+
}
24+
25+
func (s *generateSchemaSubroutine) Process(ctx context.Context, instance lifecycle.RuntimeObject) (ctrl.Result, commonserrors.OperatorError) {
26+
clusterAccess, ok := instance.(*gatewayv1alpha1.ClusterAccess)
27+
if !ok {
28+
s.reconciler.log.Error().Msg("instance is not a ClusterAccess resource")
29+
return ctrl.Result{}, commonserrors.NewOperatorError(errors.New("invalid resource type"), false, false)
30+
}
31+
32+
clusterAccessName := clusterAccess.GetName()
33+
s.reconciler.log.Info().Str("clusterAccess", clusterAccessName).Msg("processing ClusterAccess resource")
34+
35+
// Extract target cluster config from ClusterAccess spec
36+
targetConfig, clusterName, err := BuildTargetClusterConfigFromTyped(ctx, *clusterAccess, s.reconciler.opts.Client)
37+
if err != nil {
38+
s.reconciler.log.Error().Err(err).Str("clusterAccess", clusterAccessName).Msg("failed to build target cluster config")
39+
return ctrl.Result{}, commonserrors.NewOperatorError(err, false, false)
40+
}
41+
42+
s.reconciler.log.Info().Str("clusterAccess", clusterAccessName).Str("host", targetConfig.Host).Str("clusterName", clusterName).Msg("extracted target cluster config")
43+
44+
// Create discovery client for target cluster
45+
targetDiscovery, err := discovery.NewDiscoveryClientForConfig(targetConfig)
46+
if err != nil {
47+
s.reconciler.log.Error().Err(err).Str("clusterAccess", clusterAccessName).Msg("failed to create discovery client")
48+
return ctrl.Result{}, commonserrors.NewOperatorError(err, false, false)
49+
}
50+
51+
// Create REST mapper for target cluster
52+
targetRM, err := s.restMapperFromConfig(targetConfig)
53+
if err != nil {
54+
s.reconciler.log.Error().Err(err).Str("clusterAccess", clusterAccessName).Msg("failed to create REST mapper")
55+
return ctrl.Result{}, commonserrors.NewOperatorError(err, false, false)
56+
}
57+
58+
// Create schema resolver for target cluster
59+
targetResolver := &apischema.CRDResolver{
60+
DiscoveryInterface: targetDiscovery,
61+
RESTMapper: targetRM,
62+
}
63+
64+
// Generate schema for target cluster
65+
JSON, err := targetResolver.Resolve(targetDiscovery, targetRM)
66+
if err != nil {
67+
s.reconciler.log.Error().Err(err).Str("clusterAccess", clusterAccessName).Msg("failed to resolve schema")
68+
return ctrl.Result{}, commonserrors.NewOperatorError(err, false, false)
69+
}
70+
71+
// Create the complete schema file with x-cluster-metadata
72+
schemaWithMetadata, err := injectClusterMetadata(ctx, JSON, *clusterAccess, s.reconciler.opts.Client, s.reconciler.log)
73+
if err != nil {
74+
s.reconciler.log.Error().Err(err).Str("clusterAccess", clusterAccessName).Msg("failed to inject cluster metadata")
75+
return ctrl.Result{}, commonserrors.NewOperatorError(err, false, false)
76+
}
77+
78+
// Write schema to file using cluster name from path or resource name
79+
if err := s.reconciler.ioHandler.Write(schemaWithMetadata, clusterName); err != nil {
80+
s.reconciler.log.Error().Err(err).Str("clusterAccess", clusterAccessName).Msg("failed to write schema")
81+
return ctrl.Result{}, commonserrors.NewOperatorError(err, false, false)
82+
}
83+
84+
s.reconciler.log.Info().Str("clusterAccess", clusterAccessName).Msg("successfully processed ClusterAccess resource")
85+
return ctrl.Result{}, nil
86+
}
87+
88+
// restMapperFromConfig creates a REST mapper from a config
89+
func (s *generateSchemaSubroutine) restMapperFromConfig(cfg *rest.Config) (meta.RESTMapper, error) {
90+
httpClt, err := rest.HTTPClientFor(cfg)
91+
if err != nil {
92+
return nil, errors.Join(reconciler.ErrCreateHTTPClient, err)
93+
}
94+
rm, err := apiutil.NewDynamicRESTMapper(cfg, httpClt)
95+
if err != nil {
96+
return nil, errors.Join(reconciler.ErrCreateRESTMapper, err)
97+
}
98+
99+
return rm, nil
100+
}
101+
102+
func (s *generateSchemaSubroutine) Finalize(ctx context.Context, instance lifecycle.RuntimeObject) (ctrl.Result, commonserrors.OperatorError) {
103+
return ctrl.Result{}, nil
104+
}
105+
106+
func (s *generateSchemaSubroutine) GetName() string {
107+
return "generate-schema"
108+
}
109+
110+
func (s *generateSchemaSubroutine) Finalizers() []string {
111+
return nil
112+
}

0 commit comments

Comments
 (0)