Skip to content

Commit 740640c

Browse files
authored
feat: telemetry for all commands (#366)
This change will make it so all commands run with `aptible-cli` will send a tuna event with the following info: - user email - user -or- org id - format (e.g. text or json) - command run - options provided to command This is the foundation for us to evaluate usage and provide us with the ability to make decisions about the future direction of the CLI. I tried to find a `before` hook for Thor but it looks like it doesn't exist: https://stackoverflow.com/questions/61855861/is-there-a-way-to-add-hooks-to-a-thor-class-in-order-to-run-code-before-after-al
1 parent 4c9efea commit 740640c

22 files changed

+268
-2
lines changed

lib/aptible/cli/agent.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
require_relative 'helpers/ssh'
1010
require_relative 'helpers/token'
11+
require_relative 'helpers/telemetry'
1112
require_relative 'helpers/operation'
1213
require_relative 'helpers/environment'
1314
require_relative 'helpers/app'
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

lib/aptible/cli/helpers/token.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'aptible/auth'
2+
require 'jwt'
23

34
require_relative 'config_path'
45

@@ -46,6 +47,11 @@ def current_token_hash
4647
def token_file
4748
File.join(aptible_config_path, 'tokens.json').freeze
4849
end
50+
51+
def decode_token
52+
tok = fetch_token
53+
JWT.decode(tok, nil, false)
54+
end
4955
end
5056
end
5157
end

lib/aptible/cli/renderer.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ module CLI
99
module Renderer
1010
FORMAT_VAR = 'APTIBLE_OUTPUT_FORMAT'.freeze
1111

12+
def self.format
13+
ENV[FORMAT_VAR]
14+
end
15+
1216
def self.current
13-
case (format = ENV[FORMAT_VAR])
17+
case format
1418
when 'json'
1519
Json.new
1620
when 'text'

lib/aptible/cli/subcommands/apps.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ def self.included(thor)
77
include Helpers::App
88
include Helpers::Environment
99
include Helpers::Token
10+
include Helpers::Telemetry
1011

1112
desc 'apps', 'List all applications'
1213
option :environment, aliases: '--env'
1314
def apps
15+
telemetry(__method__, options)
16+
1417
Formatter.render(Renderer.current) do |root|
1518
root.grouped_keyed_list(
1619
{ 'environment' => 'handle' },
@@ -30,6 +33,8 @@ def apps
3033
desc 'apps:create HANDLE', 'Create a new application'
3134
option :environment, aliases: '--env'
3235
define_method 'apps:create' do |handle|
36+
telemetry(__method__, options.merge(handle: handle))
37+
3338
environment = ensure_environment(options)
3439
app = environment.create_app(handle: handle)
3540

@@ -56,6 +61,8 @@ def apps
5661
option :container_profile, type: :string,
5762
desc: 'Examples: m c r'
5863
define_method 'apps:scale' do |type|
64+
telemetry(__method__, options.merge(type: type))
65+
5966
service = ensure_service(options, type)
6067

6168
container_count = options[:container_count]
@@ -89,6 +96,8 @@ def apps
8996
desc 'apps:deprovision', 'Deprovision an app'
9097
app_options
9198
define_method 'apps:deprovision' do
99+
telemetry(__method__, options)
100+
92101
app = ensure_app(options)
93102
CLI.logger.info "Deprovisioning #{app.handle}..."
94103
op = app.create_operation!(type: 'deprovision')
@@ -108,6 +117,12 @@ def apps
108117
' drain destinations, you must restart the app.'
109118
option :environment, aliases: '--env'
110119
define_method 'apps:rename' do |old_handle, new_handle|
120+
opts = options.merge(
121+
old_handle: old_handle,
122+
new_handle: new_handle
123+
)
124+
telemetry(__method__, opts)
125+
111126
env = ensure_environment(options)
112127
app = ensure_app(options.merge(app: old_handle))
113128
app.update!(handle: new_handle)

lib/aptible/cli/subcommands/backup.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ def self.included(thor)
66
thor.class_eval do
77
include Helpers::Token
88
include Helpers::Database
9+
include Helpers::Telemetry
910

1011
desc 'backup:restore BACKUP_ID ' \
1112
'[--environment ENVIRONMENT_HANDLE] [--handle HANDLE] ' \
@@ -24,6 +25,8 @@ def self.included(thor)
2425
desc: 'Examples: m c r'
2526
option :iops, type: :numeric
2627
define_method 'backup:restore' do |backup_id|
28+
telemetry(__method__, options.merge(backup_id: backup_id))
29+
2730
backup = Aptible::Api::Backup.find(backup_id, token: fetch_token)
2831
raise Thor::Error, "Backup ##{backup_id} not found" if backup.nil?
2932

@@ -74,6 +77,8 @@ def self.included(thor)
7477
default: '99y',
7578
desc: 'Limit backups returned (example usage: 1w, 1y, etc.)'
7679
define_method 'backup:list' do |handle|
80+
telemetry(__method__, options.merge(handle: handle))
81+
7782
age = ChronicDuration.parse(options[:max_age])
7883
raise Thor::Error, "Invalid age: #{options[:max_age]}" if age.nil?
7984
min_created_at = Time.now - age
@@ -101,6 +106,8 @@ def self.included(thor)
101106
desc: 'Limit backups returned '\
102107
'(example usage: 1w, 1y, etc.)'
103108
define_method 'backup:orphaned' do
109+
telemetry(__method__, options)
110+
104111
age = ChronicDuration.parse(options[:max_age])
105112
raise Thor::Error, "Invalid age: #{options[:max_age]}" if age.nil?
106113
min_created_at = Time.now - age
@@ -126,6 +133,8 @@ def self.included(thor)
126133
desc 'backup:purge BACKUP_ID',
127134
'Permanently delete a backup and any copies of it'
128135
define_method 'backup:purge' do |backup_id|
136+
telemetry(__method__, options.merge(backup_id: backup_id))
137+
129138
backup = Aptible::Api::Backup.find(backup_id, token: fetch_token)
130139
raise Thor::Error, "Backup ##{backup_id} not found" if backup.nil?
131140

lib/aptible/cli/subcommands/backup_retention_policy.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ def self.included(thor)
1010
thor.class_eval do
1111
include Helpers::Environment
1212
include Term::ANSIColor
13+
include Helpers::Telemetry
1314

1415
desc 'backup_retention_policy [ENVIRONMENT_HANDLE]',
1516
'Show the current backup retention policy for the environment'
1617
define_method 'backup_retention_policy' do |env|
18+
telemetry(__method__, options.merge(env: env))
19+
1720
account = ensure_environment(environment: env)
1821
policy = account.backup_retention_policies.first
1922
unless policy
@@ -52,6 +55,8 @@ def self.included(thor)
5255
desc: 'Do not prompt for confirmation if the new policy ' \
5356
'retains fewer backups than the current policy'
5457
define_method 'backup_retention_policy:set' do |env|
58+
telemetry(__method__, options.merge(env: env))
59+
5560
if options.empty?
5661
raise Thor::Error,
5762
'Please specify at least one attribute to change'

lib/aptible/cli/subcommands/config.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ def self.included(thor)
77
thor.class_eval do
88
include Helpers::Operation
99
include Helpers::App
10+
include Helpers::Telemetry
1011

1112
desc 'config', "Print an app's current configuration"
1213
app_options
1314
def config
15+
telemetry(__method__, options)
16+
1417
app = ensure_app(options)
1518
config = app.current_configuration
1619
env = config ? config.env : {}
@@ -32,6 +35,8 @@ def config
3235
"Print a specific key within an app's current configuration"
3336
app_options
3437
define_method 'config:get' do |*args|
38+
telemetry(__method__, options)
39+
3540
app = ensure_app(options)
3641
config = app.current_configuration
3742
env = config ? config.env : {}
@@ -49,6 +54,8 @@ def config
4954
'Add an ENV variable to an app'
5055
app_options
5156
define_method 'config:add' do |*args|
57+
telemetry(__method__, options)
58+
5259
# FIXME: define_method - ?! Seriously, WTF Thor.
5360
app = ensure_app(options)
5461
env = extract_env(args)
@@ -61,13 +68,16 @@ def config
6168
'Add an ENV variable to an app'
6269
app_options
6370
define_method 'config:set' do |*args|
71+
telemetry(__method__, options)
6472
send('config:add', *args)
6573
end
6674

6775
desc 'config:rm [VAR1] [VAR2] [...]',
6876
'Remove an ENV variable from an app'
6977
app_options
7078
define_method 'config:rm' do |*args|
79+
telemetry(__method__, options)
80+
7181
# FIXME: define_method - ?! Seriously, WTF Thor.
7282
app = ensure_app(options)
7383
env = Hash[args.map do |arg|
@@ -84,6 +94,7 @@ def config
8494
'Remove an ENV variable from an app'
8595
app_options
8696
define_method 'config:unset' do |*args|
97+
telemetry(__method__, options)
8798
send('config:rm', *args)
8899
end
89100
end

0 commit comments

Comments
 (0)