diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b7ea348..b2d2e88 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -59,7 +59,7 @@ jobs: - nix-system: aarch64-darwin runner: macos-latest-xlarge - nix-system: x86_64-darwin - runner: macos-12 + runner: macos-13-large steps: - uses: actions/checkout@v3 diff --git a/Cargo.toml b/Cargo.toml index 1642d44..d215b35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ tracing-error = { version = "0.2.0", default-features = false, features = ["trac tracing-subscriber = { version = "0.3.15", default-features = false, features = [ "std", "registry", "fmt", "json", "ansi", "env-filter" ] } github-actions-oidc-claims = "0.3.0" spdx = "0.10.2" -uuid = { version = "1.4.0", features = ["serde", "v7", "rand", "std"] } +uuid = { version = "1.4.0", features = ["serde", "v4", "v7", "rand", "std"] } semver = { version = "1.0.18", features = ["serde"] } thiserror = "1.0.56" url = { version = "2.5.0", features = ["serde"] } diff --git a/README.md b/README.md index d33033e..9bc2b5d 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,14 @@ Although the `flakehub-push` Action requires little configuration, you may benef ## Integration -This action sets the `flakeref` output to the exact name and version that was published. -The flake reference can be used in subsequent steps or workflows as part of a deployment process. +This action sets outputs for integrating into continuous delivery pipelines: + +| Output | Description | Example | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | +| `flake_name` | Name of the flake. | `DeterminateSystems/flakehub-push` | +| `flake_version` | Version of the published flake. | `0.1.99+rev-2075013a3f3544d45a96f4b35df4ed03cd53779c` | +| `flakeref_exact` | A precise reference that always resolves to this to this exact release. | `DeterminateSystems/flakehub-push/=0.1.99+rev-2075013a3f3544d45a96f4b35df4ed03cd53779c` | +| `flakeref_at_least` | A loose reference to this release. Depending on this reference will require at least this version, and will also resolve to newer releases. This output is not sufficient for deployment pipelines, use `flake_exact` instead. | `DeterminateSystems/flakehub-push/0.1.99+rev-2075013a3f3544d45a96f4b35df4ed03cd53779c` | ## More Information diff --git a/src/github_actions.rs b/src/github_actions.rs index 6d64146..906e82f 100644 --- a/src/github_actions.rs +++ b/src/github_actions.rs @@ -10,22 +10,45 @@ pub(crate) enum Error { #[error("Writing to {0:?}: {1}")] WriteFile(std::ffi::OsString, std::io::Error), + + #[error("Key contains delimiter")] + KeyContainsDelimiter, + + #[error("Value contains delimiter")] + ValueContainsDelimiter, } pub(crate) async fn set_output<'a>(name: &'a str, value: &'a str) -> Result<(), Error> { let output_path = std::env::var_os("GITHUB_OUTPUT").ok_or(Error::GithubOutputUnset)?; let mut fh = tokio::fs::OpenOptions::new() - .create(true) - .read(true) .write(true) + .append(true) .truncate(false) .open(&output_path) .await .map_err(|e| Error::OpenFile(output_path.clone(), e))?; - fh.write_all(format!("{}={}\n", name, value).as_bytes()) + fh.write_all(escape_key_value(name, value)?.as_bytes()) .await .map_err(|e| Error::WriteFile(output_path, e))?; Ok(()) } + +fn escape_key_value<'a>(key: &'a str, value: &'a str) -> Result { + // see: https://github.com/actions/toolkit/blob/6dd369c0e648ed58d0ead326cf2426906ea86401/packages/core/src/file-command.ts#L27-L47 + let delimiter = format!("ghadelimiter_{}", uuid::Uuid::new_v4()); + let eol = '\n'; + + if key.contains(&delimiter) { + return Err(Error::KeyContainsDelimiter); + } + + if value.contains(&delimiter) { + return Err(Error::ValueContainsDelimiter); + } + + Ok(format!( + "{key}<<{delimiter}{eol}{value}{eol}{delimiter}{eol}" + )) +} diff --git a/src/main.rs b/src/main.rs index 0ef29b7..6bc8c1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -140,6 +140,9 @@ async fn execute() -> Result { upload_name = ctx.upload_name, release_version = &ctx.release_version, ); + + set_release_outputs(&ctx.upload_name, &ctx.release_version).await; + if ctx.error_if_release_conflicts { return Err(Error::Conflict { upload_name: ctx.upload_name.to_string(), @@ -182,14 +185,7 @@ async fn execute() -> Result { ctx.release_version ); - if let Err(e) = github_actions::set_output( - "flakeref", - &format!("{}/{}", ctx.upload_name, ctx.release_version), - ) - .await - { - tracing::warn!("Failed to set the `flakeref` output: {}", e); - } + set_release_outputs(&ctx.upload_name, &ctx.release_version).await; Ok(ExitCode::SUCCESS) } @@ -222,3 +218,28 @@ impl Display for Visibility { } } } + +async fn set_release_outputs(upload_name: &str, release_version: &str) { + let outputs = [ + ("flake_name", upload_name), + ("flake_version", release_version), + ( + "flakeref_at_least", + &format!("{}/{}", upload_name, release_version), + ), + ( + "flakeref_exact", + &format!("{}/={}", upload_name, release_version), + ), + ]; + for (output_name, value) in outputs.into_iter() { + if let Err(e) = github_actions::set_output(output_name, value).await { + tracing::warn!( + "Failed to set the `{}` output to {}: {}", + output_name, + value, + e + ); + } + } +}