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 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: