Skip to main content

release_tool/publisher/
mod.rs

1mod github_maven;
2mod github_release;
3mod oci_registry;
4
5pub use github_maven::{GithubMavenPublisher, MavenRemote};
6pub use github_release::GithubReleasePublisher;
7pub use oci_registry::OciRegistryPublisher;
8
9use crate::domain::{ArtifactIdentity, ArtifactManifest, TargetPlan};
10use anyhow::{Context, Result, bail};
11use sha2::{Digest, Sha256};
12use std::collections::HashSet;
13use std::fs;
14
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub enum PublicationState {
17    Absent,
18    Complete,
19    Partial {
20        present: Vec<String>,
21        missing: Vec<String>,
22    },
23    Invalid {
24        reason: String,
25    },
26}
27
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct PublicationReceipt {
30    pub tool_version: String,
31    pub repository: String,
32    pub tag: String,
33    pub commit: String,
34    pub target: String,
35    pub publisher: String,
36    pub artifacts: Vec<ArtifactIdentity>,
37    pub skipped: bool,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
41pub struct VerificationReport {
42    pub target: String,
43    pub verified: bool,
44    pub artifacts: Vec<String>,
45}
46
47pub trait Publisher {
48    fn inspect(&self, plan: &TargetPlan) -> Result<PublicationState>;
49    fn publish(&self, manifest: &ArtifactManifest) -> Result<PublicationReceipt>;
50    fn verify(&self, manifest: &ArtifactManifest) -> Result<VerificationReport>;
51    fn verify_existing(&self, plan: &TargetPlan) -> Result<VerificationReport>;
52}
53
54pub(crate) fn receipt(
55    manifest: &ArtifactManifest,
56    publisher: &str,
57    skipped: bool,
58) -> PublicationReceipt {
59    PublicationReceipt {
60        tool_version: env!("CARGO_PKG_VERSION").to_owned(),
61        repository: manifest.release.repository.clone(),
62        tag: manifest.release.tag.to_string(),
63        commit: manifest.release.commit.clone(),
64        target: manifest.target.clone(),
65        publisher: publisher.to_owned(),
66        artifacts: manifest
67            .artifacts
68            .iter()
69            .map(|artifact| artifact.identity.clone())
70            .collect(),
71        skipped,
72    }
73}
74
75fn validate_manifest(manifest: &ArtifactManifest) -> Result<()> {
76    if manifest.artifacts.is_empty() {
77        bail!("artifact manifest must not be empty");
78    }
79    let mut identities = HashSet::new();
80    for artifact in &manifest.artifacts {
81        if !identities.insert(&artifact.identity) {
82            bail!(
83                "artifact manifest contains a duplicate identity: {:?}",
84                artifact.identity
85            );
86        }
87        let expected_file_name = match &artifact.identity {
88            ArtifactIdentity::GithubReleaseAsset { name } => name.clone(),
89            ArtifactIdentity::MavenPackage {
90                artifact_id,
91                version,
92                extension,
93                ..
94            } => format!("{artifact_id}-{version}.{extension}"),
95            ArtifactIdentity::OciImage { .. } => {
96                bail!("file publisher received an OCI image artifact")
97            }
98        };
99        if artifact.path.file_name().and_then(|name| name.to_str())
100            != Some(expected_file_name.as_str())
101        {
102            bail!(
103                "prepared artifact file name does not match its identity: expected `{expected_file_name}`, found `{}`",
104                artifact.path.display()
105            );
106        }
107        let bytes = fs::read(&artifact.path).with_context(|| {
108            format!(
109                "failed to read prepared artifact {}",
110                artifact.path.display()
111            )
112        })?;
113        let actual = hex::encode(Sha256::digest(bytes));
114        if actual != artifact.sha256 {
115            bail!(
116                "prepared artifact changed after Prepare: {} (expected {}, found {actual})",
117                artifact.path.display(),
118                artifact.sha256
119            );
120        }
121    }
122    Ok(())
123}