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.
@@ -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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pushapp.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/**/*test.rb']
7
+ end
8
+
9
+ task default: :test
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pushapp'
4
+ require 'pushapp/cli'
5
+
6
+ Pushapp::CLI.start
@@ -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
@@ -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
@@ -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