Provide support for tarball nodes

This commit is contained in:
Luc Perkins 2024-06-18 10:00:22 -07:00
parent 0d16f33018
commit aa0f8feb98
No known key found for this signature in database
GPG Key ID: 16DB1108FB591835
8 changed files with 107 additions and 84 deletions

2
Cargo.lock generated
View File

@ -858,7 +858,7 @@ dependencies = [
[[package]]
name = "parse-flake-lock"
version = "0.1.0"
version = "0.1.1"
dependencies = [
"serde",
"serde_json",

View File

@ -60,7 +60,7 @@ You can apply a CEL condition to your flake using the `--condition` flag.
Here's an example:
```shell
flake-checker --condition "numDaysOld < 365"
flake-checker --condition "has(numDaysOld) && numDaysOld < 365"
```
This would check that each Nixpkgs input in your `flake.lock` is less than 365 days old.
@ -76,14 +76,16 @@ Variable | Description
We recommend a condition *at least* this stringent:
```ruby
supportedRefs.contains(gitRef) && numDaysOld < 30 && owner == 'NixOS'
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) && numDaysOld < 14 && owner == 'NixOS'
supportedRefs.contains(gitRef) && (has(numDaysOld) && numDaysOld < 14) && owner == 'NixOS'
# Check for most recent stable Nixpkgs
gitRef.contains("24.05")

View File

@ -14,11 +14,12 @@
"rust-overlay": "rust-overlay"
},
"locked": {
"narHash": "sha256-ASliYUzlN/aTGDZ2d0FIqxq5fiz+Cwk0q2rYXgy4pB0=",
"rev": "8cb0282cb7c7b5ad7ce1c47d48f647836f8924a0",
"revCount": 432,
"lastModified": 1697588719,
"narHash": "sha256-n9ALgm3S+ygpzjesBkB9qutEtM4dtIkhn8WnstCPOew=",
"rev": "da6b58e270d339a78a6e95728012ec2eea879612",
"revCount": 440,
"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.14.3/018b402e-8337-76a6-9764-1748a79a54fd/source.tar.gz"
},
"original": {
"type": "tarball",
@ -27,6 +28,7 @@
},
"flake-compat": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
@ -43,11 +45,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@ -57,16 +59,16 @@
},
"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.tar.gz"
}
},
"root": {
@ -105,19 +107,16 @@
},
"rust-overlay_2": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1697422411,
"narHash": "sha256-eCj20wEwATLm7Bd/+/wOIdbqq9jgvS6ZxMrxujX2DxU=",
"lastModified": 1718681902,
"narHash": "sha256-E/T7Ge6ayEQe7FVKMJqDBoHyLhRhjc6u9CmU8MyYfy0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "056256f2fcf3c5a652dbc3edba9ec1a956d41f56",
"rev": "16c8ad83297c278eebe740dea5491c1708960dd1",
"type": "github"
},
"original": {

View File

@ -1,6 +1,6 @@
{
inputs = {
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2305.*.tar.gz";
nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2405.*.tar.gz";
rust-overlay = {

View File

@ -1,6 +1,6 @@
[package]
name = "parse-flake-lock"
version = "0.1.0"
version = "0.1.1"
edition = "2021"
[dependencies]

View File

@ -375,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,

View File

@ -1,5 +1,5 @@
use cel_interpreter::{Context, Program, Value};
use parse_flake_lock::{FlakeLock, Node, RepoNode};
use parse_flake_lock::{FlakeLock, Node};
use crate::{
error::FlakeCheckerError,
@ -19,61 +19,68 @@ pub(super) fn evaluate_condition(
allowed_refs: Vec<String>,
) -> Result<Vec<Issue>, FlakeCheckerError> {
let mut issues: Vec<Issue> = vec![];
let allowed_refs: Value = Value::from(
allowed_refs
.iter()
.map(|r| Value::from(r.to_string()))
.collect::<Vec<Value>>(),
);
let mut ctx = Context::default();
ctx.add_variable_from_value(KEY_SUPPORTED_REFS, allowed_refs.clone());
let deps = nixpkgs_deps(flake_lock, nixpkgs_keys)?;
for (name, dep) in deps {
if let Node::Repo(repo) = dep {
let mut ctx = Context::default();
ctx.add_variable_from_value(KEY_SUPPORTED_REFS, allowed_refs.clone());
for (k, v) in nixpkgs_cel_values(repo) {
ctx.add_variable_from_value(k, v);
}
for (name, node) in deps {
println!("name: {name}");
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)),
}
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 nixpkgs_cel_values(repo: Box<RepoNode>) -> Vec<(&'static str, Value)> {
vec![
(
KEY_GIT_REF,
repo.original
.git_ref
.map_or_else(|| Value::Null, Value::from),
),
(
KEY_NUM_DAYS_OLD,
Value::from(num_days_old(repo.locked.last_modified)),
),
(KEY_OWNER, Value::from(repo.original.owner)),
]
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(|d| num_days_old(d))),
);
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))
}
pub(super) fn vet_condition(condition: &str) -> Result<(), FlakeCheckerError> {

View File

@ -85,25 +85,36 @@ pub(crate) fn check_flake_lock(
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 !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 num_days_old = num_days_old(repo.locked.last_modified);
let num_days_old = num_days_old(last_modified);
if num_days_old > MAX_DAYS {
issues.push(Issue {
@ -112,10 +123,11 @@ 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(),