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 } => {
111                format!("https://registry.hub.docker.com/v2/repositories/{container}/tags")
112            }
113            Resource::GhAction { user, repo } => {
114                format!("https://api.github.com/repos/{user}/{repo}/git/matching-refs/tags")
115            }
116            Resource::GhWorkflow { user, repo, .. } => {
117                format!("https://api.github.com/repos/{user}/{repo}/git/matching-refs/tags")
118            }
119        };
120        Ok(Url::parse(&url_string)?)
121    }
122
123    #[instrument(level = "debug")]
124    pub fn versioned_string(&self, version: &Version) -> String {
125        if self.is_docker() {
126            format!("{self}:{version}")
127        } else if self.is_github() {
128            format!("{self}@{version}")
129        } else {
130            panic!("unknown resource type");
131        }
132    }
133
134    #[instrument(level = "debug")]
135    pub async fn get_versions(&self) -> Result<Vec<Version>> {
136        if self.is_docker() {
137            updater::docker::get_versions(&self.url()?).await
138        } else if self.is_github() {
139            updater::github::get_versions(&self.url()?).await
140        } else {
141            panic!("unknown resource type");
142        }
143    }
144}
145
146impl fmt::Display for Resource {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        write!(
149            f,
150            "{}",
151            match self {
152                Resource::Docker { container } => format!("docker://{container}"),
153                Resource::GhAction { user, repo } => format!("{user}/{repo}"),
154                Resource::GhWorkflow {
155                    user,
156                    repo,
157                    workflow,
158                } => format!("{user}/{repo}/.github/workflows/{workflow}"),
159            }
160        )
161    }
162}