├── Dockerfile ├── README.md ├── LICENSE ├── cacher.sh └── DOCS.md /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.4 2 | MAINTAINER Michael de Wit 3 | 4 | COPY cacher.sh /usr/local/ 5 | RUN mkdir /cache && apk add --no-cache bash rsync findutils && chmod 755 /usr/local/cacher.sh 6 | VOLUME /cache 7 | 8 | ENTRYPOINT ["/usr/local/cacher.sh"] 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drone-volume-cache 2 | [![drone-volume-cache on Docker Hub](https://img.shields.io/docker/automated/drillster/drone-volume-cache.svg)](https://hub.docker.com/r/drillster/drone-volume-cache/) 3 | 4 | This is a pure Bash [Drone](https://github.com/drone/drone) 0.5+ plugin to cache files and/or folders to a locally mounted volume. 5 | 6 | For more information on how to use the plugin, please take a look at [the docs](https://github.com/Drillster/drone-volume-cache/blob/master/DOCS.md). 7 | 8 | ## Docker 9 | Build the docker image by running: 10 | 11 | ```bash 12 | docker build --rm=true -t drillster/drone-volume-cache . 13 | ``` 14 | 15 | ## Usage 16 | Execute from the working directory: 17 | 18 | ```bash 19 | docker run --rm \ 20 | -e PLUGIN_REBUILD=true \ 21 | -e PLUGIN_MOUNT="./node_modules" \ 22 | -e DRONE_REPO_OWNER="foo" \ 23 | -e DRONE_REPO_NAME="bar" \ 24 | -e DRONE_JOB_NUMBER=0 \ 25 | -v $(pwd):$(pwd) \ 26 | -v /tmp/cache:/cache \ 27 | -w $(pwd) \ 28 | drillster/drone-volume-cache 29 | ``` 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Drillster BV 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cacher.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ -z "$PLUGIN_MOUNT" ]; then 5 | echo "Specify folders to cache in the mount property! Plugin won't do anything!" 6 | exit 0 7 | fi 8 | 9 | if [[ $DRONE_COMMIT_MESSAGE == *"[NO CACHE]"* ]]; then 10 | echo "Found [NO CACHE] in commit message, skipping cache restore and rebuild!" 11 | exit 0 12 | fi 13 | 14 | CACHE_PATH="$DRONE_REPO_OWNER/$DRONE_REPO_NAME/$DRONE_JOB_NUMBER" 15 | if [[ -n "$PLUGIN_CACHE_KEY" ]]; then 16 | function join_by { local IFS="$1"; shift; echo "$*"; } 17 | IFS=','; read -ra CACHE_PATH_VARS <<< "$PLUGIN_CACHE_KEY" 18 | CACHE_PATH_VALUES=() 19 | for env_var in "${CACHE_PATH_VARS[@]}"; do 20 | env_var_value="${!env_var}" 21 | 22 | if [[ -z "$env_var_value" ]]; then 23 | echo "Warning! Environment variable '${env_var}' does not contain a value, it will be ignored!" 24 | else 25 | CACHE_PATH_VALUES+=("${env_var_value}") 26 | fi 27 | done 28 | CACHE_PATH=$(join_by / "${CACHE_PATH_VALUES[@]}") 29 | fi 30 | 31 | if [[ -e ".cache_key" ]]; then 32 | echo "Found a .cache_key file to be used as the cache path!" 33 | CACHE_PATH=$(cut -c-$(getconf NAME_MAX /) .cache_key | head -n 1) 34 | 35 | if [[ -n "$PLUGIN_CACHE_KEY_DISABLE_SANITIZE" && "$PLUGIN_CACHE_KEY_DISABLE_SANITIZE" == "true" ]]; then 36 | echo "Warning! .cache_key will be used as-is. Sanitization is your responsibility to make it filename friendly!" 37 | else 38 | CACHE_PATH=$(echo "$CACHE_PATH" | md5sum | cut -d ' ' -f 1) 39 | fi 40 | fi 41 | 42 | IFS=','; read -ra SOURCES <<< "$PLUGIN_MOUNT" 43 | if [[ -n "$PLUGIN_REBUILD" && "$PLUGIN_REBUILD" == "true" ]]; then 44 | # Create cache 45 | for source in "${SOURCES[@]}"; do 46 | if [ -d "$source" ]; then 47 | echo "Rebuilding cache for folder $source..." 48 | mkdir -p "/cache/$CACHE_PATH/$source" && \ 49 | rsync -aHA --delete "$source/" "/cache/$CACHE_PATH/$source" 50 | elif [ -f "$source" ]; then 51 | echo "Rebuilding cache for file $source..." 52 | source_dir=$(dirname $source) 53 | mkdir -p "/cache/$CACHE_PATH/$source_dir" && \ 54 | rsync -aHA --delete "$source" "/cache/$CACHE_PATH/$source_dir/" 55 | else 56 | echo "$source does not exist, removing from cached folder..." 57 | rm -rf "/cache/$CACHE_PATH/$source" 58 | fi 59 | done 60 | elif [[ -n "$PLUGIN_RESTORE" && "$PLUGIN_RESTORE" == "true" ]]; then 61 | # Clear existing cache if asked in commit message 62 | if [[ $DRONE_COMMIT_MESSAGE == *"[CLEAR CACHE]"* ]]; then 63 | if [ -d "/cache/$CACHE_PATH" ]; then 64 | echo "Found [CLEAR CACHE] in commit message, clearing cache..." 65 | rm -rf "/cache/$CACHE_PATH" 66 | exit 0 67 | fi 68 | fi 69 | # Remove files older than TTL 70 | if [[ -n "$PLUGIN_TTL" && "$PLUGIN_TTL" > "0" ]]; then 71 | if [[ $PLUGIN_TTL =~ ^[0-9]+$ ]]; then 72 | if [ -d "/cache/$CACHE_PATH" ]; then 73 | echo "Removing files and (empty) folders older than $PLUGIN_TTL days..." 74 | find "/cache/$CACHE_PATH" -type f -ctime +$PLUGIN_TTL -delete 75 | find "/cache/$CACHE_PATH" -type d -ctime +$PLUGIN_TTL -empty -delete 76 | fi 77 | else 78 | echo "Invalid value for ttl, please enter a positive integer. Plugin will ignore ttl." 79 | fi 80 | fi 81 | # Restore from cache 82 | for source in "${SOURCES[@]}"; do 83 | if [ -d "/cache/$CACHE_PATH/$source" ]; then 84 | echo "Restoring cache for folder $source..." 85 | mkdir -p "$source" && \ 86 | rsync -aHA --delete "/cache/$CACHE_PATH/$source/" "$source" 87 | elif [ -f "/cache/$CACHE_PATH/$source" ]; then 88 | echo "Restoring cache for file $source..." 89 | source_dir=$(dirname $source) 90 | mkdir -p "$source_dir" && \ 91 | rsync -aHA --delete "/cache/$CACHE_PATH/$source" "$source_dir/" 92 | else 93 | echo "No cache for $source" 94 | fi 95 | done 96 | else 97 | echo "No restore or rebuild flag specified, plugin won't do anything!" 98 | fi 99 | -------------------------------------------------------------------------------- /DOCS.md: -------------------------------------------------------------------------------- 1 | Use the Volume-cache plugin to preserve files and directories between builds. 2 | Because it uses a mounted volume, it requires repositories using it to enable the *"Trusted"* flag. 3 | 4 | ## Config 5 | The following parameters are used to configure the plugin: 6 | - **restore** - instruct plugin to restore cache, can be `true` or `false` 7 | - **rebuild** - instruct plugin to rebuild cache, can be `true` or `false` 8 | - **mount** - list of folders or files to cache 9 | - **ttl** - maximum cache lifetime in days 10 | - **cache_key** - list of environment variables to use for constructing the cache path 11 | 12 | ## Examples 13 | ```yaml 14 | pipeline: 15 | restore-cache: 16 | image: drillster/drone-volume-cache 17 | restore: true 18 | mount: 19 | - ./node_modules 20 | # Mount the cache volume, needs "Trusted" 21 | volumes: 22 | - /tmp/cache:/cache 23 | 24 | build: 25 | image: node 26 | commands: 27 | - npm install 28 | 29 | rebuild-cache: 30 | image: drillster/drone-volume-cache 31 | rebuild: true 32 | mount: 33 | - ./node_modules 34 | # Mount the cache volume, needs "Trusted" 35 | volumes: 36 | - /tmp/cache:/cache 37 | ``` 38 | 39 | The example above illustrates a typical Node.js project Drone configuration. It caches the `./node_modules` directory to a mounted volume on the host system: `/tmp/cache`. This prevents `npm` from downloading and installing the dependencies for every build. 40 | 41 | ## Using cache lifetime 42 | It is possible to limit the lifetime of cached files and folders. 43 | 44 | ```yaml 45 | pipeline: 46 | restore-cache: 47 | image: drillster/drone-volume-cache 48 | restore: true 49 | mount: 50 | - ./node_modules 51 | # Mount the cache volume, needs "Trusted" 52 | volumes: 53 | - /tmp/cache:/cache 54 | ttl: 7 55 | ``` 56 | 57 | The example above shows a situation where cached items older than 7 days will not be restored (they will be removed instead). Only the restore step needs the `ttl` parameter. 58 | 59 | ## Using the cache_key option 60 | By default, this plugin uses the repo owner, repo name, and job number to construct a cache key. Say the repository owner is `foo`, the repository name is `bar`, and the job number is `1`, 61 | the cache key (folder) the plugin will use for the build will be `foo/bar/1`. 62 | If this is not optimal for your specific situation, it is possible to modify the cache key. For example, let's say that your project differs quite a bit between different branches, you may want to include the branch somewhere in the cache key: 63 | 64 | ```yaml 65 | pipeline: 66 | restore-cache: 67 | image: drillster/drone-volume-cache 68 | restore: true 69 | mount: 70 | - ./node_modules 71 | # Mount the cache volume, needs "Trusted" 72 | volumes: 73 | - /tmp/cache:/cache 74 | cache_key: [ DRONE_REPO_OWNER, DRONE_REPO_NAME, DRONE_BRANCH, DRONE_JOB_NUMBER ] 75 | ``` 76 | 77 | This would lead to the following cache key if a build is triggered for the master branch (and the rest of the situation is the same as the example illustrated above): `foo/bar/master/1`. 78 | 79 | In theory you could even use this to share cache between different projects, but extreme caution is advised doing so. 80 | 81 | ## Using the `.cache_key` file 82 | Instead of using a `cache_key` option in your Drone yaml file, you may generate a cache key and write it to the `.cache_key` file in the root of your repo. If a `.cache_key` file is present, the first [NAME_MAX](https://www.gnu.org/software/libc/manual/html_node/Limits-for-Files.html) characters of the first line of this file is read and (by default) its MD5 is used as the actual key for sanitization purposes. You may disable MD5'ing by setting `cache_key_disable_sanitize: true`. 83 | 84 | This feature allows you to generate the cache key dynamically (e.g. by calculating the checksum of your `package.json`). Note that if a `.cache_key` file is present, it overrides your settings of `cache_key`. 85 | 86 | ```yaml 87 | pipeline: 88 | build: 89 | image: ubuntu 90 | commands: 91 | - [...] 92 | - echo -n "my custom cache key" > .cache_key 93 | restore-cache: 94 | image: drillster/drone-volume-cache 95 | restore: true 96 | mount: 97 | - ./node_modules 98 | # Mount the cache volume, needs "Trusted" 99 | volumes: 100 | - /tmp/cache:/cache 101 | ``` 102 | 103 | ## Clearing Cache 104 | Should you want to clear the cache for a project, you can do so by including `[CLEAR CACHE]` in the commit message. The entire cache folder for the project will be cleared before it is restored. The rebuilding of cache will proceed as normal. 105 | 106 | ## Skipping Cache 107 | If you want to run a build without using cache, put `[NO CACHE]` in the commit message. Both the restoring and rebuilding of cache will be skipped. Your cache will remain intact. --------------------------------------------------------------------------------