azuki 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.
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
+