github_workflows_update/
resource.rs

1// Copyright (C) 2022 Leandro Lisboa Penz <lpenz@lpenz.org>
2// This file is subject to the terms and conditions defined in
3// file 'LICENSE', which is part of this source code package.
4
5//! The [`Resource`] type.
6
7use regex::Regex;
8use std::fmt;
9use tracing::instrument;
10use url::Url;
11
12use crate::error::Error;
13use crate::error::Result;
14use crate::updater;
15use crate::version::Version;
16
17#[derive(Debug, PartialEq, Eq, Hash, Clone)]
18pub enum Resource {
19    Docker {
20        container: String,
21    },
22    GhAction {
23        user: String,
24        repo: String,
25    },
26    GhWorkflow {
27        user: String,
28        repo: String,
29        workflow: String,
30    },
31}
32
33impl Resource {
34    #[instrument(level = "debug")]
35    pub fn new_docker(container: String) -> Resource {
36        Resource::Docker { container }
37    }
38
39    #[instrument(level = "debug")]
40    pub fn new_ghaction(user: String, repo: String) -> Resource {
41        Resource::GhAction { user, repo }
42    }
43
44    #[instrument(level = "debug")]
45    pub fn new_ghworkflow(user: String, repo: String, workflow: String) -> Resource {
46        Resource::GhWorkflow {
47            user,
48            repo,
49            workflow,
50        }
51    }
52
53    pub fn is_docker(&self) -> bool {
54        matches!(self, Resource::Docker { .. })
55    }
56
57    pub fn is_github(&self) -> bool {
58        matches!(
59            self,
60            Resource::GhAction { .. } | Resource::GhWorkflow { .. }
61        )
62    }
63
64    #[instrument(level = "debug")]
65    pub fn parse(input: &str) -> Result<(Self, Version), Error> {
66        let re_docker = Regex::new(r"^docker://(?P<resource>[^:]+):(?P<version>[^:]+)$").unwrap();
67        if let Some(m) = re_docker.captures(input) {
68            let version_str = m.name("version").unwrap().as_str();
69            let version = Version::new(version_str)
70                .ok_or_else(|| Error::VersionParsing(version_str.into()))?;
71            return Ok((
72                Resource::new_docker(m.name("resource").unwrap().as_str().into()),
73                version,
74            ));
75        }
76        let re_ghworkflow = Regex::new(r"^(?P<user>[^/]+)/(?P<repo>[^/]+)/\.github/workflows/(?P<workflow>[^@]+)@(?P<version>[^@]+)$").unwrap();
77        if let Some(m) = re_ghworkflow.captures(input) {
78            let version_str = m.name("version").unwrap().as_str();
79            let version = Version::new(version_str)
80                .ok_or_else(|| Error::VersionParsing(version_str.into()))?;
81            return Ok((
82                Resource::new_ghworkflow(
83                    m.name("user").unwrap().as_str().into(),
84                    m.name("repo").unwrap().as_str().into(),
85                    m.name("workflow").unwrap().as_str().into(),
86                ),
87                version,
88            ));
89        }
90        let re_github =
91            Regex::new(r"^(?P<user>[^/]+)/(?P<repo>[^@/]+)@(?P<version>[^@]+)$").unwrap();
92        if let Some(m) = re_github.captures(input) {
93            let version_str = m.name("version").unwrap().as_str();
94            let version = Version::new(version_str)
95                .ok_or_else(|| Error::VersionParsing(version_str.into()))?;
96            return Ok((
97                Resource::new_ghaction(
98                    m.name("user").unwrap().as_str().into(),
99                    m.name("repo").unwrap().as_str().into(),
100                ),
101                version,
102            ));
103        }
104        Err(Error::ResourceParseError(input.into()))
105    }
106
107    #[instrument(level = "debug")]
108    pub fn url(&self) -> Result<Url> {
109        let url_string = match self {
110            Resource::Docker { container } => format!(
111                "https://registry.hub.docker.com/v2/repositories/{}/tags",
112                container
113            ),
114            Resource::GhAction { user, repo } => format!(
115                "https://api.github.com/repos/{}/{}/git/matching-refs/tags",
116                user, repo
117            ),
118            Resource::GhWorkflow { user, repo, .. } => format!(
119                "https://api.github.com/repos/{}/{}/git/matching-refs/tags",
120                user, repo
121            ),
122        };
123        Ok(Url::parse(&url_string)?)
124    }
125
126    #[instrument(level = "debug")]
127    pub fn versioned_string(&self, version: &Version) -> String {
128        if self.is_docker() {
129            format!("{}:{}", self, version)
130        } else if self.is_github() {
131            format!("{}@{}", self, version)
132        } else {
133            panic!("unknown resource type");
134        }
135    }
136
137    #[instrument(level = "debug")]
138    pub async fn get_versions(&self) -> Result<Vec<Version>> {
139        if self.is_docker() {
140            updater::docker::get_versions(&self.url()?).await
141        } else if self.is_github() {
142            updater::github::get_versions(&self.url()?).await
143        } else {
144            panic!("unknown resource type");
145        }
146    }
147}
148
149impl fmt::Display for Resource {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(
152            f,
153            "{}",
154            match self {
155                Resource::Docker { container } => format!("docker://{}", container),
156                Resource::GhAction { user, repo } => format!("{}/{}", user, repo),
157                Resource::GhWorkflow {
158                    user,
159                    repo,
160                    workflow,
161                } => format!("{}/{}/.github/workflows/{}", user, repo, workflow),
162            }
163        )
164    }
165}