Skip to content

Commit a4e26f3

Browse files
authored
[Feature] Propagate env variables to members (#1100)
1 parent 05f9757 commit a4e26f3

File tree

15 files changed

+349
-25
lines changed

15 files changed

+349
-25
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
- (Feature) Sensitive information protection
2929
- (Bugfix) Propagate SecurityContext to the ID Containers
3030
- (Bugfix) Fix for enabling all features
31-
31+
- (Feature) Propagate feature and predefined env variables to members
32+
3233
## [1.2.15](https://github.com/arangodb/kube-arangodb/tree/1.2.15) (2022-07-20)
3334
- (Bugfix) Ensure pod names not too long
3435
- (Refactor) Use cached member's clients

chart/kube-arangodb/templates/deployment-operator/role.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ rules:
2727
verbs: ["*"]
2828
{{- end }}
2929
- apiGroups: [""]
30-
resources: ["pods", "services", "endpoints", "persistentvolumeclaims", "events", "secrets", "serviceaccounts"]
30+
resources: ["pods", "services", "endpoints", "persistentvolumeclaims", "events", "secrets", "serviceaccounts", "configmaps"]
3131
verbs: ["*"]
3232
- apiGroups: ["apps"]
3333
resources: ["deployments", "replicasets"]

cmd/cmd.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"net"
2828
"net/http"
2929
"os"
30+
"reflect"
3031
"strconv"
3132
"strings"
3233
"time"
@@ -120,6 +121,8 @@ var (
120121
versionOnly bool // Run only version endpoint, explicitly disabled with other
121122
enableK2KClusterSync bool // Run k2kClusterSync operator
122123

124+
operatorFeatureConfigMap string // ConfigMap name
125+
123126
scalingIntegrationEnabled bool
124127

125128
alpineImage, metricsExporterImage, arangoImage string
@@ -320,6 +323,9 @@ func executeMain(cmd *cobra.Command, args []string) {
320323
if err != nil {
321324
logger.Err(err).Fatal("Failed to create operator config & deps")
322325
}
326+
if err := ensureFeaturesConfigMap(context.Background(), client.Kubernetes().CoreV1().ConfigMaps(namespace), cfg); err != nil {
327+
logger.Err(err).Error("Failed to create features config map")
328+
}
323329
o, err := operator.NewOperator(cfg, deps)
324330
if err != nil {
325331
logger.Err(err).Fatal("Failed to create operator")
@@ -531,3 +537,53 @@ func createRecorder(kubecli kubernetes.Interface, name, namespace string) record
531537
apps.AddToScheme(combinedScheme)
532538
return eventBroadcaster.NewRecorder(combinedScheme, core.EventSource{Component: name})
533539
}
540+
541+
// ensureFeaturesConfigMap creates or updates config map with enabled features.
542+
func ensureFeaturesConfigMap(ctx context.Context, client typedCore.ConfigMapInterface, cfg operator.Config) error {
543+
ft := features.GetFeatureMap()
544+
545+
featuresCM := make(map[string]string, len(ft))
546+
547+
for k, v := range ft {
548+
if v {
549+
featuresCM[k] = features.Enabled
550+
} else {
551+
featuresCM[k] = features.Disabled
552+
}
553+
}
554+
555+
nctx, c := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
556+
defer c()
557+
if cm, err := client.Get(nctx, features.ConfigMapName(), meta.GetOptions{}); err != nil {
558+
if !deploymentApi.IsNotFound(err) {
559+
return err
560+
}
561+
562+
nctx, c := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
563+
defer c()
564+
if _, err := client.Create(nctx, &core.ConfigMap{
565+
ObjectMeta: meta.ObjectMeta{
566+
Name: features.ConfigMapName(),
567+
Namespace: cfg.Namespace,
568+
},
569+
Data: make(map[string]string),
570+
}, meta.CreateOptions{}); err != nil {
571+
return err
572+
}
573+
574+
return nil
575+
} else if !reflect.DeepEqual(cm.Data, featuresCM) {
576+
q := cm.DeepCopy()
577+
q.Data = featuresCM
578+
579+
nctx, c := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
580+
defer c()
581+
if _, err := client.Update(nctx, q, meta.UpdateOptions{}); err != nil {
582+
return err
583+
}
584+
585+
return nil
586+
}
587+
588+
return nil
589+
}

pkg/deployment/deployment_pod_volumes_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ func TestEnsurePod_ArangoDB_Volumes(t *testing.T) {
172172
},
173173
},
174174
},
175-
176175
{
177176
Name: "DBserver POD with Volume Mount",
178177
ArangoDeployment: &api.ArangoDeployment{

pkg/deployment/deployment_suite_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"os"
2727
"path/filepath"
2828
"sort"
29+
"strconv"
2930
"strings"
3031
"testing"
3132

@@ -44,9 +45,11 @@ import (
4445
"github.com/arangodb/kube-arangodb/pkg/apis/shared"
4546
"github.com/arangodb/kube-arangodb/pkg/deployment/acs"
4647
"github.com/arangodb/kube-arangodb/pkg/deployment/client"
48+
"github.com/arangodb/kube-arangodb/pkg/deployment/features"
4749
"github.com/arangodb/kube-arangodb/pkg/deployment/resources"
4850
"github.com/arangodb/kube-arangodb/pkg/deployment/resources/inspector"
4951
arangofake "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned/fake"
52+
"github.com/arangodb/kube-arangodb/pkg/util"
5053
"github.com/arangodb/kube-arangodb/pkg/util/arangod/conn"
5154
"github.com/arangodb/kube-arangodb/pkg/util/constants"
5255
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
@@ -608,6 +611,8 @@ func (testCase *testCaseStruct) createTestPodData(deployment *Deployment, group
608611

609612
deployment.currentObject.Status.Members.Update(member, group)
610613
}
614+
615+
testCase.createTestEnvVariables(deployment, group)
611616
}
612617

613618
func finalizers(group api.ServerGroup) []string {
@@ -786,3 +791,81 @@ func addLifecycle(name string, uuidRequired bool, license string, group api.Serv
786791
}
787792
}
788793
}
794+
795+
func (testCase *testCaseStruct) createTestEnvVariables(deployment *Deployment, group api.ServerGroup) {
796+
if group == api.ServerGroupSyncMasters || group == api.ServerGroupSyncWorkers {
797+
return
798+
}
799+
800+
// Set up environment variables.
801+
for i, container := range testCase.ExpectedPod.Spec.Containers {
802+
if container.Name != api.ServerGroupReservedContainerNameServer {
803+
continue
804+
}
805+
806+
testCase.ExpectedPod.Spec.Containers[i].EnvFrom = []core.EnvFromSource{
807+
{
808+
ConfigMapRef: &core.ConfigMapEnvSource{
809+
LocalObjectReference: core.LocalObjectReference{
810+
Name: features.ConfigMapName(),
811+
},
812+
Optional: util.NewBool(true),
813+
},
814+
},
815+
}
816+
817+
var version, enterprise string
818+
if len(deployment.currentObjectStatus.Images) > 0 {
819+
version = string(deployment.currentObjectStatus.Images[0].ArangoDBVersion)
820+
enterprise = strconv.FormatBool(deployment.currentObjectStatus.Images[0].Enterprise)
821+
}
822+
823+
if version == "" {
824+
version = testVersion
825+
}
826+
if enterprise == "" {
827+
enterprise = "false"
828+
}
829+
830+
if !isEnvExist(testCase.ExpectedPod.Spec.Containers[i].Env, resources.ArangoDBOverrideServerGroupEnv) {
831+
testCase.ExpectedPod.Spec.Containers[i].Env = append(testCase.ExpectedPod.Spec.Containers[i].Env,
832+
core.EnvVar{
833+
Name: resources.ArangoDBOverrideServerGroupEnv,
834+
Value: group.AsRole(),
835+
})
836+
}
837+
if !isEnvExist(testCase.ExpectedPod.Spec.Containers[i].Env, resources.ArangoDBOverrideDeploymentModeEnv) {
838+
testCase.ExpectedPod.Spec.Containers[i].Env = append(testCase.ExpectedPod.Spec.Containers[i].Env,
839+
core.EnvVar{
840+
Name: resources.ArangoDBOverrideDeploymentModeEnv,
841+
Value: string(testCase.ArangoDeployment.Spec.GetMode()),
842+
})
843+
}
844+
845+
if !isEnvExist(testCase.ExpectedPod.Spec.Containers[i].Env, resources.ArangoDBOverrideVersionEnv) {
846+
testCase.ExpectedPod.Spec.Containers[i].Env = append(testCase.ExpectedPod.Spec.Containers[i].Env,
847+
core.EnvVar{
848+
Name: resources.ArangoDBOverrideVersionEnv,
849+
Value: version,
850+
})
851+
}
852+
853+
if !isEnvExist(testCase.ExpectedPod.Spec.Containers[i].Env, resources.ArangoDBOverrideEnterpriseEnv) {
854+
testCase.ExpectedPod.Spec.Containers[i].Env = append(testCase.ExpectedPod.Spec.Containers[i].Env,
855+
core.EnvVar{
856+
Name: resources.ArangoDBOverrideEnterpriseEnv,
857+
Value: enterprise,
858+
})
859+
}
860+
}
861+
}
862+
863+
func isEnvExist(envs []core.EnvVar, name string) bool {
864+
for _, env := range envs {
865+
if env.Name == name {
866+
return true
867+
}
868+
}
869+
870+
return false
871+
}

pkg/deployment/features/config.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
21+
package features
22+
23+
const (
24+
DefaultFeaturesConfigMap = "arangodb-operator-feature-config-map"
25+
)
26+
27+
var configMapName = DefaultFeaturesConfigMap
28+
29+
func ConfigMapName() string {
30+
return configMapName
31+
}

pkg/deployment/features/features.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ package features
2222

2323
import "github.com/arangodb/go-driver"
2424

25+
const (
26+
Enabled = "true"
27+
Disabled = "false"
28+
)
29+
2530
var _ Feature = &feature{}
2631

2732
type Feature interface {

pkg/deployment/features/local.go

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,18 @@ package features
2222

2323
import (
2424
"fmt"
25+
"os"
2526
"sync"
2627

2728
"github.com/spf13/cobra"
2829

2930
"github.com/arangodb/go-driver"
31+
32+
"github.com/arangodb/kube-arangodb/pkg/util"
3033
)
3134

35+
const prefixArg = "deployment.feature"
36+
3237
var features = map[string]Feature{}
3338
var featuresLock sync.Mutex
3439
var enableAll = false
@@ -54,6 +59,9 @@ var internalCMD = &cobra.Command{
5459
Run: cmdRun,
5560
}
5661

62+
// Init initializes all registered features.
63+
// If a feature is not provided via process's argument, then it is taken from environment variable
64+
// or from enabled by default setting.
5765
func Init(cmd *cobra.Command) error {
5866
featuresLock.Lock()
5967
defer featuresLock.Unlock()
@@ -62,7 +70,8 @@ func Init(cmd *cobra.Command) error {
6270

6371
f := cmd.Flags()
6472

65-
f.BoolVar(&enableAll, "deployment.feature.all", false, "Enable ALL Features")
73+
featureArgName := GetFeatureArgName("all")
74+
f.BoolVar(&enableAll, featureArgName, isEnabledFeatureFromEnv(featureArgName), "Enable ALL Features")
6675

6776
for _, feature := range features {
6877
z := ""
@@ -79,21 +88,25 @@ func Init(cmd *cobra.Command) error {
7988
}
8089
}
8190

82-
featureName := fmt.Sprintf("deployment.feature.%s", feature.Name())
83-
f.BoolVar(feature.EnabledPointer(), featureName, feature.EnabledByDefault(), z)
91+
featureArgName = GetFeatureArgName(feature.Name())
92+
enabled := feature.EnabledByDefault() || isEnabledFeatureFromEnv(featureArgName)
93+
f.BoolVar(feature.EnabledPointer(), featureArgName, enabled, z)
94+
8495
if ok, reason := feature.Deprecated(); ok {
85-
if err := f.MarkDeprecated(featureName, reason); err != nil {
96+
if err := f.MarkDeprecated(featureArgName, reason); err != nil {
8697
return err
8798
}
8899
}
89100

90101
if feature.Hidden() {
91-
if err := f.MarkHidden(featureName); err != nil {
102+
if err := f.MarkHidden(featureArgName); err != nil {
92103
return err
93104
}
94105
}
95106
}
96107

108+
f.StringVar(&configMapName, "features-config-map-name", DefaultFeaturesConfigMap, "Name of the Feature Map ConfigMap")
109+
97110
return nil
98111
}
99112

@@ -127,6 +140,39 @@ func cmdRun(_ *cobra.Command, _ []string) {
127140
}
128141
}
129142

143+
// Supported returns false when:
144+
// - feature is disabled.
145+
// - a given version is lower than minimum feature version.
146+
// - feature expects enterprise but a given enterprise arg is not true.
130147
func Supported(f Feature, v driver.Version, enterprise bool) bool {
131-
return f.Enabled() && ((f.EnterpriseRequired() && enterprise) || !f.EnterpriseRequired()) && v.CompareTo(f.Version()) >= 0
148+
if !f.Enabled() {
149+
return false
150+
}
151+
152+
if f.EnterpriseRequired() && !enterprise {
153+
// This feature requires enterprise version but current version is not enterprise.
154+
return false
155+
}
156+
157+
return v.CompareTo(f.Version()) >= 0
158+
}
159+
160+
// GetFeatureMap returns all features' arguments names.
161+
func GetFeatureMap() map[string]bool {
162+
args := make(map[string]bool, len(features))
163+
for _, f := range features {
164+
args[GetFeatureArgName(f.Name())] = f.Enabled()
165+
}
166+
167+
return args
168+
}
169+
170+
// GetFeatureArgName returns feature process argument name.
171+
func GetFeatureArgName(featureName string) string {
172+
return fmt.Sprintf("%s.%s", prefixArg, featureName)
173+
}
174+
175+
// isEnabledFeatureFromEnv returns true if argument is enabled as an environment variable.
176+
func isEnabledFeatureFromEnv(arg string) bool {
177+
return os.Getenv(util.NormalizeEnv(arg)) == Enabled
132178
}

0 commit comments

Comments
 (0)