pushapp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5346ed0413dd7cec4958c67f1bfe0593eb84c0b5
|
4
|
+
data.tar.gz: 3ccebfa60836dcf1f6101009ff4354f548540394
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1e310f5df0d17b031878c09f73e8f716c967aca4c0e2d32873ae401b394bc60e091b4d2aa0422bc91699b55382e52a325418a0ce6b6c1aa128a5d71cd98ae6ca
|
7
|
+
data.tar.gz: aed3285137b373fd8a69fa5a5dac8ea7d1916ef9883418972dc88ee9f96a2f7c78cd319d87343627980c721ecd3d2b6c1d7f8e103408f3f9cacd7aed93de21ee
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Yury Korolev
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# pushapp
|
2
|
+
|
3
|
+
Simple heroku like deployment system.
|
4
|
+
|
5
|
+
TODO: notes on blazing
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'pushapp'
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
add ./vendor/bundle to .gitignore
|
16
|
+
|
17
|
+
TODO: Write usage instructions here
|
18
|
+
|
19
|
+
## Contributing
|
20
|
+
|
21
|
+
1. Fork it
|
22
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
23
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
24
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
25
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/pushapp
ADDED
data/lib/pushapp.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'pushapp/version'
|
2
|
+
|
3
|
+
module Pushapp
|
4
|
+
|
5
|
+
autoload :CLI, 'pushapp/cli'
|
6
|
+
autoload :Config, 'pushapp/config'
|
7
|
+
autoload :Commands, 'pushapp/commands'
|
8
|
+
autoload :Pipe, 'pushapp/pipe'
|
9
|
+
autoload :Remote, 'pushapp/remote'
|
10
|
+
autoload :Hook, 'pushapp/hook'
|
11
|
+
autoload :Git, 'pushapp/git'
|
12
|
+
autoload :Logger, 'pushapp/logger'
|
13
|
+
|
14
|
+
module Tasks
|
15
|
+
autoload :Base, 'pushapp/tasks/base'
|
16
|
+
autoload :Script, 'pushapp/tasks/script'
|
17
|
+
autoload :Rake, 'pushapp/tasks/rake'
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.rmerge(a, b)
|
22
|
+
r = {}
|
23
|
+
a ||= {}
|
24
|
+
b ||= {}
|
25
|
+
a = a.merge(b) do |key, oldval, newval|
|
26
|
+
r[key] = (Hash === oldval ? rmerge(oldval, newval) : newval)
|
27
|
+
end
|
28
|
+
a.merge(r)
|
29
|
+
end
|
30
|
+
|
31
|
+
DEFAULT_CONFIG_LOCATION = 'config/pushapp.rb'
|
32
|
+
TEMPLATE_ROOT = File.expand_path(File.dirname(__FILE__) + File.join('/../templates'))
|
33
|
+
TMP_HOOK = '/tmp/post-receive'
|
34
|
+
end
|
data/lib/pushapp/cli.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'pushapp/commands'
|
3
|
+
|
4
|
+
module Pushapp
|
5
|
+
class CLI < Thor
|
6
|
+
|
7
|
+
default_task :help
|
8
|
+
|
9
|
+
desc 'init', 'Generate a pushapp config file'
|
10
|
+
|
11
|
+
def init
|
12
|
+
Pushapp::Commands.run(:init)
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'setup REMOTES', 'Setup group or remote repository/repositories for deployment'
|
16
|
+
|
17
|
+
method_option :file, type: :string, aliases: '-f', banner: 'Specify a configuration file'
|
18
|
+
|
19
|
+
def setup(*remotes)
|
20
|
+
Pushapp::Commands.run(:setup, remotes: remotes, options: options)
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'update-refs', 'Setup remote refs in local .git/config'
|
24
|
+
|
25
|
+
method_option :file, type: :string, aliases: '-f', banner: 'Specify a configuration file'
|
26
|
+
|
27
|
+
def update_refs
|
28
|
+
Pushapp::Commands.run(:update_refs, options: options)
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'update REMOTES', 'Re-Generate and upload hook based on current configuration'
|
32
|
+
|
33
|
+
method_option :file, type: :string, aliases: '-f', banner: 'Specify a configuration file'
|
34
|
+
|
35
|
+
def update(*remotes)
|
36
|
+
Pushapp::Commands.run(:update, remotes: remotes, options: options)
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'remotes', 'List all known remotes'
|
40
|
+
|
41
|
+
method_option :file, type: :string, aliases: '-f', banner: 'Specify a configuration file'
|
42
|
+
|
43
|
+
def remotes
|
44
|
+
Pushapp::Commands.run(:list_remotes, options: options)
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'tasks REMOTES', 'Show tasks list for remote(s). Default: all'
|
48
|
+
|
49
|
+
def tasks(*remotes)
|
50
|
+
Pushapp::Commands.run(:tasks, remotes: remotes, options: options)
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'trigger EVENT REMOTES', 'Triggers event on remote(s)'
|
54
|
+
|
55
|
+
method_option :file, type: :string, aliases: '-f', banner: 'Specify a configuration file'
|
56
|
+
method_option :local, type: :boolean, default: false, aliases: '-l', banner: 'Specify a configuration file'
|
57
|
+
|
58
|
+
def trigger(event, *remotes)
|
59
|
+
Pushapp::Commands.run(:trigger, event: event, remotes: remotes, local: options['local'], options: options)
|
60
|
+
end
|
61
|
+
|
62
|
+
desc 'ssh REMOTE', 'SSH to remote and setup ENV vars.'
|
63
|
+
|
64
|
+
method_option :file, type: :string, aliases: '-f', banner: 'Specify a configuration file'
|
65
|
+
|
66
|
+
def ssh(remote=nil)
|
67
|
+
Pushapp::Commands.run(:ssh, remote: remote, options: options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Pushapp
|
4
|
+
class Commands
|
5
|
+
attr_reader :logger
|
6
|
+
|
7
|
+
def self.run(command, options = {})
|
8
|
+
self.new(options.merge({ command: command })).send(command)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@options = options
|
13
|
+
@logger = Pushapp::Logger.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def init
|
17
|
+
logger.info "Creating an example config file in #{Pushapp::DEFAULT_CONFIG_LOCATION}"
|
18
|
+
logger.info "Customize it to your needs"
|
19
|
+
create_config_directory
|
20
|
+
write_config_file
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_refs
|
24
|
+
logger.info "Updating .git/config. Setting up refs to all remotes"
|
25
|
+
Pushapp::Git.new.update_tracked_repos(config)
|
26
|
+
end
|
27
|
+
|
28
|
+
def list_remotes
|
29
|
+
logger.info "Known remotes:"
|
30
|
+
remotes_table = config.remotes.map {|r| [r.full_name, r.location, r.env]}
|
31
|
+
remotes_table.unshift ['Full Name', 'Location', 'ENV']
|
32
|
+
logger.shell.print_table(remotes_table)
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup
|
36
|
+
logger.info "Setting up remotes"
|
37
|
+
remotes.each { |r| r.setup! }
|
38
|
+
update
|
39
|
+
end
|
40
|
+
|
41
|
+
def update
|
42
|
+
logger.info "Updating remotes"
|
43
|
+
remotes.each { |r| r.update! }
|
44
|
+
end
|
45
|
+
|
46
|
+
def tasks
|
47
|
+
remotes_list = remotes.empty? ? config.remotes : remotes
|
48
|
+
remotes_list.each do |r|
|
49
|
+
puts "REMOTE: #{r.full_name}"
|
50
|
+
r.tasks.keys.each do |event|
|
51
|
+
puts " EVENT: #{event}"
|
52
|
+
r.tasks[event].each do |task|
|
53
|
+
puts " #{task.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def trigger
|
60
|
+
event = @options[:event]
|
61
|
+
local = @options[:local]
|
62
|
+
if local
|
63
|
+
remotes.each do |r|
|
64
|
+
r.tasks_on(event).each do |t|
|
65
|
+
logger.info "run: #{t.inspect}"
|
66
|
+
Pushapp::Pipe.run(t)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
remotes.each {|r| r.env_run "bundle exec pushapp trigger #{event} #{r.full_name} -l true"}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def ssh
|
75
|
+
if remote
|
76
|
+
remote.ssh!
|
77
|
+
else
|
78
|
+
logger.error 'Remote not found'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def remote
|
85
|
+
@remote ||= config.remotes_named_by(@options[:remote]).first
|
86
|
+
end
|
87
|
+
|
88
|
+
def remotes
|
89
|
+
@remotes ||= config.remotes_matched(@options[:remotes])
|
90
|
+
end
|
91
|
+
|
92
|
+
def config
|
93
|
+
@config ||= Pushapp::Config.parse(@config_file)
|
94
|
+
end
|
95
|
+
|
96
|
+
def create_config_directory
|
97
|
+
Dir.mkdir 'config' unless File.exists? 'config'
|
98
|
+
end
|
99
|
+
|
100
|
+
def write_config_file
|
101
|
+
config = ERB.new(File.read("#{Pushapp::TEMPLATE_ROOT}/config.rb.erb")).result
|
102
|
+
File.open(Pushapp::DEFAULT_CONFIG_LOCATION,"wb") { |f| f.puts config }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'pushapp/remote'
|
2
|
+
require 'pushapp/tasks/base'
|
3
|
+
|
4
|
+
class Pushapp::Config
|
5
|
+
|
6
|
+
attr_reader :file
|
7
|
+
attr_reader :remotes
|
8
|
+
|
9
|
+
@@known_tasks = {}
|
10
|
+
|
11
|
+
def self.parse(configuration_file = nil)
|
12
|
+
config = self.new(configuration_file)
|
13
|
+
config.instance_eval(File.read(config.file))
|
14
|
+
|
15
|
+
config
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(configuration_file = nil)
|
19
|
+
@file = configuration_file || Pushapp::DEFAULT_CONFIG_LOCATION
|
20
|
+
@remotes = []
|
21
|
+
@group_options = {}
|
22
|
+
@group_name = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def remote(name, location, options = {})
|
26
|
+
name = name.to_s
|
27
|
+
|
28
|
+
if remotes.any? {|r| r.location == location}
|
29
|
+
raise "Can't have multiple remotes with same location"
|
30
|
+
end
|
31
|
+
|
32
|
+
full_name = [name, @group_name].compact.join('-')
|
33
|
+
if remotes.any? {|r| r.full_name == full_name}
|
34
|
+
raise "Can't have multiple remotes with same full_name. Remote '#{full_name}' already exists"
|
35
|
+
end
|
36
|
+
|
37
|
+
options = Pushapp.rmerge(@group_options, options)
|
38
|
+
remotes << Pushapp::Remote.new(name, @group_name, location, self, options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def group(group_name, options = {}, &block)
|
42
|
+
@group_name = group_name.to_s
|
43
|
+
@group_options = options
|
44
|
+
instance_eval &block if block_given?
|
45
|
+
@group_name = nil
|
46
|
+
@group_options = {}
|
47
|
+
end
|
48
|
+
|
49
|
+
def on event, &block
|
50
|
+
remotes.each {|r| r.on(event, &block) if block_given? }
|
51
|
+
end
|
52
|
+
|
53
|
+
def known_task name
|
54
|
+
task = @@known_tasks[name.to_s]
|
55
|
+
raise "Unkown task with name '#{name}'. Forget to register task?" unless task
|
56
|
+
task
|
57
|
+
end
|
58
|
+
|
59
|
+
def remotes_named_by(name)
|
60
|
+
name = name.to_s
|
61
|
+
remotes.select {|r| r.full_name == name }
|
62
|
+
end
|
63
|
+
|
64
|
+
def remotes_grouped_by(group)
|
65
|
+
group = group.to_s
|
66
|
+
remotes.select {|r| r.group == group }
|
67
|
+
end
|
68
|
+
|
69
|
+
def remotes_matched(group_or_name)
|
70
|
+
case group_or_name
|
71
|
+
when 'all'
|
72
|
+
remotes
|
73
|
+
when String
|
74
|
+
remotes.select {|r| r.full_name == group_or_name || r.group == group_or_name}
|
75
|
+
when Array
|
76
|
+
group_or_name.map {|n| remotes_matched(n)}.flatten.compact.uniq
|
77
|
+
else
|
78
|
+
[]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.register_task name, klass
|
83
|
+
@@known_tasks[name.to_s] = klass
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
data/lib/pushapp/git.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Pushapp
|
2
|
+
|
3
|
+
class Git
|
4
|
+
|
5
|
+
def update_tracked_repos config
|
6
|
+
refs = required_refs(config)
|
7
|
+
current_refs = current_refs(config)
|
8
|
+
|
9
|
+
new_refs = new_refs(refs, current_refs)
|
10
|
+
old_refs = old_refs(refs, current_refs)
|
11
|
+
|
12
|
+
new_refs.each do |r|
|
13
|
+
Pushapp::Pipe.run("git config --add remote.#{r[0]}.url #{r[1]}")
|
14
|
+
end
|
15
|
+
|
16
|
+
old_refs.each do |r|
|
17
|
+
Pushapp::Pipe.run("git config --unset remote.#{r[0]}.url #{r[1]}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def new_refs refs, cur_refs
|
24
|
+
refs.select do |r|
|
25
|
+
cur_refs.all? {|cr| r != cr }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def old_refs refs, cur_refs
|
30
|
+
cur_refs.select do |cr|
|
31
|
+
refs.all? {|r| r != cr }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_refs config
|
36
|
+
output = Pipe.capture('git remote -v')
|
37
|
+
|
38
|
+
refs = required_refs(config)
|
39
|
+
remotes = refs.map {|r| r[0]}.uniq
|
40
|
+
|
41
|
+
current_refs = output.lines.map do |l|
|
42
|
+
l.gsub(/\s\(.*\)?\Z/, "").chomp
|
43
|
+
end
|
44
|
+
current_refs = current_refs.uniq.map { |line| line.split(/\t/) }
|
45
|
+
current_refs.select {|r| remotes.include?(r[0])}
|
46
|
+
end
|
47
|
+
|
48
|
+
def required_refs config
|
49
|
+
refs = []
|
50
|
+
config.remotes.each do |r|
|
51
|
+
refs << [r.group, r.location] if r.group
|
52
|
+
refs << [r.full_name, r.location]
|
53
|
+
end
|
54
|
+
refs
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|