Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First version of a Golang version of command handling in general. #118

Merged
merged 24 commits into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
641f947
fix: apt cache performance (#104)
stevenh Oct 11, 2023
6f9e6a8
Update README.md
awalsh128 Oct 11, 2023
44c33b3
Pull staging changes upstream. (#113)
awalsh128 Oct 30, 2023
36fa536
Add link to cache dependencies docs on GirHub.
awalsh128 Nov 27, 2023
a366c76
Go based package query logic.
awalsh128 Nov 27, 2023
189ae35
Remove unused variable.
awalsh128 Nov 27, 2023
3b5900e
Add Linter for Go code.
awalsh128 Nov 27, 2023
587ece8
Experiment with new Go code for package formatting.
awalsh128 Nov 27, 2023
cc6a957
Fix package space name and Go command.
awalsh128 Nov 27, 2023
6154ec6
Switch package name back so it builds.
awalsh128 Nov 27, 2023
45e4578
Fix stupid syntax.
awalsh128 Nov 27, 2023
19a0253
Be more tolerant of apt-cache warnings.
awalsh128 Nov 27, 2023
7c7bced
Account for colons in version value.
awalsh128 Nov 27, 2023
8b55495
Only catch package not found warnings.
awalsh128 Nov 27, 2023
8010d65
Add log timing to package normalization via Go script.
awalsh128 Nov 27, 2023
643c9a4
Move Go code to canonical directories and execute binary instead.
awalsh128 Nov 27, 2023
e0a235e
Cleanup comments and recurse on Golang build search.
awalsh128 Nov 27, 2023
1d63750
Update PR action.
awalsh128 Nov 27, 2023
90d6c1c
Fix Lint errors and resort GitHub action steps.
awalsh128 Nov 27, 2023
85fe267
Fix test to reflect new filename.
awalsh128 Nov 27, 2023
297ef01
First version of a Golang version of command handling in general.
awalsh128 Dec 10, 2023
de2c363
Fixed Lint errors
awalsh128 Dec 10, 2023
d1c0c98
More Lint errors and add new binary
awalsh128 Dec 10, 2023
0d6b7b7
More Lint errors
awalsh128 Dec 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Pull Request
on:
pull_request:
types: [opened, synchronize]

permissions:
contents: read

jobs:
integrate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v4
with:
go-version-file: "go.mod"

- name: Build and test
run: |
go build -v ./...
go test -v ./...

- name: Lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.52.2
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/cmd/apt_query/apt_query*
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}",
}
]
}
55 changes: 29 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

This action allows caching of Advanced Package Tool (APT) package dependencies to improve workflow execution time instead of installing the packages on every run.

> [!IMPORTANT]
> Looking for co-maintainers to help review changes, and investigate issues. I haven't had as much time to stay on top of this action as I would like to and want to make sure it is still responsive and reliable for the community. If you are interested, please reach out.

## Documentation

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.
Expand All @@ -20,24 +23,24 @@ Create a workflow `.yml` file in your repositories `.github/workflows` directory

There are three kinds of version labels you can use.

* `@latest` - This will give you the latest release.
* `@v#` - Major only will give you the latest release for that major version only (e.g. `v1`).
* Branch
* `@master` - Most recent manual and automated tested code. Possibly unstable since it is pre-release.
* `@staging` - Most recent automated tested code and can sometimes contain experimental features. Is pulled from dev stable code.
* `@dev` - Very unstable and contains experimental features. Automated testing may not show breaks since CI is also updated based on code in dev.
- `@latest` - This will give you the latest release.
- `@v#` - Major only will give you the latest release for that major version only (e.g. `v1`).
- Branch
- `@master` - Most recent manual and automated tested code. Possibly unstable since it is pre-release.
- `@staging` - Most recent automated tested code and can sometimes contain experimental features. Is pulled from dev stable code.
- `@dev` - Very unstable and contains experimental features. Automated testing may not show breaks since CI is also updated based on code in dev.

### Inputs

* `packages` - Space delimited list of packages to install.
* `version` - Version of cache to load. Each version will have its own cache. Note, all characters except spaces are allowed.
* `execute_install_scripts` - Execute Debian package pre and post install script upon restore. See [Caveats / Non-file Dependencies](#non-file-dependencies) for more information.
- `packages` - Space delimited list of packages to install.
- `version` - Version of cache to load. Each version will have its own cache. Note, all characters except spaces are allowed.
- `execute_install_scripts` - Execute Debian package pre and post install script upon restore. See [Caveats / Non-file Dependencies](#non-file-dependencies) for more information.

### Outputs

* `cache-hit` - A boolean value to indicate a cache was found for the packages requested.
* `package-version-list` - The main requested packages and versions that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. \<package1>=<version1\>,\<package2>=\<version2>,...).
* `all-package-version-list` - All the pulled in packages and versions, including dependencies, that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. \<package1>=<version1\>,\<package2>=\<version2>,...).
- `cache-hit` - A boolean value to indicate a cache was found for the packages requested.
- `package-version-list` - The main requested packages and versions that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. \<package1>=<version1\>,\<package2>=\<version2>,...).
- `all-package-version-list` - All the pulled in packages and versions, including dependencies, that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. \<package1>=<version1\>,\<package2>=\<version2>,...).

### Cache scopes

Expand All @@ -51,7 +54,6 @@ This was a motivating use case for creating this action.
name: Create Documentation
on: push
jobs:

build_and_deploy_docs:
runs-on: ubuntu-latest
name: Build Doxygen documentation and deploy
Expand All @@ -62,7 +64,7 @@ jobs:
packages: dia doxygen doxygen-doc doxygen-gui doxygen-latex graphviz mscgen
version: 1.0

- name: Build
- name: Build
run: |
cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
Expand All @@ -75,15 +77,16 @@ jobs:
```

```yaml
...
install_doxygen_deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: dia doxygen doxygen-doc doxygen-gui doxygen-latex graphviz mscgen
version: 1.0

---
install_doxygen_deps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: dia doxygen doxygen-doc doxygen-gui doxygen-latex graphviz mscgen
version: 1.0
```

## Caveats
Expand All @@ -92,8 +95,8 @@ jobs:

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.

* Pre and post installation scripts needs to be ran from `/var/lib/dpkg/info/{package name}.[preinst, postinst]`.
* The Debian package database needs to be queried for scripts above (i.e. `dpkg-query`).
- Pre and post installation scripts needs to be ran from `/var/lib/dpkg/info/{package name}.[preinst, postinst]`.
- The Debian package database needs to be queried for scripts above (i.e. `dpkg-query`).

The `execute_install_scripts` argument can be used to attempt to execute the install scripts but they are no guaranteed to resolve the issue.

Expand All @@ -118,4 +121,4 @@ For more context and information see [issue #57](https://github.com/awalsh128/ca

### 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.
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. To get more information on how to access and manage your actions's caches, see [GitHub Actions / Using workflows / Cache dependencies](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#viewing-cache-entries).
14 changes: 7 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ runs:
DEBUG: "${{ inputs.debug }}"
PACKAGES: "${{ inputs.packages }}"

- id: upload-logs
if: ${{ inputs.debug == 'true' }}
uses: actions/upload-artifact@v3
with:
name: cache-apt-pkgs-logs_${{ env.CACHE_KEY }}
path: ~/cache-apt-pkgs/*.log

- id: save-cache
if: ${{ ! steps.load-cache.outputs.cache-hit }}
uses: actions/cache/save@v3
Expand All @@ -94,10 +101,3 @@ runs:
run: |
rm -rf ~/cache-apt-pkgs
shell: bash

- id: upload-logs
if: ${{ inputs.debug == 'true' }}
uses: actions/upload-artifact@v3
with:
name: cache-apt-pkgs-logs_${{ env.CACHE_KEY }}
path: ~/cache-apt-pkgs/*.log
Binary file added apt_query
Binary file not shown.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module awalsh128.com/cache-apt-pkgs-action

go 1.20
41 changes: 18 additions & 23 deletions install_and_cache_pkgs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,6 @@ cache_dir="${1}"
# List of the packages to use.
input_packages="${@:3}"

# Trim commas, excess spaces, sort, and version syntax.
#
# NOTE: Unless specified, all APT package listings of name and version use
# colon delimited and not equals delimited syntax (i.e. <name>[:=]<ver>).
packages="$(get_normalized_package_list "${input_packages}")"

package_count=$(wc -w <<< "${packages}")
log "Clean installing and caching ${package_count} package(s)."

log_empty_line

manifest_main=""
log "Package list:"
for package in ${packages}; do
read package_name package_ver < <(get_package_name_ver "${package}")
manifest_main="${manifest_main}${package_name}=${package_ver},"
log "- ${package_name} (${package_ver})"
done
write_manifest "main" "${manifest_main}" "${cache_dir}/manifest_main.log"

log_empty_line

if ! apt-fast --version > /dev/null 2>&1; then
log "Installing apt-fast for optimized installs..."
# Install apt-fast for optimized installs.
Expand All @@ -59,6 +37,22 @@ fi

log_empty_line

packages="$(get_normalized_package_list "${input_packages}")"
package_count=$(wc -w <<< "${packages}")
log "Clean installing and caching ${package_count} package(s)."

log_empty_line

manifest_main=""
log "Package list:"
for package in ${packages}; do
manifest_main="${manifest_main}${package},"
log "- ${package}"
done
write_manifest "main" "${manifest_main}" "${cache_dir}/manifest_main.log"

log_empty_line

# Strictly contains the requested packages.
manifest_main=""
# Contains all packages including dependencies.
Expand Down Expand Up @@ -98,7 +92,8 @@ for installed_package in ${installed_packages}; do
& get_install_script_filepath "" "${package_name}" "preinst" \
& get_install_script_filepath "" "${package_name}" "postinst"; } |
while IFS= read -r f; do test -f "${f}" -o -L "${f}" && get_tar_relpath "${f}"; done |
xargs -I {} echo \'{}\' | # Single quotes ensure literals like backslash get captured.
# Single quotes ensure literals like backslash get captured. Use \0 to avoid field separation.
awk -F"\0" '{print "\x27"$1"\x27"}' |
sudo xargs tar -cf "${cache_filepath}" -C /

log " done (compressed size $(du -h "${cache_filepath}" | cut -f1))."
Expand Down
27 changes: 15 additions & 12 deletions lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function get_installed_packages {
###############################################################################
# Splits a fully action syntax APT package into the name and version.
# Arguments:
# The action syntax colon delimited package pair or just the package name.
# The action syntax equals delimited package pair or just the package name.
# Returns:
# The package name and version pair.
###############################################################################
Expand All @@ -81,7 +81,9 @@ function get_package_name_ver {
IFS="${ORIG_IFS}"
# If version not found in the fully qualified package value.
if test -z "${ver}"; then
ver="$(grep "Version:" <<< "$(apt-cache show ${name})" | awk '{print $2}')"
# This is a fallback and should not be used any more as its slow.
log_err "Unexpected version resolution for package '${name}'"
ver="$(apt-cache show ${name} | grep '^Version:' | awk '{print $2}')"
fi
echo "${name}" "${ver}"
}
Expand All @@ -91,16 +93,17 @@ function get_package_name_ver {
# Arguments:
# The comma and/or space delimited list of packages.
# Returns:
# Sorted list of space delimited packages.
# Sorted list of space delimited package name=version pairs.
###############################################################################
function get_normalized_package_list {
# Remove commas, and block scalar folded backslashes.
local stripped=$(echo "${1}" | sed 's/[,\]/ /g')
# Remove extraneous spaces at the middle, beginning, and end.
local trimmed="$(\
echo "${stripped}" \
| sed 's/\s\+/ /g; s/^\s\+//g; s/\s\+$//g')"
echo ${trimmed} | tr ' ' '\n' | sort | tr '\n' ' '
# Remove commas, and block scalar folded backslashes,
# extraneous spaces at the middle, beginning and end
# then sort.
local packages=$(echo "${1}" \
| sed 's/[,\]/ /g; s/\s\+/ /g; s/^\s\+//g; s/\s\+$//g' \
| sort -t' ')
local script_dir="$(dirname -- "$(realpath -- "${0}")")"
${script_dir}/apt_query normalized-list ${packages}
}

###############################################################################
Expand All @@ -120,8 +123,8 @@ function get_tar_relpath {
fi
}

function log { echo "$(date +%H:%M:%S)" "${@}"; }
function log_err { >&2 echo "$(date +%H:%M:%S)" "${@}"; }
function log { echo "$(date +%T.%3N)" "${@}"; }
function log_err { >&2 echo "$(date +%T.%3N)" "${@}"; }

function log_empty_line { echo ""; }

Expand Down
23 changes: 3 additions & 20 deletions pre_cache_action.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ debug="${4}"
input_packages="${@:5}"

# Trim commas, excess spaces, and sort.
log "Normalizing package list..."
packages="$(get_normalized_package_list "${input_packages}")"
log "done"

# Create cache directory so artifacts can be saved.
mkdir -p ${cache_dir}
Expand All @@ -53,35 +55,16 @@ log "done"

log_empty_line

versioned_packages=""
log "Verifying packages..."
for package in ${packages}; do
if test ! "$(apt-cache show ${package})"; then
echo "aborted"
log "Package '${package}' not found." >&2
exit 5
fi
read package_name package_ver < <(get_package_name_ver "${package}")
versioned_packages=""${versioned_packages}" "${package_name}"="${package_ver}""
done
log "done"

log_empty_line

# Abort on any failure at this point.
set -e

log "Creating cache key..."

# TODO Can we prove this will happen again?
normalized_versioned_packages="$(get_normalized_package_list "${versioned_packages}")"
log "- Normalized package list is '${normalized_versioned_packages}'."

# Forces an update in cases where an accidental breaking change was introduced
# and a global cache reset is required.
force_update_inc="1"

value="${normalized_versioned_packages} @ ${version} ${force_update_inc}"
value="${packages} @ ${version} ${force_update_inc}"
log "- Value to hash is '${value}'."

key="$(echo "${value}" | md5sum | cut -f1 -d' ')"
Expand Down
Loading
Loading