ci_runner 0.2.0 → 0.4.0
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/lib/ci_runner/check/base.rb +3 -1
- data/lib/ci_runner/check/buildkite.rb +88 -0
- data/lib/ci_runner/check/circle_ci.rb +2 -39
- data/lib/ci_runner/check/concurrent_download.rb +57 -0
- data/lib/ci_runner/cli.rb +49 -0
- data/lib/ci_runner/client/authenticated_buildkite.rb +67 -0
- data/lib/ci_runner/client/base.rb +8 -1
- data/lib/ci_runner/client/buildkite.rb +60 -0
- data/lib/ci_runner/client/github.rb +11 -0
- data/lib/ci_runner/configuration/user.rb +30 -0
- data/lib/ci_runner/runners/minitest_runner.rb +2 -1
- data/lib/ci_runner/test_failure.rb +3 -0
- data/lib/ci_runner/test_run_finder.rb +2 -0
- data/lib/ci_runner/version.rb +1 -1
- data/lib/ci_runner/version_verifier.rb +53 -0
- data/lib/ci_runner.rb +16 -11
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6573071a75fdc013ce2cb451567a5610d4928d8e10e336bba6296b1b046f1b82
|
4
|
+
data.tar.gz: b171257681c40b76b71c5113c243bdc9ee3992e2522b3128e980527eaab151bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40a798ad29846ca92722b56ef4f1bae2a1f76358de371ebf95fec1053b135882cbbb494430d77cc720ed3d60795413b994fc7288261e1c4d2bc981b0a003b45f
|
7
|
+
data.tar.gz: c1cde1a84e5c4cdc87b4763a85388373b187a43518f72330a3a1c7855f51ca33f358b1831977d708105731ebd61c75c25184f7cfb4ec9abcb7c0b32458e247d1
|
data/lib/ci_runner/check/base.rb
CHANGED
@@ -53,8 +53,10 @@ module CIRunner
|
|
53
53
|
end
|
54
54
|
|
55
55
|
# @return [Boolean]
|
56
|
+
#
|
57
|
+
# @see https://docs.github.com/en/rest/commits/statuses#get-the-combined-status-for-a-specific-reference
|
56
58
|
def failed?
|
57
|
-
|
59
|
+
["error", "failure"].include?(status)
|
58
60
|
end
|
59
61
|
end
|
60
62
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
module CIRunner
|
7
|
+
module Check
|
8
|
+
# Check class used when a project is configured to run its CI using Buildkite.
|
9
|
+
class Buildkite < Base
|
10
|
+
include ConcurrentDownload
|
11
|
+
|
12
|
+
attr_reader :url # :private:
|
13
|
+
|
14
|
+
# @param args (See Base#initialize)
|
15
|
+
# @param url [String] The html URL pointing to the Buildkite build.
|
16
|
+
def initialize(*args, url)
|
17
|
+
super(*args)
|
18
|
+
|
19
|
+
@url = url
|
20
|
+
end
|
21
|
+
|
22
|
+
# Used to tell the user which CI provider we are downloading the log output from.
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
def provider
|
26
|
+
"Buildkite"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Download the CI logs for this Buildkite build.
|
30
|
+
#
|
31
|
+
# The Buildkite API scopes tokens per organizations (token generated for org A can't access
|
32
|
+
# resource on org B, even for public resources). This means that for opensource projects using
|
33
|
+
# Buildkite, users that are not members of the buildkite org normally can't use CI Runner.
|
34
|
+
#
|
35
|
+
# To bypass this problem, for builds that are public, CI Runner uses a different API.
|
36
|
+
# For private build, CI runner will check if the user had stored a Buildkite token in its config.
|
37
|
+
#
|
38
|
+
# @return [Tempfile]
|
39
|
+
def download_log
|
40
|
+
uri = URI(url)
|
41
|
+
_, org, pipeline, _, build = uri.path.split("/")
|
42
|
+
@client = Client::Buildkite.new
|
43
|
+
|
44
|
+
unless @client.public_build?(org, pipeline, build)
|
45
|
+
token = retrieve_token_from_config(org, url)
|
46
|
+
@client = Client::AuthenticatedBuildkite.new(token)
|
47
|
+
end
|
48
|
+
|
49
|
+
@client.job_logs(org, pipeline, build).each do |log_url|
|
50
|
+
@queue << log_url
|
51
|
+
end
|
52
|
+
|
53
|
+
process_queue
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# @param url [String]
|
59
|
+
#
|
60
|
+
# @return [void]
|
61
|
+
def process(url)
|
62
|
+
@client.reset!
|
63
|
+
response = @client.download_log(url)
|
64
|
+
|
65
|
+
@tempfile.write(response.read)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Retrieve a Buildkite token from the user confg.
|
69
|
+
#
|
70
|
+
# @param organization [String] The organization that owns this buildkite build.
|
71
|
+
# @param url [String] The FQDN pointing to the buildkite build.
|
72
|
+
#
|
73
|
+
# @return [String] The token
|
74
|
+
#
|
75
|
+
# @raise [Error] If no token for that organization exists in the config.
|
76
|
+
def retrieve_token_from_config(organization, url)
|
77
|
+
token = Configuration::User.instance.buildkite_token(organization.downcase)
|
78
|
+
|
79
|
+
token || raise(Error, <<~EOM)
|
80
|
+
Can't get the log output from the Buildkite build #{url} because it requires authentication.
|
81
|
+
|
82
|
+
Please store a Buildkite token scoped to the organization #{organization} and retry.
|
83
|
+
See {{command:ci_runner help buildkite_token}}
|
84
|
+
EOM
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -4,7 +4,6 @@ require_relative "base"
|
|
4
4
|
require "uri"
|
5
5
|
require "open-uri"
|
6
6
|
require "json"
|
7
|
-
require "tempfile"
|
8
7
|
|
9
8
|
module CIRunner
|
10
9
|
module Check
|
@@ -54,6 +53,8 @@ module CIRunner
|
|
54
53
|
|
55
54
|
# Check class used when a project is configured to run its CI using CircleCI.
|
56
55
|
class CircleCI < Base
|
56
|
+
include ConcurrentDownload
|
57
|
+
|
57
58
|
attr_reader :url # :private:
|
58
59
|
|
59
60
|
# @param args (See Base#initialize)
|
@@ -62,8 +63,6 @@ module CIRunner
|
|
62
63
|
super(*args)
|
63
64
|
|
64
65
|
@url = url
|
65
|
-
@queue = Queue.new
|
66
|
-
@tempfile = Tempfile.new
|
67
66
|
end
|
68
67
|
|
69
68
|
# Used to tell the user which CI provider we are downloading the log output from.
|
@@ -102,37 +101,10 @@ module CIRunner
|
|
102
101
|
end
|
103
102
|
|
104
103
|
process_queue
|
105
|
-
|
106
|
-
@tempfile.tap(&:flush)
|
107
|
-
end
|
108
|
-
|
109
|
-
# @return [Boolean]
|
110
|
-
#
|
111
|
-
# @see https://docs.github.com/en/rest/commits/statuses#get-the-combined-status-for-a-specific-reference
|
112
|
-
def failed?
|
113
|
-
["error", "failure"].include?(status)
|
114
104
|
end
|
115
105
|
|
116
106
|
private
|
117
107
|
|
118
|
-
# Implement a queuing system in order to download log files in parallel.
|
119
|
-
#
|
120
|
-
# @return [void]
|
121
|
-
def process_queue
|
122
|
-
max_threads = 6
|
123
|
-
threads = []
|
124
|
-
|
125
|
-
max_threads.times do
|
126
|
-
threads << Thread.new do
|
127
|
-
while (element = dequeue)
|
128
|
-
process(element)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
threads.each(&:join)
|
134
|
-
end
|
135
|
-
|
136
108
|
# @param step [Step]
|
137
109
|
#
|
138
110
|
# @return [void]
|
@@ -144,15 +116,6 @@ module CIRunner
|
|
144
116
|
@tempfile.write(log_output)
|
145
117
|
end
|
146
118
|
|
147
|
-
# Dequeue a CircleCI Step from the queue.
|
148
|
-
#
|
149
|
-
# @return [Step, nil]
|
150
|
-
def dequeue
|
151
|
-
@queue.pop(true)
|
152
|
-
rescue ThreadError
|
153
|
-
nil
|
154
|
-
end
|
155
|
-
|
156
119
|
# The URL on the commit status will look something like: https://circleci.com/gh/owner/repo/1234?query_string.
|
157
120
|
# We want the `1234` which is the builder number.
|
158
121
|
#
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tempfile"
|
4
|
+
|
5
|
+
module CIRunner
|
6
|
+
module Check
|
7
|
+
# Module used to dowload multiple logfiles in parallel.
|
8
|
+
#
|
9
|
+
# Some CI providers doesn't have an API to download a single log file for the whole
|
10
|
+
# build, and instead one log file is produced per step. CI Runner needs to download
|
11
|
+
# the logfile of all steps in the build in order to rerun all test that failed.
|
12
|
+
module ConcurrentDownload
|
13
|
+
def initialize(...)
|
14
|
+
@queue = Queue.new
|
15
|
+
@tempfile = Tempfile.new
|
16
|
+
|
17
|
+
super(...)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Implement a queuing system in order to download log files in parallel.
|
23
|
+
#
|
24
|
+
# @return [void]
|
25
|
+
def process_queue
|
26
|
+
max_threads = 6
|
27
|
+
threads = []
|
28
|
+
|
29
|
+
max_threads.times do
|
30
|
+
threads << Thread.new do
|
31
|
+
while (element = dequeue)
|
32
|
+
process(element)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
threads.each(&:join)
|
38
|
+
|
39
|
+
@tempfile.tap(&:flush)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Process item in the queue.
|
43
|
+
def process
|
44
|
+
raise(NotImplementedError)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Dequeue a CircleCI Step from the queue.
|
48
|
+
#
|
49
|
+
# @return [Step, nil]
|
50
|
+
def dequeue
|
51
|
+
@queue.pop(true)
|
52
|
+
rescue ThreadError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/ci_runner/cli.rb
CHANGED
@@ -35,6 +35,7 @@ module CIRunner
|
|
35
35
|
def rerun
|
36
36
|
::CLI::UI::StdoutRouter.enable
|
37
37
|
|
38
|
+
check_for_new_version
|
38
39
|
runner = nil
|
39
40
|
|
40
41
|
::CLI::UI.frame("Preparing CI Runner") do
|
@@ -117,8 +118,56 @@ module CIRunner
|
|
117
118
|
end
|
118
119
|
end
|
119
120
|
|
121
|
+
desc "buildkite_token TOKEN ORGANIZATION", "Save a Buildkite token in your config."
|
122
|
+
long_desc <<~EOM
|
123
|
+
Save a personal access Buildkite token in the ~/.ci_runner/config.yml file.
|
124
|
+
Storing a Buildkite token is required to retrieve log from private Buildkite builds.
|
125
|
+
|
126
|
+
The ORGANIZATION, should be the name of the organization the token has access to.
|
127
|
+
|
128
|
+
You can get a token from Buildkite by following this link: https://buildkite.com/user/api-access-tokens/new?description=CI%20Runner&scopes[]=read_builds&scopes[]=read_build_logs
|
129
|
+
EOM
|
130
|
+
def buildkite_token(token, organization)
|
131
|
+
::CLI::UI::StdoutRouter.enable
|
132
|
+
|
133
|
+
required_scopes = ["read_builds", "read_build_logs"]
|
134
|
+
token_scopes = Client::AuthenticatedBuildkite.new(token).access_token["scopes"]
|
135
|
+
missing_scopes = required_scopes - token_scopes
|
136
|
+
|
137
|
+
if missing_scopes.empty?
|
138
|
+
Configuration::User.instance.save_buildkite_token(token, organization)
|
139
|
+
|
140
|
+
::CLI::UI.puts(<<~EOM)
|
141
|
+
{{success:Your token is valid!}}
|
142
|
+
|
143
|
+
{{info:The token has been saved in this file: #{Configuration::User.instance.config_file}}}
|
144
|
+
EOM
|
145
|
+
else
|
146
|
+
::CLI::UI.puts("{{red:\nYour token is missing required scope(s): #{missing_scopes.join(",")}")
|
147
|
+
end
|
148
|
+
rescue Client::Error => e
|
149
|
+
::CLI::UI.puts("{{red:\nYour token doesn't seem to be valid. The response from Buildkite was: #{e.message}}}")
|
150
|
+
|
151
|
+
exit(false)
|
152
|
+
end
|
153
|
+
|
120
154
|
private
|
121
155
|
|
156
|
+
# Inform the user of a possible new CI Runner version.
|
157
|
+
#
|
158
|
+
# @return [void]
|
159
|
+
def check_for_new_version
|
160
|
+
version_verifier = VersionVerifier.new
|
161
|
+
return unless version_verifier.new_ci_runner_version?
|
162
|
+
|
163
|
+
::CLI::UI.puts(<<~EOM)
|
164
|
+
{{info:A newer version of CI Runner is available (#{version_verifier.upstream_version}).}}
|
165
|
+
{{info:You can update CI Runner by running}} {{command:gem update ci_runner}}
|
166
|
+
EOM
|
167
|
+
rescue StandardError
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
|
122
171
|
# Retrieve all the GitHub CI checks for a given commit. Will be used to interactively prompt
|
123
172
|
# the user which one to rerun.
|
124
173
|
#
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "stringio"
|
5
|
+
|
6
|
+
module CIRunner
|
7
|
+
module Client
|
8
|
+
# Client used to retrieve private Resources on buildkite.
|
9
|
+
#
|
10
|
+
# For public resources, the API can be used but only a limited number of users will be
|
11
|
+
# be able to access it as it requires a token scoped for the organization (most users
|
12
|
+
# working on opensource project aren't member of the organization they contribute to).
|
13
|
+
#
|
14
|
+
# @see https://forum.buildkite.community/t/api-access-to-public-builds/1425/2
|
15
|
+
# @see Client::Buildkite
|
16
|
+
#
|
17
|
+
class AuthenticatedBuildkite < Base
|
18
|
+
API_ENDPOINT = "api.buildkite.com"
|
19
|
+
|
20
|
+
# Retrieve URLs to download job logs for all steps.
|
21
|
+
#
|
22
|
+
# @param org [String] The organizatio name.
|
23
|
+
# @param pipeline [String] The pipeline name.
|
24
|
+
# @param number [Integer] The build number.
|
25
|
+
#
|
26
|
+
# @return [Array<String>] An array of URLs
|
27
|
+
#
|
28
|
+
# @see https://buildkite.com/docs/apis/rest-api/builds#get-a-build
|
29
|
+
def job_logs(org, pipeline, number)
|
30
|
+
build = get("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{number}")
|
31
|
+
|
32
|
+
build["jobs"].map do |job|
|
33
|
+
job["raw_log_url"]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param url [String] A URL pointing to a log output resource.
|
38
|
+
#
|
39
|
+
# @return [StringIO]
|
40
|
+
#
|
41
|
+
# @see https://buildkite.com/docs/apis/rest-api/jobs#get-a-jobs-log-output
|
42
|
+
def download_log(url)
|
43
|
+
StringIO.new(get(url))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get information about an access token. Used to check if the token has the correct scopes.
|
47
|
+
#
|
48
|
+
# @see https://buildkite.com/docs/apis/rest-api/access-token
|
49
|
+
#
|
50
|
+
# @return [Hash] See Buildkite doc
|
51
|
+
def access_token
|
52
|
+
get("/v2/access-token")
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Add authentication before making the request.
|
58
|
+
#
|
59
|
+
# @param request [Net::HTTPRequest] A subclass of Net::HTTPRequest.
|
60
|
+
#
|
61
|
+
# @return [void]
|
62
|
+
def authentication(request)
|
63
|
+
request["Authorization"] = "Bearer #{@access_token}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -24,6 +24,14 @@ module CIRunner
|
|
24
24
|
@client = client
|
25
25
|
end
|
26
26
|
|
27
|
+
# Set a new Client object.
|
28
|
+
# NET::HTTP is not threadsafe so each time we need to make requests concurrently we need to use a new client.
|
29
|
+
#
|
30
|
+
# @return [void]
|
31
|
+
def reset!
|
32
|
+
@client = self.class.default_client
|
33
|
+
end
|
34
|
+
|
27
35
|
private
|
28
36
|
|
29
37
|
# Add authentication before making the request.
|
@@ -32,7 +40,6 @@ module CIRunner
|
|
32
40
|
#
|
33
41
|
# @return [void]
|
34
42
|
def authentication(request)
|
35
|
-
raise(NotImplementedError, "Subclass responsability")
|
36
43
|
end
|
37
44
|
|
38
45
|
# Perform an authenticated GET request.
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "open-uri"
|
5
|
+
|
6
|
+
module CIRunner
|
7
|
+
module Client
|
8
|
+
# Client used for public Buildkite resources.
|
9
|
+
# Allow any users to download log output for builds that are in organizations they
|
10
|
+
# are not a member of.
|
11
|
+
#
|
12
|
+
# This client doesn't use the official buildkite API. The data returned are not exactly the same.
|
13
|
+
class Buildkite < Base
|
14
|
+
API_ENDPOINT = "buildkite.com"
|
15
|
+
|
16
|
+
# Check if the build is public and can be accessed without authentication.
|
17
|
+
#
|
18
|
+
# @param org [String] The organizatio name.
|
19
|
+
# @param pipeline [String] The pipeline name.
|
20
|
+
# @param number [Integer] The build number.
|
21
|
+
#
|
22
|
+
# @return [Boolean]
|
23
|
+
def public_build?(org, pipeline, build_number)
|
24
|
+
job_logs(org, pipeline, build_number)
|
25
|
+
|
26
|
+
true
|
27
|
+
rescue Error => e
|
28
|
+
return false if e.error_code == 403
|
29
|
+
|
30
|
+
raise(e)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Retrieve URL paths to download job logs for all steps.
|
34
|
+
#
|
35
|
+
# @param org [String] The organizatio name.
|
36
|
+
# @param pipeline [String] The pipeline name.
|
37
|
+
# @param number [Integer] The build number.
|
38
|
+
#
|
39
|
+
# @return [Array<String>] An array of URL paths
|
40
|
+
def job_logs(org, pipeline, build_number)
|
41
|
+
@build ||= get("/#{org}/#{pipeline}/builds/#{build_number}")
|
42
|
+
|
43
|
+
@build["jobs"].map do |job|
|
44
|
+
job["base_path"] + "/raw_log"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Download raw log output for a job.
|
49
|
+
#
|
50
|
+
# @param path [String] A URL path
|
51
|
+
#
|
52
|
+
# @return [Tempfile, IO] Depending on the size of the response. Quirk of URI.open.
|
53
|
+
def download_log(path)
|
54
|
+
redirection_url = get(path)
|
55
|
+
|
56
|
+
URI.open(redirection_url)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -22,6 +22,17 @@ module CIRunner
|
|
22
22
|
get("/user")
|
23
23
|
end
|
24
24
|
|
25
|
+
# Get the latest release of a repository.
|
26
|
+
#
|
27
|
+
# @param repository [String] The full repository name, including the owner (rails/rails)
|
28
|
+
#
|
29
|
+
# @return [Hash] See GitHub documentation.
|
30
|
+
#
|
31
|
+
# https://docs.github.com/en/rest/releases/releases#get-the-latest-release
|
32
|
+
def latest_release(repository)
|
33
|
+
get("/repos/#{repository}/releases/latest")
|
34
|
+
end
|
35
|
+
|
25
36
|
# Makes an API request to get the CI checks for the +commit+.
|
26
37
|
#
|
27
38
|
# @param repository [String] The full repository name, including the owner (rails/rails)
|
@@ -68,6 +68,36 @@ module CIRunner
|
|
68
68
|
save!(@yaml_config)
|
69
69
|
end
|
70
70
|
|
71
|
+
# Retrieve the stored Buildkite access token of the user that has access to the +organization+.
|
72
|
+
#
|
73
|
+
# @return [String, nil] Depending if the user ran the `ci_runner buildkite TOKEN ORGANIZATION` command.
|
74
|
+
def buildkite_token(organization)
|
75
|
+
@yaml_config.dig("buildkite", "tokens", organization.downcase)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Write the Buildkite token to the user configuration file.
|
79
|
+
#
|
80
|
+
# @param token [String] A valid Buildkite access token.
|
81
|
+
# @param organization [String] The name of the organization the token has access to.
|
82
|
+
#
|
83
|
+
# @return [void]
|
84
|
+
def save_buildkite_token(token, organization)
|
85
|
+
existing_tokens = @yaml_config.dig("buildkite", "tokens") || {}
|
86
|
+
existing_tokens[organization.downcase] = token
|
87
|
+
|
88
|
+
@yaml_config["buildkite"] = { "tokens" => existing_tokens }
|
89
|
+
|
90
|
+
save!(@yaml_config)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Pathname] The path of the CI Runner directory configuration.
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# puts config_directory # ~/.ci_runner
|
97
|
+
def config_directory
|
98
|
+
config_file.dirname
|
99
|
+
end
|
100
|
+
|
71
101
|
# @return [Pathname] The path of the configuration file.
|
72
102
|
#
|
73
103
|
# @example
|
@@ -74,6 +74,7 @@ module CIRunner
|
|
74
74
|
t.libs << "#{rake_load_path}"
|
75
75
|
t.libs << "#{minitest_plugin_path}"
|
76
76
|
t.test_files = #{failures.map(&:path)}
|
77
|
+
t.ruby_opts = ["-W0"] if ENV["NO_WARNING"]
|
77
78
|
end
|
78
79
|
|
79
80
|
Rake::Task[:__ci_runner_test].invoke
|
@@ -89,7 +90,7 @@ module CIRunner
|
|
89
90
|
env["RUBY"] = ruby_path.to_s if ruby_path&.exist?
|
90
91
|
env["BUNDLE_GEMFILE"] = gemfile_path.to_s if gemfile_path&.exist?
|
91
92
|
|
92
|
-
execute_within_frame(env, "bundle exec ruby -r'rake/testtask' #{rakefile_path}")
|
93
|
+
execute_within_frame(env, "bundle exec ruby -I'#{rake_load_path}' -r'rake/testtask' #{rakefile_path}")
|
93
94
|
|
94
95
|
DRb.stop_service
|
95
96
|
end
|
@@ -56,6 +56,9 @@ module CIRunner
|
|
56
56
|
|
57
57
|
regex = %r{.*/?(test/.*?)\Z}
|
58
58
|
unless path.to_s.match?(regex)
|
59
|
+
# TODO(on: '2022-09-17', to: "edouard-chin") Revisit this as it's too brittle.
|
60
|
+
# If a test file doesn't live the in the `test/` root folder, this will raise an error.
|
61
|
+
# I should instead warn the user and move on.
|
59
62
|
raise "Can't create a relative path."
|
60
63
|
end
|
61
64
|
|
@@ -129,6 +129,8 @@ module CIRunner
|
|
129
129
|
case uri.host
|
130
130
|
when "circleci.com"
|
131
131
|
Check::CircleCI.new(repository, commit, *commit_status.values_at("context", "state", "target_url"))
|
132
|
+
when "buildkite.com"
|
133
|
+
Check::Buildkite.new(repository, commit, *commit_status.values_at("context", "state", "target_url"))
|
132
134
|
else
|
133
135
|
Check::Unsupported.new(repository, commit, *commit_status.values_at("context", "state"))
|
134
136
|
end
|
data/lib/ci_runner/version.rb
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module CIRunner
|
6
|
+
# Class used to check if a newer version of CI Runner has been released.
|
7
|
+
# This is used to inform the user to update its gem.
|
8
|
+
#
|
9
|
+
# The check only runs every week.
|
10
|
+
class VersionVerifier
|
11
|
+
SEVEN_DAYS = 86_400 * 7
|
12
|
+
|
13
|
+
# Check if the user is running the latest version of CI Runner.
|
14
|
+
#
|
15
|
+
# @return [Boolean]
|
16
|
+
def new_ci_runner_version?
|
17
|
+
return false unless check?
|
18
|
+
|
19
|
+
fetch_upstream_version
|
20
|
+
FileUtils.touch(last_checked)
|
21
|
+
|
22
|
+
upstream_version > Gem::Version.new(VERSION)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Makes a request to GitHub to get the latest release on the Edouard-chin/ci_runner repository
|
26
|
+
#
|
27
|
+
# @return [Gem::Version] An instance of Gem::Version
|
28
|
+
def upstream_version
|
29
|
+
@upstream_version ||= begin
|
30
|
+
release = Client::Github.new(Configuration::User.instance.github_token).latest_release("Edouard-chin/ci_runner")
|
31
|
+
|
32
|
+
Gem::Version.new(release["tag_name"].sub(/\Av/, ""))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
alias_method :fetch_upstream_version, :upstream_version
|
36
|
+
|
37
|
+
# Path of a file used to store when we last checked for a release.
|
38
|
+
#
|
39
|
+
# @return [Pathname]
|
40
|
+
def last_checked
|
41
|
+
Configuration::User.instance.config_directory.join("last-checked")
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# @return [Boolean] Whether we checked for a release in the 7 days.
|
47
|
+
def check?
|
48
|
+
Time.now > (File.stat(last_checked).mtime + SEVEN_DAYS)
|
49
|
+
rescue Errno::ENOENT
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/ci_runner.rb
CHANGED
@@ -6,22 +6,27 @@ require_relative "ci_runner/version"
|
|
6
6
|
module CIRunner
|
7
7
|
Error = Class.new(StandardError)
|
8
8
|
|
9
|
-
autoload :CLI,
|
10
|
-
autoload :GitHelper,
|
11
|
-
autoload :TestRunFinder,
|
12
|
-
autoload :LogDownloader,
|
13
|
-
autoload :TestFailure,
|
9
|
+
autoload :CLI, "ci_runner/cli"
|
10
|
+
autoload :GitHelper, "ci_runner/git_helper"
|
11
|
+
autoload :TestRunFinder, "ci_runner/test_run_finder"
|
12
|
+
autoload :LogDownloader, "ci_runner/log_downloader"
|
13
|
+
autoload :TestFailure, "ci_runner/test_failure"
|
14
|
+
autoload :VersionVerifier, "ci_runner/version_verifier"
|
14
15
|
|
15
16
|
module Check
|
16
|
-
autoload :
|
17
|
-
autoload :
|
18
|
-
autoload :
|
17
|
+
autoload :Buildkite, "ci_runner/check/buildkite"
|
18
|
+
autoload :Github, "ci_runner/check/github"
|
19
|
+
autoload :CircleCI, "ci_runner/check/circle_ci"
|
20
|
+
autoload :Unsupported, "ci_runner/check/unsupported"
|
21
|
+
autoload :ConcurrentDownload, "ci_runner/check/concurrent_download"
|
19
22
|
end
|
20
23
|
|
21
24
|
module Client
|
22
|
-
autoload :Error,
|
23
|
-
autoload :Github,
|
24
|
-
autoload :CircleCI,
|
25
|
+
autoload :Error, "ci_runner/client/error"
|
26
|
+
autoload :Github, "ci_runner/client/github"
|
27
|
+
autoload :CircleCI, "ci_runner/client/circle_ci"
|
28
|
+
autoload :Buildkite, "ci_runner/client/buildkite"
|
29
|
+
autoload :AuthenticatedBuildkite, "ci_runner/client/authenticated_buildkite"
|
25
30
|
end
|
26
31
|
|
27
32
|
module Configuration
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ci_runner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Edouard Chin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cli-ui
|
@@ -113,11 +113,15 @@ files:
|
|
113
113
|
- exe/ci_runner
|
114
114
|
- lib/ci_runner.rb
|
115
115
|
- lib/ci_runner/check/base.rb
|
116
|
+
- lib/ci_runner/check/buildkite.rb
|
116
117
|
- lib/ci_runner/check/circle_ci.rb
|
118
|
+
- lib/ci_runner/check/concurrent_download.rb
|
117
119
|
- lib/ci_runner/check/github.rb
|
118
120
|
- lib/ci_runner/check/unsupported.rb
|
119
121
|
- lib/ci_runner/cli.rb
|
122
|
+
- lib/ci_runner/client/authenticated_buildkite.rb
|
120
123
|
- lib/ci_runner/client/base.rb
|
124
|
+
- lib/ci_runner/client/buildkite.rb
|
121
125
|
- lib/ci_runner/client/circle_ci.rb
|
122
126
|
- lib/ci_runner/client/error.rb
|
123
127
|
- lib/ci_runner/client/github.rb
|
@@ -131,6 +135,7 @@ files:
|
|
131
135
|
- lib/ci_runner/test_failure.rb
|
132
136
|
- lib/ci_runner/test_run_finder.rb
|
133
137
|
- lib/ci_runner/version.rb
|
138
|
+
- lib/ci_runner/version_verifier.rb
|
134
139
|
- lib/minitest/ci_runner_plugin.rb
|
135
140
|
homepage: https://github.com/Edouard-chin/ci_runner
|
136
141
|
licenses:
|
@@ -156,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
156
161
|
- !ruby/object:Gem::Version
|
157
162
|
version: '0'
|
158
163
|
requirements: []
|
159
|
-
rubygems_version: 3.3
|
164
|
+
rubygems_version: 3.5.3
|
160
165
|
signing_key:
|
161
166
|
specification_version: 4
|
162
167
|
summary: Re-run failing tests from CI on your local machine without copy/pasting.
|