This is an automated email from the ASF dual-hosted git repository.

jimin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-seata-k8s.git


The following commit(s) were added to refs/heads/master by this push:
     new ee6eef5  feature: support ConfigMap and Secret by changing env from 
map to EnvVar (#17)
ee6eef5 is described below

commit ee6eef565e86680f0806382c733d9e46ff5e8e75
Author: Xiangkun Yin <[email protected]>
AuthorDate: Tue Jun 4 21:34:25 2024 +0800

    feature: support ConfigMap and Secret by changing env from map to EnvVar 
(#17)
---
 README.md                                          |  17 +++-
 README.zh.md                                       |  17 +++-
 api/v1alpha1/seataserver_types.go                  |   2 +-
 api/v1alpha1/zz_generated.deepcopy.go              |   6 +-
 .../operator.seata.apache.org_seataservers.yaml    | 110 ++++++++++++++++++++-
 config/rbac/role.yaml                              |  16 +++
 controllers/seataserver_controller.go              |  19 +++-
 deploy/seata-server-cluster.yaml                   |  20 +++-
 pkg/seata/fetchers.go                              |  70 +++++++++++++
 pkg/seata/generators.go                            |   4 +-
 pkg/seata/synchronizers.go                         |  14 +--
 11 files changed, 268 insertions(+), 27 deletions(-)

diff --git a/README.md b/README.md
index 983abe1..0391667 100644
--- a/README.md
+++ b/README.md
@@ -93,8 +93,21 @@ For CRD details, you can visit 
[operator.seata.apache.org_seataservers.yaml](con
          requests:
            storage: 5Gi
      env:
-       console.user.username: seata
-       console.user.password: seata
+     - name: console.user.username
+       value: seata
+     - name: console.user.password
+       valueFrom:
+         secretKeyRef:
+           name: seata
+           key: password
+   ---
+   apiVersion: v1
+   kind: Secret
+   metadata:
+     name: seata
+   type: Opaque
+   data:
+     password: seata
    ```
 
 ## Method 2: Example without Using Operator
diff --git a/README.zh.md b/README.zh.md
index 86ffc87..fface34 100755
--- a/README.zh.md
+++ b/README.zh.md
@@ -98,8 +98,21 @@ https://github.com/seata/seata-docker
          requests:
            storage: 5Gi
      env:
-       console.user.username: seata
-       console.user.password: seata
+     - name: console.user.username
+       value: seata
+     - name: console.user.password
+       valueFrom:
+         secretKeyRef:
+           name: seata
+           key: password
+   ---
+   apiVersion: v1
+   kind: Secret
+   metadata:
+     name: seata
+   type: Opaque
+   data:
+     password: seata
    ```
    
    
diff --git a/api/v1alpha1/seataserver_types.go 
b/api/v1alpha1/seataserver_types.go
index cee9f53..1c4a761 100644
--- a/api/v1alpha1/seataserver_types.go
+++ b/api/v1alpha1/seataserver_types.go
@@ -78,7 +78,7 @@ type ContainerSpec struct {
        ContainerName string `json:"containerName"`
        Image         string `json:"image"`
        // +kubebuilder:validation:Optional
-       Env map[string]string `json:"env"`
+       Env []apiv1.EnvVar `json:"env"`
        // +kubebuilder:validation:Optional
        Resources apiv1.ResourceRequirements `json:"resources"`
 }
diff --git a/api/v1alpha1/zz_generated.deepcopy.go 
b/api/v1alpha1/zz_generated.deepcopy.go
index 35277bb..54ee94f 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -192,9 +192,9 @@ func (in *ContainerSpec) DeepCopyInto(out *ContainerSpec) {
        *out = *in
        if in.Env != nil {
                in, out := &in.Env, &out.Env
-               *out = make(map[string]string, len(*in))
-               for key, val := range *in {
-                       (*out)[key] = val
+               *out = make([]v1.EnvVar, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
                }
        }
        in.Resources.DeepCopyInto(&out.Resources)
diff --git a/config/crd/bases/operator.seata.apache.org_seataservers.yaml 
b/config/crd/bases/operator.seata.apache.org_seataservers.yaml
index 2c6883c..9d3bbf1 100644
--- a/config/crd/bases/operator.seata.apache.org_seataservers.yaml
+++ b/config/crd/bases/operator.seata.apache.org_seataservers.yaml
@@ -38,9 +38,113 @@ spec:
                 default: seata-server
                 type: string
               env:
-                additionalProperties:
-                  type: string
-                type: object
+                items:
+                  description: EnvVar represents an environment variable 
present in
+                    a Container.
+                  properties:
+                    name:
+                      description: Name of the environment variable. Must be a 
C_IDENTIFIER.
+                      type: string
+                    value:
+                      description: 'Variable references $(VAR_NAME) are 
expanded using
+                        the previously defined environment variables in the 
container
+                        and any service environment variables. If a variable 
cannot
+                        be resolved, the reference in the input string will be 
unchanged.
+                        Double $$ are reduced to a single $, which allows for 
escaping
+                        the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will 
produce the
+                        string literal "$(VAR_NAME)". Escaped references will 
never
+                        be expanded, regardless of whether the variable exists 
or
+                        not. Defaults to "".'
+                      type: string
+                    valueFrom:
+                      description: Source for the environment variable's 
value. Cannot
+                        be used if value is not empty.
+                      properties:
+                        configMapKeyRef:
+                          description: Selects a key of a ConfigMap.
+                          properties:
+                            key:
+                              description: The key to select.
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                                TODO: Add other useful fields. apiVersion, 
kind, uid?'
+                              type: string
+                            optional:
+                              description: Specify whether the ConfigMap or 
its key
+                                must be defined
+                              type: boolean
+                          required:
+                          - key
+                          type: object
+                          x-kubernetes-map-type: atomic
+                        fieldRef:
+                          description: 'Selects a field of the pod: supports 
metadata.name,
+                            metadata.namespace, `metadata.labels[''<KEY>'']`, 
`metadata.annotations[''<KEY>'']`,
+                            spec.nodeName, spec.serviceAccountName, 
status.hostIP,
+                            status.podIP, status.podIPs.'
+                          properties:
+                            apiVersion:
+                              description: Version of the schema the FieldPath 
is
+                                written in terms of, defaults to "v1".
+                              type: string
+                            fieldPath:
+                              description: Path of the field to select in the 
specified
+                                API version.
+                              type: string
+                          required:
+                          - fieldPath
+                          type: object
+                          x-kubernetes-map-type: atomic
+                        resourceFieldRef:
+                          description: 'Selects a resource of the container: 
only
+                            resources limits and requests (limits.cpu, 
limits.memory,
+                            limits.ephemeral-storage, requests.cpu, 
requests.memory
+                            and requests.ephemeral-storage) are currently 
supported.'
+                          properties:
+                            containerName:
+                              description: 'Container name: required for 
volumes,
+                                optional for env vars'
+                              type: string
+                            divisor:
+                              anyOf:
+                              - type: integer
+                              - type: string
+                              description: Specifies the output format of the 
exposed
+                                resources, defaults to "1"
+                              pattern: 
^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                              x-kubernetes-int-or-string: true
+                            resource:
+                              description: 'Required: resource to select'
+                              type: string
+                          required:
+                          - resource
+                          type: object
+                          x-kubernetes-map-type: atomic
+                        secretKeyRef:
+                          description: Selects a key of a secret in the pod's 
namespace
+                          properties:
+                            key:
+                              description: The key of the secret to select 
from.  Must
+                                be a valid secret key.
+                              type: string
+                            name:
+                              description: 'Name of the referent. More info: 
https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                                TODO: Add other useful fields. apiVersion, 
kind, uid?'
+                              type: string
+                            optional:
+                              description: Specify whether the Secret or its 
key must
+                                be defined
+                              type: boolean
+                          required:
+                          - key
+                          type: object
+                          x-kubernetes-map-type: atomic
+                      type: object
+                  required:
+                  - name
+                  type: object
+                type: array
               image:
                 type: string
               ports:
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index f2b3a78..0ab68b1 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -4,6 +4,22 @@ kind: ClusterRole
 metadata:
   name: manager-role
 rules:
+- apiGroups:
+  - ""
+  resources:
+  - configmaps
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
 - apiGroups:
   - apps
   resources:
diff --git a/controllers/seataserver_controller.go 
b/controllers/seataserver_controller.go
index a73f184..899da36 100644
--- a/controllers/seataserver_controller.go
+++ b/controllers/seataserver_controller.go
@@ -54,6 +54,8 @@ const RequeueSeconds = 10
 
//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
 
//+kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
 //+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
+//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch
+//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
 
 // Reconcile is part of the main kubernetes reconciliation loop which aims to
 // move the current state of the cluster closer to the desired state.
@@ -183,7 +185,22 @@ func (r *SeataServerReconciler) updateStatefulSet(ctx 
context.Context, s *seatav
                s.Status.Synchronized = false
        }
        if readySize == newSize && !s.Status.Synchronized {
-               if err = seata.SyncRaftCluster(ctx, s); err != nil {
+               username, password := "seata", "seata"
+               for _, env := range s.Spec.Env {
+                       if env.Name == "console.user.username" {
+                               username, err = seata.FetchEnvVar(ctx, 
r.Client, s, env)
+                               if err != nil {
+                                       logger.Error(err, "Failed to fetch Env 
console.user.username")
+                               }
+                       }
+                       if env.Name == "console.user.password" {
+                               password, err = seata.FetchEnvVar(ctx, 
r.Client, s, env)
+                               if err != nil {
+                                       logger.Error(err, "Failed to fetch Env 
console.user.password")
+                               }
+                       }
+               }
+               if err = seata.SyncRaftCluster(ctx, s, username, password); err 
!= nil {
                        logger.Error(err, "Failed to synchronize the raft 
cluster")
                        s.Status.Synchronized = false
                } else {
diff --git a/deploy/seata-server-cluster.yaml b/deploy/seata-server-cluster.yaml
index 4499c7a..db81345 100644
--- a/deploy/seata-server-cluster.yaml
+++ b/deploy/seata-server-cluster.yaml
@@ -5,9 +5,25 @@ metadata:
   namespace: default
 spec:
   serviceName: seata-server-cluster
-  replicas: 2
-  image: seataio/seata-server:2.0.0
+  replicas: 1
+  image: seataio/seata-server:latest
   store:
     resources:
       requests:
         storage: 5Gi
+  env:
+    - name: console.user.username
+      value: seata
+    - name: console.user.password
+      valueFrom:
+        secretKeyRef:
+          name: seata
+          key: password
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: seata
+type: Opaque
+data:
+  password: MTIzNDU2
diff --git a/pkg/seata/fetchers.go b/pkg/seata/fetchers.go
new file mode 100644
index 0000000..26bca69
--- /dev/null
+++ b/pkg/seata/fetchers.go
@@ -0,0 +1,70 @@
+package seata
+
+import (
+       "context"
+       "fmt"
+       seatav1alpha1 "github.com/apache/seata-k8s/api/v1alpha1"
+       v1 "k8s.io/api/core/v1"
+       "k8s.io/apimachinery/pkg/api/errors"
+       "k8s.io/apimachinery/pkg/types"
+       "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+func FetchEnvVar(ctx context.Context, c client.Client, cr 
*seatav1alpha1.SeataServer, envVar v1.EnvVar) (string, error) {
+       if envVar.ValueFrom == nil {
+               return envVar.Value, nil
+       }
+
+       // Inspired by kubelet#makeEnvironmentVariables, determine the final 
values of variables.
+       // See 
https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_pods.go#L694-L806
+       var result string
+       switch {
+       case envVar.ValueFrom.ConfigMapKeyRef != nil:
+               cm := envVar.ValueFrom.ConfigMapKeyRef
+               name := cm.Name
+               key := cm.Key
+               optional := cm.Optional != nil && *cm.Optional
+
+               configMap := &v1.ConfigMap{}
+               err := c.Get(ctx, types.NamespacedName{Name: name, Namespace: 
cr.Namespace}, configMap)
+               if err != nil {
+                       if errors.IsNotFound(err) && optional {
+                               // ignore error when marked optional
+                               return result, nil
+                       }
+                       return result, err
+               }
+               runtimeVal, ok := configMap.Data[key]
+               if !ok {
+                       if optional {
+                               return result, nil
+                       }
+                       return result, fmt.Errorf("couldn't find key %v in 
ConfigMap %v/%v", key, cr.Namespace, name)
+               }
+               result = runtimeVal
+       case envVar.ValueFrom.SecretKeyRef != nil:
+               s := envVar.ValueFrom.SecretKeyRef
+               name := s.Name
+               key := s.Key
+               optional := s.Optional != nil && *s.Optional
+               secret := &v1.Secret{}
+               err := c.Get(ctx, types.NamespacedName{Name: name, Namespace: 
cr.Namespace}, secret)
+               if err != nil {
+                       if errors.IsNotFound(err) && optional {
+                               // ignore error when marked optional
+                               return result, nil
+                       }
+                       return result, err
+               }
+               runtimeValBytes, ok := secret.Data[key]
+               if !ok {
+                       if optional {
+                               return result, nil
+                       }
+                       return result, fmt.Errorf("couldn't find key %v in 
Secret %v/%v", key, cr.Namespace, name)
+               }
+               runtimeVal := string(runtimeValBytes)
+               result = runtimeVal
+       }
+       return result, nil
+}
diff --git a/pkg/seata/generators.go b/pkg/seata/generators.go
index 76f2385..21addc6 100644
--- a/pkg/seata/generators.go
+++ b/pkg/seata/generators.go
@@ -137,8 +137,8 @@ func MakeStatefulSet(s *seatav1alpha1.SeataServer) 
*appsv1.StatefulSet {
 
        addr := utils.ConcatRaftServerAddress(s)
        envs = append(envs, apiv1.EnvVar{Name: "server.raft.serverAddr", Value: 
addr})
-       for k, v := range s.Spec.Env {
-               envs = append(envs, apiv1.EnvVar{Name: k, Value: v})
+       for _, env := range s.Spec.Env {
+               envs = append(envs, env)
        }
        container.Env = envs
 
diff --git a/pkg/seata/synchronizers.go b/pkg/seata/synchronizers.go
index 71c0da2..6a12c33 100644
--- a/pkg/seata/synchronizers.go
+++ b/pkg/seata/synchronizers.go
@@ -33,17 +33,9 @@ type rspData struct {
        Success bool   `json:"success"`
 }
 
-func changeCluster(s *seatav1alpha1.SeataServer, i int32) error {
+func changeCluster(s *seatav1alpha1.SeataServer, i int32, username string, 
password string) error {
        client := http.Client{}
        host := fmt.Sprintf("%s-%d.%s.%s.svc:%d", s.Name, i, 
s.Spec.ServiceName, s.Namespace, s.Spec.Ports.ConsolePort)
-       username, ok := s.Spec.Env["console.user.username"]
-       if !ok {
-               username = "seata"
-       }
-       password, ok := s.Spec.Env["console.user.password"]
-       if !ok {
-               password = "seata"
-       }
 
        values := map[string]string{"username": username, "password": password}
        jsonValue, _ := json.Marshal(values)
@@ -100,7 +92,7 @@ func changeCluster(s *seatav1alpha1.SeataServer, i int32) 
error {
        return nil
 }
 
-func SyncRaftCluster(ctx context.Context, s *seatav1alpha1.SeataServer) error {
+func SyncRaftCluster(ctx context.Context, s *seatav1alpha1.SeataServer, 
username string, password string) error {
        logger := log.FromContext(ctx)
        group, childContext := errgroup.WithContext(ctx)
 
@@ -111,7 +103,7 @@ func SyncRaftCluster(ctx context.Context, s 
*seatav1alpha1.SeataServer) error {
                        case <-childContext.Done():
                                return nil
                        default:
-                               err := changeCluster(s, finalI)
+                               err := changeCluster(s, finalI, username, 
password)
                                if err != nil {
                                        logger.Error(err, fmt.Sprintf("fail to 
SyncRaftCluster at %d-th pod", finalI))
                                }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to