From 263733548e235aacfd00cf7eed2dda5b02e0a317 Mon Sep 17 00:00:00 2001 From: Patrick Kelly Date: Wed, 6 Nov 2024 12:13:25 -0500 Subject: [PATCH 1/5] sdk config endpoints --- Cargo.lock | 6 +- core/Cargo.toml | 2 +- core/src/cli.rs | 198 ++++++++++++++++++++++++++++++++-------- core/src/cmds/apis.rs | 26 +++--- core/src/cmds/config.rs | 119 ++++++++++++++++++++++++ core/src/cmds/docs.rs | 8 +- core/src/cmds/mod.rs | 1 + core/src/cmds/sdk.rs | 125 +++++++++++++++++-------- core/src/config.rs | 17 +--- docs/CLI.md | 73 ++++++++++++--- sideko-py/Cargo.toml | 2 +- sideko-py/src/lib.rs | 14 +-- 12 files changed, 468 insertions(+), 123 deletions(-) create mode 100644 core/src/cmds/config.rs diff --git a/Cargo.lock b/Cargo.lock index 8f19c63..2298df1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -2057,9 +2057,9 @@ dependencies = [ [[package]] name = "sideko_rest_api" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd5aeb79769aff9f38bd22cce22851918e3efc03c8b35a8d0d6f0e68eb6a122" +checksum = "1d25bb3da842ce6e203ea03e7e37d361bb3914468cedbb59af6eed46b4b83e70" dependencies = [ "bytes", "http 1.1.0", diff --git a/core/Cargo.toml b/core/Cargo.toml index 5e17aae..1930cd9 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -40,7 +40,7 @@ uuid = "1.8.0" prettytable = "0.10.0" semver = "1.0.23" clap-markdown = "0.1.4" -sideko_rest_api = "0.2.0" +sideko_rest_api = "0.3.2" spinners = "4.1.1" walkdir = "2.5.0" tempfile = "3.12.0" diff --git a/core/src/cli.rs b/core/src/cli.rs index 66abe29..e5fe574 100644 --- a/core/src/cli.rs +++ b/core/src/cli.rs @@ -2,16 +2,18 @@ use crate::{ cmds::{ self, apis::data_list_versions, + config as SdkConfig, sdk::{load_openapi, OpenApiSource}, }, config, result::{self}, styles, utils, }; +use camino::Utf8PathBuf; use clap::{Parser, Subcommand, ValueEnum}; use clap_markdown::MarkdownOptions; use semver::Version; -use sideko_rest_api::models::{ApiSpec, NewApiSpec}; +use sideko_rest_api::models::{ApiSpec, ApiVersion, NewApiSpec, VersionTypeEnum}; use std::{path::PathBuf, str::FromStr}; @@ -73,6 +75,16 @@ pub enum SemverIncrement { Patch, } +impl std::fmt::Display for SemverIncrement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SemverIncrement::Major => write!(f, "major"), + SemverIncrement::Minor => write!(f, "minor"), + SemverIncrement::Patch => write!(f, "patch"), + } + } +} + #[derive(Debug, Clone)] pub enum SemverOrIncrement { Increment(SemverIncrement), @@ -95,52 +107,61 @@ impl FromStr for SemverOrIncrement { } } +impl std::fmt::Display for SemverOrIncrement { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SemverOrIncrement::Increment(increment) => write!(f, "{}", increment), + SemverOrIncrement::Semver(version) => write!(f, "{}", version), + } + } +} + #[derive(Debug, Clone)] pub struct GenerationLanguageClap { - inner: sideko_rest_api::models::GenerationLanguageEnum, + inner: sideko_rest_api::models::SdkLanguageEnum, } impl ValueEnum for GenerationLanguageClap { fn value_variants<'a>() -> &'a [Self] { &[ GenerationLanguageClap { - inner: sideko_rest_api::models::GenerationLanguageEnum::Go, + inner: sideko_rest_api::models::SdkLanguageEnum::Go, }, GenerationLanguageClap { - inner: sideko_rest_api::models::GenerationLanguageEnum::Ruby, + inner: sideko_rest_api::models::SdkLanguageEnum::Ruby, }, GenerationLanguageClap { - inner: sideko_rest_api::models::GenerationLanguageEnum::Rust, + inner: sideko_rest_api::models::SdkLanguageEnum::Rust, }, GenerationLanguageClap { - inner: sideko_rest_api::models::GenerationLanguageEnum::Typescript, + inner: sideko_rest_api::models::SdkLanguageEnum::Typescript, }, GenerationLanguageClap { - inner: sideko_rest_api::models::GenerationLanguageEnum::Python, + inner: sideko_rest_api::models::SdkLanguageEnum::Python, }, GenerationLanguageClap { - inner: sideko_rest_api::models::GenerationLanguageEnum::Java, + inner: sideko_rest_api::models::SdkLanguageEnum::Java, }, ] } fn to_possible_value(&self) -> Option { match &self.inner { - sideko_rest_api::models::GenerationLanguageEnum::Go => { + sideko_rest_api::models::SdkLanguageEnum::Go => { Some(clap::builder::PossibleValue::new("go")) } - sideko_rest_api::models::GenerationLanguageEnum::Ruby => { + sideko_rest_api::models::SdkLanguageEnum::Ruby => { Some(clap::builder::PossibleValue::new("ruby")) } - sideko_rest_api::models::GenerationLanguageEnum::Rust => { + sideko_rest_api::models::SdkLanguageEnum::Rust => { Some(clap::builder::PossibleValue::new("rust")) } - sideko_rest_api::models::GenerationLanguageEnum::Typescript => { + sideko_rest_api::models::SdkLanguageEnum::Typescript => { Some(clap::builder::PossibleValue::new("typescript")) } - sideko_rest_api::models::GenerationLanguageEnum::Python => { + sideko_rest_api::models::SdkLanguageEnum::Python => { Some(clap::builder::PossibleValue::new("python")) } - sideko_rest_api::models::GenerationLanguageEnum::Java => { + sideko_rest_api::models::SdkLanguageEnum::Java => { Some(clap::builder::PossibleValue::new("java")) } } @@ -169,33 +190,46 @@ enum SdkCommands { /// Create a managed SDK that Sideko can track and maintain maintain. This command returns an SDK repo with git tracking Create { /// Name of the API Specification Collection - api: String, + config_path: Utf8PathBuf, /// Programming language to generate an SDK for language: GenerationLanguageClap, - /// The name of the repository - repo_name: String, - /// The semantic version to assign to the SDK - semver: String, - #[arg(long, short)] + #[arg(long)] + /// Optionally generate from a specific API version + api_version: Option, + #[arg(long)] + /// Optionally set an initial SDK semantic version + sdk_version: Option, + #[arg(long)] /// Output path of generated source files, default: ./ output: Option, }, /// **Enterprise Only!** /// Update a Sideko managed SDK. This command returns the git patch file to update your SDK to match an updated API Update { - // Path to the existing SDK - repo_path: PathBuf, - /// Name of the SDK. Use sdk list to see existing SDKs - sdk_name: String, - /// The semantic version to assign to this updated SDK - semver: String, + /// Path to the existing SDK + repo_path: Utf8PathBuf, + /// Name of the API Specification Collection + config_path: Utf8PathBuf, + /// The release type or semantic version to assign to the updated SDK + release_type_or_semver: SemverOrIncrement, + #[arg(long)] + /// Optional specific API version to generate from (default is latest non-rc semantic version) + api_version: Option, }, /// **Enterprise Only!** /// List all Sideko managed SDKs for an API Specification Collection List { + #[arg(long)] /// The name of the API in Sideko. e.g. my-rest-api - api_name: String, + api_name: Option, + #[arg(long)] + /// Only show successful SDK generations + successful_only: Option, }, + /// **Enterprise Only!** + /// Manage SDK Configurations specifications + #[command(subcommand)] + Config(SdkConfigCommands), } #[derive(Debug, Subcommand)] @@ -250,6 +284,25 @@ enum DocCommands { }, } +#[derive(Debug, Subcommand)] +enum SdkConfigCommands { + /// Initialize an SDK Configuration + Init { + /// Name of the API in Sideko. e.g. my-rest-api + api_name: String, + #[arg(long)] + /// Optionally specify a specific API version to intitialize the config with + api_version: Option, + }, + /// Sync an SDK Configuration file with the latest state of the API + Sync { + /// Path to the Sideko SDK Configuration File + config: Utf8PathBuf, + /// Optionally specify a specific API version to sync the config with + api_version: Option, + }, +} + pub async fn cli(args: Vec) -> result::Result<()> { let cli = Cli::parse_from(args); @@ -322,10 +375,10 @@ pub async fn cli(args: Vec) -> result::Result<()> { cmds::sdk::handle_try(¶ms).await } SdkCommands::Create { - api, + config_path, language, - repo_name, - semver, + api_version, + sdk_version, output, } => { // Set defaults @@ -337,15 +390,88 @@ pub async fn cli(args: Vec) -> result::Result<()> { result::Error::general("Failed determining cwd for --output default") })? }; - cmds::sdk::handle_create(&language.inner, api, repo_name, semver, &destination) - .await + + let api_version = match api_version { + Some(v) => { + if v == "latest" { + Some(ApiVersion::StrEnum(VersionTypeEnum::Latest)) + } else { + Some(ApiVersion::Str(v.clone())) + } + } + None => None, + }; + cmds::sdk::handle_create( + config_path, + &language.inner, + api_version.clone(), + sdk_version.clone(), + &destination, + ) + .await } - SdkCommands::List { api_name } => cmds::sdk::handle_list_sdks(api_name).await, + SdkCommands::List { + api_name, + successful_only, + } => cmds::sdk::handle_list_sdks(api_name.clone(), *successful_only).await, SdkCommands::Update { repo_path, - sdk_name, - semver, - } => cmds::sdk::handle_update(repo_path, sdk_name, semver).await, + release_type_or_semver, + api_version, + config_path, + } => { + let api_version = match api_version { + Some(v) => { + if v == "latest" { + Some(ApiVersion::StrEnum(VersionTypeEnum::Latest)) + } else { + Some(ApiVersion::Str(v.clone())) + } + } + None => None, + }; + cmds::sdk::handle_update( + repo_path, + config_path, + release_type_or_semver.clone(), + api_version.clone(), + ) + .await + } + SdkCommands::Config(sdk_config_commands) => match sdk_config_commands { + SdkConfigCommands::Init { + api_name, + api_version, + } => { + let api_version = match api_version { + Some(v) => { + if v == "latest" { + Some(ApiVersion::StrEnum(VersionTypeEnum::Latest)) + } else { + Some(ApiVersion::Str(v.clone())) + } + } + None => None, + }; + SdkConfig::init(api_name.clone(), api_version.clone()).await + } + SdkConfigCommands::Sync { + config, + api_version, + } => { + let api_version = match api_version { + Some(v) => { + if v == "latest" { + Some(ApiVersion::StrEnum(VersionTypeEnum::Latest)) + } else { + Some(ApiVersion::Str(v.clone())) + } + } + None => None, + }; + SdkConfig::sync(config, api_version.clone()).await + } + }, } } Commands::Login { output } => { diff --git a/core/src/cmds/apis.rs b/core/src/cmds/apis.rs index 6c1b6ae..30ef7f1 100644 --- a/core/src/cmds/apis.rs +++ b/core/src/cmds/apis.rs @@ -19,12 +19,16 @@ pub async fn data_get_api_project(id: String) -> Result { let client = SidekoClient::default() .with_base_url(&config::get_base_url()) .with_api_key_auth(&api_key); - client.api().get(GetRequest { id }).await.map_err(|e| { - Error::api_with_debug( - "Failed finding API with the given id. Re-run the command with -v to debug.", - &format!("{e}"), - ) - }) + client + .api() + .get(GetRequest { api_name: id }) + .await + .map_err(|e| { + Error::api_with_debug( + "Failed finding API with the given id. Re-run the command with -v to debug.", + &format!("{e}"), + ) + }) } pub async fn data_list_versions(name: String) -> Result> { @@ -34,7 +38,7 @@ pub async fn data_list_versions(name: String) -> Result> { .with_api_key_auth(&api_key); client .api().spec().list(ListRequest { - id: name, + api_name: name, }) .await .map_err(|e| { @@ -70,7 +74,7 @@ pub async fn handle_list_apis(name: &Option) -> Result<()> { }; for api_project in api_projects.clone().into_iter() { - let name = api_project.id; + let name = api_project.name; let mut table = Table::new(); table.set_format(*format::consts::FORMAT_BOX_CHARS); let versions = data_list_versions(name.clone()).await?; @@ -100,7 +104,7 @@ pub async fn create_new_api_project(params: &NewApiSpec, title: String) -> Resul let api_project = client .api() .create(CreateRequest { - data: NewApi { id: title }, + data: NewApi { name: title }, }) .await .map_err(|e| { @@ -113,7 +117,7 @@ pub async fn create_new_api_project(params: &NewApiSpec, title: String) -> Resul .api() .spec() .create(SpecCreate { - id: api_project.id.clone(), + api_name: api_project.name.clone(), data: params.clone(), }) .await @@ -144,7 +148,7 @@ pub async fn create_new_api_project_version(name: String, params: &NewApiSpec) - .api() .spec() .create(SpecCreate { - id: name, + api_name: name, data: params.clone(), }) .await diff --git a/core/src/cmds/config.rs b/core/src/cmds/config.rs new file mode 100644 index 0000000..57e5ea6 --- /dev/null +++ b/core/src/cmds/config.rs @@ -0,0 +1,119 @@ +use std::fs; + +use crate::result::{Error, Result}; +use camino::Utf8PathBuf; +use prettytable::{format, row, Table}; +use sideko_rest_api::{ + models::{ApiVersion, InitSdkConfig, SyncSdkConfig}, + resources::sdk::config::{init::InitRequest, sync::SyncRequest}, + Client, UploadFile, +}; + +use crate::config; + +pub async fn init(api_name: String, api_version: Option) -> Result<()> { + let api_key = config::get_api_key()?; + let client = Client::default() + .with_base_url(&config::get_base_url()) + .with_api_key_auth(&api_key); + + let config_response = client + .sdk() + .config() + .init() + .init(InitRequest { + data: InitSdkConfig { + api_name, + api_version, + }, + }) + .await + .map_err(|e| { + Error::api_with_debug( + "Failed to initialize SDK Config. Re-run the command with -v to debug.", + &format!("{e}"), + ) + })?; + + // Convert binary response to YAML + let yaml_str = String::from_utf8(config_response.content.to_vec()).map_err(|e| { + Error::general_with_debug( + "Failed to parse yaml config response as UTF-8", + &format!("{e}"), + ) + })?; + let truncated_yaml = match yaml_str.find("modules:") { + Some(index) => &yaml_str[..index], + None => &yaml_str, + }; + + // Save YAML to file + fs::write("sdk-config.yaml", &yaml_str).map_err(|e| { + Error::general_with_debug("Failed to write SDK config file", &format!("{e}")) + })?; + + // Create and print table + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_BOX_CHARS); + table.add_row(row!["Sideko SDK Configuration"]); + table.add_row(row![truncated_yaml]); + table.printstd(); + + Ok(()) +} + +pub async fn sync(config: &Utf8PathBuf, api_version: Option) -> Result<()> { + let api_key = config::get_api_key()?; + let client = Client::default() + .with_base_url(&config::get_base_url()) + .with_api_key_auth(&api_key); + + let config_response = client + .sdk() + .config() + .sync() + .sync(SyncRequest { + data: SyncSdkConfig { + api_version, + config: UploadFile::from_path(config.as_str()).unwrap(), + }, + }) + .await + .map_err(|e| { + Error::api_with_debug( + "Failed to sync SDK Config. Re-run the command with -v to debug.", + &format!("{e}"), + ) + })?; + + // Convert binary response to YAML + let yaml_str = String::from_utf8(config_response.content.to_vec()).map_err(|e| { + Error::general_with_debug( + "Failed to parse yaml config response as UTF-8", + &format!("{e}"), + ) + })?; + + // Truncate YAML for display + let truncated_yaml = match yaml_str.find("modules:") { + Some(index) => &yaml_str[..index], + None => &yaml_str, + }; + + // Overwrite the original config file + fs::write(config, &yaml_str).map_err(|e| { + Error::general_with_debug( + &format!("Failed to write synced config to {}", config), + &format!("{e}"), + ) + })?; + + // Create and print table + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_BOX_CHARS); + table.add_row(row!["New Sideko SDK Configuration"]); + table.add_row(row![truncated_yaml]); + table.printstd(); + + Ok(()) +} diff --git a/core/src/cmds/docs.rs b/core/src/cmds/docs.rs index 366320a..4873bc8 100644 --- a/core/src/cmds/docs.rs +++ b/core/src/cmds/docs.rs @@ -39,7 +39,7 @@ pub async fn handle_list_docs() -> Result<()> { table.add_row(row![b -> "Name", b -> "Preview URL", b -> "Production URL",]); for doc_project in &doc_projects { table.add_row(row![ - doc_project.title, + doc_project.id, doc_project.domains.preview.clone().unwrap_or_default(), doc_project .domains @@ -77,7 +77,7 @@ pub async fn handle_deploy_docs(name: &str, prod: bool, no_wait: bool) -> Result }; let deployment = client .trigger_deployment(TriggerDeploymentRequest { - project_id_or_name: name.to_string(), + doc_name: name.to_string(), data: NewDeployment { doc_version_id: None, // latest draft target, @@ -129,7 +129,7 @@ pub async fn handle_deploy_docs(name: &str, prod: bool, no_wait: bool) -> Result { if let Ok(doc_project) = client .get_doc_project(GetDocProjectRequest { - project_id_or_name: name.to_string(), + doc_name: name.to_string(), }) .await { @@ -170,7 +170,7 @@ async fn poll_deployment(deployment: &Deployment) -> Result { let d = client .get_deployment(GetDeploymentRequest { - project_id_or_name: deployment.doc_version.doc_project_id.clone(), + doc_name: deployment.doc_version.doc_project_id.clone(), deployment_id: deployment.id.clone(), }) .await diff --git a/core/src/cmds/mod.rs b/core/src/cmds/mod.rs index 53fe102..c4cc7df 100644 --- a/core/src/cmds/mod.rs +++ b/core/src/cmds/mod.rs @@ -1,4 +1,5 @@ pub mod apis; +pub mod config; pub mod docs; pub mod login; pub mod sdk; diff --git a/core/src/cmds/sdk.rs b/core/src/cmds/sdk.rs index 9dd4511..67c9748 100644 --- a/core/src/cmds/sdk.rs +++ b/core/src/cmds/sdk.rs @@ -1,9 +1,11 @@ use crate::{ + cli::SemverOrIncrement, config, result::{Error, Result}, utils::{self, check_for_updates}, }; use bytes::Bytes; +use camino::Utf8PathBuf; use flate2::{write::GzEncoder, Compression}; use tempfile::TempDir; @@ -13,12 +15,15 @@ use log::debug; use prettytable::{format, row, Table}; use serde::{Deserialize, Serialize}; use sideko_rest_api::{ - models::{self as sideko_schemas, File as SidekoFile, GenerationLanguageEnum}, - Client as SidekoClient, CreateSdkRequest, ListSdksRequest, StatelessGenerateSdkRequest, - UpdateSdkRequest, UploadFile, + models::{self as sideko_schemas, ApiVersion, NewSdk, SdkLanguageEnum, UpdateSdk}, + resources::{ + sdk::{update::UpdateRequest, GenerateRequest, ListRequest}, + stateless::generate_sdk::GenerateStatelessRequest, + }, + Client as SidekoClient, UploadFile, }; use std::{ - fs::{self, File}, + fs::{self, read_to_string, File}, io::Cursor, path::{Path, PathBuf}, process::Command, @@ -50,7 +55,7 @@ impl From<&String> for OpenApiSource { pub struct GenerateSdkParams { pub source: OpenApiSource, pub destination: PathBuf, - pub language: GenerationLanguageEnum, + pub language: SdkLanguageEnum, // options pub base_url: Option, pub package_name: Option, @@ -137,8 +142,10 @@ pub async fn handle_try(params: &GenerateSdkParams) -> Result<()> { .with_base_url(&config::get_base_url()) .with_api_key_auth(&api_key); let gen_response = client - .stateless_generate_sdk(StatelessGenerateSdkRequest { - data: sideko_schemas::StatelessGenerateSdk { + .stateless() + .generate_sdk() + .generate_stateless(GenerateStatelessRequest { + data: sideko_schemas::NewStatelessSdk { openapi, language: params.language.clone(), package_name: params.package_name.clone(), @@ -172,10 +179,10 @@ pub async fn handle_try(params: &GenerateSdkParams) -> Result<()> { } pub async fn handle_create( - language: &GenerationLanguageEnum, - api_id: &str, - repo_name: &str, - semver: &str, + config_path: &Utf8PathBuf, + language: &SdkLanguageEnum, + api_version: Option, + sdk_version: Option, destination: &PathBuf, ) -> Result<()> { check_for_updates().await?; @@ -197,13 +204,15 @@ pub async fn handle_create( let client = SidekoClient::default() .with_base_url(&config::get_base_url()) .with_api_key_auth(&api_key); + let gen_response = client - .create_sdk(CreateSdkRequest { - data: sideko_schemas::SdkProject { + .sdk() + .generate(GenerateRequest { + data: NewSdk { + config: UploadFile::from_path(config_path.as_str()).unwrap(), language: language.clone(), - api_id: api_id.to_string(), - name: repo_name.into(), - semver: semver.into(), + api_version, + sdk_version, }, }) .await @@ -228,7 +237,7 @@ pub async fn handle_create( Ok(()) } -pub async fn handle_list_sdks(name: &str) -> Result<()> { +pub async fn handle_list_sdks(api: Option, successful: Option) -> Result<()> { let api_key = config::get_api_key()?; let client = SidekoClient::default() .with_base_url(&config::get_base_url()) @@ -238,9 +247,8 @@ pub async fn handle_list_sdks(name: &str) -> Result<()> { table.set_format(*format::consts::FORMAT_BOX_CHARS); let sdks = client - .list_sdks(ListSdksRequest { - api_id: name.to_string(), - }) + .sdk() + .list(ListRequest { api, successful }) .await .map_err(|e| { Error::api_with_debug( @@ -248,23 +256,43 @@ pub async fn handle_list_sdks(name: &str) -> Result<()> { &format!("{e}"), ) })?; - log::info!("Listing SDKs for the {} API Project...", name); if sdks.is_empty() { table.add_row(row!["No sdks available"]); } else { table.add_row(row![b -> "Name" , b -> "Language", b -> "Semver"]); for sdk in sdks { - table.add_row(row![sdk.name, sdk.language, sdk.semver]); + table.add_row(row![sdk.name, sdk.language, sdk.version]); } } table.printstd(); Ok(()) } -pub async fn handle_update(repo_path: &Path, name: &str, semver: &str) -> Result<()> { - log::info!("Creating patch for the new version of {}", name); +#[derive(Deserialize, Serialize, Debug)] +struct SdkJson { + id: String, +} + +pub async fn handle_update( + repo_path: &Utf8PathBuf, + config_path: &Utf8PathBuf, + version_or_increment: SemverOrIncrement, + api_version: Option, +) -> Result<()> { + let status = Command::new("git") + .current_dir(repo_path) + .args(["status", "--porcelain"]) + .output() + .map_err(|e| Error::general_with_debug("Failed to check git status", &format!("{e}")))?; + if !status.stdout.is_empty() { + return Err(Error::general( + "Git working directory is not clean. Please commit or stash your changes before updating.", + )); + } + + log::info!("Updating SDK at {}", repo_path.as_str()); let api_key = config::get_api_key()?; let client = SidekoClient::default() .with_base_url(&config::get_base_url()) @@ -280,8 +308,8 @@ pub async fn handle_update(repo_path: &Path, name: &str, semver: &str) -> Result let git_path = repo_path.join(".git"); if !git_path.exists() { return Err(Error::general(&format!( - "{} is not a git repository", - repo_path.to_string_lossy() + "{} is not a git repository. Git history is required to perform updates", + repo_path.as_str() ))); } copy_dir_all(&git_path, temp_dir.path())?; @@ -304,7 +332,6 @@ pub async fn handle_update(repo_path: &Path, name: &str, semver: &str) -> Result let entry = entry.unwrap(); let path = entry.path(); let name = path.strip_prefix(temp_dir.path()).unwrap(); - if path.is_file() { let mut file = File::open(path).unwrap(); tar.append_file(name, &mut file).unwrap(); @@ -312,38 +339,62 @@ pub async fn handle_update(repo_path: &Path, name: &str, semver: &str) -> Result tar.append_dir(name, path).unwrap(); } } - tar.finish().unwrap(); let enc = tar.into_inner().unwrap(); // Finalize the gzip stream enc.finish().unwrap(); + let git_patch_tar_path = tar_gz_path.to_string_lossy().into_owned(); + // Read and parse the SDK JSON file + let sdk_json_content = read_to_string(repo_path.join(".sdk.json")).map_err(|_e| { + Error::general( + "Could not find .sdk.json file in repository path. Is this repo a Sideko SDK?", + ) + })?; + + let sdk_json: SdkJson = serde_json::from_str(&sdk_json_content).map_err(|e| { + Error::general_with_debug( + "Failed to parse .sdk.json file", + &format!("JSON parsing error: {}", e), + ) + })?; + // Send the request - let patch_response = client - .update_sdk(UpdateSdkRequest { - name: name.into(), - semver: semver.into(), - api_version: None, - data: SidekoFile { - file: UploadFile::from_path(&git_patch_tar_path).expect("not a git repository"), + let patch_content = client + .sdk() + .update() + .update(UpdateRequest { + data: UpdateSdk { + config: UploadFile::from_path(config_path.as_str()).unwrap(), + prev_sdk_git: UploadFile::from_path(&git_patch_tar_path).unwrap(), + prev_sdk_id: sdk_json.id, + sdk_version: version_or_increment.to_string(), + api_version, }, }) .await - .unwrap(); + .map_err(|e| { + Error::api_with_debug( + "Failed to create update patch for the SDK. Re-run the command with -v to debug.", + &format!("{e}"), + ) + })?; - let patch_content = patch_response.patch; if patch_content.is_empty() { log::warn!("No updates to apply"); return Ok(()); } + let file_path = repo_path.join("update.patch"); fs::write(&file_path, patch_content.as_bytes()).expect("could not write file"); + let output = Command::new("git") .current_dir(repo_path) .arg("apply") .arg("update.patch") .output() .expect("failed to execute process"); + if output.status.success() { log::info!("Patch applied successfully with git"); fs::remove_file(&file_path).expect("failed to delete patch file"); diff --git a/core/src/config.rs b/core/src/config.rs index f739833..e3acfe4 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -12,18 +12,11 @@ pub fn config_bufs(user_defined: Vec>) -> Vec { } }; - let home_config = { - if let Ok(home) = std::env::var("HOME") { - if let Ok(mut buf) = PathBuf::from_str(&home) { - buf.push(".sideko"); - Some(buf) - } else { - None - } - } else { - None - } - }; + let home_config = std::env::var("HOME").ok().map(|home| { + let mut buf = PathBuf::from_str(&home).unwrap(); + buf.push(".sideko"); + buf + }); let mut bufs = user_defined.clone(); bufs.extend([cwd_config, home_config]); diff --git a/docs/CLI.md b/docs/CLI.md index fa61a34..12fd1e4 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -47,6 +47,7 @@ Generate and configure SDK clients * `create` — **Enterprise Only!** Create a managed SDK that Sideko can track and maintain maintain. This command returns an SDK repo with git tracking * `update` — **Enterprise Only!** Update a Sideko managed SDK. This command returns the git patch file to update your SDK to match an updated API * `list` — **Enterprise Only!** List all Sideko managed SDKs for an API Specification Collection +* `config` — **Enterprise Only!** Manage SDK Configurations specifications @@ -76,21 +77,21 @@ Generate a point-in-time SDK (unmanaged/stateless). This command is available to **Enterprise Only!** Create a managed SDK that Sideko can track and maintain maintain. This command returns an SDK repo with git tracking -**Usage:** `sideko sdk create [OPTIONS] ` +**Usage:** `sideko sdk create [OPTIONS] ` ###### **Arguments:** -* `` — Name of the API Specification Collection +* `` — Name of the API Specification Collection * `` — Programming language to generate an SDK for Possible values: `go`, `ruby`, `rust`, `typescript`, `python`, `java` -* `` — The name of the repository -* `` — The semantic version to assign to the SDK ###### **Options:** -* `-o`, `--output ` — Output path of generated source files, default: ./ +* `--api-version ` — Optionally generate from a specific API version +* `--sdk-version ` — Optionally set an initial SDK semantic version +* `--output ` — Output path of generated source files, default: ./ @@ -98,13 +99,17 @@ Generate a point-in-time SDK (unmanaged/stateless). This command is available to **Enterprise Only!** Update a Sideko managed SDK. This command returns the git patch file to update your SDK to match an updated API -**Usage:** `sideko sdk update ` +**Usage:** `sideko sdk update [OPTIONS] ` ###### **Arguments:** -* `` -* `` — Name of the SDK. Use sdk list to see existing SDKs -* `` — The semantic version to assign to this updated SDK +* `` — Path to the existing SDK +* `` — Name of the API Specification Collection +* `` — The release type or semantic version to assign to the updated SDK + +###### **Options:** + +* `--api-version ` — Optional specific API version to generate from (default is latest non-rc semantic version) @@ -112,11 +117,57 @@ Generate a point-in-time SDK (unmanaged/stateless). This command is available to **Enterprise Only!** List all Sideko managed SDKs for an API Specification Collection -**Usage:** `sideko sdk list ` +**Usage:** `sideko sdk list [OPTIONS]` + +###### **Options:** + +* `--api-name ` — The name of the API in Sideko. e.g. my-rest-api +* `--successful-only ` — Only show successful SDK generations + + Possible values: `true`, `false` + + + + +## `sideko sdk config` + +**Enterprise Only!** Manage SDK Configurations specifications + +**Usage:** `sideko sdk config ` + +###### **Subcommands:** + +* `init` — Initialize an SDK Configuration +* `sync` — Sync an SDK Configuration file with the latest state of the API + + + +## `sideko sdk config init` + +Initialize an SDK Configuration + +**Usage:** `sideko sdk config init [OPTIONS] ` + +###### **Arguments:** + +* `` — Name of the API in Sideko. e.g. my-rest-api + +###### **Options:** + +* `--api-version ` — Optionally specify a specific API version to intitialize the config with + + + +## `sideko sdk config sync` + +Sync an SDK Configuration file with the latest state of the API + +**Usage:** `sideko sdk config sync [API_VERSION]` ###### **Arguments:** -* `` — The name of the API in Sideko. e.g. my-rest-api +* `` — Path to the Sideko SDK Configuration File +* `` — Optionally specify a specific API version to sync the config with diff --git a/sideko-py/Cargo.toml b/sideko-py/Cargo.toml index 29c5687..1611d4a 100644 --- a/sideko-py/Cargo.toml +++ b/sideko-py/Cargo.toml @@ -21,4 +21,4 @@ sideko = { path = "../core" } pyo3 = { version = "0.20.0", features = ["abi3-py38"] } tokio = "1.36.0" log = "0.4.20" -sideko_rest_api = "0.2.0" +sideko_rest_api = "0.3.2" diff --git a/sideko-py/src/lib.rs b/sideko-py/src/lib.rs index eba3039..c0ba9ee 100644 --- a/sideko-py/src/lib.rs +++ b/sideko-py/src/lib.rs @@ -5,7 +5,7 @@ use pyo3::{ pyclass, wrap_pyfunction, PyResult, }; use sideko::{cmds::sdk, config, utils}; -use sideko_rest_api::models::GenerationLanguageEnum; +use sideko_rest_api::models::SdkLanguageEnum; use std::path::PathBuf; #[pyclass] @@ -19,13 +19,13 @@ pub enum Language { } impl Language { - fn to_gen_lang(&self) -> GenerationLanguageEnum { + fn to_gen_lang(&self) -> SdkLanguageEnum { match self { - Language::Python => GenerationLanguageEnum::Python, - Language::Ruby => GenerationLanguageEnum::Ruby, - Language::Typescript => GenerationLanguageEnum::Typescript, - Language::Rust => GenerationLanguageEnum::Rust, - Language::Go => GenerationLanguageEnum::Go, + Language::Python => SdkLanguageEnum::Python, + Language::Ruby => SdkLanguageEnum::Ruby, + Language::Typescript => SdkLanguageEnum::Typescript, + Language::Rust => SdkLanguageEnum::Rust, + Language::Go => SdkLanguageEnum::Go, } } } From 3eaef9025642f5f6c79aaad6f02f9526b9bb3f75 Mon Sep 17 00:00:00 2001 From: Patrick Kelly Date: Wed, 6 Nov 2024 12:38:41 -0500 Subject: [PATCH 2/5] bump version --- Cargo.lock | 4 ++-- core/Cargo.toml | 2 +- sideko-py/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2298df1..51497a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2013,7 +2013,7 @@ dependencies = [ [[package]] name = "sideko" -version = "0.9.1" +version = "0.10.0" dependencies = [ "anstyle", "bytes", @@ -2046,7 +2046,7 @@ dependencies = [ [[package]] name = "sideko-py" -version = "0.9.1" +version = "0.10.0" dependencies = [ "log", "pyo3", diff --git a/core/Cargo.toml b/core/Cargo.toml index 1930cd9..81ea67d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sideko" -version = "0.9.1" +version = "0.10.0" edition = "2021" authors = [ "Elias Posen ", diff --git a/sideko-py/Cargo.toml b/sideko-py/Cargo.toml index 1611d4a..7823e9d 100644 --- a/sideko-py/Cargo.toml +++ b/sideko-py/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sideko-py" -version = "0.9.1" +version = "0.10.0" edition = "2021" authors = [ "Elias Posen ", From 6501d4a32a654276e51648971578b686db3488b9 Mon Sep 17 00:00:00 2001 From: Patrick Kelly Date: Wed, 6 Nov 2024 13:18:11 -0500 Subject: [PATCH 3/5] naming consistency --- core/src/cli.rs | 6 +++--- docs/CLI.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/cli.rs b/core/src/cli.rs index e5fe574..27d509f 100644 --- a/core/src/cli.rs +++ b/core/src/cli.rs @@ -297,7 +297,7 @@ enum SdkConfigCommands { /// Sync an SDK Configuration file with the latest state of the API Sync { /// Path to the Sideko SDK Configuration File - config: Utf8PathBuf, + config_path: Utf8PathBuf, /// Optionally specify a specific API version to sync the config with api_version: Option, }, @@ -456,7 +456,7 @@ pub async fn cli(args: Vec) -> result::Result<()> { SdkConfig::init(api_name.clone(), api_version.clone()).await } SdkConfigCommands::Sync { - config, + config_path, api_version, } => { let api_version = match api_version { @@ -469,7 +469,7 @@ pub async fn cli(args: Vec) -> result::Result<()> { } None => None, }; - SdkConfig::sync(config, api_version.clone()).await + SdkConfig::sync(config_path, api_version.clone()).await } }, } diff --git a/docs/CLI.md b/docs/CLI.md index 12fd1e4..efff687 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -162,11 +162,11 @@ Initialize an SDK Configuration Sync an SDK Configuration file with the latest state of the API -**Usage:** `sideko sdk config sync [API_VERSION]` +**Usage:** `sideko sdk config sync [API_VERSION]` ###### **Arguments:** -* `` — Path to the Sideko SDK Configuration File +* `` — Path to the Sideko SDK Configuration File * `` — Optionally specify a specific API version to sync the config with From 81a35e46057613fb4efc7e3bfd2597b8e0ab23ef Mon Sep 17 00:00:00 2001 From: Patrick Kelly Date: Wed, 6 Nov 2024 13:48:20 -0500 Subject: [PATCH 4/5] logging --- core/src/cmds/apis.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/cmds/apis.rs b/core/src/cmds/apis.rs index 30ef7f1..9ce6808 100644 --- a/core/src/cmds/apis.rs +++ b/core/src/cmds/apis.rs @@ -159,7 +159,7 @@ pub async fn create_new_api_project_version(name: String, params: &NewApiSpec) - ) })?; log::info!( - "Updated API Spec Collection with new spec: {} ", + "Updated API with new specification as version: {} ", new_version.version ); From 95eaf04303228341676f266a6e66486525f98f82 Mon Sep 17 00:00:00 2001 From: Patrick Kelly Date: Wed, 6 Nov 2024 13:56:56 -0500 Subject: [PATCH 5/5] docs comment update --- core/src/cli.rs | 2 +- docs/CLI.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/cli.rs b/core/src/cli.rs index 27d509f..98913d7 100644 --- a/core/src/cli.rs +++ b/core/src/cli.rs @@ -189,7 +189,7 @@ enum SdkCommands { /// **Enterprise Only!** /// Create a managed SDK that Sideko can track and maintain maintain. This command returns an SDK repo with git tracking Create { - /// Name of the API Specification Collection + /// Path to the Sideko SDK Configuration File config_path: Utf8PathBuf, /// Programming language to generate an SDK for language: GenerationLanguageClap, diff --git a/docs/CLI.md b/docs/CLI.md index efff687..da53863 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -81,7 +81,7 @@ Generate a point-in-time SDK (unmanaged/stateless). This command is available to ###### **Arguments:** -* `` — Name of the API Specification Collection +* `` — Path to the Sideko SDK Configuration File * `` — Programming language to generate an SDK for Possible values: `go`, `ruby`, `rust`, `typescript`, `python`, `java`