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(())
}
}