Skip to content

Commit 6617ac0

Browse files
committed
(feat) inject 'Env' from config to deployment spec
Add a deployment initializer function that can inject environment variable(s) specified in pod configuration of a subscription into deployment object. The following rules apply: - Proxy env variable(s) defined in 'cluster' Proxy object should be injected into deployment object(s). - If proxy env variable defined in pod configuration of a Subscription overrides 'cluster' Proxy object. More here - https://github.com/operator-framework/operator-lifecycle-manager/blob/master/Documentation/contributors/design-proposals/operator-config.md
1 parent 54228c2 commit 6617ac0

33 files changed

+2597
-9
lines changed

cmd/olm/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010
"time"
1111

12+
configclientset "github.com/openshift/client-go/config/clientset/versioned"
1213
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
1314
"github.com/prometheus/client_golang/prometheus/promhttp"
1415
log "github.com/sirupsen/logrus"
@@ -158,6 +159,11 @@ func main() {
158159
if err != nil {
159160
log.Fatalf("error configuring client: %s", err.Error())
160161
}
162+
versionedConfigClient, err := configclientset.NewForConfig(config)
163+
if err != nil {
164+
err = fmt.Errorf("error configuring OpenShift Proxy client: %v", err)
165+
return
166+
}
161167
configClient, err := configv1client.NewForConfig(config)
162168
if err != nil {
163169
log.Fatalf("error configuring client: %s", err.Error())
@@ -179,6 +185,7 @@ func main() {
179185
olm.WithExternalClient(crClient),
180186
olm.WithOperatorClient(opClient),
181187
olm.WithRestConfig(config),
188+
olm.WithConfigClient(versionedConfigClient),
182189
)
183190
if err != nil {
184191
log.WithError(err).Fatalf("error configuring operator")

pkg/controller/install/deployment.go

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type StrategyDeploymentInstaller struct {
4343
owner ownerutil.Owner
4444
previousStrategy Strategy
4545
templateAnnotations map[string]string
46+
initializers DeploymentInitializerFuncChain
4647
}
4748

4849
func (d *StrategyDetailsDeployment) GetStrategyName() string {
@@ -52,26 +53,61 @@ func (d *StrategyDetailsDeployment) GetStrategyName() string {
5253
var _ Strategy = &StrategyDetailsDeployment{}
5354
var _ StrategyInstaller = &StrategyDeploymentInstaller{}
5455

55-
func NewStrategyDeploymentInstaller(strategyClient wrappers.InstallStrategyDeploymentInterface, templateAnnotations map[string]string, owner ownerutil.Owner, previousStrategy Strategy) StrategyInstaller {
56+
// DeploymentInitializerFunc takes a deployment object and appropriately
57+
// initializes it for install.
58+
//
59+
// Before a deployment is created on the cluster, we can run a series of
60+
// initializer functions that will properly initialize the deployment object.
61+
type DeploymentInitializerFunc func(deployment *appsv1.Deployment) error
62+
63+
// DeploymentInitializerFuncChain defines a chain of DeploymentInitializerFunc.
64+
type DeploymentInitializerFuncChain []DeploymentInitializerFunc
65+
66+
// Apply runs series of initializer functions that will properly initialize
67+
// the deployment object.
68+
func (c DeploymentInitializerFuncChain) Apply(deployment *appsv1.Deployment) (err error) {
69+
for _, initializer := range c {
70+
if initializer == nil {
71+
continue
72+
}
73+
74+
if initializationErr := initializer(deployment); initializationErr != nil {
75+
err = initializationErr
76+
break
77+
}
78+
}
79+
80+
return
81+
}
82+
83+
type DeploymentInitializerFuncBuilder func(owner ownerutil.Owner) DeploymentInitializerFunc
84+
85+
func NewStrategyDeploymentInstaller(strategyClient wrappers.InstallStrategyDeploymentInterface, templateAnnotations map[string]string, owner ownerutil.Owner, previousStrategy Strategy, initializers DeploymentInitializerFuncChain) StrategyInstaller {
5686
return &StrategyDeploymentInstaller{
5787
strategyClient: strategyClient,
5888
owner: owner,
5989
previousStrategy: previousStrategy,
6090
templateAnnotations: templateAnnotations,
91+
initializers: initializers,
6192
}
6293
}
6394

6495
func (i *StrategyDeploymentInstaller) installDeployments(deps []StrategyDeploymentSpec) error {
6596
for _, d := range deps {
66-
if _, err := i.strategyClient.CreateOrUpdateDeployment(i.deploymentForSpec(d.Name, d.Spec)); err != nil {
97+
deployment, err := i.deploymentForSpec(d.Name, d.Spec)
98+
if err != nil {
99+
return err
100+
}
101+
102+
if _, err := i.strategyClient.CreateOrUpdateDeployment(deployment); err != nil {
67103
return err
68104
}
69105
}
70106

71107
return nil
72108
}
73109

74-
func (i *StrategyDeploymentInstaller) deploymentForSpec(name string, spec appsv1.DeploymentSpec) *appsv1.Deployment {
110+
func (i *StrategyDeploymentInstaller) deploymentForSpec(name string, spec appsv1.DeploymentSpec) (deployment *appsv1.Deployment, err error) {
75111
dep := &appsv1.Deployment{Spec: spec}
76112
dep.SetName(name)
77113
dep.SetNamespace(i.owner.GetNamespace())
@@ -88,7 +124,14 @@ func (i *StrategyDeploymentInstaller) deploymentForSpec(name string, spec appsv1
88124

89125
ownerutil.AddNonBlockingOwner(dep, i.owner)
90126
ownerutil.AddOwnerLabelsForKind(dep, i.owner, v1alpha1.ClusterServiceVersionKind)
91-
return dep
127+
128+
if applyErr := i.initializers.Apply(dep); applyErr != nil {
129+
err = applyErr
130+
return
131+
}
132+
133+
deployment = dep
134+
return
92135
}
93136

94137
func (i *StrategyDeploymentInstaller) cleanupPrevious(current *StrategyDetailsDeployment, previous *StrategyDetailsDeployment) error {
@@ -185,7 +228,11 @@ func (i *StrategyDeploymentInstaller) checkForDeployments(deploymentSpecs []Stra
185228
}
186229

187230
// check equality
188-
calculated := i.deploymentForSpec(spec.Name, spec.Spec)
231+
calculated, err := i.deploymentForSpec(spec.Name, spec.Spec)
232+
if err != nil {
233+
return err
234+
}
235+
189236
if !i.equalDeployments(&calculated.Spec, &dep.Spec) {
190237
return StrategyError{Reason: StrategyErrDeploymentUpdated, Message: fmt.Sprintf("deployment changed, rolling update with patch: %s\n%#v\n%#v", diff.ObjectDiff(dep.Spec.Template.Spec, calculated.Spec.Template.Spec), calculated.Spec.Template.Spec, dep.Spec.Template.Spec)}
191238
}

pkg/controller/install/deployment_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ func TestNewStrategyDeploymentInstaller(t *testing.T) {
264264
},
265265
}
266266
fakeClient := new(clientfakes.FakeInstallStrategyDeploymentInterface)
267-
strategy := NewStrategyDeploymentInstaller(fakeClient, map[string]string{"test": "annotation"}, &mockOwner, nil)
267+
strategy := NewStrategyDeploymentInstaller(fakeClient, map[string]string{"test": "annotation"}, &mockOwner, nil, nil)
268268
require.Implements(t, (*StrategyInstaller)(nil), strategy)
269269
require.Error(t, strategy.Install(&BadStrategy{}))
270270
installed, err := strategy.CheckInstalled(&BadStrategy{})
@@ -302,7 +302,7 @@ func TestInstallStrategyDeploymentCheckInstallErrors(t *testing.T) {
302302
t.Run(tt.description, func(t *testing.T) {
303303
fakeClient := new(clientfakes.FakeInstallStrategyDeploymentInterface)
304304
strategy := strategy(1, namespace, &mockOwner)
305-
installer := NewStrategyDeploymentInstaller(fakeClient, map[string]string{"test": "annotation"}, &mockOwner, nil)
305+
installer := NewStrategyDeploymentInstaller(fakeClient, map[string]string{"test": "annotation"}, &mockOwner, nil, nil)
306306

307307
dep := testDeployment("olm-dep-1", namespace, &mockOwner)
308308
dep.Spec.Template.SetAnnotations(map[string]string{"test": "annotation"})

pkg/controller/install/resolver.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ type StrategyResolverInterface interface {
2828
InstallerForStrategy(strategyName string, opClient operatorclient.ClientInterface, opLister operatorlister.OperatorLister, owner ownerutil.Owner, annotations map[string]string, previousStrategy Strategy) StrategyInstaller
2929
}
3030

31-
type StrategyResolver struct{}
31+
type StrategyResolver struct {
32+
ProxyInjectorBuilder DeploymentInitializerFuncBuilder
33+
}
3234

3335
func (r *StrategyResolver) UnmarshalStrategy(s v1alpha1.NamedInstallStrategy) (strategy Strategy, err error) {
3436
switch s.StrategyName {
@@ -47,7 +49,13 @@ func (r *StrategyResolver) InstallerForStrategy(strategyName string, opClient op
4749
switch strategyName {
4850
case InstallStrategyNameDeployment:
4951
strategyClient := wrappers.NewInstallStrategyDeploymentClient(opClient, opLister, owner.GetNamespace())
50-
return NewStrategyDeploymentInstaller(strategyClient, annotations, owner, previousStrategy)
52+
53+
initializers := []DeploymentInitializerFunc{}
54+
if r.ProxyInjectorBuilder != nil {
55+
initializers = append(initializers, r.ProxyInjectorBuilder(owner))
56+
}
57+
58+
return NewStrategyDeploymentInstaller(strategyClient, annotations, owner, previousStrategy, initializers)
5159
}
5260

5361
// Insurance against these functions being called incorrectly (unmarshal strategy will return a valid strategy name)

pkg/controller/operators/olm/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
utilclock "k8s.io/apimachinery/pkg/util/clock"
1010
"k8s.io/client-go/rest"
1111

12+
configv1client "github.com/openshift/client-go/config/clientset/versioned"
1213
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/internalversion"
1314
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
1415
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
@@ -32,6 +33,7 @@ type operatorConfig struct {
3233
apiReconciler resolver.APIIntersectionReconciler
3334
apiLabeler labeler.Labeler
3435
restConfig *rest.Config
36+
configClient configv1client.Interface
3537
}
3638

3739
func (o *operatorConfig) apply(options []OperatorOption) {
@@ -160,3 +162,9 @@ func WithRestConfig(restConfig *rest.Config) OperatorOption {
160162
config.restConfig = restConfig
161163
}
162164
}
165+
166+
func WithConfigClient(configClient configv1client.Interface) OperatorOption {
167+
return func(config *operatorConfig) {
168+
config.configClient = configClient
169+
}
170+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package envvar
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
7+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
8+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
9+
"github.com/sirupsen/logrus"
10+
corev1 "k8s.io/api/core/v1"
11+
"k8s.io/apimachinery/pkg/labels"
12+
)
13+
14+
type operatorConfig struct {
15+
lister operatorlister.OperatorLister
16+
logger *logrus.Logger
17+
}
18+
19+
func (o *operatorConfig) GetOperatorConfig(ownerCSV ownerutil.Owner) (overrides []corev1.EnvVar, err error) {
20+
list, listErr := o.lister.OperatorsV1alpha1().SubscriptionLister().Subscriptions(ownerCSV.GetNamespace()).List(labels.Everything())
21+
if listErr != nil {
22+
err = fmt.Errorf("failed to list subscription namespace=%s - %v", ownerCSV.GetNamespace(), listErr)
23+
return
24+
}
25+
26+
owner := findOwner(list, ownerCSV)
27+
if owner == nil {
28+
o.logger.Debugf("failed to get the owner subscription csv=%s", ownerCSV.GetName())
29+
return
30+
}
31+
32+
overrides = owner.Spec.Config.Env
33+
return
34+
}
35+
36+
func findOwner(list []*v1alpha1.Subscription, ownerCSV ownerutil.Owner) *v1alpha1.Subscription {
37+
for i := range list {
38+
sub := list[i]
39+
if sub.Status.InstalledCSV == ownerCSV.GetName() {
40+
return sub
41+
}
42+
}
43+
44+
return nil
45+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package envvar
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
7+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
8+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
9+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/proxy"
10+
"github.com/sirupsen/logrus"
11+
appsv1 "k8s.io/api/apps/v1"
12+
corev1 "k8s.io/api/core/v1"
13+
)
14+
15+
// NewDeploymentInitializer returns a function that accepts a Deployment object
16+
// and initializes it with env variables specified in operator configuration.
17+
func NewDeploymentInitializer(logger *logrus.Logger, querier proxy.Querier, lister operatorlister.OperatorLister) *DeploymentInitializer {
18+
return &DeploymentInitializer{
19+
logger: logger,
20+
querier: querier,
21+
config: &operatorConfig{
22+
lister: lister,
23+
logger: logger,
24+
},
25+
}
26+
}
27+
28+
type DeploymentInitializer struct {
29+
logger *logrus.Logger
30+
querier proxy.Querier
31+
config *operatorConfig
32+
}
33+
34+
func (d *DeploymentInitializer) GetDeploymentInitializer(ownerCSV ownerutil.Owner) install.DeploymentInitializerFunc {
35+
return func(spec *appsv1.Deployment) error {
36+
err := d.initialize(ownerCSV, spec)
37+
return err
38+
}
39+
}
40+
41+
// Initialize initializes a deployment object with appropriate global cluster
42+
// level proxy env variable(s).
43+
func (d *DeploymentInitializer) initialize(ownerCSV ownerutil.Owner, deployment *appsv1.Deployment) error {
44+
var podConfigEnvVar, proxyEnvVar, merged []corev1.EnvVar
45+
var err error
46+
47+
podConfigEnvVar, err = d.config.GetOperatorConfig(ownerCSV)
48+
if err != nil {
49+
err = fmt.Errorf("failed to get subscription pod configuration - %v", err)
50+
return err
51+
}
52+
53+
if !proxy.IsOverridden(podConfigEnvVar) {
54+
proxyEnvVar, err = d.querier.QueryProxyConfig()
55+
if err != nil {
56+
err = fmt.Errorf("failed to query cluster proxy configuration - %v", err)
57+
return err
58+
}
59+
}
60+
61+
merged = append(podConfigEnvVar, proxyEnvVar...)
62+
63+
if len(merged) == 0 {
64+
d.logger.Debugf("no env var to inject into csv=%s", ownerCSV.GetName())
65+
}
66+
67+
podSpec := deployment.Spec.Template.Spec
68+
if err := InjectEnvIntoDeployment(&podSpec, merged); err != nil {
69+
return fmt.Errorf("failed to inject proxy env variable(s) into deployment spec name=%s - %v", deployment.Name, err)
70+
}
71+
72+
return nil
73+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package envvar
2+
3+
import (
4+
"errors"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
)
8+
9+
// InjectEnvIntoDeployment injects the proxy env variables specified in
10+
// proxyEnvVar into the container(s) of the given PodSpec.
11+
//
12+
// If any Container in PodSpec already defines an env variable of the same name
13+
// as any of the proxy env variables then it
14+
func InjectEnvIntoDeployment(podSpec *corev1.PodSpec, envVars []corev1.EnvVar) error {
15+
if podSpec == nil {
16+
return errors.New("no pod spec provided")
17+
}
18+
19+
for i := range podSpec.Containers {
20+
container := &podSpec.Containers[i]
21+
container.Env = merge(container.Env, envVars)
22+
}
23+
24+
return nil
25+
}
26+
27+
func merge(containerEnvVars []corev1.EnvVar, newEnvVars []corev1.EnvVar) (merged []corev1.EnvVar) {
28+
merged = containerEnvVars
29+
30+
for _, newEnvVar := range newEnvVars {
31+
existing, found := find(containerEnvVars, newEnvVar.Name)
32+
if !found {
33+
if newEnvVar.Value != "" {
34+
merged = append(merged, corev1.EnvVar{
35+
Name: newEnvVar.Name,
36+
Value: newEnvVar.Value,
37+
})
38+
}
39+
40+
continue
41+
}
42+
43+
existing.Value = newEnvVar.Value
44+
}
45+
46+
return
47+
}
48+
49+
func find(proxyEnvVar []corev1.EnvVar, name string) (envVar *corev1.EnvVar, found bool) {
50+
for i := range proxyEnvVar {
51+
if name == proxyEnvVar[i].Name {
52+
// Environment variable names are case sensitive.
53+
found = true
54+
envVar = &proxyEnvVar[i]
55+
56+
break
57+
}
58+
}
59+
60+
return
61+
}

0 commit comments

Comments
 (0)