sleet 0.3.10 → 0.5.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/.circleci/config.yml +7 -17
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -27
- data/.rubocop_todo.yml +5 -1
- data/CHANGELOG.md +108 -10
- data/README.md +1 -0
- data/docs/CNAME +1 -0
- data/docs/README.md +160 -0
- data/docs/_config.yml +1 -0
- data/lib/sleet.rb +1 -0
- data/lib/sleet/artifact_downloader.rb +4 -3
- data/lib/sleet/branch.rb +8 -6
- data/lib/sleet/build.rb +4 -3
- data/lib/sleet/build_selector.rb +2 -2
- data/lib/sleet/circle_ci.rb +1 -15
- data/lib/sleet/cli.rb +17 -1
- data/lib/sleet/config.rb +30 -3
- data/lib/sleet/fetch_command.rb +2 -4
- data/lib/sleet/job_fetcher.rb +7 -9
- data/lib/sleet/local_repo.rb +75 -0
- data/lib/sleet/repo.rb +23 -60
- data/lib/sleet/version.rb +1 -1
- data/sleet.gemspec +9 -12
- metadata +40 -53
- data/README.md +0 -135
data/docs/_config.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme: jekyll-theme-slate
|
data/lib/sleet.rb
CHANGED
@@ -2,20 +2,21 @@
|
|
2
2
|
|
3
3
|
module Sleet
|
4
4
|
class ArtifactDownloader
|
5
|
-
def initialize(artifacts:, file_name:)
|
5
|
+
def initialize(circle_ci_token:, artifacts:, file_name:)
|
6
|
+
@circle_ci_token = circle_ci_token
|
6
7
|
@artifacts = artifacts
|
7
8
|
@file_name = file_name
|
8
9
|
end
|
9
10
|
|
10
11
|
def files
|
11
12
|
@files ||= urls.map do |url|
|
12
|
-
Sleet::CircleCi.get(url)
|
13
|
+
Sleet::CircleCi.get(url, circle_ci_token)
|
13
14
|
end.map(&:body)
|
14
15
|
end
|
15
16
|
|
16
17
|
private
|
17
18
|
|
18
|
-
attr_reader :artifacts, :file_name
|
19
|
+
attr_reader :artifacts, :file_name, :circle_ci_token
|
19
20
|
|
20
21
|
def urls
|
21
22
|
rspec_artifacts.map { |x| x['url'] }
|
data/lib/sleet/branch.rb
CHANGED
@@ -2,26 +2,28 @@
|
|
2
2
|
|
3
3
|
module Sleet
|
4
4
|
class Branch
|
5
|
-
def initialize(github_user:, github_repo:, branch:)
|
5
|
+
def initialize(circle_ci_token:, github_user:, github_repo:, branch:)
|
6
|
+
@circle_ci_token = circle_ci_token
|
6
7
|
@github_user = github_user
|
7
8
|
@github_repo = github_repo
|
8
|
-
@branch = branch
|
9
|
+
@branch = CGI.escape(branch)
|
9
10
|
end
|
10
11
|
|
11
12
|
def builds
|
12
|
-
@builds ||= JSON.parse(Sleet::CircleCi.get(url).body)
|
13
|
+
@builds ||= JSON.parse(Sleet::CircleCi.get(url, circle_ci_token).body)
|
13
14
|
end
|
14
15
|
|
15
|
-
def
|
16
|
+
def builds_with_artifacts
|
16
17
|
builds.select { |b| b['has_artifacts'] }
|
17
18
|
end
|
18
19
|
|
19
20
|
private
|
20
21
|
|
21
|
-
attr_reader :github_user, :github_repo, :branch
|
22
|
+
attr_reader :github_user, :github_repo, :branch, :circle_ci_token
|
22
23
|
|
23
24
|
def url
|
24
|
-
"https://circleci.com/api/v1.1/project/github/#{github_user}/#{github_repo}/tree/#{branch}
|
25
|
+
"https://circleci.com/api/v1.1/project/github/#{github_user}/#{github_repo}/tree/#{branch}" \
|
26
|
+
'?filter=completed&limit=100'
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
data/lib/sleet/build.rb
CHANGED
@@ -4,19 +4,20 @@ module Sleet
|
|
4
4
|
class Build
|
5
5
|
attr_reader :build_num
|
6
6
|
|
7
|
-
def initialize(github_user:, github_repo:, build_num:)
|
7
|
+
def initialize(circle_ci_token:, github_user:, github_repo:, build_num:)
|
8
|
+
@circle_ci_token = circle_ci_token
|
8
9
|
@github_user = github_user
|
9
10
|
@github_repo = github_repo
|
10
11
|
@build_num = build_num
|
11
12
|
end
|
12
13
|
|
13
14
|
def artifacts
|
14
|
-
@artifacts ||= JSON.parse(Sleet::CircleCi.get(url).body)
|
15
|
+
@artifacts ||= JSON.parse(Sleet::CircleCi.get(url, circle_ci_token).body)
|
15
16
|
end
|
16
17
|
|
17
18
|
private
|
18
19
|
|
19
|
-
attr_reader :github_user, :github_repo
|
20
|
+
attr_reader :github_user, :github_repo, :circle_ci_token
|
20
21
|
|
21
22
|
def url
|
22
23
|
"https://circleci.com/api/v1.1/project/github/#{github_user}/#{github_repo}/#{build_num}/artifacts" # rubocop:disable Metrics/LineLength
|
data/lib/sleet/build_selector.rb
CHANGED
@@ -29,14 +29,14 @@ module Sleet
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def chosen_build_json
|
32
|
-
branch.
|
32
|
+
branch.builds_with_artifacts.find do |b|
|
33
33
|
b.fetch('workflows', nil)&.fetch('job_name', nil) == job_name
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
37
|
def must_find_a_build_with_artifacts!
|
38
38
|
!chosen_build_json.nil? ||
|
39
|
-
raise(Error, "No builds with
|
39
|
+
raise(Error, "No builds with artifacts found#{" for job name [#{job_name}]" if job_name}")
|
40
40
|
end
|
41
41
|
|
42
42
|
def chosen_build_must_have_input_file!
|
data/lib/sleet/circle_ci.rb
CHANGED
@@ -4,22 +4,8 @@ require 'singleton'
|
|
4
4
|
|
5
5
|
module Sleet
|
6
6
|
class CircleCi
|
7
|
-
|
8
|
-
|
9
|
-
def token
|
10
|
-
@token ||= File.read("#{Dir.home}/.circleci.token").strip
|
11
|
-
end
|
12
|
-
|
13
|
-
def get(url)
|
7
|
+
def self.get(url, token)
|
14
8
|
Faraday.get(url, 'circle-token' => token)
|
15
9
|
end
|
16
|
-
|
17
|
-
def reset!
|
18
|
-
@token = nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.get(url)
|
22
|
-
instance.get(url)
|
23
|
-
end
|
24
10
|
end
|
25
11
|
end
|
data/lib/sleet/cli.rb
CHANGED
@@ -4,6 +4,10 @@ module Sleet
|
|
4
4
|
class Cli < Thor
|
5
5
|
default_task :fetch
|
6
6
|
|
7
|
+
def self.exit_on_failure?
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
7
11
|
desc 'fetch', 'Fetch and Aggregate RSpec Persistance Files from CircleCI'
|
8
12
|
long_desc <<~LONGDESC
|
9
13
|
`sleet fetch` will find build(s) in CircleCI for the current branch, and
|
@@ -20,16 +24,27 @@ module Sleet
|
|
20
24
|
option :output_file, type: :string, aliases: [:o], desc: <<~DESC
|
21
25
|
This is the name for the output file, on your local system. It is relative to the source_dir. Will be IGNORED if workflows is provided.
|
22
26
|
DESC
|
27
|
+
option :username, type: :string, aliases: [:u], desc: <<~DESC
|
28
|
+
This is the GitHub username that is referenced by the CircleCI build. By default, Sleet will base this on your upstream git remote.
|
29
|
+
DESC
|
30
|
+
option :project, type: :string, aliases: [:p], desc: <<~DESC
|
31
|
+
This is the GitHub project that is referenced by the CircleCI build. By default, Sleet will base this on your upstream git remote.
|
32
|
+
DESC
|
33
|
+
option :branch, type: :string, aliases: [:b], desc: <<~DESC
|
34
|
+
This is the remote branch that is referenced by the CircleCI build. Sleet will attempt to guess this by default, but if you are pushing to a forked repo, you may need to specify a different branch name (e.g. "pull/1234").
|
35
|
+
DESC
|
23
36
|
option :workflows, type: :hash, aliases: [:w], desc: <<~DESC
|
24
37
|
To use Sleet with CircleCI Workflows you need to tell Sleet which build(s) to look in, and where each output should be saved. The input is a hash, where the key is the build name and the value is the output_file for that build. Sleet supports saving the artifacts to multiple builds, meaning it can support a mono-repo setup.
|
25
38
|
DESC
|
26
|
-
option :print_config, type: :boolean
|
39
|
+
option :print_config, type: :boolean
|
27
40
|
def fetch
|
28
41
|
sleet_config = Sleet::Config.new(cli_hash: options, dir: Dir.pwd)
|
29
42
|
if options[:print_config]
|
30
43
|
sleet_config.print!
|
31
44
|
exit
|
32
45
|
end
|
46
|
+
raise Sleet::Error, 'circle_ci_token required and not provided' unless sleet_config.circle_ci_token
|
47
|
+
|
33
48
|
Sleet::FetchCommand.new(sleet_config).do!
|
34
49
|
end
|
35
50
|
|
@@ -44,6 +59,7 @@ module Sleet
|
|
44
59
|
end
|
45
60
|
|
46
61
|
desc 'config', 'Print the config'
|
62
|
+
option :show_sensitive, type: :boolean
|
47
63
|
def config
|
48
64
|
Sleet::Config.new(cli_hash: options, dir: Dir.pwd).print!
|
49
65
|
end
|
data/lib/sleet/config.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Sleet
|
4
|
-
class Config
|
4
|
+
class Config # rubocop:disable Metrics/ClassLength
|
5
5
|
OPTION_FILENAME = '.sleet.yml'
|
6
|
+
HIDDEN_UNLESS_IN_CLI_OPTIONS = %w[show_sensitive print_config].freeze
|
6
7
|
ConfigOption = Struct.new(:value, :source)
|
7
8
|
|
8
9
|
def initialize(dir:, cli_hash: {})
|
@@ -22,10 +23,26 @@ module Sleet
|
|
22
23
|
options_hash[:output_file]
|
23
24
|
end
|
24
25
|
|
26
|
+
def username
|
27
|
+
options_hash[:username]
|
28
|
+
end
|
29
|
+
|
30
|
+
def project
|
31
|
+
options_hash[:project]
|
32
|
+
end
|
33
|
+
|
34
|
+
def branch
|
35
|
+
options_hash[:branch]
|
36
|
+
end
|
37
|
+
|
25
38
|
def workflows
|
26
39
|
options_hash[:workflows]
|
27
40
|
end
|
28
41
|
|
42
|
+
def circle_ci_token
|
43
|
+
options_hash[:circle_ci_token]
|
44
|
+
end
|
45
|
+
|
29
46
|
def print!
|
30
47
|
puts Terminal::Table.new headings: %w[Option Value Source], rows: table_rows
|
31
48
|
end
|
@@ -43,15 +60,23 @@ module Sleet
|
|
43
60
|
end
|
44
61
|
|
45
62
|
def table_rows
|
46
|
-
|
63
|
+
table_options.map do |key, option|
|
47
64
|
if key.to_sym == :workflows
|
48
65
|
[key, Terminal::Table.new(headings: ['Job Name', 'Output File'], rows: option.value.to_a), option.source]
|
66
|
+
elsif key.to_sym == :circle_ci_token && !options['show_sensitive'].value
|
67
|
+
[key, '**REDACTED**', option.source]
|
49
68
|
else
|
50
69
|
[key, option.value, option.source]
|
51
70
|
end
|
52
71
|
end
|
53
72
|
end
|
54
73
|
|
74
|
+
def table_options
|
75
|
+
options.reject do |key, _option|
|
76
|
+
HIDDEN_UNLESS_IN_CLI_OPTIONS.include?(key) && !cli_hash.key?(key)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
55
80
|
def cli_options
|
56
81
|
build_option_hash('CLI', cli_hash)
|
57
82
|
end
|
@@ -88,7 +113,9 @@ module Sleet
|
|
88
113
|
{
|
89
114
|
'source_dir' => File.expand_path(default_dir),
|
90
115
|
'input_file' => '.rspec_example_statuses',
|
91
|
-
'output_file' => '.rspec_example_statuses'
|
116
|
+
'output_file' => '.rspec_example_statuses',
|
117
|
+
'show_sensitive' => false,
|
118
|
+
'print_config' => true
|
92
119
|
}
|
93
120
|
end
|
94
121
|
|
data/lib/sleet/fetch_command.rb
CHANGED
@@ -7,7 +7,6 @@ module Sleet
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def do!
|
10
|
-
repo.validate!
|
11
10
|
error_messages = []
|
12
11
|
fetchers.map do |fetcher|
|
13
12
|
begin
|
@@ -26,8 +25,7 @@ module Sleet
|
|
26
25
|
def fetchers
|
27
26
|
job_name_to_output_files.map do |job_name, output_filename|
|
28
27
|
Sleet::JobFetcher.new(
|
29
|
-
|
30
|
-
input_filename: config.input_file,
|
28
|
+
config: config,
|
31
29
|
output_filename: output_filename,
|
32
30
|
repo: repo,
|
33
31
|
job_name: job_name
|
@@ -40,7 +38,7 @@ module Sleet
|
|
40
38
|
end
|
41
39
|
|
42
40
|
def repo
|
43
|
-
@repo ||= Sleet::Repo.
|
41
|
+
@repo ||= Sleet::Repo.from_config(config)
|
44
42
|
end
|
45
43
|
end
|
46
44
|
end
|
data/lib/sleet/job_fetcher.rb
CHANGED
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
module Sleet
|
4
4
|
class JobFetcher
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@
|
5
|
+
def initialize(config:, output_filename:, job_name:, repo:)
|
6
|
+
@circle_ci_token = config.circle_ci_token
|
7
|
+
@source_dir = config.source_dir
|
8
|
+
@input_filename = config.input_file
|
8
9
|
@output_filename = output_filename
|
9
10
|
@job_name = job_name
|
10
11
|
@repo = repo
|
@@ -17,7 +18,7 @@ module Sleet
|
|
17
18
|
|
18
19
|
private
|
19
20
|
|
20
|
-
attr_reader :input_filename, :output_filename, :job_name, :source_dir, :repo
|
21
|
+
attr_reader :input_filename, :output_filename, :job_name, :source_dir, :repo, :circle_ci_token
|
21
22
|
|
22
23
|
def validate!
|
23
24
|
build_selector.validate!
|
@@ -35,14 +36,11 @@ module Sleet
|
|
35
36
|
def build_persistance_artifacts
|
36
37
|
@build_persistance_artifacts ||= Sleet::ArtifactDownloader.new(
|
37
38
|
file_name: input_filename,
|
38
|
-
artifacts: build.artifacts
|
39
|
+
artifacts: build.artifacts,
|
40
|
+
circle_ci_token: circle_ci_token
|
39
41
|
).files
|
40
42
|
end
|
41
43
|
|
42
|
-
def branch
|
43
|
-
repo.branch
|
44
|
-
end
|
45
|
-
|
46
44
|
def build
|
47
45
|
build_selector.build
|
48
46
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sleet
|
4
|
+
class LocalRepo
|
5
|
+
REMOTE_BRANCH_REGEX = %r{^([^\/.]+)\/(.+)}.freeze
|
6
|
+
CURRENT_BRANCH_REGEX = %r{^refs\/heads\/}.freeze
|
7
|
+
GITHUB_MATCH_REGEX = %r{github.com[:\/](.+)\/(.+)\.git}.freeze
|
8
|
+
|
9
|
+
def initialize(source_dir:)
|
10
|
+
@source_dir = source_dir
|
11
|
+
end
|
12
|
+
|
13
|
+
def username
|
14
|
+
validate!
|
15
|
+
|
16
|
+
github_match[1]
|
17
|
+
end
|
18
|
+
|
19
|
+
def project
|
20
|
+
validate!
|
21
|
+
|
22
|
+
github_match[2]
|
23
|
+
end
|
24
|
+
|
25
|
+
def branch_name
|
26
|
+
validate!
|
27
|
+
|
28
|
+
current_branch.upstream.name.match(REMOTE_BRANCH_REGEX)[2]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :source_dir
|
34
|
+
|
35
|
+
def current_branch_name
|
36
|
+
@current_branch_name ||= repo.head.name.sub(CURRENT_BRANCH_REGEX, '')
|
37
|
+
end
|
38
|
+
|
39
|
+
def current_branch
|
40
|
+
@current_branch ||= repo.branches[current_branch_name]
|
41
|
+
end
|
42
|
+
|
43
|
+
def github_match
|
44
|
+
@github_match ||= GITHUB_MATCH_REGEX.match(current_branch.remote.url)
|
45
|
+
end
|
46
|
+
|
47
|
+
def repo
|
48
|
+
@repo ||= Rugged::Repository.new(source_dir)
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate!
|
52
|
+
return if @validated
|
53
|
+
|
54
|
+
must_be_on_branch!
|
55
|
+
must_have_an_upstream_branch!
|
56
|
+
upstream_remote_must_be_github!
|
57
|
+
@validated = true
|
58
|
+
end
|
59
|
+
|
60
|
+
def must_be_on_branch!
|
61
|
+
!current_branch.nil? ||
|
62
|
+
raise(Error, 'Not on a branch')
|
63
|
+
end
|
64
|
+
|
65
|
+
def must_have_an_upstream_branch!
|
66
|
+
!current_branch.remote.nil? ||
|
67
|
+
raise(Error, "No upstream branch set for the current branch of #{repo.current_branch_name}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def upstream_remote_must_be_github!
|
71
|
+
!github_match.nil? ||
|
72
|
+
raise(Error, 'Upstream remote is not GitHub')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/sleet/repo.rb
CHANGED
@@ -2,81 +2,44 @@
|
|
2
2
|
|
3
3
|
module Sleet
|
4
4
|
class Repo
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@repo = repo
|
5
|
+
def self.from_config(config)
|
6
|
+
local_repo = Sleet::LocalRepo.new(source_dir: config.source_dir)
|
7
|
+
|
8
|
+
new(
|
9
|
+
circle_ci_token: config.circle_ci_token,
|
10
|
+
username: config.username || local_repo.username,
|
11
|
+
project: config.project || local_repo.project,
|
12
|
+
branch_name: config.branch || local_repo.branch_name
|
13
|
+
)
|
15
14
|
end
|
16
15
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
def initialize(circle_ci_token:, username:, project:, branch_name:)
|
17
|
+
@circle_ci_token = circle_ci_token
|
18
|
+
@github_user = username
|
19
|
+
@github_repo = project
|
20
|
+
@branch_name = branch_name
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
|
23
|
+
def build_for(build_num)
|
24
|
+
Sleet::Build.new(
|
25
|
+
circle_ci_token: circle_ci_token,
|
25
26
|
github_user: github_user,
|
26
27
|
github_repo: github_repo,
|
27
|
-
|
28
|
+
build_num: build_num
|
28
29
|
)
|
29
30
|
end
|
30
31
|
|
31
|
-
def
|
32
|
-
Sleet::
|
32
|
+
def branch
|
33
|
+
@branch ||= Sleet::Branch.new(
|
34
|
+
circle_ci_token: circle_ci_token,
|
33
35
|
github_user: github_user,
|
34
36
|
github_repo: github_repo,
|
35
|
-
|
37
|
+
branch: branch_name
|
36
38
|
)
|
37
39
|
end
|
38
40
|
|
39
41
|
private
|
40
42
|
|
41
|
-
attr_reader :
|
42
|
-
|
43
|
-
def remote_branch
|
44
|
-
current_branch.upstream.name.match(REMOTE_BRANCH_REGEX)[2]
|
45
|
-
end
|
46
|
-
|
47
|
-
def github_user
|
48
|
-
github_match[1]
|
49
|
-
end
|
50
|
-
|
51
|
-
def github_repo
|
52
|
-
github_match[2]
|
53
|
-
end
|
54
|
-
|
55
|
-
def current_branch_name
|
56
|
-
repo.head.name.sub(CURRENT_BRANCH_REGEX, '')
|
57
|
-
end
|
58
|
-
|
59
|
-
def current_branch
|
60
|
-
repo.branches[current_branch_name]
|
61
|
-
end
|
62
|
-
|
63
|
-
def github_match
|
64
|
-
@github_match ||= GITHUB_MATCH_REGEX.match(current_branch.remote.url)
|
65
|
-
end
|
66
|
-
|
67
|
-
def must_be_on_branch!
|
68
|
-
!current_branch.nil? ||
|
69
|
-
raise(Error, 'Not on a branch')
|
70
|
-
end
|
71
|
-
|
72
|
-
def must_have_an_upstream_branch!
|
73
|
-
!current_branch.remote.nil? ||
|
74
|
-
raise(Error, "No upstream branch set for the current branch of #{repo.current_branch_name}")
|
75
|
-
end
|
76
|
-
|
77
|
-
def upstream_remote_must_be_github!
|
78
|
-
!github_match.nil? ||
|
79
|
-
raise(Error, 'Upstream remote is not GitHub')
|
80
|
-
end
|
43
|
+
attr_reader :circle_ci_token, :github_user, :github_repo, :branch_name
|
81
44
|
end
|
82
45
|
end
|