Skip to content

Commit

Permalink
Merge pull request #158 from projectsyn/feat/additional-root-apps
Browse files Browse the repository at this point in the history
Add support for bootstrapping additional root apps
  • Loading branch information
simu authored Dec 31, 2024
2 parents a68ebe8 + 8e34f02 commit 52dff4e
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 28 deletions.
6 changes: 6 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ func main() {
"Additional facts added to the dynamic facts in the cluster object. Keys in the configmap's data field can't override existing keys.").
Default("additional-facts").
StringVar(&agent.AdditionalFactsConfigMap)
app.
Flag(
"additional-root-apps-config-map",
"Config map holding metadata for additional ArgoCD root apps and app projects.").
Default("additional-root-apps").
StringVar(&agent.AdditionalRootAppsConfigMap)
app.
Flag(
"ocp-oauth-route-namespace",
Expand Down
5 changes: 4 additions & 1 deletion pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type Agent struct {
// The configmap containing additional facts to be added to the dynamic facts
AdditionalFactsConfigMap string

// The configmap containing metadata for additional root apps to deploy
AdditionalRootAppsConfigMap string

// Reference to the OpenShift OAuth route to be added to the dynamic facts
OCPOAuthRouteNamespace string
OCPOAuthRouteName string
Expand Down Expand Up @@ -140,7 +143,7 @@ func (a *Agent) registerCluster(ctx context.Context, config *rest.Config, apiCli
return
}

if err := argocd.Apply(ctx, config, a.Namespace, a.OperatorNamespace, a.ArgoCDImage, a.RedisImage, apiClient, cluster); err != nil {
if err := argocd.Apply(ctx, config, a.Namespace, a.OperatorNamespace, a.ArgoCDImage, a.RedisImage, a.AdditionalRootAppsConfigMap, apiClient, cluster); err != nil {
klog.Error(err)
}
}
Expand Down
59 changes: 48 additions & 11 deletions pkg/argocd/argo-app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package argocd

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

"github.com/projectsyn/lieutenant-api/pkg/api"
"k8s.io/apimachinery/pkg/api/errors"
k8err "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/klog"
)
Expand All @@ -23,20 +27,48 @@ var (
argoProjectGVR = argoGroupVersion.WithResource("appprojects")

localKubernetesAPI = "https://kubernetes.default.svc"

additionalRootAppsConfigKey = "teams"
)

func createArgoProject(ctx context.Context, cluster *api.Cluster, config *rest.Config, namespace string) error {
func readAdditionalRootAppsConfigMap(ctx context.Context, clientset *kubernetes.Clientset, namespace, additionalRootAppsConfigMapName string) ([]string, error) {
cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, additionalRootAppsConfigMapName, v1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
klog.Info("Additional root apps config map not present")
return []string{}, nil
} else {
return nil, fmt.Errorf("unable to fetch the additional root apps config map: %w", err)
}
}
teamsJson, ok := cm.Data[additionalRootAppsConfigKey]
if !ok {
return nil, fmt.Errorf("additional root apps ConfigMap doesn't have key %s", additionalRootAppsConfigKey)
}
var teams []string
if err := json.Unmarshal([]byte(teamsJson), &teams); err != nil {
return nil, fmt.Errorf("unmarshalling additional root apps ConfigMap contents: %v", err)
}
return teams, nil
}

func createArgoProject(ctx context.Context, cluster *api.Cluster, config *rest.Config, namespace, name string) error {
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return err
}
argoProjectClient := dynamicClient.Resource(argoProjectGVR)

if _, err = argoProjectClient.Namespace(namespace).Get(ctx, name, v1.GetOptions{}); err == nil {
return nil
}

project := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": argoProjectGVR.Group + "/" + argoProjectGVR.Version,
"kind": "AppProject",
"metadata": map[string]interface{}{
"name": argoProjectName,
"name": name,
},
"spec": map[string]interface{}{
"clusterResourceWhitelist": []map[string]interface{}{{
Expand All @@ -56,39 +88,44 @@ func createArgoProject(ctx context.Context, cluster *api.Cluster, config *rest.C

if _, err = argoProjectClient.Namespace(namespace).Create(ctx, project, v1.CreateOptions{}); err != nil {
if k8err.IsAlreadyExists(err) {
klog.Warning("Argo Project already exists, skip")
klog.Warning("Argo Project already exists, skipping... app=", name)
} else {
return err
}
} else {
klog.Info("Argo Project created")
klog.Info("Argo Project created: ", name)
}
return nil
}

func createArgoApp(ctx context.Context, cluster *api.Cluster, config *rest.Config, namespace string) error {
func createArgoApp(ctx context.Context, cluster *api.Cluster, config *rest.Config, namespace, projectName, name, appsPath string) error {
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return err
}
argoAppClient := dynamicClient.Resource(argoAppGVR)

if _, err = argoAppClient.Namespace(namespace).Get(ctx, name, v1.GetOptions{}); err == nil {
return nil
}

app := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": argoAppGVR.Group + "/" + argoAppGVR.Version,
"kind": "Application",
"metadata": map[string]interface{}{
"name": argoRootAppName,
"name": name,
},
"spec": map[string]interface{}{
"project": argoProjectName,
"project": projectName,
"source": map[string]interface{}{
"repoURL": *cluster.GitRepo.Url,
"path": argoAppsPath,
"path": appsPath + "/",
"targetRevision": "HEAD",
},
"syncPolicy": map[string]interface{}{
"automated": map[string]interface{}{
"prune": true,
"prune": false,
"selfHeal": true,
},
},
Expand All @@ -102,12 +139,12 @@ func createArgoApp(ctx context.Context, cluster *api.Cluster, config *rest.Confi

if _, err = argoAppClient.Namespace(namespace).Create(ctx, app, v1.CreateOptions{}); err != nil {
if k8err.IsAlreadyExists(err) {
klog.Warning("Argo App already exists, skip")
klog.Warning("Argo App already exists, skipping... app=", name)
} else {
return err
}
} else {
klog.Info("Argo App created")
klog.Info("Argo App created: ", name)
}
return nil
}
56 changes: 40 additions & 16 deletions pkg/argocd/argocd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@ var (
argoAnnotations = map[string]string{
"argocd.argoproj.io/sync-options": "Prune=false",
}
argoSSHSecretName = "argo-ssh-key"
argoSSHPublicKey = "sshPublicKey"
argoSSHPrivateKey = "sshPrivateKey"
argoSSHConfigMapName = "argocd-ssh-known-hosts-cm"
argoTLSConfigMapName = "argocd-tls-certs-cm"
argoRbacConfigMapName = "argocd-rbac-cm"
argoConfigMapName = "argocd-cm"
argoSecretName = "argocd-secret"
argoClusterSecretName = "syn-argocd-cluster"
argoRbacName = "argocd-application-controller"
argoRootAppName = "root"
argoProjectName = "syn"
argoAppsPath = "manifests/apps/"
argoSSHSecretName = "argo-ssh-key"
argoSSHPublicKey = "sshPublicKey"
argoSSHPrivateKey = "sshPrivateKey"
argoSSHConfigMapName = "argocd-ssh-known-hosts-cm"
argoTLSConfigMapName = "argocd-tls-certs-cm"
argoRbacConfigMapName = "argocd-rbac-cm"
argoConfigMapName = "argocd-cm"
argoSecretName = "argocd-secret"
argoClusterSecretName = "syn-argocd-cluster"
argoRbacName = "argocd-application-controller"
defaultArgoRootAppName = "root"
defaultArgoProjectName = "syn"
argoAppsPathPrefix = "manifests/apps"
)

// Apply reconciles the Argo CD deployments
func Apply(ctx context.Context, config *rest.Config, namespace, operatorNamespace, argoImage, redisArgoImage string, apiClient *api.Client, cluster *api.Cluster) error {
func Apply(ctx context.Context, config *rest.Config, namespace, operatorNamespace, argoImage, redisArgoImage, additionalRootAppsConfigMapName string, apiClient *api.Client, cluster *api.Cluster) error {
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return err
Expand All @@ -65,6 +65,10 @@ func Apply(ctx context.Context, config *rest.Config, namespace, operatorNamespac
return err
}

if err = applyAdditionalRootApps(ctx, clientset, config, namespace, additionalRootAppsConfigMapName, cluster); err != nil {
return err
}

if err == nil && len(argos.Items) > 0 {
// An ArgoCD custom resource exists in our namespace
err = fixArgoOperatorDeadlock(ctx, clientset, config, namespace, operatorNamespace)
Expand Down Expand Up @@ -122,11 +126,11 @@ func bootstrapArgo(ctx context.Context, clientset *kubernetes.Clientset, config
return err
}

if err := createArgoProject(ctx, cluster, config, namespace); err != nil {
if err := createArgoProject(ctx, cluster, config, namespace, defaultArgoProjectName); err != nil {
return err
}

if err := createArgoApp(ctx, cluster, config, namespace); err != nil {
if err := createArgoApp(ctx, cluster, config, namespace, defaultArgoProjectName, defaultArgoRootAppName, argoAppsPathPrefix); err != nil {
return err
}

Expand Down Expand Up @@ -189,3 +193,23 @@ func fixArgoOperatorDeadlock(ctx context.Context, clientset *kubernetes.Clientse

return multierr.Combine(errors...)
}

func applyAdditionalRootApps(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config, namespace, additionalRootAppsConfigMapName string, cluster *api.Cluster) error {
teamNames, err := readAdditionalRootAppsConfigMap(ctx, clientset, namespace, additionalRootAppsConfigMapName)
if err != nil {
return err
}

for _, name := range teamNames {
if err := createArgoProject(ctx, cluster, config, namespace, name); err != nil {
return err
}

// apps path for additional root apps is `manifests/apps-<team name>/`.
if err := createArgoApp(ctx, cluster, config, namespace, name, "root-"+name, argoAppsPathPrefix+"-"+name); err != nil {
return err
}
}

return nil
}

0 comments on commit 52dff4e

Please sign in to comment.