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.
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-slate
@@ -25,6 +25,7 @@ require 'sleet/config'
25
25
  require 'sleet/error'
26
26
  require 'sleet/fetch_command'
27
27
  require 'sleet/job_fetcher'
28
+ require 'sleet/local_repo'
28
29
  require 'sleet/repo'
29
30
  require 'sleet/rspec_file_merger'
30
31
  require 'sleet/version'
@@ -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'] }
@@ -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 builds_with_artificats
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}?filter=completed"
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
@@ -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
@@ -29,14 +29,14 @@ module Sleet
29
29
  end
30
30
 
31
31
  def chosen_build_json
32
- branch.builds_with_artificats.find do |b|
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 artifcats found#{" for job name [#{job_name}]" if job_name}")
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!
@@ -4,22 +4,8 @@ require 'singleton'
4
4
 
5
5
  module Sleet
6
6
  class CircleCi
7
- include Singleton
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
@@ -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, default: false
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
@@ -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
- options.map do |key, option|
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
 
@@ -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
- source_dir: config.source_dir,
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.from_dir(config.source_dir)
41
+ @repo ||= Sleet::Repo.from_config(config)
44
42
  end
45
43
  end
46
44
  end
@@ -2,9 +2,10 @@
2
2
 
3
3
  module Sleet
4
4
  class JobFetcher
5
- def initialize(source_dir:, input_filename:, output_filename:, job_name:, repo:)
6
- @source_dir = source_dir
7
- @input_filename = input_filename
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
@@ -2,81 +2,44 @@
2
2
 
3
3
  module Sleet
4
4
  class Repo
5
- REMOTE_BRANCH_REGEX = %r{^([^\/.]+)\/(.+)}
6
- CURRENT_BRANCH_REGEX = %r{^refs\/heads\/}
7
- GITHUB_MATCH_REGEX = %r{github.com[:\/](.+)\/(.+)\.git}
8
-
9
- def self.from_dir(dir)
10
- new(repo: Rugged::Repository.new(dir))
11
- end
12
-
13
- def initialize(repo:)
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 validate!
18
- must_be_on_branch!
19
- must_have_an_upstream_branch!
20
- upstream_remote_must_be_github!
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 branch
24
- @branch ||= Sleet::Branch.new(
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
- branch: remote_branch
28
+ build_num: build_num
28
29
  )
29
30
  end
30
31
 
31
- def build_for(build_num)
32
- Sleet::Build.new(
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
- build_num: build_num
37
+ branch: branch_name
36
38
  )
37
39
  end
38
40
 
39
41
  private
40
42
 
41
- attr_reader :repo
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