sleet 0.3.10 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|