Skip to content

Commit

Permalink
Merge v4-beta -> v4 (#69)
Browse files Browse the repository at this point in the history
actions/files/list:
* more options in list-files. Return 'paths' instead of 'files'
* move list-files action to actions/files/list
* provide the ability to glob with relative paths
* make relative paths to always start with './'
* add 'has-matches' and 'is-empty' outputs
* add 'target-file' option to write result directly into file instead of output

actions/artifact/save and restore
* new artifact save and restore actions that preserve permissions

notarization and signing:
* copy notarize and signing tools from internal CI repo
* do not try to sign empty list of binaries
* suppoty lists of files in inputs (both for notarization and signing)

node-python-matrix:
* feat: add node-python-matrix workflow draft

actions/node/*:
* teach node prepare action to update npm and install dependencies
* teach npm-pkg-status to load name and version from package.json
  • Loading branch information
DenKoren authored Oct 18, 2024
1 parent 55f49a4 commit 7e2c817
Show file tree
Hide file tree
Showing 24 changed files with 1,759 additions and 153 deletions.
794 changes: 794 additions & 0 deletions .github/workflows/node-python-matrix.yaml

Large diffs are not rendered by default.

97 changes: 97 additions & 0 deletions actions/artifact/restore/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Restore artifact, saved with actions/artifact/save
author: "MiLaboratories"
description: |
Restore the artifact saved with actions/artifact/save. No path transforations are
performed: the contents of artifact are restored like the data was initially created
on current agent, keeping all permissions and paths (relative to current WD)
inputs:
name:
description: |
Name of the artifact to download.
Optional. When empty, causes all artifacts to be downloaded.
required: false

pattern:
description: |
Pattern for artifacts to download.
Optional. Causes action to restore several artifacts, selected by their names.
required: false

github-token:
description: |
The GitHub token used to authenticate with the GitHub API.
This is required when downloading artifacts from a different repository or from a different workflow run.
Optional. If unspecified, the action will download artifacts from the current repo and the current workflow run.
required: false

repository:
description: |
The repository owner and the repository name joined together by "/".
If github-token is specified, this is the repository that artifacts will be downloaded from.
Optional. Default is {{ github.repository }}
required: false

run-id:
description: |
The id of the workflow run where the desired download artifact was uploaded from.
If github-token is specified, this is the run that artifacts will be downloaded from.
Optional. Default is {{ github.run_id }}
required: false

runs:
using: "composite"

steps:
- name: Generate archive name
id: archive-name
shell: bash
env:
ARTIFACT_NAME: ${{ inputs.name }}
run: |
# We need unique archive name here to not overwrite any existing file in workdir.
# We also need static name to simplify 'restore' action logic.
# This name is an attempt to meet both criteria.
if [ -z "ARTIFACT_NAME" ]; then
echo "::error::Empty 'name' input. Specify artifact name" >&2
exit 1
fi
archive_name="artifact-5b3513f5"
echo "name=${archive_name}" >> "${GITHUB_OUTPUT}"
- uses: actions/download-artifact@v4
with:
name: ${{ inputs.name }}
pattern: ${{ inputs.pattern }}
github-token: ${{ inputs.github-token }}
repository: ${{ inputs.repository }}
run-id: ${{ inputs.run-id }}
path: "artifacts-restore-5b3513f5"

- name: Extract tar
shell: bash
env:
ARTIFACT_NAME: ${{ inputs.name }}
ARCHIVE_NAME: ${{ steps.archive-name.outputs.name }}
run: |
if ! [ -d "./artifacts-restore-5b3513f5" ]; then
echo "::error::No artifacts were restored by the action. Is 'name'/'pattern' input correct?" >&2
exit 1
fi
at_least_one_found=""
while read -r file; do
at_least_one_found="true"
tar -x -f "${file}"
done < <(
find \
"./artifacts-restore-5b3513f5" \
-type f \
-name "${ARCHIVE_NAME}.tar"
)
if [ -z "${at_least_one_found}" ]; then
echo "::error::No artifacts were restored by the action. Only artifacts saved with 'artifact/save' action can be restored by 'artifact/restore'" >&2
exit 1
fi
141 changes: 141 additions & 0 deletions actions/artifact/save/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
name: Pack tar archive and save it as artifact
author: "MiLaboratories"
description: |
Get all files mathing globbing rules, pack them into single tar archive and
save this archive as artifact. The standard upload-artifact does not preserve
permissions. This action helps to bypass this limitation.
inputs:
name:
description: |
Name of the artifact to upload.
Optional. Default is 'artifact'
required: false
default: "artifact"

path:
description: |
A file, directory or wildcard pattern that describes what to upload
Required.
required: true

if-no-files-found:
description: |
The desired behavior if no files are found using the provided path.
Available Options:
warn: Output a warning but do not fail the action
error: Fail the action with an error message
ignore: Do not output any warnings or errors, the action does not fail
Optional. Default is 'error'
required: false
default: "error"

retention-days:
description: |
Duration after which artifact will expire in days. 0 means using default retention.
Minimum 1 day.
Maximum 90 days unless changed from the repository settings page.
Optional. Defaults to repository settings.
required: false
default: "0"

compression-level:
description: |
The level of compression for Zlib to be applied to the artifact archive.
The value can range from 0 to 9.
For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
The compression is applied to the zip archive, where tar is stored. The tar file itself will not be compressed.
Optional. Default is '6'
required: false
default: "6"

overwrite:
description: |
If true, an artifact with a matching name will be deleted before a new one is uploaded.
If false, the action will fail if an artifact for the given name already exists.
Does not fail if the artifact does not exist.
Optional. Default is 'false'
required: false
default: "false"

include-hidden-files:
description: |
Whether to include hidden files in the provided path in the artifact
The file contents of any hidden files in the path should be validated before
enabled this to avoid uploading sensitive information.
Optional. Default is 'false'
required: false
default: "false"

# interactive-debug:
# description: |
# Break execution and start interactive debugging session with server VSCode right on agent
# required: false
# default: "false"

runs:
using: "composite"

steps:
- name: Generate archive name
id: archive-name
shell: bash
env:
ARTIFACT_NAME: ${{ inputs.name }}
run: |
if [ -z "ARTIFACT_NAME" ]; then
echo "::error::Empty 'name' input. Specify artifact name." >&2
exit 1
fi
# We need unique archive name here to not overwrite any existing file in workdir.
# We also need static name to simplify 'restore' action logic.
# This name is an attempt to meet both criteria.
archive_name="artifact-5b3513f5"
echo "name=${archive_name}" >> "${GITHUB_OUTPUT}"
- uses: milaboratory/github-ci/actions/files/list@v4
id: artifact-files
with:
patterns: ${{ inputs.path }}
exclude-hidden-files: ${{ ! fromJSON(inputs.include-hidden-files) }}
implicit-descendants: false
relative: true

- name: Create tar
shell: bash
env:
ARCHIVE_NAME: ${{ steps.archive-name.outputs.name }}
ARTIFACT_PATHS: ${{ steps.artifact-files.outputs.paths }}
ARCHIVE_IS_EMPTY: ${{ steps.artifact-files.outputs.is-empty }}
RUNNER_OS: ${{ runner.os }}
run: |
if [ "${ARCHIVE_IS_EMPTY}" == "true" ]; then
// do not create archive to let actions/upload-artifact decide how to react on 'nothing found' case
return
fi
if [ "${RUNNER_OS}" == "Windows" ]; then
ARTIFACT_PATHS="${ARTIFACT_PATHS//\\/\\\\}" # escape windows delimiters to not confuse tar
fi
echo "${ARTIFACT_PATHS}" > "${ARCHIVE_NAME}.files"
echo "Paths to be archived:"
cat "${ARCHIVE_NAME}.files"
tar -T "${ARCHIVE_NAME}.files" -c -f "${ARCHIVE_NAME}.tar"
# - uses: fawazahmed0/action-debug-vscode@main
# if: inputs.interactive-debug == 'true'

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.name }}
if-no-files-found: ${{ inputs.if-no-files-found }}
retention-days: ${{ inputs.retention-days }}
compression-level: ${{ inputs.compression-level }}
overwrite: ${{ inputs.overwrite }}
include-hidden-files: ${{ inputs.include-hidden-files }}

path: |
${{ steps.archive-name.outputs.name }}.tar
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
49 changes: 49 additions & 0 deletions actions/files/list/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: 'File Globber'
description: 'A GitHub Action to perform globbing operations to find files based on patterns.'
author: 'MiLaboratories'

inputs:
patterns:
description: |
Glob patterns to search for files.
More information about globbing options:
https://github.com/actions/toolkit/tree/main/packages/glob
required: true

target-file:
description: |
Put the resulting list into file instead of setting the output.
Allows to prevent errors on large list sizes, like
'The template is not valid. System.InvalidOperationException: Maximum object size exceeded'
required: false
default: ''

relative:
description: |
Return relative paths instead of absolute
required: false
default: 'false'

follow-symbolic-links:
description: 'Indicates whether to follow symbolic links.'
default: 'true'

exclude-hidden-files:
description: Do not include hidden files into the match, even if they are covered by <patterns>
default: 'false'

match-directories:
description: Match directories as well as files. When false, only files would be included into globbing result.
default: 'true'

implicit-descendants:
description: When matching a directory, extend the match to all its contents
default: 'false'

outputs:
files:
description: 'Newline-delimited string of matched file paths.'

runs:
using: 'node20'
main: 'dist/index.js'
Original file line number Diff line number Diff line change
Expand Up @@ -27442,29 +27442,85 @@ var __importStar = (this && this.__importStar) || function (mod) {
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
const glob = __importStar(__nccwpck_require__(8090));
const path_1 = __importDefault(__nccwpck_require__(1017));
const fs_1 = __importDefault(__nccwpck_require__(7147));
function readInputBool(name) {
return core.getInput(name).toUpperCase() === 'TRUE';
}
async function run() {
try {
const patterns = core.getInput('patterns', { required: true });
const followSymbolicLinks = core.getInput('follow-symbolic-links').toUpperCase() !== 'FALSE';
const globOptions = { followSymbolicLinks };
const targetFile = core.getInput('target-file', { required: false });
const followSymbolicLinks = readInputBool('follow-symbolic-links');
const excludeHiddenFiles = readInputBool('exclude-hidden-files');
const relative = readInputBool('relative');
const matchDirectories = readInputBool('match-directories');
const implicitDescendants = readInputBool('implicit-descendants');
const globOptions = {
followSymbolicLinks,
excludeHiddenFiles,
matchDirectories,
implicitDescendants,
};
const globber = await glob.create(patterns, globOptions);
// Initialize an array to collect files
const files = [];
// Using async iterator to process files one by one
for await (const file of globber.globGenerator()) {
console.log(`Processing file: ${file}`);
files.push(file);
var lines;
if (targetFile !== "") {
console.log(`Found paths:\n`);
lines = await writeToFile(globber.globGenerator(), targetFile, relative);
core.setOutput('paths', '');
core.setOutput('file', targetFile);
}
console.log(`Found files: ${files.join(', ')}`);
core.setOutput('files', files.join('\n'));
else {
const matchedPaths = await writeToArray(globber.globGenerator(), relative);
lines = matchedPaths.length;
console.log(`Found paths:\n\t${matchedPaths.join('\n\t')}`);
core.setOutput('paths', matchedPaths.join('\n'));
core.setOutput('file', '');
}
core.setOutput('has-matches', lines > 0);
core.setOutput('is-empty', lines === 0);
}
catch (error) {
core.setFailed(error instanceof Error ? error.message : 'Unknown error occurred');
}
}
function transformPath(p, relative) {
if (relative) {
return `.${path_1.default.sep}${path_1.default.relative('.', p)}`;
}
return p;
}
async function writeToFile(items, filePath, relative) {
const fileStream = fs_1.default.createWriteStream(filePath, { flags: 'a' });
var linesCount = 0;
try {
for await (const item of items) {
const p = transformPath(item, relative);
if (!fileStream.write(`${p}\n`)) {
await new Promise((resolve) => fileStream.once('drain', resolve));
}
linesCount++;
console.log(`\t${p}`);
}
}
finally {
fileStream.end();
}
return linesCount;
}
async function writeToArray(items, relative) {
const result = [];
for await (const item of items) {
result.push(transformPath(item, relative));
}
return result;
}
run();


Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 7e2c817

Please sign in to comment.