├── .github ├── ISSUE_TEMPLATE │ └── bug-report.md └── workflows │ ├── pub_dev_push_event.yml │ ├── pub_staging_push_event.yml │ └── pub_master_push_event.yml ├── LICENSE ├── post_cache_action.sh ├── restore_pkgs.sh ├── pre_cache_action.sh ├── install_and_cache_pkgs.sh ├── action.yml ├── README.md └── lib.sh /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Basic template with warning about known issues. 4 | title: '' 5 | labels: '' 6 | assignees: awalsh128 7 | 8 | --- 9 | 10 | Please read about the limitation of [non-file dependencies](https://github.com/awalsh128/cache-apt-pkgs-action/blob/master/README.md#non-file-dependencies) before filing an issue. 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Andrew Walsh 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /.github/workflows/pub_dev_push_event.yml: -------------------------------------------------------------------------------- 1 | name: Publish Dev Push Event 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - dev 7 | 8 | jobs: 9 | publish_event: 10 | runs-on: ubuntu-latest 11 | name: Publish dev push 12 | steps: 13 | - run: | 14 | curl -i \ 15 | -X POST \ 16 | -H "Accept: application/vnd.github.v3+json" \ 17 | -H "Authorization: token ${{ secrets.PUBLISH_PUSH_TOKEN }}" \ 18 | https://api.github.com/repos/awalsh128/cache-apt-pkgs-action-ci/dispatches \ 19 | -d '{"event_type":"dev_push"}' 20 | -------------------------------------------------------------------------------- /.github/workflows/pub_staging_push_event.yml: -------------------------------------------------------------------------------- 1 | name: Publish Staging Push Event 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - staging 7 | 8 | jobs: 9 | publish_event: 10 | runs-on: ubuntu-latest 11 | name: Publish staging push 12 | steps: 13 | - run: | 14 | curl -i \ 15 | -X POST \ 16 | -H "Accept: application/vnd.github.v3+json" \ 17 | -H "Authorization: token ${{ secrets.PUBLISH_PUSH_TOKEN }}" \ 18 | https://api.github.com/repos/awalsh128/cache-apt-pkgs-action-ci/dispatches \ 19 | -d '{"event_type":"staging_push"}' -------------------------------------------------------------------------------- /.github/workflows/pub_master_push_event.yml: -------------------------------------------------------------------------------- 1 | name: Publish Master Push Event 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | publish_event: 10 | runs-on: ubuntu-latest 11 | name: Publish master push 12 | steps: 13 | - run: | 14 | curl -i \ 15 | -X POST \ 16 | -H "Accept: application/vnd.github.v3+json" \ 17 | -H "Authorization: token ${{ secrets.PUBLISH_PUSH_TOKEN }}" \ 18 | https://api.github.com/repos/awalsh128/cache-apt-pkgs-action-ci/dispatches \ 19 | -d '{"event_type":"master_push"}' 20 | -------------------------------------------------------------------------------- /post_cache_action.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fail on any error. 4 | set -e 5 | 6 | # Include library. 7 | script_dir="$(dirname -- "$(realpath -- "${0}")")" 8 | source "${script_dir}/lib.sh" 9 | 10 | # Directory that holds the cached packages. 11 | cache_dir="${1}" 12 | 13 | # Root directory to untar the cached packages to. 14 | # Typically filesystem root '/' but can be changed for testing. 15 | # WARNING: If non-root, this can cause errors during install script execution. 16 | cache_restore_root="${2}" 17 | 18 | # Indicates that the cache was found. 19 | cache_hit="${3}" 20 | 21 | # Cache and execute post install scripts on restore. 22 | execute_install_scripts="${4}" 23 | 24 | # Debug mode for diagnosing issues. 25 | debug="${5}" 26 | test ${debug} == "true" && set -x 27 | 28 | # List of the packages to use. 29 | packages="${@:6}" 30 | 31 | if [ "$cache_hit" == true ]; then 32 | ${script_dir}/restore_pkgs.sh "${cache_dir}" "${cache_restore_root}" "${execute_install_scripts}" "${debug}" 33 | else 34 | ${script_dir}/install_and_cache_pkgs.sh "${cache_dir}" "${debug}" ${packages} 35 | fi 36 | 37 | log_empty_line 38 | -------------------------------------------------------------------------------- /restore_pkgs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fail on any error. 4 | set -e 5 | 6 | # Debug mode for diagnosing issues. 7 | # Setup first before other operations. 8 | debug="${4}" 9 | test ${debug} == "true" && set -x 10 | 11 | # Include library. 12 | script_dir="$(dirname -- "$(realpath -- "${0}")")" 13 | source "${script_dir}/lib.sh" 14 | 15 | # Directory that holds the cached packages. 16 | cache_dir="${1}" 17 | 18 | # Root directory to untar the cached packages to. 19 | # Typically filesystem root '/' but can be changed for testing. 20 | cache_restore_root="${2}" 21 | test -d ${cache_restore_root} || mkdir ${cache_restore_root} 22 | 23 | # Cache and execute post install scripts on restore. 24 | execute_install_scripts="${3}" 25 | 26 | cache_filepaths="$(ls -1 "${cache_dir}" | sort)" 27 | log "Found $(echo ${cache_filepaths} | wc -w) files in the cache." 28 | for cache_filepath in ${cache_filepaths}; do 29 | log "- "$(basename ${cache_filepath})"" 30 | done 31 | 32 | log_empty_line 33 | 34 | log "Reading from main requested packages manifest..." 35 | for logline in $(cat "${cache_dir}/manifest_main.log" | tr ',' '\n' ); do 36 | log "- $(echo "${logline}" | tr ':' ' ')" 37 | done 38 | log "done" 39 | 40 | log_empty_line 41 | 42 | # Only search for archived results. Manifest and cache key also live here. 43 | cached_pkg_filepaths=$(ls -1 "${cache_dir}"/*.tar | sort) 44 | cached_pkg_filecount=$(echo ${cached_pkg_filepaths} | wc -w) 45 | 46 | log "Restoring ${cached_pkg_filecount} packages from cache..." 47 | for cached_pkg_filepath in ${cached_pkg_filepaths}; do 48 | 49 | log "- $(basename "${cached_pkg_filepath}") restoring..." 50 | sudo tar -xf "${cached_pkg_filepath}" -C "${cache_restore_root}" > /dev/null 51 | log " done" 52 | 53 | # Execute install scripts if available. 54 | if test ${execute_install_scripts} == "true"; then 55 | # May have to add more handling for extracting pre-install script before extracting all files. 56 | # Keeping it simple for now. 57 | execute_install_script "${cache_restore_root}" "${cached_pkg_filepath}" preinst install 58 | execute_install_script "${cache_restore_root}" "${cached_pkg_filepath}" postinst configure 59 | fi 60 | done 61 | log "done" 62 | -------------------------------------------------------------------------------- /pre_cache_action.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Include library. 6 | script_dir="$(dirname -- "$(realpath -- "${0}")")" 7 | source "${script_dir}/lib.sh" 8 | 9 | # Debug mode for diagnosing issues. 10 | # Setup first before other operations. 11 | debug="${4}" 12 | validate_bool "${debug}" debug 1 13 | test ${debug} == "true" && set -x 14 | 15 | # Directory that holds the cached packages. 16 | cache_dir="${1}" 17 | 18 | # Version of the cache to create or load. 19 | version="${2}" 20 | 21 | # Execute post-installation script. 22 | execute_install_scripts="${3}" 23 | 24 | # Debug mode for diagnosing issues. 25 | debug="${4}" 26 | 27 | # List of the packages to use. 28 | input_packages="${@:5}" 29 | 30 | # Trim commas, excess spaces, and sort. 31 | packages="$(normalize_package_list "${input_packages}")" 32 | 33 | # Create cache directory so artifacts can be saved. 34 | mkdir -p ${cache_dir} 35 | 36 | log "Validating action arguments (version='${version}', packages='${packages}')..."; 37 | if grep -q " " <<< "${version}"; then 38 | log "aborted" 39 | log "Version value '${version}' cannot contain spaces." >&2 40 | exit 2 41 | fi 42 | 43 | # Is length of string zero? 44 | if test -z "${packages}"; then 45 | log "aborted" 46 | log "Packages argument cannot be empty." >&2 47 | exit 3 48 | fi 49 | 50 | validate_bool "${execute_install_scripts}" execute_install_scripts 4 51 | 52 | log "done" 53 | 54 | log_empty_line 55 | 56 | versioned_packages="" 57 | log "Verifying packages..." 58 | for package in ${packages}; do 59 | if test ! "$(apt-cache show "${package}")"; then 60 | echo "aborted" 61 | log "Package '${package}' not found." >&2 62 | exit 5 63 | fi 64 | read package_name package_ver < <(get_package_name_ver "${package}") 65 | versioned_packages=""${versioned_packages}" "${package_name}"="${package_ver}"" 66 | done 67 | log "done" 68 | 69 | log_empty_line 70 | 71 | # Abort on any failure at this point. 72 | set -e 73 | 74 | log "Creating cache key..." 75 | 76 | # TODO Can we prove this will happen again? 77 | normalized_versioned_packages="$(normalize_package_list "${versioned_packages}")" 78 | log "- Normalized package list is '${normalized_versioned_packages}'." 79 | 80 | # Forces an update in cases where an accidental breaking change was introduced 81 | # and a global cache reset is required. 82 | force_update_inc="0" 83 | 84 | value="${normalized_versioned_packages} @ ${version} ${force_update_inc}" 85 | log "- Value to hash is '${value}'." 86 | 87 | key="$(echo "${value}" | md5sum | cut -f1 -d' ')" 88 | log "- Value hashed as '${key}'." 89 | 90 | log "done" 91 | 92 | key_filepath="${cache_dir}/cache_key.md5" 93 | echo ${key} > ${key_filepath} 94 | log "Hash value written to ${key_filepath}" 95 | -------------------------------------------------------------------------------- /install_and_cache_pkgs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fail on any error. 4 | set -e 5 | 6 | # Debug mode for diagnosing issues. 7 | # Setup first before other operations. 8 | debug="${2}" 9 | test ${debug} == "true" && set -x 10 | 11 | # Include library. 12 | script_dir="$(dirname -- "$(realpath -- "${0}")")" 13 | source "${script_dir}/lib.sh" 14 | 15 | # Directory that holds the cached packages. 16 | cache_dir="${1}" 17 | 18 | # List of the packages to use. 19 | input_packages="${@:3}" 20 | 21 | # Trim commas, excess spaces, and sort. 22 | normalized_packages="$(normalize_package_list "${input_packages}")" 23 | 24 | package_count=$(wc -w <<< "${normalized_packages}") 25 | log "Clean installing and caching ${package_count} package(s)." 26 | 27 | log_empty_line 28 | 29 | manifest_main="" 30 | log "Package list:" 31 | for package in ${normalized_packages}; do 32 | read package_name package_ver < <(get_package_name_ver "${package}") 33 | manifest_main="${manifest_main}${package_name}:${package_ver}," 34 | log "- ${package_name}:${package_ver}" 35 | done 36 | write_manifest "main" "${manifest_main}" "${cache_dir}/manifest_main.log" 37 | 38 | log_empty_line 39 | 40 | log "Installing apt-fast for optimized installs..." 41 | # Install apt-fast for optimized installs. 42 | /bin/bash -c "$(curl -sL https://git.io/vokNn)" 43 | log "done" 44 | 45 | log_empty_line 46 | 47 | log "Updating APT package list..." 48 | if [[ -z "$(find -H /var/lib/apt/lists -maxdepth 0 -mmin -5)" ]]; then 49 | sudo apt-fast update > /dev/null 50 | log "done" 51 | else 52 | log "skipped (fresh within at least 5 minutes)" 53 | fi 54 | 55 | log_empty_line 56 | 57 | # Strictly contains the requested packages. 58 | manifest_main="" 59 | # Contains all packages including dependencies. 60 | manifest_all="" 61 | 62 | install_log_filepath="${cache_dir}/install.log" 63 | 64 | log "Clean installing ${package_count} packages..." 65 | # Zero interaction while installing or upgrading the system via apt. 66 | sudo DEBIAN_FRONTEND=noninteractive apt-fast --yes install ${normalized_packages} > "${install_log_filepath}" 67 | log "done" 68 | log "Installation log written to ${install_log_filepath}" 69 | 70 | log_empty_line 71 | 72 | installed_packages=$(get_installed_packages "${install_log_filepath}") 73 | log "Installed package list:" 74 | for installed_package in ${installed_packages}; do 75 | log "- ${installed_package}" 76 | done 77 | 78 | log_empty_line 79 | 80 | installed_package_count=$(wc -w <<< "${installed_packages}") 81 | log "Caching ${installed_package_count} installed packages..." 82 | for installed_package in ${installed_packages}; do 83 | cache_filepath="${cache_dir}/${installed_package}.tar" 84 | 85 | # Sanity test in case APT enumerates duplicates. 86 | if test ! -f "${cache_filepath}"; then 87 | read installed_package_name installed_package_ver < <(get_package_name_ver "${installed_package}") 88 | log " * Caching ${installed_package_name} to ${cache_filepath}..." 89 | 90 | # Pipe all package files (no folders) and installation control data to Tar. 91 | { dpkg -L "${installed_package_name}" \ 92 | & get_install_filepath "" "${package_name}" "preinst" \ 93 | & get_install_filepath "" "${package_name}" "postinst"; } | 94 | while IFS= read -r f; do test -f "${f}" -o -L "${f}" && get_tar_relpath "${f}"; done | 95 | xargs -I {} echo \"{}\" | 96 | sudo xargs tar -cf "${cache_filepath}" -C / 97 | 98 | log " done (compressed size $(du -h "${cache_filepath}" | cut -f1))." 99 | fi 100 | 101 | # Comma delimited name:ver pairs in the all packages manifest. 102 | manifest_all="${manifest_all}${installed_package_name}:${installed_package_ver}," 103 | done 104 | log "done (total cache size $(du -h ${cache_dir} | tail -1 | awk '{print $1}'))" 105 | 106 | log_empty_line 107 | 108 | write_manifest "all" "${manifest_all}" "${cache_dir}/manifest_all.log" 109 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache APT Packages' 2 | description: 'Install APT based packages and cache them for future runs.' 3 | author: awalsh128 4 | branding: 5 | icon: 'hard-drive' 6 | color: 'green' 7 | 8 | inputs: 9 | packages: 10 | description: 'Space delimited list of packages to install.' 11 | required: true 12 | default: '' 13 | version: 14 | description: 'Version of cache to load. Each version will have its own cache. Note, all characters except spaces are allowed.' 15 | required: false 16 | default: '' 17 | execute_install_scripts: 18 | description: 'Execute Debian package pre and post install script upon restore. See README.md caveats for more information.' 19 | required: false 20 | default: 'false' 21 | refresh: 22 | description: 'OBSOLETE: Refresh is not used by the action, use version instead.' 23 | deprecationMessage: 'Refresh is not used by the action, use version instead.' 24 | debug: 25 | description: 'Enable debugging when there are issues with action. Minor performance penalty.' 26 | required: false 27 | default: 'false' 28 | 29 | outputs: 30 | cache-hit: 31 | description: 'A boolean value to indicate a cache was found for the packages requested.' 32 | # This compound expression is needed because lhs can be empty. 33 | # Need to output true and false instead of true and nothing. 34 | value: ${{ steps.load-cache.outputs.cache-hit || false }} 35 | package-version-list: 36 | description: 'The main requested packages and versions that are installed. Represented as a comma delimited list with colon delimit on the package version (i.e. ::).' 37 | value: ${{ steps.post-cache.outputs.package-version-list }} 38 | all-package-version-list: 39 | description: 'All the pulled in packages and versions, including dependencies, that are installed. Represented as a comma delimited list with colon delimit on the package version (i.e. ::).' 40 | value: ${{ steps.post-cache.outputs.all-package-version-list }} 41 | 42 | runs: 43 | using: "composite" 44 | steps: 45 | - id: pre-cache 46 | run: | 47 | ${GITHUB_ACTION_PATH}/pre_cache_action.sh \ 48 | ~/cache-apt-pkgs \ 49 | "$VERSION" \ 50 | "$EXEC_INSTALL_SCRIPTS" \ 51 | "$DEBUG" \ 52 | "$PACKAGES" 53 | echo "CACHE_KEY=$(cat ~/cache-apt-pkgs/cache_key.md5)" >> $GITHUB_ENV 54 | shell: bash 55 | env: 56 | VERSION: "${{ inputs.version }}" 57 | EXEC_INSTALL_SCRIPTS: "${{ inputs.execute_install_scripts }}" 58 | DEBUG: "${{ inputs.debug }}" 59 | PACKAGES: "${{ inputs.packages }}" 60 | 61 | - id: load-cache 62 | uses: actions/cache@v3 63 | with: 64 | path: ~/cache-apt-pkgs 65 | key: cache-apt-pkgs_${{ env.CACHE_KEY }} 66 | 67 | - id: post-cache 68 | run: | 69 | ${GITHUB_ACTION_PATH}/post_cache_action.sh \ 70 | ~/cache-apt-pkgs \ 71 | / \ 72 | "$CACHE_HIT" \ 73 | "$EXEC_INSTALL_SCRIPTS" \ 74 | "$DEBUG" \ 75 | "$PACKAGES" 76 | function create_list { local list=$(cat ~/cache-apt-pkgs/manifest_${1}.log | tr '\n' ','); echo ${list:0:-1}; }; 77 | echo "package-version-list=$(create_list main)" >> $GITHUB_OUTPUT 78 | echo "all-package-version-list=$(create_list all)" >> $GITHUB_OUTPUT 79 | shell: bash 80 | env: 81 | CACHE_HIT: "${{ steps.load-cache.outputs.cache-hit }}" 82 | EXEC_INSTALL_SCRIPTS: "${{ inputs.execute_install_scripts }}" 83 | DEBUG: "${{ inputs.debug }}" 84 | PACKAGES: "${{ inputs.packages }}" 85 | 86 | - id: upload-logs 87 | if: ${{ inputs.debug == 'true' }} 88 | uses: actions/upload-artifact@v3 89 | with: 90 | name: cache-apt-pkgs-logs_${{ env.CACHE_KEY }} 91 | path: ~/cache-apt-pkgs/*.log 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cache-apt-pkgs-action 2 | 3 | [![License: Apache2](https://shields.io/badge/license-apache2-blue.svg)](https://github.com/awalsh128/fluentcpp/blob/master/LICENSE) 4 | [![Master Test status](https://github.com/awalsh128/cache-apt-pkgs-action-ci/actions/workflows/master_test.yml/badge.svg)](https://github.com/awalsh128/cache-apt-pkgs-action-ci/actions/workflows/master_test.yml) 5 | [![Staging Test status](https://github.com/awalsh128/cache-apt-pkgs-action-ci/actions/workflows/staging_test.yml/badge.svg)](https://github.com/awalsh128/cache-apt-pkgs-action-ci/actions/workflows/staging_test.yml) 6 | 7 | This action allows caching of Advanced Package Tool (APT) package dependencies to improve workflow execution time instead of installing the packages on every run. 8 | 9 | ## Documentation 10 | 11 | This action is a composition of [actions/cache](https://github.com/actions/cache/) 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. 12 | 13 | ## Usage 14 | 15 | ### Pre-requisites 16 | 17 | 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). 18 | 19 | ### Versions 20 | 21 | There are three kinds of version labels you can use. 22 | 23 | * `@latest` - This will give you the latest release. 24 | * `@v#` - Major only will give you the latest release for that major version only (e.g. `v1`). 25 | * Branch 26 | * `@master` - Most recent manual and automated tested code. Possibly unstable since it is pre-release. 27 | * `@staging` - Most recent automated tested code and can sometimes contain experimental features. Is pulled from dev stable code. 28 | * `@dev` - Very unstable and contains experimental features. Automated testing may not show breaks since CI is also updated based on code in dev. 29 | 30 | ### Inputs 31 | 32 | * `packages` - Space delimited list of packages to install. 33 | * `version` - Version of cache to load. Each version will have its own cache. Note, all characters except spaces are allowed. 34 | * `execute_install_scripts` - Execute Debian package pre and post install script upon restore. See [Caveats / Non-file Dependencies](#non-file-dependencies) for more information. 35 | 36 | ### Outputs 37 | 38 | * `cache-hit` - A boolean value to indicate a cache was found for the packages requested. 39 | * `package-version-list` - The main requested packages and versions that are installed. Represented as a comma delimited list with colon delimit on the package version (i.e. \:,\:\,...). 40 | * `all-package-version-list` - All the pulled in packages and versions, including dependencies, that are installed. Represented as a comma delimited list with colon delimit on the package version (i.e. \:,\:\,...). 41 | 42 | ### Cache scopes 43 | 44 | The cache is scoped to the packages given and the branch. The default branch cache is available to other branches. 45 | 46 | ### Example workflow 47 | 48 | This was a motivating use case for creating this action. 49 | 50 | ```yaml 51 | name: Create Documentation 52 | on: push 53 | jobs: 54 | 55 | build_and_deploy_docs: 56 | runs-on: ubuntu-latest 57 | name: Build Doxygen documentation and deploy 58 | steps: 59 | - uses: actions/checkout@v2 60 | - uses: awalsh128/cache-apt-pkgs-action@latest 61 | with: 62 | packages: dia doxygen doxygen-doc doxygen-gui doxygen-latex graphviz mscgen 63 | version: 1.0 64 | 65 | - name: Build 66 | run: | 67 | cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 68 | cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 69 | 70 | - name: Deploy 71 | uses: JamesIves/github-pages-deploy-action@4.1.5 72 | with: 73 | branch: gh-pages 74 | folder: ${{github.workspace}}/build/website 75 | ``` 76 | 77 | ```yaml 78 | ... 79 | install_doxygen_deps: 80 | runs-on: ubuntu-latest 81 | steps: 82 | - uses: actions/checkout@v2 83 | - uses: awalsh128/cache-apt-pkgs-action@latest 84 | with: 85 | packages: dia doxygen doxygen-doc doxygen-gui doxygen-latex graphviz mscgen 86 | version: 1.0 87 | ``` 88 | 89 | ## Caveats 90 | 91 | ### Non-file Dependencies 92 | 93 | This action is based on the principle that most packages can be cached as a fileset. There are situations though where this is not enough. 94 | 95 | * Pre and post installation scripts needs to be ran from `/var/lib/dpkg/info/{package name}.[preinst, postinst]`. 96 | * The Debian package database needs to be queried for scripts above (i.e. `dpkg-query`). 97 | 98 | The `execute_install_scripts` argument can be used to attempt to execute the install scripts but they are no guaranteed to resolve the issue. 99 | 100 | ```yaml 101 | - uses: awalsh128/cache-apt-pkgs-action@latest 102 | with: 103 | packages: mypackage 104 | version: 1.0 105 | execute_install_scripts: true 106 | ``` 107 | 108 | If this does not solve your issue, you will need to run `apt-get install` as a separate step for that particular package unfortunately. 109 | 110 | ```yaml 111 | run: apt-get install mypackage 112 | shell: bash 113 | ``` 114 | 115 | Please reach out if you have found a workaround for your scenario and it can be generalized. There is only so much this action can do and can't get into the area of reverse engineering Debian package manager. It would be beyond the scope of this action and may result in a lot of extended support and brittleness. Also, it would be better to contribute to Debian packager instead at that point. 116 | 117 | For more context and information see [issue #57](https://github.com/awalsh128/cache-apt-pkgs-action/issues/57#issuecomment-1321024283) which contains the investigation and conclusion. 118 | 119 | ### Cache Limits 120 | 121 | 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. 122 | -------------------------------------------------------------------------------- /lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################################### 4 | # Execute the Debian install script. 5 | # Arguments: 6 | # Root directory to search from. 7 | # File path to cached package archive. 8 | # Installation script extension (preinst, postinst). 9 | # Parameter to pass to the installation script. 10 | # Returns: 11 | # Filepath of the install script, otherwise an empty string. 12 | ############################################################################### 13 | function execute_install_script { 14 | local package_name=$(basename ${2} | awk -F\: '{print $1}') 15 | local install_script_filepath=$(\ 16 | get_install_filepath "${1}" "${package_name}" "${3}") 17 | if test ! -z "${install_script_filepath}"; then 18 | log "- Executing ${install_script_filepath}..." 19 | # Don't abort on errors; dpkg-trigger will error normally since it is 20 | # outside its run environment. 21 | sudo sh -x ${install_script_filepath} ${4} || true 22 | log " done" 23 | fi 24 | } 25 | 26 | ############################################################################### 27 | # Gets a list of installed packages from a Debian package installation log. 28 | # Arguments: 29 | # The filepath of the Debian install log. 30 | # Returns: 31 | # The list of space delimited pairs with each pair colon delimited. 32 | # : ... 33 | ############################################################################### 34 | function get_installed_packages { 35 | local install_log_filepath="${1}" 36 | local regex="^Unpacking ([^ :]+)([^ ]+)? (\[[^ ]+\]\s)?\(([^ )]+)" 37 | local dep_packages="" 38 | while read -r line; do 39 | if [[ "${line}" =~ ${regex} ]]; then 40 | dep_packages="${dep_packages}${BASH_REMATCH[1]}:${BASH_REMATCH[4]} " 41 | else 42 | log_err "Unable to parse package name and version from \"${line}\"" 43 | exit 2 44 | fi 45 | done < <(grep "^Unpacking " ${install_log_filepath}) 46 | if test -n "${dep_packages}"; then 47 | echo "${dep_packages:0:-1}" # Removing trailing space. 48 | else 49 | echo "" 50 | fi 51 | } 52 | 53 | ############################################################################### 54 | # Splits a fully qualified package into name and version. 55 | # Arguments: 56 | # The colon delimited package pair or just the package name. 57 | # Returns: 58 | # The package name and version pair. 59 | ############################################################################### 60 | function get_package_name_ver { 61 | IFS=\: read name ver <<< "${1}" 62 | # If version not found in the fully qualified package value. 63 | if test -z "${ver}"; then 64 | ver="$(grep "Version:" <<< "$(apt-cache show ${name})" | awk '{print $2}')" 65 | fi 66 | echo "${name}" "${ver}" 67 | } 68 | 69 | ############################################################################### 70 | # Gets the package name from the cached package filepath in the 71 | # path/to/cache/dir/:.tar format. 72 | # Arguments: 73 | # Filepath to the cached packaged. 74 | # Returns: 75 | # The package name. 76 | ############################################################################### 77 | function get_package_name_from_cached_filepath { 78 | basename ${cached_pkg_filepath} | awk -F\: '{print $1}' 79 | } 80 | 81 | ############################################################################### 82 | # Gets the Debian install script file location. 83 | # Arguments: 84 | # Root directory to search from. 85 | # Name of the unqualified package to search for. 86 | # Extension of the installation script (preinst, postinst) 87 | # Returns: 88 | # Filepath of the script file, otherwise an empty string. 89 | ############################################################################### 90 | function get_install_filepath { 91 | # Filename includes arch (e.g. amd64). 92 | local filepath="$(\ 93 | ls -1 ${1}var/lib/dpkg/info/${2}*.${3} 2> /dev/null \ 94 | | grep -E ${2}'(:.*)?.'${3} | head -1 || true)" 95 | test "${filepath}" && echo "${filepath}" 96 | } 97 | 98 | ############################################################################### 99 | # Gets the relative filepath acceptable by Tar. Just removes the leading slash 100 | # that Tar disallows. 101 | # Arguments: 102 | # Absolute filepath to archive. 103 | # Returns: 104 | # The relative filepath to archive. 105 | ############################################################################### 106 | function get_tar_relpath { 107 | local filepath=${1} 108 | if test ${filepath:0:1} = "/"; then 109 | echo "${filepath:1}" 110 | else 111 | echo "${filepath}" 112 | fi 113 | } 114 | 115 | function log { echo "$(date +%H:%M:%S)" "${@}"; } 116 | function log_err { >&2 echo "$(date +%H:%M:%S)" "${@}"; } 117 | 118 | function log_empty_line { echo ""; } 119 | 120 | ############################################################################### 121 | # Sorts given packages by name and split on commas and/or spaces. 122 | # Arguments: 123 | # The comma and/or space delimited list of packages. 124 | # Returns: 125 | # Sorted list of space delimited packages. 126 | ############################################################################### 127 | function normalize_package_list { 128 | # Remove commas, and block scalar folded backslashes. 129 | local stripped=$(echo "${1}" | sed 's/[,\]/ /g') 130 | # Remove extraneous spaces at the middle, beginning, and end. 131 | local trimmed="$(\ 132 | echo "${stripped}" \ 133 | | sed 's/\s\+/ /g; s/^\s\+//g; s/\s\+$//g')" 134 | local sorted="$(echo ${trimmed} | tr ' ' '\n' | sort | tr '\n' ' ')" 135 | echo "${sorted}" 136 | } 137 | 138 | ############################################################################### 139 | # Validates an argument to be of a boolean value. 140 | # Arguments: 141 | # Argument to validate. 142 | # Variable name of the argument. 143 | # Exit code if validation fails. 144 | # Returns: 145 | # Sorted list of space delimited packages. 146 | ############################################################################### 147 | function validate_bool { 148 | if test "${1}" != "true" -a "${1}" != "false"; then 149 | log "aborted" 150 | log "${2} value '${1}' must be either true or false (case sensitive)." 151 | exit ${3} 152 | fi 153 | } 154 | 155 | ############################################################################### 156 | # Writes the manifest to a specified file. 157 | # Arguments: 158 | # Type of manifest being written. 159 | # List of packages being written to the file. 160 | # File path of the manifest being written. 161 | # Returns: 162 | # Log lines from write. 163 | ############################################################################### 164 | function write_manifest { 165 | if [ ${#2} -eq 0 ]; then 166 | log "Skipped ${1} manifest write. No packages to install." 167 | else 168 | log "Writing ${1} packages manifest to ${3}..." 169 | # 0:-1 to remove trailing comma, delimit by newline and sort. 170 | echo "${2:0:-1}" | tr ',' '\n' | sort > ${3} 171 | log "done" 172 | fi 173 | } 174 | --------------------------------------------------------------------------------