Skip to content

Commit

Permalink
(feat) incorporate proxy configuration support
Browse files Browse the repository at this point in the history
Use case: The env variables defined in cluster scoped global proxy
configuration object should be injected into deployment spec if not
overridden in subscription pod configuration.

Add a new proxy package that provides the following support:
 - Add support to check if Proxy api is available on cluster.
 - Add support to query the cluster for the 'cluster' Proxy object and
   return the env variable(s).
 - Provide logic to determine whether proxy env variable has been
   overridden in pod configuration.
  • Loading branch information
tkashem committed Jul 19, 2019
1 parent 45924f6 commit 35a159e
Show file tree
Hide file tree
Showing 48 changed files with 2,944 additions and 0 deletions.
40 changes: 40 additions & 0 deletions pkg/lib/proxy/available.go
@@ -0,0 +1,40 @@
package proxy

import (
"errors"
"strings"

"k8s.io/apimachinery/pkg/runtime/schema"
apidiscovery "k8s.io/client-go/discovery"
)

const (
// This is the error message thrown by ServerSupportsVersion function
// when an API version is not supported by the server.
notSupportedErrorMessage = "server does not support API version"
)

// IsAPIAvailable return true if OpenShift config API is present on the cluster.
// Otherwise, supported is set to false.
func IsAPIAvailable(discovery apidiscovery.DiscoveryInterface) (supported bool, err error) {
if discovery == nil {
err = errors.New("discovery interface can not be <nil>")
return
}

opStatusGV := schema.GroupVersion{
Group: "config.openshift.io",
Version: "v1",
}
if discoveryErr := apidiscovery.ServerSupportsVersion(discovery, opStatusGV); discoveryErr != nil {
if strings.Contains(discoveryErr.Error(), notSupportedErrorMessage) {
return
}

err = discoveryErr
return
}

supported = true
return
}
52 changes: 52 additions & 0 deletions pkg/lib/proxy/envvar.go
@@ -0,0 +1,52 @@
package proxy

import (
apiconfigv1 "github.com/openshift/api/config/v1"
corev1 "k8s.io/api/core/v1"
)

const (
// HTTP_PROXY is the URL of the proxy for HTTP requests.
// Empty means unset and will not result in an env var.
envHTTPProxyName = "HTTP_PROXY"

// HTTPS_PROXY is the URL of the proxy for HTTPS requests.
// Empty means unset and will not result in an env var.
envHTTPSProxyName = "HTTPS_PROXY"

// NO_PROXY is the list of domains for which the proxy should not be used.
// Empty means unset and will not result in an env var.
envNoProxyName = "NO_PROXY"
)

var (
allProxyEnvVarNames = []string{
envHTTPProxyName,
envHTTPSProxyName,
envNoProxyName,
}
)

// ToEnvVar accepts a config Proxy object and returns an array of all three
// proxy variables with values.
//
// Please note that the function uses the status of the Proxy object to rea the
// proxy env variables. It's because OpenShift validates the proxy variables in
// spec and writes them back to status.
// As a consumer we should be reading off of proxy.status.
func ToEnvVar(proxy *apiconfigv1.Proxy) []corev1.EnvVar {
return []corev1.EnvVar{
corev1.EnvVar{
Name: envHTTPProxyName,
Value: proxy.Status.HTTPProxy,
},
corev1.EnvVar{
Name: envHTTPSProxyName,
Value: proxy.Status.HTTPSProxy,
},
corev1.EnvVar{
Name: envNoProxyName,
Value: proxy.Status.NoProxy,
},
}
}
85 changes: 85 additions & 0 deletions pkg/lib/proxy/envvar_test.go
@@ -0,0 +1,85 @@
package proxy_test

import (
"testing"

apiconfigv1 "github.com/openshift/api/config/v1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/proxy"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)

const (
envHTTPProxyName = "HTTP_PROXY"
envHTTPSProxyName = "HTTPS_PROXY"
envNoProxyName = "NO_PROXY"
)

func TestToEnvVar(t *testing.T) {
tests := []struct {
name string
proxy *apiconfigv1.Proxy
envVarWant []corev1.EnvVar
}{
{
name: "WithSet",
proxy: &apiconfigv1.Proxy{
Status: apiconfigv1.ProxyStatus{
HTTPProxy: "http://",
HTTPSProxy: "https://",
NoProxy: "foo,bar",
},
},
envVarWant: []corev1.EnvVar{
corev1.EnvVar{
Name: envHTTPProxyName,
Value: "http://",
},
corev1.EnvVar{
Name: envHTTPSProxyName,
Value: "https://",
},
corev1.EnvVar{
Name: envNoProxyName,
Value: "foo,bar",
},
},
},

{
name: "WithUnset",
proxy: &apiconfigv1.Proxy{
Status: apiconfigv1.ProxyStatus{
HTTPProxy: "http://",
HTTPSProxy: "",
NoProxy: "",
},
},
envVarWant: []corev1.EnvVar{
corev1.EnvVar{
Name: envHTTPProxyName,
Value: "http://",
},
corev1.EnvVar{
Name: envHTTPSProxyName,
Value: "",
},
corev1.EnvVar{
Name: envNoProxyName,
Value: "",
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
envVarGot := proxy.ToEnvVar(tt.proxy)

assert.NotNil(t, envVarGot)
assert.Equal(t, tt.envVarWant, envVarGot)

})
}

}
35 changes: 35 additions & 0 deletions pkg/lib/proxy/overridden.go
@@ -0,0 +1,35 @@
package proxy

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

// IsOverridden returns true if the given container overrides proxy env variable(s).
// We apply the following rule:
// If a container already defines any of the proxy env variable then it
// overrides all of these.
func IsOverridden(envVar []corev1.EnvVar) (overrides bool) {
for _, envVarName := range allProxyEnvVarNames {
_, found := find(envVar, envVarName)
if found {
overrides = true
return
}
}

return
}

func find(proxyEnvVar []corev1.EnvVar, name string) (envVar *corev1.EnvVar, found bool) {
for i := range proxyEnvVar {
if name == proxyEnvVar[i].Name {
// Environment variable names are case sensitive.
found = true
envVar = &proxyEnvVar[i]

break
}
}

return
}
105 changes: 105 additions & 0 deletions pkg/lib/proxy/overridden_test.go
@@ -0,0 +1,105 @@
package proxy_test

import (
"strings"
"testing"

"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/proxy"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)

var (
globalProxyConfig = []corev1.EnvVar{
corev1.EnvVar{
Name: "HTTP_PROXY",
Value: "http://foo.com:8080",
},
corev1.EnvVar{
Name: "HTTPS_PROXY",
Value: "https://foo.com:443",
},
corev1.EnvVar{
Name: "NO_PROXY",
Value: "a.com,b.com",
},
}
)

func TestIsOverridden(t *testing.T) {
tests := []struct {
name string
envVar []corev1.EnvVar
expected bool
}{
{
name: "WithEmptyEnvVar",
envVar: []corev1.EnvVar{},
expected: false,
},
{
name: "WithNilEnvVar",
envVar: nil,
expected: false,
},
{
name: "WithUnrelatedEnvVar",
envVar: []corev1.EnvVar{
corev1.EnvVar{
Name: "foo",
Value: "foo_value",
},
},
expected: false,
},
{
name: "WithHTTP_PROXY",
envVar: []corev1.EnvVar{
corev1.EnvVar{
Name: envHTTPProxyName,
Value: "http://",
},
},
expected: true,
},
{
name: "WithHTTPS_PROXY",
envVar: []corev1.EnvVar{
corev1.EnvVar{
Name: envHTTPSProxyName,
Value: "https://",
},
},
expected: true,
},
{
name: "WithNO_PROXY",
envVar: []corev1.EnvVar{
corev1.EnvVar{
Name: envNoProxyName,
Value: "https://",
},
},
expected: true,
},
{
name: "WithCaseSensitive",
envVar: []corev1.EnvVar{
corev1.EnvVar{
Name: strings.ToLower(envHTTPSProxyName),
Value: "http://",
},
},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := proxy.IsOverridden(tt.envVar)

assert.Equal(t, tt.expected, actual)
})
}
}
26 changes: 26 additions & 0 deletions pkg/lib/proxy/querier.go
@@ -0,0 +1,26 @@
package proxy

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

// DefaultQuerier does...
func DefaultQuerier() Querier {
return &defaultQuerier{}
}

// Querier is an interface that wraps the QueryProxyConfig method.
//
// QueryProxyConfig returns the global cluster level proxy env variable(s).
type Querier interface {
QueryProxyConfig() (proxy []corev1.EnvVar, err error)
}

type defaultQuerier struct {
}

// QueryProxyConfig returns no env variable(s), err is set to nil to indicate
// that the cluster has no global proxy configuration.
func (*defaultQuerier) QueryProxyConfig() (proxy []corev1.EnvVar, err error) {
return
}

0 comments on commit 35a159e

Please sign in to comment.