From 334906ef6e4d7f1c7e85da13b96d7a12066f98d8 Mon Sep 17 00:00:00 2001 From: awalsh128 Date: Sun, 10 Oct 2021 22:11:39 -0700 Subject: [PATCH] Major revisions to everything. --- .github/workflows/main.yml | 22 -------- .github/workflows/tests.yml | 107 ++++++++++++++++++++++++++++++++++++ LICENSE | 32 ++++------- README.md | 96 +++++++++++++++++++++++++++++++- action.yml | 20 ++++--- run.sh | 105 +++++++++++++++++++++++------------ 6 files changed, 294 insertions(+), 88 deletions(-) delete mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 38657a9..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,22 +0,0 @@ -on: [push] - -jobs: - install_packages: - runs-on: ubuntu-latest - name: Test job to install and cache APT packages (i.e. rolldice, dot). - steps: - - uses: actions/checkout@v2 - - uses: actions/cached-apt-install-action@v1 - with: - packages: rolldice dot - test_packages_cache: - needs: install_packages - runs-on: ubuntu-latest - name: Test job to verify cached APT packages (i.e. rolldice, dot). - steps: - - uses: actions/checkout@v2 - - run: | - [ -d "~/cached-apt-install-packages" ] || exit 1; - [ -d "~/cached-apt-install-packages/rolldice" ] || exit 2; - [ -d "~/cached-apt-install-packages/dot" ] || exit 3; - - shell: bash \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..a35ba5c --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,107 @@ +name: Success Tests +on: [push, pull_request] + +jobs: + + install: + runs-on: ubuntu-latest + name: Install and cache. + steps: + - uses: actions/checkout@v2 + - uses: actions/cached-apt-install-action@v1 + with: + cache_key: ${{ env.GITHUB_JOB }} + packages: dot rolldice + + verify: + needs: install + runs-on: ubuntu-latest + name: Verify cached installs. + steps: + - uses: actions/checkout@v2 + - run: | + [ -d "~/cached-apt-install-packages" ] || exit 1; + [ -d "~/cached-apt-install-packages/dot" ] || exit 2; + [ -d "~/cached-apt-install-packages/rolldice" ] || exit 3; + + add_install: + needs: verify + runs-on: ubuntu-latest + name: Add another install and cache. + steps: + - uses: actions/checkout@v2 + - uses: actions/cached-apt-install-action@v1 + with: + cache_key: ${{ env.GITHUB_JOB }} + packages: dot rolldice g++ + + verify_add: + needs: add_install + runs-on: ubuntu-latest + name: Verify added and cached install. + steps: + - uses: actions/checkout@v2 + - run: | + [ -d "~/cached-apt-install-packages" ] || exit 4; + [ -d "~/cached-apt-install-packages/dot" ] || exit 5; + [ -d "~/cached-apt-install-packages/g++" ] || exit 6; + [ -d "~/cached-apt-install-packages/rolldice" ] || exit 7; + + verify_cleanup: + needs: verify_add + runs-on: ubuntu-latest + name: Verify omitted packages are cleaned up. + steps: + - uses: actions/cached-apt-install-action@v1 + with: + cache_key: ${{ env.GITHUB_JOB }}_1 + packages: dot g++ + - run: | + [ -d "~/cached-apt-install-packages" ] || exit 8; + [ -d "~/cached-apt-install-packages/dot" ] || exit 9; + [ -d "~/cached-apt-install-packages/g++" ] || exit 10; + [ -d "~/cached-apt-install-packages/rolldice" ] && exit 11; + + verify_isolation: + needs: verify_add + runs-on: ubuntu-latest + name: Verify cache state is not shared. + steps: + - uses: actions/cached-apt-install-action@v1 + with: + cache_key: ${{ env.GITHUB_JOB }}_1 + packages: show-motd + - run: | + [ -d "~/cached-apt-install-packages" ] || exit 12; + [ -d "~/cached-apt-install-packages/motd" ] || exit 13; + [ -d "~/cached-apt-install-packages/dot" ] && exit 14; + [ -d "~/cached-apt-install-packages/g++" ] && exit 15; + [ -d "~/cached-apt-install-packages/rolldice" ] && exit 16; + + no_packages: + runs-on: ubuntu-latest + name: No packages passed. + steps: + - name: Execute + uses: actions/cached-apt-install-action@v1 + with: + cache_key: + packages: dot + continue-on-error: true + - name: Verify + if: steps.execute.outcom != 'failure' + run: exit 1 + + package_not_found: + runs-on: ubuntu-latest + name: Package not found. + steps: + - name: Execute + uses: actions/cached-apt-install-action@v1 + with: + cache_key: ${{ env.GITHUB_JOB }} + packages: package_that_doesnt_exist + continue-on-error: true + - name: Verify + if: steps.execute.outcome != 'failure' + run: exit 1 \ No newline at end of file diff --git a/LICENSE b/LICENSE index 9dfbf92..a10a977 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,13 @@ -BSD 2-Clause License +Copyright 2021 Andrew Walsh -Copyright (c) 2021, Andrew Walsh -All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: + http://www.apache.org/licenses/LICENSE-2.0 -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index 50f7d8b..1864f32 100644 --- a/README.md +++ b/README.md @@ -1 +1,95 @@ -# cached-apt-install-action \ No newline at end of file +# cache-apt-pkgs-action + +[![License: Apache2](https://shields.io/badge/license-apache2-blue.svg)](https://github.com/awalsh128/fluentcpp/blob/master/LICENSE) +[![GitHub Actions status](https://github.com/awalsh128/cache-apt-pkgs-action/workflows/Tests/badge.svg?branch=main&event=push)](https://github.com/awalsh128/cache-apt-pkgs-action/actions?query=workflow%3ATests) + +This action allows caching of Advanced Package Tool (APT) package dependencies to improve workflow execution time. + +## Documentation + +This action is a composition of [actions/cache](https://github.com/actions/cache/README.md) and the `apt` utility. Some actions require additional APT based packages to be installed in order for other steps to be executed. Packages can be installed when ran but can consume much of the execution workflow time. + +## Usage + +### Pre-requisites + +Create a workflow `.yml` file in your repositories `.github/workflows` directory. An [example workflow](#example-workflow) is available below. For more information, reference the GitHub Help Documentation for [Creating a workflow file](https://help.github.com/en/articles/configuring-a-workflow#creating-a-workflow-file). + +### Inputs + +* `key` - Unique key representing the cache being used. +* `packages` - Space delimited list of packages to install. + +### Cache scopes + +The cache is scoped to the key and branch. The default branch cache is available to other branches. + +See [Matching a cache key](https://help.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key) for more info. + +### Example workflow + +This was a motivating use case for creating this action. + +```yaml +name: Documentation + +on: push + +jobs: + + build_and_deploy_docs: + runs-on: ubuntu-latest + name: Build Doxygen documentation and deploy + steps: + - uses: actions/checkout@v2 + - uses: awalsh128/cache-apt-pkgs-action-action@v1 + with: + cache_key: doxygen_env + packages: dia doxygen doxygen-doc doxygen-gui doxygen-latex graphviz mscgen + + - name: Build + run: | + cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@4.1.5 + with: + branch: gh-pages + folder: ${{github.workspace}}/build/website +``` + +## 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`](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#hashfiles) function allows you to create a new cache when dependencies change. + +```yaml + - uses: awalsh128/cache-apt-pkgs-action@v1 + with: + cache_key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + packages: dot +``` + +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 Epoch Seconds + id: get-epoch-sec + run: | + echo "::set-output name=epoch_sec::$(/bin/date +%s)" + shell: bash + + - uses: awalsh128/cache-apt-pkgs-action@v1 + with: + cache_key: ${{ runner.os }}-${{ steps.get-epoch-sec.outputs.epoch_sec }}-${{ hashFiles('**/lockfiles') }} + packages: dot +``` + +See [Using contexts to create cache keys](https://help.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows#using-contexts-to-create-cache-keys) + +## Cache Limits + +A repository can have up to 5GB of caches. Once the 5GB limit is reached, older caches will be evicted based on when the cache was last accessed. Caches that are not accessed within the last week will also be evicted. diff --git a/action.yml b/action.yml index 18182ca..d71c7ec 100644 --- a/action.yml +++ b/action.yml @@ -1,20 +1,26 @@ -name: 'Cached APT Install' +name: 'Cache APT Packages' description: 'Install APT based packages and cache them for future runs.' +author: awalsh128 + inputs: + cache_key: + description: 'Unique key representing the cache being used.' + required: true + default: '' packages: description: 'Space delimited list of packages to install.' required: true default: '' + runs: using: "composite" steps: - - name: Cache Packages + - name: Create Package Cache uses: actions/cache@v2 - id: cached-apt-install-packages - with: - path: "~/cached-apt-install-packages" - key: cached-apt-install-packages + with: + path: "~/cache-apt-pkgs" + key: cache-apt-pkgs_${{ inputs.cache_key }} - name: Install Packages - run: ${{ github.action_path }}/install.sh "~/cached-apt-install-packages" "${{ inputs.packages }}" + run: ${{ github.action_path }}/run.sh "~/cache-apt-pkgs" "${{ inputs.packages }}" shell: bash diff --git a/run.sh b/run.sh index 5942963..4c0af79 100755 --- a/run.sh +++ b/run.sh @@ -3,49 +3,82 @@ cache_dir=$1 packages="${@:2}" -if [ ! -d "$cache_dir" ]; then - echo "Cache directory '$cache_dir' does not exist." - exit 1 -fi -if [ $packages = "" ]; then - echo "Packages argument cannot be empty." - exit 2 -fi - -for dir in `ls $cache_dir`; do - remove=true +validate_args() { + echo -n "Validating action arguments... "; + if [ ! -d "$cache_dir" ]; then + echo "aborted.\nCache directory '$cache_dir' does not exist." + return 1 + fi + if [ $packages = "" ]; then + echo "aborted.\nPackages argument cannot be empty." + return 2 + fi for package in $packages; do - if [ $dir == $package ]; then - remove=false - break + if apt-cache search ^$package$ | grep $package; then + echo "aborted.\nPackage '$package' not found." + return 3 fi done - [ $remove ] && rm -fr $cache_dir/$dir -done + echo "done." + return 0 +} + +clean_cache() { + for dir in `ls $cache_dir`; do + remove=true + for package in $packages; do + if [ $dir == $package ]; then + remove=false + break + fi + done + [ $remove ] && rm -fr $cache_dir/$dir + done +} + +restore_pkg() { + package=$1 + package_dir=$2 + + echo -n "Restoring $package from cache $package_dir... " + sudo cp --verbose --force --recursive $package_dir/* / + sudo apt-get --yes --only-upgrade install $package + echo "done." +} + +install_and_cache_pkg() { + package=$1 + package_dir=$2 + + echo -n "Clean installing $package... " + sudo apt-get --yes install $package + echo "done." + + echo -n "Caching $package to $package_dir..." + mkdir --parents $package_dir + # Pipe all package files (no folders) to copy command. + sudo dpkg -L $package | + while IFS= read -r f; do + if test -f $f; then echo $f; fi; + done | + xargs cp -p -t $package_dir + echo "done." +} + +validate_code = validate_args +if validate_code -ne 0; then + exit $validate_code +fi + +clean_cache for package in $packages; do - package_dir=$cache_dir/$package - if [ -d $package_dir ]; then - - echo "Restoring $package from cache $package_dir..." - sudo cp --verbose --force --recursive $package_dir/* / - sudo apt-get --yes --only-upgrade install $package - + restore_pkg $package $package_dir else - - echo "Clean install $package and caching to $package_dir..." - sudo apt-get --yes install $package - - echo "Caching $package to $package_dir..." - mkdir --parents $package_dir - # Pipe all package files (no folders) to copy command. - sudo dpkg -L $package | - while IFS= read -r f; do - if test -f $f; then echo $f; fi; - done | - xargs cp -p -t $package_dir + install_and_cache_pkg $package $package_dir fi - done + +echo "Action complete. ${#packages[@]} packages installed."