mirror of
https://github.com/anthonyoteri/dredge.git
synced 2026-06-05 15:26:53 -04:00
Compare commits
8 Commits
v0.1.0-rc1
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| de42860be8 | |||
| b4d6002a20 | |||
| fbe43f03f1 | |||
| 48070cff1f | |||
| 13ae092b91 | |||
| b0239fb049 | |||
| ed84e92112 | |||
| cfdefb287c |
+4
-3
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dredge"
|
name = "dredge"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Anthony Oteri"]
|
authors = ["Anthony Oteri"]
|
||||||
description = "A Command Line tool for interracting with the Docker Registry API"
|
description = "A Command Line tool for interracting with the Docker Registry API"
|
||||||
@@ -23,7 +23,7 @@ categories = [
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
async-std = { version = "1.12.0", features = ["async-attributes", "attributes", "tokio1"] }
|
async-std = { version = "1.12.0", features = ["async-attributes", "attributes", "tokio1"] }
|
||||||
clap = { version = "4.4.3", features = ["derive", "env", "wrap_help"] }
|
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"
|
http = "0.2.9"
|
||||||
indoc = "2.0.4"
|
indoc = "2.0.4"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
@@ -37,4 +37,5 @@ url = { version = "2.4.1", features = ["serde"] }
|
|||||||
xdg = "2.5.2"
|
xdg = "2.5.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mockito = "1.2.0"
|
mockito = "1.2.0"
|
||||||
|
env_logger = "0.10.0"
|
||||||
@@ -32,6 +32,7 @@ Options:
|
|||||||
-V, --version
|
-V, --version
|
||||||
Print version
|
Print version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Checking the API Version
|
### Checking the API Version
|
||||||
|
|
||||||
Perform a simple API Version check towards the registry endpoint
|
Perform a simple API Version check towards the registry endpoint
|
||||||
@@ -87,6 +88,15 @@ Options:
|
|||||||
|
|
||||||
Delete a tagged image from the registry
|
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
|
```shell
|
||||||
Usage: dredge <REGISTRY> delete <IMAGE> <TAG>
|
Usage: dredge <REGISTRY> delete <IMAGE> <TAG>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Dredge Release Notes
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- 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,3 @@
|
|||||||
|
- 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,10 @@
|
|||||||
|
# Dredge Release Notes
|
||||||
|
|
||||||
|
## 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
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2023 Anthony Oteri
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
|
|
||||||
|
version=$1
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage $0 <version> [from tag]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
previous=${2:-$(git describe --abbrev=0 --match='v*')}
|
||||||
|
|
||||||
|
changelog="${REPO_ROOT}/docs/changelog-${previous}-${version}.md"
|
||||||
|
|
||||||
|
echo "- ${version}" | tee ${changelog}
|
||||||
|
echo "" | tee -a ${changelog}
|
||||||
|
git log --pretty=format:' - %s by %an %h' --no-merges ${previous}.. | tee -a ${changelog}
|
||||||
|
|
||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2023 Anthony Oteri
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
#!/usr/bin/bash -e
|
||||||
|
|
||||||
|
version=$1
|
||||||
|
previous=$2
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage $0 <version> [previous]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
|
SCRIPTS="${REPO_ROOT}/scripts"
|
||||||
|
|
||||||
|
${SCRIPTS}/generate-changelog.sh "v${version}" "${previous}" && \
|
||||||
|
${SCRIPTS}/update-release-notes.sh && \
|
||||||
|
git add "${REPO_ROOT}/docs" "${REPO_ROOT}/RELEASE_NOTES.md"
|
||||||
|
|
||||||
|
sed -i "s/^version = \".*\"/version = \"${version}\"/" \
|
||||||
|
"${REPO_ROOT}/Cargo.toml" && git add "${REPO_ROOT}/Cargo.toml"
|
||||||
|
|
||||||
|
echo "*************************************************************************"
|
||||||
|
echo "Release is ready, please use the following to commit changes"
|
||||||
|
echo
|
||||||
|
echo "git commit -am \"Release version ${version}\" && \ "
|
||||||
|
echo " git tag -a -m v${version} v${version}"
|
||||||
|
echo
|
||||||
|
echo "*************************************************************************"
|
||||||
|
|
||||||
|
|
||||||
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2023 Anthony Oteri
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
|
|
||||||
|
RELEASE_NOTES="${REPO_ROOT}/RELEASE_NOTES.md"
|
||||||
|
/usr/bin/cat "${REPO_ROOT}/docs/release-notes-template.md" | tee "${RELEASE_NOTES}"
|
||||||
|
|
||||||
|
for note in $(/usr/bin/find "${REPO_ROOT}/docs" -name "changelog*.md" -print | sort -rn); do
|
||||||
|
/usr/bin/cat "${note}" | tee -a "${RELEASE_NOTES}"
|
||||||
|
echo "" | tee -a "${RELEASE_NOTES}"
|
||||||
|
echo "" | tee -a "${RELEASE_NOTES}"
|
||||||
|
done
|
||||||
+85
-5
@@ -14,11 +14,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use http::header;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::error::ApiError;
|
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
|
/// Iterate over a paginated result set, collecting and returning the response
|
||||||
/// set.
|
/// set.
|
||||||
///
|
///
|
||||||
@@ -48,8 +51,13 @@ pub async fn fetch_paginated<T: for<'de> Deserialize<'de>>(
|
|||||||
let url = origin.join(&next_path)?;
|
let url = origin.join(&next_path)?;
|
||||||
|
|
||||||
let resp = reqwest::get(url).await?;
|
let resp = reqwest::get(url).await?;
|
||||||
|
parse_response_status(&resp)?;
|
||||||
|
|
||||||
let headers = resp.headers().clone();
|
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))? {
|
if let Some(p) = parse_rfc5988(headers.get(http::header::LINK))? {
|
||||||
next_path = p;
|
next_path = p;
|
||||||
@@ -126,7 +134,7 @@ pub fn parse_response_status(response: &reqwest::Response) -> Result<(), ApiErro
|
|||||||
log::trace!("parse_response_status(response: {response:?})");
|
log::trace!("parse_response_status(response: {response:?})");
|
||||||
|
|
||||||
match response.status() {
|
match response.status() {
|
||||||
http::StatusCode::OK => {
|
http::StatusCode::OK | http::StatusCode::ACCEPTED => {
|
||||||
let headers = response.headers();
|
let headers = response.headers();
|
||||||
if let Some(header_value) = headers.get("Docker-Distribution-API-Version") {
|
if let Some(header_value) = headers.get("Docker-Distribution-API-Version") {
|
||||||
if header_value.to_str()? == "registry/2.0" {
|
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 => {
|
http::StatusCode::UNAUTHORIZED => {
|
||||||
let headers = response.headers();
|
let headers = response.headers();
|
||||||
if let Some(header_value) = headers.get("Docker-Distribution-API-Version") {
|
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),
|
http::StatusCode::NOT_FOUND => Err(ApiError::NotFound),
|
||||||
_ => Err(ApiError::UnexpectedResponse(
|
e => Err(ApiError::UnexpectedResponse(format!(
|
||||||
"Undocumented status code".into(),
|
"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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use http::header::HeaderValue;
|
use http::header::HeaderValue;
|
||||||
@@ -201,4 +237,48 @@ mod tests {
|
|||||||
// Assert that the function returned the expected URL as Some(String)
|
// Assert that the function returned the expected URL as Some(String)
|
||||||
assert_eq!(result, None);
|
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.
|
||||||
|
#[async_std::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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-9
@@ -128,6 +128,8 @@ pub async fn show_handler(
|
|||||||
let url = registry_url.join(&path)?;
|
let url = registry_url.join(&path)?;
|
||||||
|
|
||||||
let resp = reqwest::get(url).await?;
|
let resp = reqwest::get(url).await?;
|
||||||
|
api::parse_response_status(&resp)?;
|
||||||
|
|
||||||
let headers = resp.headers();
|
let headers = resp.headers();
|
||||||
let digest: String = String::from(
|
let digest: String = String::from(
|
||||||
headers
|
headers
|
||||||
@@ -173,7 +175,17 @@ pub async fn delete_handler(
|
|||||||
tag: &str,
|
tag: &str,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
log::trace!("delete_handler(registry_url: {registry_url:?}, image: {image}, tag: {tag})");
|
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.
|
// 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 path = "/v2";
|
||||||
let url = registry_url.join(path)?;
|
let url = registry_url.join(path)?;
|
||||||
|
|
||||||
let response = reqwest::get(url).await?;
|
let resp = reqwest::get(url).await?;
|
||||||
api::parse_response_status(&response)?;
|
api::parse_response_status(&resp)?;
|
||||||
writeln!(buf, "Ok")?;
|
writeln!(buf, "Ok")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -222,12 +234,13 @@ mod tests {
|
|||||||
.mock("GET", path)
|
.mock("GET", path)
|
||||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
.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"]}"#)
|
.with_body(r#"{"repositories": ["image1", "image2", "image3"]}"#)
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
let result = catalog_handler(&mut buf, ®istry_url).await;
|
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");
|
assert_eq!(String::from_utf8(buf).unwrap(), *"image1\nimage2\nimage3\n");
|
||||||
|
|
||||||
mock_response.assert();
|
mock_response.assert();
|
||||||
@@ -251,6 +264,7 @@ mod tests {
|
|||||||
.mock("GET", path)
|
.mock("GET", path)
|
||||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||||
|
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||||
.with_header(
|
.with_header(
|
||||||
http::header::LINK.as_str(),
|
http::header::LINK.as_str(),
|
||||||
&format!(r#"<{path2}>; rel=next"#),
|
&format!(r#"<{path2}>; rel=next"#),
|
||||||
@@ -262,12 +276,13 @@ mod tests {
|
|||||||
.mock("GET", path2)
|
.mock("GET", path2)
|
||||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||||
|
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||||
.with_body(r#"{"repositories": ["image3"]}"#)
|
.with_body(r#"{"repositories": ["image3"]}"#)
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
let result = catalog_handler(&mut buf, ®istry_url).await;
|
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");
|
assert_eq!(String::from_utf8(buf).unwrap(), *"image1\nimage2\nimage3\n");
|
||||||
|
|
||||||
mock_response.assert();
|
mock_response.assert();
|
||||||
@@ -290,12 +305,13 @@ mod tests {
|
|||||||
.mock("GET", path)
|
.mock("GET", path)
|
||||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
.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"]}"#)
|
.with_body(r#"{"tags": ["tag1", "tag2", "tag3"]}"#)
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
let result = tags_handler(&mut buf, ®istry_url, "some_image").await;
|
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");
|
assert_eq!(String::from_utf8(buf).unwrap(), *"tag1\ntag2\ntag3\n");
|
||||||
|
|
||||||
mock_response.assert();
|
mock_response.assert();
|
||||||
@@ -320,6 +336,7 @@ mod tests {
|
|||||||
.mock("GET", path)
|
.mock("GET", path)
|
||||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||||
|
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||||
.with_header(
|
.with_header(
|
||||||
http::header::LINK.as_str(),
|
http::header::LINK.as_str(),
|
||||||
&format!(r#"<{path2}>; rel=next"#),
|
&format!(r#"<{path2}>; rel=next"#),
|
||||||
@@ -331,12 +348,13 @@ mod tests {
|
|||||||
.mock("GET", path2)
|
.mock("GET", path2)
|
||||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||||
|
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||||
.with_body(r#"{"tags": ["tag3"]}"#)
|
.with_body(r#"{"tags": ["tag3"]}"#)
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
let result = tags_handler(&mut buf, ®istry_url, "some_image").await;
|
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");
|
assert_eq!(String::from_utf8(buf).unwrap(), *"tag1\ntag2\ntag3\n");
|
||||||
|
|
||||||
mock_response.assert();
|
mock_response.assert();
|
||||||
@@ -364,7 +382,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
let result = check_handler(&mut buf, ®istry_url).await;
|
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");
|
assert_eq!(String::from_utf8(buf).unwrap(), *"Ok\n");
|
||||||
|
|
||||||
mock_response.assert();
|
mock_response.assert();
|
||||||
@@ -492,6 +510,7 @@ mod tests {
|
|||||||
.mock("GET", path)
|
.mock("GET", path)
|
||||||
.with_status(http::status::StatusCode::OK.as_u16().into())
|
.with_status(http::status::StatusCode::OK.as_u16().into())
|
||||||
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
.with_header(http::header::CONTENT_TYPE.as_str(), "application/json")
|
||||||
|
.with_header("Docker-Distribution-API-Version", "registry/2.0")
|
||||||
.with_header(
|
.with_header(
|
||||||
"docker-content-digest",
|
"docker-content-digest",
|
||||||
"sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50",
|
"sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50",
|
||||||
@@ -517,7 +536,7 @@ mod tests {
|
|||||||
etag: sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50\n"
|
etag: sha256:0259571889ac87efbfca5b79a0abe9baf626d058ec5f9a5744bace2229d9ed50\n"
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok(), "{:?}", result.unwrap_err());
|
||||||
assert_eq!(String::from_utf8(buf).unwrap(), *expected_body);
|
assert_eq!(String::from_utf8(buf).unwrap(), *expected_body);
|
||||||
|
|
||||||
mock_response.assert();
|
mock_response.assert();
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ pub enum DredgeError {
|
|||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
IOError(#[from] std::io::Error),
|
IOError(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
LoggerError(#[from] log::SetLoggerError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error related to the communication with the registry API.
|
/// An error related to the communication with the registry API.
|
||||||
@@ -65,6 +68,9 @@ pub enum ApiError {
|
|||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SerializerError(#[from] serde_yaml::Error),
|
SerializerError(#[from] serde_yaml::Error),
|
||||||
|
|
||||||
|
#[error("Method not allowed")]
|
||||||
|
MethodNotAllowed,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<reqwest::header::ToStrError> for ApiError {
|
impl From<reqwest::header::ToStrError> for ApiError {
|
||||||
|
|||||||
+7
-1
@@ -19,6 +19,7 @@
|
|||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use simple_logger::SimpleLogger;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::cli::Cli;
|
use crate::cli::Cli;
|
||||||
@@ -62,7 +63,12 @@ async fn main() -> Result<(), DredgeError> {
|
|||||||
|
|
||||||
// -- Initialize logging
|
// -- Initialize logging
|
||||||
let log_level = args.log_level;
|
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
|
// -- Parse the given <REGISTRY> argument into a complete URL
|
||||||
let registry_url: Url = parse_registry_arg(&args.registry)?;
|
let registry_url: Url = parse_registry_arg(&args.registry)?;
|
||||||
|
|||||||
Reference in New Issue
Block a user