gr/gitlab/
container_registry.rs

1use crate::{
2    api_traits::{ApiOperation, ContainerRegistry},
3    cmds::docker::{DockerListBodyArgs, ImageMetadata, RegistryRepository, RepositoryTag},
4    io::{HttpResponse, HttpRunner},
5    remote::query,
6    Result,
7};
8
9use super::Gitlab;
10
11impl<R: HttpRunner<Response = HttpResponse>> ContainerRegistry for Gitlab<R> {
12    fn list_repositories(&self, args: DockerListBodyArgs) -> Result<Vec<RegistryRepository>> {
13        let url = format!(
14            "{}/registry/repositories?tags_count=true",
15            self.rest_api_basepath()
16        );
17        query::paged(
18            &self.runner,
19            &url,
20            args.body_args,
21            self.headers(),
22            None,
23            ApiOperation::ContainerRegistry,
24            |value| GitlabRegistryRepositoryFields::from(value).into(),
25        )
26    }
27
28    fn list_repository_tags(&self, args: DockerListBodyArgs) -> Result<Vec<RepositoryTag>> {
29        // if tags is provided, then args.repo_id is Some at this point. This is
30        // enforced at the cli clap level.
31        let repository_id = args.repo_id.unwrap();
32        let url = format!(
33            "{}/registry/repositories/{}/tags",
34            self.rest_api_basepath(),
35            repository_id
36        );
37        query::paged(
38            &self.runner,
39            &url,
40            args.body_args,
41            self.headers(),
42            None,
43            ApiOperation::ContainerRegistry,
44            |value| GitlabRepositoryTagFields::from(value).into(),
45        )
46    }
47
48    fn num_pages_repository_tags(&self, repository_id: i64) -> Result<Option<u32>> {
49        let url = self.resource_repository_tags_metadata_url(repository_id);
50        query::num_pages(
51            &self.runner,
52            &url,
53            self.headers(),
54            ApiOperation::ContainerRegistry,
55        )
56    }
57
58    fn num_resources_repository_tags(
59        &self,
60        repository_id: i64,
61    ) -> Result<Option<crate::api_traits::NumberDeltaErr>> {
62        let url = self.resource_repository_tags_metadata_url(repository_id);
63        query::num_resources(
64            &self.runner,
65            &url,
66            self.headers(),
67            ApiOperation::ContainerRegistry,
68        )
69    }
70
71    fn num_pages_repositories(&self) -> Result<Option<u32>> {
72        let url = self.resource_repositories_metadata_url();
73        query::num_pages(
74            &self.runner,
75            &url,
76            self.headers(),
77            ApiOperation::ContainerRegistry,
78        )
79    }
80
81    fn num_resources_repositories(&self) -> Result<Option<crate::api_traits::NumberDeltaErr>> {
82        let url = self.resource_repositories_metadata_url();
83        query::num_resources(
84            &self.runner,
85            &url,
86            self.headers(),
87            ApiOperation::ContainerRegistry,
88        )
89    }
90
91    fn get_image_metadata(&self, repository_id: i64, tag: &str) -> Result<ImageMetadata> {
92        let url = format!(
93            "{}/registry/repositories/{}/tags/{}",
94            self.rest_api_basepath(),
95            repository_id,
96            tag
97        );
98        query::get::<_, (), _>(
99            &self.runner,
100            &url,
101            None,
102            self.headers(),
103            ApiOperation::ContainerRegistry,
104            |value| GitlabImageMetadataFields::from(value).into(),
105        )
106    }
107}
108
109impl<R> Gitlab<R> {
110    fn resource_repository_tags_metadata_url(&self, repository_id: i64) -> String {
111        let url = format!(
112            "{}/registry/repositories/{}/tags?page=1",
113            self.rest_api_basepath(),
114            repository_id
115        );
116        url
117    }
118
119    fn resource_repositories_metadata_url(&self) -> String {
120        let url = format!("{}/registry/repositories?page=1", self.rest_api_basepath());
121        url
122    }
123}
124
125pub struct GitlabRegistryRepositoryFields {
126    id: i64,
127    location: String,
128    tags_count: i64,
129    created_at: String,
130}
131
132impl From<&serde_json::Value> for GitlabRegistryRepositoryFields {
133    fn from(data: &serde_json::Value) -> Self {
134        GitlabRegistryRepositoryFields {
135            id: data["id"].as_i64().unwrap(),
136            location: data["location"].as_str().unwrap().to_string(),
137            tags_count: data["tags_count"].as_i64().unwrap(),
138            created_at: data["created_at"].as_str().unwrap().to_string(),
139        }
140    }
141}
142
143impl From<GitlabRegistryRepositoryFields> for RegistryRepository {
144    fn from(data: GitlabRegistryRepositoryFields) -> Self {
145        RegistryRepository::builder()
146            .id(data.id)
147            .location(data.location)
148            .tags_count(data.tags_count)
149            .created_at(data.created_at)
150            .build()
151            .unwrap()
152    }
153}
154
155pub struct GitlabRepositoryTagFields {
156    name: String,
157    path: String,
158    location: String,
159    created_at: String,
160}
161
162impl From<&serde_json::Value> for GitlabRepositoryTagFields {
163    fn from(data: &serde_json::Value) -> Self {
164        GitlabRepositoryTagFields {
165            name: data["name"].as_str().unwrap().to_string(),
166            path: data["path"].as_str().unwrap().to_string(),
167            location: data["location"].as_str().unwrap().to_string(),
168            // Repository tags don't have a creation date. It is included when
169            // querying a specific tag. Just return default UNIX epoch date.
170            created_at: "1970-01-01T00:00:00Z".to_string(),
171        }
172    }
173}
174
175impl From<GitlabRepositoryTagFields> for RepositoryTag {
176    fn from(data: GitlabRepositoryTagFields) -> Self {
177        RepositoryTag::builder()
178            .name(data.name)
179            .path(data.path)
180            .location(data.location)
181            .created_at(data.created_at)
182            .build()
183            .unwrap()
184    }
185}
186
187pub struct GitlabImageMetadataFields {
188    name: String,
189    location: String,
190    short_sha: String,
191    size: i64,
192    created_at: String,
193}
194
195impl From<&serde_json::Value> for GitlabImageMetadataFields {
196    fn from(data: &serde_json::Value) -> Self {
197        GitlabImageMetadataFields {
198            name: data["name"].as_str().unwrap().to_string(),
199            location: data["location"].as_str().unwrap().to_string(),
200            short_sha: data["short_revision"].as_str().unwrap().to_string(),
201            size: data["total_size"].as_i64().unwrap(),
202            created_at: data["created_at"].as_str().unwrap().to_string(),
203        }
204    }
205}
206
207impl From<GitlabImageMetadataFields> for ImageMetadata {
208    fn from(data: GitlabImageMetadataFields) -> Self {
209        ImageMetadata::builder()
210            .name(data.name)
211            .location(data.location)
212            .short_sha(data.short_sha)
213            .size(data.size)
214            .created_at(data.created_at)
215            .build()
216            .unwrap()
217    }
218}
219
220#[cfg(test)]
221mod test {
222    use super::*;
223    use crate::{
224        http::Headers,
225        setup_client,
226        test::utils::{default_gitlab, ContractType, ResponseContracts},
227    };
228
229    #[test]
230    fn test_list_repositories_url() {
231        let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
232            200,
233            "list_registry_repositories.json",
234            None,
235        );
236        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn ContainerRegistry);
237        let args = DockerListBodyArgs::builder().repos(true).build().unwrap();
238        gitlab.list_repositories(args).unwrap();
239        assert_eq!(
240            "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories?tags_count=true",
241            client.url().to_string(),
242        );
243        assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
244        assert_eq!(
245            Some(ApiOperation::ContainerRegistry),
246            *client.api_operation.borrow()
247        );
248    }
249
250    #[test]
251    fn test_list_repository_tags_url() {
252        let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
253            200,
254            "list_registry_repository_tags.json",
255            None,
256        );
257        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn ContainerRegistry);
258        let args = DockerListBodyArgs::builder()
259            .repos(false)
260            .tags(true)
261            .repo_id(Some(1))
262            .build()
263            .unwrap();
264        gitlab.list_repository_tags(args).unwrap();
265        assert_eq!(
266            client.url().to_string(),
267            "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories/1/tags"
268        );
269        assert_eq!("1234", client.headers().get("PRIVATE-TOKEN").unwrap());
270        assert_eq!(
271            Some(ApiOperation::ContainerRegistry),
272            *client.api_operation.borrow()
273        );
274    }
275
276    #[test]
277    fn test_query_num_pages_for_tags() {
278        let link_headers = r#"<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories/1/tags?page=1>; rel="next", <https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories/1/tags?page=1>; rel="last""#;
279        let mut headers = Headers::new();
280        headers.set("link".to_string(), link_headers.to_string());
281        let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
282            200,
283            None,
284            Some(headers),
285        );
286        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn ContainerRegistry);
287        assert_eq!(Some(1), gitlab.num_pages_repository_tags(1).unwrap());
288        assert_eq!(
289            "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories/1/tags?page=1",
290            client.url().to_string(),
291        );
292        assert_eq!(
293            Some(ApiOperation::ContainerRegistry),
294            *client.api_operation.borrow()
295        );
296    }
297
298    #[test]
299    fn test_query_num_pages_for_registry_repositories() {
300        let link_headers = r#"<https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories?page=1>; rel="next", <https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories?page=1>; rel="last""#;
301        let mut headers = Headers::new();
302        headers.set("link".to_string(), link_headers.to_string());
303        let contracts = ResponseContracts::new(ContractType::Gitlab).add_body::<String>(
304            200,
305            None,
306            Some(headers),
307        );
308        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn ContainerRegistry);
309        assert_eq!(Some(1), gitlab.num_pages_repositories().unwrap());
310        assert_eq!(
311            "https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories?page=1",
312            client.url().to_string(),
313        );
314        assert_eq!(
315            Some(ApiOperation::ContainerRegistry),
316            *client.api_operation.borrow()
317        );
318    }
319
320    #[test]
321    fn test_get_gitlab_registry_image_metadata() {
322        let contracts = ResponseContracts::new(ContractType::Gitlab).add_contract(
323            200,
324            "get_registry_repository_tag.json",
325            None,
326        );
327        let (client, gitlab) = setup_client!(contracts, default_gitlab(), dyn ContainerRegistry);
328        let _metadata = gitlab.get_image_metadata(1, "v0.0.1").unwrap();
329        assert_eq!("https://gitlab.com/api/v4/projects/jordilin%2Fgitlapi/registry/repositories/1/tags/v0.0.1",
330            client.url().to_string(),
331        );
332        assert_eq!(
333            Some(ApiOperation::ContainerRegistry),
334            *client.api_operation.borrow()
335        );
336    }
337}