Skip to content

Commit 78de13d

Browse files
committed
Add flag --unsupported-addon-features to operator binary
* enable istio-csr controller only when the feature gate is enabled Signed-off-by: Swarup Ghosh <[email protected]>
1 parent ac9cc76 commit 78de13d

File tree

5 files changed

+169
-4
lines changed

5 files changed

+169
-4
lines changed

api/operator/v1alpha1/features.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package v1alpha1
2+
3+
import (
4+
"k8s.io/component-base/featuregate"
5+
)
6+
7+
var (
8+
// TechPreview: v1.15
9+
//
10+
// IstioCSR enables the controller for istiocsr.operator.openshift.io resource,
11+
// which extends cert-manager-operator to deploy and manage the istio-csr agent.
12+
// OpenShift Service Mesh facilitates the integration and istio-csr is an agent that
13+
// allows Istio workload and control plane components to be secured using cert-manager.
14+
//
15+
// For more details,
16+
// https://github.com/openshift/enhancements/blob/master/enhancements/cert-manager/istio-csr-controller.md
17+
FeatureIstioCSR featuregate.Feature = "IstioCSR"
18+
)
19+
20+
var OperatorFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
21+
FeatureIstioCSR: {Default: false, PreRelease: "TechPreview"},
22+
}

pkg/cmd/operator/cmd.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,16 @@ func NewOperator() *cobra.Command {
2020
cmd.Short = "Start the cert-manager Operator"
2121
cmd.Flags().StringVar(&operator.TrustedCAConfigMapName, "trusted-ca-configmap", "", "The name of the config map containing TLS CA(s) which should be trusted by the controller's containers. PEM encoded file under \"ca-bundle.crt\" key is expected.")
2222
cmd.Flags().StringVar(&operator.CloudCredentialSecret, "cloud-credentials-secret", "", "The name of the secret containing cloud credentials for authenticating using cert-manager ambient credentials mode.")
23+
24+
cmd.Flags().StringVar(&operator.UnsupportedAddonFeatures, "unsupported-addon-features", "",
25+
`List of unsupported addon features that the operator optionally enables.
26+
27+
eg. --unsupported-addon-features="IstioCSR=true"
28+
29+
Note: Technology Preview features are not supported with Red Hat production service level agreements (SLAs)
30+
and might not be functionally complete. Red Hat does not recommend using them in production.
31+
32+
These features provide early access to upcoming product features,
33+
enabling customers to test functionality and provide feedback during the development process.`)
2334
return cmd
2435
}

pkg/features/features.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package features
2+
3+
import (
4+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
5+
"k8s.io/component-base/featuregate"
6+
7+
"github.com/openshift/cert-manager-operator/api/operator/v1alpha1"
8+
)
9+
10+
var (
11+
// mutableFeatureGate is the top-level FeatureGate with mutability (read/write).
12+
mutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()
13+
14+
// DefaultFeatureGate is the top-level shared global FeatureGate (read-only).
15+
DefaultFeatureGate featuregate.FeatureGate = mutableFeatureGate
16+
)
17+
18+
func init() {
19+
utilruntime.Must(mutableFeatureGate.Add(v1alpha1.OperatorFeatureGates))
20+
}
21+
22+
func SetupWithFlagValue(flagValue string) error {
23+
return mutableFeatureGate.Set(flagValue)
24+
}

pkg/features/features_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package features
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"k8s.io/component-base/featuregate"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
// expectedDefaultFeatureState is a map of features with known states
14+
//
15+
// Always update this test map when adding a new FeatureName to the api,
16+
// a test is added below to enforce this.
17+
var expectedDefaultFeatureState = map[bool][]featuregate.Feature{
18+
// features ENABLED by default,
19+
// list of features which are expected to be enabled at runtime.
20+
true: {},
21+
22+
// features DISABLED by default,
23+
// list of features which are expected to be disabled at runtime.
24+
false: {
25+
featuregate.Feature("IstioCSR"),
26+
},
27+
}
28+
29+
func TestFeatureGates(t *testing.T) {
30+
t.Run("runtime features to test should be identical with operator features", func(t *testing.T) {
31+
testFeatureNames := make([]featuregate.Feature, 0)
32+
for _, featureNames := range expectedDefaultFeatureState {
33+
testFeatureNames = append(testFeatureNames, featureNames...)
34+
}
35+
36+
knownOperatorFeatures := make([]featuregate.Feature, 0)
37+
feats := mutableFeatureGate.GetAll()
38+
for feat := range feats {
39+
// skip "AllBeta", "AllAlpha": our operator does not use those
40+
if feat == "AllBeta" || feat == "AllAlpha" {
41+
continue
42+
}
43+
knownOperatorFeatures = append(knownOperatorFeatures, feat)
44+
}
45+
46+
assert.Equal(t, knownOperatorFeatures, testFeatureNames,
47+
`the list of features known to the operator differ from what is being tested here,
48+
it could be that there was a new Feature added to the api which wasn't added to the tests.
49+
Please verify "api/operator/v1alpha1" and "pkg/operator/features" have identical features.`)
50+
})
51+
52+
t.Run("all runtime features should honor expected default feature state", func(t *testing.T) {
53+
for expectedDefaultState, features := range expectedDefaultFeatureState {
54+
for _, featureName := range features {
55+
assert.Equal(t, expectedDefaultState, DefaultFeatureGate.Enabled(featureName),
56+
"violated by feature=%s", featureName)
57+
}
58+
}
59+
})
60+
61+
t.Run("all TechPreview features should be disabled by default", func(t *testing.T) {
62+
feats := mutableFeatureGate.GetAll()
63+
for feat, spec := range feats {
64+
// skip "AllBeta", "AllAlpha": our operator does not use those
65+
if feat == "AllBeta" || feat == "AllAlpha" {
66+
continue
67+
}
68+
69+
assert.Equal(t, spec.PreRelease == "TechPreview", !spec.Default,
70+
"prerelease TechPreview %q feature should default to disabled",
71+
feat)
72+
}
73+
})
74+
75+
t.Run("runtime features should allow enabling all feature gates that are disabled by default", func(t *testing.T) {
76+
// enable all disabled features
77+
defaultDisabledFeatures := expectedDefaultFeatureState[false]
78+
79+
featVals := make([]string, len(defaultDisabledFeatures))
80+
for i := range featVals {
81+
featVals[i] = fmt.Sprintf("%s=true", defaultDisabledFeatures[i])
82+
}
83+
84+
err := mutableFeatureGate.Set(strings.Join(featVals, ","))
85+
assert.NoError(t, err)
86+
87+
// check if all those features were enabled successfully
88+
for _, featureName := range defaultDisabledFeatures {
89+
assert.Equal(t, true, DefaultFeatureGate.Enabled(featureName),
90+
"violated by feature=%s", featureName)
91+
}
92+
})
93+
}

pkg/operator/starter.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import (
1717
"github.com/openshift/library-go/pkg/operator/status"
1818
"github.com/openshift/library-go/pkg/operator/v1helpers"
1919

20+
"github.com/openshift/cert-manager-operator/api/operator/v1alpha1"
2021
"github.com/openshift/cert-manager-operator/pkg/controller/deployment"
22+
"github.com/openshift/cert-manager-operator/pkg/features"
2123
certmanoperatorclient "github.com/openshift/cert-manager-operator/pkg/operator/clientset/versioned"
2224
certmanoperatorinformers "github.com/openshift/cert-manager-operator/pkg/operator/informers/externalversions"
2325
"github.com/openshift/cert-manager-operator/pkg/operator/operatorclient"
@@ -35,6 +37,10 @@ var TrustedCAConfigMapName string
3537
// used in ambient credentials mode, and is provided as a runtime arg
3638
var CloudCredentialSecret string
3739

40+
// UnsupportedAddonFeatures is the user-specific list of unsupported addon features
41+
// that the operator optionally enables, and is provided as a runtime arg.
42+
var UnsupportedAddonFeatures string
43+
3844
func RunOperator(ctx context.Context, cc *controllercmd.ControllerContext) error {
3945
kubeClient, err := kubernetes.NewForConfig(cc.ProtoKubeConfig)
4046
if err != nil {
@@ -112,12 +118,21 @@ func RunOperator(ctx context.Context, cc *controllercmd.ControllerContext) error
112118
go controller.Run(ctx, 1)
113119
}
114120

115-
manager, err := NewControllerManager()
121+
err = features.SetupWithFlagValue(UnsupportedAddonFeatures)
116122
if err != nil {
117-
return fmt.Errorf("failed to create controller manager: %w", err)
123+
return fmt.Errorf("failed to parse addon features: %w", err)
118124
}
119-
if err := manager.Start(ctrl.SetupSignalHandler()); err != nil {
120-
return fmt.Errorf("failed to start istiocsr controller: %w", err)
125+
126+
// enable controller-runtime and istio-csr controller
127+
// only when "IstioCSR" feature is turned on from --addon-features
128+
if features.DefaultFeatureGate.Enabled(v1alpha1.FeatureIstioCSR) {
129+
manager, err := NewControllerManager()
130+
if err != nil {
131+
return fmt.Errorf("failed to create controller manager: %w", err)
132+
}
133+
if err := manager.Start(ctrl.SetupSignalHandler()); err != nil {
134+
return fmt.Errorf("failed to start istiocsr controller: %w", err)
135+
}
121136
}
122137

123138
<-ctx.Done()

0 commit comments

Comments
 (0)