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]] [[package]]
name = "parse-flake-lock" name = "parse-flake-lock"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "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: Here's an example:
```shell ```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. 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: We recommend a condition *at least* this stringent:
```ruby ```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: Here are some other example conditions:
```ruby ```ruby
# Updated in the last two weeks # 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 # Check for most recent stable Nixpkgs
gitRef.contains("24.05") gitRef.contains("24.05")

View File

@ -14,11 +14,12 @@
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"narHash": "sha256-ASliYUzlN/aTGDZ2d0FIqxq5fiz+Cwk0q2rYXgy4pB0=", "lastModified": 1697588719,
"rev": "8cb0282cb7c7b5ad7ce1c47d48f647836f8924a0", "narHash": "sha256-n9ALgm3S+ygpzjesBkB9qutEtM4dtIkhn8WnstCPOew=",
"revCount": 432, "rev": "da6b58e270d339a78a6e95728012ec2eea879612",
"revCount": 440,
"type": "tarball", "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": { "original": {
"type": "tarball", "type": "tarball",
@ -27,6 +28,7 @@
}, },
"flake-compat": { "flake-compat": {
"locked": { "locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57, "revCount": 57,
@ -43,11 +45,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1694529238, "lastModified": 1710146030,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -57,16 +59,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1704290814, "lastModified": 1717952948,
"narHash": "sha256-LWvKHp7kGxk/GEtlrGYV68qIvPHkU9iToomNFGagixU=", "narHash": "sha256-mJi4/gjiwQlSaxjA6AusXBN/6rQRaPCycR7bd8fydnQ=",
"rev": "70bdadeb94ffc8806c0570eb5c2695ad29f0e421", "rev": "2819fffa7fa42156680f0d282c60d81e8fb185b7",
"revCount": 492897, "revCount": 631440,
"type": "tarball", "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": { "original": {
"type": "tarball", "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": { "root": {
@ -105,19 +107,16 @@
}, },
"rust-overlay_2": { "rust-overlay_2": {
"inputs": { "inputs": {
"flake-utils": [
"flake-utils"
],
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1697422411, "lastModified": 1718681902,
"narHash": "sha256-eCj20wEwATLm7Bd/+/wOIdbqq9jgvS6ZxMrxujX2DxU=", "narHash": "sha256-E/T7Ge6ayEQe7FVKMJqDBoHyLhRhjc6u9CmU8MyYfy0=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "056256f2fcf3c5a652dbc3edba9ec1a956d41f56", "rev": "16c8ad83297c278eebe740dea5491c1708960dd1",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -1,6 +1,6 @@
{ {
inputs = { 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 = { rust-overlay = {

View File

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

View File

@ -375,6 +375,9 @@ pub struct TarballNode {
/// Information about the tarball input that's "locked" because it's supplied by Nix. /// Information about the tarball input that's "locked" because it's supplied by Nix.
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
pub struct TarballLocked { 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. /// The NAR hash of the input.
#[serde(alias = "narHash")] #[serde(alias = "narHash")]
pub nar_hash: String, pub nar_hash: String,

View File

@ -1,5 +1,5 @@
use cel_interpreter::{Context, Program, Value}; use cel_interpreter::{Context, Program, Value};
use parse_flake_lock::{FlakeLock, Node, RepoNode}; use parse_flake_lock::{FlakeLock, Node};
use crate::{ use crate::{
error::FlakeCheckerError, error::FlakeCheckerError,
@ -19,61 +19,68 @@ pub(super) fn evaluate_condition(
allowed_refs: Vec<String>, allowed_refs: Vec<String>,
) -> Result<Vec<Issue>, FlakeCheckerError> { ) -> Result<Vec<Issue>, FlakeCheckerError> {
let mut issues: Vec<Issue> = vec![]; let mut issues: Vec<Issue> = vec![];
let mut ctx = Context::default();
let allowed_refs: Value = Value::from( ctx.add_variable_from_value(KEY_SUPPORTED_REFS, allowed_refs.clone());
allowed_refs
.iter()
.map(|r| Value::from(r.to_string()))
.collect::<Vec<Value>>(),
);
let deps = nixpkgs_deps(flake_lock, nixpkgs_keys)?; let deps = nixpkgs_deps(flake_lock, nixpkgs_keys)?;
for (name, dep) in deps { for (name, node) in deps {
if let Node::Repo(repo) = dep { println!("name: {name}");
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);
}
match Program::compile(condition)?.execute(&ctx) { let (git_ref, last_modified, owner) = match node {
Ok(result) => match result { Node::Repo(repo) => (
Value::Bool(b) if !b => { repo.original.git_ref,
issues.push(Issue { Some(repo.locked.last_modified),
input: name.clone(), Some(repo.original.owner),
kind: IssueKind::Violation, ),
}); Node::Tarball(tarball) => (None, tarball.locked.last_modified, None),
} _ => (None, None, None),
Value::Bool(b) if b => continue, };
result => {
return Err(FlakeCheckerError::NonBooleanCondition( add_cel_variables(&mut ctx, git_ref, last_modified, owner);
result.type_of().to_string(),
)) match Program::compile(condition)?.execute(&ctx) {
} Ok(result) => match result {
}, Value::Bool(b) if !b => {
Err(e) => return Err(FlakeCheckerError::CelExecution(e)), 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) Ok(issues)
} }
fn nixpkgs_cel_values(repo: Box<RepoNode>) -> Vec<(&'static str, Value)> { fn add_cel_variables(
vec![ ctx: &mut Context,
( git_ref: Option<String>,
KEY_GIT_REF, last_modified: Option<i64>,
repo.original owner: Option<String>,
.git_ref ) {
.map_or_else(|| Value::Null, Value::from), ctx.add_variable_from_value(KEY_GIT_REF, value_or_empty_string(git_ref));
), ctx.add_variable_from_value(
( KEY_NUM_DAYS_OLD,
KEY_NUM_DAYS_OLD, value_or_zero(last_modified.map(|d| num_days_old(d))),
Value::from(num_days_old(repo.locked.last_modified)), );
), ctx.add_variable_from_value(KEY_OWNER, value_or_empty_string(owner));
(KEY_OWNER, Value::from(repo.original.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> { 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)?; let deps = nixpkgs_deps(flake_lock, &config.nixpkgs_keys)?;
for (name, dep) in deps { for (name, node) in deps {
if let Node::Repo(repo) = dep { 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 // Check if not explicitly supported
if config.check_supported { if config.check_supported {
if let Some(ref git_ref) = repo.original.git_ref { if !allowed_refs.contains(&git_ref) {
if !allowed_refs.contains(git_ref) { issues.push(Issue {
issues.push(Issue { input: name.clone(),
input: name.clone(), kind: IssueKind::Disallowed(Disallowed {
kind: IssueKind::Disallowed(Disallowed { reference: git_ref.to_string(),
reference: git_ref.to_string(), }),
}), });
});
}
} }
} }
}
if let Some(last_modified) = last_modified {
// Check if outdated // Check if outdated
if config.check_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 { if num_days_old > MAX_DAYS {
issues.push(Issue { 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 // Check that the GitHub owner is NixOS
if config.check_owner { if config.check_owner {
let owner = repo.original.owner;
if owner.to_lowercase() != "nixos" { if owner.to_lowercase() != "nixos" {
issues.push(Issue { issues.push(Issue {
input: name.clone(), input: name.clone(),