mirror of
https://github.com/anthonyoteri/dredge.git
synced 2026-06-05 15:26:53 -04:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7305f8cc4 | |||
| 02dd2ec90a | |||
| b60d433508 | |||
| 0712af9d23 | |||
| 42f8f46bd3 | |||
| 80d1acf295 | |||
| 12dd298706 | |||
| de42860be8 | |||
| b4d6002a20 | |||
| fbe43f03f1 | |||
| 48070cff1f | |||
| 13ae092b91 |
+10
-4
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dredge"
|
||||
version = "0.1.0"
|
||||
name = "dredge-tool"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
authors = ["Anthony Oteri"]
|
||||
description = "A Command Line tool for interracting with the Docker Registry API"
|
||||
@@ -18,12 +18,16 @@ categories = [
|
||||
"api-bindings",
|
||||
]
|
||||
|
||||
|
||||
[[bin]]
|
||||
path = "src/main.rs"
|
||||
name = "dredge"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "1.12.0", features = ["async-attributes", "attributes", "tokio1"] }
|
||||
clap = { version = "4.4.3", features = ["derive", "env", "wrap_help"] }
|
||||
femme = "2.2.1"
|
||||
simple_logger = { version = "4.2.0", features = ["timestamps", "colors", "stderr"] }
|
||||
http = "0.2.9"
|
||||
indoc = "2.0.4"
|
||||
log = "0.4.20"
|
||||
@@ -35,6 +39,8 @@ thiserror = "1.0.48"
|
||||
toml = "0.8.0"
|
||||
url = { version = "2.4.1", features = ["serde"] }
|
||||
xdg = "2.5.2"
|
||||
tokio = { version = "1.32.0", features = ["macros"] }
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "1.2.0"
|
||||
env_logger = "0.10.0"
|
||||
|
||||
@@ -32,6 +32,7 @@ Options:
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
||||
|
||||
### Checking the API Version
|
||||
|
||||
Perform a simple API Version check towards the registry endpoint
|
||||
@@ -87,6 +88,15 @@ Options:
|
||||
|
||||
Delete a tagged image from the registry
|
||||
|
||||
Note! This requires that the registry has storage delete rights enabled. For
|
||||
example, when creating the registry, setting the environment variable
|
||||
`REGISTRY_STORAGE_DELETE_ENABLED=true` to enable that feature. If that is not
|
||||
enabled, a `MethodNotAllowed` error will be returned.
|
||||
|
||||
Note! This will only remove the tag from the registry, it will not remove
|
||||
orphaned digests. For that, the garbage collector on the registry service must
|
||||
be run separately.
|
||||
|
||||
```shell
|
||||
Usage: dredge <REGISTRY> delete <IMAGE> <TAG>
|
||||
|
||||
|
||||
+12
-2
@@ -2,12 +2,22 @@
|
||||
|
||||
## Known Issues
|
||||
|
||||
* The delete command is currently not implemented and will return an error
|
||||
if called.
|
||||
* Docker authentication is not currently supported, and attempts to query a
|
||||
registry which requires authentication will fail.
|
||||
|
||||
## Changelog
|
||||
- v1.0.0
|
||||
|
||||
- Rename project to dredge-tool by Anthony Oteri b60d433
|
||||
- Replace async_std::test with tokio::test by Anthony Oteri 42f8f46
|
||||
- Replace async-std with tokio by Anthony Oteri 80d1acf
|
||||
- Update known issues in release notes by Anthony Oteri 12dd298
|
||||
|
||||
- v0.2.0
|
||||
|
||||
- Support deleting an image tag by Anthony Oteri fbe43f0
|
||||
- Replace femme logger with simple_logger by Anthony Oteri 13ae092
|
||||
|
||||
- v0.1.0
|
||||
|
||||
- Additional scripts for managing the release process by Anthony Oteri cfdefb2
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
- v0.2.0
|
||||
|
||||
- Support deleting an image tag by Anthony Oteri fbe43f0
|
||||
- Replace femme logger with simple_logger by Anthony Oteri 13ae092
|
||||
@@ -0,0 +1,6 @@
|
||||
- v1.0.0
|
||||
|
||||
- Rename project to dredge-tool by Anthony Oteri b60d433
|
||||
- Replace async_std::test with tokio::test by Anthony Oteri 42f8f46
|
||||
- Replace async-std with tokio by Anthony Oteri 80d1acf
|
||||
- Update known issues in release notes by Anthony Oteri 12dd298
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
## Known Issues
|
||||
|
||||
* The delete command is currently not implemented and will return an error
|
||||
if called.
|
||||
* Docker authentication is not currently supported, and attempts to query a
|
||||
registry which requires authentication will fail.
|
||||
|
||||
|
||||
+87
-7
@@ -14,11 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use http::header;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use crate::error::ApiError;
|
||||
|
||||
const MANIFEST_V2: &str = "application/vnd.docker.distribution.manifest.v2+json";
|
||||
|
||||
/// Iterate over a paginated result set, collecting and returning the response
|
||||
/// set.
|
||||
///
|
||||
@@ -48,8 +51,13 @@ pub async fn fetch_paginated<T: for<'de> Deserialize<'de>>(
|
||||
let url = origin.join(&next_path)?;
|
||||
|
||||
let resp = reqwest::get(url).await?;
|
||||
parse_response_status(&resp)?;
|
||||
|
||||
let headers = resp.headers().clone();
|
||||
responses.push(resp.json().await?);
|
||||
|
||||
if let Ok(json) = resp.json().await {
|
||||
responses.push(json);
|
||||
}
|
||||
|
||||
if let Some(p) = parse_rfc5988(headers.get(http::header::LINK))? {
|
||||
next_path = p;
|
||||
@@ -126,7 +134,7 @@ pub fn parse_response_status(response: &reqwest::Response) -> Result<(), ApiErro
|
||||
log::trace!("parse_response_status(response: {response:?})");
|
||||
|
||||
match response.status() {
|
||||
http::StatusCode::OK => {
|
||||
http::StatusCode::OK | http::StatusCode::ACCEPTED => {
|
||||
let headers = response.headers();
|
||||
if let Some(header_value) = headers.get("Docker-Distribution-API-Version") {
|
||||
if header_value.to_str()? == "registry/2.0" {
|
||||
@@ -140,6 +148,7 @@ pub fn parse_response_status(response: &reqwest::Response) -> Result<(), ApiErro
|
||||
))
|
||||
}
|
||||
}
|
||||
http::StatusCode::METHOD_NOT_ALLOWED => Err(ApiError::MethodNotAllowed),
|
||||
http::StatusCode::UNAUTHORIZED => {
|
||||
let headers = response.headers();
|
||||
if let Some(header_value) = headers.get("Docker-Distribution-API-Version") {
|
||||
@@ -155,12 +164,39 @@ pub fn parse_response_status(response: &reqwest::Response) -> Result<(), ApiErro
|
||||
}
|
||||
}
|
||||
http::StatusCode::NOT_FOUND => Err(ApiError::NotFound),
|
||||
_ => Err(ApiError::UnexpectedResponse(
|
||||
"Undocumented status code".into(),
|
||||
)),
|
||||
e => Err(ApiError::UnexpectedResponse(format!(
|
||||
"Undocumented status code: {e:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch the V2 Registry Digest for the specific manifest referenced in the
|
||||
/// provided `url`.
|
||||
///
|
||||
/// # Errors:
|
||||
///
|
||||
/// This will return an `ApiError` if there is a problem fetching the manifest
|
||||
/// headers.
|
||||
pub async fn get_digest(client: &reqwest::Client, url: &Url) -> Result<String, ApiError> {
|
||||
log::trace!("get_manifest(client: {client:?}, url: {url}");
|
||||
let resp = client
|
||||
.head(url.as_ref())
|
||||
.header(header::ACCEPT, MANIFEST_V2)
|
||||
.send()
|
||||
.await?;
|
||||
parse_response_status(&resp)?;
|
||||
|
||||
let headers = resp.headers();
|
||||
Ok(String::from(
|
||||
headers
|
||||
.get("docker-content-digest")
|
||||
.ok_or(ApiError::UnexpectedResponse(String::from(
|
||||
"Missing docker-content-digest header",
|
||||
)))?
|
||||
.to_str()?,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use http::header::HeaderValue;
|
||||
@@ -171,7 +207,7 @@ mod tests {
|
||||
///
|
||||
/// Attempt to parse a valid RFC5988 header value, and ensure that the
|
||||
/// parsed URL was returned as expected.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_parse_rfc5988_valid() {
|
||||
// Mock a valid RFC5988 header value
|
||||
let valid_header_value =
|
||||
@@ -189,7 +225,7 @@ mod tests {
|
||||
///
|
||||
/// Attempt to parse an invalid string as RFC5988, ensuring that the `None`
|
||||
/// variant is returned.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_parse_rfc5988_invalid() {
|
||||
// Mock a valid RFC5988 header value
|
||||
let invalid_header_value = HeaderValue::from_str(r#"invalid header value"#)
|
||||
@@ -201,4 +237,48 @@ mod tests {
|
||||
// Assert that the function returned the expected URL as Some(String)
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
/// Validates the happy path for the get_digest function
|
||||
///
|
||||
/// This tests starts up a mock server, and the client makes a request for
|
||||
/// the digest with the proper headers set. The test then validates that
|
||||
/// the correct digest is returned and that the mock server had the expected
|
||||
/// interactions.
|
||||
#[tokio::test]
|
||||
async fn test_get_digest() -> Result<(), ApiError> {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2/foo/manifests/latest";
|
||||
|
||||
// Mock the HTTP response for the Docker Registry API
|
||||
let registry_url = Url::parse(&server.url()).expect("Failed to parse registry URL");
|
||||
let mock_response = server
|
||||
.mock("HEAD", path)
|
||||
.match_header(http::header::ACCEPT.as_str(), MANIFEST_V2)
|
||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||
.with_header(
|
||||
"docker-content-digest",
|
||||
"sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50",
|
||||
)
|
||||
.with_header(
|
||||
"etag",
|
||||
"sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50",
|
||||
)
|
||||
.create();
|
||||
|
||||
let url = registry_url.join(path)?;
|
||||
let client = reqwest::Client::new();
|
||||
let result = get_digest(&client, &url).await;
|
||||
|
||||
assert!(result.is_ok(), "{:?}", result.unwrap_err());
|
||||
assert_eq!(
|
||||
result.unwrap(),
|
||||
*"sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50"
|
||||
);
|
||||
|
||||
mock_response.assert();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+36
-17
@@ -128,6 +128,8 @@ pub async fn show_handler(
|
||||
let url = registry_url.join(&path)?;
|
||||
|
||||
let resp = reqwest::get(url).await?;
|
||||
api::parse_response_status(&resp)?;
|
||||
|
||||
let headers = resp.headers();
|
||||
let digest: String = String::from(
|
||||
headers
|
||||
@@ -173,7 +175,17 @@ pub async fn delete_handler(
|
||||
tag: &str,
|
||||
) -> Result<(), ApiError> {
|
||||
log::trace!("delete_handler(registry_url: {registry_url:?}, image: {image}, tag: {tag})");
|
||||
todo!()
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let url = registry_url.join(&format!("/v2/{image}/manifests/{tag}"))?;
|
||||
let digest = api::get_digest(&client, &url).await?;
|
||||
|
||||
log::debug!("Deleting digest {digest}");
|
||||
let url = registry_url.join(&format!("/v2/{image}/manifests/{digest}"))?;
|
||||
let resp = client.delete(url).send().await?;
|
||||
api::parse_response_status(&resp)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Path to the Docker Registry APIs "api version check" endpoint.
|
||||
@@ -190,8 +202,8 @@ pub async fn check_handler(buf: &mut dyn Write, registry_url: &Url) -> Result<()
|
||||
let path = "/v2";
|
||||
let url = registry_url.join(path)?;
|
||||
|
||||
let response = reqwest::get(url).await?;
|
||||
api::parse_response_status(&response)?;
|
||||
let resp = reqwest::get(url).await?;
|
||||
api::parse_response_status(&resp)?;
|
||||
writeln!(buf, "Ok")?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -212,7 +224,7 @@ mod tests {
|
||||
/// This test spins up a mock server, and makes a request to the catalog
|
||||
/// endpoint. It checks that the handler both called the request the
|
||||
/// expected number of times, and did not return an error.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_catalog_handler() {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2/_catalog";
|
||||
@@ -222,12 +234,13 @@ mod tests {
|
||||
.mock("GET", path)
|
||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||
.with_body(r#"{"repositories": ["image1", "image2", "image3"]}"#)
|
||||
.create();
|
||||
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let result = catalog_handler(&mut buf, ®istry_url).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.is_ok(), "{:?}", result.unwrap_err());
|
||||
assert_eq!(String::from_utf8(buf).unwrap(), *"image1\nimage2\nimage3\n");
|
||||
|
||||
mock_response.assert();
|
||||
@@ -240,7 +253,7 @@ mod tests {
|
||||
/// should follow, resulting in the combined list. It checks that the
|
||||
/// handler both called the request the expected number of times, and did
|
||||
/// not return an error.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_catalog_handler_with_pagination() {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2/_catalog";
|
||||
@@ -251,6 +264,7 @@ mod tests {
|
||||
.mock("GET", path)
|
||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||
.with_header(
|
||||
http::header::LINK.as_str(),
|
||||
&format!(r#"<{path2}>; rel=next"#),
|
||||
@@ -262,12 +276,13 @@ mod tests {
|
||||
.mock("GET", path2)
|
||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||
.with_body(r#"{"repositories": ["image3"]}"#)
|
||||
.create();
|
||||
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let result = catalog_handler(&mut buf, ®istry_url).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.is_ok(), "{:?}", result.unwrap_err());
|
||||
assert_eq!(String::from_utf8(buf).unwrap(), *"image1\nimage2\nimage3\n");
|
||||
|
||||
mock_response.assert();
|
||||
@@ -279,7 +294,7 @@ mod tests {
|
||||
/// This test spins up a mock server, and makes a request to the tags
|
||||
/// endpoint. It checks that the handler both called the request the
|
||||
/// expected number of times, and did not return an error.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_tags_handler() {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2/some_image/tags/list";
|
||||
@@ -290,12 +305,13 @@ mod tests {
|
||||
.mock("GET", path)
|
||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||
.with_body(r#"{"tags": ["tag1", "tag2", "tag3"]}"#)
|
||||
.create();
|
||||
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let result = tags_handler(&mut buf, ®istry_url, "some_image").await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.is_ok(), "{:?}", result.unwrap_err());
|
||||
assert_eq!(String::from_utf8(buf).unwrap(), *"tag1\ntag2\ntag3\n");
|
||||
|
||||
mock_response.assert();
|
||||
@@ -308,7 +324,7 @@ mod tests {
|
||||
/// should follow, resulting in the combined list. It checks that the
|
||||
/// handler both called the request the expected number of times, and did
|
||||
/// not return an error.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_tags_handler_with_pagination() {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2/some_image/tags/list";
|
||||
@@ -320,6 +336,7 @@ mod tests {
|
||||
.mock("GET", path)
|
||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||
.with_header(
|
||||
http::header::LINK.as_str(),
|
||||
&format!(r#"<{path2}>; rel=next"#),
|
||||
@@ -331,12 +348,13 @@ mod tests {
|
||||
.mock("GET", path2)
|
||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||
.with_body(r#"{"tags": ["tag3"]}"#)
|
||||
.create();
|
||||
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let result = tags_handler(&mut buf, ®istry_url, "some_image").await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.is_ok(), "{:?}", result.unwrap_err());
|
||||
assert_eq!(String::from_utf8(buf).unwrap(), *"tag1\ntag2\ntag3\n");
|
||||
|
||||
mock_response.assert();
|
||||
@@ -348,7 +366,7 @@ mod tests {
|
||||
/// This test spins up a mock server, and makes a request to the check
|
||||
/// endpoint. It checks that the handler both called the request the
|
||||
/// expected number of times, and did not return an error.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_check_handler() {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2";
|
||||
@@ -364,7 +382,7 @@ mod tests {
|
||||
|
||||
let mut buf: Vec<u8> = Vec::new();
|
||||
let result = check_handler(&mut buf, ®istry_url).await;
|
||||
assert!(result.is_ok());
|
||||
assert!(result.is_ok(), "{:?}", result.unwrap_err());
|
||||
assert_eq!(String::from_utf8(buf).unwrap(), *"Ok\n");
|
||||
|
||||
mock_response.assert();
|
||||
@@ -374,7 +392,7 @@ mod tests {
|
||||
///
|
||||
/// This validates that if the "Docker-Distribution-API-Version" header
|
||||
/// is missing in the response, the appropriate error is returned.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_check_handler_missing_api_version() -> Result<(), Box<dyn Error>> {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2";
|
||||
@@ -407,7 +425,7 @@ mod tests {
|
||||
/// This validates that if the "Docker-Distribution-API-Version" header
|
||||
/// is present in the response but contains an unexpected value, the
|
||||
/// appropriate error is returned.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_check_handler_invalid_api_version() -> Result<(), Box<dyn Error>> {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2";
|
||||
@@ -441,7 +459,7 @@ mod tests {
|
||||
/// This test spins up a mock server, and makes a request to the image
|
||||
/// manifests endpoint. It checks that the handler both called the request
|
||||
/// the expected number of times, and did not return an error.
|
||||
#[async_std::test]
|
||||
#[tokio::test]
|
||||
async fn test_show_handler() {
|
||||
let mut server = mockito::Server::new_async().await;
|
||||
let path = "/v2/foo/manifests/latest";
|
||||
@@ -492,6 +510,7 @@ mod tests {
|
||||
.mock("GET", path)
|
||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||
.with_header(
|
||||
"docker-content-digest",
|
||||
"sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50",
|
||||
@@ -517,7 +536,7 @@ mod tests {
|
||||
etag: sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50\n"
|
||||
};
|
||||
|
||||
assert!(result.is_ok());
|
||||
assert!(result.is_ok(), "{:?}", result.unwrap_err());
|
||||
assert_eq!(String::from_utf8(buf).unwrap(), *expected_body);
|
||||
|
||||
mock_response.assert();
|
||||
|
||||
@@ -32,6 +32,9 @@ pub enum DredgeError {
|
||||
|
||||
#[error(transparent)]
|
||||
IOError(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
LoggerError(#[from] log::SetLoggerError),
|
||||
}
|
||||
|
||||
/// An error related to the communication with the registry API.
|
||||
@@ -65,6 +68,9 @@ pub enum ApiError {
|
||||
|
||||
#[error(transparent)]
|
||||
SerializerError(#[from] serde_yaml::Error),
|
||||
|
||||
#[error("Method not allowed")]
|
||||
MethodNotAllowed,
|
||||
}
|
||||
|
||||
impl From<reqwest::header::ToStrError> for ApiError {
|
||||
|
||||
+8
-2
@@ -19,6 +19,7 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
use clap::Parser;
|
||||
use simple_logger::SimpleLogger;
|
||||
use url::Url;
|
||||
|
||||
use crate::cli::Cli;
|
||||
@@ -56,13 +57,18 @@ fn parse_registry_arg(host: &str) -> Result<Url, DredgeError> {
|
||||
Url::parse(&host).or(Err(DredgeError::RegistryUrlError(host.to_string())))
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), DredgeError> {
|
||||
let args = Cli::parse();
|
||||
|
||||
// -- Initialize logging
|
||||
let log_level = args.log_level;
|
||||
femme::with_level(log::LevelFilter::from(log_level));
|
||||
SimpleLogger::new()
|
||||
.with_colors(true)
|
||||
.with_utc_timestamps()
|
||||
.with_level(log_level.into())
|
||||
.env()
|
||||
.init()?;
|
||||
|
||||
// -- Parse the given <REGISTRY> argument into a complete URL
|
||||
let registry_url: Url = parse_registry_arg(&args.registry)?;
|
||||
|
||||
Reference in New Issue
Block a user