diff --git a/README.md b/README.md index dede903..103901d 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,33 @@ While the above is doable with any of the other templating tools, `autoapps` kee the `Application` CRD, enables runtime configuration, and simplifies "glue" templating with auto discovery of `Applications`. +### Dynamically skipping/ignoring applications + +`autoapps` will only render Applications where `metadata.annotations.autoapps` exists. If the annotation is missing +from the Application, then it will be ignored. In cases where a user would want to dynamically include an Application +based off some values from a parent Applicaiton, the exlusive value `skip` is used to ignore. Combined with `envsubst` +formatting, an Application can be included unless an environment variable is included: + +```yaml +# NOTE: Portions of the complete spec are skipped below for brevity +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: mocha + annotations: + argocd.argoproj.io/sync-wave: "1" + autoapps: "${AUTOAPPS_SKIP_MOCHA:+skip}" +``` + +In the above example, if the Application was autodiscovered by a parent application that defined `AUTOAPPS_SKIP_APP1`, +then the application `mocha` as a whole will be skipped. + +The syntax above reads as: + +``` +If AUTO_APPS_SKIP_MOCHA is set, evaluate expression as skip, otherwise as empty string +``` + ## Work in progress * "safe" environment variable substitution diff --git a/go.mod b/go.mod index db0b0c4..fd97a75 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/joshrwolf/autoapps go 1.14 require ( + github.com/a8m/envsubst v1.2.0 // indirect github.com/drone/envsubst v1.0.2 github.com/rancher/wrangler-cli v0.0.0-20200712180548-91e38f783aa5 github.com/sirupsen/logrus v1.6.0 diff --git a/go.sum b/go.sum index ccfe642..d6ca81e 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/a8m/envsubst v1.2.0 h1:yvzAhJD2QKdo35Ut03wIfXQmg+ta3wC/1bskfZynz+Q= +github.com/a8m/envsubst v1.2.0/go.mod h1:PpvLvNWa+Rvu/10qXmFbFiGICIU5hZvFJNPCCkUaObg= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= diff --git a/main.go b/main.go index 78c33c2..d369dd7 100644 --- a/main.go +++ b/main.go @@ -9,12 +9,15 @@ import ( "io/ioutil" "os" "path/filepath" - "github.com/drone/envsubst" + "github.com/a8m/envsubst" "strings" ) const ( autoAppsFlag = "autoapps" + autoAppsAnnotationSkipVal = "skip" + argoAPIVersion = "argoproj.io/v1alpha1" + argoAppKind = "Application" ) type Generate struct { @@ -58,6 +61,18 @@ type MiniApp struct { } } +type App struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Metadata struct { + Annotations map[string]string `yaml:"annotations"` + } +} + +type Metadata struct { + Annotations map[string]string `yaml:"annotations"` +} + func walkForApps(base string) (apps []string, err error) { err = filepath.Walk(base, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -66,21 +81,22 @@ func walkForApps(base string) (apps []string, err error) { // Only care about valid yaml files if ext := filepath.Ext(path); ext == ".yaml" || ext == ".yml" { - var a MiniApp - dat, err := ioutil.ReadFile(path) + data, err := ioutil.ReadFile(path) if err != nil { return err } - err = yaml.Unmarshal(dat, &a) - if err != nil { - //logrus.Warnf("Failed to unmarshal %s: %v", path, err) + // TODO: This whole thing is some laaaazy logic flow + isApp, _ := isApp(data) + if !isApp { + // Bailout if it's not an app, we don't care anymore + return nil } - if a.ApiVersion == "argoproj.io/v1alpha1" && a.Kind == "Application" { - if _, ok := a.Metadata.Annotations[autoAppsFlag]; ok { - apps = append(apps, safeEnvSubst(string(dat))) - } + // Render envsubst + render, substitutedApp := safeEnvSubst(data) + if render { + apps = append(apps, substitutedApp) } } return nil @@ -93,13 +109,59 @@ func walkForApps(base string) (apps []string, err error) { return apps, nil } +func isApp(data []byte) (bool, App) { + var a App + isApp := false + + err := yaml.Unmarshal(data, &a) + if err != nil {} + + // Check if this is an app + if a.ApiVersion == argoAPIVersion && a.Kind == argoAppKind { + isApp = true + } + + return isApp, a +} + +func isAutoApp(data []byte) (bool, MiniApp) { + var m MiniApp + + err := yaml.Unmarshal(data, &m) + // Gobble up errors, need to keep stdout clean and stderr empty + if err != nil {} + + // Check if this is an autoapp + if m.ApiVersion == argoAPIVersion && m.Kind == argoAppKind { + if _, ok := m.Metadata.Annotations[autoAppsFlag]; ok { + return true, m + } + } + + return false, m +} + // TODO: Need to implement a way to make this "safe" and only support _allowed_ environment variables // Make it obvious how "allowed" envs are determined -func safeEnvSubst(original string) string { - substituted, err := envsubst.EvalEnv(original) +func safeEnvSubst(original []byte) (bool, string) { + render := false + + substituted, err := envsubst.Bytes(original) if err != nil { logrus.Fatalf("Failed to substitute: %v", err) } - return substituted + // Only return valid yaml if annotations trigger is true + var a App + err = yaml.Unmarshal(substituted, &a) + if err != nil {} + + if val, ok := a.Metadata.Annotations[autoAppsFlag]; ok { + if val != autoAppsAnnotationSkipVal { + render = true + return render, string(substituted) + } + } + + return render, "" } \ No newline at end of file diff --git a/testdata/app1.yaml b/testdata/app1.yaml index 12a5c15..a77fa4a 100644 --- a/testdata/app1.yaml +++ b/testdata/app1.yaml @@ -1,3 +1,4 @@ +# This is included unless AUTOAPPS_SKIP_APP1 exists at runtime apiVersion: argoproj.io/v1alpha1 kind: Application metadata: @@ -7,6 +8,7 @@ metadata: - resources-finalizer.argocd.argoproj.io annotations: argocd.argoproj.io/sync-wave: "1" + autoapps: ${AUTOAPPS_SKIP_APP1:+"skip"} spec: project: default diff --git a/testdata/notanapp.yaml b/testdata/notanapp.yaml index 8cfb799..b62bb8c 100644 --- a/testdata/notanapp.yaml +++ b/testdata/notanapp.yaml @@ -1,3 +1,4 @@ +# This is _not_ included because it is not of the right kind / apiVersion apiVersion: argoproj.io/v1alpha1 kind: NotAnApp metadata: diff --git a/testdata/sub/app2.yaml b/testdata/sub/app2.yaml index 79c5b86..8f7a607 100644 --- a/testdata/sub/app2.yaml +++ b/testdata/sub/app2.yaml @@ -1,3 +1,4 @@ +# This is included unless AUTOAPPS_SKIP_APP2 exists at runtime apiVersion: argoproj.io/v1alpha1 kind: Application metadata: @@ -7,6 +8,7 @@ metadata: - resources-finalizer.argocd.argoproj.io annotations: argocd.argoproj.io/sync-wave: "1" + autoapps: ${AUTOAPPS_SKIP_APP2:+"skip"} spec: project: default diff --git a/testdata/sub/app3.yaml b/testdata/sub/nonincludedapp.yaml similarity index 84% rename from testdata/sub/app3.yaml rename to testdata/sub/nonincludedapp.yaml index 7bb62d1..ea319c4 100644 --- a/testdata/sub/app3.yaml +++ b/testdata/sub/nonincludedapp.yaml @@ -1,13 +1,14 @@ +# This is not included because autoapps is not set to true apiVersion: argoproj.io/v1alpha1 kind: Application metadata: - name: app3 + name: nonincludedapp namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io annotations: argocd.argoproj.io/sync-wave: "1" - autoapps: "true" + autoapps: "skip" spec: project: default