Skip to content
Draft
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
37 changes: 37 additions & 0 deletions src/api/data_types/code_mappings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! Data types for the bulk code mappings API.

use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BulkCodeMappingsRequest {
pub project: String,
pub repository: String,
pub default_branch: String,
pub mappings: Vec<BulkCodeMapping>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BulkCodeMapping {
pub stack_root: String,
pub source_root: String,
}

#[derive(Debug, Deserialize)]
pub struct BulkCodeMappingsResponse {
pub created: u64,
pub updated: u64,
pub errors: u64,
pub mappings: Vec<BulkCodeMappingResult>,
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BulkCodeMappingResult {
pub stack_root: String,
pub source_root: String,
pub status: String,
#[serde(default)]
pub detail: Option<String>,
}
2 changes: 2 additions & 0 deletions src/api/data_types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Data types used in the api module

mod chunking;
mod code_mappings;
mod deploy;
mod snapshots;

pub use self::chunking::*;
pub use self::code_mappings::*;
pub use self::deploy::*;
pub use self::snapshots::*;
11 changes: 11 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,17 @@ impl AuthenticatedApi<'_> {
Ok(rv)
}

/// Bulk uploads code mappings for an organization.
pub fn bulk_upload_code_mappings(
&self,
org: &str,
body: &BulkCodeMappingsRequest,
) -> ApiResult<BulkCodeMappingsResponse> {
let path = format!("/organizations/{}/code-mappings/bulk/", PathArg(org));
self.post(&path, body)?
.convert_rnf(ApiErrorKind::ResourceNotFound)
}

/// Creates a preprod snapshot artifact for the given project.
pub fn create_preprod_snapshot<S: Serialize>(
&self,
Expand Down
71 changes: 58 additions & 13 deletions src/commands/code_mappings/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,12 @@ use std::fs;
use anyhow::{bail, Context as _, Result};
use clap::{Arg, ArgMatches, Command};
use log::debug;
use serde::{Deserialize, Serialize};

use crate::api::{Api, BulkCodeMapping, BulkCodeMappingsRequest};
use crate::config::Config;
use crate::utils::formatting::Table;
use crate::utils::vcs;

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
struct CodeMapping {
stack_root: String,
source_root: String,
}

pub fn make_command(command: Command) -> Command {
command
.about("Upload code mappings for a project from a JSON file.")
Expand All @@ -39,11 +33,15 @@ pub fn make_command(command: Command) -> Command {
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
let config = Config::current();
let org = config.get_org(matches)?;
let project = config.get_project(matches)?;

#[expect(clippy::unwrap_used, reason = "path is a required argument")]
let path = matches.get_one::<String>("path").unwrap();
let data = fs::read(path).with_context(|| format!("Failed to read mappings file '{path}'"))?;

let mappings: Vec<CodeMapping> =
let mappings: Vec<BulkCodeMapping> =
serde_json::from_slice(&data).context("Failed to parse mappings JSON")?;

if mappings.is_empty() {
Expand Down Expand Up @@ -74,7 +72,6 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
})?;
// Prefer explicit config (SENTRY_VCS_REMOTE / ini), then inspect
// the repo for the best remote (upstream > origin > first).
let config = Config::current();
let configured_remote = config.get_cached_vcs_remote();
let remote_name = if vcs::git_repo_remote_url(&git_repo, &configured_remote).is_ok() {
debug!("Using configured VCS remote: {configured_remote}");
Expand Down Expand Up @@ -120,9 +117,57 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
}
};

println!("Found {} code mapping(s) in {path}", mappings.len());
println!("Repository: {repo_name}");
println!("Default branch: {default_branch}");
let mapping_count = mappings.len();
let request = BulkCodeMappingsRequest {
project,
repository: repo_name,
default_branch,
mappings,
};

println!("Uploading {mapping_count} code mapping(s)...");

let api = Api::current();
let response = api
.authenticated()?
.bulk_upload_code_mappings(&org, &request)?;

// Display results
let mut table = Table::new();
table
.title_row()
.add("Stack Root")
.add("Source Root")
.add("Status");

for result in &response.mappings {
let status = match result.status.as_str() {
"error" => match &result.detail {
Some(detail) => format!("error: {detail}"),
None => "error".to_owned(),
},
s => s.to_owned(),
};
table
.add_row()
.add(&result.stack_root)
.add(&result.source_root)
.add(&status);
}

table.print();
println!();
println!(
"Created: {}, Updated: {}, Errors: {}",
response.created, response.updated, response.errors
);

if response.errors > 0 {
bail!(
"{} mapping(s) failed to upload. See errors above.",
response.errors
);
}

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
```
$ sentry-cli code-mappings upload tests/integration/_fixtures/code_mappings/mappings.json --org wat-org --project wat-project --repo owner/repo --default-branch main
? success
Uploading 2 code mapping(s)...
+------------------+---------------------------------------------+---------+
| Stack Root | Source Root | Status |
+------------------+---------------------------------------------+---------+
| com/example/core | modules/core/src/main/java/com/example/core | created |
| com/example/maps | modules/maps/src/main/java/com/example/maps | created |
+------------------+---------------------------------------------+---------+

Created: 2, Updated: 0, Errors: 0

```
4 changes: 4 additions & 0 deletions tests/integration/_fixtures/code_mappings/mappings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
{"stackRoot": "com/example/core", "sourceRoot": "modules/core/src/main/java/com/example/core"},
{"stackRoot": "com/example/maps", "sourceRoot": "modules/maps/src/main/java/com/example/maps"}
]
9 changes: 9 additions & 0 deletions tests/integration/_responses/code_mappings/post-bulk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"created": 2,
"updated": 0,
"errors": 0,
"mappings": [
{"stackRoot": "com/example/core", "sourceRoot": "modules/core/src/main/java/com/example/core", "status": "created"},
{"stackRoot": "com/example/maps", "sourceRoot": "modules/maps/src/main/java/com/example/maps", "status": "created"}
]
}
2 changes: 2 additions & 0 deletions tests/integration/code_mappings/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::integration::TestManager;

mod upload;

#[test]
fn command_code_mappings_help() {
TestManager::new().register_trycmd_test("code_mappings/code-mappings-help.trycmd");
Expand Down
12 changes: 12 additions & 0 deletions tests/integration/code_mappings/upload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::integration::{MockEndpointBuilder, TestManager};

#[test]
fn command_code_mappings_upload() {
TestManager::new()
.mock_endpoint(
MockEndpointBuilder::new("POST", "/api/0/organizations/wat-org/code-mappings/bulk/")
.with_response_file("code_mappings/post-bulk.json"),
)
.register_trycmd_test("code_mappings/code-mappings-upload.trycmd")
.with_default_token();
}
Loading