tag2upload_service_manager/
gitlab.rs

1
2use crate::prelude::*;
3use webhook::*;
4
5#[derive(Deserialize, Eq, PartialEq)]
6#[derive(derive_more::Display, derive_more::FromStr)]
7#[serde(transparent)]
8pub struct ProjectId(pub u64);
9
10#[derive(Deserialize)]
11pub struct Payload {
12    object_kind: String,
13    after: GitObjectIdOrNull,
14    #[serde(rename = "ref")]
15    tag_ref_name: String,
16    project: Project,
17    message: String,
18}
19
20#[derive(Deserialize)]
21struct Project {
22    git_http_url: String,
23    id: ProjectId,
24}
25
26pub struct DbData {
27    project_id: ProjectId,
28}
29
30impl ForgeKind for Forge1 {
31    type Payload = Payload;
32
33    fn analyse_payload(&self, payload: Self::Payload)
34        -> Result<AnalysedPayload<Self>, WebError>
35    {
36        let Payload { object_kind, after, tag_ref_name, project, message }
37            = payload;
38
39        let Project { git_http_url, id } = project;
40
41        let tag_objectid = after.try_into().map_err(
42            |UnexpectedNullGitObjectId| NFR::TagIsBeingDeleted
43        )?;
44
45        if object_kind != "tag_push" {
46            return Err(WE::MisconfiguredWebhook(anyhow!(
47                "unexpected event {:?}", object_kind
48            )));
49        }
50        let tag_name = tag_ref_name.strip_prefix("refs/tags/")
51            .ok_or_else(|| WE::MisconfiguredWebhook(anyhow!(
52                "tag ref name doesn't start refs/tags/"
53            )))?
54            .to_owned();
55
56        let repo_git_url = git_http_url;
57        let forge_data = DbData { project_id: id };
58
59        Ok(AnalysedPayload {
60            repo_git_url,
61            tag_objectid,
62            tag_name,
63            tag_message: message,
64            forge_data,
65        })
66    }
67}
68
69impl FromStr for DbData {
70    type Err = IE;
71
72    fn from_str(s: &str) -> Result<Self, IE> {
73        let project_id = s.parse().into_internal("parse project id")?;
74        Ok(DbData {
75            project_id,
76        })
77    }
78}
79impl Display for DbData {
80    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81        let DbData {
82            project_id,
83        } = self;
84        Display::fmt(project_id, f)?;
85        Ok(())
86    }
87}
88
89#[derive(Default, Debug)]
90pub struct Forge1;
91
92#[derive(Deserialize)]
93struct TagResponse {
94    #[serde(rename = "target")]
95    tag_objectid: GitObjectId,
96
97    created_at: humantime_serde::Serde<SystemTime>,
98}
99
100#[async_trait]
101impl ForgeKindDataVersion for Forge1 {
102    type DbData = DbData where Self: Sized;
103
104    fn kind_name(&self) -> &'static str { "gitlab" }
105
106    fn namever_str(&self) -> &str { "gitlab-1" }
107
108    async fn make_progress(
109        &self,
110        host: &Hostname,
111        task_tmpdir: &str,
112    ) -> Result<(), QuitTask> {
113        let globals = globals();
114
115        let mut job = JobInWorkflow::start_for_forge(
116            &host,
117            self.namever_str(),
118        ).await?;
119
120        let jid = job.jid;
121
122        let db_data = job.forge_db_data(self)?;
123
124        trace!(%host, %jid, "fetching tag");
125
126        test_hook(|| format!("fetch gitlab {jid}")).await;
127
128        /*
129         * We make the API call to fetch the tag, too,
130         * just because we need the tag's timestamp,
131         * which gitlab doesn't put in the webhook.
132         *
133         * URL example,
134 https://salsa.debian.org/api/v4/projects/36575/repository/tags/debian%2f1.39
135         */
136        let url = (|| {
137            let mut url: Url = format!("https://{}", &job.data.forge_host)
138                // no trailing slash needed, path_segments extend will add
139                .parse().context("parse initial forge_host https url")?;
140            url.path_segments_mut()
141                .map_err(|()| internal!("path no segments?"))?
142                .extend([
143                    "api", "v4", "projects",
144                    &db_data.project_id.to_string(),
145                    "repository", "tags",
146                    &job.data.tag_name,
147                ]);
148            Ok::<_, AE>(url)
149        })()
150            .into_internal("construct tag API url")?;
151
152        trace!(%jid, %url, "gitab tag info");
153
154        test_hook_url!(url);
155
156        let outcome = async {
157            let TagResponse {
158                created_at,
159                tag_objectid: confirmed_tag,
160            } = globals.http_fetch_json(url.clone())
161                .await
162                .context("fetch tag info")
163                .map_err(PE::Forge)?;
164
165            trace!(%jid, ?created_at, "gitab tag date");
166
167            MismatchError::check(
168                "tag object id",
169                &job.data.tag_objectid,
170                &confirmed_tag,
171            )?;
172
173            let _is_recent_enough = globals.check_tag_recency(*created_at)?;
174
175            gitclone::fetch_tags_via_clone(
176                &mut job,
177                &task_tmpdir,
178            ).await
179        }.await;
180
181        fetcher::record_fetch_outcome(
182            job,
183            outcome,
184        )?;
185
186        Ok(())
187    }
188}