build_status_server 0.6 → 0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -12,3 +12,7 @@ group :development do
12
12
  gem "ruby-debug"
13
13
  gem "sinatra"
14
14
  end
15
+
16
+ group :development, :test do
17
+ gem "rake"
18
+ end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- build_status_server (0.3)
4
+ build_status_server (0.7)
5
5
  json
6
6
 
7
7
  GEM
@@ -15,6 +15,7 @@ GEM
15
15
  rack (1.4.0)
16
16
  rack-protection (1.2.0)
17
17
  rack
18
+ rake (0.9.2.2)
18
19
  rbx-require-relative (0.0.5)
19
20
  rspec (2.9.0)
20
21
  rspec-core (~> 2.9.0)
@@ -41,6 +42,7 @@ PLATFORMS
41
42
  DEPENDENCIES
42
43
  build_status_server!
43
44
  json
45
+ rake
44
46
  rspec
45
47
  ruby-debug
46
48
  sinatra
@@ -16,11 +16,19 @@ built for my employer ChallengePost (http://challengepost.com). It works in
16
16
  conjunction with our Jenkins Continuous Integration server (and its
17
17
  Notification Plugin)) and an Arduino powered Traffic Light controller
18
18
  (https://github.com/jcmuller/TrafficLightController) with a pseudo-RESTful API.
19
-
20
19
  EOS
21
20
  s.description = "A build notifier server for Jenkins CI that controls an XFD over HTTP"
22
21
 
23
- s.files = Dir["{lib/**/*,spec/**/*}"] + %w(bin/build_status_server config/config-example.yml LICENSE README.md Gemfile Gemfile.lock build_status_server.gemspec)
22
+ s.files = Dir["{lib/**/*,spec/**/*}"] + %w(
23
+ Gemfile
24
+ Gemfile.lock
25
+ LICENSE
26
+ README.md
27
+ bin/build_status_server
28
+ build_status_server.gemspec
29
+ config/config-example.yml
30
+ )
31
+
24
32
  s.require_path = "lib"
25
33
  s.bindir = "bin"
26
34
  s.executables = %w(build_status_server)
@@ -28,6 +36,7 @@ Notification Plugin)) and an Arduino powered Traffic Light controller
28
36
  s.homepage = "http://github.com/jcmuller/build_status_server"
29
37
  s.test_files = Dir["spec/**/*_spec.rb"]
30
38
 
39
+ s.add_development_dependency("rake")
31
40
  s.add_development_dependency("ruby-debug")
32
41
  s.add_development_dependency("sinatra")
33
42
 
@@ -1,14 +1,14 @@
1
1
  udp_server:
2
- address: '127.0.0.1'
2
+ address: 127.0.0.1
3
3
  port: 1234
4
4
  tcp_client:
5
- host: '127.0.0.1'
5
+ host: 127.0.0.1
6
6
  port: 4567
7
- pass: "/green"
8
- fail: "/red"
7
+ pass: /green
8
+ fail: /red
9
9
  store:
10
- filename: "/tmp/build_result.yml"
10
+ filename: /tmp/build_result.yml
11
11
  mask:
12
- regex: ".*master.*"
13
- policy: "include"
14
- verbose: true
12
+ regex: !ruby/regexp /.*master.*/
13
+ policy: include
14
+ verbose: false
@@ -5,5 +5,6 @@ require "timeout"
5
5
  require "yaml"
6
6
 
7
7
  module BuildStatusServer
8
+ autoload :Config, 'build_status_server/config'
8
9
  autoload :Server, 'build_status_server/server'
9
10
  end
@@ -1,10 +1,10 @@
1
1
  require "rubygems"
2
2
  require "json"
3
- require "sinatra"
4
3
  require "socket"
5
4
  require "timeout"
6
5
  require "yaml"
7
6
 
8
7
  module BuildStatusServer
8
+ autoload :Config, 'build_status_server/config'
9
9
  autoload :Server, 'build_status_server/server'
10
10
  end
@@ -0,0 +1,91 @@
1
+ module BuildStatusServer
2
+ class Config
3
+ attr_reader :config
4
+
5
+ def initialize
6
+ @config = {}
7
+ end
8
+
9
+ # This is responsible of loading the config object
10
+ def load(options = {})
11
+ config = load_config_file(options[:config])
12
+ import_config(config, options)
13
+ end
14
+
15
+ def method_missing(meth, *args, &block)
16
+ return config[meth.to_s] if config.has_key?(meth.to_s)
17
+ super
18
+ end
19
+
20
+ def store_file
21
+ return File.expand_path(".", store["filename"]) if store
22
+ nil
23
+ end
24
+
25
+ private
26
+
27
+ # This will load the passed in config object into the config attribute
28
+ def import_config(config = {}, options = {})
29
+ config["verbose"] = options[:verbose] unless options[:verbose].nil?
30
+ @config = config
31
+ end
32
+
33
+ # This is responsible to return a hash with the contents of a YAML file
34
+ def load_config_file(config_file = nil)
35
+ curated_file = nil
36
+
37
+ if config_file
38
+ f = File.expand_path(config_file)
39
+ if File.exists?(f)
40
+ curated_file = f
41
+ else
42
+ raise "Supplied config file (#{config_file}) doesn't seem to exist"
43
+ end
44
+ else
45
+ locations_to_try.each do |possible_conf_file|
46
+ f = File.expand_path(possible_conf_file)
47
+ if File.exists?(f)
48
+ curated_file = f
49
+ break
50
+ end
51
+ end
52
+
53
+ if curated_file.nil?
54
+ STDERR.puts <<-EOT
55
+ Looks like there isn't an available configuration file for this program.
56
+ We're very diligently going to use some sensible defaults, but you're
57
+ strongly recommended to create one in any of the following locations:
58
+
59
+ #{locations_to_try.join("\n ")}
60
+
61
+ Here is a sample of the contents for that file (and the settings we're going
62
+ to use):
63
+
64
+ #{get_example_config}
65
+ EOT
66
+
67
+ return YAML.load(get_example_config)
68
+ end
69
+ end
70
+
71
+ YAML.load_file(curated_file).tap do |config|
72
+ raise "This is an invalid configuration file!" unless config.class == Hash
73
+ end
74
+ end
75
+
76
+ def locations_to_try
77
+ %w(
78
+ ~/.config/build_status_server/config.yml
79
+ ./config/config.yml
80
+ /etc/build_status_server/config.yml
81
+ /usr/local/etc/build_status_server/config.yml
82
+ )
83
+ end
84
+
85
+ def get_example_config
86
+ filename = "#{File.dirname(File.expand_path(__FILE__))}/../../config/config-example.yml"
87
+ File.open(filename).read
88
+ end
89
+ end
90
+ end
91
+
@@ -1,28 +1,18 @@
1
+ # TODO
2
+ # move all configuration stuff to Config
3
+ # and just call config[:blaj] instead of if blah
1
4
  module BuildStatusServer
2
5
  class Server
3
- attr_reader :config, :store_file, :mask_policy, :verbose
4
- attr_accessor :store, :mask
6
+ attr_reader :config, :store
5
7
 
6
8
  def initialize(options = {})
7
- load_config_file(options[:config])
8
- @verbose = options[:verbose] || config["verbose"]
9
- @store_file = File.expand_path(".", config["store"]["filename"])
10
- @mask = Regexp.new(config["mask"]["regex"])
11
- @mask_policy = config["mask"]["policy"] || "exclude"
12
- end
13
-
14
- def load_store
15
- @store = begin
16
- YAML.load_file(store_file)
17
- rescue
18
- {}
19
- end
20
- @store = {} unless store.class == Hash
9
+ @config = Config.new
10
+ config.load(options)
21
11
  end
22
12
 
23
13
  def listen
24
14
  sock = UDPSocket.new
25
- udp_server = config["udp_server"]
15
+ udp_server = config.udp_server
26
16
 
27
17
  begin
28
18
  sock.bind(udp_server["address"], udp_server["port"])
@@ -35,11 +25,11 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
35
25
  exit
36
26
  end
37
27
 
38
- puts "Listening on UDP #{udp_server["address"]}:#{udp_server["port"]}" if verbose
28
+ puts "Listening on UDP #{udp_server["address"]}:#{udp_server["port"]}" if config.verbose
39
29
 
40
30
  while true
41
31
  data, addr = sock.recvfrom(2048)
42
- #require "ruby-debug"; debugger
32
+
43
33
  if process_job(data)
44
34
  status = process_all_statuses
45
35
  notify(status)
@@ -49,13 +39,25 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
49
39
  sock.close
50
40
  end
51
41
 
42
+ private
43
+
44
+ def load_store
45
+ @store = begin
46
+ YAML.load_file(config.store_file)
47
+ rescue
48
+ {}
49
+ end
50
+ @store = {} unless store.class == Hash
51
+ end
52
+
53
+
52
54
  def process_job(data = "{}")
53
55
  job = JSON.parse(data)
54
56
 
55
57
  build_name = job["name"]
56
58
 
57
59
  unless should_process_build(build_name)
58
- STDOUT.puts "Ignoring #{build_name} (#{mask}--#{mask_policy})" if verbose
60
+ STDOUT.puts "Ignoring #{build_name} (#{config.mask["regex"]}--#{config.mask["policy"]})" if config.verbose
59
61
  return false
60
62
  end
61
63
 
@@ -65,77 +67,33 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
65
67
  return false
66
68
  end
67
69
 
68
- phase = job["build"]["phase"]
69
- status = job["build"]["status"]
70
+ phase = job["build"]["phase"]
71
+ status = job["build"]["status"]
70
72
 
71
73
  if phase == "FINISHED"
72
- STDOUT.puts "Got #{status} for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
74
+ STDOUT.puts "Got #{status} for #{build_name} on #{Time.now} [#{job.inspect}]" if config.verbose
73
75
  case status
74
76
  when "SUCCESS", "FAILURE"
75
77
  load_store
76
78
  store[build_name] = status
77
- File.open(store_file, "w") { |file| YAML.dump(store, file) }
79
+ File.open(config.store_file, "w") { |file| YAML.dump(store, file) }
78
80
  return true
79
81
  end
80
82
  else
81
- STDOUT.puts "Started for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
83
+ STDOUT.puts "Started for #{build_name} on #{Time.now} [#{job.inspect}]" if config.verbose
82
84
  end
83
85
 
84
86
  return false
85
87
  end
86
88
 
87
- # Ensure config file exists. If not, copy example into it
88
- def load_config_file(config_file)
89
- curated_file = nil
90
-
91
- if config_file
92
- f = File.expand_path(config_file)
93
- if File.exists?(f)
94
- curated_file = f
95
- else
96
- puts "Supplied config file (#{config_file}) doesn't seem to exist" if verbose
97
- exit
98
- end
99
- else
100
- locations_to_try = %w(
101
- ~/.config/build_status_server/config.yml
102
- config/config.yml
103
- /etc/build_status_server/config.yml
104
- /usr/local/etc/build_status_server/config.yml
105
- )
106
-
107
- locations_to_try.each do |possible_conf_file|
108
- f = File.expand_path(possible_conf_file)
109
- if File.exists?(f)
110
- puts "Using #{possible_conf_file}!" if verbose if verbose
111
- curated_file = f
112
- break
113
- end
114
- end
115
-
116
- puts <<-EOT
117
- Looks like there isn't an available configuration file for this program. You
118
- can create one in any of the following locations:
119
-
120
- #{locations_to_try.map{|l| File.expand_path(l)}.join("\n ")}
121
-
122
- Here is a sample of the contents for that file:
123
-
124
- #{File.open("#{File.dirname(File.expand_path(__FILE__))}/../../config/config-example.yml").read}
125
-
126
- EOT
127
-
128
- exit
129
- end
130
-
131
- puts "Using #{curated_file}!" if verbose
132
- @config = YAML.load_file(curated_file)
133
- end
134
-
135
89
  def should_process_build(build_name)
136
90
  # If mask exists, then ...
137
- ! (!!mask && ((mask_policy == "include" && build_name !~ mask) ||
138
- (mask_policy != "include" && build_name =~ mask)))
91
+ ! (
92
+ !!config.mask &&
93
+ !!config.mask["regex"] &&
94
+ ((config.mask["policy"] == "include" && build_name !~ config.mask["regex"]) ||
95
+ (config.mask["policy"] != "include" && build_name =~ config.mask["regex"])
96
+ ))
139
97
  end
140
98
 
141
99
  def process_all_statuses
@@ -149,7 +107,7 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
149
107
  end
150
108
 
151
109
  def notify(status)
152
- tcp_client = config["tcp_client"]
110
+ tcp_client = config.tcp_client
153
111
 
154
112
  attempts = 0
155
113
  light = status ? tcp_client["pass"] : tcp_client["fail"]
@@ -160,7 +118,7 @@ is listening at the same port (#{udp_server["address"]}:#{udp_server["port"]}
160
118
  client = TCPSocket.new(tcp_client["host"], tcp_client["port"])
161
119
  client.print "GET #{light} HTTP/1.0\n\n"
162
120
  answer = client.gets(nil)
163
- STDOUT.puts answer if verbose
121
+ STDOUT.puts answer if config.verbose
164
122
  client.close
165
123
  end
166
124
  rescue Timeout::Error => ex
@@ -1,3 +1,3 @@
1
1
  module BuildStatusServer
2
- VERSION = "0.6"
2
+ VERSION = "0.7"
3
3
  end
@@ -1,3 +1,3 @@
1
1
  module BuildStatusServer
2
- VERSION = "0.5"
2
+ VERSION = "0.6"
3
3
  end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+ require 'build_status_server'
3
+ require 'tempfile'
4
+
5
+ describe BuildStatusServer::Config do
6
+ let(:config) { BuildStatusServer::Config.new }
7
+
8
+ describe "#load" do
9
+ it "should call load_config_file with options passed in" do
10
+ options = {:config => nil}
11
+ config.should_receive(:load_config_file).with(options[:config])
12
+ config.load(options)
13
+ end
14
+
15
+ it "should set the config values from yaml file" do
16
+ config.should_receive(:load_config_file).and_return(YAML.load(config.send(:get_example_config)))
17
+ config.load
18
+ config.udp_server.should == {'address' => '127.0.0.1', 'port' => 1234}
19
+ config.verbose.should == false
20
+ end
21
+ end
22
+
23
+ describe "#load_config_file" do
24
+ it "should load the yaml file passed in as a file argument" do
25
+ file_name = nil
26
+
27
+ Tempfile.open('config') do |f|
28
+ f.puts "---"
29
+ f.puts "key: value"
30
+ f.puts "key2: value2"
31
+ file_name = f.path
32
+ end
33
+
34
+ config.send(:load_config_file, file_name).should == {
35
+ "key" => "value",
36
+ "key2" => "value2"
37
+ }
38
+ end
39
+
40
+ it "should try to load paths from the locations to try" do
41
+ file_name = nil
42
+ Tempfile.open("config") do |f|
43
+ f.puts "---"
44
+ f.puts "key: value"
45
+ f.puts "key2: value2"
46
+ file_name = f.path
47
+ end
48
+
49
+ config.stub!(:locations_to_try).and_return([file_name])
50
+ config.send(:load_config_file).should == {
51
+ "key" => "value",
52
+ "key2" => "value2"
53
+ }
54
+ end
55
+
56
+ it "should throw an exception if the config file doesn't exist" do
57
+ file_name = "/tmp/i_dont_exist.yml"
58
+ expect { config.send(:load_config_file, file_name) }.should raise_error RuntimeError, "Supplied config file (#{file_name}) doesn't seem to exist"
59
+ end
60
+
61
+ it "should throw an exception if the config file isn't a hash" do
62
+ file_name = nil
63
+ Tempfile.open(file_name) do |f|
64
+ f.puts "YADDA YADDA"
65
+ file_name = f.path
66
+ end
67
+ expect { config.send(:load_config_file, file_name) }.should raise_error RuntimeError, "This is an invalid configuration file!"
68
+ end
69
+
70
+ it "should return the default options if no default location is found" do
71
+ STDERR.should_receive(:puts)
72
+ config_hash = config.send(:load_config_file)
73
+ config_hash["udp_server"]["address"].should == '127.0.0.1'
74
+ config_hash["verbose"].should == false
75
+ end
76
+ end
77
+
78
+ describe "#store_file" do
79
+ it "returns the store file configured" do
80
+ config.stub!(:store).and_return("filename" => "/tmp/build_result.yml")
81
+ config.store_file.should == "/tmp/build_result.yml"
82
+ end
83
+
84
+ it "returns nil if config doesn't have store option" do
85
+ config.stub!(:store).and_return(nil)
86
+ config.store_file.should be_nil
87
+ end
88
+ end
89
+
90
+ describe "#method_missing" do
91
+ it "should respond to methods named after elements in the config hash" do
92
+ config.send(:import_config, "blah" => 1)
93
+ config.blah.should == 1
94
+ end
95
+
96
+ it "should not respond to methods named after elements that don't exist" do
97
+ expect { config.blah }.should raise_error NoMethodError
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+ require 'build_status_server'
3
+
4
+ describe BuildStatusServer::Server do
5
+ let(:server) { BuildStatusServer::Server.new }
6
+
7
+ before do
8
+ STDERR.should_receive :puts
9
+ end
10
+
11
+ describe "#listen"
12
+
13
+ context "private methods" do
14
+ describe "#load_store" do
15
+ before do
16
+ server.config.stub!(:store_file).and_return("/tmp/build")
17
+ end
18
+
19
+ it "initializes an empty hash if store file doesn't exist" do
20
+ server.send(:load_store)
21
+ server.store.should == {}
22
+ end
23
+
24
+ it "initializes an empty hash if store file is empty" do
25
+ require "tempfile"
26
+ f = Tempfile.new("server_spec")
27
+ server.stub!(:store_file).and_return(f.path)
28
+
29
+ server.send(:load_store)
30
+ server.store.should == {}
31
+ end
32
+
33
+ it "initializes a hash with the contents of the store file" do
34
+ server.config.stub!(:store_file).and_return("spec/support/build_result.yml")
35
+ server.send(:load_store)
36
+
37
+ server.store.should == {"blah" => "SUCCESS", "test" => "SUCCESS"}
38
+ end
39
+ end
40
+
41
+ describe "#notify"
42
+ describe "#process_all_statuses"
43
+ describe "#process_job"
44
+
45
+ describe "#should_process_build" do
46
+ context "when mask exists" do
47
+ before do
48
+ server.stub!(:mask).and_return(%r{.*(?:master).*})
49
+ end
50
+
51
+ context "when policy is include" do
52
+ before do
53
+ server.stub!(:mask_policy).and_return("include")
54
+ end
55
+
56
+ it "ignores builds if mask doesn't match build name" do
57
+ server.send(:should_process_build, "blah-development").should be_false
58
+ end
59
+
60
+ it "processes builds if mask matches build name" do
61
+ server.send(:should_process_build, "blah-master").should be_true
62
+ end
63
+ end
64
+
65
+ context "when policy is exclude" do
66
+ before do
67
+ server.config.stub!(:mask).and_return({"policy" => "exclude", "regex" => /.*(?:master).*/})
68
+ end
69
+
70
+ it "ignores builds if mask matches build name" do
71
+ server.send(:should_process_build, "blah-master").should be_false
72
+ end
73
+
74
+ it "processes builds if mask doesn't match build name" do
75
+ server.send(:should_process_build, "blah-development").should be_true
76
+ end
77
+ end
78
+
79
+ context "when policy is undefined it defaults to ignore" do
80
+ before do
81
+ server.config.stub!(:mask).and_return({"policy" => nil, "regex" => /.*(?:master).*/})
82
+ end
83
+
84
+ it "ignores builds if mask matches build name" do
85
+ server.send(:should_process_build, "blah-master").should be_false
86
+ end
87
+
88
+ it "processes builds if mask doesn't match build name" do
89
+ server.send(:should_process_build, "blah-development").should be_true
90
+ end
91
+ end
92
+
93
+ context "when policy is unexpected it defaults to ignore" do
94
+ before do
95
+ server.config.stub!(:mask).and_return({"policy" => "trash", "regex" => /.*(?:master).*/})
96
+ end
97
+
98
+ it "ignores builds if mask matches build name" do
99
+ server.send(:should_process_build, "blah-master").should be_false
100
+ end
101
+
102
+ it "processes builds if mask doesn't match build name" do
103
+ server.send(:should_process_build, "blah-development").should be_true
104
+ end
105
+ end
106
+ end
107
+
108
+ context "when mask doesn't" do
109
+ before do
110
+ server.config.stub!(:mask).and_return({"policy" => "include", "regex" => nil})
111
+ end
112
+
113
+ it "should process all jobs" do
114
+ server.send(:should_process_build, "blah-development").should be_true
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ # vim:set foldmethod=syntax foldlevel=1:
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: build_status_server
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 5
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 6
9
- version: "0.6"
8
+ - 7
9
+ version: "0.7"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Juan C. Muller
@@ -14,11 +14,10 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2012-04-26 00:00:00 -04:00
18
- default_executable:
17
+ date: 2012-05-01 00:00:00 Z
19
18
  dependencies:
20
19
  - !ruby/object:Gem::Dependency
21
- name: ruby-debug
20
+ name: rake
22
21
  prerelease: false
23
22
  requirement: &id001 !ruby/object:Gem::Requirement
24
23
  none: false
@@ -32,7 +31,7 @@ dependencies:
32
31
  type: :development
33
32
  version_requirements: *id001
34
33
  - !ruby/object:Gem::Dependency
35
- name: sinatra
34
+ name: ruby-debug
36
35
  prerelease: false
37
36
  requirement: &id002 !ruby/object:Gem::Requirement
38
37
  none: false
@@ -46,7 +45,7 @@ dependencies:
46
45
  type: :development
47
46
  version_requirements: *id002
48
47
  - !ruby/object:Gem::Dependency
49
- name: json
48
+ name: sinatra
50
49
  prerelease: false
51
50
  requirement: &id003 !ruby/object:Gem::Requirement
52
51
  none: false
@@ -57,8 +56,22 @@ dependencies:
57
56
  segments:
58
57
  - 0
59
58
  version: "0"
60
- type: :runtime
59
+ type: :development
61
60
  version_requirements: *id003
61
+ - !ruby/object:Gem::Dependency
62
+ name: json
63
+ prerelease: false
64
+ requirement: &id004 !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ hash: 3
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ type: :runtime
74
+ version_requirements: *id004
62
75
  description: A build notifier server for Jenkins CI that controls an XFD over HTTP
63
76
  email: jcmuller@gmail.com
64
77
  executables:
@@ -68,27 +81,24 @@ extensions: []
68
81
  extra_rdoc_files: []
69
82
 
70
83
  files:
71
- - lib/build_status_server/requirements.rb~
84
+ - lib/build_status_server/config.rb
72
85
  - lib/build_status_server/server.rb
73
- - lib/build_status_server/server.rb~
74
86
  - lib/build_status_server/version.rb
75
87
  - lib/build_status_server/version.rb~
76
88
  - lib/build_status_server.rb
77
89
  - lib/build_status_server.rb~
78
- - spec/lib/build_status_server_spec.rb
79
- - spec/lib/build_status_server_spec.rb~
90
+ - spec/lib/build_status_server/config_spec.rb
91
+ - spec/lib/build_status_server/server_spec.rb
80
92
  - spec/spec_helper.rb
81
- - spec/spec_helper.rb~
82
93
  - spec/support/build_result.yml
83
94
  - spec/support/sample.json
84
- - bin/build_status_server
85
- - config/config-example.yml
86
- - LICENSE
87
- - README.md
88
95
  - Gemfile
89
96
  - Gemfile.lock
97
+ - LICENSE
98
+ - README.md
99
+ - bin/build_status_server
90
100
  - build_status_server.gemspec
91
- has_rdoc: true
101
+ - config/config-example.yml
92
102
  homepage: http://github.com/jcmuller/build_status_server
93
103
  licenses:
94
104
  - GPL
@@ -118,9 +128,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
128
  requirements: []
119
129
 
120
130
  rubyforge_project:
121
- rubygems_version: 1.6.2
131
+ rubygems_version: 1.8.23
122
132
  signing_key:
123
133
  specification_version: 3
124
134
  summary: This utility is part of an XFD (eXtreeme Feedback Device) solution designed and built for my employer ChallengePost (http://challengepost.com). It works in conjunction with our Jenkins Continuous Integration server (and its Notification Plugin)) and an Arduino powered Traffic Light controller (https://github.com/jcmuller/TrafficLightController) with a pseudo-RESTful API.
125
135
  test_files:
126
- - spec/lib/build_status_server_spec.rb
136
+ - spec/lib/build_status_server/config_spec.rb
137
+ - spec/lib/build_status_server/server_spec.rb
@@ -1,8 +0,0 @@
1
- require "rubygems"
2
- require "bundler/setup"
3
-
4
- Bundler.require(:default)
5
-
6
- require "socket"
7
- require "timeout"
8
- require "yaml"
@@ -1,189 +0,0 @@
1
- module BuildStatusServer
2
- class Server
3
- attr_reader :config, :store_file, :mask_policy, :verbose
4
- attr_accessor :store, :mask
5
-
6
- def initialize(options = {})
7
- load_config_file(options[:config])
8
- @verbose = options[:verbose] || config["verbose"]
9
- @store_file = File.expand_path(".", config["store"]["filename"])
10
- @mask = Regexp.new(config["mask"]["regex"])
11
- @mask_policy = config["mask"]["policy"] || "exclude"
12
- end
13
-
14
- def load_store
15
- @store = begin
16
- YAML.load_file(store_file)
17
- rescue
18
- {}
19
- end
20
- @store = {} unless store.class == Hash
21
- end
22
-
23
- def listen
24
- sock = UDPSocket.new
25
- udp_server = config["udp_server"]
26
- sock.bind(udp_server["address"], udp_server["port"])
27
-
28
- puts "Listening on UDP #{udp_server["address"]}:#{udp_server["port"]}" if verbose
29
-
30
- while true
31
- data, addr = sock.recvfrom(2048)
32
- #require "ruby-debug"; debugger
33
- if process_job(data)
34
- status = process_all_statuses
35
- notify(status)
36
- end
37
- end
38
-
39
- sock.close
40
- end
41
-
42
- def process_job(data = "{}")
43
- job = JSON.parse(data)
44
-
45
- build_name = job["name"]
46
-
47
- unless should_process_build(build_name)
48
- STDOUT.puts "Ignoring #{build_name} (#{mask}--#{mask_policy})" if verbose
49
- return false
50
- end
51
-
52
- if job.class != Hash or
53
- job["build"].class != Hash
54
- STDERR.puts "Pinged with an invalid payload"
55
- return false
56
- end
57
-
58
- phase = job["build"]["phase"]
59
- status = job["build"]["status"]
60
-
61
- if phase == "FINISHED"
62
- STDOUT.puts "Got #{status} for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
63
- case status
64
- when "SUCCESS", "FAILURE"
65
- load_store
66
- store[build_name] = status
67
- File.open(store_file, "w") { |file| YAML.dump(store, file) }
68
- return true
69
- end
70
- else
71
- STDOUT.puts "Started for #{build_name} on #{Time.now} [#{job.inspect}]" if verbose
72
- end
73
-
74
- return false
75
- end
76
-
77
- # Ensure config file exists. If not, copy example into it
78
- def load_config_file(config_file)
79
- curated_file = nil
80
-
81
- if config_file
82
- f = File.expand_path(config_file)
83
- if File.exists?(f)
84
- curated_file = f
85
- else
86
- puts "Supplied config file (#{config_file}) doesn't seem to exist" if verbose
87
- exit
88
- end
89
- else
90
- locations_to_try = %w(
91
- ~/.config/build_status_server/config.yml
92
- config/config.yml
93
- /etc/build_status_server/config.yml
94
- /usr/local/etc/build_status_server/config.yml
95
- )
96
-
97
- locations_to_try.each do |possible_conf_file|
98
- f = File.expand_path(possible_conf_file)
99
- if File.exists?(f)
100
- puts "Using #{possible_conf_file}!" if verbose if verbose
101
- curated_file = f
102
- break
103
- end
104
- end
105
-
106
- puts <<-EOT
107
- Looks like there isn't an available configuration file for this program. You
108
- can create one in any of the following locations:
109
-
110
- #{locations_to_try.map{|l| File.expand_path(l)}.join("\n ")}
111
-
112
- Here is a sample of the contents for that file:
113
-
114
- #{File.open("#{File.dirname(File.expand_path(__FILE__))}/../../config/config-example.yml").read}
115
-
116
- EOT
117
-
118
- exit
119
- end
120
-
121
- puts "Using #{curated_file}!" if verbose
122
- @config = YAML.load_file(curated_file)
123
- end
124
-
125
- def should_process_build(build_name)
126
- # If mask exists, then ...
127
- ! (!!mask && ((mask_policy == "include" && build_name !~ mask) ||
128
- (mask_policy != "include" && build_name =~ mask)))
129
- end
130
-
131
- def process_all_statuses
132
- pass = true
133
-
134
- @store.values.each do |val|
135
- pass &&= (val == "pass" || val == "SUCCESS")
136
- end
137
-
138
- pass
139
- end
140
-
141
- def notify(status)
142
- tcp_client = config["tcp_client"]
143
-
144
- attempts = 0
145
- light = status ? tcp_client["pass"] : tcp_client["fail"]
146
-
147
- begin
148
- timeout(5) do
149
- attempts += 1
150
- client = TCPSocket.new(tcp_client["host"], tcp_client["port"])
151
- client.print "GET #{light} HTTP/1.0\n\n"
152
- answer = client.gets(nil)
153
- STDOUT.puts answer if verbose
154
- client.close
155
- end
156
- rescue Timeout::Error => ex
157
- STDERR.puts "Error: #{ex} while trying to send #{light}"
158
- retry unless attempts > 2
159
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => ex
160
- STDERR.puts "Error: #{ex} while trying to send #{light}"
161
- STDERR.puts "Will wait for 2 seconds and try again..."
162
- sleep 2
163
- retry unless attempts > 2
164
- end
165
- end
166
- end
167
- end
168
-
169
- __END__
170
-
171
- Example payload:
172
- {
173
- "name":"test",
174
- "url":"job/test/",
175
- "build":{
176
- "full_url":"http://cronus.local:3001/job/test/20/",
177
- "number":20,
178
- "phase":"FINISHED",
179
- "status":"SUCCESS",
180
- "url":"job/test/20/"
181
- }
182
- }
183
-
184
- We're getting this error once in a while:
185
- /usr/local/lib/ruby/1.8/timeout.rb:64:in `notify': execution expired (Timeout::Error)
186
- from /home/jcmuller/build_notifier/lib/server.rb:102:in `notify'
187
- from /home/jcmuller/build_notifier/lib/server.rb:33:in `listen'
188
- from bin/server:5
189
-
@@ -1,124 +0,0 @@
1
- $:.push File.expand_path("lib", __FILE__)
2
-
3
- require 'spec_helper'
4
- require 'build_status_server'
5
-
6
- describe BuildStatusServer::Server, :pending => true do
7
- let!(:server) { BuildStatusServer::Server.new(:config => '/dev/null') }
8
-
9
- describe "#load_config_file" do
10
- it "should use the supplied argument" do
11
- config_file = '/dev/null'
12
- File.should_receive(:exists?).and_return(true)
13
- server.load_config_file(config_file)
14
- end
15
- end
16
-
17
- describe "#load_store" do
18
-
19
- before do
20
- server.stub!(:store_file).and_return("/tmp/build")
21
- end
22
-
23
- it "initializes an empty hash if store file doesn't exist" do
24
- server.load_store
25
- server.store.should == {}
26
- end
27
-
28
- it "initializes an empty hash if store file is empty" do
29
- require "tempfile"
30
- f = Tempfile.new("server_spec")
31
- server.stub!(:store_file).and_return(f.path)
32
-
33
- server.load_store
34
- server.store.should == {}
35
- end
36
-
37
- it "initializes a hash with the contents of the store file" do
38
- server.stub!(:store_file).and_return("spec/support/build_result.yml")
39
- server.load_store
40
-
41
- server.store.should == {"blah" => "SUCCESS", "test" => "SUCCESS"}
42
- end
43
- end
44
-
45
- describe "#notify"
46
- describe "#process_all_statuses"
47
- describe "#process_job"
48
-
49
- describe "#should_process_build" do
50
- context "when mask exists" do
51
- before do
52
- server.stub!(:mask).and_return(/.*master.*/)
53
- end
54
-
55
- context "when policy is include" do
56
- before do
57
- server.stub!(:mask_policy).and_return("include")
58
- end
59
-
60
- it "ignores builds if mask doesn't match build name" do
61
- server.should_process_build("blah-development").should be_false
62
- end
63
-
64
- it "processes builds if mask matches build name" do
65
- server.should_process_build("blah-master").should be_true
66
- end
67
- end
68
-
69
- context "when policy is exclude" do
70
- before do
71
- server.stub!(:mask_policy).and_return("exclude")
72
- end
73
-
74
- it "ignores builds if mask matches build name" do
75
- server.should_process_build("blah-master").should be_false
76
- end
77
-
78
- it "processes builds if mask doesn't match build name" do
79
- server.should_process_build("blah-development").should be_true
80
- end
81
- end
82
-
83
- context "when policy is undefined" do
84
- before do
85
- server.stub!(:mask_policy).and_return(nil)
86
- end
87
-
88
- it "ignores builds if mask matches build name" do
89
- server.should_process_build("blah-master").should be_false
90
- end
91
-
92
- it "processes builds if mask doesn't match build name" do
93
- server.should_process_build("blah-development").should be_true
94
- end
95
- end
96
-
97
- context "when policy is unexpected" do
98
- before do
99
- server.stub!(:mask_policy).and_return("trash")
100
- end
101
-
102
- it "ignores builds if mask matches build name" do
103
- server.should_process_build("blah-master").should be_false
104
- end
105
-
106
- it "processes builds if mask doesn't match build name" do
107
- server.should_process_build("blah-development").should be_true
108
- end
109
- end
110
- end
111
-
112
- context "when mask doesn't" do
113
- before do
114
- server.stub!(:mask).and_return(nil)
115
- end
116
-
117
- it "should process all jobs" do
118
- server.should_process_build("blah-development").should be_true
119
- end
120
- end
121
- end
122
- end
123
-
124
- # vim:set foldmethod=syntax foldlevel=1:
@@ -1,124 +0,0 @@
1
- $:.push File.expand_path("lib", __FILE__)
2
-
3
- require 'spec_helper'
4
- require 'build_status_server'
5
-
6
- describe BuildStatusServer::Server do
7
- let!(:server) { BuildStatusServer::Server.new(:config => '/dev/null') }
8
-
9
- describe "#load_config_file" do
10
- it "should use the supplied argument" do
11
- config_file = '/dev/null'
12
- File.should_receive(:exists?).and_return(true)
13
- server.load_config_file(config_file)
14
- end
15
- end
16
-
17
- describe "#load_store" do
18
-
19
- before do
20
- server.stub!(:store_file).and_return("/tmp/build")
21
- end
22
-
23
- it "initializes an empty hash if store file doesn't exist" do
24
- server.load_store
25
- server.store.should == {}
26
- end
27
-
28
- it "initializes an empty hash if store file is empty" do
29
- require "tempfile"
30
- f = Tempfile.new("server_spec")
31
- server.stub!(:store_file).and_return(f.path)
32
-
33
- server.load_store
34
- server.store.should == {}
35
- end
36
-
37
- it "initializes a hash with the contents of the store file" do
38
- server.stub!(:store_file).and_return("spec/support/build_result.yml")
39
- server.load_store
40
-
41
- server.store.should == {"blah" => "SUCCESS", "test" => "SUCCESS"}
42
- end
43
- end
44
-
45
- describe "#notify"
46
- describe "#process_all_statuses"
47
- describe "#process_job"
48
-
49
- describe "#should_process_build" do
50
- context "when mask exists" do
51
- before do
52
- server.stub!(:mask).and_return(/.*master.*/)
53
- end
54
-
55
- context "when policy is include" do
56
- before do
57
- server.stub!(:mask_policy).and_return("include")
58
- end
59
-
60
- it "ignores builds if mask doesn't match build name" do
61
- server.should_process_build("blah-development").should be_false
62
- end
63
-
64
- it "processes builds if mask matches build name" do
65
- server.should_process_build("blah-master").should be_true
66
- end
67
- end
68
-
69
- context "when policy is exclude" do
70
- before do
71
- server.stub!(:mask_policy).and_return("exclude")
72
- end
73
-
74
- it "ignores builds if mask matches build name" do
75
- server.should_process_build("blah-master").should be_false
76
- end
77
-
78
- it "processes builds if mask doesn't match build name" do
79
- server.should_process_build("blah-development").should be_true
80
- end
81
- end
82
-
83
- context "when policy is undefined" do
84
- before do
85
- server.stub!(:mask_policy).and_return(nil)
86
- end
87
-
88
- it "ignores builds if mask matches build name" do
89
- server.should_process_build("blah-master").should be_false
90
- end
91
-
92
- it "processes builds if mask doesn't match build name" do
93
- server.should_process_build("blah-development").should be_true
94
- end
95
- end
96
-
97
- context "when policy is unexpected" do
98
- before do
99
- server.stub!(:mask_policy).and_return("trash")
100
- end
101
-
102
- it "ignores builds if mask matches build name" do
103
- server.should_process_build("blah-master").should be_false
104
- end
105
-
106
- it "processes builds if mask doesn't match build name" do
107
- server.should_process_build("blah-development").should be_true
108
- end
109
- end
110
- end
111
-
112
- context "when mask doesn't" do
113
- before do
114
- server.stub!(:mask).and_return(nil)
115
- end
116
-
117
- it "should process all jobs" do
118
- server.should_process_build("blah-development").should be_true
119
- end
120
- end
121
- end
122
- end
123
-
124
- # vim:set foldmethod=syntax foldlevel=1:
data/spec/spec_helper.rb~ DELETED
@@ -1,15 +0,0 @@
1
- # This file was generated by the `rspec --init` command. Conventionally, all
2
- # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
- # Require this file using `require "spec_helper.rb"` to ensure that it is only
4
- # loaded once.
5
- #
6
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
-
8
- #require "rspec/core"
9
- #require "rspec/mocks"
10
- #
11
- #RSpec.configure do |config|
12
- # config.treat_symbols_as_metadata_keys_with_true_values = true
13
- # config.run_all_when_everything_filtered = true
14
- # config.filter_run :focus
15
- #end