github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/cd-service/pkg/argocd/render.go (about) 1 /*This file is part of kuberpult. 2 3 Kuberpult is free software: you can redistribute it and/or modify 4 it under the terms of the Expat(MIT) License as published by 5 the Free Software Foundation. 6 7 Kuberpult is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 MIT License for more details. 11 12 You should have received a copy of the MIT License 13 along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>. 14 15 Copyright 2023 freiheit.com*/ 16 17 package argocd 18 19 import ( 20 "context" 21 "fmt" 22 "path/filepath" 23 "strings" 24 25 "gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer" 26 27 "sigs.k8s.io/yaml" 28 29 "github.com/freiheit-com/kuberpult/services/cd-service/pkg/argocd/v1alpha1" 30 "github.com/freiheit-com/kuberpult/services/cd-service/pkg/config" 31 ) 32 33 type ApiVersion string 34 35 const V1Alpha1 ApiVersion = "v1alpha1" 36 37 type AppData struct { 38 AppName string 39 TeamName string 40 } 41 42 func Render(ctx context.Context, gitUrl string, gitBranch string, config config.EnvironmentConfig, env string, appsData []AppData) (map[ApiVersion][]byte, error) { 43 span, _ := tracer.StartSpanFromContext(ctx, "Render") 44 defer span.Finish() 45 46 if config.ArgoCd == nil { 47 return nil, fmt.Errorf("no ArgoCd configured for environment %s", env) 48 } 49 result := map[ApiVersion][]byte{} 50 if content, err := RenderV1Alpha1(gitUrl, gitBranch, config, env, appsData); err != nil { 51 return nil, err 52 } else { 53 result[V1Alpha1] = content 54 } 55 return result, nil 56 } 57 58 func RenderV1Alpha1(gitUrl string, gitBranch string, config config.EnvironmentConfig, env string, appsData []AppData) ([]byte, error) { 59 applicationNs := "" 60 if config.ArgoCd.Destination.Namespace != nil { 61 applicationNs = *config.ArgoCd.Destination.Namespace 62 } else if config.ArgoCd.Destination.ApplicationNamespace != nil { 63 applicationNs = *config.ArgoCd.Destination.ApplicationNamespace 64 } 65 applicationDestination := v1alpha1.ApplicationDestination{ 66 Name: config.ArgoCd.Destination.Name, 67 Namespace: applicationNs, 68 Server: config.ArgoCd.Destination.Server, 69 } 70 buf := []string{} 71 syncWindows := v1alpha1.SyncWindows{} 72 for _, w := range config.ArgoCd.SyncWindows { 73 apps := []string{"*"} 74 if len(w.Apps) > 0 { 75 apps = w.Apps 76 } 77 syncWindows = append(syncWindows, &v1alpha1.SyncWindow{ 78 Applications: apps, 79 Schedule: w.Schedule, 80 Duration: w.Duration, 81 Kind: w.Kind, 82 ManualSync: true, 83 }) 84 } 85 accessEntries := []v1alpha1.AccessEntry{} 86 for _, w := range config.ArgoCd.ClusterResourceWhitelist { 87 accessEntries = append(accessEntries, v1alpha1.AccessEntry{ 88 Kind: w.Kind, 89 Group: w.Group, 90 }) 91 } 92 93 appProjectNs := "" 94 appProjectDestination := applicationDestination 95 if config.ArgoCd.Destination.Namespace != nil { 96 appProjectNs = *config.ArgoCd.Destination.Namespace 97 } else if config.ArgoCd.Destination.AppProjectNamespace != nil { 98 appProjectNs = *config.ArgoCd.Destination.AppProjectNamespace 99 } 100 appProjectDestination.Namespace = appProjectNs 101 102 project := v1alpha1.AppProject{ 103 TypeMeta: v1alpha1.AppProjectTypeMeta, 104 ObjectMeta: v1alpha1.ObjectMeta{ 105 Annotations: nil, 106 Labels: nil, 107 Finalizers: nil, 108 Name: env, 109 }, 110 Spec: v1alpha1.AppProjectSpec{ 111 Description: env, 112 SourceRepos: []string{"*"}, 113 Destinations: []v1alpha1.ApplicationDestination{appProjectDestination}, 114 SyncWindows: syncWindows, 115 ClusterResourceWhitelist: accessEntries, 116 }, 117 } 118 if content, err := yaml.Marshal(&project); err != nil { 119 return nil, err 120 } else { 121 buf = append(buf, string(content)) 122 } 123 ignoreDifferences := make([]v1alpha1.ResourceIgnoreDifferences, len(config.ArgoCd.IgnoreDifferences)) 124 for index, value := range config.ArgoCd.IgnoreDifferences { 125 ignoreDifferences[index] = v1alpha1.ResourceIgnoreDifferences(value) 126 } 127 syncOptions := config.ArgoCd.SyncOptions 128 for _, appData := range appsData { 129 appManifest, err := RenderAppEnv(gitUrl, gitBranch, config.ArgoCd.ApplicationAnnotations, env, appData, applicationDestination, ignoreDifferences, syncOptions) 130 if err != nil { 131 return nil, err 132 } 133 buf = append(buf, appManifest) 134 } 135 return ([]byte)(strings.Join(buf, "---\n")), nil 136 } 137 138 func RenderAppEnv(gitUrl string, gitBranch string, applicationAnnotations map[string]string, env string, appData AppData, destination v1alpha1.ApplicationDestination, ignoreDifferences []v1alpha1.ResourceIgnoreDifferences, syncOptions v1alpha1.SyncOptions) (string, error) { 139 name := appData.AppName 140 annotations := map[string]string{} 141 labels := map[string]string{} 142 manifestPath := filepath.Join("environments", env, "applications", name, "manifests") 143 for k, v := range applicationAnnotations { 144 annotations[k] = v 145 } 146 annotations["com.freiheit.kuberpult/team"] = appData.TeamName 147 annotations["com.freiheit.kuberpult/application"] = name 148 annotations["com.freiheit.kuberpult/environment"] = env 149 // This annotation is so that argoCd does not invalidate *everything* in the whole repo when receiving a git webhook. 150 // It has to start with a "/" to be absolute to the git repo. 151 // See https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/#webhook-and-manifest-paths-annotation 152 annotations["argocd.argoproj.io/manifest-generate-paths"] = "/" + manifestPath 153 labels["com.freiheit.kuberpult/team"] = appData.TeamName 154 app := v1alpha1.Application{ 155 TypeMeta: v1alpha1.ApplicationTypeMeta, 156 ObjectMeta: v1alpha1.ObjectMeta{ 157 Name: fmt.Sprintf("%s-%s", env, name), 158 Annotations: annotations, 159 Labels: labels, 160 Finalizers: calculateFinalizers(), 161 }, 162 Spec: v1alpha1.ApplicationSpec{ 163 Project: env, 164 Source: v1alpha1.ApplicationSource{ 165 RepoURL: gitUrl, 166 Path: manifestPath, 167 TargetRevision: gitBranch, 168 }, 169 Destination: destination, 170 SyncPolicy: &v1alpha1.SyncPolicy{ 171 Automated: &v1alpha1.SyncPolicyAutomated{ 172 Prune: true, 173 SelfHeal: true, 174 // We always allow empty, because it makes it easier to delete apps/environments 175 AllowEmpty: true, 176 }, 177 SyncOptions: syncOptions, 178 }, 179 IgnoreDifferences: ignoreDifferences, 180 }, 181 } 182 if content, err := yaml.Marshal(&app); err != nil { 183 return "", err 184 } else { 185 return string(content), nil 186 } 187 } 188 189 func calculateFinalizers() []string { 190 return []string{ 191 "resources-finalizer.argocd.argoproj.io", 192 } 193 }