git-runner 0.0.2 → 0.0.3

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.
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  GitRunner is a ruby framework to implement and run tasks after code has been pushed to a Git repository. It works by invoking `git-runner` through `hooks/post-update` in your remote Git repository.
4
4
 
5
+ [Demonstration Video, Using git-runner to perform automatic deploys](http://ascii.io/a/1349)
6
+
5
7
  Configuration for GitRunner is read from a pre-determined file within the repository (at this time, that file is config/deploy.rb but this will soon be configurable). Any instructions detected are then processed and ran.
6
8
 
7
9
  Instructions are contained within `lib/git-runner/instructions`, though soon these will be extracted out to separate gems, leaving only the core instructions present. Currently the only *real* instruction that users will want to run is Deploy, which will checkout your code, run bundler and perform a cap deploy (with multistage support).
@@ -14,10 +16,19 @@ Instructions are contained within `lib/git-runner/instructions`, though soon the
14
16
 
15
17
  Symlink `hooks/post-update` to `git-runner`, or if `post-update` is already in use modify it to run `git-runner` with the arguments supplied to the hook.
16
18
 
19
+ ## Configuration
20
+
21
+ Configuration can be overwritten through a YAML file at either `/etc/git-runner.yml` or `$HOME/.git-runner.yml`. The current configuration options are:
22
+
23
+ * **git_executable** (default: '/usr/bin/env git')
24
+ * **instruction_file** (default: 'config/deploy.rb')
25
+ * **instruction_prefix** (default: '# GitRunner:')
26
+ * **tmp_directory** (default: '/tmp/git-runner')
27
+
17
28
  ## TODO
18
29
 
19
- * Allow file based configuration (currently configuration is hard-coded)
20
30
  * Extract our non-core instruction functionality into individual gems (e.g. the Deploy instruction)
31
+ * Format README.md nicely
21
32
 
22
33
  ## Contributing
23
34
 
data/git-runner.gemspec CHANGED
@@ -1,7 +1,11 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/git-runner/version', __FILE__)
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'git-runner/version'
3
5
 
4
6
  Gem::Specification.new do |gem|
7
+ gem.name = "git-runner"
8
+ gem.version = GitRunner::VERSION
5
9
  gem.authors = ["James Brooks"]
6
10
  gem.email = ["james@jamesbrooks.net"]
7
11
  gem.summary = "Ruby framework to run tasks after code has been pushed to a Git repository."
@@ -10,9 +14,7 @@ Gem::Specification.new do |gem|
10
14
 
11
15
  gem.files = `git ls-files`.split($\)
12
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
- gem.name = "git-runner"
14
17
  gem.require_paths = ["lib"]
15
- gem.version = GitRunner::VERSION
16
18
 
17
19
  gem.add_dependency 'session'
18
20
  end
data/lib/git-runner.rb CHANGED
@@ -1,17 +1,11 @@
1
- require "git-runner/version"
2
1
  require "git-runner/base"
2
+ require "git-runner/branch"
3
3
  require "git-runner/command"
4
4
  require "git-runner/configuration"
5
- require "git-runner/branch"
5
+ require "git-runner/hooks"
6
6
  require "git-runner/instruction"
7
7
  require "git-runner/text"
8
+ require "git-runner/version"
8
9
 
9
10
  module GitRunner
10
- DEFAULT_CONFIGURATION = {
11
- :git_executable => '/usr/bin/env git',
12
- :instruction_file => 'config/deploy.rb',
13
- :instruction_prefix => '# GitRunner:',
14
- :tmp_directory => '/tmp/git-runner',
15
- :rvm_ruby_version => '1.9.3'
16
- }
17
11
  end
@@ -24,38 +24,50 @@ module GitRunner
24
24
  end
25
25
 
26
26
 
27
- rescue GitRunner::Instruction::InstructionHalted => ex
27
+ rescue GitRunner::Instruction::Failure => ex
28
+ Text.new_line
28
29
  Text.out("Stopping runner, no further instructions will be performed\n")
30
+ Text.new_line
29
31
 
30
32
  rescue GitRunner::Command::Failure => ex
31
- Text.out("\n")
33
+ Text.new_line
32
34
  Text.out(Text.red("\u2716 Command failed: " + Text.red(ex.result.command)), :heading)
33
35
 
34
- # Write error log
35
- log_directory = File.join(Configuration.tmp_directory, 'logs')
36
- error_log = File.join(log_directory, Time.now.strftime("%Y%m%d%H%M%S") + '-error.log')
37
-
38
- Dir.mkdir(log_directory) unless Dir.exists?(log_directory)
39
- File.open(error_log, 'w') do |file|
40
- Command.history.each do |result|
41
- file << "Status: #{result.status}; Command: #{result.command}\n"
42
- file << result.out
43
- file << "\n--------------------\n"
44
- end
36
+ write_error_log do |log|
37
+ log << Command.history_to_s
45
38
  end
46
39
 
47
- Text.out("An error log has been created: #{error_log}")
48
40
  Text.new_line
49
41
 
50
42
  rescue Exception => ex
51
43
  Text.new_line
52
44
  Text.out(Text.red("\u2716 Unknown exception occured: #{ex}"), :heading)
53
- Text.out(ex.backtrace.join("\n").gsub("\n", "\n#{''.ljust(10)}"))
45
+
46
+ write_error_log do |log|
47
+ log << Command.history_to_s
48
+ log << ex.message + "\n"
49
+ log << ex.backtrace.map { |line| " #{line}\n" }.join
50
+ end
51
+
54
52
  Text.new_line
55
53
 
56
54
  ensure
57
55
  Text.finish
58
56
  end
59
57
  end
58
+
59
+
60
+ private
61
+ def write_error_log
62
+ log_directory = File.join(Configuration.tmp_directory, 'logs')
63
+ error_log = File.join(log_directory, Time.now.strftime("%Y%m%d%H%M%S") + '-error.log')
64
+
65
+ Command.execute("mkdir -p #{log_directory}") unless Dir.exists?(log_directory)
66
+
67
+ File.open(error_log, 'w') { |file| yield(file) }
68
+
69
+ Text.out("An error log has been created: #{error_log}")
70
+ error_log
71
+ end
60
72
  end
61
73
  end
@@ -24,6 +24,12 @@ module GitRunner
24
24
  @history ||= []
25
25
  end
26
26
 
27
+ def history_to_s
28
+ history.map do |result|
29
+ "Status: #{result.status}; Command: #{result.command}\n#{result.out}\n--------------------\n"
30
+ end.join
31
+ end
32
+
27
33
 
28
34
  private
29
35
  def session
@@ -2,19 +2,45 @@ module GitRunner
2
2
  module Configuration
3
3
  extend self
4
4
 
5
+ FILE_LOCATIONS = %w(/etc/git-runner.yml ~/.git-runner.yml)
6
+
7
+ DEFAULTS = {
8
+ 'git_executable' => '/usr/bin/env git',
9
+ 'instruction_file' => 'config/deploy.rb',
10
+ 'instruction_prefix' => '# GitRunner:',
11
+ 'tmp_directory' => '/tmp/git-runner',
12
+ }
13
+
14
+
5
15
  def __attributes
6
- # TODO: Load configuration from /etc/git-runner.conf
7
- @attributes ||= DEFAULT_CONFIGURATION.dup
16
+ @attributes ||= __load_attributes
8
17
  end
9
18
 
10
19
  def method_missing(method, *args, &block)
11
- if __attributes.keys.include?(method)
12
- __attributes[method]
20
+ method_s = method.to_s
21
+
22
+ if __attributes.keys.include?(method_s)
23
+ __attributes[method_s]
13
24
  elsif method.to_s.end_with?('=')
14
- __attributes[method.to_s[0..-2].to_sym] = args[0]
25
+ __attributes[method_s[0..-2]] = args[0]
15
26
  else
16
27
  super
17
28
  end
18
29
  end
30
+
31
+
32
+ private
33
+ def __load_attributes
34
+ attrs = DEFAULTS.dup
35
+
36
+ FILE_LOCATIONS.each do |location|
37
+ begin
38
+ path = File.expand_path(location)
39
+ attrs.merge!(YAML.load_file(path)) if File.exist?(path)
40
+ end
41
+ end
42
+
43
+ attrs
44
+ end
19
45
  end
20
46
  end
@@ -0,0 +1,19 @@
1
+ module GitRunner
2
+ module Hooks
3
+ extend self
4
+
5
+
6
+ def registrations
7
+ @registrations ||= Hash.new { |hash, key| hash[key] = [] }
8
+ end
9
+
10
+ def register(name, object, method)
11
+ registrations[name] << [ object, method ]
12
+ end
13
+
14
+ def fire(name)
15
+ return unless registrations.keys.include?(name)
16
+ registrations[name].each { |object, method| object.send(method) }
17
+ end
18
+ end
19
+ end
@@ -2,22 +2,16 @@ Dir[File.join(File.dirname(__FILE__), 'instructions', '*.rb')].each { |file| req
2
2
 
3
3
  module GitRunner
4
4
  class Instruction
5
- class InstructionHalted < StandardError ; end
6
-
7
-
8
5
  def self.new(name, args={})
9
6
  begin
10
7
  const_get(name).new(args)
11
8
 
12
9
  rescue NameError => e
13
- # Return a console message instruction to inform the user
14
- instruction = ConsoleMessage.new(Text.red("\u2716 Instruction not found: #{name}"))
15
- instruction.opts = {
16
- :halt => true,
10
+ # Display message for missing instruction, halt further execution
11
+ Display.new(Text.red("\u2716 Instruction not found: #{name}"), {
12
+ :fail => true,
17
13
  :priority => true
18
- }
19
-
20
- instruction
14
+ })
21
15
  end
22
16
  end
23
17
 
@@ -26,3 +20,10 @@ module GitRunner
26
20
  end
27
21
  end
28
22
  end
23
+
24
+ module GitRunner
25
+ class Instruction
26
+ class Failure < StandardError
27
+ end
28
+ end
29
+ end
@@ -4,8 +4,9 @@ module GitRunner
4
4
  attr_accessor :branch, :args, :opts
5
5
 
6
6
 
7
- def initialize(args)
7
+ def initialize(args, opts={})
8
8
  self.args = args
9
+ self.opts = opts
9
10
  end
10
11
 
11
12
  def perform
@@ -17,8 +18,8 @@ module GitRunner
17
18
  perform
18
19
  Text.new_line
19
20
 
20
- if halt?
21
- raise InstructionHalted.new(self)
21
+ if opts[:fail]
22
+ raise Failure.new(self)
22
23
  end
23
24
  end
24
25
  end
@@ -27,17 +28,8 @@ module GitRunner
27
28
  true
28
29
  end
29
30
 
30
- def halt!
31
- self.opts ||= {}
32
- self.opts[:halt] = true
33
- end
34
-
35
- def halt?
36
- self.opts && !!self.opts[:halt]
37
- end
38
-
39
- def priority?
40
- self.opts && !!self.opts[:priority]
31
+ def fail!
32
+ raise Failure.new(self)
41
33
  end
42
34
 
43
35
 
@@ -1,6 +1,6 @@
1
1
  module GitRunner
2
2
  class Instruction
3
- class ConsoleMessage < Base
3
+ class Display < Base
4
4
  def perform
5
5
  Text.out(message, :heading)
6
6
  end
@@ -1,3 +1,3 @@
1
1
  module GitRunner
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: git-runner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-05 00:00:00.000000000 Z
12
+ date: 2012-10-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: session
@@ -49,10 +49,10 @@ files:
49
49
  - lib/git-runner/branch.rb
50
50
  - lib/git-runner/command.rb
51
51
  - lib/git-runner/configuration.rb
52
+ - lib/git-runner/hooks.rb
52
53
  - lib/git-runner/instruction.rb
53
54
  - lib/git-runner/instructions/base.rb
54
- - lib/git-runner/instructions/console_message.rb
55
- - lib/git-runner/instructions/deploy.rb
55
+ - lib/git-runner/instructions/display.rb
56
56
  - lib/git-runner/text.rb
57
57
  - lib/git-runner/version.rb
58
58
  homepage: https://github.com/JamesBrooks/git-runner
@@ -1,111 +0,0 @@
1
- require 'digest'
2
-
3
- module GitRunner
4
- class Instruction
5
-
6
- # Performs deployments using capistrano (cap deploy)
7
- class Deploy < Base
8
- attr_accessor :clone_directory
9
-
10
-
11
- def should_run?
12
- branches.empty? || branches.include?(branch.name)
13
- end
14
-
15
- def perform
16
- start_time = Time.now
17
-
18
- Text.out(Text.green("Performing Deploy (#{environment_from_branch(branch)})"), :heading)
19
-
20
- checkout_branch
21
-
22
- if missing_capfile?
23
- Text.out(Text.red("Missing Capfile, unable to complete deploy."))
24
- halt! && return
25
- end
26
-
27
- prepare_deploy_environment
28
- perform_deploy
29
- cleanup_deploy_environment
30
-
31
- end_time = Time.now
32
-
33
- Text.out(Text.green("\u2714 Deploy successful, completed in #{(end_time - start_time).ceil} seconds"))
34
- end
35
-
36
- def branches
37
- args.split(/\s+/)
38
- end
39
-
40
- def rvm_string
41
- "#{Configuration.rvm_ruby_version}@git-runner-#{branch.repository_name}-#{branch.name}"
42
- end
43
-
44
- def missing_capfile?
45
- !File.exists?("#{clone_directory}/Capfile")
46
- end
47
-
48
- def uses_bundler?
49
- File.exists?("#{clone_directory}/Gemfile")
50
- end
51
-
52
- def multistage?
53
- result = execute("grep -e 'require.*capistrano.*multistage' #{clone_directory}/#{Configuration.instruction_file} || true")
54
- !result.empty?
55
- end
56
-
57
- def checkout_branch
58
- timestamp = Time.now.strftime("%Y%m%d%H%M%S")
59
- self.clone_directory = File.join(Configuration.tmp_directory, "#{branch.repository_name}-#{environment_from_branch(branch)}-#{timestamp}")
60
-
61
- Text.out("Checking out #{branch.name} to #{clone_directory}")
62
-
63
- execute(
64
- "mkdir -p #{clone_directory}",
65
- "git clone --depth=1 --branch=#{branch.name} file://#{branch.repository_path} #{clone_directory}"
66
- )
67
- end
68
-
69
- def prepare_deploy_environment
70
- Text.out("Preparing deploy environment")
71
-
72
- if uses_bundler?
73
- execute(
74
- "cd #{clone_directory}",
75
- "bundle install --path=#{File.join(Configuration.tmp_directory, '.gems')}"
76
- )
77
- end
78
- end
79
-
80
- def perform_deploy
81
- cap_deploy_command = if multistage?
82
- Text.out("Deploying application (multistage detected)")
83
- "cap #{environment_from_branch(branch)} deploy"
84
- else
85
- Text.out("Deploying application")
86
- "cap deploy"
87
- end
88
-
89
- execute(
90
- "cd #{clone_directory}",
91
- cap_deploy_command
92
- )
93
- end
94
-
95
- def cleanup_deploy_environment
96
- Text.out("Cleaning deploy environment")
97
- execute("rm -rf #{clone_directory}")
98
- end
99
-
100
-
101
- private
102
- def environment_from_branch(branch)
103
- if branch.name == 'master'
104
- 'production'
105
- else
106
- branch.name
107
- end
108
- end
109
- end
110
- end
111
- end