From ce10cd02721445f9524de4f9fb5612ed0a67abbe Mon Sep 17 00:00:00 2001 From: David Eads Date: Thu, 18 Jul 2019 10:05:35 -0400 Subject: [PATCH] inject proxy env vars into workload resources set .metadata.annotations["config.openshift.io/inject-proxy"]="container1,initcontainer2" to inject into specified container names --- lib/resourcebuilder/apps.go | 69 +++++++++++++---- lib/resourcebuilder/podspec.go | 47 ++++++++++++ lib/resourcebuilder/podspec_test.go | 112 ++++++++++++++++++++++++++++ 3 files changed, 215 insertions(+), 13 deletions(-) create mode 100644 lib/resourcebuilder/podspec.go create mode 100644 lib/resourcebuilder/podspec_test.go diff --git a/lib/resourcebuilder/apps.go b/lib/resourcebuilder/apps.go index abacb626ea..b44f265d96 100644 --- a/lib/resourcebuilder/apps.go +++ b/lib/resourcebuilder/apps.go @@ -3,11 +3,9 @@ package resourcebuilder import ( "context" "fmt" - "strings" - "k8s.io/klog" - + configv1 "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1" "github.com/openshift/cluster-version-operator/lib" "github.com/openshift/cluster-version-operator/lib/resourceapply" "github.com/openshift/cluster-version-operator/lib/resourceread" @@ -17,18 +15,21 @@ import ( "k8s.io/apimachinery/pkg/util/wait" appsclientv1 "k8s.io/client-go/kubernetes/typed/apps/v1" "k8s.io/client-go/rest" + "k8s.io/klog" ) type deploymentBuilder struct { - client *appsclientv1.AppsV1Client - raw []byte - modifier MetaV1ObjectModifierFunc + client *appsclientv1.AppsV1Client + proxyGetter configv1.ProxiesGetter + raw []byte + modifier MetaV1ObjectModifierFunc } func newDeploymentBuilder(config *rest.Config, m lib.Manifest) Interface { return &deploymentBuilder{ - client: appsclientv1.NewForConfigOrDie(withProtobuf(config)), - raw: m.Raw, + client: appsclientv1.NewForConfigOrDie(withProtobuf(config)), + proxyGetter: configv1.NewForConfigOrDie(config), + raw: m.Raw, } } @@ -46,6 +47,26 @@ func (b *deploymentBuilder) Do(ctx context.Context) error { if b.modifier != nil { b.modifier(deployment) } + + // if proxy injection is requested, get the proxy values and use them + if containerNamesString := deployment.Annotations["config.openshift.io/inject-proxy"]; len(containerNamesString) > 0 { + proxyConfig, err := b.proxyGetter.Proxies().Get("cluster", metav1.GetOptions{}) + // not found just means that we don't have proxy configuration, so we should tolerate and fill in empty + if err != nil && !errors.IsNotFound(err) { + return err + } + err = updatePodSpecWithProxy( + &deployment.Spec.Template.Spec, + strings.Split(containerNamesString, ","), + proxyConfig.Status.HTTPProxy, + proxyConfig.Status.HTTPSProxy, + proxyConfig.Status.NoProxy, + ) + if err != nil { + return err + } + } + actual, updated, err := resourceapply.ApplyDeployment(b.client, deployment) if err != nil { return err @@ -103,15 +124,17 @@ func waitForDeploymentCompletion(ctx context.Context, client appsclientv1.Deploy } type daemonsetBuilder struct { - client *appsclientv1.AppsV1Client - raw []byte - modifier MetaV1ObjectModifierFunc + client *appsclientv1.AppsV1Client + proxyGetter configv1.ProxiesGetter + raw []byte + modifier MetaV1ObjectModifierFunc } func newDaemonsetBuilder(config *rest.Config, m lib.Manifest) Interface { return &daemonsetBuilder{ - client: appsclientv1.NewForConfigOrDie(withProtobuf(config)), - raw: m.Raw, + client: appsclientv1.NewForConfigOrDie(withProtobuf(config)), + proxyGetter: configv1.NewForConfigOrDie(config), + raw: m.Raw, } } @@ -129,6 +152,26 @@ func (b *daemonsetBuilder) Do(ctx context.Context) error { if b.modifier != nil { b.modifier(daemonset) } + + // if proxy injection is requested, get the proxy values and use them + if containerNamesString := daemonset.Annotations["config.openshift.io/inject-proxy"]; len(containerNamesString) > 0 { + proxyConfig, err := b.proxyGetter.Proxies().Get("cluster", metav1.GetOptions{}) + // not found just means that we don't have proxy configuration, so we should tolerate and fill in empty + if err != nil && !errors.IsNotFound(err) { + return err + } + err = updatePodSpecWithProxy( + &daemonset.Spec.Template.Spec, + strings.Split(containerNamesString, ","), + proxyConfig.Status.HTTPProxy, + proxyConfig.Status.HTTPSProxy, + proxyConfig.Status.NoProxy, + ) + if err != nil { + return err + } + } + actual, updated, err := resourceapply.ApplyDaemonSet(b.client, daemonset) if err != nil { return err diff --git a/lib/resourcebuilder/podspec.go b/lib/resourcebuilder/podspec.go new file mode 100644 index 0000000000..105d268d20 --- /dev/null +++ b/lib/resourcebuilder/podspec.go @@ -0,0 +1,47 @@ +package resourcebuilder + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" +) + +// updatePodSpecWithProxy mutates the input podspec with proxy env vars for all init containers and containers +// matching the container names. +func updatePodSpecWithProxy(podSpec *corev1.PodSpec, containerNames []string, httpProxy, httpsProxy, noProxy string) error { + hasProxy := len(httpsProxy) > 0 || len(httpProxy) > 0 || len(noProxy) > 0 + if !hasProxy { + return nil + } + + for _, containerName := range containerNames { + found := false + for i := range podSpec.Containers { + if podSpec.Containers[i].Name != containerName { + continue + } + found = true + + podSpec.Containers[i].Env = append(podSpec.Containers[i].Env, corev1.EnvVar{Name: "HTTP_PROXY", Value: httpProxy}) + podSpec.Containers[i].Env = append(podSpec.Containers[i].Env, corev1.EnvVar{Name: "HTTPS_PROXY", Value: httpsProxy}) + podSpec.Containers[i].Env = append(podSpec.Containers[i].Env, corev1.EnvVar{Name: "NO_PROXY", Value: noProxy}) + } + for i := range podSpec.InitContainers { + if podSpec.InitContainers[i].Name != containerName { + continue + } + found = true + + podSpec.InitContainers[i].Env = append(podSpec.InitContainers[i].Env, corev1.EnvVar{Name: "HTTP_PROXY", Value: httpProxy}) + podSpec.InitContainers[i].Env = append(podSpec.InitContainers[i].Env, corev1.EnvVar{Name: "HTTPS_PROXY", Value: httpsProxy}) + podSpec.InitContainers[i].Env = append(podSpec.InitContainers[i].Env, corev1.EnvVar{Name: "NO_PROXY", Value: noProxy}) + } + + if !found { + return fmt.Errorf("requested injection for non-existent container: %q", containerName) + } + } + + return nil + +} diff --git a/lib/resourcebuilder/podspec_test.go b/lib/resourcebuilder/podspec_test.go new file mode 100644 index 0000000000..458b7f6bcf --- /dev/null +++ b/lib/resourcebuilder/podspec_test.go @@ -0,0 +1,112 @@ +package resourcebuilder + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/util/diff" + + corev1 "k8s.io/api/core/v1" +) + +func TestUpdatePodSpecWithProxy(t *testing.T) { + tests := []struct { + name string + + input *corev1.PodSpec + containerNames []string + httpProxy, httpsProxy, noProxy string + + expectedErr string + expected *corev1.PodSpec + }{ + { + name: "no proxy info", + input: &corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "foo", + }, + }, + }, + expected: &corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "foo", + }, + }, + }, + }, + { + name: "proxy info", + containerNames: []string{"foo", "init-foo"}, + httpsProxy: "httpsProxy-val", + noProxy: "noProxy-val", + input: &corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Name: "init-foo", + }, + { + Name: "init-bar", + }, + }, + Containers: []corev1.Container{ + { + Name: "foo", + }, + { + Name: "bar", + }, + }, + }, + expected: &corev1.PodSpec{ + InitContainers: []corev1.Container{ + { + Name: "init-foo", + Env: []corev1.EnvVar{ + {Name: "HTTP_PROXY", Value: ""}, + {Name: "HTTPS_PROXY", Value: "httpsProxy-val"}, + {Name: "NO_PROXY", Value: "noProxy-val"}, + }, + }, + { + Name: "init-bar", + }, + }, + Containers: []corev1.Container{ + { + Name: "foo", + Env: []corev1.EnvVar{ + {Name: "HTTP_PROXY", Value: ""}, + {Name: "HTTPS_PROXY", Value: "httpsProxy-val"}, + {Name: "NO_PROXY", Value: "noProxy-val"}, + }, + }, + { + Name: "bar", + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := updatePodSpecWithProxy(test.input, test.containerNames, test.httpProxy, test.httpsProxy, test.noProxy) + switch { + case err == nil && len(test.expectedErr) == 0: + case err != nil && len(test.expectedErr) == 0: + t.Fatal(err) + case err == nil && len(test.expectedErr) != 0: + t.Fatal(err) + case err != nil && len(test.expectedErr) != 0 && err.Error() != test.expectedErr: + t.Fatal(err) + } + + if !reflect.DeepEqual(test.input, test.expected) { + t.Error(diff.ObjectDiff(test.input, test.expected)) + } + }) + } +}