Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8d68d35
Adds new Indexed repository type, related RepositoryIndex traits, and…
dcookspi Mar 19, 2026
4df7970
Adds 'spk repo index' subcommand for index generation and updates.
dcookspi Mar 19, 2026
34c2a47
Adds the indexing settings to the config file docs
dcookspi Mar 27, 2026
60bb597
Adds deprecated builds count to index generation output
dcookspi Mar 27, 2026
399f0d6
Disables index use for spk info command
dcookspi Mar 30, 2026
9044083
Updates config docs
dcookspi Apr 2, 2026
289b83c
Adds index docs to reference seciton
dcookspi Apr 2, 2026
bb66e4f
Moves index location path creation and access into spfs repository ha…
dcookspi Apr 7, 2026
3e592be
Updates docs on indexes
dcookspi Apr 8, 2026
4f30958
Updates to spk config file, command line options, and defaults for in…
dcookspi Apr 8, 2026
45e4956
Updates spk repo index command to just allow -r reponame command line…
dcookspi Apr 9, 2026
051989b
Updates index docs
dcookspi Apr 9, 2026
073a4c0
Updates spk repo index --update option so it can be specified multiple
dcookspi Apr 10, 2026
51bb6ac
Fixed bug caused by reading all version lists from the repository,
dcookspi Apr 13, 2026
d8b735a
Changes index updates to use OptVersionIdent
dcookspi Apr 17, 2026
3b026bb
Cleans the index update variables and comments
dcookspi Apr 17, 2026
7556016
Updates index/repository configuration file docs
dcookspi Apr 20, 2026
ebf1744
Updates docs, comments, change DISABLED_USE_INDEX to a mutex, and emp…
dcookspi May 8, 2026
f4f648e
Swaps match and empty set out for is_some_and in package version to u…
dcookspi May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/spfs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ pub enum Error {
#[source]
source: storage::OpenRepositoryError,
},
#[error("Repository does not support index storage location: {0:?}")]
NoIndexStorageLocation(url::Url),

#[error("No remote named '{0}' configured")]
#[diagnostic(
Expand Down
46 changes: 46 additions & 0 deletions crates/spfs/src/storage/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// https://github.com/spkenv/spk

use std::borrow::Cow;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;

Expand All @@ -15,9 +16,13 @@ use super::prelude::*;
use super::tag::TagSpecAndTagStream;
use super::{TagNamespace, TagNamespaceBuf, TagStorageMut};
use crate::graph::ObjectProto;
use crate::storage::IndexPath;
use crate::tracking::{self, BlobRead};
use crate::{Error, Result, graph};

// Index sub-directory inside a repository
const INDEX_SUB_DIR: &str = "index";

#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum RepositoryHandle {
Expand Down Expand Up @@ -150,6 +155,47 @@ impl Address for RepositoryHandle {
}
}

#[async_trait::async_trait]
impl IndexPath for RepositoryHandle {
async fn index_path(&self) -> Result<PathBuf> {
// Only FS repositories have a location for indexes at this time.
match self {
RepositoryHandle::FS(repo) => {
// Makes the spfs fs repository specific index
// sub-directory, if it does not exist, and returns
// the path to it.
let mut index_path = PathBuf::new();
index_path.push(repo.root());
index_path.push(INDEX_SUB_DIR);

crate::runtime::makedirs_with_perms(&index_path, 0o777).map_err(|source| {
Error::String(format!(
"Unable to make '{INDEX_SUB_DIR}' sub-directory in spfs filesystem repo: {source}"
))
})?;

Ok(index_path)
}

RepositoryHandle::Tar(repo) => {
Err(Error::NoIndexStorageLocation(repo.address().into_owned()))
}
RepositoryHandle::Rpc(repo) => {
Err(Error::NoIndexStorageLocation(repo.address().into_owned()))
}
RepositoryHandle::FallbackProxy(repo) => {
Err(Error::NoIndexStorageLocation(repo.address().into_owned()))
}
RepositoryHandle::Proxy(repo) => {
Err(Error::NoIndexStorageLocation(repo.address().into_owned()))
}
RepositoryHandle::Pinned(repo) => {
Err(Error::NoIndexStorageLocation(repo.address().into_owned()))
}
}
}
}

#[async_trait::async_trait]
impl TagStorage for RepositoryHandle {
#[inline]
Expand Down
15 changes: 15 additions & 0 deletions crates/spfs/src/storage/index_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Contributors to the SPK project.
// SPDX-License-Identifier: Apache-2.0
// https://github.com/spkenv/spk

use std::path::PathBuf;

use crate::Result;

/// The index location path of a repository.
#[async_trait::async_trait]
pub trait IndexPath {
/// Get the index location path of this repository, will create it
/// if it does not exist.
async fn index_path(&self) -> Result<PathBuf>;
}
2 changes: 2 additions & 0 deletions crates/spfs/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
mod address;
mod blob;
mod error;
mod index_path;
mod layer;
mod manifest;
pub mod payload;
Expand All @@ -27,6 +28,7 @@ pub use address::Address;
pub use blob::{BlobStorage, BlobStorageExt};
pub use error::OpenRepositoryError;
pub use handle::RepositoryHandle;
pub use index_path::IndexPath;
pub use layer::{LayerStorage, LayerStorageExt};
pub use manifest::ManifestStorage;
pub use payload::PayloadStorage;
Expand Down
2 changes: 2 additions & 0 deletions crates/spk-cli/cmd-repo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ workspace = true
miette = { workspace = true, features = ["fancy"] }
async-trait = { workspace = true }
clap = { workspace = true }
itertools = { workspace = true }
spk-cli-common = { workspace = true }
spk-schema = { workspace = true }
spk-storage = { workspace = true }
tracing = { workspace = true }
138 changes: 126 additions & 12 deletions crates/spk-cli/cmd-repo/src/cmd_repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
// https://github.com/spkenv/spk

use std::str::FromStr;
use std::time::Instant;

use clap::{Args, Subcommand};
use itertools::Itertools;
use miette::{Context, Result};
use spk_cli_common::{CommandArgs, Run};
use spk_storage as storage;
use spk_cli_common::{CommandArgs, Run, flags};
use spk_schema::ident::OptVersionIdent;
use spk_storage::{self as storage, FlatBufferRepoIndex, RepositoryHandle, RepositoryIndexMut};
use storage::Repository;

/// Perform repository-level actions and maintenance
Expand Down Expand Up @@ -45,19 +50,128 @@ pub enum RepoCommand {
#[clap(name = "REPO")]
repo: String,
},
/// Generate an index for a repository
Index {
/// Repository to generate or update an index from.
#[clap(long, short = 'r')]
repo: String,

/// Package or package/version of a published package to
/// update in an existing index.
///
/// Can be specified multiple times. Other packages in the
/// index will not be updated. Without this the full index
/// will be constructed from scratch. If the repo does not
/// have an index, a full index will be constructed from
/// scratch if the repository supports an index.
///
/// This option is only supported for flatbuffer indexes.
#[clap(long, name = "PACKAGE/VERSION")]
update: Vec<String>,
},
}

impl RepoCommand {
pub async fn run(&mut self) -> Result<i32> {
let repo = match &self {
Self::Upgrade { repo } => repo,
};
let repo = match repo.as_str() {
"local" => storage::local_repository().await?,
_ => storage::remote_repository(repo).await?,
};
let status = repo.upgrade().await.wrap_err("Upgrade failed")?;
tracing::info!("{}", status);
Ok(1)
match &self {
// spk repo upgrade ...
Self::Upgrade { repo: repo_name } => {
let repo = match repo_name.as_str() {
"local" => storage::local_repository().await?,
_ => storage::remote_repository(repo_name).await?,
};

let status = repo.upgrade().await.wrap_err("Upgrade failed")?;
tracing::info!("{}", status);
Ok(1)
}

// spk repo index ...
Self::Index { repo, update } => {
// Generate or update an index a repo. The repo must
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Generate or update an index a repo. The repo must
// Generate or update an index in a repo. The repo must

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've updated this.

// be the underlying repo and not an indexed repo. So as
// a safety measure, this disables index use for this
// command regardless of config or command line flags.
flags::disable_index_use();

// Construct the repo handle to operate on, and repo
// list that contains it.
let repo_to_index: RepositoryHandle = match repo.as_str() {
"local" => storage::local_repository().await?.into(),
name => storage::remote_repository(name).await?.into(),
};
let repos = vec![(repo_to_index.name().to_string(), repo_to_index.clone())];

if !update.is_empty() {
// Update the existing index for the given package/version
let start = Instant::now();
let idents: Vec<OptVersionIdent> = update
.iter()
.filter_map(|pv| match OptVersionIdent::from_str(pv) {
Ok(i) => Some(i),
Err(err) => {
tracing::warn!(
"Skipping '{pv}': Unable to parse it as a package/version: {err}"
);
None
}
})
.collect();

tracing::debug!(
"Command line update option: [{}]",
update.iter().map(ToString::to_string).join(", ")
);
tracing::info!(
"Package/versions to update: [{}]",
idents.iter().map(ToString::to_string).join(", ")
);
if idents.is_empty() {
tracing::error!(
"No valid package/versions given, nothing to update. Stopping."
);
return Ok(2);
}

// Load the current index for this repo now
let mut was_full_index = String::from("");
match FlatBufferRepoIndex::from_repo_file(&repo_to_index).await {
Ok(current_index) => {
current_index
.update_packages(&repo_to_index, &idents)
.await?
}
Err(err) => {
// There isn't an existing index, so generate one from scratch that
// will also include the update package version.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you match on a more specific error before assuming the problem is that the index doesn't exist?

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've changed this to match the appropriate spk storage error.

tracing::warn!("Failed to load flatbuffer index: {err}");
tracing::warn!("No current index to update. Creating a full index ...");
FlatBufferRepoIndex::index_repo(&repos).await?;
was_full_index =
" [no previous index, so a full index was created]".to_string()
}
};

tracing::info!(
"Index update for '{}' in '{}' repo completed in: {} secs{was_full_index}",
idents.iter().map(ToString::to_string).join(", "),
repo_to_index.name(),
start.elapsed().as_secs_f64()
);
} else {
// Generate a full index from scratch
let start = Instant::now();
FlatBufferRepoIndex::index_repo(&repos).await?;

tracing::info!(
"Index generation for '{}' repo completed in: {} secs",
repo_to_index.name(),
start.elapsed().as_secs_f64()
);
}

Ok(0)
}
}
}
}
Loading
Loading