mirror of
https://github.com/DeterminateSystems/flake-checker.git
synced 2024-10-05 18:27:42 +03:00
Merge pull request #124 from DeterminateSystems/cel-expressions
This commit is contained in:
commit
6ba8ec538e
15
.github/workflows/ci.yaml
vendored
15
.github/workflows/ci.yaml
vendored
@ -36,6 +36,21 @@ jobs:
|
||||
- name: cargo test
|
||||
run: nix develop -c cargo test
|
||||
|
||||
check-flake-cel-condition:
|
||||
name: Check flake.lock test (CEL condition)
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- name: Check flake.lock
|
||||
run: |
|
||||
nix develop -c \
|
||||
cargo run -- \
|
||||
--condition "supportedRefs.contains(gitRef) && numDaysOld < 30 && owner == 'NixOS'" \
|
||||
./tests/flake.cel.0.lock
|
||||
|
||||
check-flake-dirty:
|
||||
name: Check flake.lock test (dirty 😈)
|
||||
runs-on: ubuntu-22.04
|
||||
|
879
Cargo.lock
generated
879
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@ -1,9 +1,10 @@
|
||||
[package]
|
||||
name = "flake-checker"
|
||||
version = "0.1.20"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [".", "parse-flake-lock"]
|
||||
|
||||
[workspace.dependencies]
|
||||
@ -13,11 +14,8 @@ serde_json = { version = "1.0.100", default-features = false, features = [
|
||||
] }
|
||||
thiserror = { version = "1.0.40", default-features = false }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
allowed-refs = []
|
||||
|
||||
[dependencies]
|
||||
cel-interpreter = { version = "0.7.1", default-features = false }
|
||||
chrono = { version = "0.4.25", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.3.0", default-features = false, features = [
|
||||
"derive",
|
||||
@ -27,8 +25,7 @@ clap = { version = "4.3.0", default-features = false, features = [
|
||||
] }
|
||||
handlebars = { version = "4.3.7", default-features = false }
|
||||
is_ci = "1.1.1"
|
||||
once_cell = { version = "1.19.0", default-features = false }
|
||||
parse-flake-lock = { path = "parse-flake-lock" }
|
||||
parse-flake-lock = { path = "./parse-flake-lock" }
|
||||
reqwest = { version = "0.11.18", default-features = false, features = [
|
||||
"blocking",
|
||||
"json",
|
||||
@ -38,3 +35,7 @@ serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = { version = "0.10.6", default-features = false }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
allowed-refs = []
|
||||
|
83
README.md
83
README.md
@ -14,21 +14,83 @@ nix run github:DeterminateSystems/flake-checker
|
||||
nix run github:DeterminateSystems/flake-checker /path/to/flake.lock
|
||||
```
|
||||
|
||||
Nix Flake Checker looks at your `flake.lock`'s root-level [Nixpkgs] inputs and checks that:
|
||||
Nix Flake Checker looks at your `flake.lock`'s root-level [Nixpkgs] inputs.
|
||||
There are two ways to express flake policies:
|
||||
|
||||
- Any explicit Nixpkgs Git refs are in this list:
|
||||
- `nixos-23.11`
|
||||
- `nixos-23.11-small`
|
||||
- `nixos-unstable`
|
||||
- `nixos-unstable-small`
|
||||
- `nixpkgs-23.11-darwin`
|
||||
- `nixpkgs-unstable`
|
||||
- Any Nixpkgs dependencies are less than 30 days old
|
||||
- Any Nixpkgs dependencies have the [`NixOS`][nixos-org] org as the GitHub owner (and thus that the dependency isn't a fork or non-upstream variant)
|
||||
* Via [config parameters](#parameters).
|
||||
* Via [policy conditions](#policy-conditions) using [Common Expression Language][cel] (CEL).
|
||||
|
||||
If you're running it locally, Nix Flake Checker reports any issues via text output in your terminal.
|
||||
But you can also use Nix Flake Checker [in CI](#the-flake-checker-action).
|
||||
|
||||
## Supported branches
|
||||
|
||||
At any given time, [Nixpkgs] has a bounded set of branches that are considered *supported*.
|
||||
The current list:
|
||||
|
||||
* `nixos-23.11`
|
||||
* `nixos-23.11-small`
|
||||
* `nixos-24.05`
|
||||
* `nixos-24.05-small`
|
||||
* `nixos-unstable`
|
||||
* `nixos-unstable-small`
|
||||
* `nixpkgs-23.11-darwin`
|
||||
* `nixpkgs-24.05-darwin`
|
||||
* `nixpkgs-unstable`
|
||||
|
||||
## Parameters
|
||||
|
||||
By default, Flake Checker verifies that:
|
||||
|
||||
- Any explicit Nixpkgs Git refs are in the [supported list](#supported-branches).
|
||||
- Any Nixpkgs dependencies are less than 30 days old.
|
||||
- Any Nixpkgs dependencies have the [`NixOS`][nixos-org] org as the GitHub owner (and thus that the dependency isn't a fork or non-upstream variant).
|
||||
|
||||
You can adjust this behavior via configuration (all are enabled by default but you can disable them):
|
||||
|
||||
Flag | Environment variable | Action | Default
|
||||
:----|:---------------------|:-------|:-------
|
||||
`--check-outdated` | `NIX_FLAKE_CHECKER_CHECK_OUTDATED` | Check for outdated Nixpkgs inputs | `true`
|
||||
`--check-owner` | `NIX_FLAKE_CHECKER_CHECK_OWNER` | Check that Nixpkgs inputs have `NixOS` as the GitHub owner | `true`
|
||||
`--check-supported` | `NIX_FLAKE_CHECKER_CHECK_SUPPORTED` | Check that Git refs for Nixpkgs inputs are supported | `true`
|
||||
|
||||
## Policy conditions
|
||||
|
||||
You can apply a CEL condition to your flake using the `--condition` flag.
|
||||
Here's an example:
|
||||
|
||||
```shell
|
||||
flake-checker --condition "has(numDaysOld) && numDaysOld < 365"
|
||||
```
|
||||
|
||||
This would check that each Nixpkgs input in your `flake.lock` is less than 365 days old.
|
||||
These variables are available in each condition:
|
||||
|
||||
Variable | Description
|
||||
:--------|:-----------
|
||||
`gitRef` | The Git reference of the input.
|
||||
`numDaysOld` | The number of days old the input is.
|
||||
`owner` | The input's owner (if a GitHub input).
|
||||
`supportedRefs` | A list of [supported Git refs](#supported-branches) (all are branch names).
|
||||
|
||||
We recommend a condition *at least* this stringent:
|
||||
|
||||
```ruby
|
||||
supportedRefs.contains(gitRef) && (has(numDaysOld) && numDaysOld < 30) && owner == 'NixOS'
|
||||
```
|
||||
|
||||
Note that not all Nixpkgs inputs have a `numDaysOld` field, so make sure to ensure that that field exists when checking for the number of days.
|
||||
|
||||
Here are some other example conditions:
|
||||
|
||||
```ruby
|
||||
# Updated in the last two weeks
|
||||
supportedRefs.contains(gitRef) && (has(numDaysOld) && numDaysOld < 14) && owner == 'NixOS'
|
||||
|
||||
# Check for most recent stable Nixpkgs
|
||||
gitRef.contains("24.05")
|
||||
```
|
||||
|
||||
## The Nix Flake Checker Action
|
||||
|
||||
You can automate Nix Flake Checker by adding Determinate Systems' [Nix Flake Checker Action][action] to your GitHub Actions workflows:
|
||||
@ -96,6 +158,7 @@ The `parse-flake-lock` crate doesn't yet exhaustively parse all input node types
|
||||
If you'd like to help make the parser more exhaustive, [pull requests][prs] are quite welcome.
|
||||
|
||||
[action]: https://github.com/DeterminateSystems/flake-checker-action
|
||||
[cel]: https://cel.dev
|
||||
[detsys]: https://determinate.systems
|
||||
[flakes]: https://zero-to-nix.com/concepts/flakes
|
||||
[install]: https://zero-to-nix.com/start/install
|
||||
|
@ -12,7 +12,7 @@
|
||||
let
|
||||
inherit (stdenv.hostPlatform) system;
|
||||
|
||||
nightlyVersion = "2023-05-01";
|
||||
nightlyVersion = "2024-06-13";
|
||||
rustNightly = pkgs.rust-bin.nightly.${nightlyVersion}.default.override {
|
||||
extensions = [ "rust-src" "rust-analyzer-preview" ];
|
||||
targets = cargoTargets;
|
||||
@ -80,7 +80,7 @@ let
|
||||
# The Rust toolchain from rust-overlay has a dynamic libiconv in depsTargetTargetPropagated
|
||||
# Our static libiconv needs to take precedence
|
||||
++ lib.optionals pkgs.stdenv.isDarwin [
|
||||
(libiconv.override { enableStatic = true; enableShared = false; })
|
||||
libiconv
|
||||
];
|
||||
|
||||
cargoExtraArgs = "--target ${crossPlatform.rustTargetSpec}";
|
||||
|
104
flake.lock
104
flake.lock
@ -2,31 +2,26 @@
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-overlay": "rust-overlay"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"narHash": "sha256-ASliYUzlN/aTGDZ2d0FIqxq5fiz+Cwk0q2rYXgy4pB0=",
|
||||
"rev": "8cb0282cb7c7b5ad7ce1c47d48f647836f8924a0",
|
||||
"revCount": 432,
|
||||
"lastModified": 1717383740,
|
||||
"narHash": "sha256-559HbY4uhNeoYvK3H6AMZAtVfmR3y8plXZ1x6ON/cWU=",
|
||||
"rev": "b65673fce97d277934488a451724be94cc62499a",
|
||||
"revCount": 580,
|
||||
"type": "tarball",
|
||||
"url": "https://api.flakehub.com/f/pinned/ipetkov/crane/0.14.2/018b3503-625a-71c8-96ff-c86e61bd12f7/source.tar.gz"
|
||||
"url": "https://api.flakehub.com/f/pinned/ipetkov/crane/0.17.3/018fdc0e-176b-7a0f-92ce-cc2d0db7b735/source.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://flakehub.com/f/ipetkov/crane/0.14.%2A.tar.gz"
|
||||
"url": "https://flakehub.com/f/ipetkov/crane/0.17.%2A"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"revCount": 57,
|
||||
@ -35,89 +30,43 @@
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://flakehub.com/f/edolstra/flake-compat/1.0.1.tar.gz"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1694529238,
|
||||
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "flake-utils",
|
||||
"type": "indirect"
|
||||
"url": "https://flakehub.com/f/edolstra/flake-compat/1.0.1"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1704290814,
|
||||
"narHash": "sha256-LWvKHp7kGxk/GEtlrGYV68qIvPHkU9iToomNFGagixU=",
|
||||
"rev": "70bdadeb94ffc8806c0570eb5c2695ad29f0e421",
|
||||
"revCount": 492897,
|
||||
"lastModified": 1717952948,
|
||||
"narHash": "sha256-mJi4/gjiwQlSaxjA6AusXBN/6rQRaPCycR7bd8fydnQ=",
|
||||
"rev": "2819fffa7fa42156680f0d282c60d81e8fb185b7",
|
||||
"revCount": 631440,
|
||||
"type": "tarball",
|
||||
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2305.492897%2Brev-70bdadeb94ffc8806c0570eb5c2695ad29f0e421/018ce318-b896-7d27-b495-cc2cdb39d680/source.tar.gz"
|
||||
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.631440%2Brev-2819fffa7fa42156680f0d282c60d81e8fb185b7/0190034c-678d-7039-b45c-fa38168f2500/source.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.2305.%2A.tar.gz"
|
||||
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.2405.%2A"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay_2"
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"crane",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"crane",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1696299134,
|
||||
"narHash": "sha256-RS77cAa0N+Sfj5EmKbm5IdncNXaBCE1BSSQvUE8exvo=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "611ccdceed92b4d94ae75328148d84ee4a5b462d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rust-overlay_2": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1697422411,
|
||||
"narHash": "sha256-eCj20wEwATLm7Bd/+/wOIdbqq9jgvS6ZxMrxujX2DxU=",
|
||||
"lastModified": 1719800573,
|
||||
"narHash": "sha256-9DLgG4T6l7cc4pJNOCcXGUwHsFfUp8KLsiwed65MdHk=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "056256f2fcf3c5a652dbc3edba9ec1a956d41f56",
|
||||
"rev": "648b25dd9c3acd255dc50c1eb3ca8b987856f675",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -125,21 +74,6 @@
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
12
flake.nix
12
flake.nix
@ -1,25 +1,21 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2305.*.tar.gz";
|
||||
|
||||
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2405.*";
|
||||
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
|
||||
crane = {
|
||||
url = "https://flakehub.com/f/ipetkov/crane/0.14.*.tar.gz";
|
||||
url = "https://flakehub.com/f/ipetkov/crane/0.17.*";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.flake-compat.follows = "flake-compat";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
|
||||
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.0.1.tar.gz";
|
||||
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.0.1";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, rust-overlay, crane, ... }:
|
||||
outputs = { self, nixpkgs, rust-overlay, crane, ... }:
|
||||
let
|
||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f rec {
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "parse-flake-lock"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
@ -107,7 +107,10 @@ impl<'de> Deserialize<'de> for FlakeLock {
|
||||
let mut root_nodes = HashMap::new();
|
||||
let root_node = &nodes[&root];
|
||||
let Node::Root(root_node) = root_node else {
|
||||
return Err(de::Error::custom(format!("root node was not a Root node, but was a {} node", root_node.variant())));
|
||||
return Err(de::Error::custom(format!(
|
||||
"root node was not a Root node, but was a {} node",
|
||||
root_node.variant()
|
||||
)));
|
||||
};
|
||||
|
||||
for (root_name, root_input) in root_node.inputs.iter() {
|
||||
@ -204,7 +207,7 @@ pub enum Node {
|
||||
Indirect(IndirectNode),
|
||||
/// A [PathNode] flake input stemming from a filesystem path.
|
||||
Path(PathNode),
|
||||
/// TODO
|
||||
/// Nodes that point to tarball paths.
|
||||
Tarball(TarballNode),
|
||||
/// A "catch-all" variant for node types that don't (yet) have explicit struct definitions in
|
||||
/// this crate.
|
||||
@ -372,6 +375,9 @@ pub struct TarballNode {
|
||||
/// Information about the tarball input that's "locked" because it's supplied by Nix.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct TarballLocked {
|
||||
/// The timestamp for when the input was last modified.
|
||||
#[serde(alias = "lastModified")]
|
||||
pub last_modified: Option<i64>,
|
||||
/// The NAR hash of the input.
|
||||
#[serde(alias = "narHash")]
|
||||
pub nar_hash: String,
|
||||
|
82
src/condition.rs
Normal file
82
src/condition.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use cel_interpreter::{Context, Program, Value};
|
||||
use parse_flake_lock::{FlakeLock, Node};
|
||||
|
||||
use crate::{
|
||||
error::FlakeCheckerError,
|
||||
flake::{nixpkgs_deps, num_days_old},
|
||||
issue::{Issue, IssueKind},
|
||||
};
|
||||
|
||||
const KEY_GIT_REF: &str = "gitRef";
|
||||
const KEY_NUM_DAYS_OLD: &str = "numDaysOld";
|
||||
const KEY_OWNER: &str = "owner";
|
||||
const KEY_SUPPORTED_REFS: &str = "supportedRefs";
|
||||
|
||||
pub(super) fn evaluate_condition(
|
||||
flake_lock: &FlakeLock,
|
||||
nixpkgs_keys: &[String],
|
||||
condition: &str,
|
||||
supported_refs: Vec<String>,
|
||||
) -> Result<Vec<Issue>, FlakeCheckerError> {
|
||||
let mut issues: Vec<Issue> = vec![];
|
||||
let mut ctx = Context::default();
|
||||
ctx.add_variable_from_value(KEY_SUPPORTED_REFS, supported_refs);
|
||||
|
||||
let deps = nixpkgs_deps(flake_lock, nixpkgs_keys)?;
|
||||
|
||||
for (name, node) in deps {
|
||||
let (git_ref, last_modified, owner) = match node {
|
||||
Node::Repo(repo) => (
|
||||
repo.original.git_ref,
|
||||
Some(repo.locked.last_modified),
|
||||
Some(repo.original.owner),
|
||||
),
|
||||
Node::Tarball(tarball) => (None, tarball.locked.last_modified, None),
|
||||
_ => (None, None, None),
|
||||
};
|
||||
|
||||
add_cel_variables(&mut ctx, git_ref, last_modified, owner);
|
||||
|
||||
match Program::compile(condition)?.execute(&ctx) {
|
||||
Ok(result) => match result {
|
||||
Value::Bool(b) if !b => {
|
||||
issues.push(Issue {
|
||||
input: name.clone(),
|
||||
kind: IssueKind::Violation,
|
||||
});
|
||||
}
|
||||
Value::Bool(b) if b => continue,
|
||||
result => {
|
||||
return Err(FlakeCheckerError::NonBooleanCondition(
|
||||
result.type_of().to_string(),
|
||||
))
|
||||
}
|
||||
},
|
||||
Err(e) => return Err(FlakeCheckerError::CelExecution(e)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(issues)
|
||||
}
|
||||
|
||||
fn add_cel_variables(
|
||||
ctx: &mut Context,
|
||||
git_ref: Option<String>,
|
||||
last_modified: Option<i64>,
|
||||
owner: Option<String>,
|
||||
) {
|
||||
ctx.add_variable_from_value(KEY_GIT_REF, value_or_empty_string(git_ref));
|
||||
ctx.add_variable_from_value(
|
||||
KEY_NUM_DAYS_OLD,
|
||||
value_or_zero(last_modified.map(num_days_old)),
|
||||
);
|
||||
ctx.add_variable_from_value(KEY_OWNER, value_or_empty_string(owner));
|
||||
}
|
||||
|
||||
fn value_or_empty_string(value: Option<String>) -> Value {
|
||||
Value::from(value.unwrap_or(String::from("")))
|
||||
}
|
||||
|
||||
fn value_or_zero(value: Option<i64>) -> Value {
|
||||
Value::from(value.unwrap_or(0))
|
||||
}
|
@ -1,11 +1,17 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum FlakeCheckerError {
|
||||
#[error("CEL execution error: {0}")]
|
||||
CelExecution(#[from] cel_interpreter::ExecutionError),
|
||||
#[error("CEL parsing error: {0}")]
|
||||
CelParse(#[from] cel_interpreter::ParseError),
|
||||
#[error("env var error: {0}")]
|
||||
EnvVar(#[from] std::env::VarError),
|
||||
#[error("couldn't parse flake.lock: {0}")]
|
||||
FlakeLock(#[from] parse_flake_lock::FlakeLockParseError),
|
||||
#[error("http client error: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
#[error("CEL conditions must return a Boolean but returned {0} instead")]
|
||||
NonBooleanCondition(String),
|
||||
#[error("couldn't access flake.lock: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("couldn't parse flake.lock: {0}")]
|
||||
|
151
src/flake.rs
151
src/flake.rs
@ -30,9 +30,9 @@ impl Default for FlakeCheckConfig {
|
||||
}
|
||||
}
|
||||
|
||||
fn nixpkgs_deps(
|
||||
pub(super) fn nixpkgs_deps(
|
||||
flake_lock: &FlakeLock,
|
||||
keys: Vec<String>,
|
||||
keys: &[String],
|
||||
) -> Result<HashMap<String, Node>, FlakeCheckerError> {
|
||||
let mut deps: HashMap<String, Node> = HashMap::new();
|
||||
|
||||
@ -49,7 +49,7 @@ fn nixpkgs_deps(
|
||||
}
|
||||
}
|
||||
Node::Indirect(indirect_node) => {
|
||||
if &indirect_node.original.id == key {
|
||||
if keys.contains(key) && &indirect_node.original.id == key {
|
||||
deps.insert(key.to_string(), node);
|
||||
}
|
||||
}
|
||||
@ -83,29 +83,36 @@ pub(crate) fn check_flake_lock(
|
||||
) -> Result<Vec<Issue>, FlakeCheckerError> {
|
||||
let mut issues = vec![];
|
||||
|
||||
let deps = nixpkgs_deps(flake_lock, config.nixpkgs_keys.clone())?;
|
||||
let deps = nixpkgs_deps(flake_lock, &config.nixpkgs_keys)?;
|
||||
|
||||
for (name, dep) in deps {
|
||||
if let Node::Repo(repo) = dep {
|
||||
for (name, node) in deps {
|
||||
let (git_ref, last_modified, owner) = match node {
|
||||
Node::Repo(repo) => (
|
||||
repo.original.git_ref,
|
||||
Some(repo.locked.last_modified),
|
||||
Some(repo.original.owner),
|
||||
),
|
||||
Node::Tarball(tarball) => (None, tarball.locked.last_modified, None),
|
||||
_ => (None, None, None),
|
||||
};
|
||||
|
||||
// Check if not explicitly supported
|
||||
if let Some(git_ref) = git_ref {
|
||||
// Check if not explicitly supported
|
||||
if config.check_supported {
|
||||
if let Some(ref git_ref) = repo.original.git_ref {
|
||||
if !allowed_refs.contains(git_ref) {
|
||||
issues.push(Issue {
|
||||
input: name.clone(),
|
||||
kind: IssueKind::Disallowed(Disallowed {
|
||||
reference: git_ref.to_string(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
if config.check_supported && !allowed_refs.contains(&git_ref) {
|
||||
issues.push(Issue {
|
||||
input: name.clone(),
|
||||
kind: IssueKind::Disallowed(Disallowed {
|
||||
reference: git_ref.to_string(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(last_modified) = last_modified {
|
||||
// Check if outdated
|
||||
if config.check_outdated {
|
||||
let now_timestamp = Utc::now().timestamp();
|
||||
let diff = now_timestamp - repo.locked.last_modified;
|
||||
let num_days_old = Duration::seconds(diff).num_days();
|
||||
let num_days_old = num_days_old(last_modified);
|
||||
|
||||
if num_days_old > MAX_DAYS {
|
||||
issues.push(Issue {
|
||||
@ -114,39 +121,89 @@ pub(crate) fn check_flake_lock(
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(owner) = owner {
|
||||
// Check that the GitHub owner is NixOS
|
||||
if config.check_owner {
|
||||
let owner = repo.original.owner;
|
||||
if owner.to_lowercase() != "nixos" {
|
||||
issues.push(Issue {
|
||||
input: name.clone(),
|
||||
kind: IssueKind::NonUpstream(NonUpstream { owner }),
|
||||
});
|
||||
}
|
||||
if config.check_owner && owner.to_lowercase() != "nixos" {
|
||||
issues.push(Issue {
|
||||
input: name.clone(),
|
||||
kind: IssueKind::NonUpstream(NonUpstream { owner }),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(issues)
|
||||
}
|
||||
|
||||
pub(super) fn num_days_old(timestamp: i64) -> i64 {
|
||||
let now_timestamp = Utc::now().timestamp();
|
||||
let diff = now_timestamp - timestamp;
|
||||
Duration::seconds(diff).num_days()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{
|
||||
check_flake_lock,
|
||||
condition::evaluate_condition,
|
||||
issue::{Disallowed, Issue, IssueKind, NonUpstream},
|
||||
FlakeCheckConfig, FlakeLock,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_clean_flake_locks() {
|
||||
let allowed_refs: Vec<String> = serde_json::from_str(include_str!("../allowed-refs.json"))
|
||||
.expect("couldn't deserialize allowed-refs.json file");
|
||||
fn cel_conditions() {
|
||||
// (condition, expected)
|
||||
let cases: Vec<(&str, bool)> = vec![
|
||||
(include_str!("../tests/cel-condition.txt"), true),
|
||||
(
|
||||
|
||||
"has(gitRef) && has(numDaysOld) && has(owner) && has(supportedRefs) && supportedRefs.contains(gitRef) && owner != 'NixOS'",
|
||||
false,
|
||||
),
|
||||
(
|
||||
|
||||
"has(gitRef) && has(numDaysOld) && has(owner) && has(supportedRefs) && supportedRefs.contains(gitRef) && owner != 'NixOS'",
|
||||
false,
|
||||
),
|
||||
];
|
||||
|
||||
let supported_refs: Vec<String> =
|
||||
serde_json::from_str(include_str!("../allowed-refs.json")).unwrap();
|
||||
let path = PathBuf::from("tests/flake.cel.0.lock");
|
||||
|
||||
for (condition, expected) in cases {
|
||||
let flake_lock = FlakeLock::new(&path).unwrap();
|
||||
let config = FlakeCheckConfig {
|
||||
nixpkgs_keys: vec![String::from("nixpkgs")],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = evaluate_condition(
|
||||
&flake_lock,
|
||||
&config.nixpkgs_keys,
|
||||
condition,
|
||||
supported_refs.clone(),
|
||||
);
|
||||
|
||||
if expected {
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().is_empty());
|
||||
} else {
|
||||
assert!(!result.unwrap().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clean_flake_locks() {
|
||||
let allowed_refs: Vec<String> =
|
||||
serde_json::from_str(include_str!("../allowed-refs.json")).unwrap();
|
||||
for n in 0..=7 {
|
||||
let path = PathBuf::from(format!("tests/flake.clean.{n}.lock"));
|
||||
let flake_lock = FlakeLock::new(&path).expect("couldn't create flake.lock");
|
||||
let flake_lock = FlakeLock::new(&path).unwrap();
|
||||
let config = FlakeCheckConfig {
|
||||
check_outdated: false,
|
||||
..Default::default()
|
||||
@ -161,9 +218,9 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dirty_flake_locks() {
|
||||
let allowed_refs: Vec<String> = serde_json::from_str(include_str!("../allowed-refs.json"))
|
||||
.expect("couldn't deserialize allowed-refs.json file");
|
||||
fn dirty_flake_locks() {
|
||||
let allowed_refs: Vec<String> =
|
||||
serde_json::from_str(include_str!("../allowed-refs.json")).unwrap();
|
||||
let cases: Vec<(&str, Vec<Issue>)> = vec![
|
||||
(
|
||||
"flake.dirty.0.lock",
|
||||
@ -203,22 +260,21 @@ mod test {
|
||||
|
||||
for (file, expected_issues) in cases {
|
||||
let path = PathBuf::from(format!("tests/{file}"));
|
||||
let flake_lock = FlakeLock::new(&path).expect("couldn't create flake.lock");
|
||||
let flake_lock = FlakeLock::new(&path).unwrap();
|
||||
let config = FlakeCheckConfig {
|
||||
check_outdated: false,
|
||||
..Default::default()
|
||||
};
|
||||
let issues = check_flake_lock(&flake_lock, &config, allowed_refs.clone())
|
||||
.expect("couldn't run check_flake_lock function");
|
||||
let issues = check_flake_lock(&flake_lock, &config, allowed_refs.clone()).unwrap();
|
||||
dbg!(&path);
|
||||
assert_eq!(issues, expected_issues);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_explicit_nixpkgs_keys() {
|
||||
let allowed_refs: Vec<String> = serde_json::from_str(include_str!("../allowed-refs.json"))
|
||||
.expect("couldn't deserialize allowed-refs.json file");
|
||||
fn explicit_nixpkgs_keys() {
|
||||
let allowed_refs: Vec<String> =
|
||||
serde_json::from_str(include_str!("../allowed-refs.json")).unwrap();
|
||||
let cases: Vec<(&str, Vec<String>, Vec<Issue>)> = vec![(
|
||||
"flake.explicit-keys.0.lock",
|
||||
vec![String::from("nixpkgs"), String::from("nixpkgs-alt")],
|
||||
@ -232,22 +288,21 @@ mod test {
|
||||
|
||||
for (file, nixpkgs_keys, expected_issues) in cases {
|
||||
let path = PathBuf::from(format!("tests/{file}"));
|
||||
let flake_lock = FlakeLock::new(&path).expect("couldn't create flake.lock");
|
||||
let flake_lock = FlakeLock::new(&path).unwrap();
|
||||
let config = FlakeCheckConfig {
|
||||
check_outdated: false,
|
||||
nixpkgs_keys,
|
||||
..Default::default()
|
||||
};
|
||||
let issues = check_flake_lock(&flake_lock, &config, allowed_refs.clone())
|
||||
.expect("couldn't run check_flake_lock function");
|
||||
let issues = check_flake_lock(&flake_lock, &config, allowed_refs.clone()).unwrap();
|
||||
assert_eq!(issues, expected_issues);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_nixpkgs_keys() {
|
||||
let allowed_refs: Vec<String> = serde_json::from_str(include_str!("../allowed-refs.json"))
|
||||
.expect("couldn't deserialize allowed-refs.json file");
|
||||
fn missing_nixpkgs_keys() {
|
||||
let allowed_refs: Vec<String> =
|
||||
serde_json::from_str(include_str!("../allowed-refs.json")).unwrap();
|
||||
let cases: Vec<(&str, Vec<String>, String)> = vec![(
|
||||
"flake.clean.0.lock",
|
||||
vec![String::from("nixpkgs"), String::from("foo"), String::from("bar")],
|
||||
@ -260,7 +315,7 @@ mod test {
|
||||
)];
|
||||
for (file, nixpkgs_keys, expected_err) in cases {
|
||||
let path = PathBuf::from(format!("tests/{file}"));
|
||||
let flake_lock = FlakeLock::new(&path).expect("couldn't create flake.lock");
|
||||
let flake_lock = FlakeLock::new(&path).unwrap();
|
||||
let config = FlakeCheckConfig {
|
||||
check_outdated: false,
|
||||
nixpkgs_keys,
|
||||
|
@ -12,6 +12,7 @@ pub(crate) enum IssueKind {
|
||||
Disallowed(Disallowed),
|
||||
Outdated(Outdated),
|
||||
NonUpstream(NonUpstream),
|
||||
Violation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
@ -41,4 +42,8 @@ impl IssueKind {
|
||||
pub(crate) fn is_non_upstream(&self) -> bool {
|
||||
matches!(self, Self::NonUpstream(_))
|
||||
}
|
||||
|
||||
pub(crate) fn is_violation(&self) -> bool {
|
||||
matches!(self, Self::Violation)
|
||||
}
|
||||
}
|
||||
|
28
src/main.rs
28
src/main.rs
@ -1,5 +1,6 @@
|
||||
#[cfg(feature = "allowed-refs")]
|
||||
mod allowed_refs;
|
||||
mod condition;
|
||||
mod error;
|
||||
mod flake;
|
||||
mod issue;
|
||||
@ -16,6 +17,8 @@ use std::process::ExitCode;
|
||||
use clap::Parser;
|
||||
use parse_flake_lock::FlakeLock;
|
||||
|
||||
use crate::condition::evaluate_condition;
|
||||
|
||||
/// A flake.lock checker for Nix projects.
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
@ -86,6 +89,10 @@ struct Cli {
|
||||
)]
|
||||
markdown_summary: bool,
|
||||
|
||||
/// The Common Expression Language (CEL) policy to apply to each Nixpkgs input.
|
||||
#[arg(long, short, env = "NIX_FLAKE_CHECKER_CONDITION")]
|
||||
condition: Option<String>,
|
||||
|
||||
#[cfg(feature = "allowed-refs")]
|
||||
// Check to make sure that Flake Checker is aware of the current supported branches.
|
||||
#[arg(long, hide = true)]
|
||||
@ -98,8 +105,8 @@ struct Cli {
|
||||
}
|
||||
|
||||
fn main() -> Result<ExitCode, FlakeCheckerError> {
|
||||
let allowed_refs: Vec<String> = serde_json::from_str(include_str!("../allowed-refs.json"))
|
||||
.expect("couldn't deserialize allowed-refs.json file");
|
||||
let allowed_refs: Vec<String> =
|
||||
serde_json::from_str(include_str!("../allowed-refs.json")).unwrap();
|
||||
|
||||
let Cli {
|
||||
no_telemetry,
|
||||
@ -111,6 +118,7 @@ fn main() -> Result<ExitCode, FlakeCheckerError> {
|
||||
fail_mode,
|
||||
nixpkgs_keys,
|
||||
markdown_summary,
|
||||
condition,
|
||||
#[cfg(feature = "allowed-refs")]
|
||||
check_allowed_refs,
|
||||
#[cfg(feature = "allowed-refs")]
|
||||
@ -166,17 +174,27 @@ fn main() -> Result<ExitCode, FlakeCheckerError> {
|
||||
check_supported,
|
||||
check_outdated,
|
||||
check_owner,
|
||||
nixpkgs_keys,
|
||||
nixpkgs_keys: nixpkgs_keys.clone(),
|
||||
fail_mode,
|
||||
};
|
||||
|
||||
let issues = check_flake_lock(&flake_lock, &flake_check_config, allowed_refs.clone())?;
|
||||
let issues = if let Some(condition) = &condition {
|
||||
evaluate_condition(&flake_lock, &nixpkgs_keys, condition, allowed_refs.clone())?
|
||||
} else {
|
||||
check_flake_lock(&flake_lock, &flake_check_config, allowed_refs.clone())?
|
||||
};
|
||||
|
||||
if !no_telemetry {
|
||||
telemetry::TelemetryReport::make_and_send(&issues);
|
||||
}
|
||||
|
||||
let summary = Summary::new(&issues, flake_lock_path, flake_check_config, allowed_refs);
|
||||
let summary = Summary::new(
|
||||
&issues,
|
||||
flake_lock_path,
|
||||
flake_check_config,
|
||||
allowed_refs,
|
||||
condition,
|
||||
);
|
||||
|
||||
if std::env::var("GITHUB_ACTIONS").is_ok() {
|
||||
if markdown_summary {
|
||||
|
119
src/summary.rs
119
src/summary.rs
@ -10,14 +10,24 @@ use std::path::PathBuf;
|
||||
use handlebars::Handlebars;
|
||||
use serde_json::json;
|
||||
|
||||
static MARKDOWN_TEMPLATE: &str = include_str!(concat!(
|
||||
static CEL_MARKDOWN_TEMPLATE: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/templates/summary_md.hbs"
|
||||
"/src/templates/summary.cel.md.hbs"
|
||||
));
|
||||
|
||||
static TEXT_TEMPLATE: &str = include_str!(concat!(
|
||||
static CEL_TEXT_TEMPLATE: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/templates/summary_txt.hbs"
|
||||
"/src/templates/summary.cel.txt.hbs"
|
||||
));
|
||||
|
||||
static STANDARD_MARKDOWN_TEMPLATE: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/templates/summary.standard.md.hbs"
|
||||
));
|
||||
|
||||
static STANDARD_TEXT_TEMPLATE: &str = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/src/templates/summary.standard.txt.hbs"
|
||||
));
|
||||
|
||||
pub(crate) struct Summary {
|
||||
@ -25,6 +35,7 @@ pub(crate) struct Summary {
|
||||
data: serde_json::Value,
|
||||
flake_lock_path: PathBuf,
|
||||
flake_check_config: FlakeCheckConfig,
|
||||
condition: Option<String>,
|
||||
}
|
||||
|
||||
impl Summary {
|
||||
@ -33,37 +44,62 @@ impl Summary {
|
||||
flake_lock_path: PathBuf,
|
||||
flake_check_config: FlakeCheckConfig,
|
||||
allowed_refs: Vec<String>,
|
||||
condition: Option<String>,
|
||||
) -> Self {
|
||||
let disallowed: Vec<&Issue> = issues.iter().filter(|i| i.kind.is_disallowed()).collect();
|
||||
let outdated: Vec<&Issue> = issues.iter().filter(|i| i.kind.is_outdated()).collect();
|
||||
let non_upstream: Vec<&Issue> =
|
||||
issues.iter().filter(|i| i.kind.is_non_upstream()).collect();
|
||||
let num_issues = issues.len();
|
||||
let clean = issues.is_empty();
|
||||
let issue_word = if issues.len() == 1 { "issue" } else { "issues" };
|
||||
|
||||
let data = json!({
|
||||
"issues": issues,
|
||||
"num_issues": issues.len(),
|
||||
"clean": issues.is_empty(),
|
||||
"dirty": !issues.is_empty(),
|
||||
"issue_word": if issues.len() == 1 { "issue" } else { "issues" },
|
||||
// Disallowed refs
|
||||
"has_disallowed": !disallowed.is_empty(),
|
||||
"disallowed": disallowed,
|
||||
// Outdated refs
|
||||
"has_outdated": !outdated.is_empty(),
|
||||
"outdated": outdated,
|
||||
// Non-upstream refs
|
||||
"has_non_upstream": !non_upstream.is_empty(),
|
||||
"non_upstream": non_upstream,
|
||||
// Constants
|
||||
"max_days": MAX_DAYS,
|
||||
"supported_ref_names": allowed_refs,
|
||||
});
|
||||
let data = if let Some(condition) = &condition {
|
||||
let inputs_with_violations: Vec<String> = issues
|
||||
.iter()
|
||||
.filter(|i| i.kind.is_violation())
|
||||
.map(|i| i.input.to_owned())
|
||||
.collect();
|
||||
|
||||
json!({
|
||||
"issues": issues,
|
||||
"num_issues": num_issues,
|
||||
"clean": clean,
|
||||
"dirty": !clean,
|
||||
"issue_word": issue_word,
|
||||
"condition": condition,
|
||||
"inputs_with_violations": inputs_with_violations,
|
||||
})
|
||||
} else {
|
||||
let disallowed: Vec<&Issue> =
|
||||
issues.iter().filter(|i| i.kind.is_disallowed()).collect();
|
||||
let outdated: Vec<&Issue> = issues.iter().filter(|i| i.kind.is_outdated()).collect();
|
||||
let non_upstream: Vec<&Issue> =
|
||||
issues.iter().filter(|i| i.kind.is_non_upstream()).collect();
|
||||
|
||||
json!({
|
||||
"issues": issues,
|
||||
"num_issues": num_issues,
|
||||
"clean": clean,
|
||||
"dirty": !clean,
|
||||
"issue_word": issue_word,
|
||||
// Disallowed refs
|
||||
"has_disallowed": !disallowed.is_empty(),
|
||||
"disallowed": disallowed,
|
||||
// Outdated refs
|
||||
"has_outdated": !outdated.is_empty(),
|
||||
"outdated": outdated,
|
||||
// Non-upstream refs
|
||||
"has_non_upstream": !non_upstream.is_empty(),
|
||||
"non_upstream": non_upstream,
|
||||
// Constants
|
||||
"max_days": MAX_DAYS,
|
||||
"supported_ref_names": allowed_refs,
|
||||
})
|
||||
};
|
||||
|
||||
Self {
|
||||
issues: issues.to_vec(),
|
||||
data,
|
||||
flake_lock_path,
|
||||
flake_check_config,
|
||||
condition,
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,6 +108,18 @@ impl Summary {
|
||||
|
||||
if self.issues.is_empty() {
|
||||
println!("The Determinate Nix Flake Checker scanned {file} and found no issues");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(condition) = &self.condition {
|
||||
println!(
|
||||
"You supplied this CEL condition for your flake:\n\n{}",
|
||||
condition
|
||||
);
|
||||
println!("The following inputs violate that condition:\n");
|
||||
for issue in self.issues.iter() {
|
||||
println!("* {}", issue.input);
|
||||
}
|
||||
} else {
|
||||
let level = if self.flake_check_config.fail_mode {
|
||||
"error"
|
||||
@ -113,6 +161,7 @@ impl Summary {
|
||||
None
|
||||
}
|
||||
}
|
||||
IssueKind::Violation => Some(String::from("policy violation")),
|
||||
};
|
||||
|
||||
if let Some(message) = message {
|
||||
@ -124,10 +173,16 @@ impl Summary {
|
||||
}
|
||||
|
||||
pub fn generate_markdown(&self) -> Result<(), FlakeCheckerError> {
|
||||
let template = if self.condition.is_some() {
|
||||
CEL_MARKDOWN_TEMPLATE
|
||||
} else {
|
||||
STANDARD_MARKDOWN_TEMPLATE
|
||||
};
|
||||
|
||||
let mut handlebars = Handlebars::new();
|
||||
|
||||
handlebars
|
||||
.register_template_string("summary.md", MARKDOWN_TEMPLATE)
|
||||
.register_template_string("summary.md", template)
|
||||
.map_err(Box::new)?;
|
||||
let summary_md = handlebars.render("summary.md", &self.data)?;
|
||||
|
||||
@ -142,9 +197,15 @@ impl Summary {
|
||||
}
|
||||
|
||||
pub fn generate_text(&self) -> Result<(), FlakeCheckerError> {
|
||||
let template = if self.condition.is_some() {
|
||||
CEL_TEXT_TEMPLATE
|
||||
} else {
|
||||
STANDARD_TEXT_TEMPLATE
|
||||
};
|
||||
|
||||
let mut handlebars = Handlebars::new();
|
||||
handlebars
|
||||
.register_template_string("summary.txt", TEXT_TEMPLATE)
|
||||
.register_template_string("summary.txt", template)
|
||||
.map_err(Box::new)?;
|
||||
|
||||
let summary_txt = handlebars.render("summary.txt", &self.data)?;
|
||||
|
23
src/templates/summary.cel.md.hbs
Normal file
23
src/templates/summary.cel.md.hbs
Normal file
@ -0,0 +1,23 @@
|
||||
# ![](https://avatars.githubusercontent.com/u/80991770?s=30) Flake checkup
|
||||
|
||||
{{#if clean}}
|
||||
The Determinate Flake Checker Action scanned your `flake.lock` and didn't identify any issues.
|
||||
All Nixpkgs inputs conform to the flake policy expressed in your supplied [Common Expression Language](https://cel.dev) condition.
|
||||
{{/if}}
|
||||
|
||||
{{#if dirty}}
|
||||
⚠️ The Determinate Nix Installer Action scanned your `flake.lock` and discovered {{num_issues}} {{issue_word}} that we recommend looking into.
|
||||
You supplied this CEL condition:
|
||||
|
||||
```ruby
|
||||
{{condition}}
|
||||
```
|
||||
|
||||
The following inputs violate that condition:
|
||||
|
||||
{{#each inputs_with_violations}}
|
||||
* `{{this}}`
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
|
||||
<p>Feedback? Let us know at <a href="https://github.com/DeterminateSystems/flake-checker">DeterminateSystems/flake-checker</a>.</p>
|
19
src/templates/summary.cel.txt.hbs
Normal file
19
src/templates/summary.cel.txt.hbs
Normal file
@ -0,0 +1,19 @@
|
||||
Flake checker results:
|
||||
|
||||
{{#if clean}}
|
||||
The flake checker scanned your flake.lock and didn't identify any issues. You specified this CEL
|
||||
condition:
|
||||
|
||||
{{{condition}}}
|
||||
|
||||
All Nixpkgs inputs satisfy this condition.
|
||||
{{/if}}
|
||||
{{#if dirty}}
|
||||
The flake checker scanned your flake.lock and discovered {{num_issues}} {{issue_word}}
|
||||
that we recommend looking into. Here are the inputs that violate your supplied
|
||||
condition:
|
||||
|
||||
{{#each inputs_with_violations}}
|
||||
* {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
6
tests/cel-condition.txt
Normal file
6
tests/cel-condition.txt
Normal file
@ -0,0 +1,6 @@
|
||||
supportedRefs == ['nixos-24.05', 'nixos-24.05-small', 'nixos-unstable', 'nixos-unstable-small', 'nixpkgs-24.05-darwin', 'nixpkgs-unstable']
|
||||
&& owner == 'NixOS'
|
||||
&& gitRef == 'nixos-unstable'
|
||||
&& supportedRefs.contains(gitRef)
|
||||
&& has(numDaysOld)
|
||||
&& numDaysOld > 0
|
154
tests/flake.cel.0.lock
Normal file
154
tests/flake.cel.0.lock
Normal file
@ -0,0 +1,154 @@
|
||||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1684468982,
|
||||
"narHash": "sha256-EoC1N5sFdmjuAP3UOkyQujSOT6EdcXTnRw8hPjJkEgc=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "99de890b6ef4b4aab031582125b6056b792a4a30",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "flake-utils",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1686960236,
|
||||
"narHash": "sha256-AYCC9rXNLpUWzD9hm+askOfpliLEC9kwAo7ITJc4HIw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "04af42f3b31dba0ef742d254456dc4c14eedac86",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay_2"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"crane",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"crane",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683080331,
|
||||
"narHash": "sha256-nGDvJ1DAxZIwdn6ww8IFwzoHb2rqBP4wv/65Wt5vflk=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "d59c3fa0cba8336e115b376c2d9e91053aa59e56",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rust-overlay_2": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1684808436,
|
||||
"narHash": "sha256-WG5LgB1+Oguj4H4Bpqr5GoLSc382LyGlaToiOw5xhwA=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "a227d4571dd1f948138a40ea8b0d0c413eefb44b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
Loading…
Reference in New Issue
Block a user