refactor: move herokuish app cache from the filesystem into a docker volume
This simplifies cache referencing and also paves the way for removing more from the app git directory. Refs #1558
This commit is contained in:
5
docs/appendices/0.31.0-migration-guide.md
Normal file
5
docs/appendices/0.31.0-migration-guide.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 0.30.0 Migration Guide
|
||||
|
||||
## Changes
|
||||
|
||||
- Herokuish build cache is now mounted from a docker volume - eg. `cache-node-js-app` - instead of the local filesystem. All existing app cache will be cleared upon upgrading past 0.29.0.
|
||||
@@ -18,6 +18,7 @@ Docker releases updates periodically to their engine. We recommend reading their
|
||||
|
||||
Before upgrading, check the migration guides to get comfortable with new features and prepare your deployment to be upgraded.
|
||||
|
||||
- [Upgrading to 0.31](/docs/appendices/0.31.0-migration-guide.md)
|
||||
- [Upgrading to 0.30](/docs/appendices/0.30.0-migration-guide.md)
|
||||
- [Upgrading to 0.29](/docs/appendices/0.29.0-migration-guide.md)
|
||||
- [Upgrading to 0.28](/docs/appendices/0.28.0-migration-guide.md)
|
||||
|
||||
@@ -3,6 +3,16 @@ set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_AVAILABLE_PATH/config/functions"
|
||||
|
||||
fn-builder-herokuish-ensure-cache() {
|
||||
declare APP="$1"
|
||||
|
||||
existing_cache="$("$DOCKER_BIN" volume ls --quiet --filter label=com.dokku.app-name=test --filter label=com.dokku.builder-type=herokuish)"
|
||||
if [[ "$existing_cache" != "cache-$APP" ]]; then
|
||||
"$DOCKER_BIN" volume rm "cache-$APP" >/dev/null 2>&1 || true
|
||||
"$DOCKER_BIN" volume create "--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.app-name=$APP" "--label=com.dokku.builder-type=herokuish" "cache-$APP" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
trigger-builder-herokuish-builder-build() {
|
||||
declare desc="builder-herokuish builder-build plugin trigger"
|
||||
declare trigger="builder-build"
|
||||
@@ -21,8 +31,6 @@ trigger-builder-herokuish-builder-build() {
|
||||
fi
|
||||
|
||||
local IMAGE=$(get_app_image_name "$APP")
|
||||
local DOKKU_APP_CACHE_DIR="$DOKKU_ROOT/$APP/cache"
|
||||
local DOKKU_APP_HOST_CACHE_DIR="$DOKKU_HOST_ROOT/$APP/cache"
|
||||
local DOCKER_COMMIT_LABEL_ARGS=("--change" "LABEL dokku=" "--change" "LABEL org.label-schema.schema-version=1.0" "--change" "LABEL org.label-schema.vendor=dokku" "--change" "LABEL com.dokku.image-stage=build" "--change" "LABEL com.dokku.builder-type=herokuish" "--change" "LABEL com.dokku.app-name=$APP")
|
||||
local DOCKER_RUN_LABEL_ARGS=("--label=dokku" "--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.image-stage=build" "--label=com.dokku.builder-type=herokuish" "--label=com.dokku.app-name=$APP")
|
||||
local CID TAR_CID
|
||||
@@ -49,7 +57,6 @@ trigger-builder-herokuish-builder-build() {
|
||||
"$DOCKER_BIN" container commit "${DOCKER_COMMIT_LABEL_ARGS[@]}" "$TAR_CID" "$IMAGE" >/dev/null
|
||||
DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID"
|
||||
|
||||
[[ -d $DOKKU_APP_CACHE_DIR ]] || mkdir -p "$DOKKU_APP_CACHE_DIR"
|
||||
plugn trigger pre-build-buildpack "$APP" "$SOURCECODE_WORK_DIR"
|
||||
|
||||
local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE")
|
||||
@@ -60,8 +67,8 @@ trigger-builder-herokuish-builder-build() {
|
||||
eval "ARG_ARRAY=($DOCKER_ARGS)"
|
||||
|
||||
local DOKKU_CONTAINER_EXIT_CODE=0
|
||||
if ! CID=$("$DOCKER_BIN" container create "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS -v "$DOKKU_APP_HOST_CACHE_DIR:/cache" --env=CACHE_PATH=/cache "${ARG_ARRAY[@]}" "$IMAGE" /build); then
|
||||
DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID"
|
||||
fn-builder-herokuish-ensure-cache "$APP"
|
||||
if ! CID=$("$DOCKER_BIN" container create "${DOCKER_RUN_LABEL_ARGS[@]}" $DOKKU_GLOBAL_RUN_ARGS -v "cache-$APP:/cache" --env=CACHE_PATH=/cache "${ARG_ARRAY[@]}" "$IMAGE" /build); then
|
||||
plugn trigger scheduler-register-retired "$APP" "$CID"
|
||||
dokku_log_warn "Failure during app build"
|
||||
return 1
|
||||
@@ -70,7 +77,6 @@ trigger-builder-herokuish-builder-build() {
|
||||
plugn trigger post-container-create "app" "$CID" "$APP" "build"
|
||||
"$DOCKER_BIN" container start "$CID" >/dev/null || DOKKU_CONTAINER_EXIT_CODE=$?
|
||||
if ! "$DOCKER_BIN" container attach "$CID"; then
|
||||
DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID"
|
||||
plugn trigger scheduler-register-retired "$APP" "$CID"
|
||||
dokku_log_warn "Failure during app build"
|
||||
return 1
|
||||
@@ -78,14 +84,12 @@ trigger-builder-herokuish-builder-build() {
|
||||
|
||||
DOKKU_CONTAINER_EXIT_CODE="$("$DOCKER_BIN" container wait "$CID" 2>/dev/null || echo "$DOKKU_CONTAINER_EXIT_CODE")"
|
||||
if [[ "$DOKKU_CONTAINER_EXIT_CODE" -ne 0 ]]; then
|
||||
DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID"
|
||||
plugn trigger scheduler-register-retired "$APP" "$CID"
|
||||
dokku_log_warn "Failure during app build"
|
||||
return 1
|
||||
fi
|
||||
|
||||
"$DOCKER_BIN" container commit "${DOCKER_COMMIT_LABEL_ARGS[@]}" "$CID" "$IMAGE" >/dev/null
|
||||
DOKKU_SKIP_IMAGE_CLEANUP_REGISTRATION=1 plugn trigger scheduler-register-retired "$APP" "$TAR_CID"
|
||||
plugn trigger scheduler-register-retired "$APP" "$CID"
|
||||
plugn trigger post-build-buildpack "$APP" "$SOURCECODE_WORK_DIR"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
fn-builder-herokuish-remove-old-cache() {
|
||||
for app in $(dokku_apps "false" 2>/dev/null); do
|
||||
local DOKKU_APP_CACHE_DIR="$DOKKU_ROOT/$APP/cache"
|
||||
local DOKKU_APP_HOST_CACHE_DIR="$DOKKU_HOST_ROOT/$APP/cache"
|
||||
if [[ ! -d "$DOKKU_APP_CACHE_DIR" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
"$DOCKER_BIN" container run --rm --label=dokku --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku "--label=com.dokku.app-name=$APP" -v "$DOKKU_APP_HOST_CACHE_DIR:/cache" "$DOKKU_IMAGE" "find /cache -depth -mindepth 1 -maxdepth 1 -exec rm -Rf {} ;"
|
||||
rm -rf "$DOKKU_APP_CACHE_DIR"
|
||||
done
|
||||
}
|
||||
|
||||
trigger-builder-herokuish-install() {
|
||||
declare desc="installs the builder-herokuish plugin"
|
||||
declare trigger="install"
|
||||
|
||||
fn-plugin-property-setup "builder-herokuish"
|
||||
fn-builder-herokuish-remove-old-cache
|
||||
}
|
||||
|
||||
trigger-builder-herokuish-install "$@"
|
||||
|
||||
@@ -203,7 +203,7 @@ func DockerCleanup(appName string, forceCleanup bool) error {
|
||||
containerIDs := append(exitedContainerIDs, deadContainerIDs...)
|
||||
|
||||
if len(containerIDs) > 0 {
|
||||
removeContainers(containerIDs)
|
||||
DockerRemoveContainers(containerIDs)
|
||||
}
|
||||
|
||||
// delete dangling images
|
||||
@@ -351,21 +351,18 @@ func VerifyImage(image string) bool {
|
||||
return imageCmd.Execute()
|
||||
}
|
||||
|
||||
func listContainers(status string, appName string) ([]string, error) {
|
||||
// DockerFilterContainers returns a slice of container IDs based on the passed in filters
|
||||
func DockerFilterContainers(filters []string) ([]string, error) {
|
||||
command := []string{
|
||||
DockerBin(),
|
||||
"container",
|
||||
"list",
|
||||
"--quiet",
|
||||
"--all",
|
||||
"--filter",
|
||||
fmt.Sprintf("status=%v", status),
|
||||
"--filter",
|
||||
fmt.Sprintf("label=%v", os.Getenv("DOKKU_CONTAINER_LABEL")),
|
||||
}
|
||||
|
||||
if appName != "" {
|
||||
command = append(command, []string{"--filter", fmt.Sprintf("label=com.dokku.app-name=%v", appName)}...)
|
||||
for _, filter := range filters {
|
||||
command = append(command, "--filter", filter)
|
||||
}
|
||||
|
||||
var stderr bytes.Buffer
|
||||
@@ -382,6 +379,18 @@ func listContainers(status string, appName string) ([]string, error) {
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func listContainers(status string, appName string) ([]string, error) {
|
||||
filters := []string{
|
||||
fmt.Sprintf("status=%v", status),
|
||||
fmt.Sprintf("label=%v", os.Getenv("DOKKU_CONTAINER_LABEL")),
|
||||
}
|
||||
|
||||
if appName != "" {
|
||||
filters = append(filters, fmt.Sprintf("label=com.dokku.app-name=%v", appName))
|
||||
}
|
||||
return DockerFilterContainers(filters)
|
||||
}
|
||||
|
||||
func pruneUnusedImages(appName string) {
|
||||
command := []string{
|
||||
DockerBin(),
|
||||
@@ -400,7 +409,8 @@ func pruneUnusedImages(appName string) {
|
||||
pruneCmd.Execute()
|
||||
}
|
||||
|
||||
func removeContainers(containerIDs []string) {
|
||||
// DockerRemoveContainers will call `docker container rm` on the specified containers
|
||||
func DockerRemoveContainers(containerIDs []string) {
|
||||
command := []string{
|
||||
DockerBin(),
|
||||
"container",
|
||||
|
||||
@@ -18,6 +18,7 @@ type ShellCmd struct {
|
||||
CommandString string
|
||||
Args []string
|
||||
ShowOutput bool
|
||||
ExitError *exec.ExitError
|
||||
}
|
||||
|
||||
// NewShellCmd returns a new ShellCmd struct
|
||||
@@ -57,6 +58,10 @@ func (sc *ShellCmd) Execute() bool {
|
||||
sc.setup()
|
||||
|
||||
if err := sc.Command.Run(); err != nil {
|
||||
exitError, ok := err.(*exec.ExitError)
|
||||
if ok {
|
||||
sc.ExitError = exitError
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
3
plugins/repo/.gitignore
vendored
3
plugins/repo/.gitignore
vendored
@@ -2,5 +2,4 @@
|
||||
/subcommands/*
|
||||
/triggers/*
|
||||
/triggers
|
||||
/pre-delete
|
||||
/post-stack-set
|
||||
/post-*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SUBCOMMANDS = subcommands/gc subcommands/purge-cache
|
||||
TRIGGERS = triggers/post-stack-set triggers/pre-delete
|
||||
TRIGGERS = triggers/post-stack-set triggers/post-delete
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = repo
|
||||
|
||||
|
||||
@@ -6,28 +6,45 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/dokku/dokku/plugins/config"
|
||||
)
|
||||
|
||||
// PurgeCacheFailed wraps error to allow returning the correct exit code
|
||||
type PurgeCacheFailed struct {
|
||||
exitCode int
|
||||
}
|
||||
|
||||
// ExitCode returns an exit code to use in case this error bubbles
|
||||
// up into an os.Exit() call
|
||||
func (err *PurgeCacheFailed) ExitCode() int {
|
||||
return err.exitCode
|
||||
}
|
||||
|
||||
// Error returns a standard non-existent app error
|
||||
func (err *PurgeCacheFailed) Error() string {
|
||||
return fmt.Sprintf("failed to purge cache, exit code %d", err.exitCode)
|
||||
}
|
||||
|
||||
// PurgeCache deletes the contents of the build cache stored in the repository
|
||||
func PurgeCache(appName string) error {
|
||||
cacheDir := strings.Join([]string{common.AppRoot(appName), "cache"}, "/")
|
||||
cacheHostDir := strings.Join([]string{common.AppHostRoot(appName), "cache"}, "/")
|
||||
dokkuGlobalRunArgs := common.MustGetEnv("DOKKU_GLOBAL_RUN_ARGS")
|
||||
image := config.GetWithDefault(appName, "DOKKU_IMAGE", os.Getenv("DOKKU_IMAGE"))
|
||||
if info, _ := os.Stat(cacheDir); info != nil && info.IsDir() {
|
||||
dockerLabelArgs := fmt.Sprintf("--label=com.dokku.app-name=%s", appName)
|
||||
purgeCacheCmd := common.NewShellCmd(strings.Join([]string{
|
||||
common.DockerBin(),
|
||||
"container",
|
||||
"run", "--rm", dockerLabelArgs, dokkuGlobalRunArgs,
|
||||
"-v", strings.Join([]string{cacheHostDir, ":/cache"}, ""), image,
|
||||
`find /cache -depth -mindepth 1 -maxdepth 1 -exec rm -Rf {} ;`}, " "))
|
||||
purgeCacheCmd.Execute()
|
||||
err := os.MkdirAll(cacheDir, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
containerIDs, _ := common.DockerFilterContainers([]string{
|
||||
fmt.Sprintf("label=com.dokku.app-name=%v", appName),
|
||||
"label=com.dokku.image-stage=build",
|
||||
})
|
||||
if len(containerIDs) > 0 {
|
||||
common.DockerRemoveContainers(containerIDs)
|
||||
}
|
||||
purgeCacheCmd := common.NewShellCmd(strings.Join([]string{
|
||||
common.DockerBin(),
|
||||
"volume",
|
||||
"rm", "-f", fmt.Sprintf("cache-%s", appName)}, " "))
|
||||
purgeCacheCmd.ShowOutput = false
|
||||
purgeCacheCmd.Command.Stderr = os.Stderr
|
||||
if !purgeCacheCmd.Execute() {
|
||||
exitCode := 1
|
||||
if purgeCacheCmd.ExitError != nil {
|
||||
exitCode = purgeCacheCmd.ExitError.ExitCode()
|
||||
}
|
||||
return &PurgeCacheFailed{exitCode}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -21,7 +21,7 @@ func main() {
|
||||
case "post-stack-set":
|
||||
appName := flag.Arg(0)
|
||||
err = repo.PurgeCache(appName)
|
||||
case "pre-delete":
|
||||
case "post-delete":
|
||||
appName := flag.Arg(0)
|
||||
err = repo.PurgeCache(appName)
|
||||
default:
|
||||
|
||||
@@ -59,12 +59,16 @@ teardown() {
|
||||
}
|
||||
|
||||
@test "(build-env) buildpack deploy with Dockerfile" {
|
||||
run /bin/bash -c "dokku config:set --no-restart $TEST_APP BUILDPACK_URL='https://github.com/heroku/heroku-buildpack-nodejs'"
|
||||
run /bin/bash -c "dokku config:set --no-restart $TEST_APP BUILDPACK_URL='https://github.com/dokku/heroku-buildpack-null'"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run deploy_app python dokku@dokku.me:$TEST_APP move_dockerfile_into_place
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
deploy_app dockerfile
|
||||
run /bin/bash -c "dokku --quiet config:get $TEST_APP DOKKU_APP_TYPE"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
|
||||
@@ -4,9 +4,11 @@ load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
create_app
|
||||
}
|
||||
|
||||
teardown() {
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@@ -24,7 +26,7 @@ teardown() {
|
||||
assert_output "$help_output"
|
||||
}
|
||||
|
||||
@test "(repo) repo:gc, repo:purge-cache" {
|
||||
@test "(repo) repo:gc" {
|
||||
run deploy_app
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
@@ -34,29 +36,36 @@ teardown() {
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
}
|
||||
|
||||
run /bin/bash -c "touch $DOKKU_ROOT/$TEST_APP/cache/derp"
|
||||
@test "(repo) repo:purge-cache" {
|
||||
run deploy_app
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "find $DOKKU_ROOT/$TEST_APP/cache -type f | wc -l"
|
||||
run /bin/bash -c "docker volume ls -q --filter label=com.dokku.app-name=$TEST_APP | wc -l"
|
||||
echo "count: '$output'"
|
||||
echo "status: $status"
|
||||
assert_not_output 0
|
||||
|
||||
run /bin/bash -c "dokku repo:purge-cache $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output 1
|
||||
|
||||
run /bin/bash -c "find $DOKKU_ROOT/$TEST_APP/cache -type f | wc -l"
|
||||
echo "count: '$output'"
|
||||
echo "status: $status"
|
||||
assert_output 0
|
||||
|
||||
run destroy_app
|
||||
run /bin/bash -c "dokku repo:purge-cache $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "docker volume ls -q --filter label=com.dokku.app-name=$TEST_APP | wc -l"
|
||||
echo "count: '$output'"
|
||||
echo "status: $status"
|
||||
assert_output 0
|
||||
|
||||
run /bin/bash -c "find $DOKKU_ROOT/$TEST_APP/cache -type f | wc -l"
|
||||
echo "count: '$output'"
|
||||
echo "status: $status"
|
||||
assert_output 0
|
||||
}
|
||||
|
||||
@@ -535,6 +535,13 @@ add_postdeploy_command() {
|
||||
echo "${contents}" >"$APP_REPO_DIR/app.json"
|
||||
}
|
||||
|
||||
move_dockerfile_into_place() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
mv "$APP_REPO_DIR/alt.Dockerfile" "$APP_REPO_DIR/Dockerfile"
|
||||
}
|
||||
|
||||
add_requirements_txt() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
|
||||
Reference in New Issue
Block a user