pike 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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