diff --git a/README.md b/README.md index 0c9440e..000db07 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,10 @@ CMD dockerize -template /etc/nginx/nginx.tmpl:/etc/nginx/nginx.conf -stdout /var ### Command-line Options -You can specify multiple templates by passing using `-template` multiple times: +You can specify multiple templates by passing `-template` multiple times: ``` -$ dockerize -template template1.tmpl:file1.cfg -template template2.tmpl:file3 +$ dockerize -template template1.tmpl:file1.cfg -template template2.tmpl:file2 ``` @@ -68,6 +68,15 @@ $ dockerize -template template1.tmpl ``` +You can overlay files onto the container at runtime by passing `-overlay` multiple times. The argument uses a form similar to the `--volume` option of the `docker run` command: `source:dest`. Overlays are applied recursively onto the destination in a similar manner to `cp -rv`. If multiple overlays are specified, they are applied in the order in which they were listed on the command line. + +Overlays are used to replace entire sets of files with alternative content, whereas templates allow environment substitutions into a single file. The example below assumes that /tmp/overlays has already been COPY'd into the image by the Dockerfile. + +``` +$ dockerize -overlay "/tmp/overlays/_common/html:/usr/share/nginx/" \ + -overlay "/tmp/overlays/{{ .Env.DEPLOYMENT_ENV }}/html:/usr/share/nginx/" +``` + You can tail multiple files to `STDOUT` and `STDERR` by passing the options multiple times. ``` diff --git a/dockerize.go b/dockerize.go index 1670f2c..b1564c4 100644 --- a/dockerize.go +++ b/dockerize.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "os" + "os/exec" "strings" "sync" "time" @@ -18,18 +19,6 @@ import ( type sliceVar []string type hostFlagsVar []string -type Context struct { -} - -func (c *Context) Env() map[string]string { - env := make(map[string]string) - for _, i := range os.Environ() { - sep := strings.Index(i, "=") - env[i[0:sep]] = i[sep+1:] - } - return env -} - var ( buildVersion string version bool @@ -39,6 +28,7 @@ var ( templatesFlag sliceVar stdoutTailFlag sliceVar stderrTailFlag sliceVar + overlaysFlag sliceVar delimsFlag string delims []string waitFlag hostFlagsVar @@ -70,6 +60,10 @@ func (s *sliceVar) String() string { func waitForDependencies() { dependencyChan := make(chan struct{}) + if waitFlag == nil { + return + } + go func() { for _, host := range waitFlag { log.Println("Waiting for host:", host) @@ -104,7 +98,7 @@ func waitForDependencies() { } }() default: - log.Fatalf("invalid host protocol provided: %s. supported protocols are: tcp, tcp4, tcp6 and http", u.Scheme) + log.Fatalf("invalid host protocol provided: %s. supported protocols are: tcp, tcp4, tcp6, http and https", u.Scheme) } } wg.Wait() @@ -139,7 +133,9 @@ Arguments: and /var/log/nginx/error.log, waiting for a website to become available on port 8000 and start nginx.`) println(` dockerize -template nginx.tmpl:/etc/nginx/nginx.conf \ - -stdout /var/log/nginx/access.log \ + -overlay overlays/_common/html:/usr/share/nginx/ \ + -overlay overlays/{{ .Env.DEPLOYMENT_ENV }}/html:/usr/share/nginx/ \`) + println(` -stdout /var/log/nginx/access.log \ -stderr /var/log/nginx/error.log \ -wait tcp://web:8000 nginx `) @@ -152,6 +148,7 @@ func main() { flag.BoolVar(&version, "version", false, "show version") flag.BoolVar(&poll, "poll", false, "enable polling") flag.Var(&templatesFlag, "template", "Template (/template:/dest). Can be passed multiple times") + flag.Var(&overlaysFlag, "overlay", "overlay (/src:/dest). Can be passed multiple times") flag.Var(&stdoutTailFlag, "stdout", "Tails a file to stdout. Can be passed multiple times") flag.Var(&stderrTailFlag, "stderr", "Tails a file to stderr. Can be passed multiple times") flag.StringVar(&delimsFlag, "delims", "", `template tag delimiters. default "{{":"}}" `) @@ -177,6 +174,26 @@ func main() { log.Fatalf("bad delimiters argument: %s. expected \"left:right\"", delimsFlag) } } + + // Overlay files from src --> dst + for _, o := range overlaysFlag { + if strings.Contains(o, ":") { + parts := strings.Split(o, ":") + if len(parts) != 2 { + log.Fatalf("bad overlay argument: '%s'. expected \"/src:/dest\"", o) + } + src, dest := string_template_eval(parts[0]), string_template_eval(parts[1]) + + log.Printf("overlaying %s --> %s", src, dest) + + cmd := exec.Command("cp", "-rv", src, dest) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatal(err) + } + } + } + for _, t := range templatesFlag { template, dest := t, "" if strings.Contains(t, ":") { @@ -184,7 +201,7 @@ func main() { if len(parts) != 2 { log.Fatalf("bad template argument: %s. expected \"/template:/dest\"", t) } - template, dest = parts[0], parts[1] + template, dest = string_template_eval(parts[0]), string_template_eval(parts[1]) } generateFile(template, dest) } diff --git a/examples/nginx/.gitignore b/examples/nginx/.gitignore new file mode 100644 index 0000000..4367d18 --- /dev/null +++ b/examples/nginx/.gitignore @@ -0,0 +1 @@ +*.gz \ No newline at end of file diff --git a/examples/nginx/Dockerfile b/examples/nginx/Dockerfile index 38d55d9..8a63afe 100644 --- a/examples/nginx/Dockerfile +++ b/examples/nginx/Dockerfile @@ -1,20 +1,31 @@ -FROM ubuntu:14.04 +FROM nginx:1.9 MAINTAINER Jason Wilder mail@jasonwilder.com -# Install Nginx. -RUN echo "deb http://ppa.launchpad.net/nginx/stable/ubuntu trusty main" > /etc/apt/sources.list.d/nginx-stable-trusty.list -RUN echo "deb-src http://ppa.launchpad.net/nginx/stable/ubuntu trusty main" >> /etc/apt/sources.list.d/nginx-stable-trusty.list -RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C300EE8C -RUN apt-get update -RUN apt-get install -y wget nginx +# RUN wget https://github.com/markriggins/dockerize/releases/download/v0.2.0/dockerize-linux-amd64-v0.0.4.tar.gz +# RUN tar -C /usr/local/bin -xvzf dockerize-linux-amd64-v0.2.0.tar.gz -RUN wget https://github.com/jwilder/dockerize/releases/download/v0.0.4/dockerize-linux-amd64-v0.0.4.tar.gz -RUN tar -C /usr/local/bin -xvzf dockerize-linux-amd64-v0.0.4.tar.gz +COPY .dockerize_linux_amd64.tar.gz /tmp/ +RUN cd /usr/local/bin && \ + tar -xzv --strip-components=1 -f /tmp/.d*.gz dockerize_linux_amd64/dockerize && \ + rm /tmp/.d*.gz -RUN echo "daemon off;" >> /etc/nginx/nginx.conf - -ADD default.tmpl /etc/nginx/sites-available/default.tmpl +COPY default.tmpl /etc/nginx/sites-available/default.tmpl +COPY overlays /tmp/overlays EXPOSE 80 -CMD ["dockerize", "-template", "/etc/nginx/sites-available/default.tmpl:/etc/nginx/sites-available/default", "-stdout", "/var/log/nginx/access.log", "-stderr", "/var/log/nginx/error.log", "nginx"] +ENV DEPLOYMENT_ENV=staging + +# These options do not work well on virtualbox osx -- it pegs one CPU +# "-poll", \ +# "-stdout", "/var/log/nginx/access.log", \ +# "-stderr", "/var/log/nginx/error.log", \ + +CMD [ "dockerize", \ + "-template", "/etc/nginx/sites-available/default.tmpl:/etc/nginx/sites-available/default", \ + "-overlay", "/tmp/overlays/_common/html:/usr/share/nginx/", \ + "-overlay", "/tmp/overlays/{{ .Env.DEPLOYMENT_ENV }}/html:/usr/share/nginx/", \ + "-poll", \ + "-stdout", "/var/log/nginx/access.log", \ + "-stderr", "/var/log/nginx/error.log", \ + "--", "nginx", "-g", "daemon off;"] diff --git a/examples/nginx/Makefile b/examples/nginx/Makefile new file mode 100644 index 0000000..31bced3 --- /dev/null +++ b/examples/nginx/Makefile @@ -0,0 +1,18 @@ +.PHONY : build run-staging run-prod + +build: + cd ../..; goxc -os linux -arch amd64 + cp -f $(shell bash -xc 'find $(GOPATH)/bin -name dockerize_linux_amd64.tar.gz| head -1') .dockerize_linux_amd64.tar.gz + docker build -t dockerized-nginx . + rm .dockerize_linux_amd64.tar.gz + + +run-staging: + docker rm -f dockerized-nginx 2>/dev/null || true + docker run -d -p 80:80 -e DEPLOYMENT_ENV=staging --name dockerized-nginx dockerized-nginx + open http://$(shell docker-machine ip $(shell docker-machine active))/robots.txt + +run-prod: + docker rm -f dockerized-nginx 2>/dev/null || true + docker run -d -p 80:80 -e DEPLOYMENT_ENV=prod --name dockerized-nginx dockerized-nginx + open http://$(shell docker-machine ip $(shell docker-machine active))/robots.txt diff --git a/examples/nginx/overlays/_common/html/robots.txt b/examples/nginx/overlays/_common/html/robots.txt new file mode 100644 index 0000000..923702d --- /dev/null +++ b/examples/nginx/overlays/_common/html/robots.txt @@ -0,0 +1,5 @@ +# +# Common robots.txt file +# +User-agent: * +Disallow: / \ No newline at end of file diff --git a/examples/nginx/overlays/prod/html/robots.txt b/examples/nginx/overlays/prod/html/robots.txt new file mode 100644 index 0000000..496625e --- /dev/null +++ b/examples/nginx/overlays/prod/html/robots.txt @@ -0,0 +1,5 @@ +# +# Prod robots.txt file +# +User-agent: * +Allow: / \ No newline at end of file diff --git a/template.go b/template.go index 4af338d..577cfae 100644 --- a/template.go +++ b/template.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "log" "net/url" @@ -12,6 +13,18 @@ import ( "text/template" ) +type EnvContext struct { +} + +func (c *EnvContext) Env() map[string]string { + env := make(map[string]string) + for _, i := range os.Environ() { + sep := strings.Index(i, "=") + env[i[0:sep]] = i[sep+1:] + } + return env +} + func exists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { @@ -68,6 +81,30 @@ func add(arg1, arg2 int) int { return arg1 + arg2 } +// +// Execute the string_template under the EnvContext, and +// return the result as a string +// +func string_template_eval(string_template string) string { + var result bytes.Buffer + t := template.New("String Template") + + t, err := t.Parse(string_template) + if err != nil { + log.Fatalf("unable to parse template: %s", err) + } + + err = t.Execute(&result, &EnvContext{}) + if err != nil { + log.Fatalf("template error: %s\n", err) + } + + return result.String() +} + +// +// Execute the template at templatePath under the EnvContext and write it to destPath +// func generateFile(templatePath, destPath string) bool { tmpl := template.New(filepath.Base(templatePath)).Funcs(template.FuncMap{ "contains": contains, @@ -97,7 +134,7 @@ func generateFile(templatePath, destPath string) bool { defer dest.Close() } - err = tmpl.ExecuteTemplate(dest, filepath.Base(templatePath), &Context{}) + err = tmpl.ExecuteTemplate(dest, filepath.Base(templatePath), &EnvContext{}) if err != nil { log.Fatalf("template error: %s\n", err) }