roxanne 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c5d851cce33eec10554348917d888de2850376f4
4
+ data.tar.gz: 8170b18990fa9ad11befe6a0da395acdf487cda8
5
+ SHA512:
6
+ metadata.gz: ea7d837226ee30baa49c0efec2b78242803de3190d173abc2b2c1ffc4cf9db4321549dff008e6b9b56fb8f6ee5f10328f2424b73a49d08937ea4c4fa49e0ba86
7
+ data.tar.gz: cc624af0b25c3717e0a8c4a247d3c18110308e2700c1c2fa538f634311e0d6a39e851db05d19eb2dd2a270fd7c3ffc696c00b75656c352a9d956590390383f36
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ Gemfile.lock
2
+ roxanne.log
3
+ roxanne.pid
4
+ .*.swp
5
+ pkg
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.1
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.1
6
+ notifications:
7
+ hipchat:
8
+ rooms:
9
+ secure: B+SptkYk6J9+PPJnqzA8S4hce6veT/bHciATE5MEA6fBd4gZQ2YMACgdaPqqzv1TU2Lr725uKFRpMQkE/E5B6oLGBcck7tAzZccKKSLiXyKMxgpyEcw+WC2laGhqqnfzd83iYjQujMy6kwksTknmgLV34HIuMUcyREYovEIxPgA=
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in roxanne.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard :minitest do
2
+ # with Minitest::Spec
3
+ watch(%r{^spec/(.*)_spec\.rb$})
4
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
5
+ watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
6
+ watch(%r{^spec/support/(.*)\.rb$}) { 'spec' }
7
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jef Mathiot
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # Roxanne
2
+
3
+ Roxanne allows you to aggregate the status of Continuous Integration jobs or other sources and to
4
+ publish the results on a BigVisibleThing™ (traffic light, lava lamp, whatever).
5
+
6
+ [![Build Status](https://travis-ci.org/servebox/roxanne.png)](https://travis-ci.org/servebox/roxanne)
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'roxanne'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install roxanne
21
+
22
+ ## Usage
23
+
24
+ ### Configuration
25
+
26
+ Configuration is stored in a YAML file. By default, Roxanne will search for a `config/config.yml`
27
+ file.
28
+
29
+ #### Activation
30
+
31
+ Roxanne is configured by default to only be active between 9am and 20pm during working days. You
32
+ may use the `activation` section to change the default behavior:
33
+
34
+ ```yaml
35
+ roxanne:
36
+ activation:
37
+ # Active from monday to wednesday
38
+ days: !ruby/range 1..3
39
+ # 07:00 to 14:59
40
+ timerange: !ruby/range 7...15
41
+ ```
42
+
43
+ #### Consumers
44
+
45
+ Use the `consumers` section to specify consumers configuration:
46
+
47
+ ```yaml
48
+ roxanne:
49
+ consumers:
50
+ jenkins:
51
+ class: "Roxanne::Jenkins::Consumer"
52
+ host: "192.168.20.30"
53
+ port: 8080
54
+ use_ssl: false
55
+ path: "/api/json"
56
+ ```
57
+
58
+ #### Publisher
59
+
60
+ Use the `publisher` section to specify consumers configuration:
61
+
62
+ ```yaml
63
+ roxanne:
64
+ publisher:
65
+ class: "Roxanne::GPIO::Publisher"
66
+ green_pin: 25
67
+ orange_pin: 17
68
+ red_pin: 27
69
+ ```
70
+
71
+ ### Start the daemon
72
+
73
+ Start the daemon using the `start` command:
74
+
75
+ ```
76
+ bundle exec roxanne start
77
+ ```
78
+
79
+ If you want to specify a configuration file pass its path as the first argument:
80
+
81
+ ```
82
+ bundle exec roxanne start /etc/roxanne/config.yml
83
+ ```
84
+
85
+ Other available commands are obviously `stop`, `status` and `restart`.
86
+
87
+ ### Available Consumers
88
+
89
+ #### Jenkins
90
+
91
+ Use the `Roxanne::Jenkins::Consumer` class. Available options:
92
+
93
+ * **host**: the Jenkins host name or IP address
94
+ * **port**: the TCP port the Jenkins service can be reached on
95
+ * **path**: the relative path to the JSON API (`/jenkins/api/json`)
96
+ * **username**: the username to use for HTTP Basic Auth
97
+ * **password**: the password to use for HTTP Basic Auth
98
+ * **disable_certificate_verification** : set to true if you use a self-signed SSL certificate
99
+
100
+ #### Travis CI
101
+
102
+ Use the `Roxanne::Travis::Consumer` class. Available options:
103
+
104
+ * **organization_or_user**: the Github organization or user to pull build states from
105
+
106
+ ### Available Publishers
107
+
108
+ #### GPIO
109
+
110
+ Use the `Roxanne::GPIO::Consumer` class. Available options:
111
+
112
+ * **green_pin**: the GPIO pin to turn on when status changes to green
113
+ * **orange_pin**: the GPIO pin to turn on when status changes to orange
114
+ * **red_pin**: the GPIO pin to turn on when status changes to red
115
+
116
+ ## Statuses
117
+
118
+ Every 5 seconds, Roxanne will loop to check the status of the consumers:
119
+
120
+ * if any of the consumers returns `red` the publisher receive `red`
121
+ * if all of the consumers return `green` the publisher receive `green`
122
+ * if the previous state **was not** `green` and one of the consumer returns `orange` the publisher
123
+ receive `orange`
124
+
125
+ ## Contributing
126
+
127
+ 1. Fork it
128
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
129
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
130
+ 4. Push to the branch (`git push origin my-new-feature`)
131
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'spec'
6
+ t.pattern = "spec/**/*_spec.rb"
7
+ end
8
+
9
+ task :default => [:test]
data/bin/roxanne ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'roxanne'
4
+ require 'fileutils'
5
+
6
+ basedir = File.expand_path(File.join( File.dirname(__FILE__) , '..'))
7
+ %w(log pids).each do |dir|
8
+ dir = File.join(basedir, dir)
9
+ FileUtils.mkdir_p dir unless File.directory?(dir)
10
+ end
11
+ Roxanne::Server.spawn!(
12
+ log_file: File.join(basedir, 'log/roxanne.log'),
13
+ pid_file: File.join(basedir, 'pids/roxanne.pid'),
14
+ sync_log: true,
15
+ working_dir: basedir
16
+ )
data/config/config.yml ADDED
@@ -0,0 +1,6 @@
1
+ roxanne:
2
+ consumers:
3
+ test:
4
+ class: "Roxanne::Test::Consumer"
5
+ publisher:
6
+ class: "Roxanne::Test::Publisher"
@@ -0,0 +1,46 @@
1
+ require 'yaml'
2
+ require 'active_support/core_ext'
3
+
4
+ module Roxanne
5
+
6
+ module Configuration
7
+
8
+ class YAML < Base
9
+
10
+ def initialize(path)
11
+ super
12
+ yaml = ::YAML.load( File.open( path ) ).with_indifferent_access[:roxanne]
13
+ override_activation(yaml[:activation])
14
+ build_consumers(yaml[:consumers])
15
+ build_publisher(yaml[:publisher])
16
+ end
17
+
18
+ private
19
+ def override_activation(settings)
20
+ if settings
21
+ @active_days = settings[:days] if settings.has_key?(:days)
22
+ @timerange = settings[:timerange] if settings.has_key?(:timerange)
23
+ end
24
+ end
25
+
26
+ def build_consumers(hash)
27
+ (hash||{}).each do |id, hash|
28
+ @consumers << assign_properties( hash.delete(:class).constantize.new, hash )
29
+ end
30
+ end
31
+
32
+ def build_publisher(hash)
33
+ @publisher = assign_properties( hash.delete(:class).constantize.new, hash)
34
+ end
35
+
36
+ def assign_properties(object, hash)
37
+ hash.each do |prop, value|
38
+ object.send "#{prop}=", value
39
+ end
40
+ object
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ module Roxanne
2
+ module Configuration
3
+ class Base
4
+
5
+ attr_reader :consumers, :publisher, :active_days, :timerange
6
+
7
+ def initialize(*args)
8
+ @consumers = []
9
+ @publisher = nil
10
+ @active_days = 1..5
11
+ @timerange=8..19
12
+ end
13
+
14
+ def activated
15
+ will_activate(DateTime.now)
16
+ end
17
+
18
+ private
19
+ def will_activate( dt )
20
+ @active_days.include?(dt.wday) && @timerange.cover?(dt.hour)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ require 'roxanne/configuration/yaml'
@@ -0,0 +1,19 @@
1
+ module Roxanne
2
+ module Consumers
3
+ module Priority
4
+
5
+ def prioritize(current_status, former_status)
6
+ [:red, :orange].each do |status|
7
+ return status if status == former_status
8
+ end
9
+ current_status
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+
16
+ require 'roxanne/jenkins/consumer'
17
+ require 'roxanne/test/consumer'
18
+ require 'roxanne/travis/consumer'
19
+
@@ -0,0 +1,24 @@
1
+ require 'taopaipai'
2
+
3
+ module Roxanne
4
+ module GPIO
5
+ class Publisher
6
+ attr_accessor :green_pin, :orange_pin, :red_pin
7
+
8
+ def disable
9
+ [green_pin, orange_pin, red_pin].each do |num|
10
+ Taopaipai.gpio.pin(num, direction: :out).value 0
11
+ end
12
+ end
13
+
14
+ def push(previous, status)
15
+ return disable unless status
16
+ [:green, :orange, :red].each do |color|
17
+ Taopaipai.gpio.pin(send("#{color}_pin"), direction: :out).
18
+ value(color == status ? 1 : 0)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ require 'roxanne/http_support'
2
+
3
+ module Roxanne
4
+ module HTTP
5
+ class Consumer
6
+ include Consumers::Priority
7
+ include Roxanne::HTTPSupport
8
+
9
+ def pull
10
+ response = fetch_response
11
+ case response
12
+ when Net::HTTPSuccess
13
+ handle_response(response.body)
14
+ when Net::HTTPRedirection
15
+ puts "The request has been redirected to #{response['location']}"
16
+ :red
17
+ else
18
+ puts "The request has failed #{response.error}"
19
+ :red
20
+ end
21
+ rescue Exception => e
22
+ puts "Unable to fetch data : #{e.message}"
23
+ :red
24
+ end
25
+
26
+ def handle_response(body)
27
+ puts "Does nothing, HTTP consumer should be overriden."
28
+ :green
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ require 'net/http'
2
+
3
+ module Roxanne
4
+ module HTTPSupport
5
+ attr_accessor :host
6
+ attr_accessor :use_ssl
7
+ attr_accessor :disable_certificate_verification
8
+ attr_accessor :path
9
+ attr_accessor :port
10
+ attr_accessor :username
11
+ attr_accessor :password
12
+
13
+ private
14
+ def fetch_response
15
+ connection = ::Net::HTTP.new(@host, @port)
16
+ connection.use_ssl=@use_ssl
17
+ if @disable_certificate_verification
18
+ # TODO retrieve the certificate from the remote system, see http://redcorundum.blogspot.com/2008/03/ssl-certificates-and-nethttps.html
19
+ # Avoid issues with autosigned certificates
20
+ connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
21
+ end
22
+ request = Net::HTTP::Get.new(complete_path, {'Accept'=>accept})
23
+
24
+ # TODO Authentication
25
+ unless (username.nil? && password.nil?)
26
+ request.basic_auth username, password
27
+ else
28
+ end
29
+ connection.request(request)
30
+ end
31
+
32
+ def complete_path
33
+ @path
34
+ end
35
+
36
+ # Override if needed to force a particular format : application/json, text/xml, etc
37
+ protected
38
+ def accept
39
+ '*'
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ require 'roxanne/http/consumer'
2
+ require 'json'
3
+
4
+ module Roxanne
5
+ module Jenkins
6
+ class Consumer < Roxanne::HTTP::Consumer
7
+
8
+ def handle_response(body)
9
+ json = JSON.parse(body)
10
+ status = :green
11
+ json['jobs'].each do |job|
12
+ if COLORS.keys.include?(job['color'])
13
+ status = prioritize(to_status(job['color']), status)
14
+ end
15
+ end
16
+ status
17
+ end
18
+
19
+ private
20
+ COLORS = {
21
+ 'blue'=>:green,
22
+ 'yellow'=>:green,
23
+ 'red'=>:red,
24
+ 'blue_anime'=>:orange,
25
+ 'yellow_anime'=>:orange,
26
+ 'red_anime'=>:orange
27
+ }
28
+
29
+ def to_status(hudson_color)
30
+ COLORS[hudson_color]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ module Roxanne
2
+ class Loop
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ def cycle
8
+ if @config.activated
9
+ status = :green
10
+ @config.consumers.each do |consumer|
11
+ actual = consumer.pull
12
+ if actual == :red
13
+ status = :red
14
+ break
15
+ elsif actual == :orange && @previous != :green
16
+ status = :orange
17
+ end
18
+ end
19
+ publish( @previous, status )
20
+ @previous = status
21
+ else
22
+ @config.publisher.disable
23
+ end
24
+ end
25
+
26
+ def reset
27
+ @config.publisher.push(nil, nil)
28
+ end
29
+
30
+ private
31
+ def publish(previous, current)
32
+ @config.publisher.push(previous, current)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,2 @@
1
+ require 'roxanne/test/publisher'
2
+ require 'roxanne/gpio/publisher'
@@ -0,0 +1,24 @@
1
+ require 'daemon_spawn'
2
+
3
+ module Roxanne
4
+ class Server < DaemonSpawn::Base
5
+
6
+ def start(args)
7
+ puts "Roxanne starting in #{self.working_dir}"
8
+ @controller = Loop.new(config(args))
9
+ loop do
10
+ @controller.cycle
11
+ sleep 5
12
+ end
13
+ end
14
+
15
+ def stop
16
+ @controller.reset
17
+ end
18
+
19
+ def config(args)
20
+ @config ||= Configuration::YAML.new(args.first || 'config/config.yml')
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Roxanne
2
+ module Test
3
+ class Consumer
4
+ def pull
5
+ @previous = if @previous.nil?
6
+ :green
7
+ elsif @previous == :green
8
+ :red
9
+ elsif @previous == :red
10
+ :orange
11
+ else
12
+ :green
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ module Roxanne
2
+ module Test
3
+ class Publisher
4
+ def disable
5
+ puts "Publisher is now disabled"
6
+ end
7
+
8
+ def push(previous, status)
9
+ puts "Publisher switching from status #{default(previous)} to #{default(status)}"
10
+ end
11
+
12
+ private
13
+ def default(status)
14
+ status.nil? ? 'none' : status
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ require 'travis'
2
+
3
+ module Roxanne
4
+ module Travis
5
+ class Consumer
6
+ include Consumers::Priority
7
+
8
+ attr_accessor :organization_or_user
9
+
10
+ def pull
11
+ repos = ::Travis::Repository.find_all(owner_name: organization_or_user)
12
+ status = :green
13
+ repos.select{|repo| repo.active? }.each do |repo|
14
+ status = prioritize(to_status(repo.last_build_state), status)
15
+ end
16
+ status
17
+ end
18
+
19
+ private
20
+ STATES = {
21
+ 'failed' => :red,
22
+ 'started' => :orange,
23
+ 'passed' => :green
24
+ }
25
+
26
+ def to_status(travis_build_state)
27
+ STATES[travis_build_state]
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module Roxanne
2
+ VERSION = "0.1.0"
3
+ end
data/lib/roxanne.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "roxanne/version"
2
+
3
+ require 'roxanne/configuration'
4
+ require 'roxanne/loop'
5
+ require 'roxanne/server'
6
+ require 'roxanne/consumers'
7
+ require 'roxanne/publishers'
8
+
9
+ module Roxanne
10
+ end
data/roxanne.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'roxanne/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "roxanne"
8
+ spec.version = Roxanne::VERSION
9
+ spec.authors = ["Jef Mathiot", "Patrice Izzo", "Fabrice Nourisson",
10
+ "Benjamin Severac", "Eric Hartmann"]
11
+ spec.email = ["foss@servebox.com"]
12
+ spec.description = %q{Roxanne: publish your CI status to your device of choice}
13
+ spec.summary = %q{Aggregate the status of Continuous Integration jobs or other sources and
14
+ publish them}
15
+ spec.homepage = "http://github.com/servebox/roxanne"
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency 'json'
24
+ spec.add_dependency 'daemon-spawn'
25
+ spec.add_dependency 'activesupport', '>= 3.0.0'
26
+ spec.add_dependency 'taopaipai', '~> 0.1.1'
27
+ spec.add_dependency 'travis', '~> 1.6.8'
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.6"
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "minitest", "~> 5.0.7"
32
+ spec.add_development_dependency "minitest-implicit-subject", "~> 1.4.0"
33
+ spec.add_development_dependency "rb-readline", "~> 0.5.0"
34
+ spec.add_development_dependency "guard-minitest", "~> 2.1.3"
35
+ spec.add_development_dependency "timecop"
36
+ spec.add_development_dependency "mocha"
37
+ end
@@ -0,0 +1,21 @@
1
+ #! /bin/sh
2
+
3
+ case "$1" in
4
+ start)
5
+ su roxanne -c "bash /var/lib/roxanne/roxanne/service/roxanne.sh start" ; exit
6
+ ;;
7
+ stop)
8
+ su roxanne -c "bash /var/lib/roxanne/roxanne/service/roxanne.sh stop" ; exit
9
+ ;;
10
+ status)
11
+ ;;
12
+ restart|force-reload)
13
+ su roxanne -c "bash /var/lib/roxanne/roxanne/service/roxanne.sh restart" ; exit
14
+ ;;
15
+ *)
16
+ #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
17
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
18
+ exit 3
19
+ ;;
20
+ esac
21
+
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ source $HOME/.bash_profile
3
+ eval "$(rbenv init -)"
4
+ cd /var/lib/roxanne/roxanne
5
+ bundle exec ruby lib/roxanne_server.rb $1 config/golgotha.yml