jenkins-build 0.1.3 → 0.2.0.pre1
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 +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:
|