Skip to content
Closed

wip #327

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ jobs:
visibility: public
runner-map: |
{
"aarch64-darwin": "namespace-profile-mac-m2-12c28g",
"aarch64-linux": "UbuntuLatest32Cores128GArm",
"x86_64-linux": "UbuntuLatest32Cores128G"
}

Expand Down Expand Up @@ -147,13 +145,13 @@ jobs:
nix build -j0 .#packages.x86_64-linux.default
cp -r ./result/bin/flakehub-push ./artifacts/X64-Linux

nix build -j0 .#packages.aarch64-linux.default
cp -r ./result/bin/flakehub-push ./artifacts/ARM64-Linux
# nix build -j0 .#packages.aarch64-linux.default
# cp -r ./result/bin/flakehub-push ./artifacts/ARM64-Linux

nix build -j0 .#packages.aarch64-darwin.default
cp -r ./result/bin/flakehub-push ./artifacts/ARM64-macOS
# nix build -j0 .#packages.aarch64-darwin.default
# cp -r ./result/bin/flakehub-push ./artifacts/ARM64-macOS

- uses: DeterminateSystems/push-artifact-ids@main
- uses: DeterminateSystems/push-artifact-ids@wip
with:
s3_upload_role: ${{ secrets.AWS_S3_UPLOAD_ROLE }}
bucket: ${{ secrets.AWS_S3_UPLOAD_BUCKET }}
Expand Down
6 changes: 6 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ inputs:
source-url:
description: A URL pointing to a `flakehub-push` binary. Overrides all other `source-*` options.
required: false
source-checksums-url:
description: wip
required: false
source-checksums-sha256:
description: wip
required: false
_internal-strict-mode:
description: Whether to fail when any errors are thrown. Used only to test the Action; do not set this in your own workflows.
default: false
Expand Down
117 changes: 109 additions & 8 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107318,6 +107318,52 @@ async function collectBacktracesSystemd(prefixes, programNameDenyList, startTime
return backtraces;
}
//#endregion
//#region src/checksums.ts
/**
* @packageDocumentation
* Parsing and hashing helpers for `shasum`-format checksum files, used to
* hash-lock downloaded artifacts.
*/
const HEX_STRING_RE = /^[0-9a-fA-F]+$/;
/**
* Parse a `shasum`-format checksums file into a map of filename -> hex digest.
*
* Each non-empty line has the shape `<hex-digest><space(s)><filename>`. Lines
* without a space delimiter are skipped. Invalid hex digests throw, so a
* malformed file fails loudly rather than silently skipping the entry we
* care about.
*/
function parseChecksumsFile(text) {
const result = /* @__PURE__ */ new Map();
for (const record of text.split(/\r\n|\n|\r/).filter(Boolean)) {
const delimIndex = record.indexOf(" ");
if (delimIndex === -1) continue;
const digest = record.slice(0, delimIndex);
if (!HEX_STRING_RE.test(digest)) throw new Error(`Invalid digest in checksums file: ${digest}`);
const name = record.slice(delimIndex + 1).trim();
if (name === "") continue;
result.set(name, digest.toLowerCase());
}
return result;
}
/**
* Compute the SHA-256 of a file on disk and return its lowercase hex digest.
* Streams the file so memory use is constant regardless of size.
*/
async function sha256OfFile(filePath) {
return new Promise((resolve, reject) => {
const hash = (0,external_node_crypto_.createHash)("sha256").setEncoding("hex");
(0,external_node_fs_namespaceObject.createReadStream)(filePath).once("error", reject).pipe(hash).once("finish", () => resolve(hash.read()));
});
}
/**
* Compute the SHA-256 of an in-memory buffer or string and return its
* lowercase hex digest.
*/
function sha256OfBuffer(data) {
return (0,external_node_crypto_.createHash)("sha256").update(data).digest("hex");
}
//#endregion
//#region src/correlation.ts
const OPTIONAL_VARIABLES = ["INVOCATION_ID"];
function identify() {
Expand Down Expand Up @@ -107708,6 +107754,16 @@ function getNixPlatform(archOs) {
}
//#endregion
//#region src/sourcedef.ts
/**
* Throw if hash-locking is requested against a source that is not pinned to a
* fixed version. `source-tag`, `source-revision`, and `source-url` are
* immutable (or caller-controlled); any other selector resolves to a moving
* target (`branch`, `pr`, or the `stable` fallback) where the pinned checksum
* would break the moment a new release is published.
*/
function assertChecksumSourceIsPinned(source) {
if (source.url === void 0 && source.tag === void 0 && source.revision === void 0) throw new Error("Hash-locking via `source-checksums-url`/`source-checksums-sha256` requires a pinned source: set `source-tag`, `source-revision`, or `source-url`. Without one the action resolves to a moving target (e.g. `stable`) and the checksum will break the next time a release is published.");
}
function constructSourceParameters(legacyPrefix) {
return {
path: noisilyGetInput("path", legacyPrefix),
Expand Down Expand Up @@ -107751,6 +107807,7 @@ const FACT_OS = "$os";
const FACT_OS_VERSION = "$os_version";
const FACT_SOURCE_URL = "source_url";
const FACT_SOURCE_URL_ETAG = "source_url_etag";
const FACT_SOURCE_CHECKSUMS_SHA256 = "source_checksums_sha256";
const FACT_NIX_VERSION = "nix_version";
const FACT_NIX_LOCATION = "nix_location";
const FACT_NIX_STORE_TRUST = "nix_store_trusted";
Expand Down Expand Up @@ -108091,13 +108148,19 @@ var DetSysAction = class {
* to a binary on disk; otherwise, the artifact will be downloaded from the
* URL determined by the other `source-*` inputs (`source-url`, `source-pr`,
* etc.).
*
* When `source-checksums-url` and `source-checksums-sha256` are both set,
* the downloaded artifact is verified against the per-arch hash in the
* checksums file, which is itself verified against the pinned
* `source-checksums-sha256`. Both inputs must be set together.
*/
async fetchArtifact() {
const sourceBinary = getStringOrNull("source-binary");
if (sourceBinary !== null && sourceBinary !== "") {
core_debug(`Using the provided source binary at ${sourceBinary}`);
return sourceBinary;
}
const expectedArtifactHash = await this.resolveExpectedArtifactHash();
startGroup(`Downloading ${this.actionOptions.name} for ${this.architectureFetchSuffix}`);
try {
info(`Fetching from ${await this.getSourceUrl()}`);
Expand All @@ -108109,21 +108172,23 @@ var DetSysAction = class {
const v = versionCheckup.headers.etag;
this.addFact(FACT_SOURCE_URL_ETAG, v);
core_debug(`Checking the tool cache for ${await this.getSourceUrl()} at ${v}`);
const cached = await this.getCachedVersion(v);
const cached = await this.getCachedVersion(v, expectedArtifactHash);
if (cached) {
this.facts[FACT_ARTIFACT_FETCHED_FROM_CACHE] = true;
core_debug(`Tool cache hit.`);
await this.verifyArtifactHash(cached, expectedArtifactHash);
return cached;
}
}
this.facts[FACT_ARTIFACT_FETCHED_FROM_CACHE] = false;
core_debug(`No match from the cache, re-fetching from the redirect: ${versionCheckup.url}`);
const destFile = this.getTemporaryName();
const fetchStream = await this.downloadFile(new URL(versionCheckup.url), destFile);
await this.verifyArtifactHash(destFile, expectedArtifactHash);
if (fetchStream.response?.headers.etag) {
const v = fetchStream.response.headers.etag;
try {
await this.saveCachedVersion(v, destFile);
await this.saveCachedVersion(v, destFile, expectedArtifactHash);
} catch (e) {
core_debug(`Error caching the artifact: ${stringifyError$1(e)}`);
}
Expand All @@ -108137,6 +108202,41 @@ var DetSysAction = class {
}
}
/**
* Read the `source-checksums-url` and `source-checksums-sha256` inputs and,
* if both are set, fetch the checksums file, verify its hash matches the
* pin, parse it, and return the expected hash for the artifact matching
* this runner's `${name}-${architectureFetchSuffix}`. Returns `null` when
* verification is opted out (both inputs unset).
*/
async resolveExpectedArtifactHash() {
const checksumsUrl = getStringOrNull("source-checksums-url");
const checksumsSha256 = getStringOrNull("source-checksums-sha256");
if (checksumsUrl === null && checksumsSha256 === null) return null;
if (checksumsUrl === null || checksumsSha256 === null) throw new Error("`source-checksums-url` and `source-checksums-sha256` must be set together");
assertChecksumSourceIsPinned(this.sourceParameters);
const expectedFileHash = checksumsSha256.toLowerCase();
this.addFact(FACT_SOURCE_CHECKSUMS_SHA256, expectedFileHash);
const parsedUrl = new URL(checksumsUrl);
const safeUrl = parsedUrl.origin + parsedUrl.pathname;
info(`Fetching checksums file from ${safeUrl}`);
const body = (await (await this.getClient()).get(checksumsUrl)).body;
const actualFileHash = sha256OfBuffer(body);
if (actualFileHash !== expectedFileHash) throw new Error(`Checksums file hash mismatch at ${safeUrl}: expected ${expectedFileHash}, got ${actualFileHash}`);
const wanted = `${this.actionOptions.name}-${this.architectureFetchSuffix}`;
const artifactHash = parseChecksumsFile(body).get(wanted);
if (artifactHash === void 0) throw new Error(`No entry for ${wanted} in checksums file at ${safeUrl}`);
return artifactHash;
}
/**
* Verify a downloaded artifact's SHA-256 matches the expected hash. No-op
* when `expected` is `null` (verification disabled).
*/
async verifyArtifactHash(filePath, expected) {
if (expected === null) return;
const actual = await sha256OfFile(filePath);
if (actual !== expected) throw new Error(`Artifact hash mismatch for ${this.architectureFetchSuffix}: expected ${expected}, got ${actual}`);
}
/**
* A helper function for failing on error only if strict mode is enabled.
* This is intended only for CI environments testing Actions themselves.
*/
Expand Down Expand Up @@ -108196,19 +108296,20 @@ var DetSysAction = class {
this.addFact(FACT_SOURCE_URL, fetchUrl.toString());
return fetchUrl;
}
cacheKey(version) {
cacheKey(version, expectedHash) {
const cleanedVersion = version.replace(/[^a-zA-Z0-9-+.]/g, "");
return `determinatesystem-${this.actionOptions.name}-${this.architectureFetchSuffix}-${cleanedVersion}`;
const hashSuffix = expectedHash ? `-h${expectedHash}` : "";
return `determinatesystem-${this.actionOptions.name}-${this.architectureFetchSuffix}-${cleanedVersion}${hashSuffix}`;
}
async getCachedVersion(version) {
async getCachedVersion(version, expectedHash) {
const startCwd = process.cwd();
try {
const tempDir = this.getTemporaryName();
await (0,promises_namespaceObject.mkdir)(tempDir);
process.chdir(tempDir);
process.env.GITHUB_WORKSPACE_BACKUP = process.env.GITHUB_WORKSPACE;
delete process.env.GITHUB_WORKSPACE;
if (await restoreCache([this.actionOptions.name], this.cacheKey(version), [], void 0, true)) {
if (await restoreCache([this.actionOptions.name], this.cacheKey(version, expectedHash), [], void 0, true)) {
this.recordEvent(EVENT_ARTIFACT_CACHE_HIT);
return `${tempDir}/${this.actionOptions.name}`;
}
Expand All @@ -108220,7 +108321,7 @@ var DetSysAction = class {
process.chdir(startCwd);
}
}
async saveCachedVersion(version, toolPath) {
async saveCachedVersion(version, toolPath, expectedHash) {
const startCwd = process.cwd();
try {
const tempDir = this.getTemporaryName();
Expand All @@ -108229,7 +108330,7 @@ var DetSysAction = class {
await (0,promises_namespaceObject.copyFile)(toolPath, `${tempDir}/${this.actionOptions.name}`);
process.env.GITHUB_WORKSPACE_BACKUP = process.env.GITHUB_WORKSPACE;
delete process.env.GITHUB_WORKSPACE;
await cache_saveCache([this.actionOptions.name], this.cacheKey(version), void 0, true);
await cache_saveCache([this.actionOptions.name], this.cacheKey(version, expectedHash), void 0, true);
this.recordEvent(EVENT_ARTIFACT_CACHE_PERSIST);
} finally {
process.env.GITHUB_WORKSPACE = process.env.GITHUB_WORKSPACE_BACKUP;
Expand Down
31 changes: 15 additions & 16 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -65,31 +65,29 @@
packages = forAllSystems (
{ system, pkgs, ... }:
let
lib = pkgs.lib;

sharedAttrs = {
pname = "flakehub-push";
version = "0.1.0";
src = pkgs.craneLib.path (
builtins.path {
name = "flakehub-push-source";
path = inputs.self;
filter = (
path: type:
baseNameOf path != "ts"
&& baseNameOf path != "dist"
&& baseNameOf path != ".github"
&& path != "flake.nix"
);
}
);
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./Cargo.toml
./Cargo.lock
./.cargo
./src
];
};

buildInputs = pkgs.lib.optionals (pkgs.stdenv.isDarwin) (
buildInputs = lib.optionals (pkgs.stdenv.isDarwin) (
with pkgs;
[
libiconv
]
);
}
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
// lib.optionalAttrs pkgs.stdenv.isLinux {
CARGO_BUILD_TARGET =
{
"x86_64-linux" = "x86_64-unknown-linux-musl";
Expand All @@ -106,7 +104,7 @@
sharedAttrs
// {
cargoArtifacts = pkgs.craneLib.buildDepsOnly sharedAttrs;
postFixup = pkgs.lib.optionalString pkgs.stdenv.isDarwin ''
postFixup = lib.optionalString pkgs.stdenv.isDarwin ''
install_name_tool -change \
"$(otool -L $out/bin/flakehub-push | grep libiconv | awk '{print $1}')" \
/usr/lib/libiconv.2.dylib \
Expand All @@ -133,6 +131,7 @@
cargo

nodejs_latest
typescript-language-server
bacon

self.formatter.${system}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@actions/core": "^3.0.0",
"@actions/exec": "^3.0.0",
"@actions/github": "^9.0.0",
"detsys-ts": "github:DeterminateSystems/detsys-ts"
"detsys-ts": "github:DeterminateSystems/detsys-ts#ddf1678f505b22515b9cabfb79f38ce663a876a1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
Expand Down
Loading