janky 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -67,16 +67,10 @@ module Janky
67
67
  #
68
68
  # Returns the SHA1 as a String or nil when the branch doesn't exists.
69
69
  def self.branch_head_sha(nwo, branch)
70
- response = api.branches(nwo)
70
+ response = api.branch(nwo, branch)
71
71
 
72
- if response.code != "200"
73
- Exception.push_http_response(response)
74
- raise Error, "Failed to get branches"
75
- end
76
-
77
- branches = Yajl.load(response.body)
78
- branch = branches.detect { |b| b["name"] == branch }
79
- branch && branch["commit"]["sha"]
72
+ branch = Yajl.load(response.body)
73
+ branch && branch["sha"]
80
74
  end
81
75
 
82
76
  # Fetch commit details for the given SHA1.
@@ -134,6 +128,22 @@ module Janky
134
128
  api.get(url).code == "200"
135
129
  end
136
130
 
131
+ # Delete a post-receive hook for the given repository.
132
+ #
133
+ # hook_url - The repository's hook_url
134
+ #
135
+ # Returns true or raises an exception.
136
+ def self.hook_delete(url)
137
+ response = api.delete(url)
138
+
139
+ if response.code == "204"
140
+ true
141
+ else
142
+ Exception.push_http_response(response)
143
+ raise Error, "Failed to delete hook"
144
+ end
145
+ end
146
+
137
147
  # Default API implementation that goes over the wire (HTTP).
138
148
  #
139
149
  # Returns nothing.
@@ -16,6 +16,14 @@ module Janky
16
16
  http.request(request)
17
17
  end
18
18
 
19
+ def delete(hook_url)
20
+ path = build_path(URI(hook_url).path)
21
+ request = Net::HTTP::Delete.new(path)
22
+ request.basic_auth(@user, @password)
23
+
24
+ http.request(request)
25
+ end
26
+
19
27
  def trigger(hook_url)
20
28
  path = build_path(URI(hook_url).path + "/test")
21
29
  request = Net::HTTP::Post.new(path)
@@ -40,8 +48,8 @@ module Janky
40
48
  http.request(request)
41
49
  end
42
50
 
43
- def branches(nwo)
44
- path = build_path("repos/#{nwo}/branches")
51
+ def branch(nwo, branch)
52
+ path = build_path("repos/#{nwo}/commits/#{branch}")
45
53
  request = Net::HTTP::Get.new(path)
46
54
  request.basic_auth(@user, @password)
47
55
 
@@ -33,6 +33,10 @@ module Janky
33
33
  Response.new("200")
34
34
  end
35
35
 
36
+ def delete(url)
37
+ Response.new("204")
38
+ end
39
+
36
40
  def repo_get(nwo)
37
41
  repo = {
38
42
  "name" => nwo.split("/").last,
@@ -48,19 +52,9 @@ module Janky
48
52
  end
49
53
  end
50
54
 
51
- def branches(nwo)
52
- data = []
55
+ def branch(nwo, branch)
53
56
 
54
- @branch_shas.each do |(name_with_owner, branch), sha|
55
- if nwo == name_with_owner
56
- data << {
57
- "name" => branch,
58
- "commit" => {
59
- "sha" => sha
60
- }
61
- }
62
- end
63
- end
57
+ data = { "sha" => @branch_shas[[nwo, branch]] }
64
58
 
65
59
  Response.new("200", Yajl.dump(data))
66
60
  end
@@ -12,11 +12,12 @@ module Janky
12
12
  post "/setup" do
13
13
  nwo = params["nwo"]
14
14
  name = params["name"]
15
- repo = Repository.setup(nwo, name)
15
+ tmpl = params["template"]
16
+ repo = Repository.setup(nwo, name, tmpl)
16
17
 
17
18
  if repo
18
19
  url = "#{settings.base_url}#{repo.name}"
19
- [201, "Setup #{repo.name} at #{repo.uri} | #{url}"]
20
+ [201, "Setup #{repo.name} at #{repo.uri} with #{repo.job_config_path.basename} | #{url}"]
20
21
  else
21
22
  [400, "Couldn't access #{nwo}. Check the permissions."]
22
23
  end
@@ -34,9 +35,10 @@ module Janky
34
35
  post %r{\/([-_\.0-9a-zA-Z]+)\/([-_\.a-zA-z0-9\/]+)} do |repo_name, branch_name|
35
36
  repo = find_repo(repo_name)
36
37
  branch = repo.branch_for(branch_name)
37
- room_id = (params["room_id"] && Integer(params["room_id"]) rescue nil)
38
+ room_id = (params["room_id"] rescue nil)
38
39
  user = params["user"]
39
40
  build = branch.head_build_for(room_id, user)
41
+ build ||= repo.build_sha(branch_name, user, room_id)
40
42
 
41
43
  if build
42
44
  build.run
@@ -64,6 +66,20 @@ module Janky
64
66
  end
65
67
  end
66
68
 
69
+ # Update a repository's context
70
+ put %r{\/([-_\.0-9a-zA-Z]+)\/context} do |repo_name|
71
+ context = params["context"]
72
+ repo = find_repo(repo_name)
73
+
74
+ if repo
75
+ repo.context = context
76
+ repo.save
77
+ [200, "Context #{context} set for #{repo_name}"]
78
+ else
79
+ [404, "Unknown Repository #{repo_name}"]
80
+ end
81
+ end
82
+
67
83
  # Get the status of all projects.
68
84
  get "/" do
69
85
  content_type "text/plain"
@@ -100,6 +116,41 @@ module Janky
100
116
  builds.to_json
101
117
  end
102
118
 
119
+ # Get information about how a project is configured
120
+ get %r{\/show\/([-_\.0-9a-zA-Z]+)} do |repo_name|
121
+ repo = find_repo(repo_name)
122
+ res = {
123
+ :name => repo.name,
124
+ :configured_job_template => repo.job_template,
125
+ :used_job_template => repo.job_config_path.basename.to_s,
126
+ :repo => repo.uri,
127
+ :room_id => repo.room_id,
128
+ :enabled => repo.enabled,
129
+ :hook_url => repo.hook_url,
130
+ :context => repo.context
131
+ }
132
+ res.to_json
133
+ end
134
+
135
+ delete %r{\/([-_\.0-9a-zA-Z]+)} do |repo_name|
136
+ repo = find_repo(repo_name)
137
+ repo.destroy
138
+ "Janky project #{repo_name} deleted"
139
+ end
140
+
141
+ # Delete a repository's context
142
+ delete %r{\/([-_\.0-9a-zA-Z]+)\/context} do |repo_name|
143
+ repo = find_repo(repo_name)
144
+
145
+ if repo
146
+ repo.context = nil
147
+ repo.save
148
+ [200, "Context removed for #{repo_name}"]
149
+ else
150
+ [404, "Unknown Repository #{repo_name}"]
151
+ end
152
+ end
153
+
103
154
  # Get the status of a repository's branch.
104
155
  get %r{\/([-_\.0-9a-zA-Z]+)\/([-_\+\.a-zA-z0-9\/]+)} do |repo_name, branch_name|
105
156
  limit = params["limit"]
@@ -120,13 +171,18 @@ module Janky
120
171
  ci build janky
121
172
  ci build janky/fix-everything
122
173
  ci setup github/janky [name]
174
+ ci setup github/janky name template
123
175
  ci toggle janky
124
176
  ci rooms
125
177
  ci set room janky development
178
+ ci set context janky ci/janky
179
+ ci unset context janky
126
180
  ci status
127
181
  ci status janky
128
182
  ci status janky/master
129
183
  ci builds limit [building]
184
+ ci show janky
185
+ ci delete janky
130
186
  EOS
131
187
  end
132
188
 
@@ -9,6 +9,15 @@ module Janky
9
9
  @adapter = Multi.new(Array(notifiers))
10
10
  end
11
11
 
12
+ # Called whenever a build is queued
13
+ #
14
+ # build - the Build record.
15
+ #
16
+ # Returns nothing
17
+ def self.queued(build)
18
+ adapter.queued(build)
19
+ end
20
+
12
21
  # Called whenever a build starts.
13
22
  #
14
23
  # build - the Build record.
@@ -15,7 +15,7 @@ module Janky
15
15
  build.web_url
16
16
  ]
17
17
 
18
- ::Janky::ChatService.speak(message, build.room_id, {:color => color})
18
+ ::Janky::ChatService.speak(message, build.room_id, {:color => color, :build => build})
19
19
  end
20
20
  end
21
21
  end
@@ -0,0 +1,18 @@
1
+ module Janky
2
+ module Notifier
3
+ class FailureService < ChatService
4
+ def self.completed(build)
5
+ return unless need_failure_notification?(build)
6
+ ::Janky::ChatService.speak(message(build), failure_room, {:color => color(build)})
7
+ end
8
+
9
+ def self.failure_room
10
+ ENV["JANKY_CHAT_FAILURE_ROOM"]
11
+ end
12
+
13
+ def self.need_failure_notification?(build)
14
+ build.red? && failure_room != build.room_id
15
+ end
16
+ end
17
+ end
18
+ end
@@ -7,9 +7,26 @@ module Janky
7
7
  # "success" or "failure" when the build is complete.
8
8
  class GithubStatus
9
9
  # Initialize with an OAuth token to POST Statuses with
10
- def initialize(token, api_url)
10
+ def initialize(token, api_url, context = nil)
11
11
  @token = token
12
12
  @api_url = URI(api_url)
13
+ @default_context = context
14
+ end
15
+
16
+ def context(build)
17
+ repository_context(build.repository) || @default_context
18
+ end
19
+
20
+ def repository_context(repository)
21
+ repository && repository.context
22
+ end
23
+
24
+ # Create a Pending Status for the Commit when it is queued.
25
+ def queued(build)
26
+ repo = build.repo_nwo
27
+ path = "repos/#{repo}/statuses/#{build.sha1}"
28
+
29
+ post(path, "pending", build.web_url, "Build ##{build.number} queued", context(build))
13
30
  end
14
31
 
15
32
  # Create a Pending Status for the Commit when it starts.
@@ -17,7 +34,7 @@ module Janky
17
34
  repo = build.repo_nwo
18
35
  path = "repos/#{repo}/statuses/#{build.sha1}"
19
36
 
20
- post(path, "pending", build.web_url, "Build ##{build.number} started")
37
+ post(path, "pending", build.web_url, "Build ##{build.number} started", context(build))
21
38
  end
22
39
 
23
40
  # Create a Success or Failure Status for the Commit.
@@ -31,11 +48,11 @@ module Janky
31
48
  when "failure" then "Build ##{build.number} failed in #{build.duration}s"
32
49
  end
33
50
 
34
- post(path, status, build.web_url, desc)
51
+ post(path, status, build.web_url, desc, context(build))
35
52
  end
36
53
 
37
54
  # Internal: POST the new status to the API
38
- def post(path, status, url, desc)
55
+ def post(path, status, url, desc, context = nil)
39
56
  http = Net::HTTP.new(@api_url.host, @api_url.port)
40
57
  post = Net::HTTP::Post.new("#{@api_url.path}#{path}")
41
58
 
@@ -44,11 +61,18 @@ module Janky
44
61
  post["Content-Type"] = "application/json"
45
62
  post["Authorization"] = "token #{@token}"
46
63
 
47
- post.body = {
64
+ body = {
48
65
  :state => status,
49
66
  :target_url => url,
50
67
  :description => desc,
51
- }.to_json
68
+ }
69
+
70
+ unless context.nil?
71
+ post["Accept"] = "application/vnd.github.she-hulk-preview+json"
72
+ body[:context] = context
73
+ end
74
+
75
+ post.body = body.to_json
52
76
 
53
77
  http.request(post)
54
78
  end
@@ -8,6 +8,9 @@ module Janky
8
8
 
9
9
  attr_reader :notifications
10
10
 
11
+ def queued(build)
12
+ end
13
+
11
14
  def reset!
12
15
  @notifications.clear
13
16
  end
@@ -6,6 +6,12 @@ module Janky
6
6
  @notifiers = notifiers
7
7
  end
8
8
 
9
+ def queued(build)
10
+ @notifiers.each do |notifier|
11
+ notifier.queued(build) if notifier.respond_to?(:queued)
12
+ end
13
+ end
14
+
9
15
  def started(build)
10
16
  @notifiers.each do |notifier|
11
17
  notifier.started(build) if notifier.respond_to?(:started)
@@ -127,10 +127,6 @@ ul.builds a.console{
127
127
  ul.builds li:hover a.console{
128
128
  background-position:65% -90px;
129
129
  }
130
- ul.builds .building a.console{
131
- background:none;
132
- cursor:default;
133
- }
134
130
 
135
131
  ul.builds .status{
136
132
  float:left;
@@ -4,16 +4,19 @@ module Janky
4
4
  has_many :commits, :dependent => :destroy
5
5
  has_many :builds, :through => :branches
6
6
 
7
+ after_commit :delete_hook, :on => :destroy
8
+
7
9
  replicate_associations :builds, :commits, :branches
8
10
 
9
11
  default_scope(order("name"))
10
12
 
11
- def self.setup(nwo, name = nil)
13
+ def self.setup(nwo, name = nil, template = nil)
12
14
  if nwo.nil?
13
15
  raise ArgumentError, "nwo can't be nil"
14
16
  end
15
17
 
16
18
  if repo = Repository.find_by_name(nwo)
19
+ repo.update_attributes!(:job_template => template)
17
20
  repo.setup
18
21
  return repo
19
22
  end
@@ -27,10 +30,10 @@ module Janky
27
30
 
28
31
  repo =
29
32
  if repo = Repository.find_by_name(name)
30
- repo.update_attributes!(:uri => uri)
33
+ repo.update_attributes!(:uri => uri, :job_template => template)
31
34
  repo
32
35
  else
33
- Repository.create!(:name => name, :uri => uri)
36
+ Repository.create!(:name => name, :uri => uri, :job_template => template)
34
37
  end
35
38
 
36
39
  repo.setup
@@ -67,12 +70,47 @@ module Janky
67
70
 
68
71
  # Create or retrieve the given commit.
69
72
  #
70
- # name - The Hash representation of the Commit.
73
+ # commit - The Hash representation of the Commit.
71
74
  #
72
75
  # Returns a Commit record.
73
76
  def commit_for(commit)
74
77
  commits.find_by_sha1(commit[:sha1]) ||
75
- commits.create(commit)
78
+ commits.create!(commit)
79
+ end
80
+
81
+ def commit_for_sha(sha1)
82
+ commit_data = GitHub.commit(nwo, sha1)
83
+ commit_message = commit_data["commit"]["message"]
84
+ commit_url = github_url("commit/#{sha1}")
85
+ author_data = commit_data["commit"]["author"]
86
+ commit_author =
87
+ if email = author_data["email"]
88
+ "#{author_data["name"]} <#{email}>"
89
+ else
90
+ author_data["name"]
91
+ end
92
+
93
+ commit = commit_for({
94
+ :repository => self,
95
+ :sha1 => sha1,
96
+ :author => commit_author,
97
+ :message => commit_message,
98
+ :url => commit_url,
99
+ })
100
+ end
101
+
102
+ # Create a Janky::Build object given a sha
103
+ #
104
+ # sha1 - a string of the target sha to build
105
+ # user - The login of the GitHub user who pushed.
106
+ # room_id - optional Fixnum Campfire room ID. Defaults to the room set on
107
+ # compare - optional String GitHub Compare View URL. Defaults to the
108
+ #
109
+ # Returns the newly created Janky::Build
110
+ def build_sha(sha1, user, room_id = nil, compare = nil)
111
+ return nil unless sha1 =~ /^[0-9a-fA-F]{7,40}$/
112
+ commit = commit_for_sha(sha1)
113
+ commit.build!(user, room_id, compare)
76
114
  end
77
115
 
78
116
  # Jenkins host executing this repo's builds.
@@ -124,7 +162,7 @@ module Janky
124
162
  ChatService.room_name(room_id)
125
163
  end
126
164
 
127
- # Ditto but returns the Fixnum room id. Defaults to the one set
165
+ # Ditto but returns the String room id. Defaults to the one set
128
166
  # in Campfire.setup.
129
167
  def room_id
130
168
  read_attribute(:room_id) || ChatService.default_room_id
@@ -142,9 +180,15 @@ module Janky
142
180
  #
143
181
  # Returns nothing.
144
182
  def setup_hook
145
- if !hook_url || !GitHub.hook_exists?(hook_url)
146
- url = GitHub.hook_create("#{github_owner}/#{github_name}")
147
- update_attributes!(:hook_url => url)
183
+ delete_hook
184
+
185
+ url = GitHub.hook_create("#{github_owner}/#{github_name}")
186
+ update_attributes!(:hook_url => url)
187
+ end
188
+
189
+ def delete_hook
190
+ if self.hook_url? && GitHub.hook_exists?(self.hook_url)
191
+ GitHub.hook_delete(self.hook_url)
148
192
  end
149
193
  end
150
194
 
@@ -156,15 +200,19 @@ module Janky
156
200
  builder.setup(job_name, uri, job_config_path)
157
201
  end
158
202
 
159
- # The path of the Jenkins configuration template. Try "<repo-name>.xml.erb"
160
- # first then fallback to "default.xml.erb" under the root config directory.
203
+ # The path of the Jenkins configuration template. Try
204
+ # "<job_template>.xml.erb" first, "<repo-name>.xml.erb" second, and then
205
+ # fallback to "default.xml.erb" under the root config directory.
161
206
  #
162
207
  # Returns the template path as a Pathname.
163
208
  def job_config_path
209
+ user_override = Janky.jobs_config_dir.join("#{job_template.downcase}.xml.erb") if job_template
164
210
  custom = Janky.jobs_config_dir.join("#{name.downcase}.xml.erb")
165
211
  default = Janky.jobs_config_dir.join("default.xml.erb")
166
212
 
167
- if custom.readable?
213
+ if user_override && user_override.readable?
214
+ user_override
215
+ elsif custom.readable?
168
216
  custom
169
217
  elsif default.readable?
170
218
  default