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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 730faf862114af0c23a1d01d874413eafc182d41
4
- data.tar.gz: c1e1a6ad206f10b633ef145586a41f28293f0fb3
3
+ metadata.gz: 5b6b94d13fe633861036016e4433bc9cb0262d88
4
+ data.tar.gz: 1c68f33db52e5b75bb0657a977671ff8415f82d6
5
5
  SHA512:
6
- metadata.gz: 3168364d029f0ae5248e29cbf73001ca58cafc588f6bcdf00a31ca1ab31bed7e402eb48db6799c4e6925f495f76827a6e6c875712189d7648df3b62671914c89
7
- data.tar.gz: 38d0d8f0439c304c9eac1fd11dccf80d8ed2dcb83325f2f492fcb8dca5c4b99ff02e00b2bc7b771e5d4b53242eee7d928a0e5e47093ba61de074ef497d461a6f
6
+ metadata.gz: 323c82d6f72314c238b4e77a0b616a05dc96c3225138c2f6a9975bfa094a9d33e8070688cf85ef173548bc4949266a36d592c5b216fd72bdc43e4a6434521c7e
7
+ data.tar.gz: 92ce0ba7f74ce0cb6cf6ddb7a6e0080198278421154f1a58071216fb82be8b5b05059751ca98c4d029e995b6528160913bc9721c7b9df3d77b7f75e3cb54a1f1
data/.gitignore CHANGED
@@ -6,5 +6,7 @@
6
6
  /doc/
7
7
  /pkg/
8
8
  /spec/reports/
9
+ /spec/examples.txt
9
10
  /tmp/
10
11
  .jenkins-build
12
+ .*.swp
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
- --format documentation
2
1
  --color
2
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.2
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in jenkins-build.gemspec
4
4
  gemspec
5
+
6
+
7
+ gem 'pry'
8
+ gem 'pry-byebug', platform: :mri
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
- You have to configure `jenkins-build` to use your jenkins server and project.
16
- Do that by running:
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 `bin/console` for an interactive prompt that will allow you to experiment.
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 release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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
@@ -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
- unless system('which', 'hub')
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
- private
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 ||= Jenkins::Build::Configuration.new(Dir.pwd)
97
+ @configuration ||= self.class.configuration.current
55
98
  end
56
99
  end
57
100
  end
@@ -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, 'Content-Type' => 'application/json')
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
@@ -1,5 +1,5 @@
1
1
  module Jenkins
2
2
  module Build
3
- VERSION = "0.1.3"
3
+ VERSION = "0.2.0.pre1"
4
4
  end
5
5
  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.1.3
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-02-13 00:00:00.000000000 Z
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: '0'
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: