From c20fbfccf680e67f30e712f9292775c0b1dad850 Mon Sep 17 00:00:00 2001 From: Alex Quick Date: Sat, 22 Apr 2017 15:58:03 -0400 Subject: [PATCH] [config] support for env bundles --- plugins/build-env/pre-build-buildpack | 35 +++++----------- plugins/config/src/configenv/configenv.go | 40 ++++++++++++++++--- plugins/config/src/configenv/parsing.go | 2 +- .../config/src/subcommands/export/export.go | 17 +++++++- 4 files changed, 62 insertions(+), 32 deletions(-) diff --git a/plugins/build-env/pre-build-buildpack b/plugins/build-env/pre-build-buildpack index 57d32917d..33863fbbf 100755 --- a/plugins/build-env/pre-build-buildpack +++ b/plugins/build-env/pre-build-buildpack @@ -6,36 +6,23 @@ source "$PLUGIN_AVAILABLE_PATH/config/functions" build_env_pre_build_buildpack() { declare desc="build-env pre-build-buildpack plugin trigger" local trigger="build_env_pre_build_buildpack" - local APP="$1"; local IMAGE=$(get_app_image_name "$APP"); local BUILD_ENV="" + local APP="$1"; local IMAGE=$(get_app_image_name "$APP"); verify_app_name "$APP" [[ -z $(config_get --global CURL_CONNECT_TIMEOUT) ]] && config_set --global CURL_CONNECT_TIMEOUT=90 [[ -z $(config_get --global CURL_TIMEOUT) ]] && config_set --global CURL_TIMEOUT=60 - if [[ -n $(config_export global) ]]; then - local BUILD_ENV+=$'\n' - local BUILD_ENV+=$(config_export global) - local BUILD_ENV+=$'\n' - fi - if [[ -n $(config_export app "$APP") ]]; then - local BUILD_ENV+=$'\n' - local BUILD_ENV+=$(config_export app "$APP") - local BUILD_ENV+=$'\n' - fi + dokku_log_info1 "Adding BUILD_ENV to build environment..." + # create build env files for use in buildpacks like this: + # https://github.com/niteoweb/heroku-buildpack-buildout/blob/5879fa3418f7d8e079f1aa5816ba1adde73f4948/bin/compile#L34 + local id=$(config_export app "$APP" --merged --bundle | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /tmp/env; cat | tar -x -C /tmp/env") + test "$(docker wait "$id")" -eq 0 + docker commit "$id" "$IMAGE" > /dev/null - if [[ ! -z "$BUILD_ENV" ]]; then - dokku_log_info1 "Adding BUILD_ENV to build environment..." - # create build env files for use in buildpacks like this: - # https://github.com/niteoweb/heroku-buildpack-buildout/blob/5879fa3418f7d8e079f1aa5816ba1adde73f4948/bin/compile#L34 - local id=$(echo $BUILD_ENV | sed -e 's@export @@g' -e 's@\\n@ @g' | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /tmp/env; for ENV_VAR in $(cat); do echo \$ENV_VAR | cut -d'=' -f2- > /tmp/env/\$(echo \$ENV_VAR | cut -d'=' -f1); done " ) - test "$(docker wait "$id")" -eq 0 - docker commit "$id" "$IMAGE" > /dev/null - - # create build env for 'old style' buildpacks and dokku plugins - local id=$(echo -e "$BUILD_ENV" | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "cat >> /app/.env") - test "$(docker wait "$id")" -eq 0 - docker commit "$id" "$IMAGE" > /dev/null - fi + # create build env for 'old style' buildpacks and dokku plugins + local id=$(config_export app "$APP" --envfile --merged --escape-newlines | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "cat >> /app/.env") + test "$(docker wait "$id")" -eq 0 + docker commit "$id" "$IMAGE" > /dev/null } build_env_pre_build_buildpack "$@" diff --git a/plugins/config/src/configenv/configenv.go b/plugins/config/src/configenv/configenv.go index 070ea0007..db6c79ecb 100644 --- a/plugins/config/src/configenv/configenv.go +++ b/plugins/config/src/configenv/configenv.go @@ -3,19 +3,23 @@ package configenv import ( "errors" "fmt" + "io" "os" "path/filepath" "sort" "strings" + "archive/tar" + common "github.com/dokku/dokku/plugins/common" ) //Env is a representation for global or app environment type Env struct { - name string - env map[string]string - filename string + name string + env map[string]string + filename string + EscapeNewlines bool } func (e *Env) String() string { @@ -38,7 +42,11 @@ func (e *Env) StringWithPrefixAndSeparator(prefix string, separator string) stri keys := e.Keys() entries := make([]string, len(keys)) for i, k := range keys { - entries[i] = fmt.Sprintf("%s%s='%s'", prefix, k, SingleQuoteEscape(e.env[k])) + v := SingleQuoteEscape(e.env[k]) + if e.EscapeNewlines { + v = strings.Replace(v, "\n", "'$'\\n''", -1) + } + entries[i] = fmt.Sprintf("%s%s='%s'", prefix, k, v) } return strings.Join(entries, separator) } @@ -48,7 +56,29 @@ func SingleQuoteEscape(value string) string { // so that 'esc'apped' -> 'esc'\'' return strings.Replace(value, "'", "'\\''", -1) } -//NewFromTarget creates an env from the given target. Tareget is either "--global" or an app name +//ExportBundle writes a tarfile of the environmnet to the given io.Writer. +// for every environment variable there is a file with the variable's key +// with its content set to the variable's value +func (e *Env) ExportBundle(dest io.Writer) error { + tarfile := tar.NewWriter(dest) + defer tarfile.Close() + + for _, k := range e.Keys() { + val, _ := e.Get(k) + valbin := []byte(val) + + header := &tar.Header{ + Name: k, + Mode: 0600, + Size: int64(len(valbin)), + } + tarfile.WriteHeader(header) + tarfile.Write(valbin) + } + return nil +} + +//NewFromTarget creates an env from the given target. Target is either "--global" or an app name func NewFromTarget(target string) (*Env, error) { if target == "--global" { return LoadGlobal() diff --git a/plugins/config/src/configenv/parsing.go b/plugins/config/src/configenv/parsing.go index 3b30f6d9c..1c8feda37 100644 --- a/plugins/config/src/configenv/parsing.go +++ b/plugins/config/src/configenv/parsing.go @@ -36,7 +36,7 @@ func parseEnvFromReader(name string, filename string, reader io.Reader) (*Env, e StateKey = iota StateValue = iota ) - env := &Env{name, map[string]string{}, filename} + env := &Env{name, map[string]string{}, filename, false} var buffer bytes.Buffer var state = StateKey var quoted = false diff --git a/plugins/config/src/subcommands/export/export.go b/plugins/config/src/subcommands/export/export.go index d28a9b2f4..1a75d51c8 100644 --- a/plugins/config/src/subcommands/export/export.go +++ b/plugins/config/src/subcommands/export/export.go @@ -12,13 +12,16 @@ import ( // print the environment to stdout func main() { const defaultPrefix = "export " + const defaultSeparator = "\n" args := flag.NewFlagSet("config:export", flag.ExitOnError) global := args.Bool("global", false, "--global: use the global environment") merged := args.Bool("merged", false, "--merged: merge app environment and global environment") keys := args.Bool("keys", false, "--keys: export keys only") + bundle := args.Bool("bundle", false, "--bundle: export as tar bundle") envfile := args.Bool("envfile", false, "--envfile: export as envfile rather than bash exports (--prefix='')") - prefix := args.String("prefix", "export ", "--prefix: prefix") - separator := args.String("separator", "\n", "--separator: separator") + prefix := args.String("prefix", defaultPrefix, "--prefix: prefix") + separator := args.String("separator", defaultSeparator, "--separator: separator") + escapeNewlines := args.Bool("escape-newlines", false, "--escape-newlines: replace literal newlines with $'\n'") args.Parse(os.Args[2:]) appName := args.Arg(0) @@ -57,6 +60,16 @@ func main() { return } + if *escapeNewlines { + env.EscapeNewlines = true + } + + if *bundle { + if *prefix != defaultPrefix || *separator != defaultSeparator || *envfile { + common.LogFail("--bundle cannot be given with --envfile, --prefix, or --separator") + } + env.ExportBundle(os.Stdout) + } if *envfile { if *prefix != defaultPrefix { common.LogFail("--prefix and --envfile cannot both be given")