From c7a1f5f7c5612b351ffa84f3b3c7a2bf7fcaf1af Mon Sep 17 00:00:00 2001 From: Matt Schurenko Date: Sun, 27 Jan 2019 12:34:54 -0800 Subject: [PATCH] support both s3 and local vars file --- .circleci/config.yml | 2 +- Makefile | 6 ++-- README.md | 30 ++++++++++++++-- fixtures/test1.conf.tmpl | 2 +- fixtures/test2.conf.tmpl | 2 +- fixtures/vars-no-secret.yml | 9 +++++ fixtures/vars.yml | 3 +- funcs.go | 68 ++++++++++++++++++++++++------------- funcs_test.go | 31 +++++++---------- main.go | 17 ++++++---- test.sh | 3 +- 11 files changed, 114 insertions(+), 59 deletions(-) create mode 100644 fixtures/vars-no-secret.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index ba60d06..d49dbca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ version: 2 jobs: build: environment: - VERSION: &version 0.1.4 + VERSION: &version 0.1.5 AWS_REGION: us-west-2 docker: - image: &image golang:latest diff --git a/Makefile b/Makefile index de26847..97d79c1 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ test: -e AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" \ -e AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" \ -w /go/src/entrypoint \ - golang:latest go test -v + golang:latest go test -v -cover test_container: docker run --rm -ti \ @@ -13,7 +13,7 @@ test_container: -e AWS_REGION="us-west-2" \ -e AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" \ -e AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" \ - -e ENTRYPOINT_S3_PATH="mschurenko-test/fixtures/vars.yml" \ + -e ENTRYPOINT_VARS_FILE="/go/src/entrypoint/fixtures/vars-no-secret.yml" \ -e ENTRYPOINT_TEMPLATES="test1.conf.tmpl,test2.conf.tmpl" \ -w /go/src/entrypoint \ golang:latest ./test.sh @@ -24,4 +24,4 @@ build: test test_container -w /go/src/entrypoint \ golang:latest go install -PHONY: test test_container build \ No newline at end of file +PHONY: test test_container build diff --git a/README.md b/README.md index 75d2cf1..0f693bf 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ type tmplCtx struct { ``` Which can be referenced like: -```gohtml +```gotemplate value of MY_VAR is {{ .EnvVars["MY_VAR"] }} ``` @@ -39,8 +39,7 @@ funcMap = map[string]interface{}{ In addition to the above github.com/Masterminds/sprig are included. ## Dealing with empty values -By default `entrypoint` sets `template.Option` to `[]string{"missing=error"}`. This can be changed by setting `ENTRYPOINT_TMPL_OPTION`. - +By default `entrypoint` sets `template.Option` to `"missing=error"`. This can be changed by setting `ENTRYPOINT_TMPL_OPTION`. ## Special Environment Variales The following environment variables are specfic to `entrypoint` and will not be passed into your container: @@ -51,3 +50,28 @@ ENTRYPOINT_TMPL_OPTION ``` ## Testing templates + +## Examples +# `Dockerfile` +```dockerfile +... +# entrypoint +RUN curl -L https://github.com/mschurenko/entrypoint/releases/download/0.1.4/entrypoint \ + -o /entrypoint && chmod +x /entrypoint + +# templates +WORKDIR /conf +COPY tmpl1.conf.tmpl . +COPY tmpl2.conf.tmpl . + +ENTRYPOINT ["/entrypoint"] +``` + +# Template +set `ENV` and `APP` environment variables and refer to them in your template: +```gotemplate +{{- $env := .EnvVars.ENV -}} +{{- $app := .EnvVars.APP -}} +{{- $vars := index (index .Vars $env) $app -}} +http_port: {{ $vars.http_port }} +``` diff --git a/fixtures/test1.conf.tmpl b/fixtures/test1.conf.tmpl index 3aeaed1..fb207ef 100644 --- a/fixtures/test1.conf.tmpl +++ b/fixtures/test1.conf.tmpl @@ -1,2 +1,2 @@ test1 conf -my secret is {{ getSecret "/mschurenko/entrypoint/test_secret" }} +aws region: {{ getRegion }} diff --git a/fixtures/test2.conf.tmpl b/fixtures/test2.conf.tmpl index 96f1b6a..58785bf 100644 --- a/fixtures/test2.conf.tmpl +++ b/fixtures/test2.conf.tmpl @@ -1,2 +1,2 @@ test2 conf -my secret is {{ getSecret "/mschurenko/entrypoint/test_secret" }} +aws region: {{ getRegion }} diff --git a/fixtures/vars-no-secret.yml b/fixtures/vars-no-secret.yml new file mode 100644 index 0000000..4d07d4b --- /dev/null +++ b/fixtures/vars-no-secret.yml @@ -0,0 +1,9 @@ +--- +production: + web: + db: prod-db1 + cache: prod-cache1 +staging: + web: + db: stage-db1 + cache: stage-cache1 diff --git a/fixtures/vars.yml b/fixtures/vars.yml index 38eb47f..3146a8b 100644 --- a/fixtures/vars.yml +++ b/fixtures/vars.yml @@ -2,8 +2,9 @@ production: web: db: prod-db1 + password: {{ getSecret "/mschurenko/entrypoint/test_secret" }} cache: prod-cache1 staging: web: db: stage-db1 - cache: stage-cache1 \ No newline at end of file + cache: stage-cache1 diff --git a/funcs.go b/funcs.go index 0920108..51c5558 100644 --- a/funcs.go +++ b/funcs.go @@ -24,6 +24,7 @@ var funcMap map[string]interface{} var templateOptions = []string{} const tmplExt string = ".tmpl" +const s3Prefix string = "s3://" func init() { r := getRegion() @@ -66,12 +67,12 @@ func getRegion() string { client := &http.Client{Timeout: 5 * time.Second} r, err := client.Get("http://169.254.169.254/latest/meta-data/placement/availability-zone") if err != nil { - log.Fatalf("could not connect to ECS metadata: %v\n", err) + log.Fatalf("getRegion: could not connect to ECS metadata: %v\n", err) } bs, err := ioutil.ReadAll(r.Body) - return string(bs[0 : len(bs)-1]) + return string(bs[:len(bs)-1]) } func stripExt(f string) string { @@ -86,17 +87,16 @@ func getSecret(name string) string { output, err := svc.GetSecretValue(input) if err != nil { - log.Fatal(err) + log.Fatalf("getSecret: %v", err) } return *output.SecretString } func getNumCPU() float64 { - f := "/proc/cpuinfo" - bs, err := ioutil.ReadFile(f) + bs, err := ioutil.ReadFile("/proc/cpuinfo") if err != nil { - log.Fatal(err) + log.Fatalf("getNumCPU: %v", err) } lines := strings.Split(string(bs), "\n") @@ -120,7 +120,7 @@ func getNumCPU() float64 { func getNameServers() []string { bs, err := ioutil.ReadFile("/etc/resolv.conf") if err != nil { - log.Fatal(err) + log.Fatalf("getNameServers: %v", err) } var ns []string @@ -139,13 +139,42 @@ func getNameServers() []string { func getHostname() string { s, err := os.Hostname() if err != nil { - log.Fatal(err) + log.Fatalf("getHostname: %v", err) } return s } -func getVarsFromS3(s3Path string) map[string]interface{} { +func getVarsFromFile(file string) map[string]interface{} { + var s string + + if strings.HasPrefix(file, s3Prefix) { + s = getFileFromS3(file) + } else { + // assume this is a local file + bs, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("getVarsFromFile: %v", err) + } + + s = string(bs) + } + + // context is nil for vars file + y := renderStr("vars", s, nil) + + v := make(map[string]interface{}) + err := yaml.Unmarshal([]byte(y), &v) + if err != nil { + log.Fatalf("getVarsFromFile: %v", err) + } + + return v +} + +func getFileFromS3(file string) string { + s3Path := strings.Split(file, s3Prefix)[1] + xs := strings.Split(s3Path, "/") var filtered []string @@ -156,7 +185,7 @@ func getVarsFromS3(s3Path string) map[string]interface{} { } if len(filtered) < 2 { - log.Fatalf("%v is not a valid path", s3Path) + log.Fatalf("getFileFromS3: %v is not a valid path", s3Path) } bucket := filtered[0] path := strings.Join(filtered[1:], "/") @@ -170,21 +199,14 @@ func getVarsFromS3(s3Path string) map[string]interface{} { o, err := svc.GetObject(input) if err != nil { - log.Fatal(err) + log.Fatalf("getFileFromS3: %v", err) } bs, err := ioutil.ReadAll(o.Body) if err != nil { - log.Fatal(err) + log.Fatalf("getFileFromS3: %v", err) } - - d := make(map[string]interface{}) - err = yaml.Unmarshal(bs, &d) - if err != nil { - log.Fatal(err) - } - - return d + return string(bs) } func renderTmpl(tmplFile string, ctx interface{}) { @@ -194,11 +216,11 @@ func renderTmpl(tmplFile string, ctx interface{}) { f, err := os.Create(newFile) if err != nil { - log.Fatal(err) + log.Fatalf("renderTmpl: %v", err) } err = t.Execute(f, ctx) if err != nil { - log.Fatal(err) + log.Fatalf("renderTmpl: %v", err) } } @@ -208,7 +230,7 @@ func renderStr(name, tmpl string, ctx interface{}) string { var b bytes.Buffer err := t.Execute(&b, ctx) if err != nil { - log.Fatal(err) + log.Fatalf("renderStr: %v", err) } return b.String() diff --git a/funcs_test.go b/funcs_test.go index b327dcc..6f043ff 100644 --- a/funcs_test.go +++ b/funcs_test.go @@ -157,25 +157,22 @@ func TestRenderStr(t *testing.T) { } +func TestGetVarsFromFileLocal(t *testing.T) { + f := "./fixtures/vars.yml" + getVarsFromFile(f) +} + +func TestGetVarsFromFileS3(t *testing.T) { + getVarsFromFile(s3Prefix + s3Bucket + "/" + s3Key) +} + func TestRenderTmpl(t *testing.T) { + vars := getVarsFromFile(s3Prefix + s3Bucket + "/" + s3Key) ctx := tmplCtx{ EnvVars: map[string]string{ "MY_ENV": "production", }, - Vars: map[string]interface{}{ - "production": map[string]map[string]string{ - "web": map[string]string{ - "db": "prod-db1", - "cache": "prod-cache1", - }, - }, - "staging": map[string]map[string]string{ - "web": map[string]string{ - "db": "stage-db1", - "cache": "stage-cache1", - }, - }, - }, + Vars: vars, } tmplName := "test.conf.tmpl" @@ -186,6 +183,7 @@ func TestRenderTmpl(t *testing.T) { production web cache is prod-cache1 value of /mschurenko/entrypoint/test_secret is mysecret aws region is us-west-2 + value of production.web.password is mysecret ` tmplStr := ` @@ -194,6 +192,7 @@ func TestRenderTmpl(t *testing.T) { production web cache is {{ (index .Vars .EnvVars.MY_ENV).web.cache }} value of /mschurenko/entrypoint/test_secret is {{ getSecret "/mschurenko/entrypoint/test_secret" }} aws region is {{ getRegion }} + value of production.web.password is {{ .Vars.production.web.password }} ` if err := ioutil.WriteFile(tmplName, []byte(tmplStr), 0644); err != nil { @@ -216,7 +215,3 @@ func TestRenderTmpl(t *testing.T) { } } - -func TestGetVarsFromS3(t *testing.T) { - getVarsFromS3(s3Bucket + "/" + s3Key) -} diff --git a/main.go b/main.go index ecd2dd7..e8875b9 100644 --- a/main.go +++ b/main.go @@ -19,13 +19,12 @@ type tmplCtx struct { func main() { usage := fmt.Sprintf("Usage: %v cmd [argN...]", os.Args[0]) - fmt.Println(os.Args) - if len(os.Args) < 2 { log.Fatal(usage) } execArgs := os.Args[1:] + log.Println("entrypoint arguments:", strings.Join(execArgs, " ")) cmdPath, err := exec.LookPath(execArgs[0]) if err != nil { @@ -39,14 +38,20 @@ func main() { } vars := make(map[string]interface{}) - if os.Getenv("ENTRYPOINT_S3_PATH") != "" { - vars = getVarsFromS3(os.Getenv("ENTRYPOINT_S3_PATH")) + if os.Getenv("ENTRYPOINT_VARS_FILE") != "" { + vars = getVarsFromFile(os.Getenv("ENTRYPOINT_VARS_FILE")) } + var containerVars []string var templates []string // parse ENV vars for k, v := range envVars { + + if !strings.HasPrefix(k, "ENTRYPOINT_") { + containerVars = append(containerVars, k+"="+v) + } + matched, _ := regexp.Match(`^{{.*}}$`, []byte(v)) if matched { var rendered string @@ -82,9 +87,7 @@ func main() { } - fmt.Println(execArgs) - - err = syscall.Exec(cmdPath, execArgs, os.Environ()) + err = syscall.Exec(cmdPath, execArgs, containerVars) if err != nil { log.Fatal(err) } diff --git a/test.sh b/test.sh index 35f8008..4599abb 100755 --- a/test.sh +++ b/test.sh @@ -6,4 +6,5 @@ go install -race cd fixtures -entrypoint cat test1.conf test2.conf \ No newline at end of file +entrypoint cat test1.conf test2.conf +# entrypoint env