Skip to content

Commit

Permalink
e2e: support infoblox provider
Browse files Browse the repository at this point in the history
  • Loading branch information
alebedev87 committed Mar 24, 2022
1 parent 8957198 commit 54ca33f
Show file tree
Hide file tree
Showing 129 changed files with 24,459 additions and 1,514 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,18 @@ Prepare your environment for the installation commands.
For AWS:
```sh
export KUBECONFIG=/path/to/mycluster/kubeconfig
export CLOUD_PROVIDER=AWS
export DNS_PROVIDER=AWS
export AWS_ACCESS_KEY_ID=my-aws-access-key
export AWS_SECRET_ACCESS_KEY=my-aws-access-secret
```
For Infoblox:
```sh
export KUBECONFIG=/path/to/mycluster/kubeconfig
export DNS_PROVIDER=INFOBLOX
export INFOBLOX_GRID_HOST=100.100.100.100
export INFOBLOX_WAPI_USERNAME=my-infoblox-username
export INFOBLOX_WAPI_PASSWORD=my-infoblox-password
```
For the other providers: check out [e2e directory](./test/e2e/).
3. Run the test suite
Expand Down
16 changes: 16 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ spec:
name: aws-access-key
zones: # Replace with the desired hosted zone IDs
- "Z3URY6TWQ91KXX"
source:
type: Service
fqdnTemplate:
- '{{.Name}}.mydomain.net'
```

Once this is created the _external-dns-operator_ will create a deployment of _external-dns_ which is configured to
Expand Down Expand Up @@ -90,6 +94,10 @@ spec:
wapiVersion: # the WAPI version, eg: 2.11, 2.3.1
zones: # Replace with the desired hosted zones
- "ZG5zLm5ldHdvcmtfdmlldyQw"
source:
type: Service
fqdnTemplate:
- '{{.Name}}.mydomain.net'
```

Once this is created the _external-dns-operator_ will create a deployment of _external-dns_ which is configured to
Expand Down Expand Up @@ -182,6 +190,10 @@ spec:
project: gcp-devel
zones: # Replace with the desired managed zones
- "3651032588905568971"
source:
type: Service
fqdnTemplate:
- '{{.Name}}.mydomain.net'
```

# Azure
Expand Down Expand Up @@ -228,4 +240,8 @@ spec:
name: azure-config-file
zones: # Replace with the desired hosted zones
- "myzoneid"
source:
type: Service
fqdnTemplate:
- '{{.Name}}.mydomain.net'
```
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ require (
github.com/go-logr/logr v0.4.0
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/google/go-cmp v0.5.6
github.com/infobloxopen/infoblox-go-client v1.1.1
github.com/miekg/dns v1.0.14
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.15.0
github.com/openshift/api v0.0.0-20211019100638-b2cbe79f2e4b
github.com/openshift/cloud-credential-operator v0.0.0-20211118210017-9066dcc747fa
github.com/operator-framework/api v0.11.0
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
google.golang.org/api v0.58.0
k8s.io/api v0.22.1
k8s.io/apimachinery v0.22.1
k8s.io/client-go v0.22.1
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471
sigs.k8s.io/controller-runtime v0.9.5
k8s.io/utils v0.0.0-20210802155522-efc7438f0176
sigs.k8s.io/controller-runtime v0.10.0
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20211004203041-b1efff64d3d2
sigs.k8s.io/controller-tools v0.6.0
sigs.k8s.io/controller-tools v0.6.2
)
100 changes: 100 additions & 0 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion test/e2e/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (a *awsTestHelper) buildExternalDNS(name, zoneID, zoneDomain string, credsS
return resource
}

func (a *awsTestHelper) buildOpenShiftExternalDNS(name, zoneID, zoneDomain, routerName string) operatorv1alpha1.ExternalDNS {
func (a *awsTestHelper) buildOpenShiftExternalDNS(name, zoneID, zoneDomain, routerName string, _ *corev1.Secret) operatorv1alpha1.ExternalDNS {
resource := routeExternalDNS(name, zoneID, zoneDomain, routerName)
resource.Spec.Provider = operatorv1alpha1.ExternalDNSProvider{
Type: operatorv1alpha1.ProviderTypeAWS,
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (a *azureTestHelper) buildExternalDNS(name, zoneID, zoneDomain string, cred
return resource
}

func (a *azureTestHelper) buildOpenShiftExternalDNS(name, zoneID, zoneDomain, routerName string) operatorv1alpha1.ExternalDNS {
func (a *azureTestHelper) buildOpenShiftExternalDNS(name, zoneID, zoneDomain, routerName string, _ *corev1.Secret) operatorv1alpha1.ExternalDNS {
resource := routeExternalDNS(name, zoneID, zoneDomain, routerName)
resource.Spec.Provider = operatorv1alpha1.ExternalDNSProvider{
Type: operatorv1alpha1.ProviderTypeAzure,
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (g *gcpTestHelper) buildExternalDNS(name, zoneID, zoneDomain string, credsS
return resource
}

func (g *gcpTestHelper) buildOpenShiftExternalDNS(name, zoneID, zoneDomain, routerName string) operatorv1alpha1.ExternalDNS {
func (g *gcpTestHelper) buildOpenShiftExternalDNS(name, zoneID, zoneDomain, routerName string, _ *corev1.Secret) operatorv1alpha1.ExternalDNS {
resource := routeExternalDNS(name, zoneID, zoneDomain, routerName)
resource.Spec.Provider = operatorv1alpha1.ExternalDNSProvider{
Type: operatorv1alpha1.ProviderTypeGCP,
Expand Down
267 changes: 267 additions & 0 deletions test/e2e/infoblox.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
//go:build e2e
// +build e2e

package e2e

import (
"context"
"fmt"
"net"
"strconv"
"strings"

ibclient "github.com/infobloxopen/infoblox-go-client"

olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

operatorv1alpha1 "github.com/openshift/external-dns-operator/api/v1alpha1"
)

const (
infobloxGridHostEnvVar = "INFOBLOX_GRID_HOST"
infobloxWAPIUsernameEnvVar = "INFOBLOX_WAPI_USERNAME"
infobloxWAPIPasswordEnvVar = "INFOBLOX_WAPI_PASSWORD"
trustedCAConfigMapEnvVar = "TRUSTED_CA_CONFIGMAP_NAME"
defaultWAPIPort = "443"
defaultWAPIVersion = "2.3.1"
defaultTLSVerify = "false"
defaultHTTPRequestTimeout = 20
defaultHTTPConnPool = 10
defaultGridMasterHostname = "infoblox.localdomain"
operatorContainerName = "operator"
)

type infobloxTestHelper struct {
client *enhancedIBClient
gridHost string
wapiUsername string
wapiPassword string
}

var _ providerTestHelper = &infobloxTestHelper{}

func newInfobloxHelper(kubeClient client.Client) (*infobloxTestHelper, error) {
helper := &infobloxTestHelper{}

if err := helper.prepareConfigurations(kubeClient); err != nil {
return nil, fmt.Errorf("failed to prepare infoblox helper: %w", err)
}

hostConfig := ibclient.HostConfig{
Host: helper.gridHost,
Version: defaultWAPIVersion,
Port: defaultWAPIPort,
Username: helper.wapiUsername,
Password: helper.wapiPassword,
}
transportConfig := ibclient.NewTransportConfig(defaultTLSVerify, defaultHTTPRequestTimeout, defaultHTTPConnPool)

client, err := newEnhancedIBClient(hostConfig, transportConfig)
if err != nil {
return nil, fmt.Errorf("failed to intiantiate enhanced infoblox client: %w", err)
}
helper.client = client

return helper, nil
}

func (h *infobloxTestHelper) ensureHostedZone(zoneDomain string) (string, []string, error) {
zones := []ibclient.ZoneAuth{}
err := h.client.GetObject(ibclient.NewZoneAuth(ibclient.ZoneAuth{}), "", &zones)
if err != nil {
return "", nil, fmt.Errorf("failed to list authoritative zone: %w", err)
}
for _, zone := range zones {
if zone.Fqdn == zoneDomain {
return zone.Ref, []string{h.gridHost}, nil
}
}

authZone := ibclient.NewZoneAuth(ibclient.ZoneAuth{Fqdn: zoneDomain})
ref, err := h.client.CreateObject(authZone)
if err != nil {
return "", nil, fmt.Errorf("failed to create authoritative zone: %w", err)
}
authZone.Ref = ref

// NS record is not added automatically with the zone creation
if err = h.client.addNameServer(authZone.Ref, defaultGridMasterHostname); err != nil {
return "", nil, fmt.Errorf("failed to add nameserver to authoritative zone: %w", err)
}

// creation of an authoritative zone needs a restart of the DNS service
if err = h.client.restartServices(); err != nil {
return "", nil, fmt.Errorf("failed to restart grid services: %w", err)
}

return authZone.Ref, []string{h.gridHost}, nil
}

func (h *infobloxTestHelper) deleteHostedZone(zoneID, zoneDomain string) error {
if _, err := h.client.DeleteObject(zoneID); err != nil {
return err
}

// deletion of an authoritative zone needs a restart of the DNS service
if err := h.client.restartServices(); err != nil {
return err
}

return nil
}

func (h *infobloxTestHelper) platform() string {
return infobloxDNSProvider
}

func (h *infobloxTestHelper) makeCredentialsSecret(namespace string) *corev1.Secret {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("infoblox-credentials-%s", randomString(16)),
Namespace: namespace,
},
Data: map[string][]byte{
"EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME": []byte(h.wapiUsername),
"EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD": []byte(h.wapiPassword),
},
}
}

func (h *infobloxTestHelper) buildExternalDNS(name, zoneID, zoneDomain string, credsSecret *corev1.Secret) operatorv1alpha1.ExternalDNS {
resource := defaultExternalDNS(name, zoneID, zoneDomain)
wapiPort, _ := strconv.Atoi(defaultWAPIPort)
resource.Spec.Provider = operatorv1alpha1.ExternalDNSProvider{
Type: operatorv1alpha1.ProviderTypeInfoblox,
Infoblox: &operatorv1alpha1.ExternalDNSInfobloxProviderOptions{
Credentials: operatorv1alpha1.SecretReference{
Name: credsSecret.Name,
},
GridHost: h.gridHost,
WAPIPort: wapiPort,
WAPIVersion: defaultWAPIVersion,
},
}
return resource
}

func (h *infobloxTestHelper) buildOpenShiftExternalDNS(name, zoneID, zoneDomain, routerName string, credsSecret *corev1.Secret) operatorv1alpha1.ExternalDNS {
resource := routeExternalDNS(name, zoneID, zoneDomain, routerName)
wapiPort, _ := strconv.Atoi(defaultWAPIPort)
resource.Spec.Provider = operatorv1alpha1.ExternalDNSProvider{
Type: operatorv1alpha1.ProviderTypeInfoblox,
Infoblox: &operatorv1alpha1.ExternalDNSInfobloxProviderOptions{
Credentials: operatorv1alpha1.SecretReference{
Name: credsSecret.Name,
},
GridHost: h.gridHost,
WAPIPort: wapiPort,
WAPIVersion: defaultWAPIVersion,
},
}
return resource
}

func (h *infobloxTestHelper) prepareConfigurations(kubeClient client.Client) error {
h.gridHost = mustGetEnv(infobloxGridHostEnvVar)
h.wapiUsername = mustGetEnv(infobloxWAPIUsernameEnvVar)
h.wapiPassword = mustGetEnv(infobloxWAPIPasswordEnvVar)

return h.trustGridTLSCert()
}

// trustGridTLSCert instructs the operator to trust Grid Master's self signed TLS certificate.
func (h *infobloxTestHelper) trustGridTLSCert() error {
// get Grid's TLS certificate as raw PEM encoded data
certRaw, err := readServerTLSCert(net.JoinHostPort(h.gridHost, defaultWAPIPort), true)
if err != nil {
return fmt.Errorf("failed to get Grid Master's TLS certificate: %w", err)
}

operatorNs := "external-dns-operator"
trustedCAConfigMapName := fmt.Sprintf("infoblox-trustedca-%s", randomString(16))

// create a config map with the trusted CA bundle
trustedCAConfigMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: trustedCAConfigMapName,
Namespace: operatorNs,
},
Data: map[string]string{
"ca-bundle.crt": string(certRaw),
},
}
if err := kubeClient.Create(context.TODO(), trustedCAConfigMap); err != nil {
return fmt.Errorf("failed to create trusted CA configmap %s/%s: %w", trustedCAConfigMap.Namespace, trustedCAConfigMap.Name, err)
}

// trusted CA environment variable to be injected
trustedCAEnvVar := corev1.EnvVar{
Name: trustedCAConfigMapEnvVar,
Value: trustedCAConfigMapName,
}

// inject into subscription if there is one
findOperatorSubscription := func() (*olmv1alpha1.Subscription, error) {
list := &olmv1alpha1.SubscriptionList{}
if err := kubeClient.List(context.TODO(), list, client.InNamespace(operatorNs)); err != nil {
return nil, err
}
for i := range list.Items {
if strings.HasPrefix(list.Items[i].Name, "external-dns-operator") {
return &list.Items[i], nil
}
}
return nil, nil
}
subscription, err := findOperatorSubscription()
if err != nil {
return fmt.Errorf("failed to find operator subscription: %w", err)
}
if subscription != nil {
if subscription.Spec.Config == nil {
subscription.Spec.Config = &olmv1alpha1.SubscriptionConfig{}
}
subscription.Spec.Config.Env = ensureEnvVar(subscription.Spec.Config.Env, trustedCAEnvVar)
if err := kubeClient.Update(context.TODO(), subscription); err != nil {
return fmt.Errorf("failed to inject trusted CA environment variable into the subscription: %w", err)
}
return nil
}

// no subscription was found, try to update the deployment directly
findOperatorDeployment := func() (*appsv1.Deployment, error) {
list := &appsv1.DeploymentList{}
if err := kubeClient.List(context.TODO(), list, client.InNamespace(operatorNs)); err != nil {
return nil, err
}
for i := range list.Items {
if strings.HasPrefix(list.Items[i].Name, "external-dns-operator") {
return &list.Items[i], nil
}
}
return nil, nil
}
deployment, err := findOperatorDeployment()
if err != nil {
return fmt.Errorf("failed to find operator deployment: %w", err)
}
if deployment == nil {
return fmt.Errorf("no operator deployment found")
}

for i := range deployment.Spec.Template.Spec.Containers {
if deployment.Spec.Template.Spec.Containers[i].Name == operatorContainerName {
deployment.Spec.Template.Spec.Containers[i].Env = ensureEnvVar(deployment.Spec.Template.Spec.Containers[i].Env, trustedCAEnvVar)
break
}
}
if err := kubeClient.Update(context.TODO(), deployment); err != nil {
return fmt.Errorf("failed to inject trusted CA environment variable into the deployment: %w", err)
}

return nil
}
Loading

0 comments on commit 54ca33f

Please sign in to comment.