Skip to content

Commit

Permalink
Merge pull request #37 from wayofdev/feat/cache
Browse files Browse the repository at this point in the history
  • Loading branch information
lotyp authored Jan 8, 2024
2 parents d5692a9 + 978fbda commit 71b2850
Show file tree
Hide file tree
Showing 3 changed files with 273 additions and 0 deletions.
151 changes: 151 additions & 0 deletions actions/s3-cache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Custom S3 cache

This action allows caching dependencies and saving them in an AWS S3 bucket to reuse in other jobs and workflows to improve workflow execution time.


## Inputs

* `cache_action`

Specify what to do with the cache: save to an s3 bucket or restore from the s3 bucket into `cache_path`.

- Type: string
- Required
- Possible values: save, restore

* `cache_path`

Absolute or relative path to a folder with cache. When cache_action is **save** the path itself will not be saved, only the contents of the directory (including all subdirectories). When cache_action is **restore** all folders in `cache_path` will be created first and cache will be restored from the S3 bucket into this folder.

- Type: string
- Required
- Default: .

* `s3_bucket_name`

AWS S3 bucket name which will be used to save cache to and restore it from.

- Type: string
- Required

* `cache_key`

A cache key which is used only to save cache to S3 bucket

- Type: string
- Required only when `cache_action` is **save**

* `restore_keys`

An ordered list of keys to use for restoring cache from the s3 bucket

- Type: list of strings
- Required only when `cache_action` is **restore**

You can specify multiple keys by putting each key on its own line:
```yaml
restore_keys: |-
${{ runner.os }}-cache-${{ hashfiles('**/.package-lock.json') }}
${{ runner.os }}-cache
```
The first matching key will be restored.
## Environment Variables
- `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`(Required) - credential with access to provided AWS S3 bucket name
- `AWS_REGION`(Required) - AWS region.

## Example Cache Workflow

### Save Cache

```yaml
name: Create cache
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Create cache
uses: wayofdev/gh-actions/actions/s3-cache@v1
with:
cache_action: save
cache_path: ${GITHUB_WORKSPACE}/.cache
s3_bucket_name: my_s3_bucket
cache_key: ${{ runner.os }}-cache-${{ hashfiles('**/.package-lock.json') }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
```

### Restore Cache

```yaml
name: Restore cache
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Create cache
uses: wayofdev/gh-actions/actions/s3-cache@v1
with:
cache_action: restore
cache_path: ${GITHUB_WORKSPACE}/.cache
s3_bucket_name: my_s3_bucket
restore_keys: |
${{ runner.os }}-cache-${{ hashfiles('**/.package-lock.json') }}
${{ runner.os }}-cache
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
```

### Creating a Cache Key
A cache key can include any of the contexts, functions, literals, and operators supported by GitHub Actions.

For example, using the `hashFiles` function allows you to create a new cache when dependencies change. The `hashFiles` function is specific to GitHub Actions.

```yaml
cache_key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
```

Additionally, you can use arbitrary command output in a cache key, such as a date or software version:


```yaml
# http://man7.org/linux/man-pages/man1/date.1.html
- name: Get Date
id: get-date
run: |
echo "date=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_OUTPUT
shell: bash
- uses: wayofdev/gh-actions/actions/s3-cache@v1
with:
cache_action: save
cache_path: ${GITHUB_WORKSPACE}/.cache
s3_bucket_name: my_s3_bucket
cache_key: ${{ runner.os }}-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/lockfiles') }}
```

See [GitHub Contexts and Expressions](https://docs.github.com/en/actions/learn-github-actions/contexts#github-context) for more cache key examples.

## Limitations

This action has not been tested on self-hosted runners or when running inside a container.

34 changes: 34 additions & 0 deletions actions/s3-cache/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: 'Custom S3 cache'
description: 'Save and restore cache artifacts from AWS s3 bucket'
author: Alina Freydina

inputs:
cache_action:
description: "An action to do with cache: save or restore"
required: true
cache_path:
description: Absolute or relative path where cache will be restored to or saved from
required: true
default: .
s3_bucket_name:
description: AWS S3 bucket name to save cache to or restore cache from
required: true
cache_key:
description: A cache key wich used only to save cache to s3 bucket
required: false
restore_keys:
description: 'An ordered list of keys to use for restoring cache from s3 bucket'
required: false

runs:
using: "composite"
steps:
- name: Run action script
run: $GITHUB_ACTION_PATH/cache.sh
shell: bash
env:
INPUT_CACHE_ACTION: "${{ inputs.cache_action }}"
INPUT_CACHE_PATH: "${{ inputs.cache_path }}"
INPUT_S3_BUCKET_NAME: "${{ inputs.s3_bucket_name }}"
INPUT_CACHE_KEY: "${{ inputs.cache_key }}"
INPUT_RESTORE_KEYS: "${{ inputs.restore_keys }}"
88 changes: 88 additions & 0 deletions actions/s3-cache/cache.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bash

set -euo pipefail

function save_cache() {

if [[ $(aws s3 ls s3://${S3_BUCKET}/${CACHE_KEY}/ --region $AWS_REGION | head) ]]; then
echo "Cache is already existed for key: ${CACHE_KEY}"
else
echo "Saving cache for key ${CACHE_KEY}"

tmp_dir="$(mktemp -d)"
(cd $CACHE_PATH && tar czf "${tmp_dir}/archive.tgz" ./*)
size="$(ls -lh "${tmp_dir}/archive.tgz" | cut -d ' ' -f 5 )"

aws s3 cp "${tmp_dir}/archive.tgz" "s3://${S3_BUCKET}/${CACHE_KEY}/archive.tgz" --region $AWS_REGION > /dev/null
copy_exit_code=$?
rm -rf "${tmp_dir}"
echo "Cache size: ${size}"

if [[ "${copy_exit_code}" == 0 ]]; then
echo "Cache saved successfully for key: ${CACHE_KEY}"
fi
fi
}

function restore_cache() {

for key in ${RESTORE_KEYS}; do
if [[ $(aws s3 ls s3://${S3_BUCKET}/ --region $AWS_REGION | grep $key | head) ]]; then
k=$(aws s3 ls s3://${S3_BUCKET}/ --region $AWS_REGION | grep $key | head -n 1 | awk '{print $2}')
tmp_dir="$(mktemp -d)"
mkdir -p $CACHE_PATH

aws s3 cp s3://${S3_BUCKET}/${k//\//}/archive.tgz $tmp_dir/archive.tgz --region $AWS_REGION > /dev/null
tar xzf "${tmp_dir}/archive.tgz" -C $CACHE_PATH

echo "Restoring cache for key ${key}"
du -sm ${CACHE_PATH}/*
exit 0
else
echo "Cache with key $key not found."
fi
done
}

# Check if all necessary variables are set

if [[ -z "$INPUT_CACHE_ACTION" && -z "$INPUT_S3_BUCKET_NAME" ]]; then
echo "::error::Required inputs are missing: cache_action, s3_bucket_name and either cache_key (if cache_action is save) or restore_keys (if cache_action is restore) must be set."
exit 1

fi

if [[ "$INPUT_CACHE_ACTION" != 'save' ]] && [[ "$INPUT_CACHE_ACTION" != 'restore' ]]; then
echo "::error::Incorrect cache_action. Must be 'save' or 'restore'."
exit 1
fi

if [[ "$INPUT_CACHE_ACTION" == "save" && -z "$INPUT_CACHE_KEY" ]]; then
echo "::error::Required inputs are missing: cache_action, s3_bucket_name and either cache_key (if cache_action is save) or restore_keys (if cache_action is restore) must be set."
exit 1
fi

if [[ "$INPUT_CACHE_ACTION" == "restore" && -z "$INPUT_RESTORE_KEYS" ]]; then
echo "::error::Required inputs are missing: cache_action, s3_bucket_name and either cache_key (if cache_action is save) or restore_keys (if cache_action is restore) must be set."
exit 1
fi

if [[ ! -v AWS_ACCESS_KEY_ID || ! -v AWS_SECRET_ACCESS_KEY || ! -v AWS_REGION ]]; then
echo "::error::AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION must be set"
exit 1
fi

# Main logic

if [[ -v INPUT_CACHE_PATH ]]; then
CACHE_PATH=$INPUT_CACHE_PATH
fi
S3_BUCKET=$INPUT_S3_BUCKET_NAME

if [[ "$INPUT_CACHE_ACTION" == "save" ]]; then
CACHE_KEY=$INPUT_CACHE_KEY
save_cache
else
RESTORE_KEYS=$INPUT_RESTORE_KEYS
restore_cache
fi

0 comments on commit 71b2850

Please sign in to comment.