Skip to content

Commit

Permalink
feat: Create ArgoCD cluster secret (#30)
Browse files Browse the repository at this point in the history
Added reconciliation for the cluster secrets.
For every cluster secret, we create an ArgoCD cluster secret.

Change-Id: Ie46934ca4eb96eb35a72e9455d367dc6bc0c3620
  • Loading branch information
zmotso committed Mar 15, 2024
1 parent ef78475 commit 8ca1ca2
Show file tree
Hide file tree
Showing 15 changed files with 458 additions and 8 deletions.
5 changes: 2 additions & 3 deletions config/crd/bases/v2.edp.epam.com_stages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,8 @@ spec:
default: deploy
description: Specifies a name of Tekton TriggerTemplate which will
be used as a blueprint for deployment pipeline. Default value is
"deploy" which means that default TriggerTemplate will
be used. The default TriggerTemplate is delivered using edp-tekton
helm chart.
"deploy" which means that default TriggerTemplate will be used.
The default TriggerTemplate is delivered using edp-tekton helm chart.
type: string
triggerType:
description: Stage deployment trigger type. E.g. Manual, Auto
Expand Down
11 changes: 11 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ metadata:
name: manager-role
namespace: placeholder
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- get
- list
- patch
- update
- watch
- apiGroups:
- argoproj.io
resources:
Expand Down
25 changes: 25 additions & 0 deletions controllers/clustersecret/argocd_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package clustersecret

const (
argoCDClusterLabel = "argocd.argoproj.io/secret-type"
argoCDClusterLabelVal = "cluster"
)

// ClusterConfig is the ArgoCD cluster configuration.
type ClusterConfig struct {
// BearerToken is the token to authenticate with the cluster.
BearerToken string `json:"bearerToken,omitempty" protobuf:"bytes,3,opt,name=bearerToken"`

// TLSClientConfig contains settings to enable transport layer security
TLSClientConfig `json:"tlsClientConfig" protobuf:"bytes,4,opt,name=tlsClientConfig"`
}

// TLSClientConfig contains settings to enable transport layer security.
type TLSClientConfig struct {
// Insecure specifies that the server should be accessed without verifying the TLS certificate. For testing only.
Insecure bool `json:"insecure" protobuf:"bytes,1,opt,name=insecure"`

// CAData holds PEM-encoded bytes (typically read from a root certificates bundle).
// CAData takes precedence over CAFile
CAData []byte `json:"caData,omitempty" protobuf:"bytes,5,opt,name=caData"`
}
148 changes: 148 additions & 0 deletions controllers/clustersecret/clustersecret_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package clustersecret

import (
"context"
"encoding/json"
"fmt"

corev1 "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/epam/edp-cd-pipeline-operator/v2/pkg/multiclusterclient"
)

const (
// nolint:gosec // Cluster secret label.
integrationSecretTypeLabel = "app.edp.epam.com/secret-type"
integrationSecretTypeLabelVal = "cluster"
)

type ReconcileClusterSecret struct {
client client.Client
}

func NewReconcileClusterSecret(k8sClient client.Client) *ReconcileClusterSecret {
return &ReconcileClusterSecret{client: k8sClient}
}

func (r *ReconcileClusterSecret) SetupWithManager(mgr ctrl.Manager) error {
p := predicate.Funcs{
CreateFunc: func(event event.CreateEvent) bool {
return hasClusterSecretLabelLabel(event.Object)
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
return false
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
return hasClusterSecretLabelLabel(updateEvent.ObjectNew)
},
GenericFunc: func(genericEvent event.GenericEvent) bool {
return hasClusterSecretLabelLabel(genericEvent.Object)
},
}

err := ctrl.NewControllerManagedBy(mgr).
For(&corev1.Secret{}, builder.WithPredicates(p)).
Complete(r)
if err != nil {
return fmt.Errorf("failed to build ClusterSecret controller: %w", err)
}

return nil
}

//+kubebuilder:rbac:groups="",namespace=placeholder,resources=secrets,verbs=get;list;watch;update;patch;create

// Reconcile process secrets with app.edp.epam.com/secret-type: cluster label.
func (r *ReconcileClusterSecret) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
secret := &corev1.Secret{}
if err := r.client.Get(ctx, request.NamespacedName, secret); err != nil {
if k8sErrors.IsNotFound(err) {
return reconcile.Result{}, nil
}

return reconcile.Result{}, fmt.Errorf("failed to get Secret: %w", err)
}

if err := r.createArgoCDClusterSecret(ctx, secret); err != nil {
return reconcile.Result{}, err
}

return reconcile.Result{}, nil
}

func (r *ReconcileClusterSecret) createArgoCDClusterSecret(ctx context.Context, secret *corev1.Secret) error {
log := ctrl.LoggerFrom(ctx)

log.Info("Start creating ArgoCD cluster secret")

restConf, err := multiclusterclient.ClusterSecretToRestConfig(secret)
if err != nil {
return fmt.Errorf("failed to convert cluster secret to rest config: %w", err)
}

argoClusterSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-argocd-cluster", secret.Name),
Namespace: secret.Namespace,
},
}

var res controllerutil.OperationResult

if res, err = controllerutil.CreateOrUpdate(ctx, r.client, argoClusterSecret, func() error {
argoClusterConf := &ClusterConfig{}
argoClusterConf.BearerToken = restConf.BearerToken
argoClusterConf.CAData = restConf.TLSClientConfig.CAData
argoClusterConf.Insecure = restConf.TLSClientConfig.Insecure

var rawConf json.RawMessage

if rawConf, err = json.Marshal(argoClusterConf); err != nil {
return fmt.Errorf("failed to marshal cluster config: %w", err)
}

addClusterLabel(argoClusterSecret)

argoClusterSecret.Data = map[string][]byte{
"name": []byte(secret.Name),
"server": []byte(restConf.Host),
"config": rawConf,
}

if err = controllerutil.SetControllerReference(secret, argoClusterSecret, r.client.Scheme()); err != nil {
return fmt.Errorf("failed to set controller reference: %w", err)
}

return nil
}); err != nil {
return fmt.Errorf("failed to create/update or update ArgoCD cluster secret: %w", err)
}

log.Info(fmt.Sprintf("ArgoCD cluster secret has been %s", res))

return nil
}

func hasClusterSecretLabelLabel(object client.Object) bool {
return object.GetLabels()[integrationSecretTypeLabel] == integrationSecretTypeLabelVal
}

func addClusterLabel(argoClusterSecret *corev1.Secret) {
labels := argoClusterSecret.GetLabels()
if labels == nil {
labels = make(map[string]string, 1)
}

labels[argoCDClusterLabel] = argoCDClusterLabelVal

argoClusterSecret.SetLabels(labels)
}
Loading

0 comments on commit 8ca1ca2

Please sign in to comment.