1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
use crate::unwrap;
use rusty_yaml::Yaml;
use std::fmt::{Display, Error, Formatter};
use std::process::exit;

/// The scheduler struct controls when a builder is run. This is done when certain requirements specified
/// by the scheduler are fulfilled. For example, you could define a scheduler that would trigger one or
/// more builders when a file ending in ".py" is changed in a branch beginning with "fix/".
pub struct Scheduler {
    /// Name of scheduler
    name: String,

    /// This field is the password for whitelisting other people's pull requests. When
    /// a non-whitelisted user makes a pull request, we don't want to test their code until
    /// we know that it is safe. When an admin deems it's safe, they can use this password
    /// to mark the pull/merge request as safe. After posting this password, the request
    /// will permanently be marked as safe.
    /// This password is a regular expression, so you can match multiple phrases or cases
    /// if you'd like.
    /// The reason it is in the scheduler is because we want to give permission to unknown users
    /// on a per schedule basis. We want to have power over when a scheduler is allowed to assign
    /// a job, not what job is assigned.
    password: String,

    /// This field is used to determine whether or not this scheduler is dependent on another.
    /// If this field is anything other than None, then the `branch` and `file_triggers` fields
    /// will be ignored / initialized to "".
    ///
    /// When a scheduler depends on another, it will be triggered only if the scheduler it
    /// depends on was triggered and its builds were successful
    ///
    /// A scheduler in the YAML file should have the `depends` section OR the `branch`+`triggers`
    /// sections, not both.
    depends: Option<String>,

    /// A regex expr that accepts a branch name.
    /// This scheduler will only operate on the
    /// branches with names that match this regex.
    branch: String,

    /// Regex exprs accepting file names.
    /// When these file names are changed in a branch,
    /// They will trigger the scheduler's workers.
    file_triggers: Vec<String>,

    /// The builders to trigger
    buildernames: Vec<String>,
}

impl Scheduler {
    /// Create new scheduler
    fn new<S>(
        name: S,
        depends: Option<String>,
        password: S,
        branch: S,
        file_triggers: Vec<S>,
        buildernames: Vec<S>,
    ) -> Self
    where
        S: Display,
    {
        Self {
            name: name.to_string(),
            depends,
            password: password.to_string(),
            branch: branch.to_string(),
            file_triggers: file_triggers
                .iter()
                .map(|s| {
                    s.to_string()
                        .trim()
                        .trim_start_matches("\"")
                        .trim_end_matches("\"")
                        .to_string()
                })
                .collect(),
            buildernames: buildernames
                .iter()
                .map(|s| {
                    s.to_string()
                        .trim()
                        .trim_start_matches("\"")
                        .trim_end_matches("\"")
                        .to_string()
                })
                .collect(),
        }
    }
}

impl Display for Scheduler {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        match &self.depends {
            Some(depends) => write!(
                f,
                "   
{name} = schedulers.Dependent(name=\"{name}\",
                               upstream={depends},
                               builderNames={buildernames})
c['schedulers'].append({name})

    ",
                name = self.name.replace("-", "_"),
                depends = depends.replace("-", "_"),
                buildernames = format!("{:?}", self.buildernames)
            ),
            None => write!(
                f,
                "
@util.renderer
def {name}_triggers(props):
    builders = {buildernames}

    triggers = {triggers}

    if not is_whitelisted(props, \"{password}\"):
        print('NOT WHITELISTED!!!')
        return []

    for f in props.files:
        for regex in triggers:
            print(\"FILE:   \", f)
            print(\"TRIGGER:\", regex)
            if re.fullmatch(regex, str(f)):
                return builders

    return []


{name} = schedulers.AnyBranchScheduler(name=\"{name}\",
    change_filter=util.ChangeFilter(branch_re=\"{branch}\"),
    builderNames={name}_triggers)

c['schedulers'].append({name})

c['schedulers'].append(schedulers.ForceScheduler(name=\"force_{name}\",
    builderNames={buildernames}))
",
                name = self.name.replace("-", "_"),
                password = self.password.trim_matches('"'),
                branch = self.branch.trim_start_matches("\"").trim_end_matches("\""),
                triggers = format!("{:?}", self.file_triggers)
                    .replace("\\\"", "")
                    .replace("\\\\\\\\", "\\\\"),
                buildernames = format!("{:?}", self.buildernames)
            ),
        }
    }
}

impl From<Yaml> for Scheduler {
    fn from(yaml: Yaml) -> Self {
        let name = yaml.get_name();
        let depends: Option<String>;
        let branch: String;
        let password: String;
        let mut triggers = vec![];
        let mut builders = vec![];

        if !yaml.has_section("builders") {
            error!("There was an error creating a scheduler: The 'builders' section is not specified for '{}'", name);
            exit(1);
        }

        if yaml.has_section("depends") {
            depends = Some(unwrap(&yaml, "depends"));
            branch = String::from("");
            password = String::from("");
        } else {
            for section in ["branch", "password", "triggers"].iter() {
                if !yaml.has_section(section) {
                    error!("There was an error creating a scheduler: The '{}' section is not specified for '{}'", section, name);
                    exit(1);
                }
            }

            depends = None;
            branch = unwrap(&yaml, "branch");
            password = unwrap(&yaml, "password");

            for trigger in yaml.get_section("triggers").unwrap() {
                triggers.push(trigger.to_string());
            }
        }

        for builder in yaml.get_section("builders").unwrap() {
            builders.push(builder.to_string());
        }

        Scheduler::new(name, depends, password, branch, triggers, builders)
    }
}