Skip to content
This repository has been archived by the owner on Dec 3, 2024. It is now read-only.

Commit

Permalink
Merge pull request #3 from dfds/feature/add-aws-providerconfig
Browse files Browse the repository at this point in the history
Feature/add aws providerconfig
  • Loading branch information
rifisdfds authored Mar 7, 2022
2 parents f752700 + bf34341 commit 8a5c8dd
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 59 deletions.
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,14 @@

# Dependency directories (remove the comment below to include it)
# vendor/

# VSCode
/.vscode

# Build artifacts
/build
/dist
**/bin/

# Test artifacts
**/testbin/
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ RUN go mod download

# Copy the go source
COPY main.go main.go
COPY api/ api/
#COPY api/ api/
COPY controllers/ controllers/

# Build
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ IMAGE_TAG_BASE ?= dfdsdk/crossplane-operator-dfds
BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)

# Image URL to use all building/pushing image targets
IMG ?= controller:latest
IMG ?= $(IMAGE_TAG_BASE)-controller:v$(VERSION)
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.23

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# crossplane-operator-dfds
A Kubernetes operator to support the Crossplane implementation in DFDS
A Kubernetes [operator](https://sdk.operatorframework.io/) to support the Crossplane implementation in DFDS
Binary file removed bin/controller-gen
Binary file not shown.
26 changes: 26 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ metadata:
creationTimestamp: null
name: manager-role
rules:
- apiGroups:
- aws.crossplane.io
resources:
- providerconfigs
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- aws.crossplane.io
resources:
- providerconfigs/finalizers
verbs:
- update
- apiGroups:
- aws.crossplane.io
resources:
- providerconfigs/status
verbs:
- get
- patch
- update
- apiGroups:
- ""
resources:
Expand Down
246 changes: 192 additions & 54 deletions controllers/namespace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package controllers

import (
"context"
"os"

provideraws "github.com/crossplane/provider-aws/apis/v1beta1"
corev1 "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -30,11 +32,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"
)

//TODO: Read value(s) from configmap?
const (
namespaceCapabilityNameLabel = "capability-name"
)

// NamespaceReconciler reconciles a Namespace object
type NamespaceReconciler struct {
client.Client
Expand All @@ -50,6 +47,9 @@ type NamespaceReconciler struct {
//+kubebuilder:rbac:groups=rbac,resources=clusterrolebindings,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=rbac,resources=clusterrolebindings/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=rbac,resources=clusterrolebindings/finalizers,verbs=update
//+kubebuilder:rbac:groups=aws.crossplane.io,resources=providerconfigs,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=aws.crossplane.io,resources=providerconfigs/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=aws.crossplane.io,resources=providerconfigs/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand All @@ -75,14 +75,39 @@ func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
return ctrl.Result{}, err
}

log.Log.Info("Processing namespace " + namespace.Name)

// Detect annotation
labelIsPresent := len(namespace.Annotations[namespaceCapabilityNameLabel]) > 0
getCrossplaneEnabledAnnotationName, err := getAnnotationEnvVarName("DFDS_CROSSPLANE_ENABLED_ANNOTATION_NAME", "dfds-crossplane-enabled")
if err != nil {
log.Log.Error(err, "unable to get Crossplane Enabled annotation name")
}

getAWSAccountIDAnnotationName, err := getAnnotationEnvVarName("DFDS_CROSSPLANE_AWS_ACCOUNT_ID_ANNOTATION_NAME", "dfds-aws-account-id")
if err != nil {
log.Log.Error(err, "unable to get AWS Account annotation name")
}

crossplaneEnabled := "false"
if len(namespace.Annotations[getCrossplaneEnabledAnnotationName]) > 0 {
log.Log.Info("Crossplane annotation value found: " + namespace.Annotations[getCrossplaneEnabledAnnotationName])
crossplaneEnabled = namespace.Annotations[getCrossplaneEnabledAnnotationName]
}

clusterRole := &rbac.ClusterRole{
awsAccountId := ""
if len(namespace.Annotations[getAWSAccountIDAnnotationName]) > 0 {
log.Log.Info("AWS Account ID value found: " + namespace.Annotations[getAWSAccountIDAnnotationName])
awsAccountId = namespace.Annotations[getAWSAccountIDAnnotationName]
}

//configMap := getConfigMap("DFDS_CROSSPLANE_CONFIGMAP_NAME", "DFDS_CROSSPLANE_CONFIGMAP_NAMESPACE")

// TODO: Obtain this clusterrole info from a Configmap or other source, rather than hard code
clusterRole := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: "providerconfig-" + namespace.Name,
Name: namespace.Name + "-aws",
},
Rules: []rbac.PolicyRule{
Rules: []rbacv1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"aws.crossplane.io"},
Expand All @@ -92,77 +117,157 @@ func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
},
}

clusterRoleBinding := &rbac.ClusterRoleBinding{
// TODO: Obtain this clusterrolebinding info from a Configmap or other source, rather than hard code
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "providerconfig-" + namespace.Name,
Name: namespace.Name + "-aws",
},
Subjects: []rbac.Subject{
Subjects: []rbacv1.Subject{
{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Group",
Name: namespace.Name,
},
},
RoleRef: rbac.RoleRef{
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: clusterRole.Name,
},
}

if labelIsPresent {
// TODO: We need to obtain this roleArn. Currently namespaces are not annotated with AWS Account ID
// so we may need to query the capability service to get them which is not ideal. It would be good
// if we could annotate namespaces on creation
roleArn := "arn:aws:iam::" + awsAccountId + ":role/crossplane-deploy"

// TODO: Obtain this providerconfig info from a Configmap or other source, rather than hard code
providerAWS := &provideraws.ProviderConfig{
ObjectMeta: metav1.ObjectMeta{
Name: namespace.Name + "-aws",
},
Spec: provideraws.ProviderConfigSpec{
Credentials: provideraws.ProviderCredentials{
Source: "InjectedIdentity",
},
AssumeRoleARN: &roleArn,
},
}

if crossplaneEnabled == "true" {

log.Log.Info("Detected Crossplane enabled on namespace " + namespace.Name)

log.Log.Info("Capability detected on namespace " + namespace.Name)
controllerutil.SetControllerReference(&namespace, clusterRole, r.Scheme)
if awsAccountId == "" {
log.Log.Info("No AWS Account ID found on namespace. Skipping creation of resources")
} else {
controllerutil.SetControllerReference(&namespace, clusterRole, r.Scheme)
controllerutil.SetControllerReference(&namespace, clusterRoleBinding, r.Scheme)
controllerutil.SetControllerReference(&namespace, providerAWS, r.Scheme)

// Create clusterrole if not exists
if err := r.Create(ctx, clusterRole); err != nil {
if apierrors.IsAlreadyExists(err) {
log.Log.Info("ClusterRole " + clusterRole.Name + " already exists for " + namespace.Name)
// Create clusterrole if not exists
if err := r.Create(ctx, clusterRole); err != nil {
if apierrors.IsAlreadyExists(err) {
log.Log.Info("ClusterRole " + clusterRole.Name + " already exists for " + namespace.Name)

// Update clusterrole in case of changes
// TODO: Check if currently deployed clusterrole matches clusterrole to deploy before applying update
if err := r.Update(ctx, clusterRole); err != nil {
log.Log.Info("Unable to update role " + clusterRole.Name + " for " + namespace.Name)
} else {
log.Log.Info("ClusterRole " + clusterRole.Name + " for " + namespace.Name + " has been updated")
// Update clusterrole in case of changes
// TODO: Check if currently deployed clusterrole matches clusterrole to deploy before applying update
if err := r.Update(ctx, clusterRole); err != nil {
log.Log.Info("Unable to update role " + clusterRole.Name + " for " + namespace.Name)
} else {
log.Log.Info("ClusterRole " + clusterRole.Name + " for " + namespace.Name + " has been updated")
}
err = nil
}
err = nil
}
if err != nil {
log.Log.Info("Unable to make ClusterRole " + clusterRole.Name + " for " + namespace.Name)
if err != nil {
log.Log.Info("Unable to make ClusterRole " + clusterRole.Name + " for " + namespace.Name)
return ctrl.Result{}, err
}
} else {
log.Log.Info("ClusterRole " + clusterRole.Name + " created for " + namespace.Name)
}
return ctrl.Result{}, err
} else {
log.Log.Info("ClusterRole " + clusterRole.Name + " created for " + namespace.Name)
}

// Create clusterrolebinding if not exists
if err := r.Create(ctx, clusterRoleBinding); err != nil {
if apierrors.IsAlreadyExists(err) {
log.Log.Info("ClusterRoleBinding " + clusterRoleBinding.Name + " already exists for " + namespace.Name)

// Update clusterrolebinding in case of changes
// TODO: Check if currently deployed clusterrolebinding matches clusterrolebindnig to deploy before applying update
if err := r.Update(ctx, clusterRoleBinding); err != nil {
log.Log.Info("Unable to update clusterrolebinding " + clusterRoleBinding.Name + " for " + namespace.Name)
} else {
log.Log.Info("ClusterRoleBinding " + clusterRoleBinding.Name + " for " + namespace.Name + " has been updated")
// Create clusterrolebinding if not exists
if err := r.Create(ctx, clusterRoleBinding); err != nil {
if apierrors.IsAlreadyExists(err) {
log.Log.Info("ClusterRoleBinding " + clusterRoleBinding.Name + " already exists for " + namespace.Name)

// Update clusterrolebinding in case of changes
// TODO: Check if currently deployed clusterrolebinding matches clusterrolebindnig to deploy before applying update
if err := r.Update(ctx, clusterRoleBinding); err != nil {
log.Log.Info("Unable to update clusterrolebinding " + clusterRoleBinding.Name + " for " + namespace.Name)
} else {
log.Log.Info("ClusterRoleBinding " + clusterRoleBinding.Name + " for " + namespace.Name + " has been updated")
}
err = nil
}
err = nil
if err != nil {
log.Log.Info("Unable to make ClusterRoleBinding " + clusterRoleBinding.Name + " for " + namespace.Name)
return ctrl.Result{}, err
}
} else {
log.Log.Info("ClusterRoleBinding " + clusterRoleBinding.Name + " created for " + namespace.Name)
}
if err != nil {
log.Log.Info("Unable to make ClusterRoleBinding " + clusterRoleBinding.Name + " for " + namespace.Name)

// Create providerconfig if not exists
if err := r.Create(ctx, providerAWS); err != nil {
if apierrors.IsAlreadyExists(err) {
log.Log.Info("ProviderConfig " + providerAWS.Name + " already exists for " + namespace.Name)

// Update providerconfig in case of changes
/*
For some reason we need to retrieve the providerConfig and set the ResourceVersion on our referenced
object otherwise we get an error saying "ResourceVersion" must be specified for an update. Maybe there
is a better way to do it, but this seems to work for now.
TODO: Investigate better way of performing updte on ProviderConfig
*/

getProviderConfig := &provideraws.ProviderConfig{
ObjectMeta: metav1.ObjectMeta{
Name: "" + namespace.Name + "-aws",
},
Spec: provideraws.ProviderConfigSpec{
Credentials: provideraws.ProviderCredentials{
Source: "InjectedIdentity",
},
AssumeRoleARN: &roleArn,
},
}

if err := r.Get(ctx, client.ObjectKey{
Namespace: namespace.Name,
Name: providerAWS.Name,
}, getProviderConfig); err != nil {
log.Log.Info("Unable to get ProviderConfig \n\n" + err.Error())
} else {
log.Log.Info("Successfully retrieved ProviderConfig. ResourceVersion: " + getProviderConfig.ResourceVersion)
providerAWS.SetResourceVersion(getProviderConfig.GetResourceVersion())
}

if err := r.Update(ctx, providerAWS); err != nil {
log.Log.Info("Unable to update providerconfig " + providerAWS.Name + " for " + providerAWS.Name + "\n\n" + err.Error())
} else {
log.Log.Info("ProviderConfig " + providerAWS.Name + " for " + namespace.Name + " has been updated")
}

err = nil
}

//TODO: Check whether the ProviderConfig Kind exists in the cluster and silence error

if err != nil {
log.Log.Info("Unable to make ProviderConfig " + providerAWS.Name + " for " + namespace.Name)
return ctrl.Result{}, err
}
} else {
log.Log.Info("ProviderConfig " + providerAWS.Name + " created for " + namespace.Name)
}
return ctrl.Result{}, err
} else {
log.Log.Info("ClusterRoleBinding " + clusterRoleBinding.Name + " created for " + namespace.Name)
}
// Create providerconfig if not exists

return ctrl.Result{}, nil

} else {
log.Log.Info("Capability not detected on namespace " + namespace.Name)
log.Log.Info("Detected Crossplane not enabled on namespace " + namespace.Name)

// Delete clusterrole if exists
if err := r.Delete(ctx, clusterRole); err != nil {
Expand All @@ -189,7 +294,19 @@ func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
} else {
log.Log.Info("ClusterRoleBinding deleted")
}

// Delete providerconfig if exists
if err := r.Delete(ctx, providerAWS); err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Log.Info("ProviderConfig does not exist")
// return ctrl.Result{}, nil
err = nil
} else {
return ctrl.Result{}, err
}
} else {
log.Log.Info("ProviderConfig deleted")
}

}

Expand All @@ -202,3 +319,24 @@ func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&corev1.Namespace{}).
Complete(r)
}

func getAnnotationEnvVarName(envVarName string, defaultVarName string) (string, error) {
annotation, found := os.LookupEnv(envVarName)
if !found {
ctrl.Log.Info("No " + envVarName + " environment variable set. Using default of " + defaultVarName)
return defaultVarName, nil
}
return annotation, nil
}

// func getConfigMap(configMapNameEnvVar string, configMapNamespaceEnvVar string) corev1.ConfigMap {
// configMapNameEnvVar, found := os.LookupEnv(configMapNameEnvVar)
// if !found {

// }

// configMapNamespaceEnvVar, found := os.LookupEnv(configMapNamespaceEnvVar)
// if !found {

// }
// }
Loading

0 comments on commit 8a5c8dd

Please sign in to comment.