btdt: “been there, done that”
btdt is a tool for flexible caching of files in CI pipelines.
By being a simple CLI program, it is agnostic to the CI platform and can be integrated into various pipelines.
Cached data can either be stored locally in the filesystem, or remotely using the server component btdt-server.
This tool is still under active development and not feature complete yet. See below for details.
Example: caching node_modules
CACHE_KEY=node-modules-$(btdt hash package-lock.json)
btdt restore --cache path/to/cache --keys $CACHE_KEY node_modules
if [ $? -ne 0 ]; then
npm ci
btdt store --cache path/to/cache --keys $CACHE_KEY node_modules
fi
Examples for specific CI platforms can be found in the documentation (see below).
Documentation
The main user guide and documentation is located at https://jgosmann.github.io/btdt. The API documentation is found on docs.rs
Motivation
I was annoyed that there isn’t a good (self-hosted) caching solution for Jenkins and Tekton, similar to the cache for GitHub Actions. Also, it seemed that it shouldn’t be that hard to implement a caching solution. So I put my money where my mouth is. In particular, I didn’t see any reason why caching should be tied to a specific CI platform by implementing it as a plugin for that platform. To me, it seems, that this problem is solvable with a CLI tool that can be integrated into any pipeline.
Regarding Jenkins, I know of two caching plugins and I have my quarrels with both of them:
- Job Cacher will delete the complete cache once it reaches the maximum size. This is inefficient and I prefer to delete least recently used caches until the limit is met. The plugin also does not share the cache between different build jobs which severely limits its usefulness in certain scenarios. We also had some other constraints that made it impossible to use this plugin, but a CLI tool could have been integrated easily.
- jenkins-pipeline-cache-plugin requires S3 API compatible storage, which excludes some other use cases. It also doesn’t seem to provide a way to limit the cache size.
Regarding Tekton, a few suggestions are made in their blog. But I don’t think those are perfect:
- Caching layers in a container registry imposes a dependency order on your cached layers. This might be fine, if invalidating one cache/layer, implies that all subsequent caches/layers are also invalidated. But if you have two orthogonal caches, you must decide for an order, and always have one case where one of the caches might be invalidated needlessly.
- Caching on a persistent disk does not, as far as I understand, allow for multiple caches to be stored without
additional tooling. If you have builds that require different caches, you might end up overwriting caches constantly.
btdtprovides tooling to have multiple separate caches.
State of development
A basic version of btdt that can be used in most scenarios is working.
Missing features concern primarily covenience and ease of use:
- Compression of the cache (to reduce the amount of data transferred).
- Hashing multiple files in a stable way for the cache key.
- A templating system for cache keys, such that
btdt hashdoesn’t need to be called, but a cache key in the form ofcache-key-${hashFiles('**/package-lock.json')}can be used directly. - Potentially, using S3 compatible APIs as storage backend.
Installation
There are multiple ways to install btdt. Choose the method from below that best fits your needs.
Note
Currently, only Unix (Linux, macOS) systems are supported.
Pre-compiled binaries
You can download pre-compiled binaries from the GitHub Releases
page (look for btdt-cli releases).
The archive contains a single executable binary btdt.
You might want to place it in your $PATH for easy access.
For Linux, you have the choice between a version depending on a suffciently recent glibc version
(-gnu suffix) and a statically linked version (-musl suffix) that should work on most systems,
even without glibc.
Docker images
Docker images are available on Docker Hub.
This allows to directly run btdt without installing it on your system:
docker run jgosmann/btdt btdt --help
However, you will have to mount the directories with the cache and the files to cache into the container.
This can be done with the --mount or --volume option.
The images use Semantic Versioning tags. For example, jgosmann/btdt:0.1
refers to the latest v0.1.x image.
Build from source using Rust
If you have Rust installed, you can build btdt from source using cargo:
cargo install btdt
Getting Started
This guide will show you the general steps of using btdt to cache files, in particular,
installed dependencies of a package manager such as npm.
If you are looking to integrate btdt into a CI pipeline,
you might want to also check out the CI-specific integration guides.
Determining cache keys
Usually, you will have a file that completely specifies the dependencies and their versions of your project.
For example, in the JavaScript/NPM ecosystem, this is the package-lock.json file.
As long as it doesn’t change, the installed dependencies will be the same and could be cached.
Thus, the primary cache key should be based on this file.
We can use the btdt hash command to generate a cache key from the file:
CACHE_KEY=cache-key-$(btdt hash package-lock.json)
This calculates a cryptographic hash over the file contents and appends it to the string cache-key-.
The result could look something like cache-key-f3dd7a501dd93486194e752557585a1996846b9a6df16e76f104e81192b0039f.
If the package-lock.json file changes, the hash will change as well and the cache key will be different.
Trying to restore the cache
Before we try to install the dependencies, e.g. with npm ci, we can try to restore the cache instead:
btdt restore --cache path/to/cache --keys $CACHE_KEY node_modules
RESTORE_EXIT_CODE=$?
npm will install the dependencies into node_modules, so we are using this as the target directory.
Furthermore, we will store the exit code because it comes in handy in the next step. It will be 0 if the cache was
restored successfully from the first given key, and non-zero otherwise. (Use the --success-rc-on-any-key flag to
return a zero exit code no matter the key that was used to restore the cache.)
Installing dependencies and storing the cache
If the cache could not be restored, we will install the dependencies with npm ci, and then store the installed
dependencies in the cache:
if [ $RESTORE_EXIT_CODE -ne 0 ]; then
npm ci # Install dependencies
btdt store --cache path/to/cache --keys $CACHE_KEY node_modules
fi
Using multiple cache keys
You can specify multiple cache keys. This allows to have a fallback mechanism. The cache keys will be tried in order during the restore operation and allow you to use a cache which might not contain the exact dependencies required, but could still speed up the installation if most of them are contained.
With npm the usage of multiple cache keys could look like this:
CACHE_KEY=cache-key-$(btdt hash package-lock.json)
btdt restore --cache path/to/cache --keys "$CACHE_KEY,fallback" node_modules
RESTORE_EXIT_CODE=$?
npm ci
if [ $RESTORE_EXIT_CODE -ne 0 ]; then
btdt store --cache path/to/cache --keys $CACHE_KEY,fallback node_modules
fi
This will store the latest cached dependencies also under the key fallback. This cache entry will be used, if no more
specific cache enry is found.
Using a remote cache
Instead of using a local filesystem path for the cache, you can also use a remote cache server.
For this, you need to have a btdt-server instance running somewhere.
You can then specify the URL of the cache endpoint and the file with the authentication token,
to use the remote cache:
# restore
btdt restore \
--cache https://btdt-server.example.com:8707/api/caches/cache-name \
--auth-token-file ./auth-token \
--keys "$CACHE_KEY" \
node_modules
# store
btdt store \
--cache https://btdt-server.example.com:8707/api/caches/cache-name \
--auth-token-file ./auth-token \
--keys "$CACHE_KEY" \
node_modules
Note that the file with the authentication token must be readable only by the user running btdt.
Cleanup
To prevent a local cache from growing indefinitely, you might want to clean up old cache entries from time to time, for example to only keep cache entries accessed within the last seven days and limit the cache size to at most 10 GiB:
btdt clean --cache path/to/cache --max-age 7d --max-size 10GiB
CLI reference
The general syntax of the btdt command-line interface is:
btdt <SUBCOMMAND> [OPTIONS]
The available subcommands are described below.
clean
btdt clean [OPTIONS] --cache <CACHE>
Clean old entries from a local cache.
-c <CACHE>, --cache <CACHE>
Path to the local cache directory to clean.
--max-age <DURATION>
Maximum age (e.g. 7d, 48h, 1d 12h) of cache entries to keep. Cache entries not accessed within this duration will
be
deleted.
--max-size <SIZE>
Maximum total size (e.g. 10GiB, 500MB) of the cache. If the cache exceeds this size, the least recently used caches
are deleted until the total size is below this limit.
hash
btdt hash <PATH>
Calculate the hash of a file and print it to the standard output.
help
btdt help [SUBCOMMAND]
Print general help or help for a specific subcommand.
restore
btdt restore [OPTIONS] --keys <KEYS> --cache <CACHE> <DESTINATION_DIR>
Restore cached data from a cache to <DESTINATION_DIR>.
The first key that exists in the cache is used.
The result of the cache lookup is indicated via the exit code:
0: Data was successfully restored from the cache using the primary (first listed) key.1: General error.2: Error in the command invocation or arguments.3: Files were restored, but not using the primary key (i.e., a fallback key was used).4: No cache entry found for any of the specified keys.
-a <AUTH_TOKEN_FILE>, --auth-token-file <AUTH_TOKEN_FILE>
Path to a file containing the authentication token for accessing a remote cache.
Important
The file must be readable only by the user running
btdt, i.e., it should have permissions0600.
-c <CACHE>, --cache <CACHE>
Path to the cache (local directory or remote cache URL).
-k <KEYS>, --keys <KEYS>
Comma-separated list of cache keys to try in order. This argument may also be repeated to specify multiple keys.
--root-cert <ROOT_CERT>
Root certificates (in PEM format) to trust for remote caches (instead of system’s root certificates).
--success-rc-on-any-key
Exit with success status code if any key is found in the cache.
Usually, the success exit code is only returned if the primary key (i.e. first listed key) is found in the cache, and 3 is returned if another key was restored.
store
btdt store [OPTIONS] --keys <KEYS> --cache <CACHE> <SOURCE_DIR>
Store data from <SOURCE_DIR> into the cache under the specified keys.
-a <AUTH_TOKEN_FILE>, --auth-token-file <AUTH_TOKEN_FILE>
Path to a file containing the authentication token for accessing a remote cache.
Important
The file must be readable only by the user running
btdt, i.e., it should have permissions0600.
-c <CACHE>, --cache <CACHE>
Path to the cache (local directory or remote cache URL).
-k <KEYS>, --keys <KEYS>
Comma-separated list of cache keys to store the cached data under. This argument may also be repeated to specify multiple keys.
--root-cert <ROOT_CERT>
Root certificates (in PEM format) to trust for remote caches (instead of system’s root certificates).
Deployment
The btdt-server is a server application that allows to store and retrieve cached files with btdt on this server.
Installation
There are multiple ways to install btdt-server. Choose the method from below that best fits your needs.
Note
Currently, only Unix (Linux, macOS) systems are supported.
Pre-compiled binaries
You can download pre-compiled binaries from the GitHub Releases
page (look for btdt-server releases).
The archive contains a single executable binary btdt-server that will start the server.
Docker images
Docker images are available on Docker Hub.
The images use Semantic Versioning tags. For example, jgosmann/btdt-server:0.1 refers to the latest v0.1.x image.
When running the container, you likely want to mount a few files or directories into the container:
- The directory where caches are stored, so that they are persisted across container restarts.
- The configuration file (default:
/config.toml). - The file with the private key for authentication (default:
/auth_private_key.pem). - If using TLS, the PKCS#12 file with the TLS certificate and private key.
Note that, if you are using TLS, you will have to override the default health check command with:
HEALTHCHECK CMD ["btdt-server", "health-check", "https://localhost:8707/api/health"]
It is important to use the CMD form of the HEALTHCHECK instruction here, and not the CMD-SHELL form.
The latter would require a shell to be present in the container, which is not the case for the btdt-server
distroless image.
Build from source using Rust
If you have Rust installed, you can build btdt-server from source using cargo:
cargo install btdt-server
Configuration
The btdt-server is configured via a TOML configuration file at /etc/btdt-server/config.toml
(or /config.toml within the provided container image).
A minimal configuration should configure at least the location of the authorization private key and one cache location:
auth_private_key = "/etc/btdt-server/auth_private_key.pem"
[caches]
default = { type = 'Filesystem', path = '/var/lib/btdt/default' }
Note that the auth_private_key can alternatively also be set through the environment variable BTDT_AUTH_PRIVATE_KEY.
The URL to this cache location for btdt would be http(s)://<btdt-server-host>:8707/api/caches/default.
See the configuration documentation for more details on the available configuration options.
Authorization
Authorization is done with Eclipse Biscuit tokens. This avoids the need to manage user accounts on the server. The server only needs to have a private key to verify the tokens. If the private key is not present at server startup, a new key will be generated.
To generate the private key and derive authentication tokens, use the biscuit command line tool that can be
installed with
cargo install biscuit-cli
A new private key can be generated with
biscuit keypair --key-output-format pem --only-private-key | head -c -1 > auth_private_key.pem
To generate a new authorization token with all permissions and validity of 90 days, use
biscuit generate \
--private-key-file auth_private_key.pem \
--private-key-format pem \
--add-ttl 90d - <<EOF
EOF
See the authorization documentation for more details.
Enabling TLS
To enable TLS, simply provide a PKCS#12 file with the TLS certificate and private key.
Configure the path to this file with the tls_keystore option in the configuration file
or the BTDT_TLS_KEYSTORE environment variable. a password for the keystore can be provided
with the tls_keystore_password option or the BTDT_TLS_KEYSTORE_PASSWORD environment variable.
Note that, if you are using the btdt-server container image, you will have to adapt the health check command to:
HEALTHCHECK CMD ["btdt-server", "health-check", "https://localhost:8707/api/health"]
It is important to use the CMD form of the HEALTHCHECK instruction here, and not the CMD-SHELL form.
The latter would require a shell to be present in the container, which is not the case for the btdt-server
distroless image.
Configuration
The btdt-server is configured via a TOML configuration file at /etc/btdt-server/config.toml
(or /config.toml within the provided container image).
This location can be overridden by setting the BTDT_SERVER_CONFIG_FILE environment variable.
Some configuration options can also be set via environment variables, as described below.
General options
auth_private_key
- Type: string
- Default:
'' - Environment variable:
BTDT_AUTH_PRIVATE_KEY
Path to the Eclipse Biscuit private key file used to verify authorization tokens.
If the file does not exist at server startup, a new private key will be generated and saved to this location.
Note that the private key’s permission must be restricted to 0600.
bind_addrs
- Type: array of strings
- Default:
['0.0.0.0:8707'] - Environment variable:
BTDT_BIND_ADDRS
List of addresses and ports the server should bind to.
enable_api_docs
- Type: boolean
- Default:
true - Environment variable:
BTDT_ENABLE_API_DOCS
If set to true, the server will provide API documentation at /docs.
tls_keystore
- Type: string
- Default:
'' - Environment variable:
BTDT_TLS_KEYSTORE
Path to a PKCS#12 keystore file containing the TLS certificate and private key. If not set, the server will run without TLS.
tls_keystore_password
- Type: string
- Default:
'' - Environment variable:
BTDT_TLS_KEYSTORE_PASSWORD
Password for the PKCS#12 keystore file.
Cleanup options
These options have to be set in the [cleanup] table.
They configure automatic cleanup of cached data to prevent indefinite growth of the cache storage.
cache_expiration
- Type: duration string
- Default:
'7days' - Environment variable:
BTDT_CLEANUP__CACHE_EXPIRATION
Caches that have not been accessed for this duration will be deleted during cleanup runs.
interval
- Type: duration string
- Default:
'10min' - Environment variable:
BTDT_CLEANUP__INTERVAL
Interval between cleanup runs.
max_cache_size
- Type: size string
- Default:
'50GiB' - Environment variable:
BTDT_CLEANUP__MAX_CACHE_SIZE
Maximum total size of each cache. Note that a cache might temporarily exceed this size between cleanup runs.
Configuring caches
Caches are configured in the [caches] table.
Each table entry defines a cache with a unique name.
The cache base URL is derived form this name and of the form
http(s)://<btdt-server-host>:8707/api/caches/<cache-name>.
Filesystem cache
A filesystem cache stores cached data in a directory on the local filesystem.
[caches]
my_cache = { type = 'Filesystem', path = '/var/lib/btdt/my_cache' }
In-memory cache
An in-memory cache stores cached data in memory. This cache is not persistent and will be lost when the server restarts.
Important
In-memory caches are intended for testing or development purposes. They are not optimized for speed and might not perform well for production workloads.
[caches]
my_cache = { type = 'InMemory' }
Example configuration
bind_addrs = ['127.0.0.1:8707', '[::1]:8707']
enable_api_docs = false
tls_keystore = 'path/certificate.p12'
tls_keystore_password = 'password'
auth_private_key = 'path/private-key'
[cleanup]
interval = '5min'
cache_expiration = '14days'
max_cache_size = '100GiB'
[caches]
in_memory = { type = 'InMemory' }
filesystem = { type = 'Filesystem', path = '/var/lib/btdt-server/cache' }
Authorization
Authorization is done with Eclipse Biscuit tokens. This avoids the need to manage user accounts on the server. The server only needs to have a private key to verify the tokens.
To manage private keys and authorization tokens, use the biscuit command line tool that can be
installed with
cargo install biscuit-cli
Private authorization key
The server uses a private key to verify the validity of authorization tokens.
The location of the private key can be configured via the auth_private_key configuration option
(or the BTDT_AUTH_PRIVATE_KEY environment variable). Note that the private key’s permission must be restricted to
0600.
If the private key is not present at server startup, a new key will be generated.
To manually generate a new private key, use
biscuit keypair --key-output-format pem --only-private-key | head -c -1 > auth_private_key.pem
(The biscuit tool outputs a trailing newline that prevents reading it to generate tokens, so we remove it with
head -c -1.)
Generating authorization tokens
To generate a new authorization token with all permissions and validity of 90 days, use
biscuit generate \
--private-key-file auth_private_key.pem \
--private-key-format pem \
--add-ttl 90d - <<EOF
EOF
This will output a new authorization token that can be used with btdt to access the server.
It is also possible to restrict the permissions of the token by adding additional Datalog statements. For this, the following facts can be used:
cache($cache_id)declares the cache that is being accessed.operation($op)declares the operation being performed. Valid operations aregetandput.
For example, to generate a token that only allows reading from the cache my-cache, use
biscuit generate \
--private-key-file auth_private_key.pem \
--private-key-format pem \
--add-ttl 90d - <<EOF
check if operation("get");
check if cache("my-cache");
EOF
Attenuating authorization tokens
Authorization tokens can be attenuated to further restrict their permissions or validity period.
To attenuate a token, use the biscuit attenuate command.
For example, to attenuate an existing token to only allow accessing the cache my-cache for another 30 days, use
biscuit attenuate \
--block 'check if cache("my-cache");' \
--add-ttl 30d \
file-with-token
Revoking authorization tokens
Revocation is not yet implemented. If you have to revoke a token, you will have to rotate the private key and issue new tokens.
Overview
While the Getting Started guide provides a high-level overview of how to use btdt,
this section contains guides for specific CI systems:
If you have experience with a CI system that is not covered here, please consider contributing a guide.
Jenkins
This guide explains how to use btdt in a Jenkins pipeline.
You can either use a cache local to the Jenkins agents (but it won’t be shared between different agents),
or a remote cache using a btdt-server instance.
Agent-local cache
For an agent-local setup, you only need to install the btdt CLI on your Jenkins agents.
Then you can integrate btdt commands into your pipeline script as described
in Getting Started.
Ensure that the cache path you use is writable by the Jenkins agent user.
An example Jenkinsfile could look like this:
#!groovy
pipeline {
agent any
stages {
stage('Install dependencies') {
steps {
sh '''
CACHE_PATH=/var/lib/btdt/cache # Path to cache on the Jenkins agent
CACHE_KEY=cache-key-$(btdt hash package-lock.json)
btdt restore --cache "$CACHE_PATH" --keys $CACHE_KEY node_modules
RESTORE_EXIT_CODE=$?
if [ $RESTORE_EXIT_CODE -ne 0 ]; then
npm ci # Install dependencies
btdt store --cache "$CACHE_PATH" --keys $CACHE_KEY node_modules
fi
'''
}
}
stage('Run tests') {
steps {
// ...
}
}
}
}
Cleanup
To prevent the local cache from growing indefinitely, you can set up a periodic cleanup job in Jenkins.
For each agent, create a new Jenkins pipeline job that runs periodically (e.g., daily or weekly)
and add a stage that runs the btdt cleanup command on the cache path.
For example:
#!groovy
pipeline {
agent { node { label 'your-agent-label' } }
triggers {
cron('H H * * 0') // Run weekly on Sundays
}
stages {
stage('Cleanup cache') {
steps {
sh '''
CACHE_PATH=/var/lib/btdt/cache # Path to cache on the Jenkins agent
btdt cleanup --cache "$CACHE_PATH" --max-age 7d --max-size 10G
'''
}
}
}
}
Remote cache with btdt-server
The setup with a remote cache is very similar to the agent-local setup.
You also need to install the btdt CLI on your Jenkins agents.
In addition, you need to have a btdt-server instance running somewhere
and an authorization token generated.
Provide the authorization token as “secret file” credential in Jenkins (“Manage Jenkins” → “Credentials”).
Then you can integrate btdt commands into your pipeline script as described
in Getting Started,
using the remote cache URL and an authentication token file provided from the Jenkins credential.
An example Jenkinsfile could look like this:
#!groovy
pipeline {
agent any
stages {
stage('Install dependencies') {
steps {
script {
withCredentials([
file(credentialsId: 'btdt-auth-token', variable: 'BTDT_AUTH_TOKEN_FILE'),
]) {
sh '''
CACHE_URL=http://btdt.example.com:8707/api/caches/my-cache
CACHE_KEY=cache-key-$(btdt hash package-lock.json)
btdt restore \\
--cache "$CACHE_URL" \\
--auth-token-file "$BTDT_AUTH_TOKEN_FILE" \\
--keys $CACHE_KEY \\
node_modules
RESTORE_EXIT_CODE=$?
if [ $RESTORE_EXIT_CODE -ne 0 ]; then
npm ci # Install dependencies
btdt store \\
--cache "$CACHE_URL" \\
--auth-token-file "$BTDT_AUTH_TOKEN_FILE" \\
--keys $CACHE_KEY \\
node_modules
fi
'''
}
}
}
stage('Run tests') {
steps {
// ...
}
}
}
}
}
Tekton
This guide explains how to use btdt in a Tekton pipeline.
It will use the Docker images, so that no changes to the images of your tasks are necessary.
Of course, you could also install btdt within the respective task images which might simplify the integration a bit.
Provide a Persistent Volume Claim as workspace to the pipeline run
To use btdt in a Tekton pipeline, you need to provide a Persistent Volume Claim (PVC) for the cache.
This PVC should be provided as actual persistentVolumeClaim in the PipelineRun, not volumeClaimTemplate.
Otherwise, you will have a fresh volume on each pipeline run, making the cache useless.
An example PipelineRun could look like this:
# PipelineRun template, e.g. as part of your trigger
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: my-pipeline-run-$(uid)
spec:
params:
# ...
pipelineRef:
name: my-tekton-pipeline
workspaces:
- name: cache
persistentVolumeClaim:
claimName: my-tekton-cache
With the default Tekton settings (at time of writing), only a single PVC can be mounted into a task. Thus, if you are already using a PVC for you task (likely to check out your source code repository), you will have to also store the cache on this PVC.
Alternatively, you can disable the affinity assistant to be
able to mount multiple PVCs into a task. Run kubectl edit configmap feature-flags to edit the configuration.
In the following, we assume this second setup. If you are using a single PVC, you will have to adjust the paths
accordingly.
Provide the cache workspace to the task
To be able to use the cache in a task, the cache workspace needs to be provided:
# pipeline.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: my-tekton-pipeline
spec:
workspaces:
- name: cache
tasks:
- name: run-tests
taskRef:
name: run-tests
kind: Task
workspaces:
- name: git-sources
workspace: git-sources
- name: cache
workspace: cache
Use the cache in a task
You must declare the cache workspace in the task, so that it can be used by the individual steps:
# task_run-tests.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: run-tests
spec:
steps:
# ...
workspaces:
- name: git-sources
description: Provides the workspace with the cloned repository.
- name: cache
description: Provides the btdt cache.
Restore the cache
Now you can add a step to restore the cache at the beginning of the task.
Here, we try to restore a node_modules directory:
# task_run-tests.yaml
spec:
# ...
steps:
- name: restore-cache
image: jgosmann/btdt:0.1
workingDir: $(workspaces.cache.path)
onError: continue
script: |
#!/bin/sh
CACHE_KEY=node-modules-$(btdt hash package-lock.json)
echo "Cache key: $CACHE_KEY"
btdt restore --cache $(workspaces.cache.path) --keys $CACHE_KEY node_modules
Install dependencies only on cache miss
Depending on what you are caching, you might want to run some commands only a cache miss to generate the files that would be cached. For example, to install NPM dependencies only if the cache could not be restored:
# task_run-tests.yaml
spec:
# ...
steps:
# try restore
- name: run-tests
image: node
workingDir: $(workspaces.git-sources.path)
script: |
#!/bin/sh
if [ $(cat $(steps.step-restore-cache.exitCode.path)) -eq 0 ]; then
echo "Cache restore succeeded, skipping npm ci"
else
npm ci
fi
# run tests, build, etc.
Note, if you are using fallback keys, you would always want to run
npm ci to ensure that the dependencies are installed correctly.
Store the cache
For the cache to provide a benefit, we need to fill it if a cache miss occurred. This requires an additional step
after the files to cache have been generated (e.g. by running npm ci):
# task_run-tests.yaml
spec:
# ...
steps:
# try restore
# install dependencies/generate files to cache
- name: store-cache
image: jgosmann/btdt:0.1
workingDir: $(workspaces.git-sources.path)
script: |
#!/bin/sh
if [ $(cat $(steps.step-restore-cache.exitCode.path)) -eq 0 ]; then
echo "Cache restore succeeded, skipping cache store"
exit 0
fi
CACHE_KEY=node-modules-$(btdt hash package-lock.json)
echo "Cache key: $CACHE_KEY"
btdt store --cache $(workspaces.cache.path) --keys $CACHE_KEY node_modules
Example of complete task
When putting all of this together, your task definition will look something like this:
# task_run-tests.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: run-tests
spec:
steps:
- name: restore-cache
image: jgosmann/btdt:0.1
workingDir: $(workspaces.git-sources.path)
onError: continue
script: |
#!/bin/sh
CACHE_KEY=node-modules-$(btdt hash package-lock.json)
echo "Cache key: $CACHE_KEY"
btdt restore --cache $(workspaces.cache.path) --keys $CACHE_KEY node_modules
- name: run-tests
image: node
workingDir: $(workspaces.git-sources.path)
script: |
#!/bin/sh
if [ $(cat $(steps.step-restore-cache.exitCode.path)) -eq 0 ]; then
echo "Cache restore succeeded, skipping npm ci"
else
npm ci
fi
# run tests etc.
- name: store-cache
image: jgosmann/btdt
workingDir: $(workspaces.git-sources.path)
script: |
#!/bin/sh
if [ $(cat $(steps.step-restore-cache.exitCode.path)) -eq 0 ]; then
echo "Cache restore succeeded, skipping cache store"
exit 0
fi
CACHE_KEY=node-modules-$(btdt hash package-lock.json)
echo "Cache key: $CACHE_KEY"
btdt store --cache $(workspaces.cache.path) --keys $CACHE_KEY node_modules
workspaces:
- name: git-sources
description: Provides the workspace with the cloned repository.
- name: cache
description: Provides btdt cache.
Cleanup
To prevent the cache from growing indefinitely, you should configure a regular cleanup:
Clean task
# task_cache-clean.yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: cache-clean
spec:
steps:
- name: cache-clean
image: jgosmann/btdt:0.1
script: |
#!/bin/sh
btdt clean --cache $(workspaces.cache.path) --max-age 7d --max-size 10GiB
workspaces:
- name: cache
description: Provides the btdt cache.
Clean pipeline
# pipeline_cache-clean.yaml
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: cache-clean-pipeline
spec:
workspaces:
- name: cache
params:
- name: runid
type: string
tasks:
- name: cache-clean
taskRef:
name: cache-clean
kind: Task
workspaces:
- name: cache
workspace: cache
Cron trigger
# trigger_cache-clean.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: cache-clean-schedule
spec:
schedule: '@hourly'
jobTemplate:
spec:
template:
spec:
containers:
- name: cache-clean-trigger
image: curlimages/curl
command: [ '/bin/sh', '-c' ]
args: [ "curl --header \"Content-Type: application/json\" --data '{}' el-cache-clean-listener.default.svc.cluster.local:8080" ]
restartPolicy: Never
---
apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
name: cache-clean-listener
spec:
triggers:
- name: cache-clean-trigger
interceptors: [ ]
template:
spec:
resourcetemplates:
- apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: cache-clean-$(uid)
spec:
pipelineRef:
name: cache-clean-pipeline
params:
- name: runid
value: $(uid)
workspaces:
- name: cache
persistentVolumeClaim:
claimName: my-tekton-cache