Skip to main content

gitlab/api/runners/
edit.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use derive_builder::Builder;
8
9use crate::api::common::CommaSeparatedList;
10use crate::api::endpoint_prelude::*;
11use crate::api::ParamValue;
12
13/// Access levels of runners.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum RunnerAccessLevel {
17    /// Run jobs from any pipeline.
18    NotProtected,
19    /// Only run jobs from protected refs.
20    RefProtected,
21}
22
23impl RunnerAccessLevel {
24    /// The runner type as a query parameter.
25    fn as_str(self) -> &'static str {
26        match self {
27            Self::NotProtected => "not_protected",
28            Self::RefProtected => "ref_protected",
29        }
30    }
31}
32
33impl ParamValue<'static> for RunnerAccessLevel {
34    fn as_value(&self) -> Cow<'static, str> {
35        self.as_str().into()
36    }
37}
38
39/// Edit the details of a runner.
40#[derive(Debug, Builder, Clone)]
41#[builder(setter(strip_option), build_fn(validate = "Self::validate"))]
42pub struct EditRunner<'a> {
43    /// The ID of the runner.
44    runner: u64,
45
46    /// The description of the runner.
47    #[builder(setter(into), default)]
48    description: Option<Cow<'a, str>>,
49    /// Whether the runner should ignore new jobs or not.
50    #[builder(default)]
51    paused: Option<bool>,
52    /// Set the tags for the runner.
53    #[builder(setter(name = "_tag_list"), default, private)]
54    tag_list: Option<CommaSeparatedList<Cow<'a, str>>>,
55    /// Whether the runner can execute untagged jobs or not.
56    #[builder(default)]
57    run_untagged: Option<bool>,
58    /// Whether the runner is locked or not.
59    #[builder(default)]
60    locked: Option<bool>,
61    /// The access level of the runner.
62    #[builder(default)]
63    access_level: Option<RunnerAccessLevel>,
64    /// The maximum timeout allowed on the runner (in seconds).
65    #[builder(default)]
66    maximum_timeout: Option<u64>,
67    /// Maintenance note for the runner.
68    ///
69    /// Maximum size is 1024.
70    #[builder(setter(into), default)]
71    maintenance_note: Option<Cow<'a, str>>,
72}
73
74impl<'a> EditRunner<'a> {
75    /// Create a builder for the endpoint.
76    pub fn builder() -> EditRunnerBuilder<'a> {
77        EditRunnerBuilder::default()
78    }
79}
80
81impl<'a> EditRunnerBuilder<'a> {
82    /// Add a tag to the runner.
83    pub fn tag<T>(&mut self, tag: T) -> &mut Self
84    where
85        T: Into<Cow<'a, str>>,
86    {
87        self.tag_list
88            .get_or_insert(None)
89            .get_or_insert_with(CommaSeparatedList::new)
90            .push(tag.into());
91        self
92    }
93
94    /// Add multiple tags to the runner.
95    pub fn tags<I, T>(&mut self, iter: I) -> &mut Self
96    where
97        I: Iterator<Item = T>,
98        T: Into<Cow<'a, str>>,
99    {
100        self.tag_list
101            .get_or_insert(None)
102            .get_or_insert_with(CommaSeparatedList::new)
103            .extend(iter.map(|t| t.into()));
104        self
105    }
106
107    fn validate(&self) -> Result<(), EditRunnerBuilderError> {
108        if let Some(Some(maintenance_note)) = self.maintenance_note.as_ref() {
109            if maintenance_note.len() > super::MAX_MAINTENANCE_NOTE_LENGTH {
110                return Err(format!(
111                    "`maintenance_note` may be at most {} bytes",
112                    super::MAX_MAINTENANCE_NOTE_LENGTH,
113                )
114                .into());
115            }
116        }
117
118        Ok(())
119    }
120}
121
122impl Endpoint for EditRunner<'_> {
123    fn method(&self) -> Method {
124        Method::PUT
125    }
126
127    fn endpoint(&self) -> Cow<'static, str> {
128        format!("runners/{}", self.runner).into()
129    }
130
131    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
132        let mut params = FormParams::default();
133
134        params
135            .push_opt("description", self.description.as_ref())
136            .push_opt("paused", self.paused)
137            .push_opt("tag_list", self.tag_list.as_ref())
138            .push_opt("run_untagged", self.run_untagged)
139            .push_opt("locked", self.locked)
140            .push_opt("access_level", self.access_level)
141            .push_opt("maximum_timeout", self.maximum_timeout)
142            .push_opt("maintenance_note", self.maintenance_note.as_ref());
143
144        params.into_body()
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use http::Method;
151
152    use crate::api::runners::{EditRunner, EditRunnerBuilderError, RunnerAccessLevel};
153    use crate::api::{self, Query};
154    use crate::test::client::{ExpectedUrl, SingleTestClient};
155
156    #[test]
157    fn runner_access_level_as_str() {
158        let items = &[
159            (RunnerAccessLevel::NotProtected, "not_protected"),
160            (RunnerAccessLevel::RefProtected, "ref_protected"),
161        ];
162
163        for (i, s) in items {
164            assert_eq!(i.as_str(), *s);
165        }
166    }
167
168    #[test]
169    fn runner_is_required() {
170        let err = EditRunner::builder().build().unwrap_err();
171        crate::test::assert_missing_field!(err, EditRunnerBuilderError, "runner");
172    }
173
174    #[test]
175    fn runner_is_sufficient() {
176        EditRunner::builder().runner(1).build().unwrap();
177    }
178
179    #[test]
180    fn maintenance_note_length() {
181        use crate::api::runners::MAX_MAINTENANCE_NOTE_LENGTH;
182
183        let too_long = format!("{:width$}", "note", width = MAX_MAINTENANCE_NOTE_LENGTH + 1);
184        let err = EditRunner::builder()
185            .runner(1)
186            .maintenance_note(too_long)
187            .build()
188            .unwrap_err();
189        if let EditRunnerBuilderError::ValidationError(message) = err {
190            assert_eq!(
191                message,
192                format!("`maintenance_note` may be at most {MAX_MAINTENANCE_NOTE_LENGTH} bytes"),
193            );
194        } else {
195            panic!("unexpected error: {:?}", err);
196        }
197    }
198
199    #[test]
200    fn endpoint() {
201        let endpoint = ExpectedUrl::builder()
202            .method(Method::PUT)
203            .endpoint("runners/1")
204            .content_type("application/x-www-form-urlencoded")
205            .build()
206            .unwrap();
207        let client = SingleTestClient::new_raw(endpoint, "");
208
209        let endpoint = EditRunner::builder().runner(1).build().unwrap();
210        api::ignore(endpoint).query(&client).unwrap();
211    }
212
213    #[test]
214    fn endpoint_description() {
215        let endpoint = ExpectedUrl::builder()
216            .method(Method::PUT)
217            .endpoint("runners/1")
218            .content_type("application/x-www-form-urlencoded")
219            .body_str("description=desc")
220            .build()
221            .unwrap();
222        let client = SingleTestClient::new_raw(endpoint, "");
223
224        let endpoint = EditRunner::builder()
225            .runner(1)
226            .description("desc")
227            .build()
228            .unwrap();
229        api::ignore(endpoint).query(&client).unwrap();
230    }
231
232    #[test]
233    fn endpoint_paused() {
234        let endpoint = ExpectedUrl::builder()
235            .method(Method::PUT)
236            .endpoint("runners/1")
237            .content_type("application/x-www-form-urlencoded")
238            .body_str("paused=true")
239            .build()
240            .unwrap();
241        let client = SingleTestClient::new_raw(endpoint, "");
242
243        let endpoint = EditRunner::builder()
244            .runner(1)
245            .paused(true)
246            .build()
247            .unwrap();
248        api::ignore(endpoint).query(&client).unwrap();
249    }
250
251    #[test]
252    fn endpoint_tag_list() {
253        let endpoint = ExpectedUrl::builder()
254            .method(Method::PUT)
255            .endpoint("runners/1")
256            .content_type("application/x-www-form-urlencoded")
257            .body_str("tag_list=tag2%2Ctag1%2Ctag3")
258            .build()
259            .unwrap();
260        let client = SingleTestClient::new_raw(endpoint, "");
261
262        let endpoint = EditRunner::builder()
263            .runner(1)
264            .tag("tag2")
265            .tags(["tag1", "tag3"].iter().cloned())
266            .build()
267            .unwrap();
268        api::ignore(endpoint).query(&client).unwrap();
269    }
270
271    #[test]
272    fn endpoint_run_untagged() {
273        let endpoint = ExpectedUrl::builder()
274            .method(Method::PUT)
275            .endpoint("runners/1")
276            .content_type("application/x-www-form-urlencoded")
277            .body_str("run_untagged=false")
278            .build()
279            .unwrap();
280        let client = SingleTestClient::new_raw(endpoint, "");
281
282        let endpoint = EditRunner::builder()
283            .runner(1)
284            .run_untagged(false)
285            .build()
286            .unwrap();
287        api::ignore(endpoint).query(&client).unwrap();
288    }
289
290    #[test]
291    fn endpoint_locked() {
292        let endpoint = ExpectedUrl::builder()
293            .method(Method::PUT)
294            .endpoint("runners/1")
295            .content_type("application/x-www-form-urlencoded")
296            .body_str("locked=false")
297            .build()
298            .unwrap();
299        let client = SingleTestClient::new_raw(endpoint, "");
300
301        let endpoint = EditRunner::builder()
302            .runner(1)
303            .locked(false)
304            .build()
305            .unwrap();
306        api::ignore(endpoint).query(&client).unwrap();
307    }
308
309    #[test]
310    fn endpoint_access_level() {
311        let endpoint = ExpectedUrl::builder()
312            .method(Method::PUT)
313            .endpoint("runners/1")
314            .content_type("application/x-www-form-urlencoded")
315            .body_str("access_level=ref_protected")
316            .build()
317            .unwrap();
318        let client = SingleTestClient::new_raw(endpoint, "");
319
320        let endpoint = EditRunner::builder()
321            .runner(1)
322            .access_level(RunnerAccessLevel::RefProtected)
323            .build()
324            .unwrap();
325        api::ignore(endpoint).query(&client).unwrap();
326    }
327
328    #[test]
329    fn endpoint_maximum_timeout() {
330        let endpoint = ExpectedUrl::builder()
331            .method(Method::PUT)
332            .endpoint("runners/1")
333            .content_type("application/x-www-form-urlencoded")
334            .body_str("maximum_timeout=3600")
335            .build()
336            .unwrap();
337        let client = SingleTestClient::new_raw(endpoint, "");
338
339        let endpoint = EditRunner::builder()
340            .runner(1)
341            .maximum_timeout(3600)
342            .build()
343            .unwrap();
344        api::ignore(endpoint).query(&client).unwrap();
345    }
346
347    #[test]
348    fn endpoint_maintenance_note() {
349        let endpoint = ExpectedUrl::builder()
350            .method(Method::PUT)
351            .endpoint("runners/1")
352            .content_type("application/x-www-form-urlencoded")
353            .body_str("maintenance_note=note")
354            .build()
355            .unwrap();
356        let client = SingleTestClient::new_raw(endpoint, "");
357
358        let endpoint = EditRunner::builder()
359            .runner(1)
360            .maintenance_note("note")
361            .build()
362            .unwrap();
363        api::ignore(endpoint).query(&client).unwrap();
364    }
365}