Skip to main content

torrust_tracker_deployer_lib/shared/
docker_image.rs

1//! Docker image reference value object
2//!
3//! This module provides a strongly-typed Docker image reference that combines
4//! a repository name with a tag. It is used to represent Docker images in
5//! service configurations and templates.
6//!
7//! # Design Decision
8//!
9//! Docker image versions are **not user-configurable** — they are pinned as
10//! constants in the code to ensure compatibility between the deployer and the
11//! images it uses. Exposing them through domain configs (rather than hardcoding
12//! in templates) gives us:
13//!
14//! - A **single source of truth** for each image version
15//! - The ability to **inspect images via the `show` command**
16//! - Automatic propagation of version changes to both templates and CI scanning
17//!
18//! # Examples
19//!
20//! ```rust
21//! use torrust_tracker_deployer_lib::shared::docker_image::DockerImage;
22//!
23//! let image = DockerImage::new("torrust/tracker", "develop");
24//! assert_eq!(image.full_reference(), "torrust/tracker:develop");
25//! assert_eq!(image.repository(), "torrust/tracker");
26//! assert_eq!(image.tag(), "develop");
27//! ```
28
29use std::fmt;
30
31use serde::{Deserialize, Serialize};
32
33/// Docker image reference with repository and tag
34///
35/// Represents an image reference of the form `repository:tag`,
36/// e.g. `torrust/tracker:develop` or `mysql:8.4`.
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct DockerImage {
39    repository: String,
40    tag: String,
41}
42
43impl DockerImage {
44    /// Creates a new Docker image reference
45    ///
46    /// # Examples
47    ///
48    /// ```rust
49    /// use torrust_tracker_deployer_lib::shared::docker_image::DockerImage;
50    ///
51    /// let image = DockerImage::new("torrust/tracker", "develop");
52    /// assert_eq!(image.repository(), "torrust/tracker");
53    /// assert_eq!(image.tag(), "develop");
54    /// ```
55    #[must_use]
56    pub fn new(repository: impl Into<String>, tag: impl Into<String>) -> Self {
57        Self {
58            repository: repository.into(),
59            tag: tag.into(),
60        }
61    }
62
63    /// Returns the repository name (e.g. `"torrust/tracker"`)
64    #[must_use]
65    pub fn repository(&self) -> &str {
66        &self.repository
67    }
68
69    /// Returns the image tag (e.g. `"develop"` or `"8.4"`)
70    #[must_use]
71    pub fn tag(&self) -> &str {
72        &self.tag
73    }
74
75    /// Returns the full image reference as `repository:tag`
76    ///
77    /// # Examples
78    ///
79    /// ```rust
80    /// use torrust_tracker_deployer_lib::shared::docker_image::DockerImage;
81    ///
82    /// let image = DockerImage::new("mysql", "8.4");
83    /// assert_eq!(image.full_reference(), "mysql:8.4");
84    /// ```
85    #[must_use]
86    pub fn full_reference(&self) -> String {
87        format!("{}:{}", self.repository, self.tag)
88    }
89}
90
91impl fmt::Display for DockerImage {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "{}:{}", self.repository, self.tag)
94    }
95}
96
97impl From<(&str, &str)> for DockerImage {
98    fn from((repository, tag): (&str, &str)) -> Self {
99        Self::new(repository, tag)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn it_should_create_docker_image_with_repository_and_tag() {
109        let image = DockerImage::new("torrust/tracker", "develop");
110
111        assert_eq!(image.repository(), "torrust/tracker");
112        assert_eq!(image.tag(), "develop");
113    }
114
115    #[test]
116    fn it_should_return_full_reference_as_repository_colon_tag() {
117        let image = DockerImage::new("torrust/tracker", "develop");
118
119        assert_eq!(image.full_reference(), "torrust/tracker:develop");
120    }
121
122    #[test]
123    fn it_should_display_as_full_reference() {
124        let image = DockerImage::new("mysql", "8.4");
125
126        assert_eq!(format!("{image}"), "mysql:8.4");
127    }
128
129    #[test]
130    fn it_should_create_from_str_tuple() {
131        let image = DockerImage::from(("prom/prometheus", "v3.5.1"));
132
133        assert_eq!(image.full_reference(), "prom/prometheus:v3.5.1");
134    }
135
136    #[test]
137    fn it_should_implement_equality() {
138        let a = DockerImage::new("grafana/grafana", "12.4.2");
139        let b = DockerImage::new("grafana/grafana", "12.4.2");
140        let c = DockerImage::new("grafana/grafana", "11.4.0");
141
142        assert_eq!(a, b);
143        assert_ne!(a, c);
144    }
145}