@@ -18,13 +18,34 @@ package tenant
1818
1919import (
2020 "context"
21+ "errors"
22+ "fmt"
2123
24+ "github.com/aws/aws-sdk-go/aws"
25+ "github.com/aws/aws-sdk-go/aws/credentials"
26+ "github.com/aws/aws-sdk-go/aws/session"
27+ "github.com/aws/aws-sdk-go/service/s3"
28+ corev1 "k8s.io/api/core/v1"
29+ "k8s.io/apimachinery/pkg/api/equality"
30+ "k8s.io/apimachinery/pkg/api/meta"
31+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2232 "k8s.io/apimachinery/pkg/runtime"
33+ "k8s.io/apimachinery/pkg/types"
34+ "k8s.io/klog/v2"
2335 ctrl "sigs.k8s.io/controller-runtime"
2436 "sigs.k8s.io/controller-runtime/pkg/client"
25- "sigs.k8s.io/controller-runtime/pkg/log"
2637
2738 databendv1alpha1 "github.com/databendcloud/databend-operator/pkg/apis/databendlabs.io/v1alpha1"
39+ "github.com/databendcloud/databend-operator/pkg/common"
40+ )
41+
42+ type opState int
43+
44+ const (
45+ creationSucceeded opState = iota
46+ storageError opState = iota
47+ metaError opState = iota
48+ builtinUserError opState = iota
2849)
2950
3051// TenantReconciler reconciles a Tenant object
@@ -36,22 +57,138 @@ type TenantReconciler struct {
3657// +kubebuilder:rbac:groups=databendlabs.io,resources=tenants,verbs=get;list;watch;create;update;patch;delete
3758// +kubebuilder:rbac:groups=databendlabs.io,resources=tenants/status,verbs=get;update;patch
3859// +kubebuilder:rbac:groups=databendlabs.io,resources=tenants/finalizers,verbs=update
60+ // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list
3961
40- // Reconcile is part of the main kubernetes reconciliation loop which aims to
41- // move the current state of the cluster closer to the desired state.
42- // TODO(user): Modify the Reconcile function to compare the state specified by
43- // the Tenant object against the actual cluster state, and then
44- // perform operations to make the cluster state reflect the state specified by
45- // the user.
46- //
47- // For more details, check Reconcile and its Result here:
48- // - https://pkg.go.dev/sigs.k8s.io/[email protected] /pkg/reconcile 4962func (r * TenantReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (ctrl.Result , error ) {
50- _ = log .FromContext (ctx )
63+ var tenant databendv1alpha1.Tenant
64+ if err := r .Get (ctx , req .NamespacedName , & tenant ); err != nil {
65+ return ctrl.Result {}, client .IgnoreNotFound (err )
66+ }
67+ log := ctrl .LoggerFrom (ctx ).WithValues ("tenant" , klog .KObj (& tenant ))
68+ ctx = ctrl .LoggerInto (ctx , log )
69+ log .V (2 ).Info ("Reconciling Tenant" )
70+
71+ var err error
72+ originStatus := tenant .Status .DeepCopy ()
73+
74+ // Verify storage configuration
75+ opState , storageErr := r .verifyStorage (ctx , & tenant )
76+ if storageErr != nil {
77+ err = errors .Join (err , storageErr )
78+ }
79+ log .V (5 ).Info ("Succeeded to verify storage configurations" )
80+ setCondition (& tenant , opState )
81+
82+ // Verify meta configuration
83+ opState , metaErr := r .verifyMeta (ctx , & tenant )
84+ if metaErr != nil {
85+ err = errors .Join (err , metaErr )
86+ }
87+ setCondition (& tenant , opState )
88+
89+ // Verify built-in users configuration
90+ opState , userErr := r .verifyBuiltinUsers (ctx , & tenant )
91+ if userErr != nil {
92+ err = errors .Join (err , userErr )
93+ }
94+ setCondition (& tenant , opState )
95+
96+ if ! equality .Semantic .DeepEqual (& tenant .Status , originStatus ) {
97+ return ctrl.Result {}, errors .Join (err , r .Status ().Update (ctx , & tenant ))
98+ }
99+ return ctrl.Result {}, err
100+ }
101+
102+ func (r * TenantReconciler ) verifyStorage (ctx context.Context , tenant * databendv1alpha1.Tenant ) (opState , error ) {
103+ log := ctrl .LoggerFrom (ctx )
51104
52- // TODO(user): your logic here
105+ if tenant .Spec .Storage .S3 == nil {
106+ return storageError , fmt .Errorf ("missing S3 configurations" )
107+ }
108+
109+ // Get accessKey and secretKey
110+ s3Config := tenant .Spec .Storage .S3
111+ var accessKey , secretKey string
112+ if s3Config .S3Auth .SecretRef != nil {
113+ log .V (5 ).Info ("Getting credentials from Secret" )
114+ var secret corev1.Secret
115+ nn := types.NamespacedName {
116+ Namespace : s3Config .S3Auth .SecretRef .Namespace ,
117+ Name : s3Config .S3Auth .SecretRef .Name ,
118+ }
119+ if err := r .Get (ctx , nn , & secret , & client.GetOptions {}); err != nil {
120+ return storageError , fmt .Errorf ("failed to get secret %v" , nn )
121+ }
122+ accessKey , secretKey = string (secret .Data ["accessKey" ]), string (secret .Data ["secretKey" ])
123+ } else {
124+ accessKey , secretKey = s3Config .AccessKey , s3Config .SecretKey
125+ }
126+
127+ // Test connection to S3
128+ sess , err := session .NewSession (& aws.Config {
129+ Region : aws .String (s3Config .Region ),
130+ Credentials : credentials .NewStaticCredentials (accessKey , secretKey , "" ),
131+ Endpoint : aws .String (s3Config .Endpoint ),
132+ })
133+ if err != nil {
134+ return storageError , fmt .Errorf ("failed to create session: %w" , err )
135+ }
136+
137+ // Check bucket
138+ svc := s3 .New (sess )
139+ _ , err = svc .GetBucketLocation (& s3.GetBucketLocationInput {
140+ Bucket : aws .String (s3Config .BucketName ),
141+ })
142+ if err != nil {
143+ return storageError , fmt .Errorf ("failed to connect to S3: %w" , err )
144+ }
145+
146+ return creationSucceeded , nil
147+ }
148+
149+ func (r * TenantReconciler ) verifyMeta (ctx context.Context , tenant * databendv1alpha1.Tenant ) (opState , error ) {
150+ return creationSucceeded , nil
151+ }
152+
153+ func (r * TenantReconciler ) verifyBuiltinUsers (ctx context.Context , tenant * databendv1alpha1.Tenant ) (opState , error ) {
154+ return creationSucceeded , nil
155+ }
53156
54- return ctrl.Result {}, nil
157+ func setCondition (tenant * databendv1alpha1.Tenant , opState opState ) {
158+ var newCond metav1.Condition
159+ switch opState {
160+ case creationSucceeded :
161+ newCond = metav1.Condition {
162+ Type : databendv1alpha1 .TenantCreated ,
163+ Status : metav1 .ConditionTrue ,
164+ Message : common .TenantCreationSucceededMessage ,
165+ Reason : databendv1alpha1 .TenantCreationSucceededReason ,
166+ }
167+ case storageError :
168+ newCond = metav1.Condition {
169+ Type : databendv1alpha1 .TenantError ,
170+ Status : metav1 .ConditionFalse ,
171+ Message : common .TenantStorageErrorMessage ,
172+ Reason : databendv1alpha1 .TenantStorageErrorReason ,
173+ }
174+ case metaError :
175+ newCond = metav1.Condition {
176+ Type : databendv1alpha1 .TenantError ,
177+ Status : metav1 .ConditionFalse ,
178+ Message : common .TenantMetaErrorMessage ,
179+ Reason : databendv1alpha1 .TenantMetaErrorReason ,
180+ }
181+ case builtinUserError :
182+ newCond = metav1.Condition {
183+ Type : databendv1alpha1 .TenantError ,
184+ Status : metav1 .ConditionFalse ,
185+ Message : common .TenantUserErrorMessage ,
186+ Reason : databendv1alpha1 .TenantUserErrorReason ,
187+ }
188+ default :
189+ return
190+ }
191+ meta .SetStatusCondition (& tenant .Status .Conditions , newCond )
55192}
56193
57194// SetupWithManager sets up the controller with the Manager.
0 commit comments