azuki 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +71 -0
  3. data/bin/azuki +17 -0
  4. data/data/cacert.pem +3988 -0
  5. data/lib/azuki.rb +17 -0
  6. data/lib/azuki/auth.rb +339 -0
  7. data/lib/azuki/cli.rb +38 -0
  8. data/lib/azuki/client.rb +764 -0
  9. data/lib/azuki/client/azuki_postgresql.rb +141 -0
  10. data/lib/azuki/client/cisaurus.rb +26 -0
  11. data/lib/azuki/client/pgbackups.rb +113 -0
  12. data/lib/azuki/client/rendezvous.rb +108 -0
  13. data/lib/azuki/client/ssl_endpoint.rb +25 -0
  14. data/lib/azuki/command.rb +294 -0
  15. data/lib/azuki/command/account.rb +23 -0
  16. data/lib/azuki/command/accounts.rb +34 -0
  17. data/lib/azuki/command/addons.rb +305 -0
  18. data/lib/azuki/command/apps.rb +393 -0
  19. data/lib/azuki/command/auth.rb +86 -0
  20. data/lib/azuki/command/base.rb +230 -0
  21. data/lib/azuki/command/certs.rb +209 -0
  22. data/lib/azuki/command/config.rb +137 -0
  23. data/lib/azuki/command/db.rb +218 -0
  24. data/lib/azuki/command/domains.rb +85 -0
  25. data/lib/azuki/command/drains.rb +46 -0
  26. data/lib/azuki/command/fork.rb +164 -0
  27. data/lib/azuki/command/git.rb +64 -0
  28. data/lib/azuki/command/help.rb +179 -0
  29. data/lib/azuki/command/keys.rb +115 -0
  30. data/lib/azuki/command/labs.rb +147 -0
  31. data/lib/azuki/command/logs.rb +45 -0
  32. data/lib/azuki/command/maintenance.rb +61 -0
  33. data/lib/azuki/command/pg.rb +269 -0
  34. data/lib/azuki/command/pgbackups.rb +329 -0
  35. data/lib/azuki/command/plugins.rb +110 -0
  36. data/lib/azuki/command/ps.rb +232 -0
  37. data/lib/azuki/command/regions.rb +22 -0
  38. data/lib/azuki/command/releases.rb +124 -0
  39. data/lib/azuki/command/run.rb +180 -0
  40. data/lib/azuki/command/sharing.rb +89 -0
  41. data/lib/azuki/command/ssl.rb +43 -0
  42. data/lib/azuki/command/stack.rb +62 -0
  43. data/lib/azuki/command/status.rb +51 -0
  44. data/lib/azuki/command/update.rb +47 -0
  45. data/lib/azuki/command/version.rb +23 -0
  46. data/lib/azuki/deprecated.rb +5 -0
  47. data/lib/azuki/deprecated/help.rb +38 -0
  48. data/lib/azuki/distribution.rb +9 -0
  49. data/lib/azuki/excon.rb +9 -0
  50. data/lib/azuki/helpers.rb +517 -0
  51. data/lib/azuki/helpers/azuki_postgresql.rb +165 -0
  52. data/lib/azuki/helpers/log_displayer.rb +70 -0
  53. data/lib/azuki/plugin.rb +163 -0
  54. data/lib/azuki/updater.rb +171 -0
  55. data/lib/azuki/version.rb +3 -0
  56. data/lib/vendor/azuki/okjson.rb +598 -0
  57. data/spec/azuki/auth_spec.rb +256 -0
  58. data/spec/azuki/client/azuki_postgresql_spec.rb +71 -0
  59. data/spec/azuki/client/pgbackups_spec.rb +43 -0
  60. data/spec/azuki/client/rendezvous_spec.rb +62 -0
  61. data/spec/azuki/client/ssl_endpoint_spec.rb +48 -0
  62. data/spec/azuki/client_spec.rb +564 -0
  63. data/spec/azuki/command/addons_spec.rb +601 -0
  64. data/spec/azuki/command/apps_spec.rb +351 -0
  65. data/spec/azuki/command/auth_spec.rb +38 -0
  66. data/spec/azuki/command/base_spec.rb +109 -0
  67. data/spec/azuki/command/certs_spec.rb +178 -0
  68. data/spec/azuki/command/config_spec.rb +144 -0
  69. data/spec/azuki/command/db_spec.rb +110 -0
  70. data/spec/azuki/command/domains_spec.rb +87 -0
  71. data/spec/azuki/command/drains_spec.rb +34 -0
  72. data/spec/azuki/command/fork_spec.rb +56 -0
  73. data/spec/azuki/command/git_spec.rb +144 -0
  74. data/spec/azuki/command/help_spec.rb +93 -0
  75. data/spec/azuki/command/keys_spec.rb +120 -0
  76. data/spec/azuki/command/labs_spec.rb +100 -0
  77. data/spec/azuki/command/logs_spec.rb +60 -0
  78. data/spec/azuki/command/maintenance_spec.rb +51 -0
  79. data/spec/azuki/command/pg_spec.rb +236 -0
  80. data/spec/azuki/command/pgbackups_spec.rb +307 -0
  81. data/spec/azuki/command/plugins_spec.rb +104 -0
  82. data/spec/azuki/command/ps_spec.rb +195 -0
  83. data/spec/azuki/command/releases_spec.rb +130 -0
  84. data/spec/azuki/command/run_spec.rb +83 -0
  85. data/spec/azuki/command/sharing_spec.rb +59 -0
  86. data/spec/azuki/command/stack_spec.rb +46 -0
  87. data/spec/azuki/command/status_spec.rb +48 -0
  88. data/spec/azuki/command/version_spec.rb +16 -0
  89. data/spec/azuki/command_spec.rb +211 -0
  90. data/spec/azuki/helpers/azuki_postgresql_spec.rb +155 -0
  91. data/spec/azuki/helpers_spec.rb +48 -0
  92. data/spec/azuki/plugin_spec.rb +172 -0
  93. data/spec/azuki/updater_spec.rb +44 -0
  94. data/spec/helper/legacy_help.rb +16 -0
  95. data/spec/spec.opts +1 -0
  96. data/spec/spec_helper.rb +224 -0
  97. data/spec/support/display_message_matcher.rb +49 -0
  98. data/spec/support/openssl_mock_helper.rb +8 -0
  99. metadata +211 -0
@@ -0,0 +1,137 @@
1
+ require "azuki/command/base"
2
+
3
+ # manage app config vars
4
+ #
5
+ class Azuki::Command::Config < Azuki::Command::Base
6
+
7
+ # config
8
+ #
9
+ # display the config vars for an app
10
+ #
11
+ # -s, --shell # output config vars in shell format
12
+ #
13
+ #Examples:
14
+ #
15
+ # $ azuki config
16
+ # A: one
17
+ # B: two
18
+ #
19
+ # $ azuki config --shell
20
+ # A=one
21
+ # B=two
22
+ #
23
+ def index
24
+ validate_arguments!
25
+
26
+ vars = api.get_config_vars(app).body
27
+ if vars.empty?
28
+ display("#{app} has no config vars.")
29
+ else
30
+ vars.each {|key, value| vars[key] = value.to_s}
31
+ if options[:shell]
32
+ vars.keys.sort.each do |key|
33
+ display(%{#{key}=#{vars[key]}})
34
+ end
35
+ else
36
+ styled_header("#{app} Config Vars")
37
+ styled_hash(vars)
38
+ end
39
+ end
40
+ end
41
+
42
+ # config:set KEY1=VALUE1 [KEY2=VALUE2 ...]
43
+ #
44
+ # set one or more config vars
45
+ #
46
+ #Example:
47
+ #
48
+ # $ azuki config:set A=one
49
+ # Setting config vars and restarting example... done, v123
50
+ # A: one
51
+ #
52
+ # $ azuki config:set A=one B=two
53
+ # Setting config vars and restarting example... done, v123
54
+ # A: one
55
+ # B: two
56
+ #
57
+ def set
58
+ unless args.size > 0 and args.all? { |a| a.include?('=') }
59
+ error("Usage: azuki config:set KEY1=VALUE1 [KEY2=VALUE2 ...]\nMust specify KEY and VALUE to set.")
60
+ end
61
+
62
+ vars = args.inject({}) do |vars, arg|
63
+ key, value = arg.split('=', 2)
64
+ vars[key] = value
65
+ vars
66
+ end
67
+
68
+ action("Setting config vars and restarting #{app}") do
69
+ api.put_config_vars(app, vars)
70
+
71
+ @status = begin
72
+ if release = api.get_release(app, 'current').body
73
+ release['name']
74
+ end
75
+ rescue Azuki::API::Errors::RequestFailed => e
76
+ end
77
+ end
78
+
79
+ vars.each {|key, value| vars[key] = value.to_s}
80
+ styled_hash(vars)
81
+ end
82
+
83
+ alias_command "config:add", "config:set"
84
+
85
+ # config:get KEY
86
+ #
87
+ # display a config value for an app
88
+ #
89
+ #Examples:
90
+ #
91
+ # $ azuki config:get A
92
+ # one
93
+ #
94
+ def get
95
+ unless key = shift_argument
96
+ error("Usage: azuki config:get KEY\nMust specify KEY.")
97
+ end
98
+ validate_arguments!
99
+
100
+ vars = api.get_config_vars(app).body
101
+ key, value = vars.detect {|k,v| k == key}
102
+ display(value.to_s)
103
+ end
104
+
105
+ # config:unset KEY1 [KEY2 ...]
106
+ #
107
+ # unset one or more config vars
108
+ #
109
+ # $ azuki config:unset A
110
+ # Unsetting A and restarting example... done, v123
111
+ #
112
+ # $ azuki config:unset A B
113
+ # Unsetting A and restarting example... done, v123
114
+ # Unsetting B and restarting example... done, v124
115
+ #
116
+ def unset
117
+ if args.empty?
118
+ error("Usage: azuki config:unset KEY1 [KEY2 ...]\nMust specify KEY to unset.")
119
+ end
120
+
121
+ args.each do |key|
122
+ action("Unsetting #{key} and restarting #{app}") do
123
+ api.delete_config_var(app, key)
124
+
125
+ @status = begin
126
+ if release = api.get_release(app, 'current').body
127
+ release['name']
128
+ end
129
+ rescue Azuki::API::Errors::RequestFailed => e
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ alias_command "config:remove", "config:unset"
136
+
137
+ end
@@ -0,0 +1,218 @@
1
+ require "azuki/command/base"
2
+
3
+ module Azuki::Command
4
+
5
+ # manage the database for an app
6
+ #
7
+ class Db < Base
8
+
9
+ # db:push [DATABASE_URL]
10
+ #
11
+ # push local data up to your app
12
+ #
13
+ # DATABASE_URL should reference your local database. if not specified, it
14
+ # will be guessed from config/database.yml
15
+ #
16
+ # -c, --chunksize SIZE # specify the number of rows to send in each batch
17
+ # -d, --debug # enable debugging output
18
+ # -e, --exclude TABLES # exclude the specified tables from the push
19
+ # -f, --filter REGEX # only push certain tables
20
+ # -r, --resume FILE # resume transfer described by a .dat file
21
+ # -t, --tables TABLES # only push the specified tables
22
+ #
23
+ def push
24
+ load_taps
25
+ opts = parse_taps_opts
26
+
27
+ display("Warning: Data in the app '#{app}' will be overwritten and will not be recoverable.")
28
+
29
+ if confirm_command
30
+ taps_client(:push, opts)
31
+ end
32
+ end
33
+
34
+ # db:pull [DATABASE_URL]
35
+ #
36
+ # pull azuki data down into your local database
37
+ #
38
+ # DATABASE_URL should reference your local database. if not specified, it
39
+ # will be guessed from config/database.yml
40
+ #
41
+ # -c, --chunksize SIZE # specify the number of rows to send in each batch
42
+ # -d, --debug # enable debugging output
43
+ # -e, --exclude TABLES # exclude the specified tables from the pull
44
+ # -f, --filter REGEX # only pull certain tables
45
+ # -r, --resume FILE # resume transfer described by a .dat file
46
+ # -t, --tables TABLES # only pull the specified tables
47
+ #
48
+ def pull
49
+ load_taps
50
+ opts = parse_taps_opts
51
+
52
+ display("Warning: Data in the database '#{opts[:database_url]}' will be overwritten and will not be recoverable.")
53
+
54
+ if confirm_command
55
+ taps_client(:pull, opts)
56
+ end
57
+ end
58
+
59
+ protected
60
+
61
+ def parse_database_yml
62
+ return "" unless File.exists?(Dir.pwd + '/config/database.yml')
63
+ require 'erb'
64
+ require 'yaml'
65
+
66
+ environment = ENV['RAILS_ENV'] || ENV['MERB_ENV'] || ENV['RACK_ENV']
67
+ environment = 'development' if environment.nil? or environment.empty?
68
+
69
+ conf = YAML.load(ERB.new(File.read(Dir.pwd + '/config/database.yml')).result)[environment]
70
+ case conf['adapter']
71
+ when 'sqlite3'
72
+ return "sqlite://#{conf['database']}"
73
+ when 'postgresql'
74
+ uri_hash = conf_to_uri_hash(conf)
75
+ uri_hash['scheme'] = 'postgres'
76
+ return uri_hash_to_url(uri_hash)
77
+ else
78
+ return uri_hash_to_url(conf_to_uri_hash(conf))
79
+ end
80
+ rescue Exception => ex
81
+ puts "Error parsing database.yml: #{ex.message}"
82
+ puts ex.backtrace
83
+ ""
84
+ end
85
+
86
+ def conf_to_uri_hash(conf)
87
+ uri = {}
88
+ uri['scheme'] = conf['adapter']
89
+ uri['username'] = conf['user'] || conf['username']
90
+ uri['password'] = conf['password']
91
+ uri['host'] = conf['host'] || conf['hostname'] || '127.0.0.1'
92
+ uri['port'] = conf['port']
93
+ uri['path'] = conf['database']
94
+
95
+ conf['encoding'] = 'utf8' if conf['encoding'] == 'unicode' or conf['encoding'].nil?
96
+ uri['query'] = "encoding=#{conf['encoding']}"
97
+
98
+ uri
99
+ end
100
+
101
+ def userinfo_from_uri(uri)
102
+ username = uri['username'].to_s
103
+ password = uri['password'].to_s
104
+ return nil if username == ''
105
+
106
+ userinfo = ""
107
+ userinfo << username
108
+ userinfo << ":" << password if password.length > 0
109
+ userinfo
110
+ end
111
+
112
+ def uri_hash_to_url(uri)
113
+ host = uri['host']
114
+ host ||= '127.0.0.1' unless uri['scheme'] == 'postgres'
115
+ uri_parts = {
116
+ :scheme => uri['scheme'],
117
+ :userinfo => userinfo_from_uri(uri),
118
+ :password => uri['password'],
119
+ :host => host,
120
+ :port => uri['port'],
121
+ :path => "/%s" % uri['path'],
122
+ :query => uri['query'],
123
+ }
124
+
125
+ URI::Generic.build(uri_parts).to_s
126
+ end
127
+
128
+ def parse_taps_opts
129
+ opts = {}
130
+ opts[:default_chunksize] = options[:chunksize] || 1000
131
+ opts[:default_chunksize] = opts[:default_chunksize].to_i rescue 1000
132
+
133
+ if filter = options[:filter]
134
+ opts[:table_filter] = filter
135
+ elsif tables = options[:tables]
136
+ r_tables = tables.split(",").collect { |t| "^#{t.strip}$" }
137
+ opts[:table_filter] = "(#{r_tables.join("|")})"
138
+ end
139
+
140
+ if options[:'disable-compression']
141
+ opts[:disable_compression] = true
142
+ end
143
+
144
+ if excluded_tables = options[:exclude]
145
+ opts[:exclude_tables] = excluded_tables
146
+ end
147
+
148
+ if resume_file = options[:resume]
149
+ opts[:resume_filename] = resume_file
150
+ end
151
+
152
+ opts[:indexes_first] = !options[:'indexes-last']
153
+
154
+ opts[:database_url] = args.detect { |a| URI.parse(a).scheme } rescue nil
155
+
156
+ unless opts[:database_url]
157
+ opts[:database_url] = parse_database_yml
158
+ display "Auto-detected local database: #{opts[:database_url]}" if opts[:database_url] != ''
159
+ end
160
+ raise(CommandFailed, "Invalid database url") if opts[:database_url] == ''
161
+
162
+ if options[:debug]
163
+ require 'logger'
164
+ Taps.log.level = Logger::DEBUG
165
+ end
166
+
167
+ # setting local timezone equal to Azuki timezone allowing TAPS to
168
+ # correctly transfer datetime fields between databases
169
+ ENV['TZ'] = 'America/Los_Angeles'
170
+ opts
171
+ end
172
+
173
+ def taps_client(op, opts)
174
+ Taps::Config.verify_database_url(opts[:database_url])
175
+ if opts[:resume_filename]
176
+ Taps::Cli.new([]).clientresumexfer(op, opts)
177
+ else
178
+ info = azuki.database_session(app)
179
+
180
+ replacement_host = case
181
+ when ENV["TAPS_HOST"] then ENV["TAPS_HOST"]
182
+ when RUBY_VERSION >= '1.9' then "taps19.azukiapp.com"
183
+ else nil
184
+ end
185
+
186
+ if replacement_host
187
+ info["url"].gsub!("taps3.azukiapp.com", replacement_host)
188
+ end
189
+
190
+ opts[:remote_url] = info['url']
191
+ opts[:session_uri] = info['session']
192
+ Taps::Cli.new([]).clientxfer(op, opts)
193
+ end
194
+ end
195
+
196
+ def enforce_taps_version(version)
197
+ unless Taps.version >= version
198
+ error "Your taps gem is out of date (v#{version} or higher required)."
199
+ end
200
+ end
201
+
202
+ def load_taps
203
+ begin
204
+ require "rubygems"
205
+ rescue LoadError
206
+ end
207
+ require 'taps/operation'
208
+ require 'taps/cli'
209
+ enforce_taps_version "0.3.23"
210
+ display "Loaded Taps v#{Taps.version}"
211
+ rescue LoadError
212
+ message = "Taps Load Error: #{$!.message}\n"
213
+ message << "You may need to install or update the taps gem to use db commands.\n"
214
+ message << "On most systems this will be:\n\nsudo gem install taps"
215
+ error message
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,85 @@
1
+ require "azuki/command/base"
2
+
3
+ module Azuki::Command
4
+
5
+ # manage custom domains
6
+ #
7
+ class Domains < Base
8
+
9
+ # domains
10
+ #
11
+ # list custom domains for an app
12
+ #
13
+ #Examples:
14
+ #
15
+ # $ azuki domains
16
+ # === Domain names for example
17
+ # example.com
18
+ #
19
+ def index
20
+ validate_arguments!
21
+ domains = api.get_domains(app).body
22
+ if domains.length > 0
23
+ styled_header("#{app} Domain Names")
24
+ styled_array domains.map {|domain| domain["domain"]}
25
+ else
26
+ display("#{app} has no domain names.")
27
+ end
28
+ end
29
+
30
+ # domains:add DOMAIN
31
+ #
32
+ # add a custom domain to an app
33
+ #
34
+ #Examples:
35
+ #
36
+ # $ azuki domains:add example.com
37
+ # Adding example.com to example... done
38
+ #
39
+ def add
40
+ unless domain = shift_argument
41
+ error("Usage: azuki domains:add DOMAIN\nMust specify DOMAIN to add.")
42
+ end
43
+ validate_arguments!
44
+ action("Adding #{domain} to #{app}") do
45
+ api.post_domain(app, domain)
46
+ end
47
+ end
48
+
49
+ # domains:remove DOMAIN
50
+ #
51
+ # remove a custom domain from an app
52
+ #
53
+ #Examples:
54
+ #
55
+ # $ azuki domains:remove example.com
56
+ # Removing example.com from example... done
57
+ #
58
+ def remove
59
+ unless domain = shift_argument
60
+ error("Usage: azuki domains:remove DOMAIN\nMust specify DOMAIN to remove.")
61
+ end
62
+ validate_arguments!
63
+ action("Removing #{domain} from #{app}") do
64
+ api.delete_domain(app, domain)
65
+ end
66
+ end
67
+
68
+ # domains:clear
69
+ #
70
+ # remove all custom domains from an app
71
+ #
72
+ #Examples:
73
+ #
74
+ # $ azuki domains:clear
75
+ # Removing all domain names for example... done
76
+ #
77
+ def clear
78
+ validate_arguments!
79
+ action("Removing all domain names from #{app}") do
80
+ api.delete_domains(app)
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,46 @@
1
+ require "azuki/command/base"
2
+
3
+ module Azuki::Command
4
+
5
+ # display syslog drains for an app
6
+ #
7
+ class Drains < Base
8
+
9
+ # drains
10
+ #
11
+ # list all syslog drains
12
+ #
13
+ def index
14
+ puts azuki.list_drains(app)
15
+ return
16
+ end
17
+
18
+ # drains:add URL
19
+ #
20
+ # add a syslog drain
21
+ #
22
+ def add
23
+ if url = args.shift
24
+ puts azuki.add_drain(app, url)
25
+ return
26
+ else
27
+ error("Usage: azuki drains:add URL")
28
+ end
29
+ end
30
+
31
+ # drains:remove URL
32
+ #
33
+ # remove a syslog drain
34
+ #
35
+ def remove
36
+ if url = args.shift
37
+ puts azuki.remove_drain(app, url)
38
+ return
39
+ else
40
+ error("Usage: azuki drains remove URL")
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+