ci_runner 0.2.0 → 0.3.0

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
  SHA256:
3
- metadata.gz: fb83fde059747332cee33b4a0d313d2eaefb735ec3fe0e291141e8a18a535fa3
4
- data.tar.gz: 6dd4960bce217bbebba6a22b6e1863d0baaf7190c5835c1fc63de9d0cc86e94e
3
+ metadata.gz: f7834106a8db1f0770e5184dcf31fbfc892a92890b52f4709ab900ccd560f1b2
4
+ data.tar.gz: 69c9c0533ff5a52a1f97cb1f4374e012f56004a06a584b2b60ab80e4c9897517
5
5
  SHA512:
6
- metadata.gz: f1e747da3d2101445f77645cc9f4d37f1f36b53702a85b735aecac605576b0d060037b56a54fcd3269c65a4313ebc3a3b00cab05d4acfbfd0785f3138911d51e
7
- data.tar.gz: 65a90464a386601f2bd6caaaa61ec6ea6188bd65688ddf9e32db0ee7006aff914bc3d3104271d5a4607f0252a1936514ee301b16bb3a708e1d32b7a12bc75c9d
6
+ metadata.gz: 67d81327e046862ccb85ead5688aac13d9818d8ffc1487ef92001fba07ff6c6795dee7a537566e410c3c864ed0ee8e3712668310412cae9bbab3eddaeff52114
7
+ data.tar.gz: ac943446f8cf3ebd2a65c5d35e021aebcfee0cca6c5e693bafe7f4ea41b673bc669a3eabe1914eed70a98c646dc12d51dba12d14a066e6b51538a9bfc37318d8
@@ -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
- @status == "failure"
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CIRunner
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -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, "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"
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 :Github, "ci_runner/check/github"
17
- autoload :CircleCI, "ci_runner/check/circle_ci"
18
- autoload :Unsupported, "ci_runner/check/unsupported"
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, "ci_runner/client/error"
23
- autoload :Github, "ci_runner/client/github"
24
- autoload :CircleCI, "ci_runner/client/circle_ci"
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.2.0
4
+ version: 0.3.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: 2022-08-22 00:00:00.000000000 Z
11
+ date: 2022-10-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: