diff --git a/src/api/mod.rs b/src/api/mod.rs index 93c0e0f2b0..f8a69451b9 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -469,7 +469,7 @@ impl<'a> AuthenticatedApi<'a> { } /// Convenience method to call self.api.request. - fn request(&self, method: Method, url: &str) -> ApiResult { + pub fn request(&self, method: Method, url: &str) -> ApiResult { self.api.request(method, url, None) } @@ -1899,6 +1899,15 @@ impl ApiRequest { } } +impl fmt::Display for ApiResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.body { + Some(ref body) => write!(f, "{}", String::from_utf8_lossy(body)), + None => Ok(()), + } + } +} + impl ApiResponse { /// Returns the status code of the response pub fn status(&self) -> u32 { diff --git a/src/commands/api.rs b/src/commands/api.rs new file mode 100644 index 0000000000..b0b3175ffc --- /dev/null +++ b/src/commands/api.rs @@ -0,0 +1,55 @@ +use anyhow::Result; +use clap::{Arg, ArgAction, ArgMatches, Command, ValueHint}; + +use crate::api::{Api, Method}; + +pub fn make_command(command: Command) -> Command { + command + .about("Make a raw API request to the Sentry API.") + .arg( + Arg::new("endpoint") + .value_name("ENDPOINT") + .required(true) + .value_hint(ValueHint::Url) + .help( + "The API endpoint to request (e.g., 'organizations/' or '/projects/my-org/my-project/releases/').{n}\ + The endpoint will be prefixed with '/api/0/' automatically.", + ), + ) + .arg( + Arg::new("method") + .short('m') + .long("method") + .value_name("METHOD") + .value_parser(["GET", "POST", "PUT", "DELETE"]) + .default_value("GET") + .action(ArgAction::Set) + .help("The HTTP method to use for the request."), + ) +} + +pub fn execute(matches: &ArgMatches) -> Result<()> { + let endpoint = matches + .get_one::("endpoint") + .expect("endpoint is required"); + let method_str = matches + .get_one::("method") + .expect("method has a default value"); + + let method = match method_str.as_str() { + "GET" => Method::Get, + "POST" => Method::Post, + "PUT" => Method::Put, + "DELETE" => Method::Delete, + _ => unreachable!("Invalid method value"), + }; + + let api = Api::current(); + let authenticated_api = api.authenticated()?; + let resp = authenticated_api.request(method, endpoint)?.send()?; + + // Print the response body as-is to stdout + println!("{resp}"); + + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index d4f83c6fe7..001a073443 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -19,6 +19,7 @@ use crate::utils::system::{load_dotenv, print_error, set_panic_hook, QuietExit}; use crate::utils::update::run_sentrycli_update_nagger; use crate::utils::value_parsers::auth_token_parser; +mod api; mod bash_hook; mod build; mod dart_symbol_map; @@ -51,6 +52,7 @@ mod upload_proguard; macro_rules! each_subcommand { ($mac:ident) => { + $mac!(api); $mac!(bash_hook); $mac!(build); $mac!(debug_files); diff --git a/tests/integration/_cases/api/api-get-with-leading-slash.trycmd b/tests/integration/_cases/api/api-get-with-leading-slash.trycmd new file mode 100644 index 0000000000..c9ff14a551 --- /dev/null +++ b/tests/integration/_cases/api/api-get-with-leading-slash.trycmd @@ -0,0 +1,17 @@ +``` +$ sentry-cli api /organizations/ +? success +[ + { + "id": "1", + "slug": "sentry", + "name": "Sentry", + "dateCreated": "2014-12-15T12:00:00.000Z", + "isEarlyAdopter": true, + "require2FA": false, + "requireEmailVerification": false, + "features": [] + } +] + +``` diff --git a/tests/integration/_cases/api/api-get.trycmd b/tests/integration/_cases/api/api-get.trycmd new file mode 100644 index 0000000000..de0279f7cd --- /dev/null +++ b/tests/integration/_cases/api/api-get.trycmd @@ -0,0 +1,17 @@ +``` +$ sentry-cli api organizations/ +? success +[ + { + "id": "1", + "slug": "sentry", + "name": "Sentry", + "dateCreated": "2014-12-15T12:00:00.000Z", + "isEarlyAdopter": true, + "require2FA": false, + "requireEmailVerification": false, + "features": [] + } +] + +``` diff --git a/tests/integration/_cases/api/api-help.trycmd b/tests/integration/_cases/api/api-help.trycmd new file mode 100644 index 0000000000..7ad1241761 --- /dev/null +++ b/tests/integration/_cases/api/api-help.trycmd @@ -0,0 +1,24 @@ +``` +$ sentry-cli api --help +? success +Make a raw API request to the Sentry API. + +Usage: sentry-cli[EXE] api [OPTIONS] + +Arguments: + + The API endpoint to request (e.g., 'organizations/' or + '/projects/my-org/my-project/releases/'). + The endpoint will be prefixed with '/api/0/' automatically. + +Options: + -m, --method + The HTTP method to use for the request. + + [default: GET] + [possible values: GET, POST, PUT, DELETE] + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/tests/integration/_cases/api/api-post.trycmd b/tests/integration/_cases/api/api-post.trycmd new file mode 100644 index 0000000000..1fb25d0ce4 --- /dev/null +++ b/tests/integration/_cases/api/api-post.trycmd @@ -0,0 +1,14 @@ +``` +$ sentry-cli api --method POST organizations/wat-org/releases/ +? success +{ + "version": "1.0.0", + "url": null, + "dateCreated": "2024-01-15T12:00:00.000Z", + "dateReleased": null, + "lastEvent": null, + "newGroups": 0, + "projects": [] +} + +``` diff --git a/tests/integration/_cases/help/help.trycmd b/tests/integration/_cases/help/help.trycmd index a7fcba86c7..90d2a0ebaf 100644 --- a/tests/integration/_cases/help/help.trycmd +++ b/tests/integration/_cases/help/help.trycmd @@ -11,6 +11,7 @@ Usage: sentry-cli[EXE] [OPTIONS] Commands: completions Generate completions for the specified shell. + api Make a raw API request to the Sentry API. debug-files Locate, analyze or upload debug information files. [aliases: dif] deploys Manage deployments for Sentry releases. events Manage events on Sentry. diff --git a/tests/integration/_responses/api/get-organizations.json b/tests/integration/_responses/api/get-organizations.json new file mode 100644 index 0000000000..5acdd118a0 --- /dev/null +++ b/tests/integration/_responses/api/get-organizations.json @@ -0,0 +1,12 @@ +[ + { + "id": "1", + "slug": "sentry", + "name": "Sentry", + "dateCreated": "2014-12-15T12:00:00.000Z", + "isEarlyAdopter": true, + "require2FA": false, + "requireEmailVerification": false, + "features": [] + } +] diff --git a/tests/integration/_responses/api/post-releases.json b/tests/integration/_responses/api/post-releases.json new file mode 100644 index 0000000000..b80b6177a0 --- /dev/null +++ b/tests/integration/_responses/api/post-releases.json @@ -0,0 +1,9 @@ +{ + "version": "1.0.0", + "url": null, + "dateCreated": "2024-01-15T12:00:00.000Z", + "dateReleased": null, + "lastEvent": null, + "newGroups": 0, + "projects": [] +}