Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ sqlx = { version = "0.8", features = ["runtime-tokio", "tls-native-tls", "uuid",
tokio = { version = "1.43.1", features = ["sync"] }
urlencoding = "2"
packageurl = "0.4.2"
rand = "0.8"

[features]
default = ["postgres"]
Expand Down
7 changes: 5 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ async fn main() -> Result<(), anyhow::Error> {
.register_transaction(tx!(list_sboms_paginated))
.register_transaction(tx!(get_analysis_status))
.register_transaction(tx!(get_analysis_latest_cpe))
.register_transaction(tx!(list_advisory_labels));
.register_transaction(tx!(list_advisory_labels))
.register_transaction(tx!(patch_advisory_labels))
.register_transaction(tx!(put_advisory_labels));

tx!(s.get_sbom?(scenario.get_sbom.clone()));
tx!(s.get_sbom_advisories?(scenario.get_sbom_advisories.clone()));
Expand Down Expand Up @@ -224,7 +226,8 @@ async fn main() -> Result<(), anyhow::Error> {
tx!(s.delete_sbom_from_pool_sequential?(
scenario.delete_sbom_pool.clone(),
delete_counter.clone()
), name: format!("delete_sbom_from_pool_sequential[{} SBOMs]", pool.len()))
),
name: format!("delete_sbom_from_pool_sequential[{} SBOMs]", pool.len()))
}
s
})
Expand Down
135 changes: 134 additions & 1 deletion src/restapi.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
use crate::utils::DisplayVec;
use goose::goose::{GooseUser, TransactionResult};
use goose::goose::{GooseMethod, GooseRequest, GooseUser, TransactionError, TransactionResult};
use reqwest::{Client, RequestBuilder};

use rand::Rng;
use serde_json::json;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use tokio::sync::OnceCell;
use urlencoding::encode;

// Static variable to store advisory total count
static ADVISORY_TOTAL: OnceCell<u64> = OnceCell::const_new();

pub async fn get_advisory(id: String, user: &mut GooseUser) -> TransactionResult {
let uri = format!("/api/v2/advisory/{}", encode(&format!("urn:uuid:{}", id)));

Expand Down Expand Up @@ -44,6 +51,46 @@ pub async fn list_advisory(user: &mut GooseUser) -> TransactionResult {
Ok(())
}

/// Get advisory total count and store it in static OnceCell
async fn get_advisory_total(user: &mut GooseUser) -> Result<u64, Box<TransactionError>> {
let response = user.get("/api/v2/advisory").await?;
let json_data = response.response?.json::<serde_json::Value>().await?;

// Extract total from the response
if let Some(total) = json_data.get("total").and_then(|t| t.as_u64()) {
log::info!("Advisory total count: {}", total);
return Ok(total);
}

Err(Box::new(TransactionError::Custom(
"Failed to get advisory total count".to_string(),
)))
}

/// Get cached advisory total count, fetch if not available using get_or_init
async fn get_cached_advisory_total(user: &mut GooseUser) -> Result<u64, Box<TransactionError>> {
// Try to get from cache first
if let Some(&total) = ADVISORY_TOTAL.get() {
return Ok(total);
}

// If not cached, fetch it and handle errors properly
match get_advisory_total(user).await {
Ok(total) => {
// Store in cache for future use
let _ = ADVISORY_TOTAL.set(total);
Ok(total)
}
Err(e) => {
// Propagate the error with context instead of silently returning 0
Err(Box::new(TransactionError::Custom(format!(
"Failed to get advisory total count: {}",
e
))))
}
}
}

pub async fn list_advisory_paginated(user: &mut GooseUser) -> TransactionResult {
let _response = user.get("/api/v2/advisory?offset=100&limit=10").await?;

Expand All @@ -67,6 +114,92 @@ pub async fn search_advisory(user: &mut GooseUser) -> TransactionResult {
Ok(())
}

/// List advisory with random offset and limit=1, return advisory ID
async fn list_advisory_random_single(
user: &mut GooseUser,
) -> Result<String, Box<TransactionError>> {
let total = get_cached_advisory_total(user).await?;
// Generate random offset
let offset = rand::thread_rng().gen_range(0..=total);
let url = format!("/api/v2/advisory?offset={}&limit=1", offset);

let response = user.get(&url).await?;
let json_data = response.response?.json::<serde_json::Value>().await?;

// Extract advisory ID from the response
if let Some(items) = json_data.get("items").and_then(|i| i.as_array()) {
if let Some(first_item) = items.first() {
if let Some(id) = first_item.get("uuid").and_then(|u| u.as_str()) {
log::info!("Listing advisory with offset {}: {}", offset, id);
return Ok(id.to_string());
}
}
}

// Return error if no advisory found
Err(Box::new(TransactionError::Custom(format!(
"No advisory found at offset: {}",
offset
))))
}

//

/// Send Advisory labels request
async fn send_advisory_label_request(
advisory_id: String,
user: &mut GooseUser,
method: GooseMethod,
source: &str,
client_method: fn(&Client, String) -> RequestBuilder,
) -> TransactionResult {
let path = format!("/api/v2/advisory/{}/label", advisory_id);
let json = json!({
"source": source,
"foo": "bar",
"space": "with space",
"empty": "",
});

let url = user.build_url(&path)?;

let reqwest_request_builder = client_method(&user.client, url);
let goose_request = GooseRequest::builder()
.method(method)
.path(path.as_str())
.set_request_builder(reqwest_request_builder.json(&json))
.build();
let _response = user.request(goose_request).await?;

Ok(())
}

/// Send Advisory labels request using PUT method
pub async fn put_advisory_labels(user: &mut GooseUser) -> TransactionResult {
let advisory_id = list_advisory_random_single(user).await?;
send_advisory_label_request(
advisory_id,
user,
GooseMethod::Put,
"It's a put request",
Client::put,
)
.await
}

/// Send Advisory labels request using PATCH method
pub async fn patch_advisory_labels(user: &mut GooseUser) -> TransactionResult {
let advisory_id = list_advisory_random_single(user).await?;
send_advisory_label_request(
advisory_id,
user,
GooseMethod::Patch,
"It's a patch request",
Client::patch,
)
.await
}

pub async fn list_importer(user: &mut GooseUser) -> TransactionResult {
let _response = user.get("/api/v2/importer").await?;

Expand Down