tinyci 0.4.2 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/config_transformer'
2
4
  require 'tinyci/symbolize'
3
5
  require 'yaml'
@@ -6,60 +8,57 @@ module TinyCI
6
8
  # Represents the Configuration for a repo, parsed from the `.tinyci.yml` file in the repo root.
7
9
  # Mainly a wrapper around a the hash object parsed from the yaml in the config file.
8
10
  # The keys of the hash are recursively symbolized.
9
- #
11
+ #
10
12
  # As it is loaded, the configuration file data is passed through the {TinyCI::ConfigTransformer}
11
13
  # class, which translates any definitions in the concise format into the more verbose format
12
14
  class Config
13
15
  include Symbolize
14
-
16
+
17
+ attr_accessor :config_path
18
+
15
19
  # Constructor
16
- #
20
+ #
17
21
  # @param [String] working_dir The working directory in which to find the config file
18
22
  # @param [String] config_path Override the path to the config file
19
23
  # @param [String] config Override the config content
20
- #
24
+ #
21
25
  # @raise [ConfigMissingError] if the config file is not found
22
- def initialize(working_dir: '.', config_path: nil, config: nil)
23
- @working_dir = working_dir
24
- @config_pathname = config_path
26
+ def initialize(config_path, config: nil)
27
+ @config_path = config_path
25
28
  @config_content = config
26
-
27
- raise ConfigMissingError, "config file #{config_pathname} not found" unless config_file_exists?
29
+
30
+ raise ConfigMissingError, "config file #{config_path} not found" unless config_file_exists?
28
31
  end
29
-
32
+
30
33
  # Address into the config object
31
- #
34
+ #
32
35
  # @param [Symbol] key The key to address
33
36
  def [](key)
34
37
  config_content[key]
35
38
  end
36
-
39
+
37
40
  # Return the raw hash representation
38
- #
41
+ #
39
42
  # @return [Hash] The configuration as a hash
40
43
  def to_hash
41
44
  config_content
42
45
  end
43
-
46
+
44
47
  private
45
-
48
+
46
49
  def config_file_exists?
47
- File.exist? config_pathname
48
- end
49
-
50
- def config_pathname
51
- @config_pathname || File.expand_path('.tinyci.yml', @working_dir)
50
+ File.exist? config_path
52
51
  end
53
-
52
+
54
53
  def config_content
55
54
  @config_content ||= begin
56
- config = YAML.safe_load(File.read(config_pathname))
55
+ config = YAML.safe_load(File.read(config_path))
57
56
  transformed_config = ConfigTransformer.new(config).transform!
58
57
  symbolize(transformed_config).freeze
59
58
  end
60
59
  end
61
60
  end
62
-
61
+
63
62
  # Error raised when the config file cannot be found
64
63
  class ConfigMissingError < StandardError; end
65
64
  end
@@ -1,61 +1,59 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TinyCI
2
4
  # Transforms the configuration format from the condensed format to the
3
5
  # more verbose format accepted by the rest of the system
4
6
  class ConfigTransformer
5
-
6
7
  # Constructor
7
- #
8
+ #
8
9
  # @param [Hash] input The configuration object, in the condensed format
9
10
  def initialize(input)
10
11
  @input = input
11
12
  end
12
-
13
+
13
14
  # Transforms the config object
14
- #
15
+ #
15
16
  # @return [Hash] The config object in the verbose form
16
17
  def transform!
17
- @input.inject({}) do |acc, (key, value)|
18
+ @input.each_with_object({}) do |(key, value), acc|
18
19
  method_name = "transform_#{key}"
19
-
20
+
20
21
  if respond_to? method_name, true
21
-
22
22
  acc.merge! send(method_name, value)
23
23
  else
24
24
  acc[key] = value
25
25
  end
26
-
27
- acc
28
26
  end
29
27
  end
30
-
28
+
31
29
  private
32
-
30
+
33
31
  def transform_build(value)
34
32
  {
35
33
  builder: {
36
- :class => "ScriptBuilder",
34
+ class: 'ScriptBuilder',
37
35
  config: {
38
36
  command: value
39
37
  }
40
38
  }
41
39
  }
42
40
  end
43
-
41
+
44
42
  def transform_test(value)
45
43
  {
46
44
  tester: {
47
- :class => "ScriptTester",
45
+ class: 'ScriptTester',
48
46
  config: {
49
47
  command: value
50
48
  }
51
49
  }
52
50
  }
53
51
  end
54
-
52
+
55
53
  def transform_hooks(value)
56
54
  {
57
55
  hooker: {
58
- :class => "ScriptHooker",
56
+ class: 'ScriptHooker',
59
57
  config: value
60
58
  }
61
59
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/subprocesses'
2
4
  require 'tinyci/logging'
3
5
  require 'ostruct'
@@ -5,33 +7,33 @@ require 'erb'
5
7
 
6
8
  module TinyCI
7
9
  # Parent class for Builder and Tester classes
8
- #
10
+ #
9
11
  # @abstract
10
12
  class Executor
11
13
  include Subprocesses
12
14
  include Logging
13
-
15
+
14
16
  # Returns a new instance of the executor.
15
- #
16
- # @param config [Hash] Configuration hash, typically taken from relevant key in the {Config} object.
17
- # @param logger [Logger] Logger object
18
- def initialize(config, logger: nil)
17
+ #
18
+ # @param config [Hash] Configuration hash, typically taken
19
+ # from relevant key in the {Config} object.
20
+ def initialize(config)
19
21
  @config = config
20
- @logger = logger
22
+ @logger = config[:logger]
21
23
  end
22
-
24
+
23
25
  def command
24
26
  ['/bin/sh', '-c', "'#{interpolate(@config[:command])}'"]
25
27
  end
26
-
28
+
27
29
  private
28
-
30
+
29
31
  def interpolate(command)
30
32
  erb = ERB.new command
31
-
33
+
32
34
  erb.result(erb_scope)
33
35
  end
34
-
36
+
35
37
  def template_vars
36
38
  OpenStruct.new(
37
39
  commit: @config[:commit],
@@ -1,16 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/subprocesses'
4
+ require 'git_clone_url'
2
5
 
3
6
  module TinyCI
4
-
5
7
  # Methods for dealing with git repos.
6
8
  module GitUtils
7
-
8
9
  # Returns the absolute path to the root of the current git directory
9
- #
10
+ #
10
11
  # @return [String] the path
11
12
  def repo_root
12
13
  return git_directory_path if inside_bare_repo?
13
-
14
+
14
15
  if inside_git_directory?
15
16
  File.expand_path('..', git_directory_path)
16
17
  elsif inside_work_tree?
@@ -19,48 +20,116 @@ module TinyCI
19
20
  raise 'not in git directory or work tree!?'
20
21
  end
21
22
  end
22
-
23
+
23
24
  # Are we currently under a git repo?
24
25
  def inside_git_directory?
25
26
  execute(git_cmd('rev-parse', '--is-inside-git-dir')) == 'true'
26
27
  end
27
-
28
+
28
29
  # Are we under a bare repo?
29
30
  def inside_bare_repo?
30
31
  execute(git_cmd('rev-parse', '--is-bare-repository')) == 'true'
31
32
  end
32
-
33
+
33
34
  # Are we currently under a git work tree?
34
35
  def inside_work_tree?
35
36
  execute(git_cmd('rev-parse', '--is-inside-work-tree')) == 'true'
36
37
  end
37
-
38
+
38
39
  # Are we currently under a repo in any sense?
39
40
  def inside_repository?
40
- execute(git_cmd('rev-parse', '--is-inside-work-tree', '--is-inside-git-dir')).split.any? {|s| s == 'true'}
41
+ cmd = git_cmd('rev-parse', '--is-inside-work-tree', '--is-inside-git-dir')
42
+ execute(cmd).split.any? { |s| s == 'true' }
41
43
  end
42
-
44
+
43
45
  # Returns the absolute path to the .git directory
44
46
  def git_directory_path
45
- File.expand_path(execute(git_cmd('rev-parse', '--git-dir')), defined?(@working_dir) ? @working_dir : nil)
47
+ base = defined?(@working_dir) ? @working_dir.to_s : nil
48
+
49
+ File.expand_path(execute(git_cmd('rev-parse', '--git-dir')), base)
50
+ end
51
+
52
+ # Does the specified file exist in the repo at the specified revision
53
+ def file_exists_in_git?(path, ref = @commit)
54
+ cmd = git_cmd('cat-file', '-e', "#{ref}:#{path}")
55
+
56
+ execute_and_return_status(cmd).success?
57
+ end
58
+
59
+ # Does the given sha exist in this repo's history?
60
+ def commit_exists?(commit = @commit)
61
+ cmd = git_cmd('cat-file', '-e', commit)
62
+
63
+ execute_and_return_status(cmd).success?
64
+ end
65
+
66
+ # Does the repo have a commit matching the given name?
67
+ def remote_exists?(remote = @remote)
68
+ execute(git_cmd('remote')).split("\n").include? remote
69
+ end
70
+
71
+ # Get the url for a given remote
72
+ # Alias for #push_url
73
+ def remote_url(remote = @remote)
74
+ push_url remote
46
75
  end
47
-
76
+
77
+ # Does the given remote point to github?
78
+ def github_remote?(remote = @remote)
79
+ remote_url(remote).host == 'github.com'
80
+ end
81
+
82
+ # Does the given remote point to an ssh url?
83
+ def ssh_remote?(remote = @remote)
84
+ remote_url(remote).is_a? URI::SshGit::Generic
85
+ end
86
+
87
+ # Return the upstream remote for the current branch
88
+ def current_tracking_remote
89
+ full = execute git_cmd 'rev-parse', '--symbolic-full-name', '--abbrev-ref', '@{push}'
90
+ full.split('/')[0]
91
+ rescue TinyCI::Subprocesses::SubprocessError => e
92
+ log_error 'Current branch does not have an upstream remote' if e.status.exitstatus == 128
93
+ end
94
+
95
+ # The current HEAD branch
96
+ def current_branch
97
+ execute git_cmd 'rev-parse', '--abbrev-ref', 'HEAD'
98
+ end
99
+
100
+ # The push url for the specified remote, parsed into a `URI` object
101
+ def push_url(remote = @remote)
102
+ url = raw_push_url(remote)
103
+ GitCloneUrl.parse url
104
+ rescue URI::InvalidComponentError
105
+ URI.parse url
106
+ end
107
+
108
+ # Parse the commit time from git
109
+ def time
110
+ @time ||= Time.at execute(git_cmd('show', '-s', '--format=%at', @commit)).to_i
111
+ end
112
+
48
113
  # Execute a git command, passing the -C parameter if the current object has
49
114
  # the working_directory instance var set
50
115
  def git_cmd(*args)
51
116
  cmd = ['git']
52
- cmd += ['-C', @working_dir] if defined?(@working_dir) && !@working_dir.nil?
117
+ cmd += ['-C', @working_dir.to_s] if defined?(@working_dir) && !@working_dir.nil?
53
118
  cmd += args
54
-
119
+
55
120
  cmd
56
121
  end
57
-
58
- private
59
-
122
+
123
+ # Get push url as a string. Not intended to be called directly, instead call {#push_url}
124
+ def raw_push_url(remote = @remote)
125
+ @push_urls ||= {}
126
+ @push_urls[remote] ||= execute git_cmd 'remote', 'get-url', '--push', remote
127
+ end
128
+
60
129
  def self.included(base)
61
130
  base.include Subprocesses
62
131
  end
63
-
132
+
64
133
  def self.extended(base)
65
134
  base.extend Subprocesses
66
135
  end
@@ -1,60 +1,59 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tinyci/executor'
2
4
 
3
5
  module TinyCI
4
6
  module Hookers
5
7
  class ScriptHooker < TinyCI::Executor
6
8
  # All the hooks
7
- HOOKS = %w{
9
+ HOOKS = %w[
8
10
  before_build
9
-
11
+
10
12
  after_build_success
11
13
  after_build_failure
12
-
14
+
13
15
  after_build
14
-
16
+
15
17
  before_test
16
-
18
+
17
19
  after_test_success
18
20
  after_test_failure
19
-
21
+
20
22
  after_test
21
-
23
+
22
24
  after_all
23
- }
24
-
25
+ ].freeze
26
+
25
27
  # Those hooks that will halt exectution if they fail
26
- BLOCKING_HOOKS = %w{
28
+ BLOCKING_HOOKS = %w[
27
29
  before_build
28
30
  before_test
29
- }
30
-
31
+ ].freeze
32
+
31
33
  HOOKS.each do |hook|
32
- define_method hook+"_present?" do
34
+ define_method hook + '_present?' do
33
35
  @config.key? hook.to_sym
34
36
  end
35
-
36
- define_method hook+"!" do
37
+
38
+ define_method hook + '!' do
37
39
  return unless send("#{hook}_present?")
38
-
40
+
39
41
  log_info "executing #{hook} hook..."
40
42
  begin
41
43
  execute_stream(script_location(hook), label: hook, pwd: @config[:export])
42
-
44
+
43
45
  return true
44
46
  rescue SubprocessError => e
45
- if BLOCKING_HOOKS.include? hook
46
- raise e if ENV['TINYCI_ENV'] == 'test'
47
-
48
- log_error e
49
-
50
- return false
51
- else
52
- return true
53
- end
47
+ return true unless BLOCKING_HOOKS.include? hook
48
+
49
+ raise e if ENV['TINYCI_ENV'] == 'test'
50
+
51
+ log_error e
52
+ return false
54
53
  end
55
54
  end
56
55
  end
57
-
56
+
58
57
  def script_location(hook)
59
58
  ['/bin/sh', '-c', "'#{interpolate(@config[hook.to_sym])}'"]
60
59
  end