octocrab icon indicating copy to clipboard operation
octocrab copied to clipboard

Add upload release asset endpoints

Open kenr opened this issue 4 years ago • 11 comments

Do you have any recommendation on how to upload binaries to a release?

kenr avatar Jun 10 '21 14:06 kenr

Thank you for your issue! I don't think those endpoints are currently implemented in the semantic API, so you'd need to use the HTTP API. If someone wants to add those, contributions are welcome.

XAMPPRocky avatar Jun 17 '21 07:06 XAMPPRocky

Figured out how to do it with the current API, added the snippet below in case someone else need to do this.

        let filename = file.file_name().unwrap().to_str().unwrap();
        let mut new_url = base_url.clone();
        new_url.set_query(Some(format!("{}={}", "name", filename).as_str()));

        let file_size = std::fs::metadata(file)?.len();
        let file = tokio::fs::File::open(file).await?;
        let stream = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new());
        let body = reqwest::Body::wrap_stream(stream);

        let builder = octocrab.request_builder(new_url.as_str(), reqwest::Method::POST)
            .header("Content-Type", "application/octet-stream")
            .header("Content-Length", file_size.to_string());

        let response = builder.body(body).send().await?;

kenr avatar Jun 17 '21 07:06 kenr

I'm going to re-open this issue, because I don't really consider that to be a good enough solution. I want a semantic API solution that is easier to use.

XAMPPRocky avatar Jun 17 '21 07:06 XAMPPRocky

Thanks for the snippet. Here is my take. I needed simple functions to call.

in Cargo.toml:


[dependencies]
octocrab = "0.12.0"
unwrap="1.2.1"
reqwest={version="0.11.4", features=["stream"]}
tokio-util = "0.6.7"
tokio = {version = "1.10.0", features = ["rt","rt-multi-thread","fs"]}
url="2.2.2"
/// create new release on Github
/// return release_id
async fn github_create_new_release(
    owner: &str,
    repo: &str,
    version: &str,
    name: &str,
    branch: &str,
    body_md_text: &str,
) -> String {
    use octocrab::Octocrab;
    let token = std::env::var("GITHUB_TOKEN").expect("GITHUB_TOKEN env variable is required");
    let octocrab = unwrap!(Octocrab::builder().personal_token(token).build());
    let new_release = unwrap!(
        octocrab
            .repos(owner, repo)
            .releases()
            .create(&format!("v{}", version))
            .target_commitish(branch)
            .name(name)
            .body(body_md_text)
            .draft(false)
            .prerelease(false)
            .send()
            .await
    );
    new_release.id.to_string()
}

/// upload asset to github release
/// release_upload_url example: https://uploads.github.com/repos/owner/repo/releases/48127727/assets
async fn upload_asset_to_github_release(
    owner: &str,
    repo: &str,
    release_id: &str,
    path_to_file: &str,
) {
    use octocrab::Octocrab;
    let token = std::env::var("GITHUB_TOKEN").expect("GITHUB_TOKEN env variable is required");
    let octocrab = unwrap!(Octocrab::builder().personal_token(token).build());

    // println!("path_to_file: {}", path_to_file);
    let file = std::path::Path::new(&path_to_file);
    let file_name = file.file_name().unwrap().to_str().unwrap();

    let release_upload_url = format!(
        "https://uploads.github.com/repos/{owner}/{repo}/releases/{release_id}/assets",
        owner = owner,
        repo = repo,
        release_id = release_id
    );
    let mut release_upload_url = unwrap!(url::Url::from_str(&release_upload_url));
    release_upload_url.set_query(Some(format!("{}={}", "name", file_name).as_str()));
    // println!("upload_url: {}", release_upload_url);
    let file_size = unwrap!(std::fs::metadata(file)).len();
    // println!(
        "file_size: {}. It can take some time to upload. Wait...",
        file_size
    );
    let file = unwrap!(tokio::fs::File::open(file).await);
    let stream = tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new());
    let body = reqwest::Body::wrap_stream(stream);
    let builder = octocrab
        .request_builder(release_upload_url.as_str(), reqwest::Method::POST)
        .header("Content-Type", "application/octet-stream")
        .header("Content-Length", file_size.to_string());
    let _response = unwrap!(builder.body(body).send().await);
}

bestia-dev avatar Aug 19 '21 17:08 bestia-dev

Thank you for snippet, would you like to make a PR adding the upload_asset endpoint? That code looks good, except that in octocrab, we'd just take in a R: Read object rather than a file path.

XAMPPRocky avatar Aug 19 '21 17:08 XAMPPRocky

Thanks for the snippet by @kenr, we are using it in Airshipper, which is licensed under GPLv3.

ShouvikGhosh2048 avatar Feb 28 '22 09:02 ShouvikGhosh2048

Thanks this issue's codes.

I want to use deleteAssets and uploadAssets with octocrab, which is more convenient

niuhuan avatar Feb 02 '23 07:02 niuhuan

add delete fun but got original: Error("EOF while parsing a value", line: 1, column: 0)

    /// Delete a single asset by its ID.
    /// ```no_run
    /// # async fn run() -> octocrab::Result<()> {
    /// let asset = octocrab::instance()
    ///     .repos("owner", "repo")
    ///     .releases()
    ///     .delete_assets(42u64.into())
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn delete_assets(&self, asset_id: AssetId) -> crate::Result<()> {
        let url = format!(
            "repos/{owner}/{repo}/releases/assets/{asset_id}",
            owner = self.parent.owner,
            repo = self.parent.repo,
            asset_id = asset_id,
        );

        self.parent.crab.delete(url, None::<&()>).await
    }

i thins blank text can;t parse to ().

use snafu::ResultExt;

/// A trait for mapping from a `reqwest::Response` to an another type.
#[async_trait::async_trait]
pub trait FromResponse: Sized {
    async fn from_response(response: reqwest::Response) -> crate::Result<Self>;
}

#[async_trait::async_trait]
impl<T: serde::de::DeserializeOwned> FromResponse for T {
    async fn from_response(response: reqwest::Response) -> crate::Result<Self> {
        let text = response.text().await.context(crate::error::HttpSnafu)?;

        let de = &mut serde_json::Deserializer::from_str(&text);
        serde_path_to_error::deserialize(de).context(crate::error::JsonSnafu)
    }
}

Should we add delete_and_got_blank() fn ?

niuhuan avatar Feb 09 '23 05:02 niuhuan

This issue also happens with TeamRepoHandler::remove().

manchicken avatar Dec 14 '23 01:12 manchicken

I'm going to make a new issue for this EOF thing, I made a test that demonstrates it for teams().repos().remove().

manchicken avatar Dec 15 '23 11:12 manchicken

I submitted the EOF issue as #504. I made a test, too, to show how it's failing.

manchicken avatar Dec 17 '23 14:12 manchicken