aptible-cli 0.24.1 → 0.24.3

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +16 -1
  3. data/Gemfile.lock +1 -1
  4. data/lib/aptible/cli/agent.rb +9 -1
  5. data/lib/aptible/cli/helpers/app.rb +16 -1
  6. data/lib/aptible/cli/helpers/database.rb +20 -5
  7. data/lib/aptible/cli/helpers/environment.rb +28 -3
  8. data/lib/aptible/cli/helpers/operation.rb +11 -4
  9. data/lib/aptible/cli/helpers/telemetry.rb +58 -0
  10. data/lib/aptible/cli/helpers/token.rb +6 -0
  11. data/lib/aptible/cli/renderer.rb +5 -1
  12. data/lib/aptible/cli/resource_formatter.rb +7 -0
  13. data/lib/aptible/cli/subcommands/apps.rb +31 -2
  14. data/lib/aptible/cli/subcommands/backup.rb +9 -0
  15. data/lib/aptible/cli/subcommands/backup_retention_policy.rb +5 -0
  16. data/lib/aptible/cli/subcommands/config.rb +11 -0
  17. data/lib/aptible/cli/subcommands/db.rb +68 -3
  18. data/lib/aptible/cli/subcommands/deploy.rb +3 -0
  19. data/lib/aptible/cli/subcommands/endpoints.rb +26 -0
  20. data/lib/aptible/cli/subcommands/environment.rb +11 -0
  21. data/lib/aptible/cli/subcommands/log_drain.rb +26 -5
  22. data/lib/aptible/cli/subcommands/logs.rb +5 -0
  23. data/lib/aptible/cli/subcommands/maintenance.rb +5 -0
  24. data/lib/aptible/cli/subcommands/metric_drain.rb +25 -5
  25. data/lib/aptible/cli/subcommands/operation.rb +7 -0
  26. data/lib/aptible/cli/subcommands/rebuild.rb +3 -0
  27. data/lib/aptible/cli/subcommands/restart.rb +3 -0
  28. data/lib/aptible/cli/subcommands/services.rb +12 -3
  29. data/lib/aptible/cli/subcommands/ssh.rb +3 -0
  30. data/lib/aptible/cli/version.rb +1 -1
  31. data/spec/aptible/cli/subcommands/apps_spec.rb +13 -1
  32. data/spec/aptible/cli/subcommands/config_spec.rb +4 -2
  33. data/spec/aptible/cli/subcommands/db_spec.rb +7 -1
  34. data/spec/aptible/cli/subcommands/endpoints_spec.rb +9 -3
  35. data/spec/aptible/cli/subcommands/environment_spec.rb +3 -1
  36. data/spec/aptible/cli/subcommands/log_drain_spec.rb +15 -2
  37. data/spec/aptible/cli/subcommands/maintenance_spec.rb +3 -2
  38. data/spec/aptible/cli/subcommands/metric_drain_spec.rb +15 -2
  39. data/spec/aptible/cli/subcommands/operation_spec.rb +4 -2
  40. data/spec/aptible/cli/subcommands/services_spec.rb +3 -1
  41. data/spec/fabricators/account_fabricator.rb +8 -0
  42. data/spec/fabricators/app_fabricator.rb +9 -0
  43. data/spec/fabricators/database_fabricator.rb +8 -0
  44. data/spec/fabricators/log_drain_fabricator.rb +8 -0
  45. data/spec/fabricators/metric_drain_fabricator.rb +8 -0
  46. data/spec/spec_helper.rb +9 -1
  47. metadata +6 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57fd2762ecb6e2608548c08837ffa4c9cc3b233b4c4c12aae197001acd6c090a
4
- data.tar.gz: a9612cf83e1fbd680b6b0cc41daab09a7028f581b3f52bb6663b176e77b9a321
3
+ metadata.gz: f23bd0dde480e09b51d6876d15ff0628739d632197ae600bc2bc567bd54d7ee9
4
+ data.tar.gz: 7c2952e17c06a6c9593a5cbcbb3898e333d2c5e2a6d32bff053cd42798e87287
5
5
  SHA512:
6
- metadata.gz: b044b0ead595595831ec2608c48cfe0782de00272f424b21c5e2c4c8f384d2d3778d5f1aa331ff3e55e77e3670582f49d13245ca10b77a6986efc4d360fb847e
7
- data.tar.gz: 0a8a57cfd54a701edd13355b3048d570d0ba6a8f4d64199c95ec24130c34e1baf7a08afc79142df48f6d3cc37099a75bb452a7ae22c32c3dac53a753b9ce17f6
6
+ metadata.gz: b41752dc657d784ed2cc6da0ed4bc2c54f730adc59b02de4120d30aed2c6626b93a25b6049551051a17f1f6c841c9cac200f70eb480c1330c37543440e3d431e
7
+ data.tar.gz: 0f3a5c06f634a87ff7768914f8d383a86bbee84a4c6dc511797f752bdc777f29bd4882d46e8b93e53124d35fba16e1a0589cae9355e16c17392c9026322e3b69
@@ -11,7 +11,7 @@ on:
11
11
  jobs:
12
12
  test:
13
13
  name: Build and Publish
14
- runs-on: ubuntu-20.04
14
+ runs-on: ubuntu-latest
15
15
  strategy:
16
16
  fail-fast: false
17
17
  matrix:
@@ -48,3 +48,18 @@ jobs:
48
48
  contains(needs.*.result, 'failure')
49
49
  || contains(needs.*.result, 'cancelled')
50
50
  }}
51
+
52
+ report-version:
53
+ runs-on: ubuntu-latest
54
+ if: github.ref == 'refs/heads/master'
55
+ steps:
56
+ - name: Report version
57
+ run: |
58
+ curl -sS -X POST -u "$ENFORCER_USERNAME:$ENFORCER_PASSWORD" \
59
+ -H "Content-Type: application/json" \
60
+ -d '{"name":"aptible-cli", "type":"client", "version": "${{ github.sha }}"}' \
61
+ "$ENFORCER_API/repo_version"
62
+ env:
63
+ ENFORCER_API: https://app-83237.aptible-test-leeroy.com
64
+ ENFORCER_USERNAME: aptible
65
+ ENFORCER_PASSWORD: ${{ secrets.ENFORCER_PASSWORD }}
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- aptible-cli (0.24.1)
4
+ aptible-cli (0.24.3)
5
5
  activesupport (>= 4.0, < 6.0)
6
6
  aptible-api (~> 1.6.5)
7
7
  aptible-auth (~> 1.2.5)
@@ -1,5 +1,6 @@
1
1
  require 'base64'
2
2
  require 'uri'
3
+ require 'logger'
3
4
 
4
5
  require 'aptible/auth'
5
6
  require 'thor'
@@ -8,6 +9,7 @@ require 'chronic_duration'
8
9
 
9
10
  require_relative 'helpers/ssh'
10
11
  require_relative 'helpers/token'
12
+ require_relative 'helpers/telemetry'
11
13
  require_relative 'helpers/operation'
12
14
  require_relative 'helpers/environment'
13
15
  require_relative 'helpers/app'
@@ -80,7 +82,13 @@ module Aptible
80
82
 
81
83
  def initialize(*)
82
84
  nag_toolbelt unless toolbelt?
83
- Aptible::Resource.configure { |conf| conf.user_agent = version_string }
85
+ Aptible::Resource.configure do |conf|
86
+ conf.user_agent = version_string
87
+ level = Logger::WARN
88
+ debug_level = ENV['APTIBLE_DEBUG']
89
+ level = debug_level if debug_level
90
+ conf.logger.tap { |l| l.level = level }
91
+ end
84
92
  warn_sso_enforcement
85
93
  super
86
94
  end
@@ -151,12 +151,27 @@ module Aptible
151
151
  service
152
152
  end
153
153
 
154
+ def apps_href
155
+ href = '/apps'
156
+ if Renderer.format != 'json'
157
+ href = '/apps?per_page=5000&no_embed=true'
158
+ end
159
+ href
160
+ end
161
+
162
+ def apps_all
163
+ Aptible::Api::App.all(
164
+ token: fetch_token,
165
+ href: apps_href
166
+ )
167
+ end
168
+
154
169
  def apps_from_handle(handle, environment)
155
170
  # TODO: This should probably use each_app for more efficiency.
156
171
  if environment
157
172
  environment.apps
158
173
  else
159
- Aptible::Api::App.all(token: fetch_token)
174
+ apps_all
160
175
  end.select { |a| a.handle == handle }
161
176
  end
162
177
 
@@ -31,12 +31,27 @@ module Aptible
31
31
  end
32
32
  end
33
33
 
34
- def databases_from_handle(handle, environment)
35
- if environment
36
- databases = environment.databases
37
- else
38
- databases = Aptible::Api::Database.all(token: fetch_token)
34
+ def databases_href
35
+ href = '/databases'
36
+ if Renderer.format != 'json'
37
+ href = '/databases?per_page=5000&no_embed=true'
39
38
  end
39
+ href
40
+ end
41
+
42
+ def databases_all
43
+ Aptible::Api::Database.all(
44
+ token: fetch_token,
45
+ href: databases_href
46
+ )
47
+ end
48
+
49
+ def databases_from_handle(handle, environment)
50
+ databases = if environment
51
+ environment.databases
52
+ else
53
+ databases_all
54
+ end
40
55
  databases.select { |a| a.handle == handle }
41
56
  end
42
57
 
@@ -6,6 +6,14 @@ module Aptible
6
6
  module Environment
7
7
  include Helpers::Token
8
8
 
9
+ def environment_href
10
+ href = '/accounts'
11
+ if Renderer.format != 'json'
12
+ href = '/accounts?per_page=5000&no_embed=true'
13
+ end
14
+ href
15
+ end
16
+
9
17
  def scoped_environments(options)
10
18
  if options[:environment]
11
19
  if (environment = environment_from_handle(options[:environment]))
@@ -14,7 +22,11 @@ module Aptible
14
22
  raise Thor::Error, 'Specified account does not exist'
15
23
  end
16
24
  else
17
- Aptible::Api::Account.all(token: fetch_token)
25
+ href = environment_href
26
+ Aptible::Api::Account.all(
27
+ token: fetch_token,
28
+ href: href
29
+ )
18
30
  end
19
31
  end
20
32
 
@@ -30,13 +42,26 @@ module Aptible
30
42
 
31
43
  def environment_from_handle(handle)
32
44
  return nil unless handle
33
- Aptible::Api::Account.all(token: fetch_token).find do |a|
45
+ href = environment_href
46
+ Aptible::Api::Account.all(token: fetch_token, href: href).find do |a|
34
47
  a.handle == handle
35
48
  end
36
49
  end
37
50
 
51
+ def environment_map(accounts)
52
+ acc_map = {}
53
+ accounts.each do |account|
54
+ acc_map[account.links.self.href] = account
55
+ end
56
+ acc_map
57
+ end
58
+
38
59
  def ensure_default_environment
39
- environments = Aptible::Api::Account.all(token: fetch_token)
60
+ href = environment_href
61
+ environments = Aptible::Api::Account.all(
62
+ token: fetch_token,
63
+ href: href
64
+ )
40
65
  case environments.count
41
66
  when 0
42
67
  e = 'No environments. Go to https://app.aptible.com/ to proceed'
@@ -38,8 +38,10 @@ module Aptible
38
38
  # operation failed, poll_for_success will immediately fall through to
39
39
  # the error message.
40
40
  unless code == 0
41
- e = 'Disconnected from logs, waiting for operation to complete'
42
- CLI.logger.warn e
41
+ msg = ['Disconnected from logs, waiting for operation to complete',
42
+ 'Once complete, the logs can be viewed here:',
43
+ " #{ui_log_url(operation)}"]
44
+ msg.each { |e| CLI.logger.warn e }
43
45
  poll_for_success(operation)
44
46
  end
45
47
  end
@@ -76,8 +78,9 @@ module Aptible
76
78
  res = http.request(Net::HTTP::Get.new(uri.request_uri, headers))
77
79
  # note: res body with a 200 is target redirect location for download
78
80
  if !res || res.code != '200' || res.body.nil?
79
- raise Thor::Error, 'Unable to retrieve the operation\'s logs. '\
80
- 'If the issue persists please contact support for assistance.'
81
+ raise Thor::Error, 'Unable to retrieve the operation\'s logs. ' \
82
+ 'If the issue persists please contact support for assistance, or ' \
83
+ "view them at #{ui_log_url(operation)}"
81
84
  end
82
85
  res
83
86
  end
@@ -96,6 +99,10 @@ module Aptible
96
99
  end
97
100
  res
98
101
  end
102
+
103
+ def ui_log_url(operation)
104
+ "https://app.aptible.com/operations/#{operation.id}"
105
+ end
99
106
  end
100
107
  end
101
108
  end
@@ -0,0 +1,58 @@
1
+ require 'httpclient'
2
+ require 'securerandom'
3
+ require 'uri'
4
+
5
+ module Aptible
6
+ module CLI
7
+ module Helpers
8
+ module Telemetry
9
+ def telemetry(cmd, options = {})
10
+ token_hash = decode_token
11
+ format = Renderer.format
12
+ format = 'text' if format.nil?
13
+ sub = token_hash[0]['sub']
14
+ parsed_url = URI.parse(sub)
15
+ path_components = parsed_url.path.split('/')
16
+ user_or_org_id = path_components.last
17
+ # https://github.com/aptible/aptible-resource/blob/7c3a79e6eee9c88aa7dbf332e550508f22a5b08d/lib/hyper_resource/modules/http.rb#L21
18
+ client = HTTPClient.new.tap do |c|
19
+ c.cookie_manager = nil
20
+ c.connect_timeout = 30
21
+ c.send_timeout = 45
22
+ c.keep_alive_timeout = 15
23
+ c.ssl_config.set_default_paths
24
+ end
25
+
26
+ value = {
27
+ 'email' => token_hash[0]['email'],
28
+ 'format' => format,
29
+ 'cmd' => cmd,
30
+ 'options' => options,
31
+ 'version' => version_string,
32
+ # https://stackoverflow.com/a/73973555
33
+ 'github' => ENV['GITHUB_ACTIONS'],
34
+ 'gitlab' => ENV['GITLAB_CI'],
35
+ 'travis' => ENV['TRAVIS'],
36
+ 'circleci' => ENV['CIRCLECI'],
37
+ 'ci' => ENV['CI']
38
+ }
39
+
40
+ begin
41
+ uri = URI('https://tuna.aptible.com/www/e')
42
+ client.get(
43
+ uri,
44
+ 'id' => SecureRandom.uuid,
45
+ 'user_id' => user_or_org_id,
46
+ 'type' => 'cli_telemetry',
47
+ 'url' => sub,
48
+ 'value' => value
49
+ )
50
+ rescue
51
+ # since this is just for telemetry we don't want to notify
52
+ # user of an error
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,4 +1,5 @@
1
1
  require 'aptible/auth'
2
+ require 'jwt'
2
3
 
3
4
  require_relative 'config_path'
4
5
 
@@ -46,6 +47,11 @@ module Aptible
46
47
  def token_file
47
48
  File.join(aptible_config_path, 'tokens.json').freeze
48
49
  end
50
+
51
+ def decode_token
52
+ tok = fetch_token
53
+ JWT.decode(tok, nil, false)
54
+ end
49
55
  end
50
56
  end
51
57
  end
@@ -9,8 +9,12 @@ module Aptible
9
9
  module Renderer
10
10
  FORMAT_VAR = 'APTIBLE_OUTPUT_FORMAT'.freeze
11
11
 
12
+ def self.format
13
+ ENV[FORMAT_VAR]
14
+ end
15
+
12
16
  def self.current
13
- case (format = ENV[FORMAT_VAR])
17
+ case format
14
18
  when 'json'
15
19
  Json.new
16
20
  when 'text'
@@ -108,6 +108,13 @@ module Aptible
108
108
  attach_account(node, account)
109
109
  end
110
110
 
111
+ def inject_database_minimal(node, database, account)
112
+ node.value('id', database.id)
113
+ node.value('handle', database.handle)
114
+ node.value('created_at', database.created_at)
115
+ attach_account(node, account)
116
+ end
117
+
111
118
  def inject_database(node, database, account)
112
119
  node.value('id', database.id)
113
120
  node.value('handle', database.handle)
@@ -7,17 +7,34 @@ module Aptible
7
7
  include Helpers::App
8
8
  include Helpers::Environment
9
9
  include Helpers::Token
10
+ include Helpers::Telemetry
10
11
 
11
12
  desc 'apps', 'List all applications'
12
13
  option :environment, aliases: '--env'
13
14
  def apps
15
+ telemetry(__method__, options)
16
+
14
17
  Formatter.render(Renderer.current) do |root|
15
18
  root.grouped_keyed_list(
16
19
  { 'environment' => 'handle' },
17
20
  'handle'
18
21
  ) do |node|
19
- scoped_environments(options).each do |account|
20
- account.each_app do |app|
22
+ accounts = scoped_environments(options)
23
+ acc_map = environment_map(accounts)
24
+
25
+ if Renderer.format == 'json'
26
+ accounts.each do |account|
27
+ account.each_app do |app|
28
+ node.object do |n|
29
+ ResourceFormatter.inject_app(n, app, account)
30
+ end
31
+ end
32
+ end
33
+ else
34
+ apps_all.each do |app|
35
+ account = acc_map[app.links.account.href]
36
+ next if account.nil?
37
+
21
38
  node.object do |n|
22
39
  ResourceFormatter.inject_app(n, app, account)
23
40
  end
@@ -30,6 +47,8 @@ module Aptible
30
47
  desc 'apps:create HANDLE', 'Create a new application'
31
48
  option :environment, aliases: '--env'
32
49
  define_method 'apps:create' do |handle|
50
+ telemetry(__method__, options.merge(handle: handle))
51
+
33
52
  environment = ensure_environment(options)
34
53
  app = environment.create_app(handle: handle)
35
54
 
@@ -56,6 +75,8 @@ module Aptible
56
75
  option :container_profile, type: :string,
57
76
  desc: 'Examples: m c r'
58
77
  define_method 'apps:scale' do |type|
78
+ telemetry(__method__, options.merge(type: type))
79
+
59
80
  service = ensure_service(options, type)
60
81
 
61
82
  container_count = options[:container_count]
@@ -89,6 +110,8 @@ module Aptible
89
110
  desc 'apps:deprovision', 'Deprovision an app'
90
111
  app_options
91
112
  define_method 'apps:deprovision' do
113
+ telemetry(__method__, options)
114
+
92
115
  app = ensure_app(options)
93
116
  CLI.logger.info "Deprovisioning #{app.handle}..."
94
117
  op = app.create_operation!(type: 'deprovision')
@@ -108,6 +131,12 @@ module Aptible
108
131
  ' drain destinations, you must restart the app.'
109
132
  option :environment, aliases: '--env'
110
133
  define_method 'apps:rename' do |old_handle, new_handle|
134
+ opts = options.merge(
135
+ old_handle: old_handle,
136
+ new_handle: new_handle
137
+ )
138
+ telemetry(__method__, opts)
139
+
111
140
  env = ensure_environment(options)
112
141
  app = ensure_app(options.merge(app: old_handle))
113
142
  app.update!(handle: new_handle)
@@ -6,6 +6,7 @@ module Aptible
6
6
  thor.class_eval do
7
7
  include Helpers::Token
8
8
  include Helpers::Database
9
+ include Helpers::Telemetry
9
10
 
10
11
  desc 'backup:restore BACKUP_ID ' \
11
12
  '[--environment ENVIRONMENT_HANDLE] [--handle HANDLE] ' \
@@ -24,6 +25,8 @@ module Aptible
24
25
  desc: 'Examples: m c r'
25
26
  option :iops, type: :numeric
26
27
  define_method 'backup:restore' do |backup_id|
28
+ telemetry(__method__, options.merge(backup_id: backup_id))
29
+
27
30
  backup = Aptible::Api::Backup.find(backup_id, token: fetch_token)
28
31
  raise Thor::Error, "Backup ##{backup_id} not found" if backup.nil?
29
32
 
@@ -74,6 +77,8 @@ module Aptible
74
77
  default: '99y',
75
78
  desc: 'Limit backups returned (example usage: 1w, 1y, etc.)'
76
79
  define_method 'backup:list' do |handle|
80
+ telemetry(__method__, options.merge(handle: handle))
81
+
77
82
  age = ChronicDuration.parse(options[:max_age])
78
83
  raise Thor::Error, "Invalid age: #{options[:max_age]}" if age.nil?
79
84
  min_created_at = Time.now - age
@@ -101,6 +106,8 @@ module Aptible
101
106
  desc: 'Limit backups returned '\
102
107
  '(example usage: 1w, 1y, etc.)'
103
108
  define_method 'backup:orphaned' do
109
+ telemetry(__method__, options)
110
+
104
111
  age = ChronicDuration.parse(options[:max_age])
105
112
  raise Thor::Error, "Invalid age: #{options[:max_age]}" if age.nil?
106
113
  min_created_at = Time.now - age
@@ -126,6 +133,8 @@ module Aptible
126
133
  desc 'backup:purge BACKUP_ID',
127
134
  'Permanently delete a backup and any copies of it'
128
135
  define_method 'backup:purge' do |backup_id|
136
+ telemetry(__method__, options.merge(backup_id: backup_id))
137
+
129
138
  backup = Aptible::Api::Backup.find(backup_id, token: fetch_token)
130
139
  raise Thor::Error, "Backup ##{backup_id} not found" if backup.nil?
131
140
 
@@ -10,10 +10,13 @@ module Aptible
10
10
  thor.class_eval do
11
11
  include Helpers::Environment
12
12
  include Term::ANSIColor
13
+ include Helpers::Telemetry
13
14
 
14
15
  desc 'backup_retention_policy [ENVIRONMENT_HANDLE]',
15
16
  'Show the current backup retention policy for the environment'
16
17
  define_method 'backup_retention_policy' do |env|
18
+ telemetry(__method__, options.merge(env: env))
19
+
17
20
  account = ensure_environment(environment: env)
18
21
  policy = account.backup_retention_policies.first
19
22
  unless policy
@@ -52,6 +55,8 @@ module Aptible
52
55
  desc: 'Do not prompt for confirmation if the new policy ' \
53
56
  'retains fewer backups than the current policy'
54
57
  define_method 'backup_retention_policy:set' do |env|
58
+ telemetry(__method__, options.merge(env: env))
59
+
55
60
  if options.empty?
56
61
  raise Thor::Error,
57
62
  'Please specify at least one attribute to change'
@@ -7,10 +7,13 @@ module Aptible
7
7
  thor.class_eval do
8
8
  include Helpers::Operation
9
9
  include Helpers::App
10
+ include Helpers::Telemetry
10
11
 
11
12
  desc 'config', "Print an app's current configuration"
12
13
  app_options
13
14
  def config
15
+ telemetry(__method__, options)
16
+
14
17
  app = ensure_app(options)
15
18
  config = app.current_configuration
16
19
  env = config ? config.env : {}
@@ -32,6 +35,8 @@ module Aptible
32
35
  "Print a specific key within an app's current configuration"
33
36
  app_options
34
37
  define_method 'config:get' do |*args|
38
+ telemetry(__method__, options)
39
+
35
40
  app = ensure_app(options)
36
41
  config = app.current_configuration
37
42
  env = config ? config.env : {}
@@ -49,6 +54,8 @@ module Aptible
49
54
  'Add an ENV variable to an app'
50
55
  app_options
51
56
  define_method 'config:add' do |*args|
57
+ telemetry(__method__, options)
58
+
52
59
  # FIXME: define_method - ?! Seriously, WTF Thor.
53
60
  app = ensure_app(options)
54
61
  env = extract_env(args)
@@ -61,6 +68,7 @@ module Aptible
61
68
  'Add an ENV variable to an app'
62
69
  app_options
63
70
  define_method 'config:set' do |*args|
71
+ telemetry(__method__, options)
64
72
  send('config:add', *args)
65
73
  end
66
74
 
@@ -68,6 +76,8 @@ module Aptible
68
76
  'Remove an ENV variable from an app'
69
77
  app_options
70
78
  define_method 'config:rm' do |*args|
79
+ telemetry(__method__, options)
80
+
71
81
  # FIXME: define_method - ?! Seriously, WTF Thor.
72
82
  app = ensure_app(options)
73
83
  env = Hash[args.map do |arg|
@@ -84,6 +94,7 @@ module Aptible
84
94
  'Remove an ENV variable from an app'
85
95
  app_options
86
96
  define_method 'config:unset' do |*args|
97
+ telemetry(__method__, options)
87
98
  send('config:rm', *args)
88
99
  end
89
100
  end