diff --git a/nix/ofborg-carnix.nix b/nix/ofborg-carnix.nix index a39cb3d..259a0b8 100644 --- a/nix/ofborg-carnix.nix +++ b/nix/ofborg-carnix.nix @@ -378,9 +378,9 @@ let kernel = buildPlatform.parsed.kernel.name; sha256 = "1y6qnd9r8ga6y8mvlabdrr73nc8cshjjlzbvnanzyj9b8zzkfwk2"; inherit dependencies buildDependencies features; }; - ofborg_0_1_0_ = { dependencies?[], buildDependencies?[], features?[] }: buildRustCrate { + ofborg_0_1_1_ = { dependencies?[], buildDependencies?[], features?[] }: buildRustCrate { crateName = "ofborg"; - version = "0.1.0"; + version = "0.1.1"; authors = [ "Graham Christensen " ]; src = ./../ofborg; inherit dependencies buildDependencies features; @@ -1048,24 +1048,24 @@ rec { dependencies = [ libc_0_2_36 ]; }; libc_0_2_36_features."default".from_num_cpus_1_8_0__default = true; - ofborg_0_1_0 = ofborg_0_1_0_ rec { + ofborg_0_1_1 = ofborg_0_1_1_ rec { dependencies = [ amqp_0_1_0 env_logger_0_4_3 fs2_0_4_3 hubcaps_0_3_16 hyper_0_10_13 hyper_native_tls_0_2_4 log_0_3_8 lru_cache_0_1_1 md5_0_3_6 serde_1_0_27 serde_derive_1_0_27 serde_json_1_0_9 tempfile_2_2_0 uuid_0_4_0 ]; }; - amqp_0_1_0_features."default".from_ofborg_0_1_0__default = true; - env_logger_0_4_3_features."default".from_ofborg_0_1_0__default = true; - fs2_0_4_3_features."default".from_ofborg_0_1_0__default = true; - hubcaps_0_3_16_features."default".from_ofborg_0_1_0__default = true; - hyper_0_10_13_features."default".from_ofborg_0_1_0__default = true; - hyper_native_tls_0_2_4_features."default".from_ofborg_0_1_0__default = true; - log_0_3_8_features."default".from_ofborg_0_1_0__default = true; - lru_cache_0_1_1_features."default".from_ofborg_0_1_0__default = true; - md5_0_3_6_features."default".from_ofborg_0_1_0__default = true; - serde_1_0_27_features."default".from_ofborg_0_1_0__default = true; - serde_derive_1_0_27_features."default".from_ofborg_0_1_0__default = true; - serde_json_1_0_9_features."default".from_ofborg_0_1_0__default = true; - tempfile_2_2_0_features."default".from_ofborg_0_1_0__default = true; - uuid_0_4_0_features."v4".from_ofborg_0_1_0 = true; - uuid_0_4_0_features."default".from_ofborg_0_1_0__default = true; + amqp_0_1_0_features."default".from_ofborg_0_1_1__default = true; + env_logger_0_4_3_features."default".from_ofborg_0_1_1__default = true; + fs2_0_4_3_features."default".from_ofborg_0_1_1__default = true; + hubcaps_0_3_16_features."default".from_ofborg_0_1_1__default = true; + hyper_0_10_13_features."default".from_ofborg_0_1_1__default = true; + hyper_native_tls_0_2_4_features."default".from_ofborg_0_1_1__default = true; + log_0_3_8_features."default".from_ofborg_0_1_1__default = true; + lru_cache_0_1_1_features."default".from_ofborg_0_1_1__default = true; + md5_0_3_6_features."default".from_ofborg_0_1_1__default = true; + serde_1_0_27_features."default".from_ofborg_0_1_1__default = true; + serde_derive_1_0_27_features."default".from_ofborg_0_1_1__default = true; + serde_json_1_0_9_features."default".from_ofborg_0_1_1__default = true; + tempfile_2_2_0_features."default".from_ofborg_0_1_1__default = true; + uuid_0_4_0_features."v4".from_ofborg_0_1_1 = true; + uuid_0_4_0_features."default".from_ofborg_0_1_1__default = true; openssl_0_9_23 = openssl_0_9_23_ rec { dependencies = [ bitflags_0_9_1 foreign_types_0_3_2 lazy_static_1_0_0 libc_0_2_36 openssl_sys_0_9_24 ]; features = mkFeatures openssl_0_9_23_features; diff --git a/ofborg/Cargo.lock b/ofborg/Cargo.lock index 9a10e1d..d472b62 100644 --- a/ofborg/Cargo.lock +++ b/ofborg/Cargo.lock @@ -374,7 +374,7 @@ dependencies = [ [[package]] name = "ofborg" -version = "0.1.0" +version = "0.1.1" dependencies = [ "amqp 0.1.0 (git+https://github.com/grahamc/rust-amqp.git)", "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/ofborg/Cargo.toml b/ofborg/Cargo.toml index 3329099..fdc84ae 100644 --- a/ofborg/Cargo.toml +++ b/ofborg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ofborg" -version = "0.1.0" +version = "0.1.1" authors = ["Graham Christensen "] [dependencies] diff --git a/ofborg/src/bin/github-comment-poster.rs b/ofborg/src/bin/github-comment-poster.rs new file mode 100644 index 0000000..835c5fc --- /dev/null +++ b/ofborg/src/bin/github-comment-poster.rs @@ -0,0 +1,89 @@ +extern crate ofborg; +extern crate amqp; +extern crate env_logger; + +extern crate hyper; +extern crate hubcaps; +extern crate hyper_native_tls; + + +use std::env; + +use amqp::Basic; + +use ofborg::config; +use ofborg::worker; +use ofborg::tasks; +use ofborg::easyamqp; +use ofborg::easyamqp::TypedWrappers; + + +fn main() { + let cfg = config::load(env::args().nth(1).unwrap().as_ref()); + ofborg::setup_log(); + + let mut session = easyamqp::session_from_config(&cfg.rabbitmq).unwrap(); + let mut channel = session.open_channel(1).unwrap(); + + channel + .declare_exchange(easyamqp::ExchangeConfig { + exchange: "build-results".to_owned(), + exchange_type: easyamqp::ExchangeType::Fanout, + passive: false, + durable: true, + auto_delete: false, + no_wait: false, + internal: false, + arguments: None, + }) + .unwrap(); + + channel + .declare_queue(easyamqp::QueueConfig { + queue: "build-results".to_owned(), + passive: false, + durable: true, + exclusive: false, + auto_delete: false, + no_wait: false, + arguments: None, + }) + .unwrap(); + + channel + .bind_queue(easyamqp::BindQueueConfig { + queue: "build-results".to_owned(), + exchange: "build-results".to_owned(), + routing_key: None, + no_wait: false, + arguments: None, + }) + .unwrap(); + + channel.basic_prefetch(1).unwrap(); + channel + .consume( + worker::new(tasks::githubcommentposter::GitHubCommentPoster::new( + cfg.github(), + )), + easyamqp::ConsumeConfig { + queue: "build-results".to_owned(), + consumer_tag: format!("{}-github-comment-poster", cfg.whoami()), + no_local: false, + no_ack: false, + no_wait: false, + exclusive: false, + arguments: None, + }, + ) + .unwrap(); + + channel.start_consuming(); + + println!("Finished consuming?"); + + channel.close(200, "Bye").unwrap(); + println!("Closed the channel"); + session.close(200, "Good Bye"); + println!("Closed the session... EOF"); +} diff --git a/ofborg/src/easyamqp.rs b/ofborg/src/easyamqp.rs index 5d55cbf..e3c57ed 100644 --- a/ofborg/src/easyamqp.rs +++ b/ofborg/src/easyamqp.rs @@ -48,6 +48,53 @@ pub struct ConsumeConfig { pub arguments: Option, } +pub struct BindQueueConfig { + /// Specifies the name of the queue to bind. + /// + /// The client MUST either specify a queue name or have previously + /// declared a queue on the same channel Error code: not-found + /// + /// The client MUST NOT attempt to bind a queue that does not + /// exist. Error code: not-found + pub queue: String, + + /// Name of the exchange to bind to. + /// + /// A client MUST NOT be allowed to bind a queue to a non-existent + /// exchange. Error code: not-found + /// + /// The server MUST accept a blank exchange name to mean the + /// default exchange. + pub exchange: String, + + /// Specifies the routing key for the binding. The routing key is + /// used for routing messages depending on the exchange + /// configuration. Not all exchanges use a routing key - refer to + /// the specific exchange documentation. If the queue name is + /// empty, the server uses the last queue declared on the channel. + /// If the routing key is also empty, the server uses this queue + /// name for the routing key as well. If the queue name is + /// provided but the routing key is empty, the server does the + /// binding with that empty routing key. The meaning of empty + /// routing keys depends on the exchange implementation. + /// + /// If a message queue binds to a direct exchange using routing + /// key K and a publisher sends the exchange a message with + /// routing key R, then the message MUST be passed to the message + /// queue if K = R. + pub routing_key: Option, + + /// If set, the server will not respond to the method. The client + /// should not wait for a reply method. If the server could not + /// complete the method it will raise a channel or connection + /// exception. + pub no_wait: bool, + + /// A set of arguments for the binding. The syntax and semantics + /// of these arguments depends on the exchange class. + pub arguments: Option, +} + pub enum ExchangeType { Topic, Headers, @@ -78,7 +125,7 @@ pub struct ExchangeConfig { /// The exchange name consists of a non-empty sequence of these /// characters: letters, digits, hyphen, underscore, period, or /// colon. Error code: precondition-failed - exchange: String, + pub exchange: String, /// Each exchange belongs to one of a set of exchange types /// implemented by the server. The exchange types define the @@ -93,7 +140,7 @@ pub struct ExchangeConfig { /// /// The client MUST NOT attempt to declare an exchange with a type /// that the server does not support. Error code: command-invalid - exchange_type: ExchangeType, + pub exchange_type: ExchangeType, /// If set, the server will reply with Declare-Ok if the exchange /// already exists with the same name, and raise an error if not. @@ -112,7 +159,7 @@ pub struct ExchangeConfig { /// and arguments fields. The server MUST respond with Declare-Ok /// if the requested exchange matches these fields, and MUST raise /// a channel exception if not. - passive: bool, + pub passive: bool, /// If set when creating a new exchange, the exchange will be /// marked as durable. Durable exchanges remain active when a @@ -120,7 +167,7 @@ pub struct ExchangeConfig { /// are purged if/when a server restarts. /// /// The server MUST support both durable and transient exchanges. - durable: bool, + pub durable: bool, /// If set, the exchange is deleted when all queues have finished /// using it. @@ -134,23 +181,105 @@ pub struct ExchangeConfig { /// /// The server MUST ignore the auto-delete field if the exchange /// already exists. - auto_delete: bool, + pub auto_delete: bool, /// If set, the exchange may not be used directly by publishers, /// but only when bound to other exchanges. Internal exchanges are /// used to construct wiring that is not visible to applications. - internal: bool, + pub internal: bool, /// If set, the server will not respond to the method. The client /// should not wait for a reply method. If the server could not /// complete the method it will raise a channel or connection /// exception. - nowait: bool, + pub no_wait: bool, /// A set of arguments for the declaration. The syntax and /// semantics of these arguments depends on the server /// implementation. - arguments: Option, + pub arguments: Option, +} + +pub struct QueueConfig { + /// The queue name MAY be empty, in which case the server MUST + /// create a new queue with a unique generated name and return + /// this to the client in the Declare-Ok method. + /// + /// Queue names starting with "amq." are reserved for pre-declared + /// and standardised queues. The client MAY declare a queue + /// starting with "amq." if the passive option is set, or the + /// queue already exists. Error code: access-refused + /// + /// The queue name can be empty, or a sequence of these + /// characters: letters, digits, hyphen, underscore, period, or + /// colon. Error code: precondition-failed + pub queue: String, + + /// If set, the server will reply with Declare-Ok if the queue + /// already exists with the same name, and raise an error if not. + /// The client can use this to check whether a queue exists + /// without modifying the server state. When set, all other + /// method fields except name and no-wait are ignored. A declare + /// with both passive and no-wait has no effect. Arguments are + /// compared for semantic equivalence. + /// + /// The client MAY ask the server to assert that a queue exists + /// without creating the queue if not. If the queue does not + /// exist, the server treats this as a failure. Error code: + /// not-found + /// + /// If not set and the queue exists, the server MUST check that + /// the existing queue has the same values for durable, exclusive, + /// auto-delete, and arguments fields. The server MUST respond + /// with Declare-Ok if the requested queue matches these fields, + /// and MUST raise a channel exception if not. + pub passive: bool, + + /// If set when creating a new queue, the queue will be marked as + /// durable. Durable queues remain active when a server restarts. + /// Non-durable queues (transient queues) are purged if/when a + /// server restarts. Note that durable queues do not necessarily + /// hold persistent messages, although it does not make sense to + /// send persistent messages to a transient queue. + /// + /// The server MUST recreate the durable queue after a restart. + /// + /// The server MUST support both durable and transient queues. + pub durable: bool, + + /// Exclusive queues may only be accessed by the current + /// connection, and are deleted when that connection closes. + /// Passive declaration of an exclusive queue by other connections + /// are not allowed. + /// + /// The server MUST support both exclusive (private) and + /// non-exclusive (shared) queues. + /// The client MAY NOT attempt to use a queue that was declared as + /// exclusive by another still-open connection. Error code: + /// resource-locked + pub exclusive: bool, + + /// If set, the queue is deleted when all consumers have finished + /// using it. The last consumer can be cancelled either explicitly + /// or because its channel is closed. If there was no consumer + /// ever on the queue, it won't be deleted. Applications can + /// explicitly delete auto-delete queues using the Delete method + /// as normal. + /// + /// The server MUST ignore the auto-delete field if the queue + /// already exists. + pub auto_delete: bool, + + /// If set, the server will not respond to the method. The client + /// should not wait for a reply method. If the server could not + /// complete the method it will raise a channel or connection + /// exception. + pub no_wait: bool, + + /// A set of arguments for the declaration. The syntax and + /// semantics of these arguments depends on the server + /// implementation. + pub arguments: Option, } pub fn session_from_config(config: &RabbitMQConfig) -> Result { @@ -191,12 +320,20 @@ pub trait TypedWrappers { where T: amqp::Consumer + 'static; - fn declare_exchange( + fn declare_exchange( &mut self, config: ExchangeConfig, - ) -> Result - where - T: amqp::Consumer + 'static; + ) -> Result; + + fn declare_queue( + &mut self, + config: QueueConfig, + ) -> Result; + + fn bind_queue( + &mut self, + config: BindQueueConfig, + ) -> Result; } impl TypedWrappers for amqp::Channel { @@ -216,13 +353,10 @@ impl TypedWrappers for amqp::Channel { ) } - fn declare_exchange( + fn declare_exchange( &mut self, config: ExchangeConfig, - ) -> Result - where - T: amqp::Consumer + 'static, - { + ) -> Result { self.exchange_declare( config.exchange, config.exchange_type.into(), @@ -230,7 +364,36 @@ impl TypedWrappers for amqp::Channel { config.durable, config.auto_delete, config.internal, - config.nowait, + config.no_wait, + config.arguments.unwrap_or(amqp::Table::new()), + ) + } + + + fn declare_queue( + &mut self, + config: QueueConfig, + ) -> Result { + self.queue_declare( + config.queue, + config.passive, + config.durable, + config.exclusive, + config.auto_delete, + config.no_wait, + config.arguments.unwrap_or(amqp::Table::new()), + ) + } + + fn bind_queue( + &mut self, + config: BindQueueConfig, + ) -> Result { + self.queue_bind( + config.queue, + config.exchange, + config.routing_key.unwrap_or("".to_owned()), + config.no_wait, config.arguments.unwrap_or(amqp::Table::new()), ) } diff --git a/ofborg/src/message/buildresult.rs b/ofborg/src/message/buildresult.rs index 2e6a8d3..c386a8e 100644 --- a/ofborg/src/message/buildresult.rs +++ b/ofborg/src/message/buildresult.rs @@ -6,5 +6,6 @@ pub struct BuildResult { pub pr: Pr, pub system: String, pub output: Vec, + pub attempt_id: Option, pub success: bool, } diff --git a/ofborg/src/tasks/build.rs b/ofborg/src/tasks/build.rs index 778f597..7b2a52a 100644 --- a/ofborg/src/tasks/build.rs +++ b/ofborg/src/tasks/build.rs @@ -111,7 +111,7 @@ impl<'a, 'b> JobActions<'a, 'b> { pr: self.job.pr.clone(), system: self.system.clone(), output: vec![String::from("Merge failed")], - + attempt_id: Some(self.attempt_id.clone()), success: false, }; @@ -171,6 +171,7 @@ impl<'a, 'b> JobActions<'a, 'b> { pr: self.job.pr.clone(), system: self.system.clone(), output: lines, + attempt_id: Some(self.attempt_id.clone()), success: success, }; diff --git a/ofborg/src/tasks/githubcommentposter.rs b/ofborg/src/tasks/githubcommentposter.rs new file mode 100644 index 0000000..ae93ce0 --- /dev/null +++ b/ofborg/src/tasks/githubcommentposter.rs @@ -0,0 +1,276 @@ +extern crate amqp; +extern crate env_logger; + +use serde_json; + +use hubcaps; +use ofborg::message::buildresult::BuildResult; +use ofborg::worker; +use amqp::protocol::basic::{Deliver, BasicProperties}; + + +pub struct GitHubCommentPoster { + github: hubcaps::Github, +} + +impl GitHubCommentPoster { + pub fn new(github: hubcaps::Github) -> GitHubCommentPoster { + return GitHubCommentPoster { github: github }; + } +} + +impl worker::SimpleWorker for GitHubCommentPoster { + type J = BuildResult; + + fn msg_to_job( + &mut self, + _: &Deliver, + _: &BasicProperties, + body: &Vec, + ) -> Result { + return match serde_json::from_slice(body) { + Ok(e) => Ok(e), + Err(e) => { + Err(format!( + "Failed to deserialize BuildResult: {:?}, err: {:}", + String::from_utf8_lossy(&body.clone()), + e + )) + } + }; + } + + fn consumer(&mut self, job: &BuildResult) -> worker::Actions { + let comment = hubcaps::comments::CommentOptions { body: result_to_comment(&job) }; + + let comment_attempt = self.github + .repo(job.repo.owner.clone(), job.repo.name.clone()) + .pulls() + .get(job.pr.number) + .comments() + .create(&comment); + + match comment_attempt { + Ok(comment) => { + info!( + "Successfully sent {:?} to {}", + comment, + job.pr.number, + ) + } + Err(err) => { + info!( + "Failed to send comment {:?} to {}", + err, + job.pr.number, + ) + } + } + + return vec![worker::Action::Ack]; + } +} + +fn result_to_comment(result: &BuildResult) -> String { + let mut reply: Vec = vec![]; + + reply.push(format!( + "{} on {} [(full log)](https://logs.nix.gsc.io/?key={}/{}.{}&attempt_id={})", + (match result.success { + true => "Success", + false => "Failure", + }), + result.system, + &result.repo.owner.to_lowercase(), + &result.repo.name.to_lowercase(), + result.pr.number, + (match result.attempt_id { + Some(ref attempt_id) => &attempt_id, + None => "none", + }) + )); + reply.push("".to_owned()); + reply.push( + "
Partial log (click to expand)

".to_owned(), + ); + reply.push("".to_owned()); + reply.push("```".to_owned()); + reply.extend(result.output.clone()); + reply.push("```".to_owned()); + reply.push("

".to_owned()); + reply.push("".to_owned()); + reply.push("".to_owned()); + + reply.join("\n") +} + +#[cfg(test)] +mod tests { + use super::*; + use message::{Pr, Repo}; + + #[test] + pub fn test_passing_build() { + let result = BuildResult { + repo: Repo { + clone_url: "https://github.com/nixos/nixpkgs.git".to_owned(), + full_name: "NixOS/nixpkgs".to_owned(), + owner: "NixOS".to_owned(), + name: "nixpkgs".to_owned(), + }, + pr: Pr { + head_sha: "abc123".to_owned(), + number: 2345, + target_branch: Some("master".to_owned()), + }, + output: vec![ + "make[2]: Entering directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline'".to_owned(), + "make[2]: Nothing to be done for 'install'.".to_owned(), + "make[2]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline'".to_owned(), + "make[1]: Nothing to be done for 'install-target'.".to_owned(), + "make[1]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1'".to_owned(), + "removed '/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1/share/info/bfd.info'".to_owned(), + "post-installation fixup".to_owned(), + "strip is /nix/store/5a88zk3jgimdmzg8rfhvm93kxib3njf9-cctools-binutils-darwin/bin/strip".to_owned(), + "patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1".to_owned(), + "/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1".to_owned(), + ], + attempt_id: Some("neatattemptid".to_owned()), + system: "x86_64-linux".to_owned(), + success: true, + }; + + assert_eq!( + &result_to_comment(&result), + "Success on x86_64-linux [(full log)](https://logs.nix.gsc.io/?key=nixos/nixpkgs.2345&attempt_id=neatattemptid) + +
Partial log (click to expand)

+ +``` +make[2]: Entering directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline' +make[2]: Nothing to be done for 'install'. +make[2]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline' +make[1]: Nothing to be done for 'install-target'. +make[1]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1' +removed '/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1/share/info/bfd.info' +post-installation fixup +strip is /nix/store/5a88zk3jgimdmzg8rfhvm93kxib3njf9-cctools-binutils-darwin/bin/strip +patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1 +/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1 +``` +

+ +" + ); + } + + #[test] + pub fn test_failing_build() { + let result = BuildResult { + repo: Repo { + clone_url: "https://github.com/nixos/nixpkgs.git".to_owned(), + full_name: "NixOS/nixpkgs".to_owned(), + owner: "NixOS".to_owned(), + name: "nixpkgs".to_owned(), + }, + pr: Pr { + head_sha: "abc123".to_owned(), + number: 2345, + target_branch: Some("master".to_owned()), + }, + output: vec![ + "make[2]: Entering directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline'".to_owned(), + "make[2]: Nothing to be done for 'install'.".to_owned(), + "make[2]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline'".to_owned(), + "make[1]: Nothing to be done for 'install-target'.".to_owned(), + "make[1]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1'".to_owned(), + "removed '/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1/share/info/bfd.info'".to_owned(), + "post-installation fixup".to_owned(), + "strip is /nix/store/5a88zk3jgimdmzg8rfhvm93kxib3njf9-cctools-binutils-darwin/bin/strip".to_owned(), + "patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1".to_owned(), + "/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1".to_owned(), + ], + attempt_id: Some("neatattemptid".to_owned()), + system: "x86_64-linux".to_owned(), + success: false, + }; + + assert_eq!( + &result_to_comment(&result), + "Failure on x86_64-linux [(full log)](https://logs.nix.gsc.io/?key=nixos/nixpkgs.2345&attempt_id=neatattemptid) + +
Partial log (click to expand)

+ +``` +make[2]: Entering directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline' +make[2]: Nothing to be done for 'install'. +make[2]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline' +make[1]: Nothing to be done for 'install-target'. +make[1]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1' +removed '/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1/share/info/bfd.info' +post-installation fixup +strip is /nix/store/5a88zk3jgimdmzg8rfhvm93kxib3njf9-cctools-binutils-darwin/bin/strip +patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1 +/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1 +``` +

+ +" + ); + } + + #[test] + pub fn test_failing_build_no_attempt_id() { + let result = BuildResult { + repo: Repo { + clone_url: "https://github.com/nixos/nixpkgs.git".to_owned(), + full_name: "NixOS/nixpkgs".to_owned(), + owner: "NixOS".to_owned(), + name: "nixpkgs".to_owned(), + }, + pr: Pr { + head_sha: "abc123".to_owned(), + number: 2345, + target_branch: Some("master".to_owned()), + }, + output: vec![ + "make[2]: Entering directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline'".to_owned(), + "make[2]: Nothing to be done for 'install'.".to_owned(), + "make[2]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline'".to_owned(), + "make[1]: Nothing to be done for 'install-target'.".to_owned(), + "make[1]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1'".to_owned(), + "removed '/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1/share/info/bfd.info'".to_owned(), + "post-installation fixup".to_owned(), + "strip is /nix/store/5a88zk3jgimdmzg8rfhvm93kxib3njf9-cctools-binutils-darwin/bin/strip".to_owned(), + "patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1".to_owned(), + "/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1".to_owned(), + ], + attempt_id: None, + system: "x86_64-linux".to_owned(), + success: false, + }; + + assert_eq!( + &result_to_comment(&result), + "Failure on x86_64-linux [(full log)](https://logs.nix.gsc.io/?key=nixos/nixpkgs.2345&attempt_id=none) + +
Partial log (click to expand)

+ +``` +make[2]: Entering directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline' +make[2]: Nothing to be done for 'install'. +make[2]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1/readline' +make[1]: Nothing to be done for 'install-target'. +make[1]: Leaving directory '/private/tmp/nix-build-gdb-8.1.drv-0/gdb-8.1' +removed '/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1/share/info/bfd.info' +post-installation fixup +strip is /nix/store/5a88zk3jgimdmzg8rfhvm93kxib3njf9-cctools-binutils-darwin/bin/strip +patching script interpreter paths in /nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1 +/nix/store/pcja75y9isdvgz5i00pkrpif9rxzxc29-gdb-8.1 +``` +

+ +" + ); + } +} diff --git a/ofborg/src/tasks/mod.rs b/ofborg/src/tasks/mod.rs index eb6ed18..d735e60 100644 --- a/ofborg/src/tasks/mod.rs +++ b/ofborg/src/tasks/mod.rs @@ -2,4 +2,5 @@ pub mod build; pub mod massrebuilder; pub mod githubcommentfilter; +pub mod githubcommentposter; pub mod log_message_collector; diff --git a/php/poster.php b/php/poster.php deleted file mode 100644 index b42624c..0000000 --- a/php/poster.php +++ /dev/null @@ -1,93 +0,0 @@ -channel(); -$channel->basic_qos(null, 1, true); - - -$channel->exchange_declare('build-results', 'fanout', false, true, false); -$channel->queue_bind('build-results', 'build-results', ''); - -list($queueName, , ) = $channel->queue_declare('build-results', - false, true, false, false); - -function runner($msg) { - $body = json_decode($msg->body); - - $num = $body->pr->number; - if ($body->success) { - echo "yay! $num passed!\n"; - } else { - echo "Yikes, $num failed\n"; - } - - reply_to_issue($body, implode("\n", $body->output), $body->success, $body->system); - - var_dump($body->success); - - $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']); -} - -function reply_to_issue($body, $output, $success, $system) { - $num = $body->pr->number; - $owner = $body->repo->owner; - $repo = $body->repo->name; - $event = $success ? 'APPROVE' : 'COMMENT'; - $passfail = $success ? "Success" : "Failure"; - - echo "Sending $event to $owner/$repo#$num with " . $passfail . " on $system\n"; - - $client = gh_client(); - $pr = $client->api('pull_request')->show( - $owner, - $repo, - $num - ); - - if ($pr['state'] == 'closed') { - $event = 'COMMENT'; - } - - // With multiple archs, it is better to not approve at all, since - // another arch may come in later with a failure. - // - By request of Domen - $event = 'COMMENT'; - - $sha = $body->pr->head_sha; - echo "On sha: $sha\n"; - echo "Body:\n"; - echo $output; - echo "\n\n"; - - $fullloglink = strtolower("https://logs.nix.gsc.io/?key=$owner/$repo.$num"); - - $client->api('pull_request')->reviews()->create( - $owner, - $repo, - $num, - array( - 'body' => - "$passfail on $system [(full log)]($fullloglink)\n\n". - "
Partial log (click to expand)

\n\n". - "```\n$output\n```\n". - "

\n\n", - 'event' => $event, - 'commit_id' => $sha, - )); -} - - -function outrunner($msg) { - return runner($msg); -} - - -$consumerTag = 'poster-' . getmypid(); -$channel->basic_consume($queueName, $consumerTag, false, false, false, false, 'outrunner'); -while(count($channel->callbacks)) { - $channel->wait(); -}