Skip to content

Commit 52dff4e

Browse files
authored
Merge pull request #158 from projectsyn/feat/additional-root-apps
Add support for bootstrapping additional root apps
2 parents a68ebe8 + 8e34f02 commit 52dff4e

File tree

4 files changed

+98
-28
lines changed

4 files changed

+98
-28
lines changed

main.go

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ func main() {
5454
"Additional facts added to the dynamic facts in the cluster object. Keys in the configmap's data field can't override existing keys.").
5555
Default("additional-facts").
5656
StringVar(&agent.AdditionalFactsConfigMap)
57+
app.
58+
Flag(
59+
"additional-root-apps-config-map",
60+
"Config map holding metadata for additional ArgoCD root apps and app projects.").
61+
Default("additional-root-apps").
62+
StringVar(&agent.AdditionalRootAppsConfigMap)
5763
app.
5864
Flag(
5965
"ocp-oauth-route-namespace",

pkg/agent/agent.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ type Agent struct {
3636
// The configmap containing additional facts to be added to the dynamic facts
3737
AdditionalFactsConfigMap string
3838

39+
// The configmap containing metadata for additional root apps to deploy
40+
AdditionalRootAppsConfigMap string
41+
3942
// Reference to the OpenShift OAuth route to be added to the dynamic facts
4043
OCPOAuthRouteNamespace string
4144
OCPOAuthRouteName string
@@ -140,7 +143,7 @@ func (a *Agent) registerCluster(ctx context.Context, config *rest.Config, apiCli
140143
return
141144
}
142145

143-
if err := argocd.Apply(ctx, config, a.Namespace, a.OperatorNamespace, a.ArgoCDImage, a.RedisImage, apiClient, cluster); err != nil {
146+
if err := argocd.Apply(ctx, config, a.Namespace, a.OperatorNamespace, a.ArgoCDImage, a.RedisImage, a.AdditionalRootAppsConfigMap, apiClient, cluster); err != nil {
144147
klog.Error(err)
145148
}
146149
}

pkg/argocd/argo-app.go

+48-11
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ package argocd
22

33
import (
44
"context"
5+
"encoding/json"
6+
"fmt"
57

68
"github.com/projectsyn/lieutenant-api/pkg/api"
9+
"k8s.io/apimachinery/pkg/api/errors"
710
k8err "k8s.io/apimachinery/pkg/api/errors"
811
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
912
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1013
"k8s.io/apimachinery/pkg/runtime/schema"
1114
"k8s.io/client-go/dynamic"
15+
"k8s.io/client-go/kubernetes"
1216
"k8s.io/client-go/rest"
1317
"k8s.io/klog"
1418
)
@@ -23,20 +27,48 @@ var (
2327
argoProjectGVR = argoGroupVersion.WithResource("appprojects")
2428

2529
localKubernetesAPI = "https://kubernetes.default.svc"
30+
31+
additionalRootAppsConfigKey = "teams"
2632
)
2733

28-
func createArgoProject(ctx context.Context, cluster *api.Cluster, config *rest.Config, namespace string) error {
34+
func readAdditionalRootAppsConfigMap(ctx context.Context, clientset *kubernetes.Clientset, namespace, additionalRootAppsConfigMapName string) ([]string, error) {
35+
cm, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, additionalRootAppsConfigMapName, v1.GetOptions{})
36+
if err != nil {
37+
if errors.IsNotFound(err) {
38+
klog.Info("Additional root apps config map not present")
39+
return []string{}, nil
40+
} else {
41+
return nil, fmt.Errorf("unable to fetch the additional root apps config map: %w", err)
42+
}
43+
}
44+
teamsJson, ok := cm.Data[additionalRootAppsConfigKey]
45+
if !ok {
46+
return nil, fmt.Errorf("additional root apps ConfigMap doesn't have key %s", additionalRootAppsConfigKey)
47+
}
48+
var teams []string
49+
if err := json.Unmarshal([]byte(teamsJson), &teams); err != nil {
50+
return nil, fmt.Errorf("unmarshalling additional root apps ConfigMap contents: %v", err)
51+
}
52+
return teams, nil
53+
}
54+
55+
func createArgoProject(ctx context.Context, cluster *api.Cluster, config *rest.Config, namespace, name string) error {
2956
dynamicClient, err := dynamic.NewForConfig(config)
3057
if err != nil {
3158
return err
3259
}
3360
argoProjectClient := dynamicClient.Resource(argoProjectGVR)
61+
62+
if _, err = argoProjectClient.Namespace(namespace).Get(ctx, name, v1.GetOptions{}); err == nil {
63+
return nil
64+
}
65+
3466
project := &unstructured.Unstructured{
3567
Object: map[string]interface{}{
3668
"apiVersion": argoProjectGVR.Group + "/" + argoProjectGVR.Version,
3769
"kind": "AppProject",
3870
"metadata": map[string]interface{}{
39-
"name": argoProjectName,
71+
"name": name,
4072
},
4173
"spec": map[string]interface{}{
4274
"clusterResourceWhitelist": []map[string]interface{}{{
@@ -56,39 +88,44 @@ func createArgoProject(ctx context.Context, cluster *api.Cluster, config *rest.C
5688

5789
if _, err = argoProjectClient.Namespace(namespace).Create(ctx, project, v1.CreateOptions{}); err != nil {
5890
if k8err.IsAlreadyExists(err) {
59-
klog.Warning("Argo Project already exists, skip")
91+
klog.Warning("Argo Project already exists, skipping... app=", name)
6092
} else {
6193
return err
6294
}
6395
} else {
64-
klog.Info("Argo Project created")
96+
klog.Info("Argo Project created: ", name)
6597
}
6698
return nil
6799
}
68100

69-
func createArgoApp(ctx context.Context, cluster *api.Cluster, config *rest.Config, namespace string) error {
101+
func createArgoApp(ctx context.Context, cluster *api.Cluster, config *rest.Config, namespace, projectName, name, appsPath string) error {
70102
dynamicClient, err := dynamic.NewForConfig(config)
71103
if err != nil {
72104
return err
73105
}
74106
argoAppClient := dynamicClient.Resource(argoAppGVR)
107+
108+
if _, err = argoAppClient.Namespace(namespace).Get(ctx, name, v1.GetOptions{}); err == nil {
109+
return nil
110+
}
111+
75112
app := &unstructured.Unstructured{
76113
Object: map[string]interface{}{
77114
"apiVersion": argoAppGVR.Group + "/" + argoAppGVR.Version,
78115
"kind": "Application",
79116
"metadata": map[string]interface{}{
80-
"name": argoRootAppName,
117+
"name": name,
81118
},
82119
"spec": map[string]interface{}{
83-
"project": argoProjectName,
120+
"project": projectName,
84121
"source": map[string]interface{}{
85122
"repoURL": *cluster.GitRepo.Url,
86-
"path": argoAppsPath,
123+
"path": appsPath + "/",
87124
"targetRevision": "HEAD",
88125
},
89126
"syncPolicy": map[string]interface{}{
90127
"automated": map[string]interface{}{
91-
"prune": true,
128+
"prune": false,
92129
"selfHeal": true,
93130
},
94131
},
@@ -102,12 +139,12 @@ func createArgoApp(ctx context.Context, cluster *api.Cluster, config *rest.Confi
102139

103140
if _, err = argoAppClient.Namespace(namespace).Create(ctx, app, v1.CreateOptions{}); err != nil {
104141
if k8err.IsAlreadyExists(err) {
105-
klog.Warning("Argo App already exists, skip")
142+
klog.Warning("Argo App already exists, skipping... app=", name)
106143
} else {
107144
return err
108145
}
109146
} else {
110-
klog.Info("Argo App created")
147+
klog.Info("Argo App created: ", name)
111148
}
112149
return nil
113150
}

pkg/argocd/argocd.go

+40-16
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,23 @@ var (
2626
argoAnnotations = map[string]string{
2727
"argocd.argoproj.io/sync-options": "Prune=false",
2828
}
29-
argoSSHSecretName = "argo-ssh-key"
30-
argoSSHPublicKey = "sshPublicKey"
31-
argoSSHPrivateKey = "sshPrivateKey"
32-
argoSSHConfigMapName = "argocd-ssh-known-hosts-cm"
33-
argoTLSConfigMapName = "argocd-tls-certs-cm"
34-
argoRbacConfigMapName = "argocd-rbac-cm"
35-
argoConfigMapName = "argocd-cm"
36-
argoSecretName = "argocd-secret"
37-
argoClusterSecretName = "syn-argocd-cluster"
38-
argoRbacName = "argocd-application-controller"
39-
argoRootAppName = "root"
40-
argoProjectName = "syn"
41-
argoAppsPath = "manifests/apps/"
29+
argoSSHSecretName = "argo-ssh-key"
30+
argoSSHPublicKey = "sshPublicKey"
31+
argoSSHPrivateKey = "sshPrivateKey"
32+
argoSSHConfigMapName = "argocd-ssh-known-hosts-cm"
33+
argoTLSConfigMapName = "argocd-tls-certs-cm"
34+
argoRbacConfigMapName = "argocd-rbac-cm"
35+
argoConfigMapName = "argocd-cm"
36+
argoSecretName = "argocd-secret"
37+
argoClusterSecretName = "syn-argocd-cluster"
38+
argoRbacName = "argocd-application-controller"
39+
defaultArgoRootAppName = "root"
40+
defaultArgoProjectName = "syn"
41+
argoAppsPathPrefix = "manifests/apps"
4242
)
4343

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

68+
if err = applyAdditionalRootApps(ctx, clientset, config, namespace, additionalRootAppsConfigMapName, cluster); err != nil {
69+
return err
70+
}
71+
6872
if err == nil && len(argos.Items) > 0 {
6973
// An ArgoCD custom resource exists in our namespace
7074
err = fixArgoOperatorDeadlock(ctx, clientset, config, namespace, operatorNamespace)
@@ -122,11 +126,11 @@ func bootstrapArgo(ctx context.Context, clientset *kubernetes.Clientset, config
122126
return err
123127
}
124128

125-
if err := createArgoProject(ctx, cluster, config, namespace); err != nil {
129+
if err := createArgoProject(ctx, cluster, config, namespace, defaultArgoProjectName); err != nil {
126130
return err
127131
}
128132

129-
if err := createArgoApp(ctx, cluster, config, namespace); err != nil {
133+
if err := createArgoApp(ctx, cluster, config, namespace, defaultArgoProjectName, defaultArgoRootAppName, argoAppsPathPrefix); err != nil {
130134
return err
131135
}
132136

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

190194
return multierr.Combine(errors...)
191195
}
196+
197+
func applyAdditionalRootApps(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config, namespace, additionalRootAppsConfigMapName string, cluster *api.Cluster) error {
198+
teamNames, err := readAdditionalRootAppsConfigMap(ctx, clientset, namespace, additionalRootAppsConfigMapName)
199+
if err != nil {
200+
return err
201+
}
202+
203+
for _, name := range teamNames {
204+
if err := createArgoProject(ctx, cluster, config, namespace, name); err != nil {
205+
return err
206+
}
207+
208+
// apps path for additional root apps is `manifests/apps-<team name>/`.
209+
if err := createArgoApp(ctx, cluster, config, namespace, name, "root-"+name, argoAppsPathPrefix+"-"+name); err != nil {
210+
return err
211+
}
212+
}
213+
214+
return nil
215+
}

0 commit comments

Comments
 (0)