tinyci 0.4.2 → 0.5

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.
@@ -1,61 +1,63 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
  require 'tinyci/git_utils'
3
5
 
4
6
  module TinyCI
5
-
6
7
  # Responsible for writing the git hook file
7
8
  class Installer
8
9
  include GitUtils
9
-
10
+
10
11
  # Constructor
11
- #
12
- # @param [String] working_dir The directory from which to run. Does not have to be the root of the repo
12
+ #
13
+ # @param [String] working_dir The directory from which to run. Does not have to be the
14
+ # root of the repo.
13
15
  # @param [Logger] logger Logger object
14
16
  def initialize(working_dir: nil, logger: nil, absolute_path: false)
15
17
  @logger = logger
16
18
  @working_dir = working_dir || repo_root
17
19
  @absolute_path = absolute_path
18
20
  end
19
-
21
+
20
22
  # Write the hook to the relevant path and make it executable
21
23
  def install!
22
24
  unless inside_repository?
23
- log_error "not currently in a git repository"
25
+ log_error 'not currently in a git repository'
24
26
  return false
25
27
  end
26
-
28
+
27
29
  if hook_exists?
28
- log_error "post-update hook already exists in this repository"
30
+ log_error 'post-update hook already exists in this repository'
29
31
  return false
30
32
  end
31
-
32
- File.open(hook_path, 'a') {|f| f.write hook_content}
33
+
34
+ File.open(hook_path, 'a') { |f| f.write hook_content }
33
35
  FileUtils.chmod('u+x', hook_path)
34
-
36
+
35
37
  log_info 'tinyci post-update hook installed successfully'
36
38
  end
37
-
39
+
38
40
  private
39
-
41
+
40
42
  def hook_exists?
41
43
  File.exist? hook_path
42
44
  end
43
-
45
+
44
46
  def hook_path
45
47
  File.expand_path('hooks/post-update', git_directory_path)
46
48
  end
47
-
49
+
48
50
  def bin_path
49
51
  @absolute_path ? Gem.bin_path('tinyci', 'tinyci') : 'tinyci'
50
52
  end
51
-
53
+
52
54
  def hook_content
53
- <<-EOF
54
- #!/bin/sh
55
- unset GIT_DIR
55
+ <<~HOOK
56
+ #!/bin/sh
57
+ unset GIT_DIR
56
58
 
57
- #{bin_path} run --all
58
- EOF
59
+ #{bin_path} run --all
60
+ HOOK
59
61
  end
60
62
  end
61
63
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tinyci/path_utils'
4
+ require 'tinyci/git_utils'
5
+ require 'file-tail'
6
+
7
+ module TinyCI
8
+ # For reviewing the log files created by tinyCI runs. Can print lines from either a specific
9
+ # commit's logfile, or from the global logfile. Has functionality similar to the coreutils `tail`
10
+ # command.
11
+ class LogViewer
12
+ include PathUtils
13
+ include GitUtils
14
+
15
+ #
16
+ # Constructor
17
+ #
18
+ # @param [<Type>] working_dir The directory from which to run.
19
+ # @param [<Type>] commit The commit to run against
20
+ # @param [<Type>] follow After printing, instead of exiting, block and wait for additional data to be appended be the file and print it as it
21
+ # is written. Equivalent to unix `tail -f`
22
+ # @param [<Type>] num_lines How many lines of the file to print, starting from the end.
23
+ # Equivalent to unix `tail -n`
24
+ #
25
+ def initialize(working_dir:, commit: nil, follow: false, num_lines: nil)
26
+ @working_dir = working_dir
27
+ @commit = commit
28
+ @follow = follow
29
+ @num_lines = num_lines
30
+ end
31
+
32
+ def view!
33
+ if @follow
34
+ tail
35
+ else
36
+ dump
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def dump
43
+ unless inside_repository?
44
+ warn 'Error: Not currently inside a git repo, or not on a branch'
45
+ return false
46
+ end
47
+
48
+ unless logfile_exists?
49
+ warn "Error: Logfile does not exist at #{logfile_to_read}"
50
+ warn "Did you mean \e[1mtinyci --remote #{current_tracking_remote} log\e[22m?"
51
+ return false
52
+ end
53
+
54
+ if @num_lines.nil?
55
+ puts File.read(logfile_to_read)
56
+ else
57
+ File.open(logfile_to_read) do |log|
58
+ log.extend File::Tail
59
+ log.return_if_eof = true
60
+ log.backward @num_lines if @num_lines
61
+ log.tail { |line| puts line }
62
+ end
63
+ end
64
+ end
65
+
66
+ def tail
67
+ File.open(logfile_to_read) do |log|
68
+ log.extend(File::Tail)
69
+
70
+ log.backward @num_lines if @num_lines
71
+ log.tail { |line| puts line }
72
+ end
73
+ end
74
+
75
+ def logfile_to_read
76
+ if @commit
77
+ logfile_path
78
+ else
79
+ repo_logfile_path
80
+ end
81
+ end
82
+
83
+ def logfile_exists?
84
+ File.exist? logfile_to_read
85
+ end
86
+ end
87
+ end
@@ -1,16 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/multi_logger'
2
4
 
3
5
  module TinyCI
4
6
  # Defines helper instance methods for logging to reduce code verbosity
5
7
  module Logging
6
-
7
- %w(log debug info warn error fatal unknown).each do |m|
8
+ %w[log debug info warn error fatal unknown].each do |m|
8
9
  define_method("log_#{m}") do |*args|
9
10
  return false unless defined?(@logger) && @logger.is_a?(MultiLogger)
10
-
11
+
11
12
  @logger.send(m, *args)
12
13
  end
13
- end
14
-
14
+ end
15
15
  end
16
16
  end
@@ -1,48 +1,57 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
4
+ require 'fileutils'
2
5
 
3
6
  module TinyCI
4
7
  # This class allows logging to both `STDOUT` and to a file with a single call.
5
8
  # @attr [Boolean] quiet Disables logging to STDOUT
6
9
  class MultiLogger
7
- FORMAT = Proc.new do |severity, datetime, progname, msg|
8
- "[#{datetime.strftime "%T"}] #{msg}\n"
10
+ FORMAT = proc do |_severity, datetime, _progname, msg|
11
+ "[#{datetime.strftime '%T'}] #{msg}\n"
9
12
  end
10
-
13
+
11
14
  LEVEL = Logger::INFO
12
-
15
+
13
16
  attr_accessor :quiet
14
-
17
+
15
18
  # Constructor
16
- #
19
+ #
17
20
  # @param [Boolean] quiet Disables logging to STDOUT
18
21
  # @param [String] path Location to write logfile to
19
- def initialize(quiet: false, path: nil)
20
- @file_logger = nil
21
- self.output_path = path
22
+ def initialize(quiet: false, path: nil, paths: [])
23
+ @file_loggers = []
24
+ add_output_path path
25
+ paths.each { |p| add_output_path(p) }
22
26
  @quiet = quiet
23
-
27
+
24
28
  @stdout_logger = Logger.new($stdout)
25
29
  @stdout_logger.formatter = FORMAT
26
30
  @stdout_logger.level = LEVEL
27
31
  end
28
-
32
+
29
33
  def targets
30
34
  logs = []
31
- logs << @file_logger if @file_logger
35
+ logs += @file_loggers
32
36
  logs << @stdout_logger unless @quiet
33
-
37
+
34
38
  logs
35
39
  end
36
-
37
- def output_path=(path)
38
- if path
39
- @file_logger = Logger.new(path)
40
- @file_logger.formatter = FORMAT
41
- @file_logger.level = LEVEL
42
- end
40
+
41
+ def add_output_path(path)
42
+ return unless path
43
+
44
+ FileUtils.touch path
45
+
46
+ logger = Logger.new(path)
47
+ logger.formatter = FORMAT
48
+ logger.level = LEVEL
49
+ @file_loggers << logger
50
+
51
+ logger
43
52
  end
44
53
 
45
- %w(log debug info warn error fatal unknown).each do |m|
54
+ %w[log debug info warn error fatal unknown].each do |m|
46
55
  define_method(m) do |*args|
47
56
  targets.each { |t| t.send(m, *args) }
48
57
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tinyci/subprocesses'
4
+ require 'tinyci/git_utils'
5
+ require 'fileutils'
6
+
7
+ module TinyCI
8
+ # Methods for computing paths.
9
+ module PathUtils
10
+ def builds_path
11
+ File.absolute_path("#{@working_dir}/builds")
12
+ end
13
+
14
+ # Build the absolute target path
15
+ def target_path
16
+ File.join(builds_path, "#{time.to_i}_#{@commit}")
17
+ end
18
+
19
+ # Build the export path
20
+ def export_path
21
+ File.join(target_path, 'export')
22
+ end
23
+
24
+ private
25
+
26
+ def logfile_path
27
+ File.join(target_path, 'tinyci.log')
28
+ end
29
+
30
+ def repo_logfile_path
31
+ File.join(builds_path, 'tinyci.log')
32
+ end
33
+
34
+ # Ensure a path exists
35
+ def ensure_path(path)
36
+ FileUtils.mkdir_p path
37
+ end
38
+
39
+ def self.included(base)
40
+ base.include TinyCI::Subprocesses
41
+ base.include TinyCI::GitUtils
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/subprocesses'
2
4
  require 'tinyci/git_utils'
5
+ require 'tinyci/path_utils'
3
6
  require 'tinyci/logging'
4
7
  require 'tinyci/config'
5
8
 
@@ -14,178 +17,191 @@ require 'fileutils'
14
17
 
15
18
  module TinyCI
16
19
  # Responsible for managing the running of TinyCI against a single git object.
17
- #
18
- # @attr builder [TinyCI::Executor] Returns the Builder object. Used solely for testing at this time.
19
- # @attr tester [TinyCI::Executor] Returns the Tester object. Used solely for testing at this time.
20
+ #
21
+ # @attr builder [TinyCI::Executor] Returns the Builder object. Used solely for testing.
22
+ # @attr tester [TinyCI::Executor] Returns the Tester object. Used solely for testing.
20
23
  class Runner
21
24
  include Subprocesses
22
25
  include GitUtils
26
+ include PathUtils
23
27
  include Logging
24
-
28
+
29
+ CONFIG_FILENAME = '.tinyci.yml'
30
+
25
31
  attr_accessor :builder, :tester, :hooker
26
-
32
+
27
33
  # Constructor, allows injection of generic configuration params.
28
- #
34
+ #
29
35
  # @param working_dir [String] The working directory to execute against.
30
36
  # @param commit [String] SHA1 of git object to run against
31
37
  # @param logger [Logger] Logger object
32
- # @param time [Time] Override time of object creation. Used solely for testing at this time.
33
- # @param config [Hash] Override TinyCI config object, normally loaded from `.tinyci` file. Used solely for testing at this time.
38
+ # @param time [Time] Override time of object creation. Used solely for testing.
39
+ # @param config [Hash] Override TinyCI config object, normally loaded from `.tinyci` file.
40
+ # Used solely for testing.
34
41
  def initialize(working_dir: '.', commit:, time: nil, logger: nil, config: nil)
35
42
  @working_dir = working_dir
36
- @logger = logger
37
43
  @config = config
38
44
  @commit = commit
39
- @time = time || commit_time
45
+ @time = time
46
+ @logger = logger.is_a?(MultiLogger) ? MultiLogger.new(quiet: logger.quiet) : nil
47
+ ensure_path target_path
48
+ setup_log
40
49
  end
41
-
50
+
42
51
  # Runs the TinyCI system against the single git object referenced in `@commit`.
43
- #
52
+ #
44
53
  # @return [Boolean] `true` if the commit was built and tested successfully, `false` otherwise
54
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity
45
55
  def run!
46
56
  begin
47
- ensure_path target_path
48
- setup_log
49
-
50
57
  log_info "Commit: #{@commit}"
51
-
52
- log_info "Cleaning..."
58
+
59
+ unless config_exists?
60
+ log_error "No config found for #{@commit}"
61
+
62
+ return false
63
+ end
64
+
65
+ log_info 'Cleaning...'
53
66
  clean
54
-
55
- log_info "Exporting..."
67
+
68
+ log_info 'Exporting...'
56
69
  ensure_path export_path
57
70
  export
58
-
71
+
59
72
  begin
60
- load_config
73
+ config
61
74
  rescue ConfigMissingError => e
62
75
  log_error e.message
63
76
  log_error 'Removing export...'
64
77
  clean
65
-
78
+
66
79
  return false
67
80
  end
68
81
  @builder ||= instantiate_builder
69
82
  @tester ||= instantiate_tester
70
83
  @hooker ||= instantiate_hooker
71
-
72
- log_info "Building..."
84
+
85
+ log_info 'Building...'
73
86
  run_hook! :before_build
74
87
  begin
75
88
  @builder.build
76
- rescue => e
89
+ rescue StandardError => e
77
90
  run_hook! :after_build_failure
78
-
91
+
79
92
  raise e if ENV['TINYCI_ENV'] == 'test'
80
-
93
+
81
94
  log_error e
82
95
  log_debug e.backtrace
83
-
96
+
84
97
  return false
85
98
  else
86
99
  run_hook! :after_build_success
87
100
  ensure
88
101
  run_hook! :after_build
89
102
  end
90
-
91
-
92
- log_info "Testing..."
103
+
104
+ log_info 'Testing...'
93
105
  run_hook! :before_test
94
106
  begin
95
107
  @tester.test
96
- rescue => e
108
+ rescue StandardError => e
97
109
  run_hook! :after_test_failure
98
-
110
+
99
111
  raise e if ENV['TINYCI_ENV'] == 'test'
100
-
112
+
101
113
  log_error e
102
114
  log_debug e.backtrace
103
-
115
+
104
116
  return false
105
117
  else
106
118
  run_hook! :after_test_success
107
119
  ensure
108
120
  run_hook! :after_test
109
121
  end
110
-
122
+
111
123
  log_info "Finished #{@commit}"
112
- rescue => e
124
+ rescue StandardError => e
113
125
  raise e if ENV['TINYCI_ENV'] == 'test'
114
-
126
+
115
127
  log_error e
116
128
  log_debug e.backtrace
117
129
  return false
118
130
  ensure
119
131
  run_hook! :after_all
120
132
  end
121
-
133
+
122
134
  true
123
135
  end
124
-
125
- # Build the absolute target path
126
- def target_path
127
- File.absolute_path("#{@working_dir}/builds/#{@time.to_i}_#{@commit}/")
128
- end
129
-
130
- # Build the export path
131
- def export_path
132
- File.join(target_path, 'export')
133
- end
134
-
136
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/CyclomaticComplexity
137
+
135
138
  private
136
-
139
+
137
140
  def run_hook!(name)
138
141
  return unless @hooker
139
-
142
+
140
143
  @hooker.send("#{name}!")
141
144
  end
142
-
145
+
143
146
  # Creates log file if it doesnt exist
144
147
  def setup_log
145
148
  return unless @logger.is_a? MultiLogger
146
- FileUtils.touch logfile_path
147
- @logger.output_path = logfile_path
148
- end
149
-
150
- def logfile_path
151
- File.join(target_path, 'tinyci.log')
149
+
150
+ @logger.add_output_path logfile_path
151
+ @logger.add_output_path repo_logfile_path
152
152
  end
153
-
153
+
154
154
  # Instantiate the Builder object according to the class named in the config
155
155
  def instantiate_builder
156
156
  klass = TinyCI::Builders.const_get(@config[:builder][:class])
157
- klass.new(@config[:builder][:config].merge(target: target_path, export: export_path, commit: @commit), logger: @logger)
157
+ konfig = @config[:builder][:config].merge(
158
+ target: target_path,
159
+ export: export_path,
160
+ commit: @commit,
161
+ logger: @logger
162
+ )
163
+ klass.new(konfig)
158
164
  end
159
-
165
+
160
166
  # Instantiate the Tester object according to the class named in the config
161
167
  def instantiate_tester
162
168
  klass = TinyCI::Testers.const_get(@config[:tester][:class])
163
- klass.new(@config[:tester][:config].merge(target: target_path, export: export_path, commit: @commit), logger: @logger)
169
+ konfig = @config[:tester][:config].merge(
170
+ target: target_path,
171
+ export: export_path,
172
+ commit: @commit,
173
+ logger: @logger
174
+ )
175
+ klass.new(konfig)
164
176
  end
165
-
177
+
166
178
  # Instantiate the Hooker object according to the class named in the config
167
179
  def instantiate_hooker
168
180
  return nil unless @config[:hooker].is_a? Hash
169
-
181
+
170
182
  klass = TinyCI::Hookers.const_get(@config[:hooker][:class])
171
- klass.new(@config[:hooker][:config].merge(target: target_path, export: export_path, commit: @commit), logger: @logger)
183
+ konfig = @config[:hooker][:config].merge(
184
+ target: target_path,
185
+ export: export_path,
186
+ commit: @commit,
187
+ logger: @logger
188
+ )
189
+ klass.new(konfig)
172
190
  end
173
-
174
- # Instantiate the {Config} object from the `.tinyci.yml` file in the exported directory
175
- def load_config
176
- @config ||= Config.new(working_dir: export_path)
191
+
192
+ def config_path
193
+ File.expand_path(CONFIG_FILENAME, export_path)
177
194
  end
178
195
 
179
- # Parse the commit time from git
180
- def commit_time
181
- Time.at execute(git_cmd('show', '-s', '--format=%ct', @commit)).to_i
196
+ def config_exists?
197
+ file_exists_in_git? CONFIG_FILENAME
182
198
  end
183
199
 
184
- # Ensure a path exists
185
- def ensure_path(path)
186
- execute 'mkdir', '-p', path
200
+ # The {Config} object from the `.tinyci.yml` file in the exported directory
201
+ def config
202
+ @config ||= Config.new(config_path)
187
203
  end
188
-
204
+
189
205
  # Delete the export path
190
206
  def clean
191
207
  FileUtils.rm_rf export_path
@@ -196,7 +212,8 @@ module TinyCI
196
212
  # a `git export` subcommand.
197
213
  # see https://stackoverflow.com/a/163769
198
214
  def export
199
- execute_pipe git_cmd('archive', '--format=tar', @commit), ['tar', '-C', export_path, '-xf', '-']
215
+ execute_pipe git_cmd('archive', '--format=tar',
216
+ @commit), ['tar', '-C', export_path, '-xf', '-']
200
217
  end
201
218
  end
202
219
  end