[snowpatch] [PATCH v3 3/3] settings: improve parsing of jobs
Russell Currey
ruscur at russell.cc
Mon Mar 27 15:02:40 AEDT 2017
On Fri, 2017-03-24 at 14:28 +1100, Andrew Donnellan wrote:
> Job entries in the config file are a mixture of parameters which should be
> passed straight through to Jenkins, and flags that have meaning to
> snowpatch.
>
> I don't really feel like splitting these two things apart in the config, as
> we already have enough levels of nesting. Instead, add our own deserialize
> method to do this more intelligently. This also serves as an example of how
> to do more advanced parsing such that we can reject invalid values at parse
> time.
>
> Thanks-to: David Tolnay <dtolnay at gmail.com> # help with boilerplate code
> Signed-off-by: Andrew Donnellan <andrew.donnellan at au1.ibm.com>
> ---
> v2->v3:
> * I figure I should just throw this patch in now...
Kinda sketchy just throwing this in but whatever
Acked-by: Russell Currey <ruscur at russell.cc>
>
> ---
> src/main.rs | 28 ++++++++---------
> src/settings.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
> -
> 2 files changed, 98 insertions(+), 25 deletions(-)
>
> diff --git a/src/main.rs b/src/main.rs
> index 6ba550e..a0e6576 100644
> --- a/src/main.rs
> +++ b/src/main.rs
> @@ -108,22 +108,18 @@ fn run_tests(settings: &Config, client: Arc<Client>,
> project: &Project, tag: &st
> token: settings.jenkins.token.clone(),
> };
> let project = project.clone();
> - for job_params in &project.jobs {
> - let job_name = job_params.get("job").unwrap();
> - let job_title = settings::get_job_title(job_params);
> + for job in &project.jobs {
> let mut jenkins_params = Vec::<(&str, &str)>::new();
> - for (param_name, param_value) in job_params.iter() {
> + for (param_name, param_value) in job.parameters.iter() {
> + // TODO(ajd): do this more neatly
> debug!("Param name {}, value {}", ¶m_name, ¶m_value);
> - match param_name.as_ref() {
> - // TODO: Validate special parameter names in config at start
> of program
> - "job" | "title" => { },
> - "remote" => jenkins_params.push((param_value,
> &project.remote_uri)),
> - "branch" => jenkins_params.push((param_value, tag)),
> - _ => jenkins_params.push((param_name, param_value)),
> - }
> + jenkins_params.push((param_name, param_value));
> }
> - info!("Starting job: {}", &job_title);
> - let res = jenkins.start_test(job_name, jenkins_params)
> + jenkins_params.push((&job.remote, &project.remote_uri));
> + jenkins_params.push((&job.branch, tag));
> +
> + info!("Starting job: {}", &job.title);
> + let res = jenkins.start_test(&job.job, jenkins_params)
> .unwrap_or_else(|err| panic!("Starting Jenkins test failed: {}",
> err));
> debug!("{:?}", &res);
> let build_url_real;
> @@ -137,12 +133,12 @@ fn run_tests(settings: &Config, client: Arc<Client>,
> project: &Project, tag: &st
> debug!("Build URL: {}", build_url_real);
> jenkins.wait_build(&build_url_real);
> let test_result = jenkins.get_build_result(&build_url_real).unwrap();
> - info!("Jenkins job for {}/{} complete.", branch_name, job_title);
> + info!("Jenkins job for {}/{} complete.", branch_name, job.title);
> results.push(TestResult {
> - test_name: format!("Test {} on branch {}", job_title.to_string(),
> + test_name: format!("Test {} on branch {}", job.title,
> branch_name.to_string()).to_string(),
> state: test_result,
> - url: Some(jenkins.get_results_url(&build_url_real, job_params)),
> + url: Some(jenkins.get_results_url(&build_url_real,
> &job.parameters)),
> summary: Some("TODO: get this summary from Jenkins".to_string()),
> });
> }
> diff --git a/src/settings.rs b/src/settings.rs
> index 826f792..44c321c 100644
> --- a/src/settings.rs
> +++ b/src/settings.rs
> @@ -16,8 +16,11 @@
>
> use toml;
>
> +use serde::de::{self, MapVisitor, Visitor, Deserializer, Deserialize};
> +
> use git2::{Repository, Error};
>
> +use std::fmt;
> use std::fs::File;
> use std::io::Read;
> use std::collections::BTreeMap;
> @@ -58,8 +61,8 @@ pub struct Project {
> pub test_all_branches: Option<bool>,
> pub remote_name: String,
> pub remote_uri: String,
> - pub jobs: Vec<BTreeMap<String, String>>,
> - pub push_results: bool
> + pub jobs: Vec<Job>,
> + pub push_results: bool,
> }
>
> impl Project {
> @@ -68,6 +71,87 @@ impl Project {
> }
> }
>
> +#[derive(Clone)]
> +pub struct Job {
> + pub job: String,
> + pub title: String,
> + pub remote: String,
> + pub branch: String,
> + pub parameters: BTreeMap<String, String>,
> +}
> +
> +impl Deserialize for Job {
> + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
> + where D: Deserializer
> + {
> + struct JobVisitor;
> +
> + impl Visitor for JobVisitor {
> + type Value = Job;
> +
> + fn expecting(&self, formatter: &mut fmt::Formatter) ->
> fmt::Result {
> + formatter.write_str("struct Job with arbitrary fields")
> + }
> +
> + fn visit_map<V>(self, mut visitor: V) -> Result<Job, V::Error>
> + where V: MapVisitor
> + {
> + let mut job = None;
> + let mut title = None;
> + let mut remote = None;
> + let mut branch = None;
> + let mut parameters = BTreeMap::new();
> + while let Some(key) = visitor.visit_key::<String>()? {
> + match key.as_str() {
> + "job" => {
> + if job.is_some() {
> + return
> Err(de::Error::duplicate_field("job"));
> + }
> + job = Some(visitor.visit_value()?);
> + }
> + "title" => {
> + if title.is_some() {
> + return
> Err(de::Error::duplicate_field("title"));
> + }
> + title = Some(visitor.visit_value()?);
> + }
> + "remote" => {
> + if remote.is_some() {
> + return
> Err(de::Error::duplicate_field("remote"));
> + }
> + remote = Some(visitor.visit_value()?);
> + }
> + "branch" => {
> + if branch.is_some() {
> + return
> Err(de::Error::duplicate_field("branch"));
> + }
> + branch = Some(visitor.visit_value()?);
> + }
> + _ => {
> + parameters.insert(key, visitor.visit_value()?);
> + }
> + }
> + }
> +
> + let job: String = job.ok_or_else(||
> de::Error::missing_field("job"))?;
> + let remote = remote.ok_or_else(||
> de::Error::missing_field("remote"))?;
> + let branch = branch.ok_or_else(||
> de::Error::missing_field("branch"))?;
> + let title = title.unwrap_or(job.clone());
> +
> + Ok(Job {
> + job: job,
> + title: title,
> + remote: remote,
> + branch: branch,
> + parameters: parameters,
> + })
> + }
> + }
> +
> + deserializer.deserialize_map(JobVisitor)
> + }
> +}
> +
> #[derive(Deserialize, Clone)]
> pub struct Config {
> pub git: Git,
> @@ -76,13 +160,6 @@ pub struct Config {
> pub projects: BTreeMap<String, Project>
> }
>
> -pub fn get_job_title(job: &BTreeMap<String, String>) -> String {
> - match job.get("title") {
> - Some(title) => title.to_string(),
> - None => job.get("job").unwrap().to_string()
> - }
> -}
> -
> pub fn parse(path: &str) -> Config {
> let mut toml_config = String::new();
>
More information about the snowpatch
mailing list