aptible-cli 0.14.1 → 0.15.0

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 (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