mirror of
https://github.com/awalsh128/cache-apt-pkgs-action.git
synced 2026-01-11 21:12:45 +00:00
Compare commits
118 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acb598e5dd | ||
|
|
23602f4229 | ||
|
|
2ae65d5bbf | ||
|
|
9f7a885e33 | ||
|
|
a605dbde2a | ||
|
|
2c09a5e66d | ||
|
|
9a146f43d1 | ||
|
|
cbdbab28e6 | ||
|
|
23ccb675d9 | ||
|
|
db548ecc55 | ||
|
|
4c82c3ccdc | ||
|
|
7ca5f46d06 | ||
|
|
2330cb6dfb | ||
|
|
128de25ccb | ||
|
|
13d2226e13 | ||
|
|
d1a184e480 | ||
|
|
dfe9c8af34 | ||
|
|
5902b33ae2 | ||
|
|
f2fc6d1af4 | ||
|
|
a6c3917cc9 | ||
|
|
2555a377df | ||
|
|
75ab37ec52 | ||
|
|
a9d925863b | ||
|
|
5c74a020dc | ||
|
|
6460a33c29 | ||
|
|
44c33b32f8 | ||
|
|
6f9e6a86db | ||
|
|
641f947ac2 | ||
|
|
1850ee53f6 | ||
|
|
135ee20306 | ||
|
|
0a4812359d | ||
|
|
6d3c7590b1 | ||
|
|
f5bcdd76d3 | ||
|
|
cdad971850 | ||
|
|
971da5988a | ||
|
|
bd5455834e | ||
|
|
a644619d1f | ||
|
|
bdc09286d1 | ||
|
|
946776e670 | ||
|
|
854bb539e1 | ||
|
|
797d1a2f52 | ||
|
|
270eae5fc9 | ||
|
|
5b6c3ab114 | ||
|
|
622a4c5687 | ||
|
|
e649dfdfe5 | ||
|
|
9ecdf39b43 | ||
|
|
9b2b4f2004 | ||
|
|
9b3b2b590c | ||
|
|
a71b0ea8ac | ||
|
|
d07fc5d9ff | ||
|
|
9bc3e210f3 | ||
|
|
982de21c87 | ||
|
|
4fcaa16dbf | ||
|
|
c51c800bea | ||
|
|
51678ad913 | ||
|
|
276bbdc9f3 | ||
|
|
d93c95e39c | ||
|
|
677fd9bce5 | ||
|
|
c58510bf41 | ||
|
|
09f9c08872 | ||
|
|
fc1d3efd6e | ||
|
|
91b541353e | ||
|
|
c5df606b25 | ||
|
|
97133735cc | ||
|
|
704b3e1a64 | ||
|
|
c4bc8b6ed5 | ||
|
|
e3e8536b71 | ||
|
|
d3470979cf | ||
|
|
f1a79a837b | ||
|
|
8af57b352e | ||
|
|
1e75d90dc9 | ||
|
|
b61cf9a6f1 | ||
|
|
a85a2de3b3 | ||
|
|
c58b29a1aa | ||
|
|
f7b89333d6 | ||
|
|
0735dbdc22 | ||
|
|
f20c69935b | ||
|
|
053557ecd1 | ||
|
|
d5b9449cd3 | ||
|
|
1d2f5e1b23 | ||
|
|
e5ab96be47 | ||
|
|
cda771820d | ||
|
|
fa7091e9a8 | ||
|
|
9a4f0f7d01 | ||
|
|
63f7fccab9 | ||
|
|
c21469a518 | ||
|
|
39faaf9bee | ||
|
|
768417e4ec | ||
|
|
60c15ce2f0 | ||
|
|
7274c01428 | ||
|
|
f6ea1022c2 | ||
|
|
eb177a2a2b | ||
|
|
843a78dbf3 | ||
|
|
f5249e37b0 | ||
|
|
f9b0016d02 | ||
|
|
a91bf7b286 | ||
|
|
903a8a32ff | ||
|
|
bc3229644b | ||
|
|
cb0990bfa2 | ||
|
|
686ae5cbb1 | ||
|
|
e6baec0bc6 | ||
|
|
45b8861f3a | ||
|
|
c961be0ea5 | ||
|
|
1ca2ac1a4c | ||
|
|
f1c5c6fb4a | ||
|
|
4d55f8a4fb | ||
|
|
6fe1db8111 | ||
|
|
49b4515f51 | ||
|
|
d7e83f6ca0 | ||
|
|
c25bf34f45 | ||
|
|
2719bca2f4 | ||
|
|
c86218038a | ||
|
|
17cdbf8735 | ||
|
|
01059849f8 | ||
|
|
c8f8c819da | ||
|
|
b229047610 | ||
|
|
9568dc9783 | ||
|
|
e68df30bd6 |
10
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Basic template with warning about known issues.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: awalsh128
|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
19
.github/workflows/pub_dev_push_event.yml
vendored
Normal file
19
.github/workflows/pub_dev_push_event.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name: Publish Dev Push Event
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
publish_event:
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish dev push
|
||||
steps:
|
||||
- run: |
|
||||
curl -i \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${{ secrets.PUBLISH_PUSH_TOKEN }}" \
|
||||
https://api.github.com/repos/awalsh128/cache-apt-pkgs-action-ci/dispatches \
|
||||
-d '{"event_type":"dev_push"}'
|
||||
4
.github/workflows/pub_master_push_event.yml
vendored
4
.github/workflows/pub_master_push_event.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
|||
jobs:
|
||||
publish_event:
|
||||
runs-on: ubuntu-latest
|
||||
name: Publish staging push
|
||||
name: Publish master push
|
||||
steps:
|
||||
- run: |
|
||||
curl -i \
|
||||
|
|
@ -16,4 +16,4 @@ jobs:
|
|||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: token ${{ secrets.PUBLISH_PUSH_TOKEN }}" \
|
||||
https://api.github.com/repos/awalsh128/cache-apt-pkgs-action-ci/dispatches \
|
||||
-d '{"event_type":"master_push"}'
|
||||
-d '{"event_type":"master_push"}'
|
||||
|
|
|
|||
29
.github/workflows/pull_request.yml
vendored
Normal file
29
.github/workflows/pull_request.yml
vendored
Normal file
|
|
@ -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
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
src/cmd/apt_query/apt_query*
|
||||
*.log
|
||||
12
.vscode/launch.json
vendored
Normal file
12
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}",
|
||||
}
|
||||
]
|
||||
}
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
|||
Copyright 2021 Andrew Walsh
|
||||
Copyright 2022 Andrew Walsh
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
|||
122
README.md
122
README.md
|
|
@ -2,13 +2,16 @@
|
|||
|
||||
[](https://github.com/awalsh128/fluentcpp/blob/master/LICENSE)
|
||||
[](https://github.com/awalsh128/cache-apt-pkgs-action-ci/actions/workflows/master_test.yml)
|
||||
[](https://github.com/awalsh128/cache-apt-pkgs-action-ci/actions/workflows/staging_test.yml)
|
||||
[](https://github.com/awalsh128/cache-apt-pkgs-action-ci/actions/workflows/dev_test.yml)
|
||||
|
||||
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/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.
|
||||
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.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -16,16 +19,30 @@ This action is a composition of [actions/cache](https://github.com/actions/cache
|
|||
|
||||
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).
|
||||
|
||||
### Versions
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
- `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.
|
||||
- `empty_packages_behavior` - Desired behavior when the given `packages` is empty. `'error'` (default), `'warn'` or `'ignore'`.
|
||||
- `add-repository` - Space delimited list of repositories to add via `apt-add-repository` before installing packages. Supports PPA (e.g., `ppa:user/repo`) and other repository formats.
|
||||
|
||||
### Outputs
|
||||
|
||||
* `cache-hit` - A boolean value to indicate a cache was found for the packages requested.
|
||||
* `package-version-list` - The packages and versions that are installed as a comma delimited list with colon 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
|
||||
|
||||
|
|
@ -39,18 +56,17 @@ 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
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
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}}
|
||||
|
|
@ -63,18 +79,78 @@ jobs:
|
|||
```
|
||||
|
||||
```yaml
|
||||
...
|
||||
install_doxygen_deps:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
with:
|
||||
packages: dia doxygen doxygen-doc doxygen-gui doxygen-latex graphviz mscgen
|
||||
version: 1.0
|
||||
refresh: true # Force refresh / upgrade v1.0 cache.
|
||||
|
||||
---
|
||||
install_doxygen_deps:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: dia doxygen doxygen-doc doxygen-gui doxygen-latex graphviz mscgen
|
||||
version: 1.0
|
||||
```
|
||||
|
||||
## Cache Limits
|
||||
### Using with Third-party PPAs
|
||||
|
||||
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.
|
||||
This example shows how to install packages from a third-party PPA:
|
||||
|
||||
```yaml
|
||||
install_from_ppa:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: chromium-browser
|
||||
add-repository: ppa:canonical-chromium-builds/stage
|
||||
version: 1.0
|
||||
```
|
||||
|
||||
You can also add multiple repositories:
|
||||
|
||||
```yaml
|
||||
install_from_multiple_repos:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: package1 package2
|
||||
add-repository: ppa:user/repo1 ppa:user/repo2
|
||||
version: 1.0
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
### Non-file Dependencies
|
||||
|
||||
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`).
|
||||
|
||||
The `execute_install_scripts` argument can be used to attempt to execute the install scripts but they are no guaranteed to resolve the issue.
|
||||
|
||||
```yaml
|
||||
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: mypackage
|
||||
version: 1.0
|
||||
execute_install_scripts: true
|
||||
```
|
||||
|
||||
If this does not solve your issue, you will need to run `apt-get install` as a separate step for that particular package unfortunately.
|
||||
|
||||
```yaml
|
||||
run: apt-get install mypackage
|
||||
shell: bash
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### 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. 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).
|
||||
|
|
|
|||
102
action.yml
102
action.yml
|
|
@ -7,17 +7,38 @@ branding:
|
|||
|
||||
inputs:
|
||||
packages:
|
||||
description: 'Space delimited list of packages to install.'
|
||||
description: 'Space delimited list of packages to install. Version can be specified optionally using APT command syntax of <name>=<version> (e.g. xdot=1.2-2).'
|
||||
required: true
|
||||
default: ''
|
||||
version:
|
||||
description: 'Version will create a new cache and install packages.'
|
||||
description: 'Version of cache to load. Each version will have its own cache. Note, all characters except spaces are allowed.'
|
||||
required: false
|
||||
default: ''
|
||||
refresh:
|
||||
description: 'Option to refresh / upgrade the packages in the same cache.'
|
||||
default: ''
|
||||
execute_install_scripts:
|
||||
description: 'Execute Debian package pre and post install script upon restore. See README.md caveats for more information.'
|
||||
required: false
|
||||
default: 'false'
|
||||
empty_packages_behavior:
|
||||
description: >
|
||||
Desired behavior when the provided package list is empty.
|
||||
|
||||
Available Options:
|
||||
error: Fail the action with an error message
|
||||
warn: Output a warning without failing the action
|
||||
ignore: Proceed silently without warnings or errors
|
||||
required: false
|
||||
default: 'error'
|
||||
refresh:
|
||||
description: 'OBSOLETE: Refresh is not used by the action, use version instead.'
|
||||
deprecationMessage: 'Refresh is not used by the action, use version instead.'
|
||||
debug:
|
||||
description: 'Enable debugging when there are issues with action. Minor performance penalty.'
|
||||
required: false
|
||||
default: 'false'
|
||||
add-repository:
|
||||
description: 'Space delimited list of repositories to add via apt-add-repository before installing packages. Supports PPA (ppa:user/repo) and other repository formats.'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
|
|
@ -26,33 +47,82 @@ outputs:
|
|||
# Need to output true and false instead of true and nothing.
|
||||
value: ${{ steps.load-cache.outputs.cache-hit || false }}
|
||||
package-version-list:
|
||||
description: 'The packages and versions that are installed as a comma delimited list with colon delimit on the package version (i.e. <package>:<version,<package>:<version>).'
|
||||
description: 'The main requested packages and versions that are installed. Represented as a comma delimited list with equals delimit on the package version (i.e. <package>:<version,<package>:<version>).'
|
||||
value: ${{ steps.post-cache.outputs.package-version-list }}
|
||||
all-package-version-list:
|
||||
description: '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. <package>:<version,<package>:<version>).'
|
||||
value: ${{ steps.post-cache.outputs.all-package-version-list }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- id: pre-cache
|
||||
run: |
|
||||
${{ github.action_path }}/pre_cache_action.sh \
|
||||
${GITHUB_ACTION_PATH}/pre_cache_action.sh \
|
||||
~/cache-apt-pkgs \
|
||||
"${{ inputs.version }}" \
|
||||
${{ inputs.packages }}
|
||||
echo "CACHE_KEY=$(cat ~/cache-apt-pkgs/cache_key.md5)" >> $GITHUB_ENV
|
||||
"$VERSION" \
|
||||
"$EXEC_INSTALL_SCRIPTS" \
|
||||
"$DEBUG" \
|
||||
"$ADD_REPOSITORY" \
|
||||
"$PACKAGES"
|
||||
if [ -f ~/cache-apt-pkgs/cache_key.md5 ]; then
|
||||
echo "CACHE_KEY=$(cat ~/cache-apt-pkgs/cache_key.md5)" >> $GITHUB_ENV
|
||||
else
|
||||
echo "CACHE_KEY=" >> $GITHUB_ENV
|
||||
fi
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: "${{ inputs.version }}"
|
||||
EXEC_INSTALL_SCRIPTS: "${{ inputs.execute_install_scripts }}"
|
||||
EMPTY_PACKAGES_BEHAVIOR: "${{ inputs.empty_packages_behavior }}"
|
||||
DEBUG: "${{ inputs.debug }}"
|
||||
ADD_REPOSITORY: "${{ inputs.add-repository }}"
|
||||
PACKAGES: "${{ inputs.packages }}"
|
||||
|
||||
- id: load-cache
|
||||
uses: actions/cache@v2
|
||||
if: ${{ env.CACHE_KEY }}
|
||||
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ~/cache-apt-pkgs
|
||||
key: cache-apt-pkgs_${{ env.CACHE_KEY }}
|
||||
|
||||
- id: post-cache
|
||||
run: |
|
||||
${{ github.action_path }}/post_cache_action.sh \
|
||||
if: ${{ env.CACHE_KEY }}
|
||||
run: |
|
||||
${GITHUB_ACTION_PATH}/post_cache_action.sh \
|
||||
~/cache-apt-pkgs \
|
||||
/ \
|
||||
"${{ steps.load-cache.outputs.cache-hit }}" \
|
||||
${{ inputs.packages }}
|
||||
echo "::set-output name=package-version-list::$(cat ~/cache-apt-pkgs/manifest.log)"
|
||||
"$CACHE_HIT" \
|
||||
"$EXEC_INSTALL_SCRIPTS" \
|
||||
"$DEBUG" \
|
||||
"$ADD_REPOSITORY" \
|
||||
"$PACKAGES"
|
||||
function create_list { local list=$(cat ~/cache-apt-pkgs/manifest_${1}.log | tr '\n' ','); echo ${list:0:-1}; };
|
||||
echo "package-version-list=$(create_list main)" >> $GITHUB_OUTPUT
|
||||
echo "all-package-version-list=$(create_list all)" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
env:
|
||||
CACHE_HIT: "${{ steps.load-cache.outputs.cache-hit }}"
|
||||
EXEC_INSTALL_SCRIPTS: "${{ inputs.execute_install_scripts }}"
|
||||
DEBUG: "${{ inputs.debug }}"
|
||||
ADD_REPOSITORY: "${{ inputs.add-repository }}"
|
||||
PACKAGES: "${{ inputs.packages }}"
|
||||
|
||||
- id: upload-logs
|
||||
if: ${{ env.CACHE_KEY && inputs.debug == 'true' }}
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: cache-apt-pkgs-logs_${{ env.CACHE_KEY }}
|
||||
path: ~/cache-apt-pkgs/*.log
|
||||
|
||||
- id: save-cache
|
||||
if: ${{ env.CACHE_KEY && ! steps.load-cache.outputs.cache-hit }}
|
||||
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ~/cache-apt-pkgs
|
||||
key: ${{ steps.load-cache.outputs.cache-primary-key }}
|
||||
|
||||
- id: clean-cache
|
||||
run: |
|
||||
rm -rf ~/cache-apt-pkgs
|
||||
shell: bash
|
||||
|
|
|
|||
BIN
apt_query-arm64
Executable file
BIN
apt_query-arm64
Executable file
Binary file not shown.
BIN
apt_query-x86
Executable file
BIN
apt_query-x86
Executable file
Binary file not shown.
|
|
@ -3,49 +3,130 @@
|
|||
# Fail on any error.
|
||||
set -e
|
||||
|
||||
# Debug mode for diagnosing issues.
|
||||
# Setup first before other operations.
|
||||
debug="${2}"
|
||||
test "${debug}" = "true" && set -x
|
||||
|
||||
# Include library.
|
||||
script_dir="$(dirname -- "$(realpath -- "${0}")")"
|
||||
source "${script_dir}/lib.sh"
|
||||
|
||||
# Directory that holds the cached packages.
|
||||
cache_dir=$1
|
||||
cache_dir="${1}"
|
||||
|
||||
# Repositories to add before installing packages.
|
||||
add_repository="${3}"
|
||||
|
||||
# List of the packages to use.
|
||||
packages="${@:2}"
|
||||
input_packages="${@:4}"
|
||||
|
||||
package_count=$(echo $packages | wc -w)
|
||||
echo "Clean installing and caching $package_count package(s)."
|
||||
echo "Package list:"
|
||||
for package in $packages; do
|
||||
echo "- $package"
|
||||
if ! apt-fast --version > /dev/null 2>&1; then
|
||||
log "Installing apt-fast for optimized installs..."
|
||||
# Install apt-fast for optimized installs.
|
||||
/bin/bash -c "$(curl -sL https://raw.githubusercontent.com/ilikenwf/apt-fast/master/quick-install.sh)"
|
||||
log "done"
|
||||
|
||||
log_empty_line
|
||||
fi
|
||||
|
||||
# Add custom repositories if specified
|
||||
if [ -n "${add_repository}" ]; then
|
||||
log "Adding custom repositories..."
|
||||
for repository in ${add_repository}; do
|
||||
log "- Adding repository: ${repository}"
|
||||
sudo apt-add-repository -y "${repository}"
|
||||
done
|
||||
log "done"
|
||||
log_empty_line
|
||||
fi
|
||||
|
||||
log "Updating APT package list..."
|
||||
if [[ -z "$(find -H /var/lib/apt/lists -maxdepth 0 -mmin -5)" ]]; then
|
||||
sudo apt-fast update > /dev/null
|
||||
log "done"
|
||||
else
|
||||
log "skipped (fresh within at least 5 minutes)"
|
||||
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.
|
||||
manifest_all=""
|
||||
|
||||
install_log_filepath="${cache_dir}/install.log"
|
||||
|
||||
log "Clean installing ${package_count} packages..."
|
||||
# Zero interaction while installing or upgrading the system via apt.
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-fast --yes install ${packages} > "${install_log_filepath}"
|
||||
log "done"
|
||||
log "Installation log written to ${install_log_filepath}"
|
||||
|
||||
log_empty_line
|
||||
|
||||
installed_packages=$(get_installed_packages "${install_log_filepath}")
|
||||
log "Installed package list:"
|
||||
for installed_package in ${installed_packages}; do
|
||||
# Reformat for human friendly reading.
|
||||
log "- $(echo ${installed_package} | awk -F\= '{print $1" ("$2")"}')"
|
||||
done
|
||||
|
||||
echo -n "Updating APT package list..."
|
||||
sudo apt-get update > /dev/null
|
||||
echo "done."
|
||||
log_empty_line
|
||||
|
||||
echo "Clean installing and caching $(echo $packages | wc -w) packages..."
|
||||
for package in $packages; do
|
||||
cache_filepath=$cache_dir/$package.tar.gz
|
||||
installed_packages_count=$(wc -w <<< "${installed_packages}")
|
||||
log "Caching ${installed_packages_count} installed packages..."
|
||||
for installed_package in ${installed_packages}; do
|
||||
cache_filepath="${cache_dir}/${installed_package}.tar"
|
||||
|
||||
echo "- $package"
|
||||
echo -n " Installing..."
|
||||
sudo apt-get --yes install $package > /dev/null
|
||||
echo "done."
|
||||
# Sanity test in case APT enumerates duplicates.
|
||||
if test ! -f "${cache_filepath}"; then
|
||||
read package_name package_ver < <(get_package_name_ver "${installed_package}")
|
||||
log " * Caching ${package_name} to ${cache_filepath}..."
|
||||
|
||||
echo -n " Caching to $cache_filepath..."
|
||||
# Pipe all package files (no folders) to Tar.
|
||||
dpkg -L $package |
|
||||
while IFS= read -r f; do
|
||||
if test -f $f; then echo ${f:1}; fi; #${f:1} removes the leading slash that Tar disallows
|
||||
done |
|
||||
xargs tar -czf $cache_filepath -C /
|
||||
echo "done."
|
||||
# Pipe all package files (no folders), including symlinks, their targets, and installation control data to Tar.
|
||||
tar -cf "${cache_filepath}" -C / --verbatim-files-from --files-from <(
|
||||
{ dpkg -L "${package_name}" &&
|
||||
get_install_script_filepath "" "${package_name}" "preinst" &&
|
||||
get_install_script_filepath "" "${package_name}" "postinst" ; } |
|
||||
while IFS= read -r f; do
|
||||
if test -f "${f}" -o -L "${f}"; then
|
||||
get_tar_relpath "${f}"
|
||||
if [ -L "${f}" ]; then
|
||||
target="$(readlink -f "${f}")"
|
||||
if [ -f "${target}" ]; then
|
||||
get_tar_relpath "${target}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
)
|
||||
|
||||
log " done (compressed size $(du -h "${cache_filepath}" | cut -f1))."
|
||||
fi
|
||||
|
||||
# Comma delimited name:ver pairs in the all packages manifest.
|
||||
manifest_all="${manifest_all}${package_name}=${package_ver},"
|
||||
done
|
||||
echo "done."
|
||||
log "done (total cache size $(du -h ${cache_dir} | tail -1 | awk '{print $1}'))"
|
||||
|
||||
manifest_filepath="$cache_dir/manifest.log"
|
||||
echo -n "Writing package manifest to $manifest_filepath..."
|
||||
manifest=
|
||||
for package in $packages; do
|
||||
manifest=$manifest$package:$(dpkg -s $package | grep Version | awk '{print $2}'),
|
||||
done
|
||||
# Remove trailing comma.
|
||||
echo ${manifest:0:-1} > $manifest_filepath
|
||||
echo "done."
|
||||
log_empty_line
|
||||
|
||||
write_manifest "all" "${manifest_all}" "${cache_dir}/manifest_all.log"
|
||||
|
|
|
|||
178
lib.sh
Executable file
178
lib.sh
Executable file
|
|
@ -0,0 +1,178 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Don't fail on error. We use the exit status as a conditional.
|
||||
#
|
||||
# This is the default behavior but can be overridden by the caller in the
|
||||
# SHELLOPTS env var.
|
||||
set +e
|
||||
|
||||
###############################################################################
|
||||
# Execute the Debian install script.
|
||||
# Arguments:
|
||||
# Root directory to search from.
|
||||
# File path to cached package archive.
|
||||
# Installation script extension (preinst, postinst).
|
||||
# Parameter to pass to the installation script.
|
||||
# Returns:
|
||||
# Filepath of the install script, otherwise an empty string.
|
||||
###############################################################################
|
||||
function execute_install_script {
|
||||
local package_name=$(basename ${2} | awk -F\= '{print $1}')
|
||||
local install_script_filepath=$(\
|
||||
get_install_script_filepath "${1}" "${package_name}" "${3}")
|
||||
if test ! -z "${install_script_filepath}"; then
|
||||
log "- Executing ${install_script_filepath}..."
|
||||
# Don't abort on errors; dpkg-trigger will error normally since it is
|
||||
# outside its run environment.
|
||||
sudo sh -x ${install_script_filepath} ${4} || true
|
||||
log " done"
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Gets the Debian install script filepath.
|
||||
# Arguments:
|
||||
# Root directory to search from.
|
||||
# Name of the unqualified package to search for.
|
||||
# Extension of the installation script (preinst, postinst)
|
||||
# Returns:
|
||||
# Filepath of the script file, otherwise an empty string.
|
||||
###############################################################################
|
||||
function get_install_script_filepath {
|
||||
# Filename includes arch (e.g. amd64).
|
||||
local filepath="$(\
|
||||
ls -1 ${1}var/lib/dpkg/info/${2}*.${3} 2> /dev/null \
|
||||
| grep -E ${2}'(:.*)?.'${3} | head -1 || true)"
|
||||
test "${filepath}" && echo "${filepath}"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Gets a list of installed packages from a Debian package installation log.
|
||||
# Arguments:
|
||||
# The filepath of the Debian install log.
|
||||
# Returns:
|
||||
# The list of colon delimited action syntax pairs with each pair equals
|
||||
# delimited. <name>:<version> <name>:<version>...
|
||||
###############################################################################
|
||||
function get_installed_packages {
|
||||
local install_log_filepath="${1}"
|
||||
local regex="^Unpacking ([^ :]+)([^ ]+)? (\[[^ ]+\]\s)?\(([^ )]+)"
|
||||
local dep_packages=""
|
||||
while read -r line; do
|
||||
# ${regex} should be unquoted since it isn't a literal.
|
||||
if [[ "${line}" =~ ${regex} ]]; then
|
||||
dep_packages="${dep_packages}${BASH_REMATCH[1]}=${BASH_REMATCH[4]} "
|
||||
else
|
||||
log_err "Unable to parse package name and version from \"${line}\""
|
||||
exit 2
|
||||
fi
|
||||
done < <(grep "^Unpacking " ${install_log_filepath})
|
||||
if test -n "${dep_packages}"; then
|
||||
echo "${dep_packages:0:-1}" # Removing trailing space.
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Splits a fully action syntax APT package into the name and version.
|
||||
# Arguments:
|
||||
# The action syntax equals delimited package pair or just the package name.
|
||||
# Returns:
|
||||
# The package name and version pair.
|
||||
###############################################################################
|
||||
function get_package_name_ver {
|
||||
local ORIG_IFS="${IFS}"
|
||||
IFS=\= read name ver <<< "${1}"
|
||||
IFS="${ORIG_IFS}"
|
||||
# If version not found in the fully qualified package value.
|
||||
if test -z "${ver}"; then
|
||||
# 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}"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Sorts given packages by name and split on commas and/or spaces.
|
||||
# Arguments:
|
||||
# The comma and/or space delimited list of packages.
|
||||
# Returns:
|
||||
# Sorted list of space delimited package name=version pairs.
|
||||
###############################################################################
|
||||
function get_normalized_package_list {
|
||||
# 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}")")"
|
||||
|
||||
local architecture=$(dpkg --print-architecture)
|
||||
if [ "${architecture}" == "arm64" ]; then
|
||||
${script_dir}/apt_query-arm64 normalized-list ${packages}
|
||||
else
|
||||
${script_dir}/apt_query-x86 normalized-list ${packages}
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Gets the relative filepath acceptable by Tar. Just removes the leading slash
|
||||
# that Tar disallows.
|
||||
# Arguments:
|
||||
# Absolute filepath to archive.
|
||||
# Returns:
|
||||
# The relative filepath to archive.
|
||||
###############################################################################
|
||||
function get_tar_relpath {
|
||||
local filepath=${1}
|
||||
if test ${filepath:0:1} = "/"; then
|
||||
echo "${filepath:1}"
|
||||
else
|
||||
echo "${filepath}"
|
||||
fi
|
||||
}
|
||||
|
||||
function log { echo "${@}"; }
|
||||
function log_err { >&2 echo "${@}"; }
|
||||
|
||||
function log_empty_line { echo ""; }
|
||||
|
||||
###############################################################################
|
||||
# Validates an argument to be of a boolean value.
|
||||
# Arguments:
|
||||
# Argument to validate.
|
||||
# Variable name of the argument.
|
||||
# Exit code if validation fails.
|
||||
# Returns:
|
||||
# Sorted list of space delimited packages.
|
||||
###############################################################################
|
||||
function validate_bool {
|
||||
if test "${1}" != "true" -a "${1}" != "false"; then
|
||||
log "aborted"
|
||||
log "${2} value '${1}' must be either true or false (case sensitive)."
|
||||
exit ${3}
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Writes the manifest to a specified file.
|
||||
# Arguments:
|
||||
# Type of manifest being written.
|
||||
# List of packages being written to the file.
|
||||
# File path of the manifest being written.
|
||||
# Returns:
|
||||
# Log lines from write.
|
||||
###############################################################################
|
||||
function write_manifest {
|
||||
if [ ${#2} -eq 0 ]; then
|
||||
log "Skipped ${1} manifest write. No packages to install."
|
||||
else
|
||||
log "Writing ${1} packages manifest to ${3}..."
|
||||
# 0:-1 to remove trailing comma, delimit by newline and sort.
|
||||
echo "${2:0:-1}" | tr ',' '\n' | sort > ${3}
|
||||
log "done"
|
||||
fi
|
||||
}
|
||||
|
|
@ -3,24 +3,38 @@
|
|||
# Fail on any error.
|
||||
set -e
|
||||
|
||||
# Include library.
|
||||
script_dir="$(dirname -- "$(realpath -- "${0}")")"
|
||||
source "${script_dir}/lib.sh"
|
||||
|
||||
# Directory that holds the cached packages.
|
||||
cache_dir=$1
|
||||
cache_dir="${1}"
|
||||
|
||||
# Root directory to untar the cached packages to.
|
||||
# Typically filesystem root '/' but can be changed for testing.
|
||||
cache_restore_root=$2
|
||||
# WARNING: If non-root, this can cause errors during install script execution.
|
||||
cache_restore_root="${2}"
|
||||
|
||||
# Indicates that the cache was found.
|
||||
cache_hit=$3
|
||||
cache_hit="${3}"
|
||||
|
||||
# Cache and execute post install scripts on restore.
|
||||
execute_install_scripts="${4}"
|
||||
|
||||
# Debug mode for diagnosing issues.
|
||||
debug="${5}"
|
||||
test "${debug}" = "true" && set -x
|
||||
|
||||
# Repositories to add before installing packages.
|
||||
add_repository="${6}"
|
||||
|
||||
# List of the packages to use.
|
||||
packages="${@:4}"
|
||||
packages="${@:7}"
|
||||
|
||||
script_dir=$(dirname $0)
|
||||
|
||||
if [ "$cache_hit" == true ]; then
|
||||
$script_dir/restore_pkgs.sh ~/cache-apt-pkgs $cache_restore_root
|
||||
if test "${cache_hit}" = "true"; then
|
||||
${script_dir}/restore_pkgs.sh "${cache_dir}" "${cache_restore_root}" "${execute_install_scripts}" "${debug}"
|
||||
else
|
||||
$script_dir/install_and_cache_pkgs.sh ~/cache-apt-pkgs $packages
|
||||
${script_dir}/install_and_cache_pkgs.sh "${cache_dir}" "${debug}" "${add_repository}" ${packages}
|
||||
fi
|
||||
echo ""
|
||||
|
||||
log_empty_line
|
||||
|
|
|
|||
|
|
@ -1,60 +1,123 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Include library.
|
||||
script_dir="$(dirname -- "$(realpath -- "${0}")")"
|
||||
source "${script_dir}/lib.sh"
|
||||
|
||||
# Debug mode for diagnosing issues.
|
||||
# Setup first before other operations.
|
||||
debug="${4}"
|
||||
validate_bool "${debug}" debug 1
|
||||
test ${debug} == "true" && set -x
|
||||
|
||||
# Directory that holds the cached packages.
|
||||
cache_dir=$1
|
||||
cache_dir="${1}"
|
||||
|
||||
# Version of the cache to create or load.
|
||||
version=$2
|
||||
version="${2}"
|
||||
|
||||
# Execute post-installation script.
|
||||
execute_install_scripts="${3}"
|
||||
|
||||
# Debug mode for diagnosing issues.
|
||||
debug="${4}"
|
||||
|
||||
# Repositories to add before installing packages.
|
||||
add_repository="${5}"
|
||||
|
||||
# List of the packages to use.
|
||||
packages=${@:3}
|
||||
input_packages="${@:6}"
|
||||
|
||||
# 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
|
||||
mkdir -p ${cache_dir}
|
||||
|
||||
echo -n "Validating action arguments (version='$version', packages='$packages')...";
|
||||
echo $version | grep -o " " > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "aborted."
|
||||
echo "Version value '$version' cannot contain spaces." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$packages" == "" ]; then
|
||||
echo "aborted."
|
||||
echo "Packages argument cannot be empty." >&2
|
||||
log "Validating action arguments (version='${version}', packages='${packages}')...";
|
||||
if grep -q " " <<< "${version}"; then
|
||||
log "aborted"
|
||||
log "Version value '${version}' cannot contain spaces." >&2
|
||||
exit 2
|
||||
fi
|
||||
echo "done."
|
||||
|
||||
echo -n "Verifying packages..."
|
||||
for package in $packages; do
|
||||
escaped=$(echo $package | sed 's/+/\\+/g')
|
||||
apt-cache search ^$escaped$ | grep $package > /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "aborted."
|
||||
echo "Package '$package' not found." >&2
|
||||
exit 3
|
||||
fi
|
||||
done
|
||||
echo "done."
|
||||
# Is length of string zero?
|
||||
if test -z "${packages}"; then
|
||||
case "$EMPTY_PACKAGES_BEHAVIOR" in
|
||||
ignore)
|
||||
exit 0
|
||||
;;
|
||||
warn)
|
||||
echo "::warning::Packages argument is empty."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log "aborted"
|
||||
log "Packages argument is empty." >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
validate_bool "${execute_install_scripts}" execute_install_scripts 4
|
||||
|
||||
# Basic validation for repository parameter
|
||||
if [ -n "${add_repository}" ]; then
|
||||
log "Validating repository parameter..."
|
||||
for repository in ${add_repository}; do
|
||||
# Check if repository format looks valid (basic check)
|
||||
if [[ "${repository}" =~ [^a-zA-Z0-9:\/.-] ]]; then
|
||||
log "aborted"
|
||||
log "Repository '${repository}' contains invalid characters." >&2
|
||||
log "Supported formats: 'ppa:user/repo', 'deb http://...', 'http://...', 'multiverse', etc." >&2
|
||||
exit 6
|
||||
fi
|
||||
done
|
||||
log "done"
|
||||
fi
|
||||
|
||||
log "done"
|
||||
|
||||
log_empty_line
|
||||
|
||||
# Abort on any failure at this point.
|
||||
set -e
|
||||
|
||||
echo "Creating cache key..."
|
||||
log "Creating cache key..."
|
||||
|
||||
# Remove package delimiters, sort (requires newline) and then convert back to inline list.
|
||||
normalized_list=$(echo $packages | sed 's/[\s,]+/ /g' | tr ' ' '\n' | sort | tr '\n' ' ')
|
||||
echo "- Normalized package list is '$normalized_list'."
|
||||
# Forces an update in cases where an accidental breaking change was introduced
|
||||
# and a global cache reset is required, or change in cache action requiring reload.
|
||||
force_update_inc="3"
|
||||
|
||||
value=$(echo $normalized_list @ $version)
|
||||
echo "- Value to hash is '$value'."
|
||||
# Force a different cache key for different architectures (currently x86_64 and aarch64 are available on GitHub)
|
||||
cpu_arch="$(arch)"
|
||||
log "- CPU architecture is '${cpu_arch}'."
|
||||
|
||||
key=$(echo $value | md5sum | /bin/cut -f1 -d' ')
|
||||
echo "- Value hashed as '$key'."
|
||||
value="${packages} @ ${version} ${force_update_inc}"
|
||||
|
||||
echo "done."
|
||||
# Include repositories in cache key to ensure different repos get different caches
|
||||
if [ -n "${add_repository}" ]; then
|
||||
value="${value} ${add_repository}"
|
||||
log "- Repositories '${add_repository}' added to value."
|
||||
fi
|
||||
|
||||
key_filepath="$cache_dir/cache_key.md5"
|
||||
echo $key > $key_filepath
|
||||
echo "Hash value written to $key_filepath"
|
||||
# Don't invalidate existing caches for the standard Ubuntu runners
|
||||
if [ "${cpu_arch}" != "x86_64" ]; then
|
||||
value="${value} ${cpu_arch}"
|
||||
log "- Architecture '${cpu_arch}' added to value."
|
||||
fi
|
||||
|
||||
log "- Value to hash is '${value}'."
|
||||
|
||||
key="$(echo "${value}" | md5sum | cut -f1 -d' ')"
|
||||
log "- Value hashed as '${key}'."
|
||||
|
||||
log "done"
|
||||
|
||||
key_filepath="${cache_dir}/cache_key.md5"
|
||||
echo ${key} > ${key_filepath}
|
||||
log "Hash value written to ${key_filepath}"
|
||||
|
|
|
|||
|
|
@ -3,32 +3,59 @@
|
|||
# Fail on any error.
|
||||
set -e
|
||||
|
||||
# Debug mode for diagnosing issues.
|
||||
# Setup first before other operations.
|
||||
debug="${4}"
|
||||
test ${debug} == "true" && set -x
|
||||
|
||||
# Include library.
|
||||
script_dir="$(dirname -- "$(realpath -- "${0}")")"
|
||||
source "${script_dir}/lib.sh"
|
||||
|
||||
# Directory that holds the cached packages.
|
||||
cache_dir=$1
|
||||
cache_dir="${1}"
|
||||
|
||||
# Root directory to untar the cached packages to.
|
||||
# Typically filesystem root '/' but can be changed for testing.
|
||||
cache_restore_root=$2
|
||||
cache_restore_root="${2}"
|
||||
test -d ${cache_restore_root} || mkdir ${cache_restore_root}
|
||||
|
||||
cache_filepaths=$(ls -1 $cache_dir | sort)
|
||||
echo "Found $(echo $cache_filepaths | wc -w) files in the cache."
|
||||
for cache_filepath in $cache_filepaths; do
|
||||
echo "- $(basename $cache_filepath)"
|
||||
# Cache and execute post install scripts on restore.
|
||||
execute_install_scripts="${3}"
|
||||
|
||||
cache_filepaths="$(ls -1 "${cache_dir}" | sort)"
|
||||
log "Found $(echo ${cache_filepaths} | wc -w) files in the cache."
|
||||
for cache_filepath in ${cache_filepaths}; do
|
||||
log "- "$(basename ${cache_filepath})""
|
||||
done
|
||||
|
||||
echo "Reading from manifest..."
|
||||
for logline in $(cat $cache_dir/manifest.log | tr ',' '\n' ); do
|
||||
echo "- $(echo $logline | tr ':' ' ')"
|
||||
log_empty_line
|
||||
|
||||
log "Reading from main requested packages manifest..."
|
||||
for logline in $(cat "${cache_dir}/manifest_main.log" | tr ',' '\n' ); do
|
||||
log "- $(echo "${logline}" | tr ':' ' ')"
|
||||
done
|
||||
echo "done."
|
||||
log "done"
|
||||
|
||||
log_empty_line
|
||||
|
||||
# Only search for archived results. Manifest and cache key also live here.
|
||||
cache_pkg_filepaths=$(ls -1 $cache_dir/*.tar.gz | sort)
|
||||
cache_pkg_filecount=$(echo $cache_pkg_filepaths | wc -w)
|
||||
echo "Restoring $cache_pkg_filecount packages from cache..."
|
||||
for cache_pkg_filepath in $cache_pkg_filepaths; do
|
||||
echo -n "- $(basename $cache_pkg_filepath) restoring..."
|
||||
sudo tar -xf $cache_pkg_filepath -C $cache_restore_root > /dev/null
|
||||
echo "done."
|
||||
cached_filepaths=$(ls -1 "${cache_dir}"/*.tar 2>/dev/null | sort)
|
||||
cached_filecount=$(echo ${cached_filepaths} | wc -w)
|
||||
|
||||
log "Restoring ${cached_filecount} packages from cache..."
|
||||
for cached_filepath in ${cached_filepaths}; do
|
||||
|
||||
log "- $(basename "${cached_filepath}") restoring..."
|
||||
sudo tar -xf "${cached_filepath}" -C "${cache_restore_root}" > /dev/null
|
||||
log " done"
|
||||
|
||||
# Execute install scripts if available.
|
||||
if test ${execute_install_scripts} == "true"; then
|
||||
# May have to add more handling for extracting pre-install script before extracting all files.
|
||||
# Keeping it simple for now.
|
||||
execute_install_script "${cache_restore_root}" "${cached_filepath}" preinst install
|
||||
execute_install_script "${cache_restore_root}" "${cached_filepath}" postinst configure
|
||||
fi
|
||||
done
|
||||
echo "done."
|
||||
log "done"
|
||||
|
|
|
|||
53
src/cmd/apt_query/main.go
Normal file
53
src/cmd/apt_query/main.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/common"
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/exec"
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/logging"
|
||||
)
|
||||
|
||||
func getExecutor(replayFilename string) exec.Executor {
|
||||
if len(replayFilename) == 0 {
|
||||
return &exec.BinExecutor{}
|
||||
}
|
||||
return exec.NewReplayExecutor(replayFilename)
|
||||
}
|
||||
|
||||
func main() {
|
||||
debug := flag.Bool("debug", false, "Log diagnostic information to a file alongside the binary.")
|
||||
|
||||
replayFilename := flag.String("replayfile", "",
|
||||
"Replay command output from a specified file rather than executing a binary."+
|
||||
"The file should be in the same format as the log generated by the debug flag.")
|
||||
|
||||
flag.Parse()
|
||||
unparsedFlags := flag.Args()
|
||||
|
||||
logging.Init(os.Args[0]+".log", *debug)
|
||||
|
||||
executor := getExecutor(*replayFilename)
|
||||
|
||||
if len(unparsedFlags) < 2 {
|
||||
logging.Fatalf("Expected at least 2 non-flag arguments but found %d.", len(unparsedFlags))
|
||||
return
|
||||
}
|
||||
command := unparsedFlags[0]
|
||||
pkgNames := unparsedFlags[1:]
|
||||
|
||||
switch command {
|
||||
|
||||
case "normalized-list":
|
||||
pkgs, err := common.GetAptPackages(executor, pkgNames)
|
||||
if err != nil {
|
||||
logging.Fatalf("Encountered error resolving some or all package names, see combined std[out,err] below.\n%s", err.Error())
|
||||
}
|
||||
fmt.Println(pkgs.Serialize())
|
||||
|
||||
default:
|
||||
logging.Fatalf("Command '%s' not recognized.", command)
|
||||
}
|
||||
}
|
||||
70
src/cmd/apt_query/main_test.go
Normal file
70
src/cmd/apt_query/main_test.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/cmdtesting"
|
||||
)
|
||||
|
||||
var createReplayLogs bool = false
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&createReplayLogs, "createreplaylogs", false, "Execute the test commands, save the command output for future replay and skip the tests themselves.")
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cmdtesting.TestMain(m)
|
||||
}
|
||||
|
||||
func TestNormalizedList_MultiplePackagesExists_StdoutsAlphaSortedPackageNameVersionPairs(t *testing.T) {
|
||||
result := cmdtesting.New(t, createReplayLogs).Run("normalized-list", "xdot", "rolldice")
|
||||
result.ExpectSuccessfulOut("rolldice=1.16-1build1 xdot=1.1-2")
|
||||
}
|
||||
|
||||
func TestNormalizedList_SamePackagesDifferentOrder_StdoutsMatch(t *testing.T) {
|
||||
expected := "rolldice=1.16-1build1 xdot=1.1-2"
|
||||
|
||||
ct := cmdtesting.New(t, createReplayLogs)
|
||||
|
||||
result := ct.Run("normalized-list", "rolldice", "xdot")
|
||||
result.ExpectSuccessfulOut(expected)
|
||||
|
||||
result = ct.Run("normalized-list", "xdot", "rolldice")
|
||||
result.ExpectSuccessfulOut(expected)
|
||||
}
|
||||
|
||||
func TestNormalizedList_MultiVersionWarning_StdoutSingleVersion(t *testing.T) {
|
||||
var result = cmdtesting.New(t, createReplayLogs).Run("normalized-list", "libosmesa6-dev", "libgl1-mesa-dev")
|
||||
result.ExpectSuccessfulOut("libgl1-mesa-dev=21.2.6-0ubuntu0.1~20.04.2 libosmesa6-dev=21.2.6-0ubuntu0.1~20.04.2")
|
||||
}
|
||||
|
||||
func TestNormalizedList_SinglePackageExists_StdoutsSinglePackageNameVersionPair(t *testing.T) {
|
||||
var result = cmdtesting.New(t, createReplayLogs).Run("normalized-list", "xdot")
|
||||
result.ExpectSuccessfulOut("xdot=1.1-2")
|
||||
}
|
||||
|
||||
func TestNormalizedList_VersionContainsColon_StdoutsEntireVersion(t *testing.T) {
|
||||
var result = cmdtesting.New(t, createReplayLogs).Run("normalized-list", "default-jre")
|
||||
result.ExpectSuccessfulOut("default-jre=2:1.11-72")
|
||||
}
|
||||
|
||||
func TestNormalizedList_NonExistentPackageName_StderrsAptCacheErrors(t *testing.T) {
|
||||
var result = cmdtesting.New(t, createReplayLogs).Run("normalized-list", "nonexistentpackagename")
|
||||
result.ExpectError(
|
||||
`Error encountered running apt-cache --quiet=0 --no-all-versions show nonexistentpackagename
|
||||
Exited with status code 100; see combined std[out,err] below:
|
||||
N: Unable to locate package nonexistentpackagename
|
||||
N: Unable to locate package nonexistentpackagename
|
||||
E: No packages found`)
|
||||
}
|
||||
|
||||
func TestNormalizedList_NoPackagesGiven_StderrsArgMismatch(t *testing.T) {
|
||||
var result = cmdtesting.New(t, createReplayLogs).Run("normalized-list")
|
||||
result.ExpectError("Expected at least 2 non-flag arguments but found 1.")
|
||||
}
|
||||
|
||||
func TestNormalizedList_VirtualPackagesExists_StdoutsConcretePackage(t *testing.T) {
|
||||
result := cmdtesting.New(t, createReplayLogs).Run("normalized-list", "libvips")
|
||||
result.ExpectSuccessfulOut("libvips42=8.9.1-2")
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
2025/03/15 22:29:08 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:09 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "apt-cache --quiet=0 --no-all-versions show xdot rolldice",
|
||||
"Stdout": "Package: xdot\nArchitecture: all\nVersion: 1.1-2\nPriority: optional\nSection: universe/python\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Python Applications Packaging Team \u003cpython-apps-team@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 153\nDepends: gir1.2-gtk-3.0, graphviz, python3-gi, python3-gi-cairo, python3:any\nFilename: pool/universe/x/xdot/xdot_1.1-2_all.deb\nSize: 26708\nMD5sum: aab630b6e1f73a0e3ae85b208b8b6d00\nSHA1: 202155c123c7bd7628023b848e997f342d14359d\nSHA256: 4b7ecb2c4dc948a850024a9b7378d58195230659307bbde4018a1be17645e690\nHomepage: https://github.com/jrfonseca/xdot.py\nDescription-en: interactive viewer for Graphviz dot files\n xdot is an interactive viewer for graphs written in Graphviz's dot language.\n It uses internally the graphviz's xdot output format as an intermediate\n format, and PyGTK and Cairo for rendering. xdot can be used either as a\n standalone application from command line, or as a library embedded in your\n Python 3 application.\n .\n Features:\n * Since it doesn't use bitmaps it is fast and has a small memory footprint.\n * Arbitrary zoom.\n * Keyboard/mouse navigation.\n * Supports events on the nodes with URLs.\n * Animated jumping between nodes.\n * Highlights node/edge under mouse.\nDescription-md5: eb58f25a628b48a744f1b904af3b9282\n\nPackage: rolldice\nArchitecture: amd64\nVersion: 1.16-1build1\nPriority: optional\nSection: universe/games\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Thomas Ross \u003cthomasross@thomasross.io\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 31\nDepends: libc6 (\u003e= 2.7), libreadline8 (\u003e= 6.0)\nFilename: pool/universe/r/rolldice/rolldice_1.16-1build1_amd64.deb\nSize: 9628\nMD5sum: af6390bf2d5d5b4710d308ac06d4913a\nSHA1: 1d87ccac5b20f4e2a217a0e058408f46cfe5caff\nSHA256: 2e076006200057da0be52060e3cc2f4fc7c51212867173e727590bd7603a0337\nHomepage: https://github.com/sstrickl/rolldice\nDescription-en: virtual dice roller\n rolldice is a virtual dice roller that takes a string on the command\n line in the format of some fantasy role playing games like Advanced\n Dungeons \u0026 Dragons [1] and returns the result of the dice rolls.\n .\n [1] Advanced Dungeons \u0026 Dragons is a registered trademark of TSR, Inc.\nDescription-md5: fc24e9e12c794a8f92ab0ca6e1058501\n\n",
|
||||
"Stderr": "",
|
||||
"CombinedOut": "Package: xdot\nArchitecture: all\nVersion: 1.1-2\nPriority: optional\nSection: universe/python\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Python Applications Packaging Team \u003cpython-apps-team@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 153\nDepends: gir1.2-gtk-3.0, graphviz, python3-gi, python3-gi-cairo, python3:any\nFilename: pool/universe/x/xdot/xdot_1.1-2_all.deb\nSize: 26708\nMD5sum: aab630b6e1f73a0e3ae85b208b8b6d00\nSHA1: 202155c123c7bd7628023b848e997f342d14359d\nSHA256: 4b7ecb2c4dc948a850024a9b7378d58195230659307bbde4018a1be17645e690\nHomepage: https://github.com/jrfonseca/xdot.py\nDescription-en: interactive viewer for Graphviz dot files\n xdot is an interactive viewer for graphs written in Graphviz's dot language.\n It uses internally the graphviz's xdot output format as an intermediate\n format, and PyGTK and Cairo for rendering. xdot can be used either as a\n standalone application from command line, or as a library embedded in your\n Python 3 application.\n .\n Features:\n * Since it doesn't use bitmaps it is fast and has a small memory footprint.\n * Arbitrary zoom.\n * Keyboard/mouse navigation.\n * Supports events on the nodes with URLs.\n * Animated jumping between nodes.\n * Highlights node/edge under mouse.\nDescription-md5: eb58f25a628b48a744f1b904af3b9282\n\nPackage: rolldice\nArchitecture: amd64\nVersion: 1.16-1build1\nPriority: optional\nSection: universe/games\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Thomas Ross \u003cthomasross@thomasross.io\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 31\nDepends: libc6 (\u003e= 2.7), libreadline8 (\u003e= 6.0)\nFilename: pool/universe/r/rolldice/rolldice_1.16-1build1_amd64.deb\nSize: 9628\nMD5sum: af6390bf2d5d5b4710d308ac06d4913a\nSHA1: 1d87ccac5b20f4e2a217a0e058408f46cfe5caff\nSHA256: 2e076006200057da0be52060e3cc2f4fc7c51212867173e727590bd7603a0337\nHomepage: https://github.com/sstrickl/rolldice\nDescription-en: virtual dice roller\n rolldice is a virtual dice roller that takes a string on the command\n line in the format of some fantasy role playing games like Advanced\n Dungeons \u0026 Dragons [1] and returns the result of the dice rolls.\n .\n [1] Advanced Dungeons \u0026 Dragons is a registered trademark of TSR, Inc.\nDescription-md5: fc24e9e12c794a8f92ab0ca6e1058501\n\n",
|
||||
"ExitCode": 0
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
2025/03/15 22:29:10 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:12 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "apt-cache --quiet=0 --no-all-versions show libosmesa6-dev libgl1-mesa-dev",
|
||||
"Stdout": "Package: libosmesa6-dev\nArchitecture: amd64\nVersion: 21.2.6-0ubuntu0.1~20.04.2\nMulti-Arch: same\nPriority: extra\nSection: devel\nSource: mesa\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Debian X Strike Force \u003cdebian-x@lists.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 88\nProvides: libosmesa-dev\nDepends: libosmesa6 (= 21.2.6-0ubuntu0.1~20.04.2), mesa-common-dev (= 21.2.6-0ubuntu0.1~20.04.2) | libgl-dev\nConflicts: libosmesa-dev\nReplaces: libosmesa-dev\nFilename: pool/main/m/mesa/libosmesa6-dev_21.2.6-0ubuntu0.1~20.04.2_amd64.deb\nSize: 8844\nMD5sum: b6c380d1b916bc6955aaf108a3be468e\nSHA1: 4b772c8127e60a342dabec4ff0939969d99038b4\nSHA256: bf003b66573d611877664e01659046d281b26698f3665345cb784ddded662c6a\nSHA512: 761557925874473e4408504772a0af4d29f6dc1dcbd53e772315dffb6da87d47960edca4de39deda7cae33a8730d87a19b40a7d29739ba7cff5b60ee4900a13a\nHomepage: https://mesa3d.org/\nDescription-en: Mesa Off-screen rendering extension -- development files\n This package provides the required environment for developing programs\n that use the off-screen rendering extension of Mesa.\n .\n For more information on OSmesa see the libosmesa6 package.\nDescription-md5: 9b1d7a0b3e6a2ea021f4443f42dcff4f\n\nPackage: libgl1-mesa-dev\nArchitecture: amd64\nVersion: 21.2.6-0ubuntu0.1~20.04.2\nMulti-Arch: same\nPriority: extra\nSection: libdevel\nSource: mesa\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Debian X Strike Force \u003cdebian-x@lists.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 70\nDepends: libgl-dev, libglvnd-dev\nFilename: pool/main/m/mesa/libgl1-mesa-dev_21.2.6-0ubuntu0.1~20.04.2_amd64.deb\nSize: 6420\nMD5sum: 759a811dcb12adfcebfc1a6aa52e85b9\nSHA1: 3b9de17b1c67ee40603e1eebaefa978810a2f2d2\nSHA256: 76846d96ae0706a7edcd514d452a1393bb8b8a8ac06518253dd5869441807052\nSHA512: 581e4b3752b4c98399f3519fce2c5ab033cea3cac66bde3c204af769ff0377400087f9c4c6aaebe06c19d05f8715b3346d249a86c3ae80a098ca476e76af01c3\nHomepage: https://mesa3d.org/\nDescription-en: transitional dummy package\n This is a transitional dummy package, it can be safely removed.\nDescription-md5: 635a93bcd1440d16621693fe064c2aa9\n\n",
|
||||
"Stderr": "N: There are 2 additional records. Please use the '-a' switch to see them.\n",
|
||||
"CombinedOut": "Package: libosmesa6-dev\nArchitecture: amd64\nVersion: 21.2.6-0ubuntu0.1~20.04.2\nMulti-Arch: same\nPriority: extra\nSection: devel\nSource: mesa\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Debian X Strike Force \u003cdebian-x@lists.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 88\nProvides: libosmesa-dev\nDepends: libosmesa6 (= 21.2.6-0ubuntu0.1~20.04.2), mesa-common-dev (= 21.2.6-0ubuntu0.1~20.04.2) | libgl-dev\nConflicts: libosmesa-dev\nReplaces: libosmesa-dev\nFilename: pool/main/m/mesa/libosmesa6-dev_21.2.6-0ubuntu0.1~20.04.2_amd64.deb\nSize: 8844\nMD5sum: b6c380d1b916bc6955aaf108a3be468e\nSHA1: 4b772c8127e60a342dabec4ff0939969d99038b4\nSHA256: bf003b66573d611877664e01659046d281b26698f3665345cb784ddded662c6a\nSHA512: 761557925874473e4408504772a0af4d29f6dc1dcbd53e772315dffb6da87d47960edca4de39deda7cae33a8730d87a19b40a7d29739ba7cff5b60ee4900a13a\nHomepage: https://mesa3d.org/\nDescription-en: Mesa Off-screen rendering extension -- development files\n This package provides the required environment for developing programs\n that use the off-screen rendering extension of Mesa.\n .\n For more information on OSmesa see the libosmesa6 package.\nDescription-md5: 9b1d7a0b3e6a2ea021f4443f42dcff4f\n\nPackage: libgl1-mesa-dev\nArchitecture: amd64\nVersion: 21.2.6-0ubuntu0.1~20.04.2\nMulti-Arch: same\nPriority: extra\nSection: libdevel\nSource: mesa\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Debian X Strike Force \u003cdebian-x@lists.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 70\nDepends: libgl-dev, libglvnd-dev\nFilename: pool/main/m/mesa/libgl1-mesa-dev_21.2.6-0ubuntu0.1~20.04.2_amd64.deb\nSize: 6420\nMD5sum: 759a811dcb12adfcebfc1a6aa52e85b9\nSHA1: 3b9de17b1c67ee40603e1eebaefa978810a2f2d2\nSHA256: 76846d96ae0706a7edcd514d452a1393bb8b8a8ac06518253dd5869441807052\nSHA512: 581e4b3752b4c98399f3519fce2c5ab033cea3cac66bde3c204af769ff0377400087f9c4c6aaebe06c19d05f8715b3346d249a86c3ae80a098ca476e76af01c3\nHomepage: https://mesa3d.org/\nDescription-en: transitional dummy package\n This is a transitional dummy package, it can be safely removed.\nDescription-md5: 635a93bcd1440d16621693fe064c2aa9\n\nN: There are 2 additional records. Please use the '-a' switch to see them.\n",
|
||||
"ExitCode": 0
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
2025/03/15 22:29:13 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:14 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "apt-cache --quiet=0 --no-all-versions show nonexistentpackagename",
|
||||
"Stdout": "",
|
||||
"Stderr": "N: Unable to locate package nonexistentpackagename\nN: Unable to locate package nonexistentpackagename\nE: No packages found\n",
|
||||
"CombinedOut": "N: Unable to locate package nonexistentpackagename\nN: Unable to locate package nonexistentpackagename\nE: No packages found\n",
|
||||
"ExitCode": 100
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
2025/03/15 22:29:14 Error encountered running apt-cache --quiet=0 --no-all-versions show nonexistentpackagename
|
||||
Exited with status code 100; see combined std[out,err] below:
|
||||
N: Unable to locate package nonexistentpackagename
|
||||
N: Unable to locate package nonexistentpackagename
|
||||
E: No packages found
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
2025/03/15 22:29:14 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:14 Expected at least 2 non-flag arguments but found 1.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
2025/03/15 22:29:09 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:10 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "apt-cache --quiet=0 --no-all-versions show rolldice xdot",
|
||||
"Stdout": "Package: rolldice\nArchitecture: amd64\nVersion: 1.16-1build1\nPriority: optional\nSection: universe/games\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Thomas Ross \u003cthomasross@thomasross.io\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 31\nDepends: libc6 (\u003e= 2.7), libreadline8 (\u003e= 6.0)\nFilename: pool/universe/r/rolldice/rolldice_1.16-1build1_amd64.deb\nSize: 9628\nMD5sum: af6390bf2d5d5b4710d308ac06d4913a\nSHA1: 1d87ccac5b20f4e2a217a0e058408f46cfe5caff\nSHA256: 2e076006200057da0be52060e3cc2f4fc7c51212867173e727590bd7603a0337\nHomepage: https://github.com/sstrickl/rolldice\nDescription-en: virtual dice roller\n rolldice is a virtual dice roller that takes a string on the command\n line in the format of some fantasy role playing games like Advanced\n Dungeons \u0026 Dragons [1] and returns the result of the dice rolls.\n .\n [1] Advanced Dungeons \u0026 Dragons is a registered trademark of TSR, Inc.\nDescription-md5: fc24e9e12c794a8f92ab0ca6e1058501\n\nPackage: xdot\nArchitecture: all\nVersion: 1.1-2\nPriority: optional\nSection: universe/python\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Python Applications Packaging Team \u003cpython-apps-team@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 153\nDepends: gir1.2-gtk-3.0, graphviz, python3-gi, python3-gi-cairo, python3:any\nFilename: pool/universe/x/xdot/xdot_1.1-2_all.deb\nSize: 26708\nMD5sum: aab630b6e1f73a0e3ae85b208b8b6d00\nSHA1: 202155c123c7bd7628023b848e997f342d14359d\nSHA256: 4b7ecb2c4dc948a850024a9b7378d58195230659307bbde4018a1be17645e690\nHomepage: https://github.com/jrfonseca/xdot.py\nDescription-en: interactive viewer for Graphviz dot files\n xdot is an interactive viewer for graphs written in Graphviz's dot language.\n It uses internally the graphviz's xdot output format as an intermediate\n format, and PyGTK and Cairo for rendering. xdot can be used either as a\n standalone application from command line, or as a library embedded in your\n Python 3 application.\n .\n Features:\n * Since it doesn't use bitmaps it is fast and has a small memory footprint.\n * Arbitrary zoom.\n * Keyboard/mouse navigation.\n * Supports events on the nodes with URLs.\n * Animated jumping between nodes.\n * Highlights node/edge under mouse.\nDescription-md5: eb58f25a628b48a744f1b904af3b9282\n\n",
|
||||
"Stderr": "",
|
||||
"CombinedOut": "Package: rolldice\nArchitecture: amd64\nVersion: 1.16-1build1\nPriority: optional\nSection: universe/games\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Thomas Ross \u003cthomasross@thomasross.io\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 31\nDepends: libc6 (\u003e= 2.7), libreadline8 (\u003e= 6.0)\nFilename: pool/universe/r/rolldice/rolldice_1.16-1build1_amd64.deb\nSize: 9628\nMD5sum: af6390bf2d5d5b4710d308ac06d4913a\nSHA1: 1d87ccac5b20f4e2a217a0e058408f46cfe5caff\nSHA256: 2e076006200057da0be52060e3cc2f4fc7c51212867173e727590bd7603a0337\nHomepage: https://github.com/sstrickl/rolldice\nDescription-en: virtual dice roller\n rolldice is a virtual dice roller that takes a string on the command\n line in the format of some fantasy role playing games like Advanced\n Dungeons \u0026 Dragons [1] and returns the result of the dice rolls.\n .\n [1] Advanced Dungeons \u0026 Dragons is a registered trademark of TSR, Inc.\nDescription-md5: fc24e9e12c794a8f92ab0ca6e1058501\n\nPackage: xdot\nArchitecture: all\nVersion: 1.1-2\nPriority: optional\nSection: universe/python\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Python Applications Packaging Team \u003cpython-apps-team@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 153\nDepends: gir1.2-gtk-3.0, graphviz, python3-gi, python3-gi-cairo, python3:any\nFilename: pool/universe/x/xdot/xdot_1.1-2_all.deb\nSize: 26708\nMD5sum: aab630b6e1f73a0e3ae85b208b8b6d00\nSHA1: 202155c123c7bd7628023b848e997f342d14359d\nSHA256: 4b7ecb2c4dc948a850024a9b7378d58195230659307bbde4018a1be17645e690\nHomepage: https://github.com/jrfonseca/xdot.py\nDescription-en: interactive viewer for Graphviz dot files\n xdot is an interactive viewer for graphs written in Graphviz's dot language.\n It uses internally the graphviz's xdot output format as an intermediate\n format, and PyGTK and Cairo for rendering. xdot can be used either as a\n standalone application from command line, or as a library embedded in your\n Python 3 application.\n .\n Features:\n * Since it doesn't use bitmaps it is fast and has a small memory footprint.\n * Arbitrary zoom.\n * Keyboard/mouse navigation.\n * Supports events on the nodes with URLs.\n * Animated jumping between nodes.\n * Highlights node/edge under mouse.\nDescription-md5: eb58f25a628b48a744f1b904af3b9282\n\n",
|
||||
"ExitCode": 0
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
2025/03/15 22:29:10 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:10 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "apt-cache --quiet=0 --no-all-versions show xdot rolldice",
|
||||
"Stdout": "Package: xdot\nArchitecture: all\nVersion: 1.1-2\nPriority: optional\nSection: universe/python\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Python Applications Packaging Team \u003cpython-apps-team@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 153\nDepends: gir1.2-gtk-3.0, graphviz, python3-gi, python3-gi-cairo, python3:any\nFilename: pool/universe/x/xdot/xdot_1.1-2_all.deb\nSize: 26708\nMD5sum: aab630b6e1f73a0e3ae85b208b8b6d00\nSHA1: 202155c123c7bd7628023b848e997f342d14359d\nSHA256: 4b7ecb2c4dc948a850024a9b7378d58195230659307bbde4018a1be17645e690\nHomepage: https://github.com/jrfonseca/xdot.py\nDescription-en: interactive viewer for Graphviz dot files\n xdot is an interactive viewer for graphs written in Graphviz's dot language.\n It uses internally the graphviz's xdot output format as an intermediate\n format, and PyGTK and Cairo for rendering. xdot can be used either as a\n standalone application from command line, or as a library embedded in your\n Python 3 application.\n .\n Features:\n * Since it doesn't use bitmaps it is fast and has a small memory footprint.\n * Arbitrary zoom.\n * Keyboard/mouse navigation.\n * Supports events on the nodes with URLs.\n * Animated jumping between nodes.\n * Highlights node/edge under mouse.\nDescription-md5: eb58f25a628b48a744f1b904af3b9282\n\nPackage: rolldice\nArchitecture: amd64\nVersion: 1.16-1build1\nPriority: optional\nSection: universe/games\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Thomas Ross \u003cthomasross@thomasross.io\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 31\nDepends: libc6 (\u003e= 2.7), libreadline8 (\u003e= 6.0)\nFilename: pool/universe/r/rolldice/rolldice_1.16-1build1_amd64.deb\nSize: 9628\nMD5sum: af6390bf2d5d5b4710d308ac06d4913a\nSHA1: 1d87ccac5b20f4e2a217a0e058408f46cfe5caff\nSHA256: 2e076006200057da0be52060e3cc2f4fc7c51212867173e727590bd7603a0337\nHomepage: https://github.com/sstrickl/rolldice\nDescription-en: virtual dice roller\n rolldice is a virtual dice roller that takes a string on the command\n line in the format of some fantasy role playing games like Advanced\n Dungeons \u0026 Dragons [1] and returns the result of the dice rolls.\n .\n [1] Advanced Dungeons \u0026 Dragons is a registered trademark of TSR, Inc.\nDescription-md5: fc24e9e12c794a8f92ab0ca6e1058501\n\n",
|
||||
"Stderr": "",
|
||||
"CombinedOut": "Package: xdot\nArchitecture: all\nVersion: 1.1-2\nPriority: optional\nSection: universe/python\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Python Applications Packaging Team \u003cpython-apps-team@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 153\nDepends: gir1.2-gtk-3.0, graphviz, python3-gi, python3-gi-cairo, python3:any\nFilename: pool/universe/x/xdot/xdot_1.1-2_all.deb\nSize: 26708\nMD5sum: aab630b6e1f73a0e3ae85b208b8b6d00\nSHA1: 202155c123c7bd7628023b848e997f342d14359d\nSHA256: 4b7ecb2c4dc948a850024a9b7378d58195230659307bbde4018a1be17645e690\nHomepage: https://github.com/jrfonseca/xdot.py\nDescription-en: interactive viewer for Graphviz dot files\n xdot is an interactive viewer for graphs written in Graphviz's dot language.\n It uses internally the graphviz's xdot output format as an intermediate\n format, and PyGTK and Cairo for rendering. xdot can be used either as a\n standalone application from command line, or as a library embedded in your\n Python 3 application.\n .\n Features:\n * Since it doesn't use bitmaps it is fast and has a small memory footprint.\n * Arbitrary zoom.\n * Keyboard/mouse navigation.\n * Supports events on the nodes with URLs.\n * Animated jumping between nodes.\n * Highlights node/edge under mouse.\nDescription-md5: eb58f25a628b48a744f1b904af3b9282\n\nPackage: rolldice\nArchitecture: amd64\nVersion: 1.16-1build1\nPriority: optional\nSection: universe/games\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Thomas Ross \u003cthomasross@thomasross.io\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 31\nDepends: libc6 (\u003e= 2.7), libreadline8 (\u003e= 6.0)\nFilename: pool/universe/r/rolldice/rolldice_1.16-1build1_amd64.deb\nSize: 9628\nMD5sum: af6390bf2d5d5b4710d308ac06d4913a\nSHA1: 1d87ccac5b20f4e2a217a0e058408f46cfe5caff\nSHA256: 2e076006200057da0be52060e3cc2f4fc7c51212867173e727590bd7603a0337\nHomepage: https://github.com/sstrickl/rolldice\nDescription-en: virtual dice roller\n rolldice is a virtual dice roller that takes a string on the command\n line in the format of some fantasy role playing games like Advanced\n Dungeons \u0026 Dragons [1] and returns the result of the dice rolls.\n .\n [1] Advanced Dungeons \u0026 Dragons is a registered trademark of TSR, Inc.\nDescription-md5: fc24e9e12c794a8f92ab0ca6e1058501\n\n",
|
||||
"ExitCode": 0
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
2025/03/15 22:29:12 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:12 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "apt-cache --quiet=0 --no-all-versions show xdot",
|
||||
"Stdout": "Package: xdot\nArchitecture: all\nVersion: 1.1-2\nPriority: optional\nSection: universe/python\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Python Applications Packaging Team \u003cpython-apps-team@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 153\nDepends: gir1.2-gtk-3.0, graphviz, python3-gi, python3-gi-cairo, python3:any\nFilename: pool/universe/x/xdot/xdot_1.1-2_all.deb\nSize: 26708\nMD5sum: aab630b6e1f73a0e3ae85b208b8b6d00\nSHA1: 202155c123c7bd7628023b848e997f342d14359d\nSHA256: 4b7ecb2c4dc948a850024a9b7378d58195230659307bbde4018a1be17645e690\nHomepage: https://github.com/jrfonseca/xdot.py\nDescription-en: interactive viewer for Graphviz dot files\n xdot is an interactive viewer for graphs written in Graphviz's dot language.\n It uses internally the graphviz's xdot output format as an intermediate\n format, and PyGTK and Cairo for rendering. xdot can be used either as a\n standalone application from command line, or as a library embedded in your\n Python 3 application.\n .\n Features:\n * Since it doesn't use bitmaps it is fast and has a small memory footprint.\n * Arbitrary zoom.\n * Keyboard/mouse navigation.\n * Supports events on the nodes with URLs.\n * Animated jumping between nodes.\n * Highlights node/edge under mouse.\nDescription-md5: eb58f25a628b48a744f1b904af3b9282\n\n",
|
||||
"Stderr": "",
|
||||
"CombinedOut": "Package: xdot\nArchitecture: all\nVersion: 1.1-2\nPriority: optional\nSection: universe/python\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Python Applications Packaging Team \u003cpython-apps-team@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 153\nDepends: gir1.2-gtk-3.0, graphviz, python3-gi, python3-gi-cairo, python3:any\nFilename: pool/universe/x/xdot/xdot_1.1-2_all.deb\nSize: 26708\nMD5sum: aab630b6e1f73a0e3ae85b208b8b6d00\nSHA1: 202155c123c7bd7628023b848e997f342d14359d\nSHA256: 4b7ecb2c4dc948a850024a9b7378d58195230659307bbde4018a1be17645e690\nHomepage: https://github.com/jrfonseca/xdot.py\nDescription-en: interactive viewer for Graphviz dot files\n xdot is an interactive viewer for graphs written in Graphviz's dot language.\n It uses internally the graphviz's xdot output format as an intermediate\n format, and PyGTK and Cairo for rendering. xdot can be used either as a\n standalone application from command line, or as a library embedded in your\n Python 3 application.\n .\n Features:\n * Since it doesn't use bitmaps it is fast and has a small memory footprint.\n * Arbitrary zoom.\n * Keyboard/mouse navigation.\n * Supports events on the nodes with URLs.\n * Animated jumping between nodes.\n * Highlights node/edge under mouse.\nDescription-md5: eb58f25a628b48a744f1b904af3b9282\n\n",
|
||||
"ExitCode": 0
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
2025/03/15 22:29:12 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:13 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "apt-cache --quiet=0 --no-all-versions show default-jre",
|
||||
"Stdout": "Package: default-jre\nArchitecture: amd64\nVersion: 2:1.11-72\nPriority: optional\nSection: interpreters\nSource: java-common (0.72)\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Debian Java Maintainers \u003cpkg-java-maintainers@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 6\nProvides: java-runtime, java10-runtime, java11-runtime, java2-runtime, java5-runtime, java6-runtime, java7-runtime, java8-runtime, java9-runtime\nDepends: default-jre-headless (= 2:1.11-72), openjdk-11-jre\nFilename: pool/main/j/java-common/default-jre_1.11-72_amd64.deb\nSize: 1084\nMD5sum: 4f441bb884801f3a07806934e4519652\nSHA1: 9922edaa7bd91921a2beee6c60343ebf551957a9\nSHA256: 063bb2ca3b51309f6625033c336beffb0eb8286aaabcf3bf917eef498de29ea5\nHomepage: https://wiki.debian.org/Java/\nDescription-en: Standard Java or Java compatible Runtime\n This dependency package points to the Java runtime, or Java compatible\n runtime recommended for this architecture, which is\n openjdk-11-jre for amd64.\nDescription-md5: 071b7a2f9baf048d89c849c14bcafb9e\nCnf-Extra-Commands: java,jexec\n\n",
|
||||
"Stderr": "",
|
||||
"CombinedOut": "Package: default-jre\nArchitecture: amd64\nVersion: 2:1.11-72\nPriority: optional\nSection: interpreters\nSource: java-common (0.72)\nOrigin: Ubuntu\nMaintainer: Ubuntu Developers \u003cubuntu-devel-discuss@lists.ubuntu.com\u003e\nOriginal-Maintainer: Debian Java Maintainers \u003cpkg-java-maintainers@lists.alioth.debian.org\u003e\nBugs: https://bugs.launchpad.net/ubuntu/+filebug\nInstalled-Size: 6\nProvides: java-runtime, java10-runtime, java11-runtime, java2-runtime, java5-runtime, java6-runtime, java7-runtime, java8-runtime, java9-runtime\nDepends: default-jre-headless (= 2:1.11-72), openjdk-11-jre\nFilename: pool/main/j/java-common/default-jre_1.11-72_amd64.deb\nSize: 1084\nMD5sum: 4f441bb884801f3a07806934e4519652\nSHA1: 9922edaa7bd91921a2beee6c60343ebf551957a9\nSHA256: 063bb2ca3b51309f6625033c336beffb0eb8286aaabcf3bf917eef498de29ea5\nHomepage: https://wiki.debian.org/Java/\nDescription-en: Standard Java or Java compatible Runtime\n This dependency package points to the Java runtime, or Java compatible\n runtime recommended for this architecture, which is\n openjdk-11-jre for amd64.\nDescription-md5: 071b7a2f9baf048d89c849c14bcafb9e\nCnf-Extra-Commands: java,jexec\n\n",
|
||||
"ExitCode": 0
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
2025/03/15 22:29:14 Debug log created at /home/awalsh128/cache-apt-pkgs-action/src/cmd/apt_query/apt_query.log
|
||||
2025/03/15 22:29:14 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "apt-cache --quiet=0 --no-all-versions show libvips",
|
||||
"Stdout": "",
|
||||
"Stderr": "N: Can't select candidate version from package libvips as it has no candidate\nN: Can't select versions from package 'libvips' as it is purely virtual\nN: No packages found\n",
|
||||
"CombinedOut": "N: Can't select candidate version from package libvips as it has no candidate\nN: Can't select versions from package 'libvips' as it is purely virtual\nN: No packages found\n",
|
||||
"ExitCode": 0
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
2025/03/15 22:29:14 EXECUTION-OBJ-START
|
||||
{
|
||||
"Cmd": "bash -c apt-cache showpkg libvips | grep -A 1 \"Reverse Provides\" | tail -1",
|
||||
"Stdout": "libvips42 8.9.1-2 (= )\n",
|
||||
"Stderr": "",
|
||||
"CombinedOut": "libvips42 8.9.1-2 (= )\n",
|
||||
"ExitCode": 0
|
||||
}
|
||||
EXECUTION-OBJ-END
|
||||
91
src/internal/cmdtesting/cmdtesting.go
Normal file
91
src/internal/cmdtesting/cmdtesting.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package cmdtesting
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/common"
|
||||
)
|
||||
|
||||
const binaryName = "apt_query"
|
||||
|
||||
type CmdTesting struct {
|
||||
*testing.T
|
||||
createReplayLogs bool
|
||||
replayFilename string
|
||||
}
|
||||
|
||||
func New(t *testing.T, createReplayLogs bool) *CmdTesting {
|
||||
replayFilename := "testlogs/" + strings.ToLower(t.Name()) + ".log"
|
||||
if createReplayLogs {
|
||||
os.Remove(replayFilename)
|
||||
os.Remove(binaryName + ".log")
|
||||
}
|
||||
return &CmdTesting{t, createReplayLogs, replayFilename}
|
||||
}
|
||||
|
||||
type RunResult struct {
|
||||
Testing *CmdTesting
|
||||
CombinedOut string
|
||||
Err error
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
cmd := exec.Command("go", "build")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
panic(string(out))
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func (t *CmdTesting) Run(command string, pkgNames ...string) RunResult {
|
||||
replayfile := "testlogs/" + strings.ToLower(t.Name()) + ".log"
|
||||
|
||||
flags := []string{"-debug=true"}
|
||||
if !t.createReplayLogs {
|
||||
flags = append(flags, "-replayfile="+replayfile)
|
||||
}
|
||||
|
||||
cmd := exec.Command("./"+binaryName, append(append(flags, command), pkgNames...)...)
|
||||
combinedOut, err := cmd.CombinedOutput()
|
||||
|
||||
if t.createReplayLogs {
|
||||
err := common.AppendFile(binaryName+".log", t.replayFilename)
|
||||
if err != nil {
|
||||
t.T.Fatalf("Error encountered appending log file.\n%s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return RunResult{Testing: t, CombinedOut: string(combinedOut), Err: err}
|
||||
}
|
||||
|
||||
func (r *RunResult) ExpectSuccessfulOut(expected string) {
|
||||
if r.Testing.createReplayLogs {
|
||||
r.Testing.Log("Skipping test while creating replay logs.")
|
||||
return
|
||||
}
|
||||
|
||||
if r.Err != nil {
|
||||
r.Testing.Errorf("Error running command: %v\n%s", r.Err, r.CombinedOut)
|
||||
return
|
||||
}
|
||||
fullExpected := expected + "\n" // Output will always have a end of output newline.
|
||||
if r.CombinedOut != fullExpected {
|
||||
r.Testing.Errorf("Unexpected combined std[err,out] found.\nExpected:\n'%s'\nActual:\n'%s'", fullExpected, r.CombinedOut)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RunResult) ExpectError(expectedCombinedOut string) {
|
||||
if r.Testing.createReplayLogs {
|
||||
r.Testing.Log("Skipping test while creating replay logs.")
|
||||
return
|
||||
}
|
||||
|
||||
fullExpectedCombinedOut := expectedCombinedOut + "\n" // Output will always have a end of output newline.
|
||||
if r.CombinedOut != fullExpectedCombinedOut {
|
||||
r.Testing.Errorf("Unexpected combined std[err,out] found.\nExpected:\n'%s'\nActual:\n'%s'", fullExpectedCombinedOut, r.CombinedOut)
|
||||
}
|
||||
}
|
||||
127
src/internal/common/apt.go
Normal file
127
src/internal/common/apt.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/exec"
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/logging"
|
||||
)
|
||||
|
||||
// An APT package name and version representation.
|
||||
type AptPackage struct {
|
||||
Name string
|
||||
Version string
|
||||
}
|
||||
|
||||
type AptPackages []AptPackage
|
||||
|
||||
// Serialize the APT packages into lines of <name>=<version>.
|
||||
func (ps AptPackages) Serialize() string {
|
||||
tokens := []string{}
|
||||
for _, p := range ps {
|
||||
tokens = append(tokens, p.Name+"="+p.Version)
|
||||
}
|
||||
return strings.Join(tokens, " ")
|
||||
}
|
||||
|
||||
func isErrLine(line string) bool {
|
||||
return strings.HasPrefix(line, "E: ") || strings.HasPrefix(line, "N: ")
|
||||
}
|
||||
|
||||
// Resolves virtual packages names to their concrete one.
|
||||
func getNonVirtualPackage(executor exec.Executor, name string) (pkg *AptPackage, err error) {
|
||||
execution := executor.Exec("bash", "-c", fmt.Sprintf("apt-cache showpkg %s | grep -A 1 \"Reverse Provides\" | tail -1", name))
|
||||
err = execution.Error()
|
||||
if err != nil {
|
||||
logging.Fatal(err)
|
||||
return pkg, err
|
||||
}
|
||||
if isErrLine(execution.CombinedOut) {
|
||||
return pkg, execution.Error()
|
||||
}
|
||||
splitLine := GetSplitLine(execution.CombinedOut, " ", 3)
|
||||
if len(splitLine.Words) < 2 {
|
||||
return pkg, fmt.Errorf("unable to parse space delimited line's package name and version from apt-cache showpkg output below:\n%s", execution.CombinedOut)
|
||||
}
|
||||
return &AptPackage{Name: splitLine.Words[0], Version: splitLine.Words[1]}, nil
|
||||
}
|
||||
|
||||
func getPackage(executor exec.Executor, paragraph string) (pkg *AptPackage, err error) {
|
||||
errMsgs := []string{}
|
||||
for _, splitLine := range GetSplitLines(paragraph, ":", 2) {
|
||||
if len(splitLine.Words) < 2 {
|
||||
logging.Debug("Skipping invalid line: %+v\n", splitLine.Line)
|
||||
continue
|
||||
}
|
||||
switch splitLine.Words[0] {
|
||||
case "Package":
|
||||
// Initialize since this will provide the first struct value if present.
|
||||
pkg = &AptPackage{}
|
||||
pkg.Name = splitLine.Words[1]
|
||||
|
||||
case "Version":
|
||||
pkg.Version = splitLine.Words[1]
|
||||
|
||||
case "N":
|
||||
// e.g. Can't select versions from package 'libvips' as it is purely virtual
|
||||
if strings.Contains(splitLine.Words[1], "as it is purely virtual") {
|
||||
return getNonVirtualPackage(executor, GetSplitLine(splitLine.Words[1], "'", 4).Words[2])
|
||||
}
|
||||
if strings.HasPrefix(splitLine.Words[1], "Unable to locate package") && !ArrContainsString(errMsgs, splitLine.Line) {
|
||||
errMsgs = append(errMsgs, splitLine.Line)
|
||||
}
|
||||
case "E":
|
||||
if !ArrContainsString(errMsgs, splitLine.Line) {
|
||||
errMsgs = append(errMsgs, splitLine.Line)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errMsgs) == 0 {
|
||||
return pkg, nil
|
||||
}
|
||||
return pkg, errors.New(strings.Join(errMsgs, "\n"))
|
||||
}
|
||||
|
||||
// Gets the APT based packages as a sorted by package name list (normalized).
|
||||
func GetAptPackages(executor exec.Executor, names []string) (AptPackages, error) {
|
||||
prefixArgs := []string{"--quiet=0", "--no-all-versions", "show"}
|
||||
execution := executor.Exec("apt-cache", append(prefixArgs, names...)...)
|
||||
pkgs := []AptPackage{}
|
||||
|
||||
err := execution.Error()
|
||||
if err != nil {
|
||||
logging.Fatal(err)
|
||||
return pkgs, err
|
||||
}
|
||||
|
||||
errMsgs := []string{}
|
||||
|
||||
for _, paragraph := range strings.Split(execution.CombinedOut, "\n\n") {
|
||||
trimmed := strings.TrimSpace(paragraph)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
pkg, err := getPackage(executor, trimmed)
|
||||
if err != nil {
|
||||
errMsgs = append(errMsgs, err.Error())
|
||||
} else if pkg != nil { // Ignore cases where no package parsed and no errors occurred.
|
||||
pkgs = append(pkgs, *pkg)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errMsgs) > 0 {
|
||||
errMsgs = append(errMsgs, strings.Join(errMsgs, "\n"))
|
||||
}
|
||||
|
||||
sort.Slice(pkgs, func(i, j int) bool {
|
||||
return pkgs[i].Name < pkgs[j].Name
|
||||
})
|
||||
if len(errMsgs) > 0 {
|
||||
return pkgs, errors.New(strings.Join(errMsgs, "\n"))
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
85
src/internal/common/io.go
Normal file
85
src/internal/common/io.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func AppendFile(source string, destination string) error {
|
||||
err := createDirectoryIfNotPresent(filepath.Dir(destination))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.OpenFile(destination, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
data, err := io.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = out.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CopyFile(source string, destination string) error {
|
||||
err := createDirectoryIfNotPresent(filepath.Dir(destination))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in, err := os.Open(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
data, err := io.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = out.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func MoveFile(source string, destination string) error {
|
||||
err := createDirectoryIfNotPresent(filepath.Dir(destination))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(source, destination)
|
||||
}
|
||||
|
||||
func createDirectoryIfNotPresent(path string) error {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(path, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
44
src/internal/common/strings.go
Normal file
44
src/internal/common/strings.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Checks if an exact string is in an array of strings.
|
||||
func ArrContainsString(arr []string, element string) bool {
|
||||
for _, x := range arr {
|
||||
if x == element {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A line that has been split into words.
|
||||
type SplitLine struct {
|
||||
Line string // The original line.
|
||||
Words []string // The split words in the line.
|
||||
}
|
||||
|
||||
// Splits a line into words by the delimiter and max number of delimitation.
|
||||
func GetSplitLine(line string, delimiter string, numWords int) SplitLine {
|
||||
words := strings.SplitN(line, delimiter, numWords)
|
||||
trimmedWords := make([]string, len(words))
|
||||
for i, word := range words {
|
||||
trimmedWords[i] = strings.TrimSpace(word)
|
||||
}
|
||||
return SplitLine{line, trimmedWords}
|
||||
}
|
||||
|
||||
// Splits a paragraph into lines by newline and then splits each line into words specified by the delimiter and max number of delimitation.
|
||||
func GetSplitLines(paragraph string, delimiter string, numWords int) []SplitLine {
|
||||
lines := []SplitLine{}
|
||||
for _, line := range strings.Split(paragraph, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, GetSplitLine(trimmed, delimiter, numWords))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
37
src/internal/exec/binexecutor.go
Normal file
37
src/internal/exec/binexecutor.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package exec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/logging"
|
||||
)
|
||||
|
||||
// An executor that proxies command executions from the OS.
|
||||
//
|
||||
// NOTE: Extra abstraction layer needed for testing and replay.
|
||||
type BinExecutor struct{}
|
||||
|
||||
func (c *BinExecutor) Exec(name string, arg ...string) *Execution {
|
||||
cmd := exec.Command(name, arg...)
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
logging.Fatal(err)
|
||||
}
|
||||
|
||||
execution := &Execution{
|
||||
Cmd: name + " " + strings.Join(arg, " "),
|
||||
CombinedOut: string(out),
|
||||
ExitCode: cmd.ProcessState.ExitCode(),
|
||||
}
|
||||
|
||||
logging.DebugLazy(func() string {
|
||||
return fmt.Sprintf("EXECUTION-OBJ-START\n%s\nEXECUTION-OBJ-END", execution.Serialize())
|
||||
})
|
||||
if err != nil {
|
||||
logging.Fatal(execution.Error())
|
||||
}
|
||||
return execution
|
||||
}
|
||||
49
src/internal/exec/executor.go
Normal file
49
src/internal/exec/executor.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package exec
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/logging"
|
||||
)
|
||||
|
||||
type Executor interface {
|
||||
// Executes a command and either returns the output or exits the programs and writes the output (including error) to STDERR.
|
||||
Exec(name string, arg ...string) *Execution
|
||||
}
|
||||
|
||||
type Execution struct {
|
||||
Cmd string
|
||||
CombinedOut string
|
||||
ExitCode int
|
||||
}
|
||||
|
||||
// Gets the error, if the command ran with a non-zero exit code.
|
||||
func (e *Execution) Error() error {
|
||||
if e.ExitCode == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"Error encountered running %s\nExited with status code %d; see combined std[out,err] below:\n%s",
|
||||
e.Cmd,
|
||||
e.ExitCode,
|
||||
e.CombinedOut,
|
||||
)
|
||||
}
|
||||
|
||||
func DeserializeExecution(payload string) *Execution {
|
||||
var execution Execution
|
||||
err := json.Unmarshal([]byte(payload), &execution)
|
||||
if err != nil {
|
||||
logging.Fatalf("Error encountered deserializing Execution object.\n%s", err)
|
||||
}
|
||||
return &execution
|
||||
}
|
||||
|
||||
func (e *Execution) Serialize() string {
|
||||
bytes, err := json.MarshalIndent(e, "", " ")
|
||||
if err != nil {
|
||||
logging.Fatalf("Error encountered serializing Execution object.\n%s", err)
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
74
src/internal/exec/replayexecutor.go
Normal file
74
src/internal/exec/replayexecutor.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package exec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"awalsh128.com/cache-apt-pkgs-action/src/internal/logging"
|
||||
)
|
||||
|
||||
// An executor that replays execution results from a recorded result.
|
||||
type ReplayExecutor struct {
|
||||
logFilepath string
|
||||
cmdExecs map[string]*Execution
|
||||
}
|
||||
|
||||
func NewReplayExecutor(logFilepath string) *ReplayExecutor {
|
||||
file, err := os.Open(logFilepath)
|
||||
if err != nil {
|
||||
logging.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
cmdExecs := make(map[string]*Execution)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.Contains(line, "EXECUTION-OBJ-START") {
|
||||
payload := ""
|
||||
for scanner.Scan() {
|
||||
line = scanner.Text()
|
||||
if strings.Contains(line, "EXECUTION-OBJ-END") {
|
||||
execution := DeserializeExecution(payload)
|
||||
cmdExecs[execution.Cmd] = execution
|
||||
break
|
||||
} else {
|
||||
payload += line + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
logging.Fatal(err)
|
||||
}
|
||||
return &ReplayExecutor{logFilepath, cmdExecs}
|
||||
}
|
||||
|
||||
func (e *ReplayExecutor) getCmds() []string {
|
||||
cmds := []string{}
|
||||
for cmd := range e.cmdExecs {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
func (e *ReplayExecutor) Exec(name string, arg ...string) *Execution {
|
||||
cmd := name + " " + strings.Join(arg, " ")
|
||||
value, ok := e.cmdExecs[cmd]
|
||||
if !ok {
|
||||
var available string
|
||||
if len(e.getCmds()) > 0 {
|
||||
available = "\n" + strings.Join(e.getCmds(), "\n")
|
||||
} else {
|
||||
available = " NONE"
|
||||
}
|
||||
logging.Fatalf(
|
||||
"Unable to replay command '%s'.\n"+
|
||||
"No command found in the debug log; available commands:%s", cmd, available)
|
||||
}
|
||||
return value
|
||||
}
|
||||
57
src/internal/logging/logger.go
Normal file
57
src/internal/logging/logger.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
wrapped *log.Logger
|
||||
Filename string
|
||||
Debug bool
|
||||
}
|
||||
|
||||
var logger *Logger
|
||||
|
||||
var LogFilepath = os.Args[0] + ".log"
|
||||
|
||||
func Init(filename string, debug bool) *Logger {
|
||||
os.Remove(LogFilepath)
|
||||
file, err := os.OpenFile(LogFilepath, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(2)
|
||||
}
|
||||
cwd, _ := os.Getwd()
|
||||
logger = &Logger{
|
||||
wrapped: log.New(file, "", log.LstdFlags),
|
||||
Filename: filepath.Join(cwd, file.Name()),
|
||||
Debug: debug,
|
||||
}
|
||||
Debug("Debug log created at %s", logger.Filename)
|
||||
return logger
|
||||
}
|
||||
|
||||
func DebugLazy(getLine func() string) {
|
||||
if logger.Debug {
|
||||
logger.wrapped.Println(getLine())
|
||||
}
|
||||
}
|
||||
|
||||
func Debug(format string, a ...any) {
|
||||
if logger.Debug {
|
||||
logger.wrapped.Printf(format, a...)
|
||||
}
|
||||
}
|
||||
|
||||
func Fatal(err error) {
|
||||
fmt.Fprintf(os.Stderr, "%s", err.Error())
|
||||
logger.wrapped.Fatal(err)
|
||||
}
|
||||
|
||||
func Fatalf(format string, a ...any) {
|
||||
fmt.Fprintf(os.Stderr, format+"\n", a...)
|
||||
logger.wrapped.Fatalf(format, a...)
|
||||
}
|
||||
Loading…
Reference in a new issue