github-xcode-bot-builder 0.0.1
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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +71 -0
- data/LICENSE +20 -0
- data/README.md +77 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/bin/bot-delete +9 -0
- data/bin/bot-devices +9 -0
- data/bin/bot-status +9 -0
- data/bin/bot-sync-github +9 -0
- data/lib/bot_builder.rb +215 -0
- data/lib/bot_cli.rb +39 -0
- data/lib/bot_config.rb +58 -0
- data/lib/bot_github.rb +208 -0
- data/spec/github_xcode_bot_builder_spec.rb +7 -0
- data/spec/spec_helper.rb +12 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 59137bad110f52349a68bd7159f7bf012bf64524
|
4
|
+
data.tar.gz: 70ac9923f4199f55fa8e1369048a4d3cc5a27c33
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e00b616e8f1bb4b6faf15604b190a335c5f7f5c2a8b25e47d46bc14e377c838e1ba779f070f3a8989783bf670ab67b2afd6f9261613e1f63e94b7c7712bbfd21
|
7
|
+
data.tar.gz: ae108e20b23815b6c2d3c922d4d497cdca8d7868aea4b469909f2d8ae8a46f9556d55f9ba169b86d0b6f74a8c2e02737cbef9d122439f33e44ad76cf7b586fcb
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
addressable (2.3.5)
|
5
|
+
builder (3.2.2)
|
6
|
+
diff-lcs (1.1.3)
|
7
|
+
faraday (0.8.8)
|
8
|
+
multipart-post (~> 1.2.0)
|
9
|
+
git (1.2.6)
|
10
|
+
github_api (0.10.1)
|
11
|
+
addressable
|
12
|
+
faraday (~> 0.8.1)
|
13
|
+
hashie (>= 1.2)
|
14
|
+
multi_json (~> 1.4)
|
15
|
+
nokogiri (~> 1.5.2)
|
16
|
+
oauth2
|
17
|
+
hashie (2.0.5)
|
18
|
+
highline (1.6.20)
|
19
|
+
httpauth (0.2.0)
|
20
|
+
jeweler (1.8.8)
|
21
|
+
builder
|
22
|
+
bundler (~> 1.0)
|
23
|
+
git (>= 1.2.5)
|
24
|
+
github_api (= 0.10.1)
|
25
|
+
highline (>= 1.6.15)
|
26
|
+
nokogiri (= 1.5.10)
|
27
|
+
rake
|
28
|
+
rdoc
|
29
|
+
json (1.8.1)
|
30
|
+
jwt (0.1.8)
|
31
|
+
multi_json (>= 1.5)
|
32
|
+
multi_json (1.8.2)
|
33
|
+
multi_xml (0.5.5)
|
34
|
+
multipart-post (1.2.0)
|
35
|
+
nokogiri (1.5.10)
|
36
|
+
oauth2 (0.9.2)
|
37
|
+
faraday (~> 0.8)
|
38
|
+
httpauth (~> 0.2)
|
39
|
+
jwt (~> 0.1.4)
|
40
|
+
multi_json (~> 1.0)
|
41
|
+
multi_xml (~> 0.5)
|
42
|
+
rack (~> 1.2)
|
43
|
+
octokit (2.5.0)
|
44
|
+
sawyer (~> 0.5.1)
|
45
|
+
parseconfig (1.0.2)
|
46
|
+
rack (1.5.2)
|
47
|
+
rake (10.1.0)
|
48
|
+
rdoc (3.12.2)
|
49
|
+
json (~> 1.4)
|
50
|
+
rspec (2.8.0)
|
51
|
+
rspec-core (~> 2.8.0)
|
52
|
+
rspec-expectations (~> 2.8.0)
|
53
|
+
rspec-mocks (~> 2.8.0)
|
54
|
+
rspec-core (2.8.0)
|
55
|
+
rspec-expectations (2.8.0)
|
56
|
+
diff-lcs (~> 1.1.2)
|
57
|
+
rspec-mocks (2.8.0)
|
58
|
+
sawyer (0.5.1)
|
59
|
+
addressable (~> 2.3.5)
|
60
|
+
faraday (~> 0.8, < 0.10)
|
61
|
+
|
62
|
+
PLATFORMS
|
63
|
+
ruby
|
64
|
+
|
65
|
+
DEPENDENCIES
|
66
|
+
bundler (~> 1.0)
|
67
|
+
jeweler (~> 1.8.7)
|
68
|
+
octokit (~> 2.0)
|
69
|
+
parseconfig (~> 1.0.2)
|
70
|
+
rdoc (~> 3.12)
|
71
|
+
rspec (~> 2.8.0)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 ModCloth, Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
Github Xcode Bot Builder
|
2
|
+
========================
|
3
|
+
|
4
|
+
A command line tool that creates/manages/deletes Xcode 5 server bots for each Github pull request. When a pull request is opened
|
5
|
+
a corresponding Xcode bot is created. When a new commit is pushed the bot is re-run. When the build finishes the github
|
6
|
+
pull request status is updated with a comment if there's an error. Users can request that a pull request be retested by
|
7
|
+
adding a comment that includes the word "retest" (case insensitive). When a pull request is closed the corresponding
|
8
|
+
bot is deleted.
|
9
|
+
|
10
|
+
Setup
|
11
|
+
=====
|
12
|
+
Make sure your Xcode server is correctly setup to allow ANYONE to create a build (without a username or password, see suggested features below).
|
13
|
+
Then make sure you can manually create and execute a build and run it.
|
14
|
+
|
15
|
+
Create a ~/.bot-sync-github.cfg
|
16
|
+
|
17
|
+
Go to your [Github Account Settings](https://github.com/settings/applications) and create a personal access token which
|
18
|
+
you will use as your *github_access_token* so that the **bot-sync-github** script can access your github repo
|
19
|
+
|
20
|
+
```
|
21
|
+
github_access_token = 57244a72a7ca33931a40eb4ec21621505ab9f6b3
|
22
|
+
github_url = https://github.com/someuser/Some-Repo.git
|
23
|
+
github_repo = someuser/Some-Repo
|
24
|
+
xcode_server = 192.168.10.123
|
25
|
+
xcode_devices = iphonesimulator iPhone Retina (4-inch) 7.0|iphonesimulator iPhone Retina (4-inch) 6.1
|
26
|
+
xcode_scheme = Some-Scheme-Name-app
|
27
|
+
xcode_project_or_workspace = SomeProject.xcworkspace # or SomeProject.xcproject
|
28
|
+
```
|
29
|
+
|
30
|
+
Note that *xcode_devices* need to be pipe delimited. To get the list of available devices run the bot-devices command.
|
31
|
+
The *xcode_server* can either be an ip address or a hostname.
|
32
|
+
|
33
|
+
Manually run **bot-sync-github** from the command line to make sure it works
|
34
|
+
|
35
|
+
Schedule **bot-sync-github** to run in cron every couple of minutes. For example if you're using RVM:
|
36
|
+
|
37
|
+
```
|
38
|
+
*/2 * * * * $HOME/.rvm/bin/ruby-2.0.0-p247 $HOME/.rvm/gems/ruby-2.0.0-p247/bin/bot-sync-github >> /tmp/bot-sync-github.log 2>&1
|
39
|
+
```
|
40
|
+
|
41
|
+
Troubleshooting
|
42
|
+
===============
|
43
|
+
Send us a pull request with your troubleshooting tips here!
|
44
|
+
|
45
|
+
Contributing
|
46
|
+
============
|
47
|
+
|
48
|
+
* Github Xcode Bot Builder uses [Jeweler](https://github.com/technicalpickles/jeweler) for managing the Gem, versioning,
|
49
|
+
generating the Gemspec, etc. so do not manually edit the gemspec since it is auto generated from the Rakefile.
|
50
|
+
* Check out the latest **master** to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
51
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
52
|
+
* Fork the project.
|
53
|
+
* Start a feature/bugfix branch.
|
54
|
+
* Commit and push until you are happy with your contribution.
|
55
|
+
* Don't forget to add yourself to the contributors section below
|
56
|
+
|
57
|
+
Suggested features to contribute
|
58
|
+
================================
|
59
|
+
* Support for configuring username and password to use with your Xcode server
|
60
|
+
* Add specs that use VCR to help us add test coverage
|
61
|
+
* Add support for multiple repositories
|
62
|
+
* Add better error handling
|
63
|
+
* Update this README.md to make it easier for new users to get started and troubleshoot
|
64
|
+
|
65
|
+
Contributors
|
66
|
+
============
|
67
|
+
- [ModCloth](http://www.modcloth.com/)
|
68
|
+
- [Geoffery Nix](http://github.com/geoffnix)
|
69
|
+
- [Two Bit Labs](http://twobitlabs.com/)
|
70
|
+
- [Todd Huss](http://github.com/thuss)
|
71
|
+
|
72
|
+
Copyright
|
73
|
+
=========
|
74
|
+
|
75
|
+
Copyright (c) 2013 ModCloth. See LICENSE for further details.
|
76
|
+
|
77
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "github-xcode-bot-builder"
|
18
|
+
gem.homepage = "http://github.com/ModCloth/github-xcode-bot-builder"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Create Xcode bots to run when github pull requests are created or updated}
|
21
|
+
gem.description = %Q{A command line tool that can be run via cron that configures and manages Xcode server bots for each pull request}
|
22
|
+
gem.email = ""
|
23
|
+
gem.authors = ["ModCloth", "Two Bit Labs", "Geoffery Nix", "Todd Huss"]
|
24
|
+
gem.executables = ['bot-sync-github', 'bot-devices', 'bot-status', 'bot-delete']
|
25
|
+
# dependencies defined in Gemfile
|
26
|
+
end
|
27
|
+
Jeweler::RubygemsDotOrgTasks.new
|
28
|
+
|
29
|
+
require 'rspec/core'
|
30
|
+
require 'rspec/core/rake_task'
|
31
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
32
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
36
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
37
|
+
spec.rcov = true
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rdoc/task'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "github-xcode-bot-builder #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/bot-delete
ADDED
data/bin/bot-devices
ADDED
data/bin/bot-status
ADDED
data/bin/bot-sync-github
ADDED
data/lib/bot_builder.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'cgi/cookie'
|
4
|
+
require 'SecureRandom'
|
5
|
+
require 'json'
|
6
|
+
require 'pp'
|
7
|
+
require 'bot_config'
|
8
|
+
require 'singleton'
|
9
|
+
require 'ostruct'
|
10
|
+
|
11
|
+
class BotBuilder
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
def delete_bot(guid)
|
15
|
+
success = false
|
16
|
+
service_requests = [ service_request('deleteBotWithGUID:', [guid]) ]
|
17
|
+
delete_info = batch_service_request(service_requests)
|
18
|
+
if (delete_info['responses'][0]['responseStatus'] == 'succeeded')
|
19
|
+
puts "BOT Deleted #{guid}"
|
20
|
+
success = true
|
21
|
+
else
|
22
|
+
puts "Error deleting BOT #{guid}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_bot(short_name, long_name, branch, scm_url, project_path, scheme_name, devices = [])
|
27
|
+
device_guids = find_guids_for_devices(devices)
|
28
|
+
if (device_guids.count != devices.count)
|
29
|
+
puts "Some of the following devices could not be found on the server: #{devices}"
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
|
33
|
+
scm_guid = find_guid_for_scm_url(scm_url)
|
34
|
+
if (scm_guid.nil? || scm_guid.empty?)
|
35
|
+
puts "Could not find repository on the server #{scm_url}"
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create the bot
|
40
|
+
buildSchemeKey = (project_path =~ /xcworkspace/) ? :buildWorkspacePath : :buildProjectPath
|
41
|
+
|
42
|
+
service_requests = [
|
43
|
+
service_request('createBotWithProperties:', [
|
44
|
+
{
|
45
|
+
shortName: short_name,
|
46
|
+
longName: long_name,
|
47
|
+
extendedAttributes: {
|
48
|
+
scmInfo: {
|
49
|
+
"/" => {
|
50
|
+
scmBranch: branch,
|
51
|
+
}
|
52
|
+
},
|
53
|
+
scmInfoGUIDMap: {
|
54
|
+
"/" => scm_guid
|
55
|
+
},
|
56
|
+
buildSchemeKey => project_path,
|
57
|
+
buildSchemeName: scheme_name,
|
58
|
+
pollForSCMChanges: false,
|
59
|
+
buildOnTrigger: false,
|
60
|
+
buildFromClean: true,
|
61
|
+
integratePerformsAnalyze: true,
|
62
|
+
integratePerformsTest: true,
|
63
|
+
integratePerformsArchive: false,
|
64
|
+
deviceSpecification: "specificDevices",
|
65
|
+
deviceInfo: device_guids
|
66
|
+
},
|
67
|
+
notifyCommitterOnSuccess: false,
|
68
|
+
notifyCommitterOnFailure: false,
|
69
|
+
type: "com.apple.entity.Bot"
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
])
|
74
|
+
]
|
75
|
+
bot_info = batch_service_request(service_requests)
|
76
|
+
bot_guid = bot_info['responses'][0]['response']['guid']
|
77
|
+
puts "BOT Created #{bot_guid} #{short_name}"
|
78
|
+
|
79
|
+
# Start the bot
|
80
|
+
start_bot bot_guid
|
81
|
+
|
82
|
+
bot_guid
|
83
|
+
end
|
84
|
+
|
85
|
+
def start_bot(bot_guid)
|
86
|
+
service_requests = [ service_request('startBotRunForBotGUID:', [bot_guid]) ]
|
87
|
+
bot_start_info = batch_service_request(service_requests)
|
88
|
+
puts "BOT Started #{bot_guid}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def status_of_all_bots
|
92
|
+
# After immediately creating: latest_run_status "" run_sub_status ""
|
93
|
+
# While running: latest_run_status "running" run_sub_status ""
|
94
|
+
# After completion: latest_run_status "completed" run_sub_status "build-failed|build-errors|test-failures|warnings|analysis-issues|succeeded"
|
95
|
+
service_requests = [ service_request('query:', [
|
96
|
+
{
|
97
|
+
fields: ['guid','tinyID','latestRunStatus','latestRunSubStatus'],
|
98
|
+
entityTypes: ["com.apple.entity.Bot"]
|
99
|
+
}
|
100
|
+
], 'SearchService') ]
|
101
|
+
status_info = batch_service_request(service_requests)
|
102
|
+
results = status_info['responses'][0]['response']['results']
|
103
|
+
statuses = {}
|
104
|
+
results.each do |result|
|
105
|
+
bot = OpenStruct.new result['entity']
|
106
|
+
bot.status_url = "http://#{BotConfig.instance.xcode_server_hostname}/xcode/bots/#{bot.tinyID}"
|
107
|
+
bot.latest_run_status = (bot.latestRunStatus.nil? || bot.latestRunStatus.empty?) ? :unknown : bot.latestRunStatus.to_sym
|
108
|
+
bot.latest_run_sub_status = (bot.latestRunSubStatus.nil? || bot.latestRunSubStatus.empty?) ? :unknown : bot.latestRunSubStatus.to_sym
|
109
|
+
bot.short_name = bot.tinyID
|
110
|
+
bot.short_name_without_version = bot.short_name.sub(/_v\d*$/, '_v')
|
111
|
+
statuses[bot.short_name_without_version] = bot
|
112
|
+
end
|
113
|
+
statuses
|
114
|
+
end
|
115
|
+
|
116
|
+
def status
|
117
|
+
status_of_all_bots.values.each do |bot|
|
118
|
+
puts "#{bot.status_url} #{bot.latest_run_status} #{bot.latest_run_sub_status}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def devices
|
123
|
+
device_info = get_device_info
|
124
|
+
device_info.each do |device|
|
125
|
+
puts device_string_for_device(device)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def find_guid_for_scm_url(scm_url)
|
132
|
+
scm_info = get_scm_info
|
133
|
+
scm_guid = nil
|
134
|
+
scm_info.each do |scm|
|
135
|
+
if (scm['scmRepoPath'] == scm_url)
|
136
|
+
scm_guid = scm['scmGUID']
|
137
|
+
end
|
138
|
+
end
|
139
|
+
scm_guid
|
140
|
+
end
|
141
|
+
|
142
|
+
def find_guids_for_devices(devices)
|
143
|
+
device_info = get_device_info
|
144
|
+
device_guids = []
|
145
|
+
device_info.each do |device|
|
146
|
+
device_string = device_string_for_device device
|
147
|
+
if (devices.include? device_string)
|
148
|
+
device_guids << device['guid']
|
149
|
+
end
|
150
|
+
end
|
151
|
+
device_guids
|
152
|
+
end
|
153
|
+
|
154
|
+
def device_string_for_device(device)
|
155
|
+
"#{device['adcDevicePlatform']} #{device['adcDeviceName']} #{device['adcDeviceSoftwareVersion']}"
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_device_info
|
159
|
+
# Put to get device and Device Info
|
160
|
+
service_requests = [
|
161
|
+
service_request('allDevices', [])
|
162
|
+
]
|
163
|
+
device_info = batch_service_request(service_requests)['responses'][0]['response']
|
164
|
+
device_info
|
165
|
+
end
|
166
|
+
|
167
|
+
def get_scm_info
|
168
|
+
# Put to get device and Device Info
|
169
|
+
service_requests = [
|
170
|
+
service_request('findAllSCMInfos', [])
|
171
|
+
]
|
172
|
+
scm_info = batch_service_request(service_requests)['responses'][0]['response']
|
173
|
+
scm_info
|
174
|
+
end
|
175
|
+
|
176
|
+
def get_session_guid
|
177
|
+
# Get the guid
|
178
|
+
if (@session_guid == nil)
|
179
|
+
response = Net::HTTP.get_response(URI.parse("http://#{BotConfig.instance.xcode_server_hostname}/xcode"))
|
180
|
+
cookies = CGI::Cookie::parse(response['set-cookie'])
|
181
|
+
@session_guid = cookies['cc.collabd_session_guid']
|
182
|
+
end
|
183
|
+
@session_guid
|
184
|
+
end
|
185
|
+
|
186
|
+
def batch_service_request(service_requests)
|
187
|
+
payload = {
|
188
|
+
type: 'com.apple.BatchServiceRequest' ,
|
189
|
+
requests: service_requests
|
190
|
+
}
|
191
|
+
http = Net::HTTP.new(BotConfig.instance.xcode_server_hostname)
|
192
|
+
request = Net::HTTP::Put.new('/collabdproxy')
|
193
|
+
request['Content-Type'] = 'application/json; charset=UTF-8'
|
194
|
+
request['Cookie'] = "cc.collabd_session_guid=#{@session_guid}"
|
195
|
+
request.body = payload.to_json
|
196
|
+
response = http.request(request)
|
197
|
+
json = JSON.parse(response.body)
|
198
|
+
# response_status = json['responses'][0]['responseStatus']
|
199
|
+
# puts "Result status #{response_status}"
|
200
|
+
json
|
201
|
+
end
|
202
|
+
|
203
|
+
def service_request(name, arguments, service = 'XCBotService')
|
204
|
+
get_session_guid
|
205
|
+
{
|
206
|
+
type: 'com.apple.ServiceRequest',
|
207
|
+
arguments: arguments,
|
208
|
+
sessionGUID: @session_guid,
|
209
|
+
serviceName: service,
|
210
|
+
methodName: name,
|
211
|
+
expandReferencedObjects: false
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
data/lib/bot_cli.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'bot_builder'
|
2
|
+
require 'bot_github'
|
3
|
+
|
4
|
+
# Patch net/http to set a reasonable open_timeout to prevent hanging
|
5
|
+
module Net
|
6
|
+
class HTTP
|
7
|
+
alias old_initialize initialize
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
old_initialize(*args)
|
11
|
+
@open_timeout = 60
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class BotCli
|
17
|
+
|
18
|
+
def delete(args)
|
19
|
+
guid = args[0]
|
20
|
+
if (guid == nil || guid.empty?)
|
21
|
+
$stderr.puts "Missing guid of bot to delete"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
BotBuilder.instance.delete_bot guid
|
25
|
+
end
|
26
|
+
|
27
|
+
def status(args)
|
28
|
+
BotBuilder.instance.status
|
29
|
+
end
|
30
|
+
|
31
|
+
def devices(args)
|
32
|
+
BotBuilder.instance.devices
|
33
|
+
end
|
34
|
+
|
35
|
+
def sync_github(args)
|
36
|
+
BotGithub.instance.sync
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/bot_config.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'parseconfig'
|
3
|
+
|
4
|
+
class BotConfig
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@filename = File.expand_path('~/.bot-sync-github.cfg')
|
9
|
+
if (!File.exists? @filename)
|
10
|
+
$stderr.puts "Missing configuration file #{@filename}"
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
@config = ParseConfig.new(@filename)
|
15
|
+
|
16
|
+
# Make sure every param is configured properly since param will throw an error for a missing key
|
17
|
+
[:xcode_server, :github_url, :github_repo, :github_access_token, :xcode_devices, :xcode_scheme, :xcode_project_or_workspace].each do |key|
|
18
|
+
param key
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def xcode_server_hostname
|
23
|
+
param :xcode_server
|
24
|
+
end
|
25
|
+
|
26
|
+
def github_access_token
|
27
|
+
param :github_access_token
|
28
|
+
end
|
29
|
+
|
30
|
+
def scm_path
|
31
|
+
param :github_url
|
32
|
+
end
|
33
|
+
|
34
|
+
def github_repo
|
35
|
+
param :github_repo
|
36
|
+
end
|
37
|
+
|
38
|
+
def xcode_devices
|
39
|
+
param(:xcode_devices).split('|')
|
40
|
+
end
|
41
|
+
|
42
|
+
def xcode_scheme
|
43
|
+
param :xcode_scheme
|
44
|
+
end
|
45
|
+
|
46
|
+
def xcode_project_or_workspace
|
47
|
+
param :xcode_project_or_workspace
|
48
|
+
end
|
49
|
+
|
50
|
+
def param(key)
|
51
|
+
value = @config[key.to_s]
|
52
|
+
if (value.nil?)
|
53
|
+
$stderr.puts "Missing configuration key #{key} in #{@filename}"
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
value
|
57
|
+
end
|
58
|
+
end
|
data/lib/bot_github.rb
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'singleton'
|
3
|
+
require 'bot_config'
|
4
|
+
require 'bot_builder'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
class BotGithub
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@client = Octokit::Client.new :access_token => BotConfig.instance.github_access_token
|
12
|
+
@client.login
|
13
|
+
end
|
14
|
+
|
15
|
+
def sync
|
16
|
+
puts "\nStarting Github Xcode Bot Builder #{Time.now}\n-----------------------------------------------------------"
|
17
|
+
# Check to see if we're already running and skip this run if so
|
18
|
+
running_instances = `ps aux | grep [b]ot-sync-github | grep -v bin/sh`.split("\n")
|
19
|
+
if (running_instances.count > 1)
|
20
|
+
$stderr.puts "Skipping run since bot-sync-github is already running"
|
21
|
+
PP.pp(running_instances, STDERR)
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
|
25
|
+
bot_statuses = BotBuilder.instance.status_of_all_bots
|
26
|
+
bots_processed = []
|
27
|
+
pull_requests.each do |pr|
|
28
|
+
# Check if a bot exists for this PR
|
29
|
+
bot = bot_statuses[pr.bot_short_name_without_version]
|
30
|
+
bots_processed << pr.bot_short_name
|
31
|
+
if (bot.nil?)
|
32
|
+
# Create a new bot
|
33
|
+
BotBuilder.instance.create_bot(pr.bot_short_name, pr.bot_long_name, pr.branch,
|
34
|
+
BotConfig.instance.scm_path,
|
35
|
+
BotConfig.instance.xcode_project_or_workspace,
|
36
|
+
BotConfig.instance.xcode_scheme,
|
37
|
+
BotConfig.instance.xcode_devices)
|
38
|
+
create_status_new_build(pr)
|
39
|
+
else
|
40
|
+
github_state_cur = latest_github_state(pr).state # :unknown :pending :success :error :failure
|
41
|
+
github_state_new = convert_bot_status_to_github_state(bot)
|
42
|
+
if (github_state_new == :pending && github_state_cur != github_state_new)
|
43
|
+
# User triggered a new build by clicking Integrate on the Xcode server interface
|
44
|
+
create_status(pr, github_state_new, convert_bot_status_to_github_description(bot), bot.status_url)
|
45
|
+
elsif (github_state_new != :unknown && github_state_cur != github_state_new)
|
46
|
+
# Build has passed or failed so update status and comment on the issue
|
47
|
+
create_comment_for_bot_status(pr, bot)
|
48
|
+
create_status(pr, github_state_new, convert_bot_status_to_github_description(bot), bot.status_url)
|
49
|
+
elsif (github_state_cur == :unknown)
|
50
|
+
# Unknown state occurs when there's a new commit so trigger a new build
|
51
|
+
BotBuilder.instance.start_bot(bot.guid)
|
52
|
+
create_status_new_build(pr)
|
53
|
+
elsif (user_requested_retest(pr, bot))
|
54
|
+
BotBuilder.instance.start_bot(bot.guid)
|
55
|
+
else
|
56
|
+
puts "PR #{pr.number} (#{github_state_cur}) is up to date for bot #{bot.short_name}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Delete bots that no longer have open pull requests
|
62
|
+
bots_unprocessed = bot_statuses.keys - bots_processed
|
63
|
+
bots_unprocessed.each do |bot_short_name|
|
64
|
+
bot = bot_statuses[bot_short_name]
|
65
|
+
BotBuilder.instance.delete_bot(bot.guid) unless !is_managed_bot(bot)
|
66
|
+
end
|
67
|
+
|
68
|
+
puts "-----------------------------------------------------------\nFinished Github Xcode Bot Builder #{Time.now}\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def convert_bot_status_to_github_description(bot)
|
74
|
+
bot_run_status = bot.latest_run_status # :unknown :running :completed
|
75
|
+
bot_run_sub_status = bot.latest_run_sub_status # :unknown :build-failed :build-errors :test-failures :warnings :analysis-issues :succeeded
|
76
|
+
github_description = bot_run_status == :running ? "Build Triggered." : ""
|
77
|
+
if (bot_run_status == :completed || bot_run_status == :failed)
|
78
|
+
github_description = bot_run_sub_status.to_s.split('-').map(&:capitalize).join(' ') + "."
|
79
|
+
end
|
80
|
+
github_description
|
81
|
+
end
|
82
|
+
|
83
|
+
def convert_bot_status_to_github_state(bot)
|
84
|
+
bot_run_status = bot.latest_run_status # :unknown :running :completed
|
85
|
+
bot_run_sub_status = bot.latest_run_sub_status # :unknown :build-failed :build-errors :test-failures :warnings :analysis-issues :succeeded
|
86
|
+
github_state = bot_run_status == :running ? :pending : :unknown
|
87
|
+
if (bot_run_status == :completed || bot_run_status == :failed)
|
88
|
+
github_state = case bot_run_sub_status
|
89
|
+
when :"test-failures", :"warnings", :"analysis-issues"
|
90
|
+
:failure
|
91
|
+
when :"succeeded"
|
92
|
+
:success
|
93
|
+
else
|
94
|
+
:error
|
95
|
+
end
|
96
|
+
end
|
97
|
+
github_state
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_comment_for_bot_status(pr, bot)
|
101
|
+
message = "Build " + convert_bot_status_to_github_state(bot).to_s.capitalize + ": " + convert_bot_status_to_github_description(bot)
|
102
|
+
message += "\n#{bot.status_url}"
|
103
|
+
@client.add_comment(BotConfig.instance.github_repo, pr.number, message)
|
104
|
+
puts "PR #{pr.number} added comment \"#{message}\""
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_status_new_build(pr)
|
108
|
+
create_status(pr, :pending, "Build Triggered.")
|
109
|
+
end
|
110
|
+
|
111
|
+
def create_status(pr, github_state, description = nil, target_url = nil)
|
112
|
+
options = {}
|
113
|
+
if (!description.nil?)
|
114
|
+
options['description'] = description
|
115
|
+
end
|
116
|
+
if (!target_url.nil?)
|
117
|
+
options['target_url'] = target_url
|
118
|
+
end
|
119
|
+
@client.create_status(BotConfig.instance.github_repo, pr.sha, github_state.to_s, options)
|
120
|
+
puts "PR #{pr.number} status updated to \"#{github_state}\" with description \"#{description}\""
|
121
|
+
end
|
122
|
+
|
123
|
+
def latest_github_state(pr)
|
124
|
+
statuses = @client.statuses(BotConfig.instance.github_repo, pr.sha)
|
125
|
+
status = OpenStruct.new
|
126
|
+
status.state = statuses[0].state.to_sym
|
127
|
+
status.updated_at = statuses[0].updated_at
|
128
|
+
status
|
129
|
+
end
|
130
|
+
|
131
|
+
def pull_requests
|
132
|
+
github_repo = BotConfig.instance.github_repo
|
133
|
+
responses = @client.pull_requests(github_repo)
|
134
|
+
prs = []
|
135
|
+
responses.each do |response|
|
136
|
+
pr = OpenStruct.new
|
137
|
+
pr.sha = response.head.sha
|
138
|
+
pr.branch = response.head.ref
|
139
|
+
pr.title = response.title
|
140
|
+
pr.state = response.state
|
141
|
+
pr.number = response.number
|
142
|
+
pr.updated_at = response.updated_at
|
143
|
+
pr.bot_short_name = bot_short_name(pr)
|
144
|
+
pr.bot_short_name_without_version = bot_short_name_without_version(pr)
|
145
|
+
pr.bot_long_name = bot_long_name(pr)
|
146
|
+
prs << pr
|
147
|
+
end
|
148
|
+
prs
|
149
|
+
end
|
150
|
+
|
151
|
+
def user_requested_retest(pr, bot)
|
152
|
+
should_retest = false
|
153
|
+
github_repo = BotConfig.instance.github_repo
|
154
|
+
|
155
|
+
# Check for a user retest request comment
|
156
|
+
comments = @client.issue_comments(github_repo, pr.number)
|
157
|
+
latest_retest_time = Time.at(0)
|
158
|
+
found_retest_comment = false
|
159
|
+
comments.each do |comment|
|
160
|
+
if (comment.body =~ /retest/i)
|
161
|
+
latest_retest_time = comment.updated_at
|
162
|
+
found_retest_comment = true
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
return should_retest unless found_retest_comment
|
167
|
+
|
168
|
+
# Get the latest status time
|
169
|
+
latest_status_time = latest_github_state(pr)
|
170
|
+
if (latest_status_time.nil? || latest_status_time.updated_at.nil?)
|
171
|
+
latest_status_time = Time.at(0)
|
172
|
+
end
|
173
|
+
|
174
|
+
if (latest_retest_time > latest_status_time.updated_at)
|
175
|
+
should_retest = true
|
176
|
+
puts "PR #{pr.number} user requested a retest"
|
177
|
+
end
|
178
|
+
|
179
|
+
should_retest
|
180
|
+
end
|
181
|
+
|
182
|
+
def bot_long_name(pr)
|
183
|
+
github_repo = BotConfig.instance.github_repo
|
184
|
+
"PR #{pr.number} #{pr.title} #{github_repo}"
|
185
|
+
end
|
186
|
+
|
187
|
+
def bot_short_name(pr)
|
188
|
+
short_name = "#{pr.number}-#{pr.branch}".gsub(/[^[:alnum:]]/, '_') + bot_short_name_suffix
|
189
|
+
short_name
|
190
|
+
end
|
191
|
+
|
192
|
+
# For duplicate bot names xcode server appends a version
|
193
|
+
# bot_short_name_v, bot_short_name_v1, bot_short_name_v2. This method converts bot_short_name_v2 to bot_short_name_v
|
194
|
+
def bot_short_name_without_version(pr)
|
195
|
+
bot_short_name(pr).sub(/_v\d*$/, '_v')
|
196
|
+
end
|
197
|
+
|
198
|
+
def is_managed_bot(bot)
|
199
|
+
# Check the suffix of the bot to see if it matches the bot_short_name_suffix
|
200
|
+
bot.short_name =~ /#{bot_short_name_suffix}\d*$/
|
201
|
+
end
|
202
|
+
|
203
|
+
def bot_short_name_suffix
|
204
|
+
github_repo = BotConfig.instance.github_repo.downcase
|
205
|
+
('_' + github_repo + '_v').gsub(/[^[:alnum:]]/, '_')
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'github-xcode-bot-builder'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: github-xcode-bot-builder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ModCloth
|
8
|
+
- Two Bit Labs
|
9
|
+
- Geoffery Nix
|
10
|
+
- Todd Huss
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2013-11-25 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: octokit
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: parseconfig
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ~>
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 1.0.2
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.0.2
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rspec
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ~>
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 2.8.0
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 2.8.0
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rdoc
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ~>
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '3.12'
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '3.12'
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: bundler
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ~>
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '1.0'
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1.0'
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: jeweler
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ~>
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.8.7
|
93
|
+
type: :development
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ~>
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 1.8.7
|
100
|
+
description: A command line tool that can be run via cron that configures and manages
|
101
|
+
Xcode server bots for each pull request
|
102
|
+
email: ''
|
103
|
+
executables:
|
104
|
+
- bot-sync-github
|
105
|
+
- bot-devices
|
106
|
+
- bot-status
|
107
|
+
- bot-delete
|
108
|
+
extensions: []
|
109
|
+
extra_rdoc_files:
|
110
|
+
- LICENSE
|
111
|
+
- README.md
|
112
|
+
files:
|
113
|
+
- .document
|
114
|
+
- .rspec
|
115
|
+
- Gemfile
|
116
|
+
- Gemfile.lock
|
117
|
+
- LICENSE
|
118
|
+
- README.md
|
119
|
+
- Rakefile
|
120
|
+
- VERSION
|
121
|
+
- bin/bot-delete
|
122
|
+
- bin/bot-devices
|
123
|
+
- bin/bot-status
|
124
|
+
- bin/bot-sync-github
|
125
|
+
- lib/bot_builder.rb
|
126
|
+
- lib/bot_cli.rb
|
127
|
+
- lib/bot_config.rb
|
128
|
+
- lib/bot_github.rb
|
129
|
+
- spec/github_xcode_bot_builder_spec.rb
|
130
|
+
- spec/spec_helper.rb
|
131
|
+
homepage: http://github.com/ModCloth/github-xcode-bot-builder
|
132
|
+
licenses:
|
133
|
+
- MIT
|
134
|
+
metadata: {}
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - '>='
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 2.0.3
|
152
|
+
signing_key:
|
153
|
+
specification_version: 4
|
154
|
+
summary: Create Xcode bots to run when github pull requests are created or updated
|
155
|
+
test_files: []
|