aptible-cli 0.14.1 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -1
  3. data/aptible-cli.gemspec +1 -0
  4. data/bin/aptible +9 -5
  5. data/lib/aptible/cli.rb +36 -0
  6. data/lib/aptible/cli/agent.rb +10 -6
  7. data/lib/aptible/cli/error.rb +6 -0
  8. data/lib/aptible/cli/formatter.rb +21 -0
  9. data/lib/aptible/cli/formatter/grouped_keyed_list.rb +54 -0
  10. data/lib/aptible/cli/formatter/keyed_list.rb +25 -0
  11. data/lib/aptible/cli/formatter/keyed_object.rb +16 -0
  12. data/lib/aptible/cli/formatter/list.rb +33 -0
  13. data/lib/aptible/cli/formatter/node.rb +8 -0
  14. data/lib/aptible/cli/formatter/object.rb +38 -0
  15. data/lib/aptible/cli/formatter/root.rb +46 -0
  16. data/lib/aptible/cli/formatter/value.rb +25 -0
  17. data/lib/aptible/cli/helpers/app.rb +1 -0
  18. data/lib/aptible/cli/helpers/database.rb +22 -6
  19. data/lib/aptible/cli/helpers/operation.rb +3 -2
  20. data/lib/aptible/cli/helpers/tunnel.rb +1 -3
  21. data/lib/aptible/cli/helpers/vhost.rb +9 -46
  22. data/lib/aptible/cli/renderer.rb +26 -0
  23. data/lib/aptible/cli/renderer/base.rb +8 -0
  24. data/lib/aptible/cli/renderer/json.rb +26 -0
  25. data/lib/aptible/cli/renderer/text.rb +99 -0
  26. data/lib/aptible/cli/resource_formatter.rb +136 -0
  27. data/lib/aptible/cli/subcommands/apps.rb +26 -14
  28. data/lib/aptible/cli/subcommands/backup.rb +22 -4
  29. data/lib/aptible/cli/subcommands/config.rb +15 -11
  30. data/lib/aptible/cli/subcommands/db.rb +82 -31
  31. data/lib/aptible/cli/subcommands/deploy.rb +1 -1
  32. data/lib/aptible/cli/subcommands/endpoints.rb +11 -8
  33. data/lib/aptible/cli/subcommands/operation.rb +2 -1
  34. data/lib/aptible/cli/subcommands/rebuild.rb +1 -1
  35. data/lib/aptible/cli/subcommands/restart.rb +1 -1
  36. data/lib/aptible/cli/subcommands/services.rb +8 -9
  37. data/lib/aptible/cli/version.rb +1 -1
  38. data/spec/aptible/cli/agent_spec.rb +11 -14
  39. data/spec/aptible/cli/formatter_spec.rb +4 -0
  40. data/spec/aptible/cli/renderer/json_spec.rb +63 -0
  41. data/spec/aptible/cli/renderer/text_spec.rb +150 -0
  42. data/spec/aptible/cli/resource_formatter_spec.rb +113 -0
  43. data/spec/aptible/cli/subcommands/apps_spec.rb +144 -28
  44. data/spec/aptible/cli/subcommands/backup_spec.rb +37 -16
  45. data/spec/aptible/cli/subcommands/config_spec.rb +95 -0
  46. data/spec/aptible/cli/subcommands/db_spec.rb +185 -93
  47. data/spec/aptible/cli/subcommands/endpoints_spec.rb +10 -8
  48. data/spec/aptible/cli/subcommands/operation_spec.rb +0 -1
  49. data/spec/aptible/cli/subcommands/rebuild_spec.rb +17 -0
  50. data/spec/aptible/cli/subcommands/services_spec.rb +8 -12
  51. data/spec/aptible/cli_spec.rb +31 -0
  52. data/spec/fabricators/account_fabricator.rb +11 -0
  53. data/spec/fabricators/app_fabricator.rb +15 -0
  54. data/spec/fabricators/configuration_fabricator.rb +8 -0
  55. data/spec/fabricators/database_image_fabricator.rb +17 -0
  56. data/spec/fabricators/operation_fabricator.rb +1 -0
  57. data/spec/fabricators/service_fabricator.rb +4 -0
  58. data/spec/spec_helper.rb +63 -1
  59. metadata +55 -4
  60. data/spec/aptible/cli/helpers/vhost_spec.rb +0 -105
@@ -11,12 +11,19 @@ module Aptible
11
11
  desc 'apps', 'List all applications'
12
12
  option :environment
13
13
  def apps
14
- scoped_environments(options).each do |env|
15
- say "=== #{env.handle}"
16
- env.apps.each do |app|
17
- say app.handle
14
+ Formatter.render(Renderer.current) do |root|
15
+ root.grouped_keyed_list(
16
+ { 'environment' => 'handle' },
17
+ 'handle'
18
+ ) do |node|
19
+ scoped_environments(options).each do |account|
20
+ account.each_app do |app|
21
+ node.object do |n|
22
+ ResourceFormatter.inject_app(n, app, account)
23
+ end
24
+ end
25
+ end
18
26
  end
19
- say ''
20
27
  end
21
28
  end
22
29
 
@@ -29,8 +36,13 @@ module Aptible
29
36
  if app.errors.any?
30
37
  raise Thor::Error, app.errors.full_messages.first
31
38
  else
32
- say "App #{handle} created!"
33
- say "Git remote: #{app.git_repo}"
39
+ CLI.logger.info "App #{handle} created!"
40
+
41
+ Formatter.render(Renderer.current) do |root|
42
+ root.object do |o|
43
+ o.value('git_remote', app.git_repo)
44
+ end
45
+ end
34
46
  end
35
47
  end
36
48
 
@@ -56,9 +68,9 @@ module Aptible
56
68
  # Noop
57
69
  when 1
58
70
  if container_count.nil?
59
- m = yellow('Passing container count as a positional ' \
60
- 'argument is deprecated, use --container-count')
61
- $stderr.puts m
71
+ m = 'Passing container count as a positional ' \
72
+ 'argument is deprecated, use --container-count'
73
+ CLI.logger.warn(m)
62
74
  container_count = Integer(more.first)
63
75
  else
64
76
  raise Thor::Error, 'Container count was passed via both ' \
@@ -77,9 +89,9 @@ module Aptible
77
89
 
78
90
  if options[:size]
79
91
  if container_size.nil?
80
- m = yellow('Passing container size via the --size keyword ' \
81
- 'argument is deprecated, use --container-size')
82
- $stderr.puts m
92
+ m = 'Passing container size via the --size keyword ' \
93
+ 'argument is deprecated, use --container-size'
94
+ CLI.logger.warn(m)
83
95
  container_size = options[:size]
84
96
  else
85
97
  raise Thor::Error, 'Container size was passed via both ' \
@@ -106,7 +118,7 @@ module Aptible
106
118
  app_options
107
119
  define_method 'apps:deprovision' do
108
120
  app = ensure_app(options)
109
- say "Deprovisioning #{app.handle}..."
121
+ CLI.logger.info "Deprovisioning #{app.handle}..."
110
122
  app.create_operation!(type: 'deprovision')
111
123
  end
112
124
  end
@@ -40,8 +40,13 @@ module Aptible
40
40
  }.delete_if { |_, v| v.nil? }
41
41
 
42
42
  operation = backup.create_operation!(opts)
43
- say "Restoring backup into #{handle}"
43
+ CLI.logger.info "Restoring backup into #{handle}"
44
44
  attach_to_operation_logs(operation)
45
+
46
+ account = destination_account || backup.account
47
+
48
+ database = databases_from_handle(handle, account).first
49
+ render_database(database, account)
45
50
  end
46
51
 
47
52
  desc 'backup:list DB_HANDLE', 'List backups for a database'
@@ -55,9 +60,22 @@ module Aptible
55
60
  min_created_at = Time.now - age
56
61
 
57
62
  database = ensure_database(options.merge(db: handle))
58
- database.each_backup do |backup|
59
- break if backup.created_at < min_created_at
60
- say "#{backup.id}: #{backup.created_at}, #{backup.aws_region}"
63
+
64
+ Formatter.render(Renderer.current) do |root|
65
+ root.keyed_list('description') do |l|
66
+ database.each_backup do |backup|
67
+ break if backup.created_at < min_created_at
68
+ description = "#{backup.id}: #{backup.created_at}, " \
69
+ "#{backup.aws_region}"
70
+
71
+ l.object do |o|
72
+ o.value('id', backup.id)
73
+ o.value('description', description)
74
+ o.value('created_at', backup.created_at)
75
+ o.value('region', backup.aws_region)
76
+ end
77
+ end
78
+ end
61
79
  end
62
80
  end
63
81
  end
@@ -14,8 +14,19 @@ module Aptible
14
14
  def config
15
15
  app = ensure_app(options)
16
16
  config = app.current_configuration
17
- env = config ? config.env : nil
18
- puts formatted_config(env || {})
17
+ env = config ? config.env : {}
18
+
19
+ Formatter.render(Renderer.current) do |root|
20
+ root.keyed_list('shell_export') do |list|
21
+ env.each_pair do |k, v|
22
+ list.object do |node|
23
+ node.value('key', k)
24
+ node.value('value', v)
25
+ node.value('shell_export', "#{k}=#{Shellwords.escape(v)}")
26
+ end
27
+ end
28
+ end
29
+ end
19
30
  end
20
31
 
21
32
  desc 'config:add', 'Add an ENV variable to an app'
@@ -25,7 +36,7 @@ module Aptible
25
36
  app = ensure_app(options)
26
37
  env = extract_env(args)
27
38
  operation = app.create_operation!(type: 'configure', env: env)
28
- puts 'Updating configuration and restarting app...'
39
+ CLI.logger.info 'Updating configuration and restarting app...'
29
40
  attach_to_operation_logs(operation)
30
41
  end
31
42
 
@@ -45,7 +56,7 @@ module Aptible
45
56
  [arg, '']
46
57
  end]
47
58
  operation = app.create_operation!(type: 'configure', env: env)
48
- puts 'Updating configuration and restarting app...'
59
+ CLI.logger.info 'Updating configuration and restarting app...'
49
60
  attach_to_operation_logs(operation)
50
61
  end
51
62
 
@@ -54,13 +65,6 @@ module Aptible
54
65
  define_method 'config:unset' do |*args|
55
66
  send('config:rm', *args)
56
67
  end
57
-
58
- private
59
-
60
- def formatted_config(env)
61
- env = Hash[env.sort]
62
- env.map { |k, v| "#{k}=#{Shellwords.escape(v)}" }.join("\n")
63
- end
64
68
  end
65
69
  end
66
70
  end
@@ -15,28 +15,73 @@ module Aptible
15
15
  desc 'db:list', 'List all databases'
16
16
  option :environment
17
17
  define_method 'db:list' do
18
- scoped_environments(options).each do |env|
19
- present_environment_databases(env)
18
+ Formatter.render(Renderer.current) do |root|
19
+ root.grouped_keyed_list(
20
+ { 'environment' => 'handle' },
21
+ 'handle'
22
+ ) do |node|
23
+ scoped_environments(options).each do |account|
24
+ account.each_database do |db|
25
+ node.object do |n|
26
+ ResourceFormatter.inject_database(n, db, account)
27
+ end
28
+ end
29
+ end
30
+ end
20
31
  end
21
32
  end
22
33
 
23
- desc 'db:create HANDLE' \
24
- '[--type TYPE] [--container-size SIZE_MB] [--size SIZE_GB]',
34
+ desc 'db:versions', 'List available database versions'
35
+ define_method 'db:versions' do
36
+ Formatter.render(Renderer.current) do |root|
37
+ root.grouped_keyed_list('type', 'version') do |node|
38
+ Aptible::Api::DatabaseImage.all(
39
+ token: fetch_token
40
+ ).each do |database_image|
41
+ node.object do |n|
42
+ n.value('type', database_image.type)
43
+ n.value('version', database_image.version)
44
+ n.value('default', database_image.default)
45
+ n.value('description', database_image.description)
46
+ n.value('docker_repo', database_image.default)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ desc 'db:create HANDLE ' \
54
+ '[--type TYPE] [--version VERSION] ' \
55
+ '[--container-size SIZE_MB] [--size SIZE_GB]',
25
56
  'Create a new database'
26
- option :type, default: 'postgresql'
57
+ option :type, type: :string
58
+ option :version, type: :string
27
59
  option :container_size, type: :numeric
28
60
  option :size, default: 10, type: :numeric
29
61
  option :environment
30
62
  define_method 'db:create' do |handle|
31
- environment = ensure_environment(options)
63
+ account = ensure_environment(options)
32
64
 
33
65
  db_opts = {
34
66
  handle: handle,
35
- type: options[:type],
36
67
  initial_container_size: options[:container_size],
37
68
  initial_disk_size: options[:size]
38
69
  }.delete_if { |_, v| v.nil? }
39
- database = environment.create_database!(db_opts)
70
+
71
+ type = options[:type]
72
+ version = options[:version]
73
+
74
+ if version && type
75
+ image = find_database_image(type, version)
76
+ db_opts[:type] = image.type
77
+ db_opts[:database_image] = image
78
+ elsif version
79
+ raise Thor::Error, '--type is required when passing --version'
80
+ else
81
+ db_opts[:type] = type || 'postgresql'
82
+ end
83
+
84
+ database = account.create_database!(db_opts)
40
85
 
41
86
  op_opts = {
42
87
  type: 'provision',
@@ -55,15 +100,17 @@ module Aptible
55
100
  end
56
101
 
57
102
  attach_to_operation_logs(op)
58
- say database.reload.connection_url
103
+
104
+ render_database(database.reload, account)
59
105
  end
60
106
 
61
107
  desc 'db:clone SOURCE DEST', 'Clone a database to create a new one'
62
108
  option :environment
63
109
  define_method 'db:clone' do |source_handle, dest_handle|
110
+ # TODO: Deprecate + recommend backup
64
111
  source = ensure_database(options.merge(db: source_handle))
65
- dest = clone_database(source, dest_handle)
66
- say dest.connection_url
112
+ database = clone_database(source, dest_handle)
113
+ render_database(database, database.account)
67
114
  end
68
115
 
69
116
  desc 'db:dump HANDLE', 'Dump a remote database to file'
@@ -72,7 +119,7 @@ module Aptible
72
119
  database = ensure_database(options.merge(db: handle))
73
120
  with_postgres_tunnel(database) do |url|
74
121
  filename = "#{handle}.dump"
75
- say "Dumping to #{filename}"
122
+ CLI.logger.info "Dumping to #{filename}"
76
123
  `pg_dump #{url} > #{filename}`
77
124
  end
78
125
  end
@@ -82,7 +129,7 @@ module Aptible
82
129
  define_method 'db:execute' do |handle, sql_path|
83
130
  database = ensure_database(options.merge(db: handle))
84
131
  with_postgres_tunnel(database) do |url|
85
- say "Executing #{sql_path} against #{handle}"
132
+ CLI.logger.info "Executing #{sql_path} against #{handle}"
86
133
  `psql #{url} < #{sql_path}`
87
134
  end
88
135
  end
@@ -97,37 +144,37 @@ module Aptible
97
144
 
98
145
  credential = find_credential(database, options[:type])
99
146
 
100
- say "Creating #{credential.type} tunnel to #{database.handle}...",
101
- :green
147
+ m = "Creating #{credential.type} tunnel to #{database.handle}..."
148
+ CLI.logger.info m
102
149
 
103
150
  if options[:type].nil?
104
151
  types = database.database_credentials.map(&:type)
105
152
  unless types.empty?
106
153
  valid = types.join(', ')
107
- say 'Use --type TYPE to specify a tunnel type', :green
108
- say "Valid types for #{database.handle}: #{valid}", :green
154
+ CLI.logger.info 'Use --type TYPE to specify a tunnel type'
155
+ CLI.logger.info "Valid types for #{database.handle}: #{valid}"
109
156
  end
110
157
  end
111
158
 
112
159
  with_local_tunnel(credential, desired_port) do |tunnel_helper|
113
160
  port = tunnel_helper.port
114
- say "Connect at #{local_url(credential, port)}", :green
161
+ CLI.logger.info "Connect at #{local_url(credential, port)}"
115
162
 
116
163
  uri = URI(local_url(credential, port))
117
164
  db = uri.path.gsub(%r{^/}, '')
118
- say 'Or, use the following arguments:', :green
119
- say("* Host: #{uri.host}", :green)
120
- say("* Port: #{uri.port}", :green)
121
- say("* Username: #{uri.user}", :green) unless uri.user.empty?
122
- say("* Password: #{uri.password}", :green)
123
- say("* Database: #{db}", :green) unless db.empty?
165
+ CLI.logger.info 'Or, use the following arguments:'
166
+ CLI.logger.info "* Host: #{uri.host}"
167
+ CLI.logger.info "* Port: #{uri.port}"
168
+ CLI.logger.info "* Username: #{uri.user}" unless uri.user.empty?
169
+ CLI.logger.info "* Password: #{uri.password}"
170
+ CLI.logger.info "* Database: #{db}" unless db.empty?
124
171
 
125
- say 'Connected. Ctrl-C to close connection.'
172
+ CLI.logger.info 'Connected. Ctrl-C to close connection.'
126
173
 
127
174
  begin
128
175
  tunnel_helper.wait
129
176
  rescue Interrupt
130
- say 'Closing tunnel'
177
+ CLI.logger.warn 'Closing tunnel'
131
178
  end
132
179
  end
133
180
  end
@@ -136,7 +183,7 @@ module Aptible
136
183
  option :environment
137
184
  define_method 'db:deprovision' do |handle|
138
185
  database = ensure_database(options.merge(db: handle))
139
- say "Deprovisioning #{database.handle}..."
186
+ CLI.logger.info "Deprovisioning #{database.handle}..."
140
187
  database.create_operation!(type: 'deprovision')
141
188
  end
142
189
 
@@ -144,7 +191,7 @@ module Aptible
144
191
  option :environment
145
192
  define_method 'db:backup' do |handle|
146
193
  database = ensure_database(options.merge(db: handle))
147
- say "Backing up #{database.handle}..."
194
+ CLI.logger.info "Backing up #{database.handle}..."
148
195
  op = database.create_operation!(type: 'backup')
149
196
  attach_to_operation_logs(op)
150
197
  end
@@ -153,7 +200,7 @@ module Aptible
153
200
  option :environment
154
201
  define_method 'db:reload' do |handle|
155
202
  database = ensure_database(options.merge(db: handle))
156
- say "Reloading #{database.handle}..."
203
+ CLI.logger.info "Reloading #{database.handle}..."
157
204
  op = database.create_operation!(type: 'reload')
158
205
  attach_to_operation_logs(op)
159
206
  end
@@ -173,7 +220,7 @@ module Aptible
173
220
  disk_size: options[:size]
174
221
  }.delete_if { |_, v| v.nil? }
175
222
 
176
- say "Restarting #{database.handle}..."
223
+ CLI.logger.info "Restarting #{database.handle}..."
177
224
  op = database.create_operation!(opts)
178
225
  attach_to_operation_logs(op)
179
226
  end
@@ -185,7 +232,11 @@ module Aptible
185
232
  database = ensure_database(options.merge(db: handle))
186
233
  credential = find_credential(database, options[:type])
187
234
 
188
- say(credential.connection_url)
235
+ Formatter.render(Renderer.current) do |root|
236
+ root.keyed_object('connection_url') do |node|
237
+ node.value('connection_url', credential.connection_url)
238
+ end
239
+ end
189
240
  end
190
241
  end
191
242
  end
@@ -78,7 +78,7 @@ module Aptible
78
78
 
79
79
  operation = app.create_operation!(opts)
80
80
 
81
- puts 'Deploying app...'
81
+ CLI.logger.info 'Deploying app...'
82
82
  attach_to_operation_logs(operation)
83
83
  end
84
84
  end
@@ -127,12 +127,15 @@ module Aptible
127
127
  define_method 'endpoints:list' do
128
128
  resource = ensure_app_or_database(options)
129
129
 
130
- first = true
131
- each_vhost(resource) do |service|
132
- service.each_vhost do |vhost|
133
- say '' unless first
134
- first = false
135
- explain_vhost(service, vhost)
130
+ Formatter.render(Renderer.current) do |root|
131
+ root.list do |list|
132
+ each_service(resource) do |service|
133
+ service.each_vhost do |vhost|
134
+ list.object do |node|
135
+ ResourceFormatter.inject_vhost(node, vhost, service)
136
+ end
137
+ end
138
+ end
136
139
  end
137
140
  end
138
141
  end
@@ -143,7 +146,7 @@ module Aptible
143
146
  app_or_database_options
144
147
  define_method 'endpoints:deprovision' do |hostname|
145
148
  resource = ensure_app_or_database(options)
146
- vhost = find_vhost(each_vhost(resource), hostname)
149
+ vhost = find_vhost(each_service(resource), hostname)
147
150
  op = vhost.create_operation!(type: 'deprovision')
148
151
  attach_to_operation_logs(op)
149
152
  end
@@ -170,7 +173,7 @@ module Aptible
170
173
 
171
174
  def modify_app_vhost(flags, options, hostname)
172
175
  app = ensure_app(options)
173
- vhost = find_vhost(each_vhost(app), hostname)
176
+ vhost = find_vhost(each_service(app), hostname)
174
177
  vhost.update!(**flags.prepare(vhost.service.account, options))
175
178
  provision_vhost_and_explain(vhost.service, vhost)
176
179
  end
@@ -12,7 +12,8 @@ module Aptible
12
12
  o = Aptible::Api::Operation.find(operation_id, token: fetch_token)
13
13
  raise "Operation ##{operation_id} not found" if o.nil?
14
14
 
15
- puts "Requesting cancellation on #{prettify_operation(o)}..."
15
+ m = "Requesting cancellation on #{prettify_operation(o)}..."
16
+ CLI.logger.info m
16
17
  o.update!(cancelled: true)
17
18
  end
18
19
  end
@@ -12,7 +12,7 @@ module Aptible
12
12
  def rebuild
13
13
  app = ensure_app(options)
14
14
  operation = app.create_operation!(type: 'rebuild')
15
- puts 'Rebuilding app...'
15
+ CLI.logger.info 'Rebuilding app...'
16
16
  attach_to_operation_logs(operation)
17
17
  end
18
18
  end