pagoda 0.3.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.DS_Store +0 -0
  2. data/Gemfile +3 -3
  3. data/Gemfile.lock +33 -24
  4. data/README.md +68 -0
  5. data/Rakefile +4 -7
  6. data/bin/pagoda +100 -8
  7. data/lib/pagoda/cli/commands/clone.rb +13 -0
  8. data/lib/pagoda/cli/commands/create.rb +13 -0
  9. data/lib/pagoda/cli/commands/deploy.rb +12 -0
  10. data/lib/pagoda/cli/commands/destroy.rb +16 -0
  11. data/lib/pagoda/cli/commands/info.rb +13 -0
  12. data/lib/pagoda/cli/commands/init.rb +13 -0
  13. data/lib/pagoda/cli/commands/list.rb +20 -0
  14. data/lib/pagoda/cli/commands/rename.rb +17 -0
  15. data/lib/pagoda/cli/commands/rollback.rb +12 -0
  16. data/lib/pagoda/cli/commands/ssh_key.rb +27 -0
  17. data/lib/pagoda/cli/commands/tunnel.rb +18 -0
  18. data/lib/pagoda/cli/commands.rb +11 -0
  19. data/lib/pagoda/{helpers.rb → cli/core_ext.rb} +10 -118
  20. data/lib/pagoda/cli/helpers/app.rb +143 -0
  21. data/lib/pagoda/cli/helpers/base.rb +199 -0
  22. data/lib/pagoda/cli/helpers/key.rb +69 -0
  23. data/lib/pagoda/cli/helpers/tunnel.rb +36 -0
  24. data/lib/pagoda/cli/helpers.rb +127 -0
  25. data/lib/pagoda/cli/override.rb +23 -0
  26. data/lib/pagoda/cli/version.rb +5 -0
  27. data/lib/pagoda/cli.rb +11 -0
  28. data/lib/pagoda-cli.rb +1 -0
  29. data/pagoda.gemspec +20 -24
  30. data/pagoda.rdoc +139 -0
  31. data/pkg/newpagoda-0.1.10.gem +0 -0
  32. data/pkg/newpagoda-0.1.11.gem +0 -0
  33. data/pkg/newpagoda-0.1.12.gem +0 -0
  34. data/pkg/newpagoda-0.1.13.gem +0 -0
  35. data/pkg/newpagoda-0.1.15.gem +0 -0
  36. data/pkg/newpagoda-0.1.4.gem +0 -0
  37. data/pkg/newpagoda-0.1.6.gem +0 -0
  38. data/pkg/newpagoda-0.1.7.gem +0 -0
  39. data/pkg/newpagoda-0.1.8.gem +0 -0
  40. data/pkg/newpagoda-0.1.9.gem +0 -0
  41. data/pkg/newpagoda-0.5.0.gem +0 -0
  42. data/spec/lib/helper_spec.rb +32 -0
  43. data/spec/lib/helpers/app_spec.rb +104 -0
  44. data/spec/lib/helpers/base_spec.rb +27 -0
  45. data/spec/lib/helpers/key_spec.rb +42 -0
  46. data/spec/lib/helpers/tunnel_spec.rb +30 -0
  47. data/spec/spec_helper.rb +17 -0
  48. metadata +74 -50
  49. data/.bundle/config +0 -1
  50. data/README +0 -3
  51. data/lib/pagoda/client.rb +0 -225
  52. data/lib/pagoda/command.rb +0 -96
  53. data/lib/pagoda/commands/app.rb +0 -247
  54. data/lib/pagoda/commands/auth.rb +0 -149
  55. data/lib/pagoda/commands/base.rb +0 -184
  56. data/lib/pagoda/commands/db.rb +0 -18
  57. data/lib/pagoda/commands/help.rb +0 -100
  58. data/lib/pagoda/commands/tunnel.rb +0 -49
  59. data/lib/pagoda/tunnel_proxy.rb +0 -130
  60. data/lib/pagoda/version.rb +0 -3
  61. data/lib/pagoda.rb +0 -3
  62. data/spec/base.rb +0 -21
  63. data/spec/client_spec.rb +0 -255
  64. data/spec/command_spec.rb +0 -26
  65. data/spec/commands/auth_spec.rb +0 -57
@@ -1,149 +0,0 @@
1
- module Pagoda::Command
2
- class Auth < Base
3
- attr_accessor :credentials
4
-
5
- def initialize(args)
6
- @args = args
7
- check_for_credentials
8
- end
9
-
10
-
11
- def client
12
- @client ||= init_client
13
- end
14
-
15
- def init_client
16
- client = Pagoda::Client.new(user, password)
17
- client.on_warning { |message| self.display("\n#{message}\n\n") }
18
- client
19
- end
20
-
21
- # just a stub; will raise if not authenticated
22
- def check
23
- client.app_list
24
- end
25
-
26
- def check_for_credentials
27
- if option_value("-u", "--username") && option_value("-p", "--password")
28
- reauthorize
29
- end
30
- end
31
-
32
- def reauthorize
33
- @credentials = ask_for_credentials
34
- write_credentials
35
- end
36
-
37
- def user # :nodoc:
38
- get_credentials
39
- @credentials[0]
40
- end
41
-
42
- def password # :nodoc:
43
- get_credentials
44
- @credentials[1]
45
- end
46
-
47
- def credentials_file
48
- "#{home_directory}/.pagoda/credentials"
49
- end
50
-
51
- def get_credentials # :nodoc:
52
- return if @credentials
53
- unless @credentials = read_credentials
54
- @credentials = ask_for_credentials
55
- save_credentials
56
- end
57
- @credentials
58
- end
59
-
60
- def read_credentials
61
- File.exists?(credentials_file) and File.read(credentials_file).split("\n")
62
- end
63
-
64
- def echo_off
65
- system "stty -echo"
66
- end
67
-
68
- def echo_on
69
- system "stty echo"
70
- end
71
-
72
- def ask_for_credentials
73
- unless username = option_value("-u", "--username")
74
- username = ask "Username: "
75
- end
76
- unless password = option_value("-p", "--password")
77
- display "Password: ", false
78
- password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
79
- end
80
- [username, password] # return
81
- end
82
-
83
- def ask_for_password
84
- echo_off
85
- password = ask
86
- puts
87
- echo_on
88
- return password
89
- end
90
-
91
- def ask_for_password_on_windows
92
- require "Win32API"
93
- char = nil
94
- password = ''
95
-
96
- while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
97
- break if char == 10 || char == 13 # received carriage return or newline
98
- if char == 127 || char == 8 # backspace and delete
99
- password.slice!(-1, 1)
100
- else
101
- # windows might throw a -1 at us so make sure to handle RangeError
102
- (password << char.chr) rescue RangeError
103
- end
104
- end
105
- return password
106
- end
107
-
108
- def save_credentials
109
- begin
110
- write_credentials
111
- Pagoda::Command.run_internal('auth:check', args)
112
- rescue RestClient::Unauthorized => e
113
- delete_credentials
114
- raise e unless retry_login?
115
-
116
- error "Authentication failed"
117
- @credentials = ask_for_credentials
118
- @client = init_client
119
- retry
120
- rescue Exception => e
121
- delete_credentials
122
- raise e
123
- end
124
- end
125
-
126
- def retry_login?
127
- @login_attempts ||= 0
128
- @login_attempts += 1
129
- @login_attempts < 3
130
- end
131
-
132
- def write_credentials
133
- FileUtils.mkdir_p(File.dirname(credentials_file))
134
- File.open(credentials_file, 'w') do |file|
135
- file.puts self.credentials
136
- end
137
- set_credentials_permissions
138
- end
139
-
140
- def set_credentials_permissions
141
- FileUtils.chmod 0700, File.dirname(credentials_file)
142
- FileUtils.chmod 0600, credentials_file
143
- end
144
-
145
- def delete_credentials
146
- FileUtils.rm_f(credentials_file)
147
- end
148
- end
149
- end
@@ -1,184 +0,0 @@
1
- require 'iniparse'
2
-
3
- module Pagoda
4
- module Command
5
- class Base
6
- include Pagoda::Helpers
7
-
8
- attr_accessor :args
9
-
10
- def initialize(args)
11
- @args = args
12
- end
13
-
14
- def client
15
- @client ||= Pagoda::Command.run_internal('auth:client', args)
16
- end
17
-
18
- def shell(cmd)
19
- FileUtils.cd(Dir.pwd) {|d| return `#{cmd}`}
20
- end
21
-
22
- def app(soft_fail=false)
23
- if override = option_value("-a", "--app")
24
- return override
25
- else
26
- if name = find_app
27
- return name
28
- else
29
- if locate_app_root
30
- if extract_git_clone_url
31
- return false if soft_fail
32
- errors = []
33
- errors << "This repo is either not launched, or not paired with a launched app"
34
- errors << ""
35
- errors << "To launch this app run 'pagoda launch <app-name>'"
36
- errors << ""
37
- errors << "To pair this project with a deployed app, run 'pagoda pair <app-name>'"
38
- errors << ""
39
- errors << "To see a list of currently deployed apps, run 'pagoda list'"
40
- error errors
41
- else
42
- return false if soft_fail
43
- errors = []
44
- errors << "It appears you are using git (fantastic)."
45
- errors << "However we only support git repos hosted with github."
46
- errors << "Please ensure your repo is hosted with github."
47
- errors << ""
48
- errors << "If you are trying to reference a specific app, try argument: -a <app-name>"
49
- error errors
50
- end
51
- else
52
- return false if soft_fail
53
- errors = []
54
- errors << "Unable to find git config in this directory or in any parent directory"
55
- errors << ""
56
- errors << "If you are trying to reference a specific app, try argument: -a <app-name>"
57
- error errors
58
- end
59
- end
60
- end
61
- name
62
- end
63
-
64
- def parse_branch
65
- option_value("-b", "--branch") || find_branch
66
- end
67
-
68
- def parse_commit
69
- option_value("-c", "--commit") || find_commit
70
- end
71
-
72
- def find_app
73
- read_apps.each do |line|
74
- app = line.split(" ")
75
- return app[0] if app[2] == locate_app_root
76
- end
77
- false
78
- end
79
-
80
- def find_branch
81
- begin
82
- line = File.new("#{locate_app_root}/.git/HEAD").gets
83
- line.strip.split(' ').last.split("/").last
84
- rescue
85
- nil
86
- end
87
- end
88
-
89
- def find_commit
90
- begin
91
- File.new("#{locate_app_root}/.git/refs/heads/#{parse_branch}").gets.strip
92
- rescue
93
- nil
94
- end
95
- end
96
-
97
- def read_apps
98
- return [] if !File.exists?(apps_file)
99
- File.read(apps_file).split(/\n/).inject([]) {|apps, line| apps << line if line.include?("git@github.com"); apps}
100
- end
101
-
102
- def write_app(name, git_url=nil, app_root=nil)
103
- git_url = extract_git_clone_url unless git_url
104
- app_root = locate_app_root unless app_root
105
- FileUtils.mkdir_p(File.dirname(apps_file)) if !File.exists?(apps_file)
106
- current_apps = read_apps
107
- File.open(apps_file, 'w') do |file|
108
- current_apps.each do |app|
109
- file.puts app
110
- end
111
- file.puts "#{name} #{git_url} #{app_root}"
112
- end
113
- set_apps_file_permissions
114
- end
115
- alias :add_app :write_app
116
-
117
- def remove_app(name)
118
- current_apps = read_apps
119
- current_apps.delete_if do |app|
120
- app.split(" ")[0] == name
121
- end
122
- File.open(apps_file, 'w') do |file|
123
- current_apps.each do |app|
124
- file.puts app
125
- end
126
- end
127
- end
128
-
129
- def set_apps_file_permissions
130
- FileUtils.chmod 0700, File.dirname(apps_file)
131
- FileUtils.chmod 0600, apps_file
132
- end
133
-
134
- def apps_file
135
- "#{home_directory}/.pagoda/apps"
136
- end
137
-
138
- def extract_possible_name
139
- cleanup_name(extract_git_clone_url.split(":")[1].split("/")[1].split(".")[0])
140
- end
141
-
142
- def cleanup_name(name)
143
- name.gsub(/-/, '').gsub(/_/, '').gsub(/ /, '').downcase
144
- end
145
-
146
- def extract_git_clone_url(soft=false)
147
- begin
148
- url = IniParse.parse( File.read("#{locate_app_root}/.git/config") )['remote "origin"']["url"]
149
- raise unless url.match(/^((git@github.com:)|(.*:)|((http|https):\/\/.*github.com\/)|(git:\/\/github.com\/))(.*)\/(.*).git$/i)
150
- url
151
- rescue Exception => e
152
- return false
153
- end
154
- end
155
-
156
- def version
157
- display Client.gem_version_string
158
- end
159
-
160
- def locate_app_root(dir=Dir.pwd)
161
- return dir if File.exists? "#{dir}/.git/config"
162
- parent = dir.split('/')[0..-2].join('/')
163
- return false if parent.empty?
164
- locate_app_root(parent)
165
- end
166
-
167
- def extract_option(options, default=true)
168
- values = options.is_a?(Array) ? options : [options]
169
- return unless opt_index = args.select { |a| values.include? a }.first
170
- opt_position = args.index(opt_index) + 1
171
- if args.size > opt_position && opt_value = args[opt_position]
172
- if opt_value.include?('--')
173
- opt_value = nil
174
- else
175
- args.delete_at(opt_position)
176
- end
177
- end
178
- opt_value ||= default
179
- args.delete(opt_index)
180
- block_given? ? yield(opt_value) : opt_value
181
- end
182
- end
183
- end
184
- end
@@ -1,18 +0,0 @@
1
- module Pagoda::Command
2
- class Db < Base
3
-
4
- def create
5
- app
6
- display
7
- client.app_database_create(app)
8
- display "+> creating a sql database on pagodabox...", false
9
- loop_transaction
10
- display "+> created"
11
- display
12
- end
13
-
14
-
15
-
16
-
17
- end
18
- end
@@ -1,100 +0,0 @@
1
- module Pagoda::Command
2
- class Help < Base
3
- def index
4
- display %{
5
- Pagoda
6
-
7
- NAME
8
- pagoda -- command line utility for pagodabox.com
9
-
10
- SYNOPSIS
11
- pagoda [command] [parameters]
12
-
13
- DESCRIPTION
14
-
15
- If no operands are given, we will attempt to pull data from the current
16
- directory. If more than one operand is given, non-directory operands are
17
- displayed first.
18
-
19
- The following options are available:
20
-
21
- COMMANDS
22
-
23
- list # list your apps
24
- deploy # Deploy your current state to pagoda
25
- launch <name> # create (register) a new app
26
- info # display info about an app
27
- destroy # remove app
28
- rollback # rollback app
29
- tunnel # create a tunnel to your database on pagoda
30
-
31
-
32
- PARAMETERS
33
-
34
- ---------------------------
35
- GLOBAL :
36
- ---------------------------
37
- -a <name> | --app=<name>
38
- Set the application name (Only necessary when not in repo dir).
39
-
40
- -u <username> | --username=<username>
41
- When set, will not attempt to save your username. Also over-rides
42
- any saved username.
43
-
44
- -p <password> | --password=<password>
45
- When set, will not attempt to save your password. Also over-rides
46
- any saved password.
47
-
48
- -f
49
- Executes all commands without confirmation request.
50
-
51
- ---------------------------
52
- DEPLOYING - pagoda deploy :
53
- ---------------------------
54
- -b <branch> | --branch=<branch>
55
- Specify the branch name. By default uses the branch
56
- your local repo is on.
57
-
58
- -c <commit> | --commit=<commit>
59
- Specify the commit id. By default uses the commit HEAD is set to.
60
-
61
- --latest
62
- Will attempt to deploy to the latest commit on github rather than
63
- your local repo's current commit.
64
-
65
- ---------------------------
66
- TUNNELING - pagoda tunnel :
67
- ---------------------------
68
-
69
- -t <type> | --type=<type>
70
- Specify the tunnel type. (ex:mysql)
71
-
72
- -n <instance> | --name=<instance>
73
- Specify the instance name you want to operate on used for
74
- database instance
75
-
76
-
77
- EXAMPLES
78
- launch an application on pagoda from inside the clone folder:
79
- (must be done inside your repo folder)
80
- pagoda launch <app name>
81
-
82
- list your applications:
83
-
84
- pagoda list
85
-
86
- create tunnel to your database:
87
- (must be inside your repo folder or specify app)
88
-
89
- pagoda tunnel -a <app name> -t mysql -n <database name>
90
-
91
- destroy an application:
92
- (must be inside your repo folder or specify app)
93
-
94
- pagoda destroy
95
-
96
-
97
- }
98
- end
99
- end
100
- end
@@ -1,49 +0,0 @@
1
- module Pagoda::Command
2
- class Tunnel < Auth
3
-
4
- def index
5
- app
6
- type = option_value("-t", "--type") || 'mysql'
7
- Pagoda::Command.run_internal("tunnel:#{type}", args)
8
- end
9
-
10
- def mysql
11
- instance_name = option_value("-n", "--name")
12
- unless instance_name
13
- # try to find mysql instances here
14
- dbs = client.app_databases(app)
15
- if dbs.length == 0
16
- errors = []
17
- errors << "It looks like you don't have any MySQL instances for #{app}"
18
- errors << "Feel free to add one in the admin panel (10 MB Free)"
19
- error errors
20
- elsif dbs.length == 1
21
- instance_name = dbs.first[:name]
22
- else
23
- errors = []
24
- errors << "Multiple MySQL instances found"
25
- errors << ""
26
- dbs.each do |instance|
27
- errors << "-> #{instance[:name]}"
28
- end
29
- errors << ""
30
- errors << "Please specify which instance you would like to use."
31
- errors << ""
32
- errors << "ex: pagoda tunnel -n #{dbs[0][:name]}"
33
- error errors
34
- end
35
- end
36
- display
37
- display "+> Authenticating Database Ownership"
38
-
39
- if client.database_exists?(app, instance_name)
40
- Pagoda::TunnelProxy.new(:mysql, user, password, app, instance_name).start
41
- else
42
- errors = []
43
- errors << "Security exception -"
44
- errors << "Either the MySQL instance doesn't exist or you are unauthorized"
45
- error errors
46
- end
47
- end
48
- end
49
- end
@@ -1,130 +0,0 @@
1
- require "socket"
2
- require 'openssl'
3
-
4
- module Pagoda
5
- class TunnelProxy
6
- include Pagoda::Helpers
7
-
8
- def initialize(type, user, pass, app, instance)
9
- @type = type
10
- @user = user
11
- @pass = pass
12
- @app = app
13
- @instance = instance
14
- end
15
-
16
- def start
17
-
18
- [:INT, :TERM].each do |sig|
19
- Signal.trap(sig) do
20
- display "Tunnel Closed."
21
- display "-----------------------------------------------"
22
- display
23
- exit
24
- end
25
- end
26
-
27
- local_port = 3307
28
- remote_host = "www.pagodabox.com"
29
- remote_port = 3306
30
-
31
- max_threads = 20
32
- threads = []
33
-
34
- chunk = 4096*4096
35
-
36
- #puts "start TCP server"
37
- display "+> Opening Tunnel"
38
- bound = false
39
- until bound
40
- begin
41
- proxy_server = TCPServer.new('0.0.0.0', local_port)
42
- bound = true
43
- rescue Errno::EADDRINUSE
44
- local_port += 1
45
- end
46
- end
47
-
48
- display
49
- display "Tunnel Established! Accepting connections on :"
50
- display "-----------------------------------------------"
51
- display
52
- display "HOST : 127.0.0.1 (or localhost)", true, 2
53
- display "PORT : #{local_port}", true, 2
54
- display "USER : (found in pagodabox dashboard)", true, 2
55
- display "PASS : (found in pagodabox dashboard)", true, 2
56
- display
57
- display "-----------------------------------------------"
58
- display "(note : ctrl-c To close this tunnel)"
59
-
60
- loop do
61
-
62
- #puts "start a new thread for every client connection"
63
- threads << Thread.new(proxy_server.accept) do |client_socket|
64
-
65
- begin
66
- # puts "client connection"
67
- begin
68
- server_socket = TCPSocket.new(remote_host, remote_port)
69
- ssl_context = OpenSSL::SSL::SSLContext.new()
70
- ssl_socket = OpenSSL::SSL::SSLSocket.new(server_socket, ssl_context)
71
- ssl_socket.sync_close = true
72
- ssl_socket.connect
73
- rescue Errno::ECONNREFUSED
74
- # puts "connection refused"
75
- client_socket.close
76
- raise
77
- end
78
-
79
- # puts "authenticate"
80
- if ssl_socket.readpartial(chunk) == "auth"
81
- # puts "authentication"
82
- ssl_socket.write "auth=#{@user}:#{@pass}:#{@app}:#{@instance}"
83
- if ssl_socket.readpartial(chunk) == "success"
84
- # puts "successful connection"
85
- else
86
- # puts "failed connection"
87
- end
88
- else
89
- # puts "danger will robbinson! abort!"
90
- end
91
-
92
- loop do
93
- # puts "wait for data on either socket"
94
- (ready_sockets, dummy, dummy) = IO.select([client_socket, ssl_socket])
95
-
96
- # puts "full duplex connection until data stream ends"
97
- begin
98
- ready_sockets.each do |socket|
99
- data = socket.readpartial(chunk)
100
- if socket == client_socket
101
- #puts "read from client and write to server"
102
- ssl_socket.write data
103
- ssl_socket.flush
104
- else
105
- #puts "read from server and write to client."
106
- client_socket.write data
107
- client_socket.flush
108
- end
109
- end
110
- rescue EOFError
111
- break
112
- end
113
- end
114
-
115
- rescue StandardError => error
116
- end
117
- client_socket.close rescue StandardError
118
- ssl_socket.close rescue StandardError
119
- end
120
-
121
- #puts "clean up the dead threads, and wait until we have available threads"
122
- threads = threads.select { |thread| thread.alive? ? true : (thread.join; false) }
123
- while threads.size >= max_threads
124
- sleep 1
125
- threads = threads.select { |thread| thread.alive? ? true : (thread.join; false) }
126
- end
127
- end
128
- end
129
- end
130
- end
@@ -1,3 +0,0 @@
1
- module Pagoda
2
- VERSION = "0.3.2"
3
- end
data/lib/pagoda.rb DELETED
@@ -1,3 +0,0 @@
1
- module Pagoda
2
-
3
- end
data/spec/base.rb DELETED
@@ -1,21 +0,0 @@
1
- require 'webmock/rspec'
2
-
3
- require 'pagoda/command'
4
- require 'pagoda/commands/base'
5
-
6
- Dir["#{File.dirname(__FILE__)}/../lib/pagoda/commands/*"].each { |c| require c }
7
-
8
- include WebMock::API
9
-
10
- def stub_api_request(method, path)
11
- stub_request(method, "http://www.pagodabox.com#{path}")
12
- end
13
-
14
- def prepare_command(klass)
15
- command = klass.new(['--app', 'myapp'])
16
- command.stub!(:args).and_return([])
17
- command.stub!(:display)
18
- command.stub!(:pagoda).and_return(mock('pagoda client', :host => 'pagoda.com'))
19
- command.stub!(:extract_app).and_return('myapp')
20
- command
21
- end