From 77b242fe9225529c67690087a5a9cc651a3df170 Mon Sep 17 00:00:00 2001 From: pei-mysten <147538877+pei-mysten@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:59:28 -0700 Subject: [PATCH] [suiop][image] add list and get current status endpoint (#18373) ## Description Adding two endpoints for improving image building process visibility: 1. list: show all the images in the AR 2. status: check whether the image is built and stored in AR, if not in AR, show real-time status of the building process ## Test plan list image status ``` suiop ci image status --repo-name infra-metadata-service --image-name go-executor Requested status for repo: infra-metadata-service, image: go-executor, ref: branch:main Image Status: Found Image SHA: 1d01c74ca5cfb3c7103 ``` ``` suiop ci image status --repo-name infra-metadata-service --image-name go-executor --ref-type=commit --ref-val=123 Requested status for repo: infra-metadata-service, image: go-executor, ref: commit:123 Image Status: Not built and image not found Image SHA: 123 ``` How did you test the new or updated feature? --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: --- Cargo.lock | 2 +- crates/suiop-cli/Cargo.toml | 2 +- crates/suiop-cli/src/cli/ci/image.rs | 202 ++++++++++++++++++++++++++- 3 files changed, 201 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8409b7fd5d40..d03c25f0532a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14421,7 +14421,7 @@ dependencies = [ [[package]] name = "suiop-cli" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "axum", diff --git a/crates/suiop-cli/Cargo.toml b/crates/suiop-cli/Cargo.toml index af23543f755ce..472321273efae 100644 --- a/crates/suiop-cli/Cargo.toml +++ b/crates/suiop-cli/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" license = "Apache-2.0" name = "suiop-cli" publish = false -version = "0.2.3" +version = "0.2.4" [lib] name = "suioplib" diff --git a/crates/suiop-cli/src/cli/ci/image.rs b/crates/suiop-cli/src/cli/ci/image.rs index 14a69024cc6ed..d1c8b1598b1c8 100644 --- a/crates/suiop-cli/src/cli/ci/image.rs +++ b/crates/suiop-cli/src/cli/ci/image.rs @@ -7,7 +7,7 @@ use anyhow::Result; use chrono::{DateTime, Local, Utc}; use clap::{Parser, ValueEnum}; use colored::Colorize; -use serde::{self, Serialize}; +use serde::{self, Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; use tabled::{settings::Style, Table, Tabled}; use tracing::debug; @@ -89,6 +89,26 @@ pub enum ImageAction { #[arg(short, long)] limit: Option, }, + #[command(name = "status")] + Status { + #[arg(short = 'r', long)] + repo_name: String, + #[arg(short = 'i', long)] + image_name: String, + #[arg(short = 't', long)] + ref_type: Option, + #[arg(short = 'v', long)] + ref_val: Option, + }, + #[command(name = "list")] + List { + #[arg(short, long)] + repo_name: String, + #[arg(short, long)] + image_name: Option, + #[arg(short, long)] + limit: Option, + }, } #[derive(serde::Serialize, Debug)] @@ -107,7 +127,17 @@ struct QueryBuildsRequest { limit: u32, } +#[derive(serde::Serialize)] +struct ImageStatusRequest { + repo_name: String, + image_name: String, + repo_ref_type: RefType, + repo_ref: String, +} + const ENDPOINT: &str = "/automation/image-build"; +const STATUS_ENDPOINT: &str = "/automation/image-status"; +const LIST_ENDPOINT: &str = "/automation/images"; pub async fn image_cmd(args: &ImageArgs) -> Result<()> { let token = get_oauth_token().await?; @@ -130,6 +160,85 @@ struct QueryBuildResponse { pods: Vec, } +#[derive(ValueEnum, Clone, Debug)] +// #[clap(rename_all = "snake_case")] +enum ImageStatus { + Found, + Pending, + Building, + BuiltNotFound, + Failed, + Unknown, + NotBuiltNotFound, +} + +impl<'a> Deserialize<'a> for ImageStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + let s = i32::deserialize(deserializer)?; + match s { + 0 => Ok(ImageStatus::Found), + 1 => Ok(ImageStatus::Pending), + 2 => Ok(ImageStatus::Building), + 3 => Ok(ImageStatus::BuiltNotFound), + 4 => Ok(ImageStatus::Failed), + 5 => Ok(ImageStatus::Unknown), + 6 => Ok(ImageStatus::NotBuiltNotFound), + _ => Ok(ImageStatus::Unknown), + } + } +} + +impl Display for ImageStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ImageStatus::Found => write!(f, "Found"), + ImageStatus::Pending => write!(f, "Pending"), + ImageStatus::Building => write!(f, "Building"), + ImageStatus::BuiltNotFound => { + write!(f, "Build succeed but image not found") + } + ImageStatus::Failed => write!(f, "Failed"), + ImageStatus::Unknown => write!(f, "Unknown"), + ImageStatus::NotBuiltNotFound => { + write!(f, "Not built and image not found") + } + } + } +} + +#[derive(serde::Deserialize)] +struct ImageStatusResponse { + pub status: ImageStatus, + pub image_sha: String, +} + +#[derive(serde::Serialize)] +struct ImageListRequest { + repo_name: String, + image_name: Option, + limit: Option, +} + +#[derive(serde::Deserialize)] +struct ImageDetails { + pub name: String, + pub tags: Vec, +} + +#[derive(Tabled)] +struct ImageRow { + name: String, + tags: String, +} + +#[derive(serde::Deserialize)] +struct ImageListResponse { + pub images: Vec, +} + async fn send_image_request(token: &str, action: &ImageAction) -> Result<()> { let req = generate_image_request(token, action); @@ -193,6 +302,57 @@ async fn send_image_request(token: &str, action: &ImageAction) -> Result<()> { let mut tabled = Table::new(job_statuses); tabled.with(Style::rounded()); + let tabled_str = tabled.to_string(); + println!("{}", tabled_str); + } + ImageAction::Status { + repo_name, + image_name, + ref_type, + ref_val, + } => { + let mut ref_name = "".to_string(); + if let Some(ref_type) = ref_type { + ref_name.push_str(&ref_type.to_string()) + } else { + ref_name.push_str("branch"); + } + if let Some(ref_val) = ref_val { + ref_name.push_str(&format!(":{}", ref_val)) + } else { + ref_name.push_str(":main") + } + println!( + "Requested status for repo: {}, image: {}, ref: {}", + repo_name.green(), + image_name.green(), + ref_name.green() + ); + // println!("resp: {:?}", resp.text().await?); + + let json_resp = resp.json::().await?; + println!("Image Status: {}", json_resp.status.to_string().green()); + println!("Image SHA: {}", json_resp.image_sha.green()); + } + ImageAction::List { + repo_name, + image_name: _, + limit: _, + } => { + println!("Requested list for repo: {}", repo_name.green()); + let json_resp = resp.json::().await?; + let details = json_resp.images.into_iter().map(|image| { + let image_name = image.name; + let image_tags = image.tags; + ImageRow { + name: image_name, + // convert images tags vec to multiple strings + tags: image_tags.join(" | "), + } + }); + let mut tabled = Table::new(details); + tabled.with(Style::rounded()); + let tabled_str = tabled.to_string(); println!("{}", tabled_str); } @@ -233,8 +393,6 @@ fn generate_headers_with_auth(token: &str) -> reqwest::header::HeaderMap { fn generate_image_request(token: &str, action: &ImageAction) -> reqwest::RequestBuilder { let client = reqwest::Client::new(); let api_server = get_api_server(); - let full_url = format!("{}{}", api_server, ENDPOINT); - debug!("full_url: {}", full_url); let req = match action { ImageAction::Build { repo_name, @@ -244,6 +402,8 @@ fn generate_image_request(token: &str, action: &ImageAction) -> reqwest::Request ref_type, ref_val, } => { + let full_url = format!("{}{}", api_server, ENDPOINT); + debug!("full_url: {}", full_url); let req = client.post(full_url); let body = RequestBuildRequest { repo_name: repo_name.clone(), @@ -257,6 +417,8 @@ fn generate_image_request(token: &str, action: &ImageAction) -> reqwest::Request req.json(&body).headers(generate_headers_with_auth(token)) } ImageAction::Query { repo_name, limit } => { + let full_url = format!("{}{}", api_server, ENDPOINT); + debug!("full_url: {}", full_url); let req = client.get(full_url); let limit = (*limit).unwrap_or(10); let query = QueryBuildsRequest { @@ -265,6 +427,40 @@ fn generate_image_request(token: &str, action: &ImageAction) -> reqwest::Request }; req.query(&query).headers(generate_headers_with_auth(token)) } + ImageAction::Status { + repo_name, + image_name, + ref_type, + ref_val, + } => { + let full_url = format!("{}{}", api_server, STATUS_ENDPOINT); + debug!("full_url: {}", full_url); + let req = client.get(full_url); + let ref_type = ref_type.clone().unwrap_or(RefType::Branch); + let ref_val = ref_val.clone().unwrap_or("main".to_string()); + let query = ImageStatusRequest { + repo_name: repo_name.clone(), + image_name: image_name.clone(), + repo_ref_type: ref_type, + repo_ref: ref_val, + }; + req.query(&query).headers(generate_headers_with_auth(token)) + } + ImageAction::List { + repo_name, + image_name, + limit, + } => { + let full_url = format!("{}{}", api_server, LIST_ENDPOINT); + debug!("full_url: {}", full_url); + let req = client.get(full_url); + let query = ImageListRequest { + repo_name: repo_name.clone(), + image_name: image_name.clone(), + limit: *limit, + }; + req.query(&query).headers(generate_headers_with_auth(token)) + } }; debug!("req: {:?}", req);