mirror of
https://github.com/anthonyoteri/dredge.git
synced 2026-06-05 15:26:53 -04:00
Support for specifying the URL on the command line
This commit is contained in:
+3
-3
@@ -15,8 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
|
|
||||||
/// Iterate over a paginated result set, collecting and returning the response
|
/// Iterate over a paginated result set, collecting and returning the response
|
||||||
@@ -37,7 +37,7 @@ use crate::error::ApiError;
|
|||||||
/// error deserializing the HTTP response body as JSON, or if there is an
|
/// error deserializing the HTTP response body as JSON, or if there is an
|
||||||
/// error parsing the `Link` header value as an RFC5988 URL.
|
/// error parsing the `Link` header value as an RFC5988 URL.
|
||||||
pub async fn fetch_all<T: for<'de> Deserialize<'de>>(
|
pub async fn fetch_all<T: for<'de> Deserialize<'de>>(
|
||||||
config: &Config,
|
base: &Url,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<Vec<T>, ApiError> {
|
) -> Result<Vec<T>, ApiError> {
|
||||||
log::trace!("fetch_all({path:?})");
|
log::trace!("fetch_all({path:?})");
|
||||||
@@ -46,7 +46,7 @@ pub async fn fetch_all<T: for<'de> Deserialize<'de>>(
|
|||||||
let mut path = String::from(path);
|
let mut path = String::from(path);
|
||||||
loop {
|
loop {
|
||||||
log::debug!("GET {path:?}");
|
log::debug!("GET {path:?}");
|
||||||
let url = config.registry_url.join(&path)?;
|
let url = base.join(&path)?;
|
||||||
|
|
||||||
let resp = reqwest::get(url).await?;
|
let resp = reqwest::get(url).await?;
|
||||||
let headers = resp.headers().clone();
|
let headers = resp.headers().clone();
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ pub(crate) struct Cli {
|
|||||||
value_enum
|
value_enum
|
||||||
)]
|
)]
|
||||||
pub log_level: LogLevel,
|
pub log_level: LogLevel,
|
||||||
|
|
||||||
|
/// The host or host:port for the Docker Registry
|
||||||
|
pub registry: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
|||||||
+10
-12
@@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::api;
|
use crate::api;
|
||||||
use crate::config::Config;
|
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
|
|
||||||
/// Handler for the `Catalog` endpoint
|
/// Handler for the `Catalog` endpoint
|
||||||
@@ -29,7 +29,7 @@ use crate::error::ApiError;
|
|||||||
///
|
///
|
||||||
/// Returns an `ApiError` if there is a problem fetching or parsing the
|
/// Returns an `ApiError` if there is a problem fetching or parsing the
|
||||||
/// responses from the Docker Registry API.
|
/// responses from the Docker Registry API.
|
||||||
pub async fn catalog_handler(config: &Config) -> Result<(), ApiError> {
|
pub async fn catalog_handler(registry_url: &Url) -> Result<(), ApiError> {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Response {
|
struct Response {
|
||||||
repositories: Vec<String>,
|
repositories: Vec<String>,
|
||||||
@@ -38,7 +38,7 @@ pub async fn catalog_handler(config: &Config) -> Result<(), ApiError> {
|
|||||||
log::trace!("catalog_handler()");
|
log::trace!("catalog_handler()");
|
||||||
let path = "v2/_catalog";
|
let path = "v2/_catalog";
|
||||||
|
|
||||||
let responses: Vec<Response> = api::fetch_all(config, path).await?;
|
let responses: Vec<Response> = api::fetch_all(registry_url, path).await?;
|
||||||
let repository_list: Vec<&str> = responses
|
let repository_list: Vec<&str> = responses
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|r| r.repositories.iter().map(String::as_str))
|
.flat_map(|r| r.repositories.iter().map(String::as_str))
|
||||||
@@ -60,7 +60,7 @@ pub async fn catalog_handler(config: &Config) -> Result<(), ApiError> {
|
|||||||
///
|
///
|
||||||
/// Returns an `ApiError` if there is a problem fetching or parsing the
|
/// Returns an `ApiError` if there is a problem fetching or parsing the
|
||||||
/// responses from the Docker Registry API.
|
/// responses from the Docker Registry API.
|
||||||
pub async fn tags_handler(config: &Config, name: &str) -> Result<(), ApiError> {
|
pub async fn tags_handler(registry_url: &Url, name: &str) -> Result<(), ApiError> {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Response {
|
struct Response {
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
@@ -69,7 +69,7 @@ pub async fn tags_handler(config: &Config, name: &str) -> Result<(), ApiError> {
|
|||||||
log::trace!("tags_handler(name: {name})");
|
log::trace!("tags_handler(name: {name})");
|
||||||
let path = format!("/v2/{name}/tags/list");
|
let path = format!("/v2/{name}/tags/list");
|
||||||
|
|
||||||
let responses: Vec<Response> = api::fetch_all(config, &path).await?;
|
let responses: Vec<Response> = api::fetch_all(registry_url, &path).await?;
|
||||||
let tag_list: Vec<&str> = responses
|
let tag_list: Vec<&str> = responses
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|r| r.tags.iter().map(String::as_str))
|
.flat_map(|r| r.tags.iter().map(String::as_str))
|
||||||
@@ -89,11 +89,10 @@ pub async fn tags_handler(config: &Config, name: &str) -> Result<(), ApiError> {
|
|||||||
/// Returns an `ApiError` if there is a problem fetching the manifest or if there
|
/// Returns an `ApiError` if there is a problem fetching the manifest or if there
|
||||||
/// is a problem parsing the response from the Docker Registry API.
|
/// is a problem parsing the response from the Docker Registry API.
|
||||||
#[allow(clippy::unused_async)]
|
#[allow(clippy::unused_async)]
|
||||||
pub async fn show_handler(config: &Config, image: &str, tag: &str) -> Result<(), ApiError> {
|
pub async fn show_handler(registry_url: &Url, image: &str, tag: &str) -> Result<(), ApiError> {
|
||||||
log::trace!("show_handler(image: {image}, tag: {tag})");
|
log::trace!("show_handler(image: {image}, tag: {tag})");
|
||||||
let base = config.registry_url.clone();
|
|
||||||
let path = format!("/v2/{image}/manifests/{tag}");
|
let path = format!("/v2/{image}/manifests/{tag}");
|
||||||
let _url = base.join(&path)?;
|
let _url = registry_url.join(&path)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +104,7 @@ pub async fn show_handler(config: &Config, image: &str, tag: &str) -> Result<(),
|
|||||||
/// manifest digest, or if there is a problem deleting the manifest from the
|
/// manifest digest, or if there is a problem deleting the manifest from the
|
||||||
/// Docker Registry API.
|
/// Docker Registry API.
|
||||||
#[allow(clippy::unused_async)]
|
#[allow(clippy::unused_async)]
|
||||||
pub async fn delete_handler(_config: &Config, image: &str, tag: &str) -> Result<(), ApiError> {
|
pub async fn delete_handler(_registry_url: &Url, image: &str, tag: &str) -> Result<(), ApiError> {
|
||||||
log::trace!("delete_handler(image: {image}, tag: {tag})");
|
log::trace!("delete_handler(image: {image}, tag: {tag})");
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
@@ -118,12 +117,11 @@ pub async fn delete_handler(_config: &Config, image: &str, tag: &str) -> Result<
|
|||||||
///
|
///
|
||||||
/// Returns an `ApiError` if there is a problem communicating with the
|
/// Returns an `ApiError` if there is a problem communicating with the
|
||||||
/// endpoint or if the required version is not supported.
|
/// endpoint or if the required version is not supported.
|
||||||
pub async fn check_handler(config: &Config) -> Result<(), ApiError> {
|
pub async fn check_handler(registry_url: &Url) -> Result<(), ApiError> {
|
||||||
log::trace!("check_handler()");
|
log::trace!("check_handler()");
|
||||||
|
|
||||||
let base = config.registry_url.clone();
|
|
||||||
let path = "/v2";
|
let path = "/v2";
|
||||||
let url = base.join(path)?;
|
let url = registry_url.join(path)?;
|
||||||
|
|
||||||
let response = reqwest::get(url).await?;
|
let response = reqwest::get(url).await?;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ pub enum DredgeError {
|
|||||||
/// An error communicating with the Registry API
|
/// An error communicating with the Registry API
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ApiError(#[from] ApiError),
|
ApiError(#[from] ApiError),
|
||||||
|
|
||||||
|
/// An error building the registry URL
|
||||||
|
#[error("Error determining registry URL from {0}")]
|
||||||
|
RegistryUrlError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error related to the configuration fo the program.
|
/// An error related to the configuration fo the program.
|
||||||
|
|||||||
+28
-59
@@ -16,15 +16,12 @@
|
|||||||
|
|
||||||
#![deny(clippy::pedantic)]
|
#![deny(clippy::pedantic)]
|
||||||
|
|
||||||
use std::ffi::OsString;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::cli::Cli;
|
use crate::cli::Cli;
|
||||||
use crate::cli::Commands;
|
use crate::cli::Commands;
|
||||||
use crate::config::Config;
|
|
||||||
use crate::error::ConfigError;
|
|
||||||
use crate::error::DredgeError;
|
use crate::error::DredgeError;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
@@ -33,55 +30,27 @@ mod commands;
|
|||||||
mod config;
|
mod config;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
||||||
/// The default basename of the main configuration file.
|
/// Generate the full Docker Registry URL from a given `host[:port]`
|
||||||
const CONFIG_FILE_NAME: &str = "dredge.toml";
|
|
||||||
|
|
||||||
/// The XDG directory prefix.
|
|
||||||
const CONFIG_PREFIX: &str = "dredge";
|
|
||||||
|
|
||||||
/// Locate the absolute path to the saved configuration file on disk.
|
|
||||||
///
|
///
|
||||||
/// If given an optional `path` to a configuration file, and that file
|
/// This prepends the HTTPS scheme and converts the given string to a `Url`
|
||||||
/// exists on disk, the absolute path to that file will be returned.
|
/// instance.
|
||||||
/// Otherwise, the XDG configuration path will be used. If neither the
|
///
|
||||||
/// optional `path` parameter refers to an existing file on disk, nor a
|
/// If the given `host` value is already a valid URL, then it will be returned
|
||||||
/// suitable configuration file can be located within the XDG configuration
|
/// as-is.
|
||||||
/// path, the `None` variant will be returned.
|
|
||||||
fn locate_config_file(path: Option<OsString>) -> Option<PathBuf> {
|
|
||||||
log::trace!("locate_config_file({path:?})");
|
|
||||||
|
|
||||||
if let Some(path) = path {
|
|
||||||
let p = PathBuf::from(path);
|
|
||||||
log::debug!("Checking if path {p:?} exists");
|
|
||||||
p.try_exists().map(|_| Some(p)).unwrap_or(None)
|
|
||||||
} else {
|
|
||||||
let xdg_dirs = xdg::BaseDirectories::with_prefix(CONFIG_PREFIX).ok()?;
|
|
||||||
let search_paths: Vec<PathBuf> = vec![xdg_dirs.get_config_home()]
|
|
||||||
.into_iter()
|
|
||||||
.chain(xdg_dirs.get_config_dirs())
|
|
||||||
.collect();
|
|
||||||
log::debug!("Searching configuration directories for {CONFIG_FILE_NAME} {search_paths:?}");
|
|
||||||
xdg_dirs.find_config_file(CONFIG_FILE_NAME)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to create a default configuration file in the XDG configuration
|
|
||||||
/// path. Any sub-directories of the XDG configuration path which do not
|
|
||||||
/// already exist will be created automatically.
|
|
||||||
///
|
///
|
||||||
/// # Errors:
|
/// # Errors:
|
||||||
///
|
///
|
||||||
/// This returns a `ConfigError` if a problem occurred which prevented either
|
/// If there is a problem parsing the resulting string as a valid URL, a
|
||||||
/// the creation of the directory tree, or in writing the default configuration
|
/// `DredgeError::RegistryUrlError` will be returned.
|
||||||
/// to the file.
|
fn make_registry_url(host: &str) -> Result<Url, DredgeError> {
|
||||||
fn create_default_config_file() -> Result<PathBuf, ConfigError> {
|
log::trace!("make_registry_url(host: {host})");
|
||||||
log::trace!("create_default_config_file()");
|
|
||||||
|
|
||||||
let xdg_dirs = xdg::BaseDirectories::with_prefix(CONFIG_PREFIX)?;
|
Url::parse(host)
|
||||||
let config_path = xdg_dirs.place_config_file(CONFIG_FILE_NAME)?;
|
.or_else(|_| {
|
||||||
let default_config = toml::to_string_pretty(&Config::default())?;
|
let url_string = format!("https://{host}");
|
||||||
std::fs::write(&config_path, default_config)?;
|
Url::parse(&url_string)
|
||||||
Ok(config_path)
|
})
|
||||||
|
.or(Err(DredgeError::RegistryUrlError(host.to_string())))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_std::main]
|
#[async_std::main]
|
||||||
@@ -92,20 +61,20 @@ async fn main() -> Result<(), DredgeError> {
|
|||||||
let log_level = args.log_level;
|
let log_level = args.log_level;
|
||||||
femme::with_level(log::LevelFilter::from(log_level));
|
femme::with_level(log::LevelFilter::from(log_level));
|
||||||
|
|
||||||
// -- Load and parse configuration file
|
// -- Generate the complete registry URL from the given host[:path]
|
||||||
let config_file =
|
let registry_url: Url = make_registry_url(&args.registry)?;
|
||||||
locate_config_file(args.config).map_or_else(create_default_config_file, Ok)?;
|
|
||||||
log::debug!("Using configuration file {config_file:?}");
|
|
||||||
|
|
||||||
let config = Config::try_from(config_file.as_ref())?;
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Commands::Catalog => commands::catalog_handler(&config).await?,
|
Commands::Catalog => commands::catalog_handler(®istry_url).await?,
|
||||||
Commands::Tags { name } => commands::tags_handler(&config, &name).await?,
|
Commands::Tags { name } => commands::tags_handler(®istry_url, &name).await?,
|
||||||
Commands::Show { image, tag } => {
|
Commands::Show { image, tag } => {
|
||||||
commands::show_handler(&config, &image, &tag.unwrap_or("latest".to_string())).await?;
|
commands::show_handler(®istry_url, &image, &tag.unwrap_or("latest".to_string()))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
Commands::Delete { image, tag } => commands::delete_handler(&config, &image, &tag).await?,
|
Commands::Delete { image, tag } => {
|
||||||
Commands::Check => commands::check_handler(&config).await?,
|
commands::delete_handler(®istry_url, &image, &tag).await?;
|
||||||
|
}
|
||||||
|
Commands::Check => commands::check_handler(®istry_url).await?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user