Fetch flake outputs and display them
This commit is contained in:
36
figure-out-flake-outputs.nix
Normal file
36
figure-out-flake-outputs.nix
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
outputAttrs: let
|
||||||
|
|
||||||
|
attrNamePathToString = attrNamePath: builtins.concatStringsSep "." attrNamePath;
|
||||||
|
|
||||||
|
outputInfo = drv: builtins.listToAttrs (builtins.map (outputName: {
|
||||||
|
name = outputName;
|
||||||
|
value = {
|
||||||
|
store_path = drv."${outputName}".outPath;
|
||||||
|
};
|
||||||
|
}) drv.outputs);
|
||||||
|
|
||||||
|
recurseAttrs = attrs: attrNamePath: builtins.concatLists (builtins.attrValues (
|
||||||
|
builtins.mapAttrs (attrName: attrValue:
|
||||||
|
if builtins.typeOf attrValue == "set" then
|
||||||
|
if builtins.hasAttr "type" attrValue then
|
||||||
|
if attrValue.type == "derivation" then
|
||||||
|
[ {
|
||||||
|
name = attrNamePathToString (attrNamePath ++ [ attrName ]);
|
||||||
|
value = {
|
||||||
|
name = attrValue.name;
|
||||||
|
derivation_path = attrValue.drvPath;
|
||||||
|
system = attrValue.system;
|
||||||
|
outputs = outputInfo attrValue;
|
||||||
|
};
|
||||||
|
} ]
|
||||||
|
else
|
||||||
|
[ "unknown type" ]
|
||||||
|
else
|
||||||
|
recurseAttrs attrValue (attrNamePath ++ [ attrName ])
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
) attrs
|
||||||
|
));
|
||||||
|
|
||||||
|
in
|
||||||
|
builtins.listToAttrs (recurseAttrs outputAttrs [])
|
@@ -1,20 +1,20 @@
|
|||||||
CREATE TABLE revisions (
|
CREATE TABLE revisions (
|
||||||
revision_uri TEXT NOT NULL PRIMARY KEY,
|
revision_uri TEXT NOT NULL PRIMARY KEY,
|
||||||
flake_uri TEXT NOT NULL,
|
flake_uri TEXT NOT NULL,
|
||||||
store_path TEXT,
|
store_path TEXT NOT NULL,
|
||||||
last_modified INT
|
last_modified INT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE output_attributes (
|
CREATE TABLE output_attributes (
|
||||||
revision_uri TEXT NOT NULL,
|
revision_uri TEXT NOT NULL,
|
||||||
output_attribute_name TEXT NOT NULL,
|
output_attribute_name TEXT NOT NULL,
|
||||||
derivation_path TEXT,
|
derivation_path TEXT NOT NULL,
|
||||||
PRIMARY KEY (revision_uri, output_attribute_name)
|
PRIMARY KEY (revision_uri, output_attribute_name)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE build_outputs (
|
CREATE TABLE build_outputs (
|
||||||
derivation_path TEXT NOT NULL,
|
derivation_path TEXT NOT NULL,
|
||||||
build_output_name TEXT NOT NULL,
|
build_output_name TEXT NOT NULL,
|
||||||
store_path TEXT,
|
store_path TEXT NOT NULL,
|
||||||
PRIMARY KEY (derivation_path, build_output_name)
|
PRIMARY KEY (derivation_path, build_output_name)
|
||||||
);
|
);
|
||||||
|
54
src/scan.rs
54
src/scan.rs
@@ -13,12 +13,29 @@ use crate::{
|
|||||||
},
|
},
|
||||||
storage::{
|
storage::{
|
||||||
FlakeRow,
|
FlakeRow,
|
||||||
|
OutputAttributeRow,
|
||||||
RevisionRow,
|
RevisionRow,
|
||||||
Storage,
|
Storage,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use serde::{
|
||||||
|
Deserialize,
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct OutputAttribute {
|
||||||
|
pub derivation_path: String,
|
||||||
|
pub name: String,
|
||||||
|
pub outputs: HashMap<String, BuildOutput>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct BuildOutput {
|
||||||
|
pub store_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn scan_flake(storage: Storage, flake_uri: &str) -> Result<()> {
|
pub async fn scan_flake(storage: Storage, flake_uri: &str) -> Result<()> {
|
||||||
let scan_time = Utc::now().timestamp();
|
let scan_time = Utc::now().timestamp();
|
||||||
|
|
||||||
@@ -30,6 +47,20 @@ pub async fn scan_flake(storage: Storage, flake_uri: &str) -> Result<()> {
|
|||||||
storage.set_revision(revision_row)
|
storage.set_revision(revision_row)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let flake_outputs = fetch_outputs(flake_uri)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for (output_attribute_name, derivation_info) in &flake_outputs {
|
||||||
|
let output_attribute_row = OutputAttributeRow {
|
||||||
|
revision_uri: flake_metadata.locked.flake_uri()?.clone(),
|
||||||
|
output_attribute_name: output_attribute_name.clone(),
|
||||||
|
derivation_path: derivation_info.derivation_path.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
storage.set_output_attribute(output_attribute_row)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +83,28 @@ pub fn get_revision_from_metadata(flake_metadata: &FlakeMetadata) -> Result<Revi
|
|||||||
let revision_row = RevisionRow {
|
let revision_row = RevisionRow {
|
||||||
revision_uri: flake_metadata.locked.flake_uri()?.clone(),
|
revision_uri: flake_metadata.locked.flake_uri()?.clone(),
|
||||||
flake_uri: flake_metadata.resolved.flake_uri()?.clone(),
|
flake_uri: flake_metadata.resolved.flake_uri()?.clone(),
|
||||||
store_path: Some(flake_metadata.path.clone()),
|
store_path: flake_metadata.path.clone(),
|
||||||
last_modified: Some(flake_metadata.locked.last_modified.clone()),
|
last_modified: flake_metadata.locked.last_modified.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(revision_row)
|
Ok(revision_row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_outputs(flake_uri: &str) -> Result<HashMap<String, OutputAttribute>> {
|
||||||
|
let figure_out_flake_outputs = std::include_str!("../figure-out-flake-outputs.nix");
|
||||||
|
|
||||||
|
let flake_outputs_raw = Command::new("nix")
|
||||||
|
.arg("eval")
|
||||||
|
.arg("--json")
|
||||||
|
.arg(format!("{}#hydraJobs", flake_uri))
|
||||||
|
.arg("--apply")
|
||||||
|
.arg(figure_out_flake_outputs)
|
||||||
|
.output()
|
||||||
|
.context("Failed to fetch flake outputs")?;
|
||||||
|
|
||||||
|
println!("{}", str::from_utf8(&flake_outputs_raw.stdout)?);
|
||||||
|
let flake_outputs: HashMap<String, OutputAttribute> = serde_json::from_slice(&flake_outputs_raw.stdout)
|
||||||
|
.context("Failed to parse flake outputs")?;
|
||||||
|
|
||||||
|
Ok(flake_outputs)
|
||||||
|
}
|
||||||
|
@@ -97,14 +97,44 @@ impl Storage {
|
|||||||
.await
|
.await
|
||||||
.context("Failed to fetch data from database")
|
.context("Failed to fetch data from database")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn set_output_attribute(&self, output_attribute_row: OutputAttributeRow) -> Result<SqliteQueryResult> {
|
||||||
|
sqlx::query("INSERT INTO output_attributes (revision_uri, output_attribute_name, derivation_path)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
ON CONFLICT(revision_uri, output_attribute_name) DO UPDATE SET
|
||||||
|
derivation_path=excluded.derivation_path
|
||||||
|
")
|
||||||
|
.bind(&output_attribute_row.revision_uri)
|
||||||
|
.bind(&output_attribute_row.output_attribute_name)
|
||||||
|
.bind(&output_attribute_row.derivation_path)
|
||||||
|
.execute(&self.db)
|
||||||
|
.await
|
||||||
|
.context("Failed to execute database query")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn output_attributes_for_revision(&self, revision_uri: &str) -> Result<Vec<OutputAttributeRow>> {
|
||||||
|
sqlx::query_as("
|
||||||
|
SELECT
|
||||||
|
revision_uri,
|
||||||
|
output_attribute_name,
|
||||||
|
derivation_path
|
||||||
|
FROM output_attributes
|
||||||
|
WHERE revision_uri = ?
|
||||||
|
ORDER BY output_attribute_name DESC
|
||||||
|
")
|
||||||
|
.bind(&revision_uri)
|
||||||
|
.fetch_all(&self.db)
|
||||||
|
.await
|
||||||
|
.context("Failed to fetch data from database")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromRow)]
|
#[derive(FromRow)]
|
||||||
pub struct RevisionRow {
|
pub struct RevisionRow {
|
||||||
pub revision_uri: String,
|
pub revision_uri: String,
|
||||||
pub flake_uri: String,
|
pub flake_uri: String,
|
||||||
pub store_path: Option<String>,
|
pub store_path: String,
|
||||||
pub last_modified: Option<i64>,
|
pub last_modified: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RevisionRow {
|
impl RevisionRow {
|
||||||
@@ -117,10 +147,7 @@ impl RevisionRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_modified_time(&self) -> Option<DateTime<Utc>> {
|
pub fn last_modified_time(&self) -> Option<DateTime<Utc>> {
|
||||||
match &self.last_modified {
|
DateTime::from_timestamp(self.last_modified.clone(), 0)
|
||||||
Some(last_modified) => DateTime::from_timestamp(last_modified.clone(), 0),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,3 +162,33 @@ impl FlakeRow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
|
pub struct OutputAttributeRow {
|
||||||
|
pub revision_uri: String,
|
||||||
|
pub output_attribute_name: String,
|
||||||
|
pub derivation_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputAttributeRow {
|
||||||
|
pub fn revision_link(&self) -> String {
|
||||||
|
format!("/revisions/{}", urlencode(&self.revision_uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn derivation_link(&self) -> String {
|
||||||
|
format!("/derivations/{}", urlencode(&self.derivation_path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
|
pub struct BuildOutputRow {
|
||||||
|
pub derivation_path: String,
|
||||||
|
pub build_output_name: String,
|
||||||
|
pub store_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BuildOutputRow {
|
||||||
|
pub fn derivation_link(&self ) -> String {
|
||||||
|
format!("/derivations/{}", urlencode(&self.derivation_path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ use askama::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
storage::{
|
storage::{
|
||||||
FlakeRow,
|
FlakeRow,
|
||||||
|
OutputAttributeRow,
|
||||||
RevisionRow,
|
RevisionRow,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -26,4 +27,5 @@ pub struct FlakeTemplate {
|
|||||||
pub struct RevisionTemplate {
|
pub struct RevisionTemplate {
|
||||||
pub revision_uri: String,
|
pub revision_uri: String,
|
||||||
pub revision: RevisionRow,
|
pub revision: RevisionRow,
|
||||||
|
pub output_attributes: Vec<OutputAttributeRow>,
|
||||||
}
|
}
|
||||||
|
@@ -118,5 +118,6 @@ async fn route_revision(
|
|||||||
Ok(render_template(&RevisionTemplate {
|
Ok(render_template(&RevisionTemplate {
|
||||||
revision_uri: revision_uri.clone(),
|
revision_uri: revision_uri.clone(),
|
||||||
revision: state.storage.revision(&revision_uri).await?,
|
revision: state.storage.revision(&revision_uri).await?,
|
||||||
|
output_attributes: state.storage.output_attributes_for_revision(&revision_uri).await?,
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
@@ -8,5 +8,12 @@
|
|||||||
<li>Revision of: <a href="{{ revision.flake_link() }}">{{ revision.flake_uri }}</a></a>
|
<li>Revision of: <a href="{{ revision.flake_link() }}">{{ revision.flake_uri }}</a></a>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h2>Outputs</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for output in output_attributes %}
|
||||||
|
<li>{{ output.output_attribute_name }}: <a href="{{ output.derivation_link() }}">{{ output.derivation_path }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user