jenkins-build 0.1.3 → 0.2.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rspec +1 -1
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/README.md +15 -9
- data/lib/jenkins/build.rb +3 -0
- data/lib/jenkins/build/build.rb +20 -0
- data/lib/jenkins/build/cli.rb +48 -5
- data/lib/jenkins/build/client.rb +30 -1
- data/lib/jenkins/build/configuration.rb +10 -0
- data/lib/jenkins/build/hub.rb +32 -0
- data/lib/jenkins/build/test_report.rb +150 -0
- data/lib/jenkins/build/version.rb +1 -1
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b6b94d13fe633861036016e4433bc9cb0262d88
|
4
|
+
data.tar.gz: 1c68f33db52e5b75bb0657a977671ff8415f82d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 323c82d6f72314c238b4e77a0b616a05dc96c3225138c2f6a9975bfa094a9d33e8070688cf85ef173548bc4949266a36d592c5b216fd72bdc43e4a6434521c7e
|
7
|
+
data.tar.gz: 92ce0ba7f74ce0cb6cf6ddb7a6e0080198278421154f1a58071216fb82be8b5b05059751ca98c4d029e995b6528160913bc9721c7b9df3d77b7f75e3cb54a1f1
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -9,14 +9,19 @@ Install it from rubygems.org:
|
|
9
9
|
gem install jenkins-build
|
10
10
|
```
|
11
11
|
|
12
|
-
|
13
12
|
## Usage
|
14
13
|
|
15
|
-
|
16
|
-
|
14
|
+
First, you need to get your user and API Key for Jenkins. To do that you need to
|
15
|
+
log in, in the right corner click your name, then click Configure in the left
|
16
|
+
sidebar and finally press 'Show API Token'. You'll need later both the User ID
|
17
|
+
and API Token.
|
18
|
+
|
19
|
+
Then you have to configure `jenkins-build` to use your jenkins server and
|
20
|
+
project. Do that by running:
|
17
21
|
|
18
22
|
```shell
|
19
|
-
jenkins-build configure
|
23
|
+
jenkins-build configure --api-key JENKINS_API_KEY --user JENKINS_USER_ID
|
24
|
+
--server JENKINS_SERVER_URL --project JENKINS_PROJECT_NAME
|
20
25
|
```
|
21
26
|
|
22
27
|
And then you can trigger build by running:
|
@@ -25,14 +30,15 @@ And then you can trigger build by running:
|
|
25
30
|
jenkins-build trigger
|
26
31
|
```
|
27
32
|
|
28
|
-
To get your API Key go to your Jenkins, log in, in the right corner click your name, then click
|
29
|
-
Configuration in the left sidebar and finally press 'Show API Token'.
|
30
|
-
|
31
33
|
## Development
|
32
34
|
|
33
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
35
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
36
|
+
`bin/console` for an interactive prompt that will allow you to experiment.
|
34
37
|
|
35
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To
|
38
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
39
|
+
release a new version, update the version number in `version.rb`, and then run
|
40
|
+
`bundle exec rake release` to create a git tag for the version, push git commits
|
41
|
+
and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
36
42
|
|
37
43
|
## Contributing
|
38
44
|
|
data/lib/jenkins/build.rb
CHANGED
@@ -4,6 +4,9 @@ module Jenkins
|
|
4
4
|
autoload :CLI, 'jenkins/build/cli'
|
5
5
|
autoload :Configuration, 'jenkins/build/configuration'
|
6
6
|
autoload :Client, 'jenkins/build/client'
|
7
|
+
autoload :Hub, 'jenkins/build/hub'
|
8
|
+
autoload :TestReport, 'jenkins/build/test_report'
|
9
|
+
autoload :Build, 'jenkins/build/build'
|
7
10
|
autoload :VERSION, 'jenkins/build/version'
|
8
11
|
end
|
9
12
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Jenkins
|
2
|
+
module Build
|
3
|
+
class Build
|
4
|
+
BUILDS = %w[\d+ lastBuild lastStableBuild lastSuccessfulBuild lastFailedBuild lastUnstableBuild lastUnsuccessfulBuild lastCompletedBuild].freeze
|
5
|
+
JENKINS_PATH = %r{^/jobs/(?<job>[\w-]+)/(?<build_number>#{Regexp.union(BUILDS)})}.freeze
|
6
|
+
NUMBER = /\A\d+\z/.freeze
|
7
|
+
|
8
|
+
attr_reader :uri, :job, :number
|
9
|
+
|
10
|
+
def initialize(url)
|
11
|
+
@uri = URI(url)
|
12
|
+
|
13
|
+
match = @uri.path.match(JENKINS_PATH) or return
|
14
|
+
|
15
|
+
@job = match[:job]
|
16
|
+
@number = (number = match[:build_number]) =~ NUMBER ? number.to_i : number
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/jenkins/build/cli.rb
CHANGED
@@ -6,6 +6,10 @@ module Jenkins
|
|
6
6
|
|
7
7
|
extend Jenkins::Build::Git
|
8
8
|
|
9
|
+
def self.configuration
|
10
|
+
Jenkins::Build::Configuration
|
11
|
+
end
|
12
|
+
|
9
13
|
desc "configure", "configures this project"
|
10
14
|
option :project, desc: 'Jenkins project name', required: true
|
11
15
|
option :server, desc: 'Jenkins server', required: true
|
@@ -33,14 +37,49 @@ module Jenkins
|
|
33
37
|
option :branch, desc: 'Git branch', default: current_branch
|
34
38
|
desc 'status', 'prints status of a branch'
|
35
39
|
def status
|
36
|
-
|
40
|
+
ci_status = self.ci_status
|
41
|
+
|
42
|
+
puts "#{ci_status.status}: #{ci_status.uri}"
|
43
|
+
end
|
44
|
+
|
45
|
+
option :branch, desc: 'Git branch', default: current_branch
|
46
|
+
option :job, desc: 'Jenkins job', default: configuration.project
|
47
|
+
option :build, desc: 'Jenkins build number', default: 'lastBuild'
|
48
|
+
option :kind, desc: 'Kind of test failures', type: :string, enum: %w[test spec feature]
|
49
|
+
|
50
|
+
desc 'failures', 'prints failed tests and how to run them'
|
51
|
+
def failures
|
52
|
+
ci_status = self.ci_status
|
53
|
+
|
54
|
+
build = ci_status.build || Jenkins::Build::Build.new(jenkins_job_url)
|
55
|
+
|
56
|
+
report = client.test_report(number: build.number, project: build.job)
|
57
|
+
|
58
|
+
failures = report.failures.compact
|
59
|
+
|
60
|
+
if (kind = options[:kind])
|
61
|
+
failures.select! { |failure| failure.start_with?(kind) }
|
62
|
+
end
|
63
|
+
|
64
|
+
puts failures
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def ci_status
|
70
|
+
hub.ci_status(sha: branch)
|
71
|
+
end
|
72
|
+
|
73
|
+
def hub
|
74
|
+
hub = Hub.new
|
75
|
+
|
76
|
+
unless hub.available?
|
37
77
|
warn 'install `hub` tool to get ci status (https://github.com/github/hub)'
|
38
78
|
exit 1
|
39
79
|
end
|
40
|
-
exec('hub', 'ci-status', branch, '-v')
|
41
|
-
end
|
42
80
|
|
43
|
-
|
81
|
+
hub
|
82
|
+
end
|
44
83
|
|
45
84
|
def branch
|
46
85
|
options[:branch]
|
@@ -50,8 +89,12 @@ module Jenkins
|
|
50
89
|
@client ||= Jenkins::Build::Client.new(configuration)
|
51
90
|
end
|
52
91
|
|
92
|
+
def jenkins_job_url
|
93
|
+
URI.join(configuration.server, ['jobs', options[:job], options[:build]].join('/'))
|
94
|
+
end
|
95
|
+
|
53
96
|
def configuration
|
54
|
-
@configuration ||=
|
97
|
+
@configuration ||= self.class.configuration.current
|
55
98
|
end
|
56
99
|
end
|
57
100
|
end
|
data/lib/jenkins/build/client.rb
CHANGED
@@ -13,6 +13,10 @@ module Jenkins
|
|
13
13
|
@connection = Net::HTTP.new(@base_uri.host, @base_uri.port)
|
14
14
|
@connection.use_ssl = (@base_uri.scheme == 'https')
|
15
15
|
|
16
|
+
if ENV['DEBUG']
|
17
|
+
@connection.set_debug_output $stderr
|
18
|
+
end
|
19
|
+
|
16
20
|
@connection
|
17
21
|
end
|
18
22
|
|
@@ -25,6 +29,20 @@ module Jenkins
|
|
25
29
|
end
|
26
30
|
end
|
27
31
|
|
32
|
+
def build(number: 'lastBuild'.freeze, project: @configuration.project)
|
33
|
+
response = get("/job/#{project}/#{number}/api/json")
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_report(number: 'lastBuild'.freeze, project: @configuration.project, builder: TestReport::Builder.new)
|
37
|
+
response = get("/job/#{project}/#{number}/testReport/api/json")
|
38
|
+
|
39
|
+
case response
|
40
|
+
when Net::HTTPOK then builder.build_report(JSON.parse response.body)
|
41
|
+
else
|
42
|
+
raise InvalidResponse, response
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
28
46
|
private
|
29
47
|
|
30
48
|
class InvalidResponse < StandardError
|
@@ -42,12 +60,23 @@ module Jenkins
|
|
42
60
|
uri = URI.join(@base_uri, path)
|
43
61
|
body = JSON.generate(params)
|
44
62
|
|
45
|
-
post = Net::HTTP::Post.new(uri,
|
63
|
+
post = Net::HTTP::Post.new(uri, json_header)
|
46
64
|
post.set_form_data json: body
|
47
65
|
|
48
66
|
request(post)
|
49
67
|
end
|
50
68
|
|
69
|
+
def get(path)
|
70
|
+
uri = URI.join(@base_uri, path)
|
71
|
+
post = Net::HTTP::Get.new(uri)
|
72
|
+
|
73
|
+
request(post)
|
74
|
+
end
|
75
|
+
|
76
|
+
def json_header
|
77
|
+
{ 'Content-Type' => 'application/json' }
|
78
|
+
end
|
79
|
+
|
51
80
|
def request(request)
|
52
81
|
request.basic_auth(@configuration.user, @configuration.api_key)
|
53
82
|
|
@@ -9,6 +9,16 @@ module Jenkins
|
|
9
9
|
|
10
10
|
CONFIG_KEYS = [:server, :project, :api_key, :user]
|
11
11
|
|
12
|
+
class << self
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def current
|
16
|
+
new(Dir.pwd)
|
17
|
+
end
|
18
|
+
|
19
|
+
def_delegators :current, *CONFIG_KEYS
|
20
|
+
end
|
21
|
+
|
12
22
|
def initialize(folder)
|
13
23
|
@config = Pathname(CONFIG).expand_path(folder).freeze
|
14
24
|
@store = YAML::Store.new(@config)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Jenkins
|
2
|
+
module Build
|
3
|
+
class Hub
|
4
|
+
|
5
|
+
def available?
|
6
|
+
system('which', 'hub', out: '/dev/null')
|
7
|
+
end
|
8
|
+
|
9
|
+
def ci_status(sha: nil)
|
10
|
+
status, build_url = CiStatus.parse self.class.execute(sha)
|
11
|
+
CiStatus.new(status, build_url)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.execute(commit)
|
15
|
+
`hub ci-status -v #{commit}`
|
16
|
+
end
|
17
|
+
|
18
|
+
class CiStatus < Struct.new(:status, :build)
|
19
|
+
MATCHER = %r{^(?<status>[^:]+): (?<build_url>https?://.+)$}.freeze
|
20
|
+
|
21
|
+
|
22
|
+
def self.parse(output)
|
23
|
+
hub = output.match(MATCHER) or return
|
24
|
+
|
25
|
+
build = Jenkins::Build::Build.new(hub[:build_url])
|
26
|
+
[ hub[:status], build ]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module Jenkins
|
2
|
+
module Build
|
3
|
+
class TestReport
|
4
|
+
def initialize(suites, cases)
|
5
|
+
@suites = suites.freeze
|
6
|
+
@cases = cases.freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def failures
|
10
|
+
path = project_path
|
11
|
+
failures = @cases.select(&:failure?)
|
12
|
+
|
13
|
+
failures.map do |failure|
|
14
|
+
failure.cause(path)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def suites
|
19
|
+
@suites
|
20
|
+
end
|
21
|
+
|
22
|
+
def project_path
|
23
|
+
group = @cases.map(&:project_path).compact.group_by{ |path| path }.values.max_by(&:size) or return
|
24
|
+
group.first
|
25
|
+
end
|
26
|
+
|
27
|
+
class Builder
|
28
|
+
|
29
|
+
def build_report(report)
|
30
|
+
suites, cases = report.delete('suites'.freeze).map(&method(:build_suite)).transpose
|
31
|
+
|
32
|
+
TestReport.new(suites, cases.flatten)
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_suite(suite)
|
36
|
+
cases = suite.delete('cases'.freeze).map(&method(:build_case))
|
37
|
+
[ Suite.new(suite, cases), cases ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_case(test)
|
41
|
+
status = Status.new(test.delete('status'.freeze))
|
42
|
+
stack_trace = StackTrace.new(test.delete('errorStackTrace'))
|
43
|
+
Case.new(test, status, stack_trace)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
class Suite
|
51
|
+
def initialize(suite, cases)
|
52
|
+
@suite = suite
|
53
|
+
@cases = cases
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Case
|
58
|
+
attr_reader :status, :stack_trace
|
59
|
+
|
60
|
+
def initialize(test, status, stack_trace)
|
61
|
+
@test = test
|
62
|
+
@status = status
|
63
|
+
@stack_trace = stack_trace
|
64
|
+
end
|
65
|
+
|
66
|
+
def failure?
|
67
|
+
status.failure?
|
68
|
+
end
|
69
|
+
|
70
|
+
def cause(path)
|
71
|
+
stack_trace.cause(path)
|
72
|
+
end
|
73
|
+
|
74
|
+
def project_path
|
75
|
+
path = stack_trace.root_path
|
76
|
+
path.empty? ? nil : path
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Status
|
81
|
+
def initialize(status)
|
82
|
+
@status = status
|
83
|
+
end
|
84
|
+
|
85
|
+
def failure?
|
86
|
+
@status == 'FAILED'.freeze
|
87
|
+
end
|
88
|
+
|
89
|
+
def success?
|
90
|
+
@status == 'PASSED'.freeze
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class StackTrace
|
95
|
+
attr_reader :stack_trace, :stderr, :root_path
|
96
|
+
|
97
|
+
def initialize(stderr)
|
98
|
+
@stderr = stderr
|
99
|
+
@stack_trace = extract_stack_frames(stderr.to_s).flatten.compact.reject(&method(:system_path?))
|
100
|
+
@root_path = longest_common_substr(stack_trace) || ''
|
101
|
+
end
|
102
|
+
|
103
|
+
def cause(project_path = root_path)
|
104
|
+
stack_trace.lazy.reverse_each.
|
105
|
+
map { |path| relative_path(path, project_path) }.
|
106
|
+
find(&method(:test_stack_frame?))
|
107
|
+
end
|
108
|
+
|
109
|
+
TEST_PATHS = %w[test spec features]
|
110
|
+
|
111
|
+
def test_stack_frame?(project_frame)
|
112
|
+
TEST_PATHS.any?(&project_frame.method(:start_with?))
|
113
|
+
end
|
114
|
+
|
115
|
+
def relative_path(full_frame, project_path)
|
116
|
+
full_frame.sub(project_path, ''.freeze)
|
117
|
+
end
|
118
|
+
|
119
|
+
RUBY_STACK_FRAME = /[^\w\/](\.?\/[\w._-][\/\w._-]+:\d+)/.freeze
|
120
|
+
CUCUMBER_STACK_FRAME = /(features\/[\w._-][\/\w._-]+:\d+)/.freeze
|
121
|
+
|
122
|
+
def extract_stack_frames(text)
|
123
|
+
text.scan(RUBY_STACK_FRAME) + text.scan(CUCUMBER_STACK_FRAME)
|
124
|
+
end
|
125
|
+
|
126
|
+
SYSTEM_PATHS = %w[/usr/lib /lib]
|
127
|
+
|
128
|
+
def system_path?(path)
|
129
|
+
SYSTEM_PATHS.any?(&path.method(:start_with?))
|
130
|
+
end
|
131
|
+
|
132
|
+
# http://stackoverflow.com/questions/2158313/finding-common-string-in-array-of-strings-ruby
|
133
|
+
def longest_common_substr(strings)
|
134
|
+
return unless strings.length > 1
|
135
|
+
shortest = strings.min_by(&:length) or return
|
136
|
+
maxlen = shortest.length
|
137
|
+
maxlen.downto(0) do |len|
|
138
|
+
0.upto(maxlen - len) do |start|
|
139
|
+
substr = shortest[start,len]
|
140
|
+
return substr if strings.all?{|str| str.start_with?(substr) }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jenkins-build
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michal Cichra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-08-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -76,6 +76,7 @@ extra_rdoc_files: []
|
|
76
76
|
files:
|
77
77
|
- ".gitignore"
|
78
78
|
- ".rspec"
|
79
|
+
- ".ruby-version"
|
79
80
|
- ".travis.yml"
|
80
81
|
- Gemfile
|
81
82
|
- README.md
|
@@ -83,10 +84,13 @@ files:
|
|
83
84
|
- bin/jenkins-build
|
84
85
|
- jenkins-build.gemspec
|
85
86
|
- lib/jenkins/build.rb
|
87
|
+
- lib/jenkins/build/build.rb
|
86
88
|
- lib/jenkins/build/cli.rb
|
87
89
|
- lib/jenkins/build/client.rb
|
88
90
|
- lib/jenkins/build/configuration.rb
|
89
91
|
- lib/jenkins/build/git.rb
|
92
|
+
- lib/jenkins/build/hub.rb
|
93
|
+
- lib/jenkins/build/test_report.rb
|
90
94
|
- lib/jenkins/build/version.rb
|
91
95
|
homepage: https://github.com/3scale/jenkins-build
|
92
96
|
licenses: []
|
@@ -102,13 +106,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
102
106
|
version: '0'
|
103
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
108
|
requirements:
|
105
|
-
- - "
|
109
|
+
- - ">"
|
106
110
|
- !ruby/object:Gem::Version
|
107
|
-
version:
|
111
|
+
version: 1.3.1
|
108
112
|
requirements: []
|
109
113
|
rubyforge_project:
|
110
|
-
rubygems_version: 2.4.5
|
114
|
+
rubygems_version: 2.4.5.1
|
111
115
|
signing_key:
|
112
116
|
specification_version: 4
|
113
117
|
summary: CLI utility to trigger jobs on jenkins.
|
114
118
|
test_files: []
|
119
|
+
has_rdoc:
|