Skip to content

[RFD 621 3/n] switch to Tufaceous v2 in Nexus and Sled Agent#10774

Open
iliana wants to merge 10 commits into
mainfrom
iliana/tufaceous-v2/30.nexus
Open

[RFD 621 3/n] switch to Tufaceous v2 in Nexus and Sled Agent#10774
iliana wants to merge 10 commits into
mainfrom
iliana/tufaceous-v2/30.nexus

Conversation

@iliana

@iliana iliana commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

This replaces Tufaceous v1 with Tufaceous v2 in Nexus, Sled Agent, and a couple of other crates. Closes #10734.

This PR is independent of (but somewhat useless without) #10676.

iliana added 2 commits July 8, 2026 13:34
these are in their own commit for reviewability
@iliana

iliana commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

Current status (will be edited)

This (along with #10755) has been tested on a racklette and performs as expected with both v1 and v2 repos.

@iliana
iliana force-pushed the iliana/tufaceous-v2/30.nexus branch from 7c5fbb6 to 5a34123 Compare July 9, 2026 04:51
@iliana
iliana requested a review from karencfv July 9, 2026 20:39

@karencfv karencfv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry again for taking so long to have a look! I haven't finished reviewing, but I have some initial questions. A lot of this is just me wanting to understand what's going on. I'll continue with the review tomorrow.

Comment thread common/src/update/install_manifest.rs
Comment thread dev-tools/reconfigurator-cli/tests/input/cmds-target-release.txt
Comment thread dev-tools/reconfigurator-cli/tests/input/cmds-unsafe-zone-mgs.txt
Ok(Some(rv))
oxide_tokio_rt::run(async {
let output_path = args.output.unwrap_or_else(|| {
// This is relative to the current directory.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice to have this comment as part of the flag's documentation above

@karencfv karencfv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, time to log off now, I've added a few more comments. Will continue with the review tomorrow!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know if the new repo-depot-standalone still works with other dev tools that use it like reconfigurator-sp-updater?

/// as part of the process, which `From` doesn't necessarily communicate
/// and can be surprising.
/// [`nexus_types::tuf_repo::TufRepoDescription`].
pub fn from_external(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this still be called from_external if we're no longer converting from external::TufRepoDescription?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know what to name it. When I had that in omicron_common in an earlier draft it was renamed to from_common but there's not really an obvious choice here...


impl TufArtifactDescription {
pub fn from_artifact(
artifact: Artifact,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be a little confusing to differentiate between TufArtifact and Artifact. Can you add some documentation above the method?

#[diesel(table_name = tuf_artifact_tag)]
pub struct TufArtifactTag {
pub tuf_artifact_id: DbTypedUuid<TufArtifactKind>,
pub key: String,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tag keys are not freeform right? We already know what they are. It'd be nice to have the keys be an enum for correctness.

@iliana iliana Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are by definition freeform, an old Nexus cannot necessarily know about future tags. (See RFD 621 section 2.1.1)

Comment on lines +54 to +56
// We don't bother paginating in this method because each repo has a
// limited number of artifacts (currently <100 as of February 2026) and tags
// (currently <250).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like if the number of tags blew up for some reason, we could forget to modify this method to paginate. What's the downside of paginating now?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The downside is making the APIs much more difficult to use even though all of the callers need a complete listing of artifacts + tags in a repository regardless. The key point here is not that the number of entries is "currently" small, more that it is a consistent size that users are not likely to intentionally increase to cause a problem.

Comment on lines +534 to +535
// We don't bother paginating in this method because each repo has a
// limited number of artifacts (currently <100 as of February 2026).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. Is there a downside to pagination?

Comment on lines +778 to +779
// We don't enforce that two artifacts with the same SHA-256 checksum
// have the same tags; they are entered as separate artifacts. The

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my own understanding: Why not?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have this situation today but we want to allow for it in the future. Consider a future where we need two different kinds of host phase 2 images depending on what board we're booting. The cosmo and gimlet images could still be the same, so they should still only be in the repository once, but we can have multiple sets of tags that refer to the same content so that the application logic of "which image do I pick" is not inconsistent. Additionally we would need to support the old tag definitions, so we would have the same cosmo/gimlet image be described as {"kind":"os_phase_2","os_variant":"host"} as well as a new set of tags (e.g. {"kind":"os_phase_2_v2","os_variant":"host","os_board":"gimlet"}).

Also, it is not really possible to write a database constraint that limits this.

Comment on lines +787 to +792
let mut filter_dsl = tuf_artifact_dsl::tuf_artifact.into_boxed();
for artifact in &desc.artifacts {
filter_dsl = filter_dsl.or_filter(
tuf_artifact_dsl::sha256.eq(artifact.artifact.sha256),
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take this with a grain of salt. I seem to remember ANY being more efficient than OR. Additionaly, the code is simpler to read.

let shas: Vec<_> = desc.artifacts.iter().map(|a| a.artifact.sha256).collect();
let filter_dsl = tuf_artifact_dsl::tuf_artifact
    .filter(tuf_artifact_dsl::sha256.eq_any(shas));

let mut filter_dsl =
tuf_artifact_tag_dsl::tuf_artifact_tag.into_boxed();
for artifact in &results {
filter_dsl = filter_dsl.or_filter(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smae comment about using eq_any() over or_filter()

@@ -567,7 +659,7 @@ impl DataStore {
async fn insert_impl(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is pretty gnarly, is there some way to add a test for this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

insert_impl is only split out here because the 80-character line limit is stifling. This is covered by tests that use tuf_repo_insert.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tufaceous v2: integrate into Nexus

2 participants