sleet 0.3.10 → 0.5.0

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