Skip to content

Commit

Permalink
Add feature flags to config store
Browse files Browse the repository at this point in the history
Feature flags configuration is now tracked by the config store. This
allows watching for the corresponding config map changes, removing the
need to call kubernetes API to get the feature flags config map every
time a pod is created.

Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
  • Loading branch information
adshmh committed May 22, 2020
1 parent fc24674 commit 76b39ec
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 9 deletions.
78 changes: 78 additions & 0 deletions pkg/apis/config/feature_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
Copyright 2020 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
"fmt"
"os"
"strconv"

corev1 "k8s.io/api/core/v1"
)

const (
disableHomeEnvOverwriteKey = "disable-home-env-overwrite"
disableWorkingDirOverwriteKey = "disable-working-directory-overwrite"
DefaultDisableHomeEnvOverwrite = false
DefaultDisableWorkingDirOverwrite = false
)

// FeatureFlags holds the features configurations
// +k8s:deepcopy-gen=true
type FeatureFlags struct {
DisableHomeEnvOverwrite bool
DisableWorkingDirOverwrite bool
}

// GetFeatureFlagsConfigName returns the name of the configmap containing all
// feature flags.
func GetFeatureFlagsConfigName() string {
if e := os.Getenv("CONFIG_FEATURE_FLAGS_NAME"); e != "" {
return e
}
return "feature-flags"
}

// NewFeatureFlagsFromMap returns a Config given a map corresponding to a ConfigMap
func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) {
tc := FeatureFlags{
DisableHomeEnvOverwrite: DefaultDisableHomeEnvOverwrite,
DisableWorkingDirOverwrite: DefaultDisableWorkingDirOverwrite,
}

if disableHomeEnvOverwrite, ok := cfgMap[disableHomeEnvOverwriteKey]; ok {
disable, err := strconv.ParseBool(disableHomeEnvOverwrite)
if err != nil {
return nil, fmt.Errorf("failed parsing feature flags config %q: %v", disableHomeEnvOverwriteKey, err)
}
tc.DisableHomeEnvOverwrite = disable
}
if disableWorkingDirOverwrite, ok := cfgMap[disableWorkingDirOverwriteKey]; ok {
disable, err := strconv.ParseBool(disableWorkingDirOverwrite)
if err != nil {
return nil, fmt.Errorf("failed parsing feature flags config %q: %v", disableWorkingDirOverwriteKey, err)
}

tc.DisableWorkingDirOverwrite = disable
}
return &tc, nil
}

// NewFeatureFlagsFromConfigMap returns a Config for the given configmap
func NewFeatureFlagsFromConfigMap(config *corev1.ConfigMap) (*FeatureFlags, error) {
return NewFeatureFlagsFromMap(config.Data)
}
99 changes: 99 additions & 0 deletions pkg/apis/config/feature_flags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
Copyright 2020 The Tekton Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
"os"
"testing"

"github.com/google/go-cmp/cmp"
test "github.com/tektoncd/pipeline/pkg/reconciler/testing"
"github.com/tektoncd/pipeline/test/diff"
)

func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
type testCase struct {
expectedConfig *FeatureFlags
fileName string
}

testCases := []testCase{
{
expectedConfig: &FeatureFlags{},
fileName: GetFeatureFlagsConfigName(),
},
{
expectedConfig: &FeatureFlags{
DisableHomeEnvOverwrite: true,
DisableWorkingDirOverwrite: true,
},
fileName: "feature-flags-all-flags-set",
},
}

for _, tc := range testCases {
verifyConfigFileWithExpectedFeatureFlagsConfig(t, tc.fileName, tc.expectedConfig)
}
}

func TestNewFeatureFlagsFromEmptyConfigMap(t *testing.T) {
FeatureFlagsConfigEmptyName := "feature-flags-empty"
expectedConfig := &FeatureFlags{}
verifyConfigFileWithExpectedFeatureFlagsConfig(t, FeatureFlagsConfigEmptyName, expectedConfig)
}

func TestGetFeatureFlagsConfigName(t *testing.T) {
for _, tc := range []struct {
description string
featureFlagEnvValue string
expected string
}{{
description: "Feature flags config value not set",
featureFlagEnvValue: "",
expected: "feature-flags",
}, {
description: "Feature flags config value set",
featureFlagEnvValue: "feature-flags-test",
expected: "feature-flags-test",
}} {
t.Run(tc.description, func(t *testing.T) {
original := os.Getenv("CONFIG_FEATURE_FLAGS_NAME")
defer t.Cleanup(func() {
os.Setenv("CONFIG_FEATURE_FLAGS_NAME", original)
})
if tc.featureFlagEnvValue != "" {
os.Setenv("CONFIG_FEATURE_FLAGS_NAME", tc.featureFlagEnvValue)
}
got := GetFeatureFlagsConfigName()
want := tc.expected
if got != want {
t.Errorf("GetFeatureFlagsConfigName() = %s, want %s", got, want)
}
})
}
}

func verifyConfigFileWithExpectedFeatureFlagsConfig(t *testing.T, fileName string, expectedConfig *FeatureFlags) {
cm := test.ConfigMapFromTestFile(t, fileName)
if flags, err := NewFeatureFlagsFromConfigMap(cm); err == nil {
if d := cmp.Diff(flags, expectedConfig); d != "" {
t.Errorf("Diff:\n%s", diff.PrintWantGot(d))
}
} else {
t.Errorf("NewFeatureFlagsFromConfigMap(actual) = %v", err)
}
}
24 changes: 19 additions & 5 deletions pkg/apis/config/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ type cfgKey struct{}
// Config holds the collection of configurations that we attach to contexts.
// +k8s:deepcopy-gen=false
type Config struct {
Defaults *Defaults
Defaults *Defaults
FeatureFlags *FeatureFlags
}

// FromContext extracts a Config from the provided context.
Expand All @@ -46,8 +47,10 @@ func FromContextOrDefaults(ctx context.Context) *Config {
return cfg
}
defaults, _ := NewDefaultsFromMap(map[string]string{})
featureFlags, _ := NewFeatureFlagsFromMap(map[string]string{})
return &Config{
Defaults: defaults,
Defaults: defaults,
FeatureFlags: featureFlags,
}
}

Expand All @@ -67,10 +70,11 @@ type Store struct {
func NewStore(logger configmap.Logger, onAfterStore ...func(name string, value interface{})) *Store {
store := &Store{
UntypedStore: configmap.NewUntypedStore(
"defaults",
"defaults/features",
logger,
configmap.Constructors{
GetDefaultsConfigName(): NewDefaultsFromConfigMap,
GetDefaultsConfigName(): NewDefaultsFromConfigMap,
GetFeatureFlagsConfigName(): NewFeatureFlagsFromConfigMap,
},
onAfterStore...,
),
Expand All @@ -86,7 +90,17 @@ func (s *Store) ToContext(ctx context.Context) context.Context {

// Load creates a Config from the current config state of the Store.
func (s *Store) Load() *Config {
defaults := s.UntypedLoad(GetDefaultsConfigName())
if defaults == nil {
defaults, _ = NewDefaultsFromMap(map[string]string{})
}
featureFlags := s.UntypedLoad(GetFeatureFlagsConfigName())
if featureFlags == nil {
featureFlags, _ = NewFeatureFlagsFromMap(map[string]string{})
}

return &Config{
Defaults: s.UntypedLoad(GetDefaultsConfigName()).(*Defaults).DeepCopy(),
Defaults: defaults.(*Defaults).DeepCopy(),
FeatureFlags: featureFlags.(*FeatureFlags).DeepCopy(),
}
}
18 changes: 14 additions & 4 deletions pkg/apis/config/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,24 @@ import (
)

func TestStoreLoadWithContext(t *testing.T) {
store := NewStore(logtesting.TestLogger(t))
defaultConfig := test.ConfigMapFromTestFile(t, "config-defaults")
featuresConfig := test.ConfigMapFromTestFile(t, "feature-flags-all-flags-set")

expectedDefaults, _ := NewDefaultsFromConfigMap(defaultConfig)
expectedFeatures, _ := NewFeatureFlagsFromConfigMap(featuresConfig)

expected := &Config{
Defaults: expectedDefaults,
FeatureFlags: expectedFeatures,
}

store := NewStore(logtesting.TestLogger(t))
store.OnConfigChanged(defaultConfig)
store.OnConfigChanged(featuresConfig)

config := FromContext(store.ToContext(context.Background()))

expected, _ := NewDefaultsFromConfigMap(defaultConfig)
if d := cmp.Diff(config.Defaults, expected); d != "" {
t.Errorf("Unexpected default config %s", diff.PrintWantGot(d))
if d := cmp.Diff(config, expected); d != "" {
t.Errorf("Unexpected config %s", diff.PrintWantGot(d))
}
}
22 changes: 22 additions & 0 deletions pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2020 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: ConfigMap
metadata:
name: feature-flags
namespace: tekton-pipelines
data:
disable-home-env-overwrite: "true"
disable-working-directory-overwrite: "true"
26 changes: 26 additions & 0 deletions pkg/apis/config/testdata/feature-flags-empty.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2020 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: ConfigMap
metadata:
name: feature-flags
namespace: tekton-pipelines
data:
_example: |
################################
# #
# EXAMPLE CONFIGURATION #
# #
################################
22 changes: 22 additions & 0 deletions pkg/apis/config/testdata/feature-flags.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2020 The Tekton Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: ConfigMap
metadata:
name: feature-flags
namespace: tekton-pipelines
data:
disable-home-env-overwrite: "false"
disable-working-directory-overwrite: "false"
16 changes: 16 additions & 0 deletions pkg/apis/config/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 76b39ec

Please sign in to comment.