gitlab_s3_releaser 0.1.5

Tool to create GitLab releases from S3 bucket
Documentation
use crate::artifact::Artifact;
use anyhow::Context;
use gitlab::api::projects::releases::links::LinkType;
use gitlab::api::{self, projects, AsyncQuery};
use gitlab::{AsyncGitlab, GitlabBuilder};
use log::{debug, info};
use serde::Deserialize;
use std::borrow::Cow;
use std::collections::HashSet;

pub struct GitlabReleases {
    client: AsyncGitlab,
    gitlab_host: String,
    project: Project,
}

#[derive(Debug, Deserialize)]
pub struct Release {
    pub name: String,
    pub tag_name: String,
}

#[derive(Debug, Deserialize)]
pub struct Project {
    id: i64,
    name: String,
    path_with_namespace: String,
}

#[derive(Debug, Deserialize)]
struct Tag {
    pub name: String,
}

impl GitlabReleases {
    pub async fn new(
        gitlab_host: String,
        token: String,
        project_name: String,
    ) -> anyhow::Result<Self> {
        let client = GitlabBuilder::new(gitlab_host.clone(), token)
            .build_async()
            .await
            .context("Cannot create Gitlab client")?;

        let project_endpoint = projects::Project::builder()
            .project(&project_name)
            .build()
            .context("Build list projects endpoint")?;

        let project: Project = project_endpoint.query_async(&client).await?;
        info!("Project found, id {}, name {}", project.id, project.name);

        Ok(Self {
            client,
            gitlab_host,
            project,
        })
    }

    pub async fn list_releases(&self) -> anyhow::Result<Vec<Release>> {
        let releases_endpoint = projects::releases::ProjectReleases::builder()
            .project(&self.project.path_with_namespace)
            .build()
            .context("Build list releases endpoint")?;
        let releases: Vec<Release> = api::paged(releases_endpoint, api::Pagination::All)
            .query_async(&self.client)
            .await?;
        debug!("Releases {releases:?}");
        Ok(releases)
    }

    pub async fn list_tags(&self) -> anyhow::Result<HashSet<String>> {
        let tags_endpoint = projects::repository::tags::Tags::builder()
            .project(&self.project.path_with_namespace)
            .build()
            .context("Build list tag endpoint")?;
        let tags: Vec<Tag> = api::paged(tags_endpoint, api::Pagination::All)
            .query_async(&self.client)
            .await?;
        debug!("Tags {tags:?}");
        let tag_values = tags.into_iter().map(|tag| tag.name).collect();
        Ok(tag_values)
    }

    pub async fn add_release(
        &self,
        package_name: &String,
        artifact: &Artifact,
    ) -> anyhow::Result<Release> {
        if self.gitlab_host.starts_with("http") {
            anyhow::bail!("Gitlab host should not start with scheme");
        }
        let artifact_file_name = &artifact.file_name()?;
        let package_url = format!(
            "https://{}/api/v4/projects/{}/packages/generic/{}/{}/{}",
            &self.gitlab_host,
            &self.project.id,
            &package_name,
            &artifact.version,
            &artifact_file_name
        );
        let package_asset = projects::releases::CreateReleaseAssetLinks::builder()
            .link_type(LinkType::Package)
            .name(artifact_file_name)
            .url(package_url)
            .build()
            .context("Build asset")?;

        let create_release_endpoint = projects::releases::CreateRelease::builder()
            .project(&self.project.path_with_namespace)
            .name(format!("Release {}", artifact.version))
            .tag_name(&artifact.version)
            .tag_message("Autogenerated release from S3")
            .asset(package_asset)
            .build()
            .context("Build create release endpoint")?;

        let release: Release = create_release_endpoint.query_async(&self.client).await?;
        Ok(release)
    }

    pub async fn add_package(
        &self,
        package_name: &String,
        artifact: &Artifact,
        artifact_contents: Vec<u8>,
    ) -> anyhow::Result<()> {
        let contents = artifact_contents;
        let file_name: Cow<str> = Cow::from(artifact.file_name()?);
        let upload_package_endpoint = projects::packages::generic::UploadPackageFile::builder()
            .project(&self.project.path_with_namespace)
            .package_name(package_name)
            .package_version(&artifact.version)
            .file_name(file_name)
            .contents(contents)
            .build()
            .context("Build upload package endpoint")?;
        api::ignore(upload_package_endpoint)
            .query_async(&self.client)
            .await?;
        Ok(())
    }
}