janky 0.10.2 → 0.11.0

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.
@@ -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