|
| 1 | +package reckoner |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "errors" |
| 6 | + "os" |
| 7 | + "strings" |
| 8 | + |
| 9 | + "github.com/fairwindsops/reckoner/pkg/course" |
| 10 | + "github.com/fatih/color" |
| 11 | + "github.com/imdario/mergo" |
| 12 | + "gopkg.in/yaml.v3" |
| 13 | + "k8s.io/klog/v2" |
| 14 | +) |
| 15 | + |
| 16 | +func generateArgoApplication(release course.Release, courseFile course.FileV2) (app course.ArgoApplication, err error) { |
| 17 | + app = courseFile.GitOps.ArgoCD // use global config at root of course file |
| 18 | + |
| 19 | + // if release.GitOps.ArgoCD.<whatever> exists, override the app.<whatever> with that one, recursively. |
| 20 | + err = mergo.Merge(&app, release.GitOps.ArgoCD, mergo.WithOverride) |
| 21 | + if err != nil { |
| 22 | + return app, err |
| 23 | + } |
| 24 | + |
| 25 | + // default to a kind of Application if it was omitted in the course file |
| 26 | + if app.Kind == "" { |
| 27 | + app.Kind = "Application" |
| 28 | + } |
| 29 | + |
| 30 | + // default to an API version of v1alpha1 if it was omitted in the course file |
| 31 | + if app.APIVersion == "" { |
| 32 | + app.APIVersion = "argoproj.io/v1alpha1" |
| 33 | + } |
| 34 | + |
| 35 | + // unless they overrode it in the course file, assume the name of the argocd app is the same as the helm release |
| 36 | + // app:release should be a 1:1 |
| 37 | + if app.Metadata.Name == "" { |
| 38 | + app.Metadata.Name = release.Name |
| 39 | + } |
| 40 | + |
| 41 | + // Application.Metadata.Namespace is where the ArgoCD Application resource will go (not the helm release) |
| 42 | + if app.Metadata.Namespace == "" { |
| 43 | + klog.V(3).Infoln("No namespace declared in course file. Your ArgoCD Application manifests will likely get applied to the agent's default context.") |
| 44 | + } |
| 45 | + |
| 46 | + // default source path to release name |
| 47 | + if app.Spec.Source.Path == "" { |
| 48 | + klog.V(3).Infoln("No .gitops.argocd.spec.source.path declared in course file for " + release.Name + ". The path has been set to its name.") |
| 49 | + app.Spec.Source.Path = release.Name |
| 50 | + } |
| 51 | + |
| 52 | + // don't support ArgoCD Application spec.destination.namespace at all |
| 53 | + if app.Spec.Destination.Namespace != "" { |
| 54 | + klog.V(3).Infoln("Refusing to respect the .gitops.argocd.spec.destination.namespace value declared in course file for " + release.Name + ". Using the release namespace instead, if it exists. If none is specified, the default at the root of the course YAML file will be used. Remove the namespace from the ArgoCD destination to stop seeing this warning.") |
| 55 | + } |
| 56 | + |
| 57 | + // Application.Spec.Destination.Namespace is where the helm releases will be applied |
| 58 | + if release.Namespace != "" { // there's a specific namespace for this release |
| 59 | + app.Spec.Destination.Namespace = release.Namespace // specify it as the destination namespace |
| 60 | + } else { // nothing was specified in the release |
| 61 | + app.Spec.Destination.Namespace = courseFile.DefaultNamespace // use the default namespace at the root of the course file |
| 62 | + } |
| 63 | + |
| 64 | + if app.Spec.Destination.Server == "" { |
| 65 | + klog.V(3).Infoln("No .gitops.argocd.spec.destination.server declared in course file for " + release.Name + ". Assuming you want the kubernetes service in the default namespace. Specify to make this warning go away.") |
| 66 | + app.Spec.Destination.Server = "https://kubernetes.default.svc" |
| 67 | + } |
| 68 | + |
| 69 | + if app.Spec.Project == "" { |
| 70 | + klog.V(3).Infoln("No .gitops.argocd.spec.project declared in course file for " + release.Name + ". We'll set it to a sensible default value of 'default'. Specify to make this warning go away.") |
| 71 | + app.Spec.Project = "default" |
| 72 | + } |
| 73 | + |
| 74 | + return app, err |
| 75 | +} |
| 76 | + |
| 77 | +func (c *Client) WriteArgoApplications(outputDir string) (err error) { |
| 78 | + appsOutputDir := outputDir + "/argocd-apps" |
| 79 | + for _, dir := range []string{outputDir, appsOutputDir} { |
| 80 | + if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { |
| 81 | + err := os.Mkdir(dir, os.ModePerm) |
| 82 | + if err != nil { |
| 83 | + return err |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + for _, release := range c.CourseFile.Releases { |
| 89 | + // generate an argocd application resource |
| 90 | + app, err := generateArgoApplication(*release, c.CourseFile) |
| 91 | + if err != nil { |
| 92 | + return err |
| 93 | + } |
| 94 | + |
| 95 | + if app.Metadata.Name == "" { |
| 96 | + color.Yellow("No metadata found for release " + release.Name + ". Skipping ArgoCD Applicationgeneration...") |
| 97 | + continue |
| 98 | + } |
| 99 | + |
| 100 | + // generate name of app file |
| 101 | + appOutputFile := appsOutputDir + "/" + strings.ToLower(app.Metadata.Name) + ".yaml" |
| 102 | + |
| 103 | + // prepare to write stuff (pretty) |
| 104 | + var b bytes.Buffer // used for encoding & return |
| 105 | + yamlEncoder := yaml.NewEncoder(&b) // create an encoder to handle custom configuration |
| 106 | + yamlEncoder.SetIndent(2) // people expect two-space indents instead of the default four |
| 107 | + err = yamlEncoder.Encode(&app) // encode proper YAML into slice of bytes |
| 108 | + if err != nil { // check for errors |
| 109 | + return err // bubble up |
| 110 | + } |
| 111 | + |
| 112 | + // write stuff |
| 113 | + err = writeYAML(b.Bytes(), appOutputFile) |
| 114 | + if err != nil { // check for errors |
| 115 | + return err // bubble up |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + return err |
| 120 | +} |
0 commit comments