pike 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,34 @@
1
+ module Pike
2
+ module SSH
3
+ class Process
4
+ attr_accessor :stdout
5
+ attr_accessor :stderr
6
+ attr_accessor :exit_code
7
+ attr_accessor :command
8
+
9
+
10
+ def initialize(command)
11
+ @stdout = ''
12
+ @stderr = ''
13
+ @exit_code = 0
14
+ @command = command
15
+ end
16
+
17
+ def on_exit_status(channel, data)
18
+ @exit_code = data.read_long
19
+ end
20
+
21
+ def on_stderr(channel, type, data)
22
+ @stderr << data
23
+ end
24
+
25
+ def on_stdout(channel, data)
26
+ @stdout << data
27
+ end
28
+
29
+ def success?
30
+ exit_code == 0
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,115 @@
1
+ require 'pike/ssh/process'
2
+
3
+ module Pike
4
+ module SSH
5
+ class Runner
6
+ SUDO_PATTERN = /\[sudo\] password for .+:\s*$/
7
+
8
+
9
+ ##
10
+ ## Constructor
11
+ ##
12
+
13
+ def initialize(connection, cmd)
14
+ @cmd = cmd
15
+ @process = SSH::Process.new(@cmd)
16
+ @connection = connection
17
+ @state = :init
18
+ end
19
+
20
+
21
+ ##
22
+ ## Executes the process of the current Runner instance
23
+ ##
24
+
25
+ def run!
26
+ @connection.open_channel &method(:run)
27
+ @connection.wait!
28
+
29
+ return @process
30
+ end
31
+
32
+
33
+ ##
34
+ ## Simple method to run commands on a connection
35
+ ##
36
+
37
+ def self.run(connection, cmd)
38
+ ins = self.new connection, cmd
39
+ ins.run!
40
+ end
41
+
42
+ private
43
+
44
+
45
+
46
+ ##
47
+ ## Get's called if a channel is opened and executes the command
48
+ ##
49
+
50
+ def run(channel)
51
+ channel.request_pty
52
+
53
+ channel.exec("cd #{@connection.cwd} && (#{@cmd})") do |ch, success|
54
+ Main.error("Could not run command #{@cmd}") unless success
55
+
56
+ channel.on_request("exit-status", &@process.method(:on_exit_status))
57
+ channel.on_extended_data(&@process.method(:on_stderr))
58
+ channel.on_data(&method(:on_data))
59
+ end
60
+ end
61
+
62
+
63
+ ##
64
+ ## Wrapper method to catch sudo password prompt and handle it
65
+ ##
66
+
67
+ def on_data(channel, data)
68
+ handle_sudo_response(channel, data) if @state == :try_sudo
69
+
70
+ if data =~ SUDO_PATTERN
71
+ handle_sudo(channel, data)
72
+ else
73
+ @state = :sudo_successful
74
+ @process.on_stdout(channel, data)
75
+ channel.on_data(&@process.method(:on_stdout))
76
+ end
77
+ end
78
+
79
+
80
+ ##
81
+ ## Handles the response of the sudo command
82
+ ##
83
+
84
+ def handle_sudo_response(channel, data)
85
+ if data == 'ok'
86
+ @state = :sudo_successful
87
+ elsif data =~ /^\s+$/
88
+ @connection.wait!
89
+ elsif data =~ /incorrect password attempts\s*$/
90
+ Main.error("Too much incorrect password attempts")
91
+ elsif data =~ SUDO_PATTERN
92
+ # Wrong sudo password
93
+ @connection.sudo_password = nil
94
+ handle_sudo(channel, data)
95
+ end
96
+ end
97
+
98
+
99
+ ##
100
+ ## Handles a sudo password prompt by sending the password
101
+ ##
102
+
103
+ def handle_sudo(channel, data, wrong = false)
104
+ @state = :try_sudo
105
+
106
+ unless @connection.sudo_password
107
+ text = wrong ? "Wrong password. Please try again:" : "Please enter sudo password:"
108
+ @connection.sudo_password = retrieve(text) { |q| q.echo = false }
109
+ end
110
+
111
+ channel.send_data "#{@connection.sudo_password}\n"
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,35 @@
1
+ module Pike
2
+ module Tasks
3
+ class Command
4
+ class Local
5
+ def initialize(task)
6
+ @task = task
7
+ end
8
+
9
+ def run(cmd)
10
+ re = `#{cmd} 2>&1`
11
+
12
+ Logger.log re
13
+
14
+ if $? != 0
15
+ Main.error(re)
16
+ end
17
+
18
+ re
19
+ end
20
+
21
+ def cd(new_cwd)
22
+ begin
23
+ return Dir.chdir new_cwd
24
+ rescue Exception => e
25
+ Main.error(e)
26
+ end
27
+ end
28
+
29
+ def bundler(cmd, var_string, do_not_use)
30
+ run("#{var_string} bundle #{cmd}")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ module Pike
2
+ module Tasks
3
+ class Command
4
+ class Remote
5
+ def initialize(task)
6
+ @task = task
7
+ end
8
+
9
+ def run(cmd)
10
+ prc = Pike::SSH::Connection.run(cmd)
11
+
12
+ if prc.success?
13
+ Logger.log prc.stdout
14
+ Logger.log prc.stderr
15
+ return prc.stdout + prc.stderr
16
+ else
17
+ Main.error("Error while executing #{cmd}: " + prc.stdout + prc.stderr)
18
+ end
19
+ end
20
+
21
+ def cd(new_cwd)
22
+ return Pike::SSH::Connection.cd(new_cwd)
23
+ end
24
+
25
+ def bundler(cmd, var_string, sudo = false)
26
+ run("#{@task.current_env.bundler_prefix} #{sudo ? 'sudo ' : ''} #{var_string} bundle #{cmd}")
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,150 @@
1
+ require 'pike/tasks/command/local'
2
+ require 'pike/tasks/command/remote'
3
+
4
+ module Pike
5
+ module Tasks
6
+ class Command
7
+ attr_reader \
8
+ :where, # :local || :remote - Determines on which machine to run the command
9
+ :cmd, # The command string
10
+ :mode # :cd, :cmd, :bundler, :scp, :build_rba # TODO move those? polymorphism?
11
+ :vars # Hash of vars
12
+
13
+
14
+ ##
15
+ ## Constructor
16
+ ##
17
+
18
+ def initialize(task, where, cmd, vars = {})
19
+ @task = task
20
+ @where = where
21
+ @vars = vars
22
+
23
+ case cmd.split(' ').first
24
+ when 'cd'
25
+ @mode = :cd
26
+ @cd_to = cmd.gsub('cd ', '')
27
+
28
+ when 'bundle'
29
+ @mode = :bundler
30
+ @cmd = cmd.gsub('bundle ', '')
31
+
32
+ when '!BUILD_RBA'
33
+ @mode = :build_rba
34
+ @cmd = nil
35
+
36
+ when '!SCP'
37
+ @mode = :scp
38
+
39
+ splitted = cmd.gsub('!SCP ', '').split(' ')
40
+ @local = splitted[0]
41
+ @remote = splitted[1]
42
+
43
+ else
44
+ @mode = :cmd
45
+ @cmd = cmd
46
+ end
47
+ end
48
+
49
+
50
+ ##
51
+ ## The cmd prefix for the log. Looks like a bash prompt
52
+ ##
53
+
54
+ def get_cmd_prefix
55
+ "\n" + (where == :local ? Dir.pwd : SSH::Connection.cwd) + " (#{where})$ #{to_s}"
56
+ end
57
+
58
+
59
+ ##
60
+ ## Runs the command
61
+ ##
62
+
63
+ def run(env)
64
+ # Log command
65
+ Logger.log get_cmd_prefix
66
+ report("#{where == :remote ? '>' : '<'} #{to_s}") if Main.verbose
67
+
68
+ # Determine target environment
69
+ target = (where == :local ? Local.new(@task) : Remote.new(@task))
70
+
71
+ unless Main.dry_run
72
+ # Mode depending routines
73
+ case @mode
74
+ when :cmd
75
+ return target.run("#{var_string} #{sudo}#{@cmd}")
76
+
77
+ when :cd
78
+ return target.cd(@cd_to)
79
+
80
+ when :bundler
81
+ return target.bundler(@cmd, var_string, sudo?)
82
+
83
+ when :build_rba
84
+ return build_rba
85
+
86
+ when :scp
87
+ SSH::Connection.scp(@local, @remote, env.get_var(:host))
88
+
89
+ else
90
+ Main.error("Unkown command mode #{@mode}")
91
+ end
92
+ end
93
+ end
94
+
95
+
96
+ ##
97
+ ## Generates the vars for the shell command
98
+ ##
99
+
100
+ def var_string
101
+ re = ''
102
+ @vars.each do |key, value|
103
+ re << "#{key.to_s.upcase.strip}=#{value} "
104
+ end
105
+ re
106
+ end
107
+
108
+
109
+ ##
110
+ ## Starts the build process for .rba file
111
+ ##
112
+
113
+ def build_rba
114
+ target_file = '../' + Main.get_var(:name) + '.rba'
115
+ Logger.log "Building RBA file: #{target_file} ..."
116
+ RBA::Builder.build(target_file)
117
+ target_file
118
+ end
119
+
120
+
121
+ ##
122
+ ## Converts command to string. Depending on the mode, the result may differ
123
+ ##
124
+
125
+ def to_s
126
+ case @mode
127
+ when :cmd
128
+ "#{sudo}#{var_string}#{@cmd}"
129
+ when :bundler
130
+ "#{sudo}#{var_string}bundle #{@cmd}"
131
+ when :cd
132
+ "cd #{@cd_to}"
133
+ when :scp
134
+ "scp #{@local} #{@remote}"
135
+ when :build_rba
136
+ "build .rba file"
137
+ end
138
+ end
139
+
140
+
141
+ def sudo
142
+ sudo? ? 'sudo ' : ''
143
+ end
144
+
145
+ def sudo?
146
+ where == :remote && @task.current_env.get_var(:use_sudo) && !@cmd.strip.start_with?('source ')
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,125 @@
1
+ module Pike
2
+ class Task
3
+ attr_accessor :current_env
4
+
5
+ ##
6
+ ## Constructor
7
+ ##
8
+
9
+ def initialize(name, &block)
10
+ @block = block
11
+ @current_env = nil
12
+ @current_params = {}
13
+ @name = name
14
+ end
15
+
16
+
17
+ ##
18
+ ## Call run that task
19
+ ##
20
+
21
+ def run(env, params)
22
+ log_header
23
+
24
+ # Setting @current_params and @current_env
25
+ set_current_params params
26
+ @current_env = env
27
+
28
+ # Parse the task block
29
+ DSL::Task.load(self, &@block)
30
+ end
31
+
32
+
33
+ ##
34
+ ## Called from the DSL to run another task
35
+ ##
36
+
37
+ def run_task(task)
38
+ @current_env.run_task(task)
39
+ end
40
+
41
+
42
+ ##
43
+ ## Called from the DSL to get the value of a varaible
44
+ ##
45
+
46
+ def get_var(key, default = nil)
47
+ return @current_env.get_var(key) if var_given?(key)
48
+ return default if default
49
+ undef_error(:variable, key)
50
+ end
51
+
52
+
53
+
54
+ ##
55
+ ## Called from the DSL to get the value of a param
56
+ ##
57
+
58
+
59
+ def get_param(key, default = nil)
60
+ return @current_params[key] if param_given?(key)
61
+ return default if default
62
+ undef_error(:param, key)
63
+ end
64
+
65
+
66
+ ##
67
+ ## Checks if a param with the given key is defined
68
+ ##
69
+
70
+ def param_given?(key)
71
+ @current_params.include?(key)
72
+ end
73
+
74
+
75
+ ##
76
+ ## Checks if a variable with the given key is defined
77
+ ##
78
+
79
+ def var_given?(key)
80
+ (@current_env.get_var(key) != nil)
81
+ end
82
+
83
+
84
+
85
+ private
86
+
87
+
88
+ ##
89
+ ## Log header for that task
90
+ ##
91
+
92
+ def log_header
93
+ Logger.log
94
+ Logger.log
95
+ Logger.log " Run Task: #{@name}"
96
+ Logger.log "==============" + @name.length.times.collect { '=' }.join
97
+ Logger.log
98
+ end
99
+
100
+
101
+ ##
102
+ ## Iterates over all params, adds them to @current_params and logs them
103
+ ##
104
+
105
+ def set_current_params(params)
106
+ @current_params = params
107
+
108
+ @current_params.keys.each do |key|
109
+ Logger.log "Option '#{key}': #{@current_params[key]}"
110
+ end
111
+
112
+ Logger.log
113
+ end
114
+
115
+
116
+
117
+ ##
118
+ ## Throw error if param or variable is not defined
119
+ ##
120
+
121
+ def undef_error(type, key)
122
+ Main.error("Require the #{type} #{key} for the task #{@name} but it's not defined.")
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,168 @@
1
+ # TODO the order of the tasks may vary. So make sure every tasks cds in his required directory
2
+
3
+ task :clone do
4
+ local "rm -rf #{get :build_dir}"
5
+
6
+ branch = param(:branch, 'master')
7
+ depth = param(:depth, 1)
8
+
9
+ local "git clone --depth=#{depth} -b #{branch} #{param :repository} #{get :build_dir}"
10
+ local "cd #{get :build_dir}"
11
+ end
12
+
13
+ task :submodules do
14
+ local "cd #{get :build_dir}"
15
+ local "git submodule update --init"
16
+ end
17
+
18
+ task :reduce do
19
+ local "cd #{get :build_dir}"
20
+ local 'rm -rf ' + param(:clean).join(' ')
21
+ end
22
+
23
+ task :bundle do
24
+ local "cd #{get :build_dir}"
25
+ without = param_given?(:without) ? '--without ' + param(:without).join(' ') : ''
26
+ local "bundle install #{without}"
27
+ end
28
+
29
+ task :rspec do
30
+ local "cd #{get :build_dir}"
31
+ local "bundle exec rspec"
32
+ end
33
+
34
+ task :assets do
35
+ local "cd #{get :build_dir}"
36
+ vars = { RAILS_ENV: param(:rails_env, 'development') }
37
+ vars[:RAILS_GROUPS] = param(:rails_group) if param_given?(:rails_group)
38
+
39
+ local "bundle exec rake assets:precompile", vars
40
+ end
41
+
42
+ task :build do
43
+ local "cd #{get :build_dir}"
44
+ local build_rba
45
+ local "cd #{get :root_dir}"
46
+
47
+ unless param_given?(:keep_build_dir) && param(:keep_build_dir) == true
48
+ local "rm -rf #{get :build_dir}"
49
+ end
50
+ end
51
+
52
+
53
+ # Directory sturcture:
54
+ # - /opt/jarvis
55
+ # - config/
56
+ # - config.yml
57
+ # - db/
58
+ # - jarvis.sqlite3
59
+ # - config.ru
60
+ # - .pike
61
+ # - releases/
62
+ # - 123/
63
+ # - app
64
+ # - config/
65
+ # - (s) config.yml -> ../../../../config/config.yml
66
+ # - db/
67
+ # - (s) jarvis.sqlite3 -> ../../../../db/jarvis.sqlite3
68
+ # - (s) current -> releases/123
69
+ # - (s) assets -> current/public/assets
70
+ # - (s) index.html -> current/public/index.html
71
+
72
+ task :deploy do
73
+ name = get(:name)
74
+ deploy_to = get(:deploy_to)
75
+ target_dir = get(:release_path)
76
+
77
+ local "cd #{get :root_dir}"
78
+
79
+ # Copy the rba file to the server in the /tmp directory
80
+ upload "#{name}.rba", "/tmp/#{name}.rba"
81
+
82
+ # Create dir /path/to/deploy/.rba/releases/timestamp/
83
+ remote "mkdir -pv #{target_dir}"
84
+
85
+ # Move the .rba file to the release directory
86
+ remote "mv -vf /tmp/#{name}.rba #{target_dir}"
87
+
88
+ # CD into the release directory
89
+ remote "cd #{target_dir}"
90
+
91
+ # Extract the rba file
92
+ remote "tar -xzvf #{name}.rba"
93
+
94
+ # Remove the rba file
95
+ remote "rm -vf #{name}.rba"
96
+
97
+ # Make sure tmp/pids exists
98
+ remote "mkdir -pv tmp/pids"
99
+ end
100
+
101
+
102
+ task :symlink_shared do
103
+ remote "cd #{get :deploy_to}"
104
+
105
+ param(:files).each do |file|
106
+ remote "ln -nfvs #{get :deploy_to}#{file} #{get :release_path}#{file}"
107
+ end
108
+ end
109
+
110
+ task :symlinks do
111
+ remote "cd #{get :deploy_to}"
112
+
113
+ # Current release link
114
+ remote "ln -nfvs #{get :release_path} #{get :deploy_to}.pike/current"
115
+
116
+ # public/ files
117
+ files = remote "ls -lAh #{get :release_path}public | awk -F ' ' '{print $9}'"
118
+
119
+ files.split("\n").each do |file|
120
+ file.strip!
121
+ if file != ''
122
+ remote "ln -nfvs #{get :deploy_to}.pike/current/public/#{file} #{get :deploy_to}.pike/#{file}"
123
+ end
124
+ end
125
+ end
126
+
127
+ task :bundle_deployment do
128
+ remote "cd #{get :release_path}"
129
+ without = param_given?(:without) ? '--without ' + param(:without).join(' ') : ''
130
+ no_cache = param_given?(:no_cache) && param(:no_cache) == true ? '--no-cache' : ''
131
+ remote "bundle install --deployment #{without} #{no_cache}"
132
+ end
133
+
134
+ task :migrate do
135
+ remote "cd #{get :release_path}"
136
+ remote "bundle exec rake db:migrate", RAILS_ENV: get(:rails_env)
137
+ end
138
+
139
+ task :cleanup do
140
+ remote "cd #{get :release_path}"
141
+
142
+ releases = remote "ls -lAh #{get :deploy_to}.pike/releases/ | awk -F ' ' '{print $9}'"
143
+ releases = releases.split("\n").find_all { |r| r.strip =~ /^\d+$/ }.map { |r| r.strip }
144
+
145
+ i = 0
146
+ delete = releases.length - param(:keep_releases).to_i
147
+
148
+ releases.each do |release|
149
+ i += 1
150
+ remote "rm -rf #{get :deploy_to}.pike/releases/#{release}" if i < delete
151
+ end
152
+ end
153
+
154
+ task :chown do
155
+ remote "chown -R #{param :user}:#{param :group} #{get :deploy_to}"
156
+ end
157
+
158
+ task :restart do
159
+ if param(:server) == :passenger
160
+ remote "touch #{get :release_path}tmp/restart.txt"
161
+ else
162
+ raise "Unsupported server type #{param :server}. Feel free to contribute and add support for that server! :)"
163
+ end
164
+ end
165
+
166
+ task :setup do
167
+ remote "mkdir -p #{get :deploy_to}/.pike/releases"
168
+ end
@@ -0,0 +1,3 @@
1
+ module Pike
2
+ VERSION = "0.0.1"
3
+ end
data/lib/pike.rb ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'optparse'
3
+ require 'steps'
4
+ require 'pike/main'
5
+
6
+
7
+ # Command line options
8
+ options = {
9
+ debug: false,
10
+ dry_run: false,
11
+ verbose: false
12
+ }
13
+
14
+ opt_parser = OptionParser.new do |opt|
15
+ opt.banner = 'Usage: pike [OPTIONS] [ENVIRONMENT] [TASK]'
16
+ opt.separator ''
17
+ opt.separator 'Options'
18
+
19
+ opt.on('-d', '--debug', 'Enable stacktrace output.') do
20
+ options[:debug] = true
21
+ end
22
+
23
+ opt.on('-v', '--verbose', 'Enable verbosity: Executed command will be display.') do
24
+ options[:verbose] = true
25
+ end
26
+
27
+ opt.on('-s', '--simulate', 'Dry run: Simulate and don\'t really execute the commands.') do
28
+ options[:dry_run] = true
29
+ end
30
+
31
+ opt.on('-h', '--help', 'Displays help text') do
32
+ puts opt_parser
33
+ exit
34
+ end
35
+
36
+ opt.on('-V', '--version', 'Displays version') do
37
+ puts Pike::VERSION.to_s
38
+ exit
39
+ end
40
+ end
41
+
42
+ opt_parser.parse!
43
+
44
+
45
+ # Check for environment and set default
46
+ unless ARGV[0]
47
+ ARGV.push('production')
48
+ end
49
+
50
+
51
+ # Initialize pike
52
+ Pike::Main.init options
53
+
54
+
55
+ # Start the magic!
56
+ Pike::Main.run