Skip to content

Commit 9e933ef

Browse files
committed
list and describe metadata from oci
1 parent 8af2e01 commit 9e933ef

File tree

12 files changed

+282
-51
lines changed

12 files changed

+282
-51
lines changed

Cargo.lock

Lines changed: 9 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.nix

Lines changed: 22 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ phf = "0.11"
3838
phf_codegen = "0.11"
3939
rand = "0.8"
4040
regex = "1.10"
41-
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
41+
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
4242
rstest = "0.22"
4343
semver = { version = "1.0", features = ["serde"] }
4444
serde = { version = "1.0", features = ["derive"] }
@@ -54,6 +54,7 @@ tower-http = { version = "0.5", features = ["validate-request"] }
5454
tracing = "0.1"
5555
tracing-subscriber = "0.3"
5656
url = "2.5"
57+
urlencoding = "2.1.3"
5758
utoipa = { version = "4.2", features = ["indexmap"] }
5859
utoipa-swagger-ui = { version = "7.1", features = ["axum"] }
5960
uuid = { version = "1.10", features = ["v4"] }

rust/stackable-cockpit/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ tera.workspace = true
3232
tokio.workspace = true
3333
tracing.workspace = true
3434
url.workspace = true
35+
urlencoding.workspace = true
3536
utoipa = { workspace = true, optional = true }
3637
which.workspace = true
3738
futures.workspace = true

rust/stackable-cockpit/src/constants.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub const HELM_REPO_NAME_TEST: &str = "stackable-test";
2222
pub const HELM_REPO_NAME_DEV: &str = "stackable-dev";
2323
pub const HELM_REPO_INDEX_FILE: &str = "index.yaml";
2424

25+
pub const HELM_OCI_BASE: &str = "oci.stackable.tech";
2526
pub const HELM_OCI_REGISTRY: &str = "oci://oci.stackable.tech/sdp-charts";
2627

2728
pub const HELM_DEFAULT_CHART_VERSION: &str = ">0.0.0-0";

rust/stackable-cockpit/src/helm.rs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::collections::HashMap;
21
use std::fmt::Display;
32

43
use serde::{Deserialize, Serialize};
@@ -7,7 +6,10 @@ use tokio::task::block_in_place;
76
use tracing::{debug, error, info, instrument};
87
use url::Url;
98

10-
use crate::constants::{HELM_DEFAULT_CHART_VERSION, HELM_REPO_INDEX_FILE};
9+
use crate::{
10+
constants::{HELM_DEFAULT_CHART_VERSION, HELM_REPO_INDEX_FILE},
11+
utils::chartsource::ChartSourceMetadata,
12+
};
1113

1214
#[derive(Debug, Deserialize, Serialize)]
1315
#[serde(rename_all = "camelCase")]
@@ -35,17 +37,6 @@ pub struct ChartRepo {
3537
pub url: String,
3638
}
3739

38-
#[derive(Clone, Debug, Deserialize)]
39-
pub struct Repository {
40-
pub entries: HashMap<String, Vec<RepositoryEntry>>,
41-
}
42-
43-
#[derive(Clone, Debug, Deserialize)]
44-
pub struct RepositoryEntry {
45-
pub name: String,
46-
pub version: String,
47-
}
48-
4940
#[derive(Debug, Snafu)]
5041
pub enum Error {
5142
#[snafu(display("failed to parse URL"))]
@@ -398,7 +389,7 @@ pub fn add_repo(repository_name: &str, repository_url: &str) -> Result<(), Error
398389

399390
/// Retrieves the Helm index file from the repository URL.
400391
#[instrument]
401-
pub async fn get_helm_index<T>(repo_url: T) -> Result<Repository, Error>
392+
pub async fn get_helm_index<T>(repo_url: T) -> Result<ChartSourceMetadata, Error>
402393
where
403394
T: AsRef<str> + std::fmt::Debug,
404395
{

rust/stackable-cockpit/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod common;
22
pub mod constants;
33
pub mod engine;
44
pub mod helm;
5+
pub mod oci;
56
pub mod platform;
67
pub mod utils;
78
pub mod xfer;

rust/stackable-cockpit/src/oci.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
use std::collections::HashMap;
2+
3+
use serde::Deserialize;
4+
use snafu::{OptionExt, ResultExt, Snafu};
5+
use tracing::debug;
6+
use urlencoding::encode;
7+
8+
use crate::{
9+
constants::{HELM_OCI_BASE, HELM_REPO_NAME_DEV, HELM_REPO_NAME_STABLE, HELM_REPO_NAME_TEST},
10+
utils::chartsource::{ChartSourceEntry, ChartSourceMetadata},
11+
};
12+
13+
#[derive(Debug, Snafu)]
14+
pub enum Error {
15+
#[snafu(display("cannot get repositories"))]
16+
GetRepositories { source: reqwest::Error },
17+
18+
#[snafu(display("cannot parse repositories"))]
19+
ParseRepositories { source: reqwest::Error },
20+
21+
#[snafu(display("cannot get artifacts"))]
22+
GetArtifacts { source: reqwest::Error },
23+
24+
#[snafu(display("cannot parse artifacts"))]
25+
ParseArtifacts { source: reqwest::Error },
26+
27+
#[snafu(display("unexpected OCI repository name"))]
28+
UnexpectedOciRepositoryName,
29+
}
30+
31+
#[derive(Deserialize, Debug)]
32+
pub struct OciRepository {
33+
pub name: String,
34+
}
35+
36+
#[derive(Deserialize, Debug)]
37+
pub struct Tag {
38+
pub name: String,
39+
}
40+
41+
#[derive(Deserialize, Debug)]
42+
pub struct Artifact {
43+
pub digest: String,
44+
pub tags: Option<Vec<Tag>>,
45+
}
46+
47+
pub async fn get_oci_index<'a>() -> Result<HashMap<&'a str, ChartSourceMetadata>, Error> {
48+
let mut source_index_files: HashMap<&str, ChartSourceMetadata> = HashMap::new();
49+
50+
// initialize map
51+
for repo_name in [
52+
HELM_REPO_NAME_STABLE,
53+
HELM_REPO_NAME_TEST,
54+
HELM_REPO_NAME_DEV,
55+
] {
56+
source_index_files.insert(
57+
repo_name,
58+
ChartSourceMetadata {
59+
entries: HashMap::new(),
60+
},
61+
);
62+
}
63+
let base_url = format!("https://{}/api/v2.0", HELM_OCI_BASE);
64+
65+
// fetch all operators
66+
let url = format!(
67+
"{}/repositories?page_size={}&q=name=~sdp-charts/",
68+
base_url, 100
69+
);
70+
71+
// reuse connections
72+
let client = reqwest::Client::new();
73+
74+
let repositories: Vec<OciRepository> = client
75+
.get(&url)
76+
.send()
77+
.await
78+
.context(GetRepositoriesSnafu)?
79+
.json()
80+
.await
81+
.context(ParseRepositoriesSnafu)?;
82+
83+
debug!("OCI repos {:?}", repositories);
84+
85+
for repository in &repositories {
86+
// fetch all artifacts pro operator
87+
let (project_name, repository_name) = repository
88+
.name
89+
.split_once('/')
90+
.context(UnexpectedOciRepositoryNameSnafu)?;
91+
92+
debug!("OCI repo parts {} and {}", project_name, repository_name);
93+
94+
let mut artifacts = Vec::new();
95+
let mut page = 1;
96+
let page_size = 20;
97+
98+
while let Ok(artifacts_page) = client
99+
.get(format!(
100+
"{}/projects/{}/repositories/{}/artifacts?page_size={}&page={}",
101+
base_url,
102+
encode(project_name),
103+
encode(repository_name),
104+
page_size,
105+
page
106+
))
107+
.send()
108+
.await
109+
.context(GetArtifactsSnafu)?
110+
.json::<Vec<Artifact>>()
111+
.await
112+
.context(ParseArtifactsSnafu)
113+
{
114+
let count = artifacts_page.len();
115+
artifacts.extend(artifacts_page);
116+
if count < page_size {
117+
break;
118+
}
119+
page += 1;
120+
}
121+
122+
for artifact in &artifacts {
123+
if let Some(release_artifact) =
124+
artifact.tags.as_ref().and_then(|tags| tags.iter().next())
125+
{
126+
let release_version = release_artifact
127+
.name
128+
.replace("-arm64", "")
129+
.replace("-amd64", "");
130+
131+
debug!(
132+
"OCI resolved artifact {}, {}, {}",
133+
release_version.to_string(),
134+
repository_name.to_string(),
135+
release_artifact.name.to_string()
136+
);
137+
138+
let entry = ChartSourceEntry {
139+
name: repository_name.to_string(),
140+
version: release_version.to_string(),
141+
};
142+
143+
match release_version.as_str() {
144+
"0.0.0-dev" => {
145+
if let Some(repo) = source_index_files.get_mut(HELM_REPO_NAME_DEV) {
146+
repo.entries
147+
.entry(repository_name.to_string())
148+
.or_default()
149+
.push(entry)
150+
}
151+
}
152+
version if version.contains("-pr") => {
153+
if let Some(repo) = source_index_files.get_mut(HELM_REPO_NAME_TEST) {
154+
repo.entries
155+
.entry(repository_name.to_string())
156+
.or_default()
157+
.push(entry)
158+
}
159+
}
160+
_ => {
161+
if let Some(repo) = source_index_files.get_mut(HELM_REPO_NAME_STABLE) {
162+
repo.entries
163+
.entry(repository_name.to_string())
164+
.or_default()
165+
.push(entry)
166+
}
167+
}
168+
}
169+
}
170+
}
171+
}
172+
Ok(source_index_files)
173+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use std::collections::HashMap;
2+
3+
use serde::Deserialize;
4+
5+
#[derive(Clone, Debug, Deserialize)]
6+
pub struct ChartSourceMetadata {
7+
pub entries: HashMap<String, Vec<ChartSourceEntry>>,
8+
}
9+
10+
#[derive(Clone, Debug, Deserialize)]
11+
pub struct ChartSourceEntry {
12+
pub name: String,
13+
pub version: String,
14+
}

rust/stackable-cockpit/src/utils/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod chartsource;
12
pub mod check;
23
pub mod k8s;
34
pub mod params;

0 commit comments

Comments
 (0)