Skip to content

Support envfrom #3191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkg/controller/operators/olm/overrides/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type operatorConfig struct {
logger *logrus.Logger
}

func (o *operatorConfig) GetConfigOverrides(ownerCSV ownerutil.Owner) (envVarOverrides []corev1.EnvVar, volumeOverrides []corev1.Volume, volumeMountOverrides []corev1.VolumeMount, tolerationOverrides []corev1.Toleration, resourcesOverride *corev1.ResourceRequirements, nodeSelectorOverride map[string]string, affinity *corev1.Affinity, annotations map[string]string, err error) {
func (o *operatorConfig) GetConfigOverrides(ownerCSV ownerutil.Owner) (envVarOverrides []corev1.EnvVar, envFromOverrides []corev1.EnvFromSource, volumeOverrides []corev1.Volume, volumeMountOverrides []corev1.VolumeMount, tolerationOverrides []corev1.Toleration, resourcesOverride *corev1.ResourceRequirements, nodeSelectorOverride map[string]string, affinity *corev1.Affinity, annotations map[string]string, err error) {
list, listErr := o.lister.OperatorsV1alpha1().SubscriptionLister().Subscriptions(ownerCSV.GetNamespace()).List(labels.Everything())
if listErr != nil {
err = fmt.Errorf("failed to list subscription namespace=%s - %v", ownerCSV.GetNamespace(), listErr)
Expand All @@ -35,6 +35,7 @@ func (o *operatorConfig) GetConfigOverrides(ownerCSV ownerutil.Owner) (envVarOve
}

envVarOverrides = owner.Spec.Config.Env
envFromOverrides = owner.Spec.Config.EnvFrom
volumeOverrides = owner.Spec.Config.Volumes
volumeMountOverrides = owner.Spec.Config.VolumeMounts
tolerationOverrides = owner.Spec.Config.Tolerations
Expand Down
6 changes: 5 additions & 1 deletion pkg/controller/operators/olm/overrides/initializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (d *DeploymentInitializer) initialize(ownerCSV ownerutil.Owner, deployment
var envVarOverrides, proxyEnvVar, merged []corev1.EnvVar
var err error

envVarOverrides, volumeOverrides, volumeMountOverrides, tolerationOverrides, resourcesOverride, nodeSelectorOverride, affinity, annotations, err := d.config.GetConfigOverrides(ownerCSV)
envVarOverrides, envFromOverrides, volumeOverrides, volumeMountOverrides, tolerationOverrides, resourcesOverride, nodeSelectorOverride, affinity, annotations, err := d.config.GetConfigOverrides(ownerCSV)
if err != nil {
err = fmt.Errorf("failed to get subscription pod configuration - %v", err)
return err
Expand All @@ -72,6 +72,10 @@ func (d *DeploymentInitializer) initialize(ownerCSV ownerutil.Owner, deployment
return fmt.Errorf("failed to inject proxy env variable(s) into deployment spec name=%s - %v", deployment.Name, err)
}

if err := inject.InjectEnvFromIntoDeployment(podSpec, envFromOverrides); err != nil {
return fmt.Errorf("failed to inject envFrom variable(s) into deployment spec name=%s - %v", deployment.Name, err)
}

if err = inject.InjectVolumesIntoDeployment(podSpec, volumeOverrides); err != nil {
return fmt.Errorf("failed to inject volume(s) into deployment spec name=%s - %v", deployment.Name, err)
}
Expand Down
75 changes: 75 additions & 0 deletions pkg/controller/operators/olm/overrides/inject/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,81 @@ func mergeEnvVars(containerEnvVars []corev1.EnvVar, newEnvVars []corev1.EnvVar)
return merged
}

// InjectEnvFromIntoDeployment injects the envFrom variables
// into the container(s) of the given PodSpec.
//
// If any Container in PodSpec already defines an envFrom variable
// as any of the provided envFrom then it will be overwritten.
func InjectEnvFromIntoDeployment(podSpec *corev1.PodSpec, envFromVars []corev1.EnvFromSource) error {
if podSpec == nil {
return errors.New("no pod spec provided")
}

for i := range podSpec.Containers {
container := &podSpec.Containers[i]
container.EnvFrom = mergeEnvFromVars(container.EnvFrom, envFromVars)
}

return nil
}

func mergeEnvFromVars(containerEnvFromVars []corev1.EnvFromSource, newEnvFromVars []corev1.EnvFromSource) (merged []corev1.EnvFromSource) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: named returns. There is a single return statement here, and it could easily be changed to return merged

merged = containerEnvFromVars

for _, newEnvFromVar := range newEnvFromVars {
found := findEnvFromVar(containerEnvFromVars, newEnvFromVar)
if found {
continue
}

merged = append(merged, newEnvFromVar)
}

return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: If you're going to use a named return, do you really need the return statement at the end of the function?

}

func findEnvFromVar(EnvFromVar []corev1.EnvFromSource, newEnvFromVar corev1.EnvFromSource) (found bool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Looks like some mixed case variables here. for consistency:

Suggested change
func findEnvFromVar(EnvFromVar []corev1.EnvFromSource, newEnvFromVar corev1.EnvFromSource) (found bool) {
func findEnvFromVar(envFromVar []corev1.EnvFromSource, newEnvFromVar corev1.EnvFromSource) (found bool) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also Nit: named returns; there is a single return statement in this function.

for i := range EnvFromVar {
if compareEnvFromVar(EnvFromVar[i], newEnvFromVar) {
found = true
break
}
}

return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: If you're going to use a named return, do you really need the return statement at the end of the function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please no named returns!

}

func compareEnvFromVar(envFromVar corev1.EnvFromSource, newEnvFromVar corev1.EnvFromSource) (found bool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Named return, but the return name is never assigned. The return value is explicitly specified.

compareprefix := newEnvFromVar.Prefix == envFromVar.Prefix
var compareConfigMap, compareSecret bool

// Compare ConfigMapRef
if newEnvFromVar.ConfigMapRef == nil && envFromVar.ConfigMapRef == nil {
compareConfigMap = true
} else if newEnvFromVar.ConfigMapRef != nil && envFromVar.ConfigMapRef != nil {
if newEnvFromVar.ConfigMapRef.Optional == nil && envFromVar.ConfigMapRef.Optional == nil {
compareConfigMap = newEnvFromVar.ConfigMapRef.LocalObjectReference == envFromVar.ConfigMapRef.LocalObjectReference
} else if newEnvFromVar.ConfigMapRef.Optional != nil && envFromVar.ConfigMapRef.Optional != nil {
compareConfigMap = newEnvFromVar.ConfigMapRef.LocalObjectReference == envFromVar.ConfigMapRef.LocalObjectReference && *newEnvFromVar.ConfigMapRef.Optional == *envFromVar.ConfigMapRef.Optional
} else {
compareConfigMap = false
}
Comment on lines +121 to +123
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly necessary, compareConfigMap defaults to false and is not set in any other way outside this nested if

}
Comment on lines +114 to +124
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block of conditionals looks pretty complicated and is a bit difficult to read. I wonder if something like this might be a bit easier to grok:

if newEnvFromVar.ConfigMapRef == nil && envFromVar.ConfigMapRef == nil {
  compareConfigMap = true
} else if newEnvFromVar.ConfigMapRef != nil && envFromVar.ConfigMapRef != nil {
  compareConfigMap = newEnvFromVar.ConfigMapRef.LocalObjectReference == envFromVar.ConfigMapRef.LocalObjectReference
  
  if newEnvFromVar.ConfigMapRef.Optional != nil && envFromVar.ConfigMapRef.Optional != nil {
    compareConfigMap = compareConfigMap && *newEnvFromVar.ConfigMapRef.Optional == *envFromVar.ConfigMapRef.Optional
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this does the same thing but reduces the overall number of conditionals and is IMO a bit easier to read. I am also willing to concede that the GH PR review UI is making this harder to read than it normally would be

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing that could be done, if constant time checks are not necessary, is to exit early from the function on any false condition. Right away, I could see this:

if newEnvFromVar.Prefix != envFromVar.Prefix {
    return false
}

etc.

// Compare SecretRef
if newEnvFromVar.SecretRef == nil && envFromVar.SecretRef == nil {
compareSecret = true
} else if newEnvFromVar.SecretRef != nil && envFromVar.SecretRef != nil {
if newEnvFromVar.SecretRef.Optional == nil && envFromVar.SecretRef.Optional == nil {
compareSecret = newEnvFromVar.SecretRef.LocalObjectReference == envFromVar.SecretRef.LocalObjectReference
} else if newEnvFromVar.SecretRef.Optional != nil && envFromVar.SecretRef.Optional != nil {
compareSecret = newEnvFromVar.SecretRef.LocalObjectReference == envFromVar.SecretRef.LocalObjectReference && *newEnvFromVar.SecretRef.Optional == *envFromVar.ConfigMapRef.Optional
} else {
compareSecret = false
}
Comment on lines +133 to +135
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly necessary, compareSecret defaults to false and is not set in any other way outside this nested if

}
return compareprefix && compareConfigMap && compareSecret
}

// InjectVolumesIntoDeployment injects the provided Volumes
// into the container(s) of the given PodSpec.
//
Expand Down
189 changes: 189 additions & 0 deletions pkg/controller/operators/olm/overrides/inject/inject_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ var (
},
}

defaultEnvFromVars = []corev1.EnvFromSource{
{
Prefix: "test",
},
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "configmapForTest",
},
},
},
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "secretForTest",
},
},
},
}

defaultVolumeMounts = []corev1.VolumeMount{
{
Name: "foo",
Expand Down Expand Up @@ -526,6 +546,175 @@ func TestInjectEnvIntoDeployment(t *testing.T) {
}
}

func TestInjectEnvFromIntoDeployment(t *testing.T) {
tests := []struct {
name string
podSpec *corev1.PodSpec
envFromVar []corev1.EnvFromSource
expected *corev1.PodSpec
}{
{
// PodSpec has one container and `EnvFrom` is empty.
// Expected: All env variable(s) specified are injected.
name: "WithContainerHasNoEnvFromVar",
podSpec: &corev1.PodSpec{
Containers: []corev1.Container{
{},
},
},
envFromVar: defaultEnvFromVars,
expected: &corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: defaultEnvFromVars,
},
},
},
},
{
// PodSpec has one container and it has overlapping envFrom var(s).
// Expected: existing duplicate env vars won't be appended in the envFrom.
name: "WithContainerHasOverlappingEnvFromVar",
podSpec: &corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: "test",
},
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "configmapForTest",
},
},
},
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "secretForTest",
},
},
},
},
},
},
},
envFromVar: defaultEnvFromVars,
expected: &corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: "test",
},
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "configmapForTest",
},
},
},
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: "secretForTest",
},
},
},
},
},
},
},
},
{
// PodSpec has one container and it has non overlapping envFrom var(s).
// Expected: existing non overlapping env vars are intact.
name: "WithContainerHasNonOverlappingEnvFromVar",
podSpec: &corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: "foo",
},
},
},
},
},
envFromVar: defaultEnvFromVars,
expected: &corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: append([]corev1.EnvFromSource{
{
Prefix: "foo",
},
}, defaultEnvFromVars...),
},
},
},
},
{
// PodSpec has more than one container(s)
// Expected: All container(s) should be updated as expected.
name: "WithMultipleContainers",
podSpec: &corev1.PodSpec{
Containers: []corev1.Container{
{},
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: "foo",
},
},
},
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: "bar",
},
},
},
},
},
envFromVar: defaultEnvFromVars,
expected: &corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: defaultEnvFromVars,
},
{
EnvFrom: append([]corev1.EnvFromSource{
{
Prefix: "foo",
},
}, defaultEnvFromVars...),
},
{
EnvFrom: append([]corev1.EnvFromSource{
{
Prefix: "bar",
},
}, defaultEnvFromVars...),
},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inject.InjectEnvFromIntoDeployment(tt.podSpec, tt.envFromVar)

podSpecWant := tt.expected
podSpecGot := tt.podSpec

assert.Equal(t, podSpecWant, podSpecGot)
Comment on lines +710 to +713
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
podSpecWant := tt.expected
podSpecGot := tt.podSpec
assert.Equal(t, podSpecWant, podSpecGot)
assert.Equal(t, tt.expected, tt.podSpec)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need the temporary variables.

})
}
}

func TestInjectTolerationsIntoDeployment(t *testing.T) {
tests := []struct {
name string
Expand Down