rusty_ci/buildbot/
scheduler.rs

1use crate::unwrap;
2use rusty_yaml::Yaml;
3use std::fmt::{Display, Error, Formatter};
4use std::process::exit;
5
6/// The scheduler struct controls when a builder is run. This is done when certain requirements specified
7/// by the scheduler are fulfilled. For example, you could define a scheduler that would trigger one or
8/// more builders when a file ending in ".py" is changed in a branch beginning with "fix/".
9pub struct Scheduler {
10    /// Name of scheduler
11    name: String,
12
13    /// This field is the password for whitelisting other people's pull requests. When
14    /// a non-whitelisted user makes a pull request, we don't want to test their code until
15    /// we know that it is safe. When an admin deems it's safe, they can use this password
16    /// to mark the pull/merge request as safe. After posting this password, the request
17    /// will permanently be marked as safe.
18    /// This password is a regular expression, so you can match multiple phrases or cases
19    /// if you'd like.
20    /// The reason it is in the scheduler is because we want to give permission to unknown users
21    /// on a per schedule basis. We want to have power over when a scheduler is allowed to assign
22    /// a job, not what job is assigned.
23    password: String,
24
25    /// This field is used to determine whether or not this scheduler is dependent on another.
26    /// If this field is anything other than None, then the `branch` and `file_triggers` fields
27    /// will be ignored / initialized to "".
28    ///
29    /// When a scheduler depends on another, it will be triggered only if the scheduler it
30    /// depends on was triggered and its builds were successful
31    ///
32    /// A scheduler in the YAML file should have the `depends` section OR the `branch`+`triggers`
33    /// sections, not both.
34    depends: Option<String>,
35
36    /// A regex expr that accepts a branch name.
37    /// This scheduler will only operate on the
38    /// branches with names that match this regex.
39    branch: String,
40
41    /// Regex exprs accepting file names.
42    /// When these file names are changed in a branch,
43    /// They will trigger the scheduler's workers.
44    file_triggers: Vec<String>,
45
46    /// The builders to trigger
47    buildernames: Vec<String>,
48}
49
50impl Scheduler {
51    /// Create new scheduler
52    fn new<S>(
53        name: S,
54        depends: Option<String>,
55        password: S,
56        branch: S,
57        file_triggers: Vec<S>,
58        buildernames: Vec<S>,
59    ) -> Self
60    where
61        S: Display,
62    {
63        Self {
64            name: name.to_string(),
65            depends,
66            password: password.to_string(),
67            branch: branch.to_string(),
68            file_triggers: file_triggers
69                .iter()
70                .map(|s| {
71                    s.to_string()
72                        .trim()
73                        .trim_start_matches('\"')
74                        .trim_end_matches('\"')
75                        .to_string()
76                })
77                .collect(),
78            buildernames: buildernames
79                .iter()
80                .map(|s| {
81                    s.to_string()
82                        .trim()
83                        .trim_start_matches('\"')
84                        .trim_end_matches('\"')
85                        .to_string()
86                })
87                .collect(),
88        }
89    }
90}
91
92impl Display for Scheduler {
93    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
94        match &self.depends {
95            Some(depends) => write!(
96                f,
97                "   
98{name} = schedulers.Dependent(name=\"{name}\",
99                               upstream={depends},
100                               builderNames={buildernames})
101c['schedulers'].append({name})
102
103    ",
104                name = self.name.replace("-", "_"),
105                depends = depends.replace("-", "_"),
106                buildernames = format!("{:?}", self.buildernames)
107            ),
108            None => writeln!(
109                f,
110                "
111@util.renderer
112def {name}_triggers(props):
113    builders = {buildernames}
114
115    triggers = {triggers}
116
117    if not is_whitelisted(props, \"{password}\"):
118        print('NOT WHITELISTED!!!')
119        return []
120
121    for f in props.files:
122        for regex in triggers:
123            print(\"FILE:   \", f)
124            print(\"TRIGGER:\", regex)
125            if re.fullmatch(regex, str(f)):
126                return builders
127
128    return []
129
130
131{name} = schedulers.AnyBranchScheduler(name=\"{name}\",
132    change_filter=util.ChangeFilter(branch_re=\"{branch}\"),
133    builderNames={name}_triggers)
134
135c['schedulers'].append({name})
136
137c['schedulers'].append(schedulers.ForceScheduler(name=\"force_{name}\",
138    builderNames={buildernames}))
139",
140                name = self.name.replace("-", "_"),
141                password = self.password.trim_matches('"'),
142                branch = self.branch.trim_start_matches('\"').trim_end_matches('\"'),
143                triggers = format!("{:?}", self.file_triggers)
144                    .replace("\\\"", "")
145                    .replace("\\\\\\\\", "\\\\"),
146                buildernames = format!("{:?}", self.buildernames)
147            ),
148        }
149    }
150}
151
152impl From<Yaml> for Scheduler {
153    fn from(yaml: Yaml) -> Self {
154        let name = yaml.get_name();
155        let depends: Option<String>;
156        let branch: String;
157        let password: String;
158        let mut triggers = vec![];
159        let mut builders = vec![];
160
161        if !yaml.has_section("builders") {
162            error!("There was an error creating a scheduler: The 'builders' section is not specified for '{}'", name);
163            exit(1);
164        }
165
166        if yaml.has_section("depends") {
167            depends = Some(unwrap(&yaml, "depends"));
168            branch = String::from("");
169            password = String::from("");
170        } else {
171            for section in ["branch", "password", "triggers"].iter() {
172                if !yaml.has_section(section) {
173                    error!("There was an error creating a scheduler: The '{}' section is not specified for '{}'", section, name);
174                    exit(1);
175                }
176            }
177
178            depends = None;
179            branch = unwrap(&yaml, "branch");
180            password = unwrap(&yaml, "password");
181
182            for trigger in yaml.get_section("triggers").unwrap() {
183                triggers.push(trigger.to_string());
184            }
185        }
186
187        for builder in yaml.get_section("builders").unwrap() {
188            builders.push(builder.to_string());
189        }
190
191        Scheduler::new(name, depends, password, branch, triggers, builders)
192    }
193}