yle_tf 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7b1c7b056ec918749df48648c4648f2c48f1ea1
4
- data.tar.gz: a914bf80887e137d875a462ff6fa41e0d90719e7
3
+ metadata.gz: dc3f6bcfcb30e2863c067ca306b13a834ec66444
4
+ data.tar.gz: 3ba84a88565e830b0686f49532259fab3c466d82
5
5
  SHA512:
6
- metadata.gz: '097b3721ffacb30739b37cd9eacfa583a582f8e210778869a47d5f47c18269f98df41b68387e996199abd879855d9bddcf9100d2dd278793cb837ac9abdcbc83'
7
- data.tar.gz: 353f0124461ff8cfd6c742f0a4eeae635793c9dabc39cce25b3aac64b76325aedfb1d62118b831d79a73b98cd15696dc4be7b659812b48fc06e0ab8999f05725
6
+ metadata.gz: d438544dfde60a06feb23f9c12ba19230c6892fdaeaa43bcf7aa3360f1d34469b65ec13fd8184b920ba6ec76602644e2f1583678f469bbac66f5116b13302f68
7
+ data.tar.gz: 7a136fb090de4474ffd55e664d371374cd70dcab53dd6851f2c85ed6ce89364b5eb105e42c6131c5863d0894c7c990bc974566a227da31d92d6b5ff6159a66c5
@@ -15,8 +15,8 @@ class YleTf
15
15
  config = env[:config]
16
16
  backend = backend_config(config)
17
17
 
18
- Logger.debug('Initializing Terraform with backend configuration:')
19
- Logger.debug(backend.to_s)
18
+ Logger.info('Initializing Terraform')
19
+ Logger.debug("Backend configuration: #{backend}")
20
20
 
21
21
  if VersionRequirement.pre_0_9?(env[:terraform_version])
22
22
  init_pre_0_9(backend)
@@ -29,17 +29,21 @@ class YleTf
29
29
 
30
30
  def init_pre_0_9(backend)
31
31
  cli_args = backend.cli_args
32
- YleTf::System.cmd('terraform', 'remote', 'config', *cli_args) if cli_args
32
+ if cli_args
33
+ YleTf::System.cmd('terraform', 'remote', 'config',
34
+ '-no-color', *cli_args,
35
+ stdout: :debug)
36
+ end
33
37
 
34
38
  Logger.debug('Fetching Terraform modules')
35
- YleTf::System.cmd('terraform', 'get')
39
+ YleTf::System.cmd('terraform', 'get', '-no-color', stdout: :debug)
36
40
  end
37
41
 
38
42
  def init(backend)
39
43
  Logger.debug('Generating the backend configuration')
40
44
  backend.generate_config do
41
45
  Logger.debug('Initializing Terraform')
42
- YleTf::System.cmd('terraform', 'init', '-no-color')
46
+ YleTf::System.cmd('terraform', 'init', '-no-color', stdout: :debug)
43
47
  end
44
48
  end
45
49
 
@@ -20,7 +20,7 @@ class YleTf
20
20
  @app.call(env)
21
21
  end
22
22
  ensure
23
- FileUtils.rm_r(tmpdir) if tmpdir
23
+ FileUtils.rm_r(tmpdir) if tmpdir && Dir.exist?(tmpdir)
24
24
  end
25
25
 
26
26
  def tmpdir_prefix(config)
@@ -1,5 +1,6 @@
1
1
  require 'yle_tf/error'
2
2
  require 'yle_tf/logger'
3
+ require 'yle_tf/system'
3
4
  require 'yle_tf/version_requirement'
4
5
 
5
6
  class YleTf
@@ -22,10 +23,8 @@ class YleTf
22
23
  end
23
24
 
24
25
  def terraform_version
25
- # TODO: move `command` to YleTf::System
26
- Regexp.last_match(1) if `terraform version` =~ /^Terraform v([^\s]+)/
27
- rescue Errno::ENOENT
28
- nil
26
+ v = YleTf::System.read_cmd('terraform', 'version', error_handler: proc {})
27
+ Regexp.last_match(1) if v =~ /^Terraform v([^\s]+)/
29
28
  end
30
29
 
31
30
  def verify_version(env)
@@ -12,9 +12,13 @@ class YleTf
12
12
  config = env[:config]
13
13
  all_envs = VarsFile.list_all_envs(config)
14
14
 
15
+ if all_envs.empty?
16
+ raise Error, "No Terraform vars files found in '#{VarsFile::ENV_DIR}/'"
17
+ end
18
+
15
19
  if !all_envs.include?(config.tf_env)
16
20
  raise Error, "Terraform vars file not found for the '#{config.tf_env}' " \
17
- " environment. Existing envs: #{all_envs.join(', ')}"
21
+ "environment. Existing envs: #{all_envs.join(', ')}"
18
22
  end
19
23
 
20
24
  @app.call(env)
data/lib/yle_tf/cli.rb CHANGED
@@ -5,7 +5,7 @@ class YleTf
5
5
  attr_reader :tf_options, :tf_command, :tf_command_args, :tf_env
6
6
 
7
7
  # YleTf option arguments
8
- TF_OPTIONS = %w[--debug --no-hooks --only-hooks].freeze
8
+ TF_OPTIONS = %w[--debug --no-color --no-hooks --only-hooks].freeze
9
9
 
10
10
  HELP_ARGS = %w[-h --help help].freeze
11
11
  VERSION_ARGS = %w[-v --version version].freeze
@@ -65,7 +65,8 @@ class YleTf
65
65
  @tf_env = 'error'
66
66
  end
67
67
 
68
- self.debug = true if @tf_options.include?(:debug)
68
+ self.debug = true if @tf_options[:debug]
69
+ YleTf::Logger.color = !@tf_options[:no_color]
69
70
  end
70
71
 
71
72
  # Returns `Symbol` for the arg, e.g. `"--foo-bar"` -> `:foo_bar`
@@ -0,0 +1,22 @@
1
+ class YleTf
2
+ module Logger
3
+ module Colorize
4
+ COLORS = {
5
+ black: 30,
6
+ red: 31,
7
+ brown: 33,
8
+ blue: 34,
9
+ magenta: 35,
10
+ cyan: 36,
11
+ gray: 37
12
+ }.freeze
13
+
14
+ # Wraps the message with the specified ANSI color code
15
+ # if the color is specified and supported
16
+ def self.colorize(msg, color)
17
+ code = COLORS[color]
18
+ code ? "\033[#{code}m#{msg}\033[0m" : msg
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/yle_tf/logger.rb CHANGED
@@ -1,14 +1,17 @@
1
1
  require 'forwardable'
2
2
  require 'logger'
3
3
  require 'rubygems'
4
+ require 'yle_tf/logger/colorize'
4
5
 
5
6
  class YleTf
6
7
  # Logger for debug, error, etc. outputs.
7
8
  # Prints to STDERR, so it does not mess with e.g. `terraform output`.
8
9
  module Logger
10
+ LEVELS = %i[debug info warn error fatal].freeze
11
+
9
12
  class << self
10
13
  extend Forwardable
11
- def_delegators :logger, :debug, :info, :warn, :error, :fatal
14
+ def_delegators :logger, *LEVELS
12
15
  def_delegators :logger, :debug?
13
16
  end
14
17
 
@@ -28,14 +31,35 @@ class YleTf
28
31
 
29
32
  def self.log_formatter
30
33
  proc do |severity, _datetime, progname, msg|
34
+ msg = Colorize.colorize(msg, color(severity))
35
+
31
36
  if progname
32
- "[#{progname}] #{severity}: #{msg}\n"
37
+ "#{severity}: [#{progname}] #{msg}\n"
33
38
  else
34
39
  "#{severity}: #{msg}\n"
35
40
  end
36
41
  end
37
42
  end
38
43
 
44
+ def self.color?
45
+ @color
46
+ end
47
+
48
+ def self.color=(value)
49
+ @color = value
50
+ end
51
+
52
+ def self.color(severity)
53
+ return if !color?
54
+
55
+ case severity.to_s
56
+ when 'FATAL', 'ERROR'
57
+ :red
58
+ when 'WARN'
59
+ :brown
60
+ end
61
+ end
62
+
39
63
  # Patches the `::Logger` in older Ruby versions to
40
64
  # accept log level as a `String`
41
65
  def self.patch_for_old_ruby(logger)
@@ -0,0 +1,105 @@
1
+ require 'yle_tf/system/output_logger'
2
+
3
+ class YleTf
4
+ class System
5
+ class IOHandlers
6
+ BLOCK_SIZE = 1024
7
+
8
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
9
+ def self.input_handler(handler)
10
+ case handler
11
+ when :close
12
+ close
13
+ when :console
14
+ console_input
15
+ when :dev_null
16
+ dev_null_input
17
+ when IO, StringIO
18
+ io_input(handler)
19
+ else
20
+ if !handler.respond_to?(:call)
21
+ raise YleTf::Error, "Unknown input handler #{handler.inspect}"
22
+ end
23
+ handler
24
+ end
25
+ end
26
+
27
+ def self.output_handler(handler)
28
+ case handler
29
+ when :close
30
+ close
31
+ when :console
32
+ console_output
33
+ when :dev_null
34
+ dev_null_output
35
+ when IO, StringIO
36
+ io_output(handler)
37
+ when Symbol
38
+ OutputLogger.new(handler)
39
+ else
40
+ if !handler.respond_to?(:call)
41
+ raise YleTf::Error, "Unknown output handler #{handler.inspect}"
42
+ end
43
+ handler
44
+ end
45
+ end
46
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
47
+
48
+ # Returns a lambda that just closes the IO
49
+ def self.close
50
+ ->(io, *) { io.close }
51
+ end
52
+
53
+ # Returns a lambda that pipes STDIN to the IO
54
+ def self.console_input
55
+ io_input(STDIN)
56
+ end
57
+
58
+ # Returns a lambda that pipes IO's output to STDOUT
59
+ def self.console_output
60
+ io_output(STDOUT)
61
+ end
62
+
63
+ # Returns a lambda that does nothing
64
+ def self.dev_null_input
65
+ ->(*) {}
66
+ end
67
+
68
+ # Returns a lambda that just consumes the IO's output
69
+ def self.dev_null_output
70
+ lambda do |io, *|
71
+ Thread.new do
72
+ while io.read; end
73
+ end
74
+ end
75
+ end
76
+
77
+ # Returns a lambda that pipes the source IO to the IO's input
78
+ def self.io_input(source)
79
+ lambda do |target, *|
80
+ Thread.new do
81
+ begin
82
+ while (data = source.readpartial(BLOCK_SIZE))
83
+ target.write(data)
84
+ end
85
+ ensure
86
+ target.close_write
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ # Returns a lambda that pipes IO's output to the target IO
93
+ # Does not close the target stream
94
+ def self.io_output(target)
95
+ lambda do |source, *|
96
+ Thread.new do
97
+ while (data = source.readpartial(BLOCK_SIZE))
98
+ target.write(data)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,29 @@
1
+ require 'yle_tf/logger'
2
+
3
+ class YleTf
4
+ class System
5
+ class OutputLogger
6
+ attr_reader :level
7
+
8
+ def initialize(level)
9
+ @level = level.to_sym
10
+
11
+ raise YleTf::Error, "Unknown log level '#{@level}'" if !level?(@level)
12
+ end
13
+
14
+ def call(io, progname)
15
+ Thread.new do
16
+ io.each { |line| log(progname, line.chomp) }
17
+ end
18
+ end
19
+
20
+ def level?(level)
21
+ YleTf::Logger::LEVELS.include?(level)
22
+ end
23
+
24
+ def log(progname, line)
25
+ YleTf::Logger.public_send(level, progname) { line }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ require 'yle_tf/logger'
2
+ require 'yle_tf/system/output_logger'
3
+
4
+ class YleTf
5
+ class System
6
+ # An IO handler class for YleTf Hook output.
7
+ #
8
+ # Allows hooks to emit log messages with specific levels
9
+ # by prefixing a line with `<LEVEL>: `.
10
+ class TfHookOutputLogger < OutputLogger
11
+ def log(progname, line)
12
+ # Remove `[<progname>] ` prefix from the output line.
13
+ # This is mostly for backwards compatibility in Yle.
14
+ line.sub!(/^\[#{progname}\] /, '')
15
+
16
+ level, line = line_level(line)
17
+
18
+ YleTf::Logger.public_send(level, progname) { line }
19
+ end
20
+
21
+ # Extracts the log level from the line if found,
22
+ # otherwise returns the default level and the line as is
23
+ def line_level(line)
24
+ if (m = /^(?<level>[A-Z]+): (?<line>.*)$/.match(line))
25
+ line_level = m[:level].downcase.to_sym
26
+ return [line_level, m[:line]] if level?(line_level)
27
+ end
28
+
29
+ [level, line]
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/yle_tf/system.rb CHANGED
@@ -1,22 +1,98 @@
1
+ require 'English'
2
+ require 'open3'
1
3
  require 'shellwords'
2
-
4
+ require 'thwait'
3
5
  require 'yle_tf/error'
4
6
  require 'yle_tf/logger'
7
+ require 'yle_tf/system/io_handlers'
5
8
 
6
9
  class YleTf
7
10
  # Helpers to execute system commands with error handling
8
- #
9
- # TODO: Add way to wrap stdout of the commands and direct it to `Logger`
10
11
  class System
11
12
  ExecuteError = Class.new(YleTf::Error)
12
13
 
14
+ DEFAULT_ERROR_HANDLER = ->(_exit_code, error) { raise error }.freeze
15
+
16
+ DEFAULT_IO_HANDLERS = {
17
+ stdin: :dev_null,
18
+ stdout: :info,
19
+ stderr: :error
20
+ }.freeze
21
+
22
+ def self.console_cmd(*args, **opts)
23
+ env = opts[:env] || {}
24
+
25
+ YleTf::Logger.debug { "Calling #{cmd_string(args, env)}" }
26
+
27
+ system(env, *args)
28
+ verify_exit_status($CHILD_STATUS, opts[:error_handler], cmd_string(args))
29
+ end
30
+
31
+ # Executes the command and attaches IO streams
13
32
  def self.cmd(*args, **opts)
14
- env = opts[:env]
15
- YleTf::Logger.debug { "Calling `#{args.shelljoin}`#{" with env '#{env}'" if env}" }
33
+ opts = DEFAULT_IO_HANDLERS.merge(opts)
34
+ env = opts[:env] || {}
35
+ progname = opts.fetch(:progname) { args.first }
36
+
37
+ YleTf::Logger.debug { "Calling #{cmd_string(args, env)}" }
38
+
39
+ status = Open3.popen3(env, *args, &handle_io(progname, opts))
40
+ verify_exit_status(status, opts[:error_handler], cmd_string(args))
41
+ rescue Interrupt, Errno::ENOENT => e
42
+ error(opts[:error_handler], "Failed to execute #{cmd_string(args)}: #{e}")
43
+ end
44
+
45
+ def self.read_cmd(*args, **opts)
46
+ buffer = StringIO.new
47
+ cmd(*args, opts.merge(stdout: buffer))
48
+ buffer.string
49
+ end
50
+
51
+ def self.cmd_string(args, env = nil)
52
+ "`#{args.shelljoin}`#{" with env '#{env}'" if env && !env.empty?}"
53
+ end
54
+
55
+ def self.handle_io(progname, handlers)
56
+ lambda do |stdin, stdout, stderr, wait_thr|
57
+ in_thr = attach_input_handler(handlers[:stdin], stdin, progname)
58
+ out_thr = [
59
+ attach_output_handler(handlers[:stdout], stdout, progname),
60
+ attach_output_handler(handlers[:stderr], stderr, progname)
61
+ ]
62
+
63
+ # Wait for the process to exit
64
+ wait_thr.value.tap do
65
+ YleTf::Logger.debug("`#{progname}` exited, killing input handler thread")
66
+ in_thr.kill if in_thr.is_a?(Thread)
67
+
68
+ YleTf::Logger.debug('Waiting for output handler threads to stop')
69
+ ThreadsWait.all_waits(out_thr)
70
+ end
71
+ end
72
+ end
73
+
74
+ def self.attach_input_handler(handler, io, progname)
75
+ io_proc = IOHandlers.input_handler(handler)
76
+ io_proc.call(io, progname)
77
+ end
78
+
79
+ def self.attach_output_handler(handler, io, progname)
80
+ io_proc = IOHandlers.output_handler(handler)
81
+ io_proc.call(io, progname)
82
+ end
83
+
84
+ def self.verify_exit_status(status, handler, cmd)
85
+ status.success? ||
86
+ error(handler,
87
+ "Failed to execute #{cmd} (#{status.exitstatus})",
88
+ status.exitstatus)
89
+ end
90
+
91
+ def self.error(handler, error_msg, exit_code = nil)
92
+ YleTf::Logger.debug(error_msg)
16
93
 
17
- system(env || {}, *args) ||
18
- raise(ExecuteError,
19
- "Failed to execute `#{args.shelljoin}`#{" with env '#{env}'" if env}")
94
+ handler ||= DEFAULT_ERROR_HANDLER
95
+ handler.call(exit_code, ExecuteError.new(error_msg))
20
96
  end
21
97
  end
22
98
  end
@@ -4,6 +4,7 @@ require 'tmpdir'
4
4
  require 'yle_tf/error'
5
5
  require 'yle_tf/logger'
6
6
  require 'yle_tf/system'
7
+ require 'yle_tf/system/tf_hook_output_logger'
7
8
 
8
9
  class YleTf
9
10
  class TfHook
@@ -39,8 +40,14 @@ class YleTf
39
40
  def run(tf_vars)
40
41
  fetch if !path
41
42
 
42
- Logger.info("Running hook '#{description}'...")
43
- YleTf::System.cmd(path, env: vars.merge(tf_vars))
43
+ Logger.info("Running hook '#{description}'")
44
+ YleTf::System.cmd(
45
+ path,
46
+ env: vars.merge(tf_vars),
47
+ progname: File.basename(path),
48
+ stdout: System::TfHookOutputLogger.new(:info),
49
+ stderr: System::TfHookOutputLogger.new(:error)
50
+ )
44
51
  ensure
45
52
  delete_tmpdir
46
53
  end
@@ -64,9 +71,9 @@ class YleTf
64
71
  end
65
72
 
66
73
  def clone_git_repo(config)
67
- Logger.info("Cloning hook '#{description}' from #{config[:uri]} (#{config[:ref]})")
74
+ Logger.debug("Cloning hook '#{description}' from #{config[:uri]} (#{config[:ref]})")
68
75
  YleTf::System.cmd(
69
- 'git', 'clone', '--no-progress', '--depth=1', '--branch', config[:ref],
76
+ 'git', 'clone', '--quiet', '--depth=1', '--branch', config[:ref],
70
77
  '--', config[:uri], config[:dir]
71
78
  )
72
79
  end
@@ -76,7 +83,7 @@ class YleTf
76
83
  end
77
84
 
78
85
  def delete_tmpdir
79
- FileUtils.rm_r(@tmpdir) if @tmpdir
86
+ FileUtils.rm_r(@tmpdir) if @tmpdir && Dir.exist?(@tmpdir)
80
87
  @tmpdir = nil
81
88
  end
82
89
 
@@ -1,14 +1,16 @@
1
1
  class YleTf
2
2
  class VarsFile
3
+ ENV_DIR = 'envs'.freeze
4
+
3
5
  # Returns the env specific tfvars file path if it exists
4
6
  def self.find_env_vars_file(config)
5
- path = "#{config.module_dir}/envs/#{config.tf_env}.tfvars"
7
+ path = "#{config.module_dir}/#{ENV_DIR}/#{config.tf_env}.tfvars"
6
8
  VarsFile.new(path) if File.exist?(path)
7
9
  end
8
10
 
9
11
  # Returns all envs that have tfvars files
10
12
  def self.list_all_envs(config)
11
- Dir.glob("#{config.module_dir}/envs/*.tfvars").map do |path|
13
+ Dir.glob("#{config.module_dir}/#{ENV_DIR}/*.tfvars").map do |path|
12
14
  File.basename(path, '.tfvars')
13
15
  end
14
16
  end
@@ -1,3 +1,3 @@
1
1
  class YleTf
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '0.2.0'.freeze
3
3
  end
@@ -1,3 +1,4 @@
1
+ require 'yle_tf/logger'
1
2
  require 'yle_tf/system'
2
3
 
3
4
  module YleTfPlugins
@@ -5,9 +6,16 @@ module YleTfPlugins
5
6
  class Command
6
7
  def execute(env)
7
8
  command = env[:tf_command]
8
- args = env[:tf_command_args]
9
9
 
10
- YleTf::System.cmd('terraform', command, *args)
10
+ args = env[:tf_command_args].dup
11
+ args << '-no-color' if !color?(env)
12
+
13
+ YleTf::Logger.info "Running `terraform #{command}`"
14
+ YleTf::System.console_cmd('terraform', command, *args)
15
+ end
16
+
17
+ def color?(env)
18
+ !env[:tf_options][:no_color]
11
19
  end
12
20
  end
13
21
  end
@@ -1,5 +1,6 @@
1
1
  require 'optparse'
2
2
 
3
+ require 'yle_tf/system'
3
4
  require 'yle_tf/plugin'
4
5
 
5
6
  module YleTfPlugins
@@ -20,6 +21,7 @@ module YleTfPlugins
20
21
  o.on('-h', '--help', 'Prints this help')
21
22
  o.on('-v', '--version', 'Prints the version information')
22
23
  o.on('--debug', 'Print debug information')
24
+ o.on('--no-color', 'Do not output with colors')
23
25
  o.on('--no-hooks', 'Do not run any hooks')
24
26
  o.on('--only-hooks', 'Only run the hooks')
25
27
  o.separator ''
@@ -46,9 +48,14 @@ module YleTfPlugins
46
48
  end
47
49
 
48
50
  def terraform_help
49
- `terraform help`.lines.grep(/^ /)
50
- rescue Errno::ENOENT
51
- ' [Terraform not found]'
51
+ on_error = proc do |exit_code|
52
+ # exit_code is nil if the command was not found
53
+ # Ignore other exit codes, as older Terraform versions return
54
+ # non-zero exit codes for the help.
55
+ return ' [Terraform not found]' if exit_code.nil?
56
+ end
57
+ help = YleTf::System.read_cmd('terraform', '--help', error_handler: on_error)
58
+ help.lines.grep(/^ /)
52
59
  end
53
60
  end
54
61
  end
@@ -1,5 +1,4 @@
1
- require 'optparse'
2
-
1
+ require 'yle_tf/system'
3
2
  require 'yle_tf/version'
4
3
 
5
4
  module YleTfPlugins
@@ -11,9 +10,9 @@ module YleTfPlugins
11
10
  end
12
11
 
13
12
  def terraform_version
14
- `terraform version`.lines.first
15
- rescue Errno::ENOENT
16
- '[Terraform not found]'
13
+ on_error = proc { return '[Terraform not found]' }
14
+ v = YleTf::System.read_cmd('terraform', 'version', error_handler: on_error)
15
+ v.lines.first
17
16
  end
18
17
  end
19
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yle_tf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yleisradio
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-08-31 00:00:00.000000000 Z
13
+ date: 2017-09-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -86,11 +86,15 @@ files:
86
86
  - lib/yle_tf/config/loader.rb
87
87
  - lib/yle_tf/error.rb
88
88
  - lib/yle_tf/logger.rb
89
+ - lib/yle_tf/logger/colorize.rb
89
90
  - lib/yle_tf/plugin.rb
90
91
  - lib/yle_tf/plugin/action_hook.rb
91
92
  - lib/yle_tf/plugin/loader.rb
92
93
  - lib/yle_tf/plugin/manager.rb
93
94
  - lib/yle_tf/system.rb
95
+ - lib/yle_tf/system/io_handlers.rb
96
+ - lib/yle_tf/system/output_logger.rb
97
+ - lib/yle_tf/system/tf_hook_output_logger.rb
94
98
  - lib/yle_tf/tf_hook.rb
95
99
  - lib/yle_tf/tf_hook/runner.rb
96
100
  - lib/yle_tf/vars_file.rb