mirror of
https://github.com/anthonyoteri/dredge.git
synced 2026-06-05 15:26:53 -04:00
Add API Version check endpoint
This commit is contained in:
+9
-1
@@ -7,13 +7,16 @@ use clap::ValueEnum;
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Dredge is a command line tool for working with the Docker Registry
|
||||||
|
/// V2 API.
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(name = "dredge", version, author)]
|
#[command(name = "dredge", version, author)]
|
||||||
#[command(about = "A Docker Registry CLI tool", long_about = None)]
|
#[command(about, long_about)]
|
||||||
pub(crate) struct Cli {
|
pub(crate) struct Cli {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Commands,
|
pub command: Commands,
|
||||||
|
|
||||||
|
/// Optional configuration file override.
|
||||||
#[arg(short = 'c', long = "config")]
|
#[arg(short = 'c', long = "config")]
|
||||||
pub config: Option<OsString>,
|
pub config: Option<OsString>,
|
||||||
|
|
||||||
@@ -54,5 +57,10 @@ impl From<LogLevel> for log::LevelFilter {
|
|||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
|
/// Fetch the list of available repositories from the catalog.
|
||||||
Catalog,
|
Catalog,
|
||||||
|
|
||||||
|
/// Perform a simple API Version check towards the configured registry
|
||||||
|
/// endpoint.
|
||||||
|
Check,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
pub mod catalog;
|
pub mod catalog;
|
||||||
|
pub mod version;
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
//! Command module responsible for handling the API Version check.
|
||||||
|
//!
|
||||||
|
//! This is a minimal endpoint suitable for ensuring that the configured
|
||||||
|
//! Docker Regsitry API supports the correct API version.
|
||||||
|
//!
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::error::ApiError;
|
||||||
|
|
||||||
|
/// Path to the Docker Registry API's "api version check" endpoint.
|
||||||
|
const BASE_URL: &str = "/v2";
|
||||||
|
|
||||||
|
/// Handler for the API Version Check.
|
||||||
|
///
|
||||||
|
/// # Errors:
|
||||||
|
///
|
||||||
|
/// Returns an `ApiError` if there is a problem communicating with the
|
||||||
|
/// endpoint or if the required version is not supported.
|
||||||
|
pub async fn handler(config: &Config) -> Result<(), ApiError> {
|
||||||
|
log::trace!("handler()");
|
||||||
|
|
||||||
|
let url = config.registry_url.join(BASE_URL)?;
|
||||||
|
let response = reqwest::get(url).await?;
|
||||||
|
|
||||||
|
parse_response_status(&response)?;
|
||||||
|
println!("Ok");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the response according to the API Documentation.
|
||||||
|
///
|
||||||
|
/// If a 200 OK response is returned, the registry implements the V2(.1)
|
||||||
|
/// registry API and the client may proceed safely with other V2 operations.
|
||||||
|
/// Optionally, the response may contain information about the supported
|
||||||
|
/// paths in the response body. The client should be prepared to ignore this data.
|
||||||
|
///
|
||||||
|
/// If a 401 Unauthorized response is returned, the client should take action
|
||||||
|
/// based on the contents of the "WWW-Authenticate" header and try the endpoint
|
||||||
|
/// again. Depending on access control setup, the client may still have to
|
||||||
|
/// authenticate against different resources, even if this check succeeds.
|
||||||
|
///
|
||||||
|
/// If 404 Not Found response status, or other unexpected status, is returned,
|
||||||
|
/// the client should proceed with the assumption that the registry does not
|
||||||
|
/// implement V2 of the API.
|
||||||
|
///
|
||||||
|
/// When a 200 OK or 401 Unauthorized response is returned, the
|
||||||
|
/// "Docker-Distribution-API-Version" header should be set to "registry/2.0".
|
||||||
|
/// Clients may require this header value to determine if the endpoint serves
|
||||||
|
/// this API. When this header is omitted, clients may fallback to an older
|
||||||
|
/// API version.
|
||||||
|
///
|
||||||
|
/// # Errors:
|
||||||
|
///
|
||||||
|
/// Returns an `ApiError` on the following conditions:
|
||||||
|
///
|
||||||
|
/// * There is an error parsing the "Docker-Distribution-API-Version" header.
|
||||||
|
/// * The value of the above header is not the expected result.
|
||||||
|
/// * The above header is missing from the response.
|
||||||
|
/// * A non 200 HTTP response status code is returned.
|
||||||
|
fn parse_response_status(response: &reqwest::Response) -> Result<(), ApiError> {
|
||||||
|
match response.status() {
|
||||||
|
http::StatusCode::OK => {
|
||||||
|
let headers = response.headers();
|
||||||
|
if let Some(header_value) = headers.get("Docker-Distribution-API-Version") {
|
||||||
|
if header_value.to_str()? != "registry/2.0" {
|
||||||
|
Err(ApiError::UnsupportedVersion(header_value.to_str()?.into()))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ApiError::UnexpectedResponse(
|
||||||
|
"Missing version header".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http::StatusCode::UNAUTHORIZED => {
|
||||||
|
let headers = response.headers();
|
||||||
|
if let Some(header_value) = headers.get("Docker-Distribution-API-Version") {
|
||||||
|
if header_value.to_str()? != "registry/2.0" {
|
||||||
|
Err(ApiError::UnsupportedVersion(header_value.to_str()?.into()))
|
||||||
|
} else {
|
||||||
|
Err(ApiError::AuthorizationFailed)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ApiError::UnexpectedResponse(
|
||||||
|
"Missing version header".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http::StatusCode::NOT_FOUND => Err(ApiError::NotFound),
|
||||||
|
_ => Err(ApiError::UnexpectedResponse(
|
||||||
|
"Undocumented status code".into(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,6 +60,18 @@ pub enum ApiError {
|
|||||||
|
|
||||||
#[error("Failed to parse response headers")]
|
#[error("Failed to parse response headers")]
|
||||||
ResponseHeaderParseError(Box<dyn std::error::Error>),
|
ResponseHeaderParseError(Box<dyn std::error::Error>),
|
||||||
|
|
||||||
|
#[error("Version Mismatch {0}")]
|
||||||
|
UnsupportedVersion(String),
|
||||||
|
|
||||||
|
#[error("Unexpected response from API: {0}")]
|
||||||
|
UnexpectedResponse(String),
|
||||||
|
|
||||||
|
#[error("HTTP Authorization failed")]
|
||||||
|
AuthorizationFailed,
|
||||||
|
|
||||||
|
#[error("Resource not found")]
|
||||||
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<reqwest::header::ToStrError> for ApiError {
|
impl From<reqwest::header::ToStrError> for ApiError {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ async fn main() -> Result<(), DredgeError> {
|
|||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
Commands::Catalog => commands::catalog::handler(&config).await?,
|
Commands::Catalog => commands::catalog::handler(&config).await?,
|
||||||
|
Commands::Check => commands::version::handler(&config).await?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user