pushapp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ require 'erb'
2
+
3
+ module Pushapp
4
+ class Hook
5
+
6
+ ANSI_COLORS = {
7
+ :reset => 0,
8
+ :black => 30,
9
+ :red => 31,
10
+ :green => 32,
11
+ :yellow => 33,
12
+ :blue => 34,
13
+ :magenta => 35,
14
+ :cyan => 36,
15
+ :white => 37,
16
+ :bright_black => 30,
17
+ :bright_red => 31,
18
+ :bright_green => 32,
19
+ :bright_yellow => 33,
20
+ :bright_blue => 34,
21
+ :bright_magenta => 35,
22
+ :bright_cyan => 36,
23
+ :bright_white => 37,
24
+ }
25
+
26
+ HOOK_COLORS = %w( cyan yellow green magenta blue bright_cyan bright_yellow
27
+ bright_magenta bright_blue red bright_green bright_red )
28
+
29
+ attr_accessor :remote
30
+
31
+ def initialize(remote)
32
+ @remote = remote
33
+ @config = remote.config
34
+ @options = remote.options
35
+
36
+ @remote_index = @config.remotes.index(@remote)
37
+ @remote_color = ANSI_COLORS[HOOK_COLORS[@remote_index % HOOK_COLORS.length].to_sym]
38
+ @echo_color = ANSI_COLORS[:bright_green]
39
+ @error_color = ANSI_COLORS[:bright_red]
40
+ end
41
+
42
+ def setup
43
+ prepare_hook
44
+ deploy_hook
45
+ end
46
+
47
+ private
48
+
49
+ def padded_max_length
50
+ if @remote.group
51
+ @config.remotes_grouped_by(@remote.group).map {|r| r.full_name}.max.length
52
+ else
53
+ @remote.name.length
54
+ end
55
+ end
56
+
57
+ def padded_name
58
+ @padden_name ||= @remote.full_name.ljust([padded_max_length, "remote:".length].max)
59
+ end
60
+
61
+ def prefix
62
+ "\e[1G\e[#{@remote_color}m#{padded_name} |\e[0m "
63
+ end
64
+
65
+ def pre
66
+ "#\{ENV[\"PAP_PRE\"]\}"
67
+ end
68
+
69
+ def colorize
70
+ %{2>&1 | ruby -pe '$_="#{pre}#\{$_\}"'}
71
+ end
72
+
73
+ def info message
74
+ %{ruby -e 'puts "#{pre}\e[#{@echo_color}m\e[1m[pushapp]\e[0m\e[#{@echo_color}m INFO: #{message}\e[0m"'}
75
+ end
76
+
77
+ def load_template(template_name)
78
+ ::ERB.new(File.read(find_template(template_name))).result(binding)
79
+ end
80
+
81
+ def find_template(template_name)
82
+ "#{Pushapp::TEMPLATE_ROOT}/#{template_name}.erb"
83
+ end
84
+
85
+ def prepare_hook
86
+ # info "Generating and uploading post-receive hook for #{@remote.name}"
87
+ hook = generate_hook
88
+ write hook
89
+ end
90
+
91
+ def deploy_hook
92
+ # debug "Copying hook for #{@remote.name} to #{@remote.location}"
93
+ copy_hook
94
+ set_hook_permissions
95
+ end
96
+
97
+ def generate_hook
98
+ load_template 'hook/base'
99
+ end
100
+
101
+ def write(hook)
102
+ File.open(Pushapp::TMP_HOOK, "wb") do |f|
103
+ f.puts hook
104
+ end
105
+ end
106
+
107
+ def set_hook_permissions
108
+ @remote.run "#{make_hook_executable}"
109
+ end
110
+
111
+ def copy_hook
112
+ # debug "Making hook executable"
113
+ # TODO: handle missing user?
114
+ if @remote.host
115
+ Pushapp::Pipe.run "scp #{Pushapp::TMP_HOOK} #{@remote.user}@#{@remote.host}:#{@remote.path}/.git/hooks/post-receive"
116
+ else
117
+ Pushapp::Pipe.run "cp #{Pushapp::TMP_HOOK} #{@remote.path}/.git/hooks/post-receive"
118
+ end
119
+ end
120
+
121
+ def make_hook_executable
122
+ # debug "Making hook executable"
123
+ "chmod +x #{@remote.path}/.git/hooks/post-receive"
124
+ end
125
+ end
126
+ end
127
+
@@ -0,0 +1,34 @@
1
+ require 'logger'
2
+ require 'thor'
3
+
4
+ module Pushapp
5
+ class Logger < ::Logger
6
+ attr_reader :shell
7
+
8
+ def initialize
9
+ super($stdout)
10
+ @shell = Thor::Shell::Color.new
11
+
12
+ self.progname = '[pushapp]'
13
+
14
+ # DEBUG INFO WARN ERROR FATAL UNKNOWN
15
+ @colors = {
16
+ 'DEBUG' => :blue,
17
+ 'INFO' => :green,
18
+ 'WARN' => :magenta,
19
+ 'ERROR' => :red,
20
+ 'FATAL' => :red,
21
+ 'UNKOWN' => :black
22
+ }
23
+
24
+ self.formatter = proc { |severity, datetime, progname, msg|
25
+ color = @colors[severity]
26
+
27
+ progname = @shell.set_color progname, color, :bold
28
+ sev = @shell.set_color "#{severity}:", color
29
+ msg = @shell.set_color msg, color
30
+ "#{progname} #{sev} #{msg}\n"
31
+ }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ require 'open3'
2
+ require 'stringio'
3
+
4
+ module Pushapp
5
+ class Pipe
6
+
7
+ def self.run(command)
8
+ case command
9
+ when String
10
+ pipe(command)
11
+ when Pushapp::Tasks::Script
12
+ pipe(command.cmd)
13
+ when Pushapp::Tasks::Base
14
+ command.run
15
+ when Array
16
+ pipe(command)
17
+ else
18
+ raise "Unknown command format: '#{command.inspect}'"
19
+ end
20
+ end
21
+
22
+ def self.capture(cmd)
23
+ output, s = Open3.capture2e(cmd)
24
+ raise "Failed with status #{s.exitstatus}: #{cmd.inspect}" unless s.success?
25
+ output
26
+ end
27
+
28
+ private
29
+
30
+ def self.pipe cmd, stdin=$stdin, stdout=$stdout
31
+ s = Open3.pipeline(cmd, :in => stdin, :out => stdout).last
32
+ raise "Failed with status #{s.exitstatus}: #{cmd.inspect}" unless s.success?
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,141 @@
1
+ require 'pushapp/tasks/rake'
2
+ require 'shellwords'
3
+
4
+ class Pushapp::Remote
5
+
6
+ attr_reader :config
7
+ attr_reader :tasks
8
+ attr_reader :name
9
+ attr_reader :location
10
+ attr_reader :options
11
+ attr_reader :group
12
+
13
+ def initialize(name, group, location, config, options = {})
14
+ @name = name
15
+ @location = location
16
+ @config = config
17
+ @options = options
18
+ @group = group
19
+ @tasks = Hash.new { |hash, key| hash[key] = [] }
20
+ end
21
+
22
+ def full_name
23
+ [group, name].compact.join('-')
24
+ end
25
+
26
+ def rake(task_name, task_options={})
27
+ tasks[@event] << Pushapp::Tasks::Rake.new(task_name, merge_options(task_options))
28
+ end
29
+
30
+ def script(script_name, script_options={})
31
+ tasks[@event] << Pushapp::Tasks::Script.new(script_name, merge_options(script_options))
32
+ end
33
+
34
+ def task(task_name, task_options={})
35
+ tasks[@event] << config.known_task(task_name).new(merge_options(task_options).merge(task_name: task_name))
36
+ end
37
+
38
+ def on event, &block
39
+ @event = event.to_s
40
+ instance_eval(&block) if block_given?
41
+ end
42
+
43
+ def tasks_on event
44
+ tasks[event.to_s]
45
+ end
46
+
47
+ def path
48
+ if host
49
+ @location.match(/:(.*)$/)[1]
50
+ else
51
+ @location
52
+ end
53
+ end
54
+
55
+ def host
56
+ host = @location.match(/@(.*):/)
57
+ host[1] unless host.nil?
58
+ end
59
+
60
+ def user
61
+ user = @location.match(/(.*)@/)
62
+ user[1] unless user.nil?
63
+ end
64
+
65
+ def ssh!
66
+ exec "cd #{path} && #{shell_env} $SHELL -l"
67
+ end
68
+
69
+ #
70
+ # Set up Repositories and Hook
71
+ #
72
+ def setup!
73
+ run "#{init_repository} && #{setup_repository}"
74
+ end
75
+
76
+ #
77
+ # Update git remote and hook
78
+ #
79
+ def update!
80
+ Pushapp::Hook.new(self).setup
81
+ end
82
+
83
+ def exec cmd
84
+ if host
85
+ Kernel.exec "ssh -t #{user}@#{host} '#{cmd}'"
86
+ else
87
+ Kernel.exec cmd
88
+ end
89
+ end
90
+
91
+ def env_run cmd
92
+ if host
93
+ Pushapp::Pipe.run "ssh #{user}@#{host} 'cd #{path} && #{shell_env} $SHELL -l -c \"#{cmd}\"'"
94
+ else
95
+ Bundler.with_original_env do
96
+ Pushapp::Pipe.run "cd #{path} && #{shell_env} #{cmd}"
97
+ end
98
+ end
99
+ end
100
+
101
+ def run cmd
102
+ if host
103
+ Pushapp::Pipe.run "ssh #{user}@#{host} '#{cmd}'"
104
+ else
105
+ Bundler.with_original_env do
106
+ Pushapp::Pipe.run cmd
107
+ end
108
+ end
109
+ end
110
+
111
+ def env
112
+ (options[:env] || {})
113
+ end
114
+
115
+ private
116
+
117
+ #
118
+ # Initialize an empty repository
119
+ #
120
+ def init_repository
121
+ # Instead of git init with a path, so it does not fail on older
122
+ # git versions (https://github.com/effkay/blazing/issues/53)
123
+ "mkdir #{path}; cd #{path} && git init"
124
+ end
125
+
126
+ #
127
+ # Allow pushing to currently checked out branch
128
+ #
129
+ def setup_repository
130
+ "cd #{path} && git config receive.denyCurrentBranch ignore"
131
+ end
132
+
133
+ def shell_env
134
+ env.map {|k,v| "#{k}=\"#{Shellwords.escape(v)}\""}.join(" ")
135
+ end
136
+
137
+ def merge_options task_options={}
138
+ Pushapp.rmerge(task_options, options).merge(remote: name)
139
+ end
140
+
141
+ end
@@ -0,0 +1,27 @@
1
+ require 'pushapp/config'
2
+ require 'pushapp/logger'
3
+
4
+ module Pushapp
5
+ module Tasks
6
+ class Base
7
+ attr_reader :options
8
+ attr_reader :logger
9
+
10
+ def initialize options={}
11
+ @options = options
12
+ @logger = Pushapp::Logger.new
13
+ end
14
+
15
+ def run
16
+ end
17
+
18
+ def self.register_as name
19
+ Pushapp::Config.register_task name, self
20
+ end
21
+
22
+ def inspect
23
+ options[:task_name]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ require 'pushapp/tasks/base'
2
+
3
+ module Pushapp
4
+ module Tasks
5
+ class ForemanExport < Base
6
+
7
+ def run
8
+ logger.info 'Forman exporting...'
9
+ end
10
+
11
+ register_as :foreman_export
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ require 'pushapp/tasks/script'
2
+
3
+ module Pushapp
4
+ module Tasks
5
+ class Rake < Script
6
+ def cmd
7
+ [env, "rake #{script}"]
8
+ end
9
+
10
+ def inspect
11
+ "rake: #{script}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ require 'pushapp/tasks/base'
2
+
3
+ module Pushapp
4
+ module Tasks
5
+ class Script < Base
6
+ attr_reader :script
7
+
8
+ def initialize script, options={}
9
+ super(options)
10
+
11
+ @script = script
12
+ end
13
+
14
+ def cmd
15
+ [env, script]
16
+ end
17
+
18
+ def env
19
+ Hash[env_options.map {|k, v| [k.to_s, v.to_s] }]
20
+ end
21
+
22
+ def env_options
23
+ options[:env] || {}
24
+ end
25
+
26
+ def inspect
27
+ "script: #{script}"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Pushapp
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pushapp/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'pushapp'
8
+ spec.version = Pushapp::VERSION
9
+ spec.authors = ["Yury Korolev"]
10
+ spec.email = ["yurykorolev@me.com"]
11
+ spec.description = %q{Push your App}
12
+ spec.summary = %q{Push your App}
13
+ spec.homepage = 'https://github.com/anjlab/pushapp'
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'thor'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.3'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'minitest'
26
+ end