Skip to content

Commit

Permalink
Add DockerHub implementation for send_tags
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Nelson <minelson@vmware.com>
  • Loading branch information
absoludity committed Aug 7, 2023
1 parent 41e940a commit d3e097b
Showing 1 changed file with 134 additions and 11 deletions.
145 changes: 134 additions & 11 deletions cmd/oci-catalog/src/providers/dockerhub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use tonic::Status;
/// The default page size with which requests are sent to docker hub.
const DEFAULT_PAGE_SIZE: u8 = 100;
pub const PROVIDER_NAME: &str = "DockerHubAPI";
pub const DOCKERHUB_URI: &str = "https://hub.docker.com";

#[derive(Serialize, Deserialize)]
struct DockerHubV2Repository {
Expand All @@ -29,6 +30,21 @@ struct DockerHubV2RepositoriesResult {
results: Vec<DockerHubV2Repository>,
}

#[derive(Serialize, Deserialize)]
struct DockerHubV2Tag {
name: String,
repository_type: Option<String>,
content_type: String,
}

#[derive(Serialize, Deserialize)]
struct DockerHubV2TagsResult {
count: u16,
next: Option<String>,
previous: Option<String>,
results: Vec<DockerHubV2Tag>,
}

#[derive(Debug, Default)]
pub struct DockerHubAPI {}

Expand All @@ -44,7 +60,7 @@ impl OCICatalogSender for DockerHubAPI {
tx: mpsc::Sender<Result<Repository, Status>>,
request: &ListRepositoriesRequest,
) {
let mut url = url_for_request(request);
let mut url = url_for_request_repositories(request);

let client = reqwest::Client::builder().build().unwrap();

Expand Down Expand Up @@ -105,25 +121,79 @@ impl OCICatalogSender for DockerHubAPI {
}
}

async fn send_tags(&self, tx: mpsc::Sender<Result<Tag, Status>>, _request: &ListTagsRequest) {
for count in 0..10 {
tx.send(Ok(Tag {
name: format!("tag-{}", count),
}))
.await
.unwrap();
async fn send_tags(&self, tx: mpsc::Sender<Result<Tag, Status>>, request: &ListTagsRequest) {
let mut url = match url_for_request_tags(request) {
Ok(u) => u,
Err(e) => {
tx.send(Err(e)).await.unwrap();
return;
}
};

let client = reqwest::Client::builder().build().unwrap();

loop {
log::debug!("requesting: {}", url);
let response = match client.get(url.clone()).send().await {
Ok(r) => r,
Err(e) => {
tx.send(Err(Status::failed_precondition(e.to_string())))
.await
.unwrap();
return;
}
};

if response.status() != StatusCode::OK {
tx.send(Err(Status::failed_precondition(format!(
"unexpected status code when requesting {}: {}",
url,
response.status()
))))
.await
.unwrap();
return;
}

let body = match response.text().await {
Ok(b) => b,
Err(e) => {
tx.send(Err(Status::failed_precondition(format!(
"unable to extract body from response: {}",
e.to_string()
))))
.await
.unwrap();
return;
}
};
log::trace!("response body: {}", body);

let response: DockerHubV2TagsResult = serde_json::from_str(&body).unwrap();

for tag in response.results {
tx.send(Ok(Tag { name: tag.name })).await.unwrap();
}

if response.next.is_some() {
url = reqwest::Url::parse(&response.next.unwrap()).unwrap();
} else {
break;
}
}
}
}

fn url_for_request(request: &ListRepositoriesRequest) -> Url {
let mut url = reqwest::Url::parse("https://hub.docker.com/v2/repositories/").unwrap();
fn url_for_request_repositories(request: &ListRepositoriesRequest) -> Url {
let mut url = reqwest::Url::parse(DOCKERHUB_URI).unwrap();

if !request.namespace.is_empty() {
url.set_path(&format!(
"/v2/namespaces/{}/repositories/",
request.namespace
));
} else {
url.set_path("/v2/repositories/");
}
// For now we use a default page size and default ordering.
url.query_pairs_mut()
Expand All @@ -137,6 +207,30 @@ fn url_for_request(request: &ListRepositoriesRequest) -> Url {
url
}

fn url_for_request_tags(request: &ListTagsRequest) -> Result<Url, Status> {
let mut url = reqwest::Url::parse(DOCKERHUB_URI).unwrap();

let repo = match request.repository.clone() {
Some(r) => r,
None => {
return Err(Status::invalid_argument(format!(
"repository not set in request"
)))
}
};

url.set_path(&format!(
"/v2/repositories/{}/{}/tags",
repo.namespace, repo.name
));

// For now we use a default page size.
url.query_pairs_mut()
.append_pair("page_size", &format!("{}", DEFAULT_PAGE_SIZE));

Ok(url)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -165,6 +259,35 @@ mod tests {
..Default::default()
}, "https://hub.docker.com/v2/namespaces/bitnamicharts/repositories/?page_size=100&ordering=name&content_types=helm&content_types=image")]
fn test_url_for_request(#[case] request: ListRepositoriesRequest, #[case] expected_url: Url) {
assert_eq!(url_for_request(&request), expected_url);
assert_eq!(url_for_request_repositories(&request), expected_url);
}

#[rstest]
#[case::without_repository(ListTagsRequest{
..Default::default()
}, Err(Status::invalid_argument("bang")))]
#[case::with_repository(ListTagsRequest{
repository: Some(Repository{
namespace: "bitnamicharts".to_string(),
name: "apache".to_string(),
..Default::default()
}),
..Default::default()
}, Ok(reqwest::Url::parse("https://hub.docker.com/v2/repositories/bitnamicharts/apache/tags?page_size=100").unwrap()))]
fn test_url_for_request_tags(
#[case] request: ListTagsRequest,
#[case] expected_result: Result<Url, Status>,
) {
match expected_result {
Ok(url) => {
assert_eq!(url_for_request_tags(&request).unwrap(), url);
}
Err(e) => {
assert_eq!(
url_for_request_tags(&request).err().unwrap().code(),
e.code()
)
}
}
}
}

0 comments on commit d3e097b

Please sign in to comment.