pushapp 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +25 -0
- data/Rakefile +9 -0
- data/bin/pushapp +6 -0
- data/lib/pushapp.rb +34 -0
- data/lib/pushapp/cli.rb +70 -0
- data/lib/pushapp/commands.rb +106 -0
- data/lib/pushapp/config.rb +86 -0
- data/lib/pushapp/git.rb +57 -0
- data/lib/pushapp/hook.rb +127 -0
- data/lib/pushapp/logger.rb +34 -0
- data/lib/pushapp/pipe.rb +35 -0
- data/lib/pushapp/remote.rb +141 -0
- data/lib/pushapp/tasks/base.rb +27 -0
- data/lib/pushapp/tasks/foreman_export.rb +14 -0
- data/lib/pushapp/tasks/rake.rb +15 -0
- data/lib/pushapp/tasks/script.rb +31 -0
- data/lib/pushapp/version.rb +3 -0
- data/pushapp.gemspec +26 -0
- data/templates/config.rb.erb +30 -0
- data/templates/hook/base.erb +4 -0
- data/templates/hook/bundler.erb +3 -0
- data/templates/hook/git-reset.erb +10 -0
- data/templates/hook/setup.erb +11 -0
- data/templates/hook/tasks.erb +1 -0
- data/test/fixtures/empty_config.rb +0 -0
- data/test/pushapp/cli_test.rb +27 -0
- data/test/pushapp/config_test.rb +82 -0
- data/test/pushapp/git_test.rb +13 -0
- data/test/pushapp/pipe_test.rb +29 -0
- data/test/pushapp_test.rb +26 -0
- data/test/test_helper.rb +6 -0
- metadata +143 -0
data/lib/pushapp/hook.rb
ADDED
@@ -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
|
data/lib/pushapp/pipe.rb
ADDED
@@ -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,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
|
data/pushapp.gemspec
ADDED
@@ -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
|