Skip to content

Commit

Permalink
fixed hc creation issue when already existing (#31)
Browse files Browse the repository at this point in the history
fixed hc reference issue from traffic policy
fixed using health check to steer traffic
fixed hc tagging for better readability
added automatic creation of cloud credentials
included dns endpoint in deployment

Signed-off-by: raffaelespazzoli <[email protected]>
  • Loading branch information
raffaelespazzoli authored May 27, 2021
1 parent 8cf5acf commit 6409ab6
Show file tree
Hide file tree
Showing 15 changed files with 275 additions and 236 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ testbin/*
bundle/
bundle.Dockerfile
charts/
testbin/
9 changes: 5 additions & 4 deletions api/v1alpha1/globaldnszone_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ type Route53ProviderConfig struct {
// +kubebuilder:validation:Required
ZoneID string `json:"zoneID"`

//CredentialsSecretRef is a reference to a secret containing the credentials to access the AWS API //TODO (content and needed permissions)
// expected secret keys are "aws_access_key_id" and "aws_secret_access_key"
// +kubebuilder:validation:Required
CredentialsSecretRef NamespacedName `json:"credentialsSecretRef"`
//CredentialsSecretRef is a reference to a secret containing the credentials to access the AWS API. The expected secret keys are "aws_access_key_id" and "aws_secret_access_key".
// This is needed when you want to use route53 as your global load balancer but the operator does not run in an AWS cluster.
// If the operator runs in an AWS cluster, credentials are automatically requested via a CredendialRequest object.
// +kubebuilder:validation:Optional
CredentialsSecretRef NamespacedName `json:"credentialsSecretRef,omitempty"`
}

// GlobalDNSZoneStatus defines the observed state of GlobalDNSZone
Expand Down
10 changes: 6 additions & 4 deletions config/crd/bases/redhatcop.redhat.io_globaldnszones.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ spec:
properties:
credentialsSecretRef:
description: CredentialsSecretRef is a reference to a secret
containing the credentials to access the AWS API //TODO
(content and needed permissions) expected secret keys are
"aws_access_key_id" and "aws_secret_access_key"
containing the credentials to access the AWS API. The expected
secret keys are "aws_access_key_id" and "aws_secret_access_key".
This is needed when you want to use route53 as your global
load balancer but the operator does not run in an AWS cluster.
If the operator runs in an AWS cluster, credentials are
automatically requested via a CredendialRequest object.
properties:
name:
type: string
Expand All @@ -76,7 +79,6 @@ spec:
description: ZoneID is the AWS route53 zone ID.
type: string
required:
- credentialsSecretRef
- zoneID
type: object
type: object
Expand Down
86 changes: 86 additions & 0 deletions config/crd/external-dns/dnsendpoint-crd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.5.0
api-approved.kubernetes.io: "https://github.com/kubernetes-sigs/external-dns/pull/2007"
creationTimestamp: null
name: dnsendpoints.externaldns.k8s.io
spec:
group: externaldns.k8s.io
names:
kind: DNSEndpoint
listKind: DNSEndpointList
plural: dnsendpoints
singular: dnsendpoint
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: DNSEndpointSpec defines the desired state of DNSEndpoint
properties:
endpoints:
items:
description: Endpoint is a high-level way of a connection between a service and an IP
properties:
dnsName:
description: The hostname of the DNS record
type: string
labels:
additionalProperties:
type: string
description: Labels stores labels defined for the Endpoint
type: object
providerSpecific:
description: ProviderSpecific stores provider specific config
items:
description: ProviderSpecificProperty holds the name and value of a configuration which is specific to individual DNS providers
properties:
name:
type: string
value:
type: string
type: object
type: array
recordTTL:
description: TTL for the record
format: int64
type: integer
recordType:
description: RecordType type of record, e.g. CNAME, A, SRV, TXT etc
type: string
setIdentifier:
description: Identifier to distinguish multiple records with the same name and type (e.g. Route53 records with routing policies other than 'simple')
type: string
targets:
description: The targets the DNS record points to
items:
type: string
type: array
type: object
type: array
type: object
status:
description: DNSEndpointStatus defines the observed state of DNSEndpoint
properties:
observedGeneration:
description: The generation observed by the external-dns controller.
format: int64
type: integer
type: object
type: object
served: true
storage: true
subresources:
status: {}
1 change: 1 addition & 0 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ resources:
- bases/redhatcop.redhat.io_globaldnsrecords.yaml
- bases/redhatcop.redhat.io_globalroutediscoveries.yaml
- bases/redhatcop.redhat.io_globaldnszones.yaml
- external-dns/dnsendpoint-crd.yaml
# +kubebuilder:scaffold:crdkustomizeresource

patchesStrategicMerge:
Expand Down
5 changes: 5 additions & 0 deletions config/helmchart/templates/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ spec:
- /manager
args:
- --leader-elect
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
name: {{ .Chart.Name }}
Expand Down
5 changes: 5 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ spec:
- /manager
args:
- --leader-elect
env:
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: controller:latest
name: manager
securityContext:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ metadata:
categories: Networking, Cloud Provider
certified: "false"
containerImage: quay.io/redhat-cop/global-load-balancer-operator
operatorframework.io/suggested-namespace: global-load-balancer-operator
createdAt: 07/30/2020
description: This operator creates automation around a DNS to operate as a global load balancer for a set of OpenShift clusters.
operatorframework.io/suggested-namespace: global-load-balancer-operator
repository: https://github.com/redhat-cop/global-load-balancer-operator
support: Best Effort
name: global-load-balancer-operator.v0.0.0
Expand Down
10 changes: 10 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ rules:
- get
- list
- watch
- apiGroups:
- cloudcredential.openshift.io
resources:
- credentialsrequests
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- externaldns.k8s.io
resources:
Expand Down
133 changes: 129 additions & 4 deletions controllers/common/route53/route53.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,48 @@ package route53
import (
"context"
"errors"
"os"
"reflect"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/route53"
cloudcredentialv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1"
redhatcopv1alpha1 "github.com/redhat-cop/global-load-balancer-operator/api/v1alpha1"
"github.com/redhat-cop/operator-utils/pkg/util"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

const credentialsName = "global-load-balancer-operator-infra-credentials"
const credentialsNamespace = "openshift-cloud-credential-operator"
const credentialsSecretName = "global-load-balancer-operator-infra-credentials"

var log = logf.Log.WithName("common")
var operatorNamespace string

func init() {
on, found := os.LookupEnv("NAMESPACE")
if !found {
log.Error(errors.New("the \"NAMESPACE\" environment variable must be defined. It must point to the namespace in which the operator runs"), "")
operatorNamespace = "global-load-balancer-operator"
return
}
operatorNamespace = on
}

func GetRoute53Client(context context.Context, instance *redhatcopv1alpha1.GlobalDNSZone, r *util.ReconcilerBase) (*route53.Route53, error) {
err := ensureCredentialsRequestExists(context, r)
if err != nil {
log.Error(err, "unable to create CredentialRequest for route53")
return nil, err
}
id, key, err := getAWSCredentials(context, instance, r)
if err != nil {
log.Error(err, "unable to get aws credentials")
Expand Down Expand Up @@ -57,16 +84,114 @@ func getAWSCredentials(context context.Context, instance *redhatcopv1alpha1.Glob

func getAWSCredentialSecret(context context.Context, instance *redhatcopv1alpha1.GlobalDNSZone, r *util.ReconcilerBase) (*corev1.Secret, error) {
credentialSecret := &corev1.Secret{}
var secret_name, secret_namespace string
if instance.Spec.Provider.Route53.CredentialsSecretRef.Name != "" {
secret_name = instance.Spec.Provider.Route53.CredentialsSecretRef.Name
secret_namespace = instance.Spec.Provider.Route53.CredentialsSecretRef.Namespace
} else {
secret_name = credentialsSecretName
secret_namespace = operatorNamespace
}
err := r.GetClient().Get(context, types.NamespacedName{
Name: instance.Spec.Provider.Route53.CredentialsSecretRef.Name,
Namespace: instance.Spec.Provider.Route53.CredentialsSecretRef.Namespace,
Name: secret_name,
Namespace: secret_namespace,
}, credentialSecret)
if err != nil {
log.Error(err, "unable to retrive aws credential ", "secret", types.NamespacedName{
Name: instance.Spec.Provider.Route53.CredentialsSecretRef.Name,
Namespace: instance.Spec.Provider.Route53.CredentialsSecretRef.Namespace,
Name: secret_name,
Namespace: secret_namespace,
})
return &corev1.Secret{}, err
}
return credentialSecret, nil
}

func ensureCredentialsRequestExists(context context.Context, r *util.ReconcilerBase) error {

credentialRequest := &cloudcredentialv1.CredentialsRequest{}
err := r.GetClient().Get(context, types.NamespacedName{
Name: credentialsName,
Namespace: credentialsNamespace,
}, credentialRequest)

if err != nil {
if apierrors.IsNotFound(err) {
credentialRequest = getAWSCredentialRequest()
err := r.GetClient().Create(context, credentialRequest, &client.CreateOptions{})
if err != nil {
log.Error(err, "unable to create", "credentials request", credentialRequest)
return err
}
} else {
log.Error(err, "unable to lookup", "credential request", types.NamespacedName{
Name: credentialsName,
Namespace: credentialsNamespace,
})
return err
}

}
desiredCredentialRequest := getAWSCredentialRequest()
if !reflect.DeepEqual(credentialRequest.Spec, desiredCredentialRequest.Spec) {
credentialRequest.Spec = desiredCredentialRequest.Spec
err := r.GetClient().Update(context, credentialRequest, &client.UpdateOptions{})
if err != nil {
log.Error(err, "unable to update ", "credentials request", credentialRequest)
return err
}
}
return nil
}

func getAWSCredentialRequest() *cloudcredentialv1.CredentialsRequest {
awsSpec := cloudcredentialv1.AWSProviderSpec{
TypeMeta: metav1.TypeMeta{
APIVersion: "cloudcredential.openshift.io/v1",
Kind: "AWSProviderSpec",
},
StatementEntries: []cloudcredentialv1.StatementEntry{
{
Action: []string{
"route53:GetHostedZone",
"route53:CreateTrafficPolicy",
"route53:DeleteTrafficPolicy",
"route53:GetTrafficPolicy",
"route53:ListTrafficPolicies",
"route53:CreateTrafficPolicyInstance",
"route53:DeleteTrafficPolicyInstance",
"route53:GetTrafficPolicyInstance",
"route53:ListTrafficPolicyInstancesByHostedZone",
"route53:ListTrafficPolicyInstancesByPolicy",
"route53:ListHealthChecks",
"route53:GetHealthCheck",
"route53:UpdateHealthCheck",
"route53:DeleteHealthCheck",
"route53:CreateHealthCheck",
"route53:ChangeTagsForResource",
},
Effect: "Allow",
Resource: "*",
},
},
}
request := cloudcredentialv1.CredentialsRequest{
ObjectMeta: metav1.ObjectMeta{
Name: credentialsName,
Namespace: credentialsNamespace,
},
TypeMeta: metav1.TypeMeta{
APIVersion: "cloudcredential.openshift.io/v1",
Kind: "CredentialsRequest",
},
Spec: cloudcredentialv1.CredentialsRequestSpec{
SecretRef: corev1.ObjectReference{
Name: credentialsSecretName,
Namespace: operatorNamespace,
},
ProviderSpec: &runtime.RawExtension{
Object: &awsSpec,
},
},
}
return &request
}
Loading

0 comments on commit 6409ab6

Please sign in to comment.