Skip to content

Commit

Permalink
add keyword to dynamically exclude auto discovered applications
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Wolf committed Jul 19, 2020
1 parent 1f475e2 commit fa61057
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 15 deletions.
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
88 changes: 75 additions & 13 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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, ""
}
2 changes: 2 additions & 0 deletions testdata/app1.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This is included unless AUTOAPPS_SKIP_APP1 exists at runtime
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
Expand All @@ -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

Expand Down
1 change: 1 addition & 0 deletions testdata/notanapp.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This is _not_ included because it is not of the right kind / apiVersion
apiVersion: argoproj.io/v1alpha1
kind: NotAnApp
metadata:
Expand Down
2 changes: 2 additions & 0 deletions testdata/sub/app2.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# This is included unless AUTOAPPS_SKIP_APP2 exists at runtime
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
Expand All @@ -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

Expand Down
5 changes: 3 additions & 2 deletions testdata/sub/app3.yaml → testdata/sub/nonincludedapp.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down

0 comments on commit fa61057

Please sign in to comment.