remove tracking (rollbar + segment.io) (#570)
This commit is contained in:
10
README.rst
10
README.rst
@@ -39,16 +39,6 @@ Note that the DCOS CLI has tight integration with DCOS and certain
|
||||
functionality may not work as expected or at all when using it directly with
|
||||
Mesos and Marathon.
|
||||
|
||||
Controlling Analytics Reporting
|
||||
-------------------------------
|
||||
|
||||
The CLI reports usage and exception information to Segment.io for production
|
||||
usage.
|
||||
|
||||
If you wish to turn reporting of analytics off entirely,
|
||||
you can set the :code:`core.reporting` property to false::
|
||||
dcos config set core.reporting false
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import dcoscli
|
||||
import docopt
|
||||
import rollbar
|
||||
import six
|
||||
from dcos import http, util
|
||||
from dcoscli.constants import (ROLLBAR_SERVER_POST_KEY,
|
||||
SEGMENT_IO_CLI_ERROR_EVENT,
|
||||
SEGMENT_IO_CLI_EVENT, SEGMENT_IO_WRITE_KEY_PROD,
|
||||
SEGMENT_URL)
|
||||
from dcoscli.subcommand import default_doc
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
logger = util.get_logger(__name__)
|
||||
session_id = uuid.uuid4().hex
|
||||
|
||||
|
||||
def _track(conf):
|
||||
"""
|
||||
Whether or not to send reporting information
|
||||
|
||||
:param conf: dcos config file
|
||||
:type conf: Toml
|
||||
:returns: whether to send reporting information
|
||||
:rtype: bool
|
||||
"""
|
||||
|
||||
return dcoscli.version != 'SNAPSHOT' and conf.get('core.reporting', True)
|
||||
|
||||
|
||||
def _segment_track(event, conf, properties):
|
||||
"""
|
||||
Send a segment.io 'track' event
|
||||
|
||||
:param event: name of event
|
||||
:type event: string
|
||||
:param conf: dcos config file
|
||||
:type conf: Toml
|
||||
:param properties: event properties
|
||||
:type properties: dict
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
data = {'event': event,
|
||||
'properties': properties,
|
||||
'anonymousId': session_id}
|
||||
|
||||
_segment_request('track', data)
|
||||
|
||||
|
||||
def _segment_request(path, data):
|
||||
"""
|
||||
Send a segment.io HTTP request
|
||||
|
||||
:param path: URL path
|
||||
:type path: str
|
||||
:param data: json POST data
|
||||
:type data: dict
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
key = SEGMENT_IO_WRITE_KEY_PROD
|
||||
|
||||
try:
|
||||
# Set both the connect timeout and the request timeout to 1s,
|
||||
# to prevent rollbar from hanging the CLI commands
|
||||
http.post('{}/{}'.format(SEGMENT_URL, path),
|
||||
json=data,
|
||||
auth=HTTPBasicAuth(key, ''),
|
||||
timeout=(1, 1))
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
|
||||
def track_err(pool, exit_code, err, conf, cluster_id):
|
||||
"""
|
||||
Report error details to analytics services.
|
||||
|
||||
:param pool: thread pool
|
||||
:type pool: ThreadPoolExecutor
|
||||
:param exit_code: exit code of tracked process
|
||||
:type exit_code: int
|
||||
:param err: stderr of tracked process
|
||||
:type err: str
|
||||
:param conf: dcos config file
|
||||
:type conf: Toml
|
||||
:param cluster_id: dcos cluster id to send to segment
|
||||
:type cluster_id: str
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
if not _track(conf):
|
||||
return
|
||||
|
||||
# Segment.io calls are async, but rollbar is not, so for
|
||||
# parallelism, we must call segment first.
|
||||
_segment_track_err(pool, conf, cluster_id, err, exit_code)
|
||||
_rollbar_track_err(conf, cluster_id, err, exit_code)
|
||||
|
||||
|
||||
def segment_track_cli(pool, conf, cluster_id):
|
||||
"""
|
||||
Send segment.io cli event.
|
||||
|
||||
:param pool: thread pool
|
||||
:type pool: ThreadPoolExecutor
|
||||
:param conf: dcos config file
|
||||
:type conf: Toml
|
||||
:param cluster_id: dcos cluster id to send to segment
|
||||
:type cluster_id: str
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
if not _track(conf):
|
||||
return
|
||||
|
||||
props = _base_properties(conf, cluster_id)
|
||||
pool.submit(_segment_track, SEGMENT_IO_CLI_EVENT, conf, props)
|
||||
|
||||
|
||||
def _segment_track_err(pool, conf, cluster_id, err, exit_code):
|
||||
"""
|
||||
Send segment.io error event.
|
||||
|
||||
:param pool: thread pool
|
||||
:type segment: ThreadPoolExecutor
|
||||
:param conf: dcos config file
|
||||
:type conf: Toml
|
||||
:param cluster_id: dcos cluster id to send to segment
|
||||
:type cluster_id: str
|
||||
:param err: stderr of tracked process
|
||||
:type err: str
|
||||
:param exit_code: exit code of tracked process
|
||||
:type exit_code: int
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
props = _base_properties(conf, cluster_id)
|
||||
props['err'] = err
|
||||
props['exit_code'] = exit_code
|
||||
pool.submit(_segment_track, SEGMENT_IO_CLI_ERROR_EVENT, conf, props)
|
||||
|
||||
|
||||
def _rollbar_track_err(conf, cluster_id, err, exit_code):
|
||||
"""
|
||||
Report to rollbar. Synchronous.
|
||||
|
||||
:param conf: dcos config file
|
||||
:type conf: Toml
|
||||
:param cluster_id: dcos cluster id to send to segment
|
||||
:type cluster_id: str
|
||||
:param err: stderr of tracked process
|
||||
:type err: str
|
||||
:param exit_code: exit code of tracked process
|
||||
:type exit_code: int
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
rollbar.init(ROLLBAR_SERVER_POST_KEY, 'prod')
|
||||
|
||||
props = _base_properties(conf, cluster_id)
|
||||
props['exit_code'] = exit_code
|
||||
|
||||
lines = err.split('\n')
|
||||
if len(lines) >= 2:
|
||||
title = lines[-2]
|
||||
else:
|
||||
title = err
|
||||
props['stderr'] = err
|
||||
|
||||
try:
|
||||
rollbar.report_message(title, 'error', extra_data=props)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
|
||||
|
||||
def _command():
|
||||
""" Return the subcommand used in this dcos process.
|
||||
|
||||
:returns: subcommand used in this dcos process
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
args = docopt.docopt(default_doc("dcos"),
|
||||
help=False,
|
||||
options_first=True)
|
||||
return args.get('<command>', "") or ""
|
||||
|
||||
|
||||
def _base_properties(conf=None, cluster_id=None):
|
||||
"""
|
||||
These properties are sent with every analytics event.
|
||||
|
||||
:param conf: dcos config file
|
||||
:type conf: Toml
|
||||
:param cluster_id: dcos cluster id to send to segment
|
||||
:type cluster_id: str
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
if not conf:
|
||||
conf = util.get_config()
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
cmd = 'dcos ' + _command()
|
||||
full_cmd = 'dcos ' + ' '.join(sys.argv[1:])
|
||||
else:
|
||||
cmd = 'dcos'
|
||||
full_cmd = 'dcos'
|
||||
|
||||
try:
|
||||
dcos_hostname = six.moves.urllib.parse.urlparse(
|
||||
conf.get('core.dcos_url')).hostname
|
||||
except:
|
||||
logger.exception('Unable to find the hostname of the cluster.')
|
||||
dcos_hostname = None
|
||||
|
||||
conf = [prop for prop in list(conf.property_items())
|
||||
if prop[0] != "core.dcos_acs_token"]
|
||||
|
||||
return {
|
||||
'cmd': cmd,
|
||||
'full_cmd': full_cmd,
|
||||
'dcoscli.version': dcoscli.version,
|
||||
'python_version': str(sys.version_info),
|
||||
'config': json.dumps(conf),
|
||||
'DCOS_HOSTNAME': dcos_hostname,
|
||||
'CLUSTER_ID': cluster_id
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
ROLLBAR_SERVER_POST_KEY = '62f87c5df3674629b143a137de3d3244'
|
||||
|
||||
SEGMENT_IO_WRITE_KEY_PROD = '51ybGTeFEFU1xo6u10XMDrr6kATFyRyh'
|
||||
SEGMENT_IO_CLI_EVENT = 'dcos-cli'
|
||||
SEGMENT_IO_CLI_ERROR_EVENT = 'dcos-cli-error'
|
||||
SEGMENT_URL = 'https://api.segment.io/v1'
|
||||
|
||||
DCOS_PRODUCTION_ENV = 'DCOS_PRODUCTION'
|
||||
@@ -1,13 +1,11 @@
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import dcoscli
|
||||
import docopt
|
||||
from dcos import constants, emitting, errors, http, mesos, subcommand, util
|
||||
from dcos.errors import DCOSAuthenticationException, DCOSException
|
||||
from dcoscli import analytics
|
||||
from dcos import constants, emitting, errors, http, subcommand, util
|
||||
from dcos.errors import DCOSException
|
||||
from dcoscli.subcommand import SubcommandMain, default_doc
|
||||
|
||||
logger = util.get_logger(__name__)
|
||||
@@ -48,39 +46,15 @@ def _main():
|
||||
if not command:
|
||||
command = "help"
|
||||
|
||||
cluster_id = None
|
||||
if dcoscli.version != 'SNAPSHOT' and command and \
|
||||
command not in ["config", "help"]:
|
||||
try:
|
||||
cluster_id = mesos.DCOSClient().metadata().get('CLUSTER_ID')
|
||||
except DCOSAuthenticationException:
|
||||
raise
|
||||
except:
|
||||
msg = 'Unable to get the cluster_id of the cluster.'
|
||||
logger.exception(msg)
|
||||
if command in subcommand.default_subcommands():
|
||||
sc = SubcommandMain(command, args['<args>'])
|
||||
else:
|
||||
executable = subcommand.command_executables(command)
|
||||
sc = subcommand.SubcommandProcess(
|
||||
executable, command, args['<args>'])
|
||||
|
||||
# send args call to segment.io
|
||||
with ThreadPoolExecutor(max_workers=2) as reporting_executor:
|
||||
analytics.segment_track_cli(reporting_executor, config, cluster_id)
|
||||
|
||||
# the call to retrieve cluster_id must happen before we run the
|
||||
# subcommand so that if you have auth enabled we don't ask for
|
||||
# user/pass multiple times (with the text being out of order)
|
||||
# before we can cache the auth token
|
||||
if command in subcommand.default_subcommands():
|
||||
sc = SubcommandMain(command, args['<args>'])
|
||||
else:
|
||||
executable = subcommand.command_executables(command)
|
||||
sc = subcommand.SubcommandProcess(
|
||||
executable, command, args['<args>'])
|
||||
|
||||
exitcode, err = sc.run_and_capture()
|
||||
|
||||
if err:
|
||||
analytics.track_err(
|
||||
reporting_executor, exitcode, err, config, cluster_id)
|
||||
|
||||
return exitcode
|
||||
exitcode, _ = sc.run_and_capture()
|
||||
return exitcode
|
||||
|
||||
|
||||
def _config_log_level_environ(log_level):
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
from functools import wraps
|
||||
|
||||
import dcoscli.analytics
|
||||
import rollbar
|
||||
from dcos import constants
|
||||
from dcoscli.main import main
|
||||
|
||||
from mock import patch
|
||||
|
||||
|
||||
ANON_ID = 0
|
||||
USER_ID = 'test@mail.com'
|
||||
|
||||
|
||||
def _mock(fn):
|
||||
@wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
with patch('rollbar.init'), \
|
||||
patch('rollbar.report_message'), \
|
||||
patch('dcos.http.post'), \
|
||||
patch('dcos.http.get'), \
|
||||
patch('dcoscli.analytics.session_id'):
|
||||
|
||||
dcoscli.analytics.session_id = ANON_ID
|
||||
fn()
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@_mock
|
||||
def test_cluster_id_not_sent_on_config_call():
|
||||
"""Tests that cluster_id is not sent to segment.io on call to config
|
||||
subcommand
|
||||
"""
|
||||
|
||||
args = ['dcos', 'config', 'show']
|
||||
|
||||
with patch('sys.argv', args), \
|
||||
patch('dcos.mesos.DCOSClient.metadata') as get_cluster_id:
|
||||
assert main() == 0
|
||||
|
||||
assert get_cluster_id.call_count == 0
|
||||
|
||||
|
||||
def test_dont_send_acs_token():
|
||||
"""Tests that we donn't send acs token"""
|
||||
|
||||
args = ['dcos', 'help']
|
||||
env = _env_reporting()
|
||||
version = 'release'
|
||||
|
||||
with patch('sys.argv', args), \
|
||||
patch.dict(os.environ, env), \
|
||||
patch('dcoscli.version', version):
|
||||
to_send = dcoscli.analytics._base_properties()
|
||||
config_to_send = json.loads(to_send.get("config"))
|
||||
assert "core.dcos_acs_token" not in config_to_send
|
||||
|
||||
|
||||
@_mock
|
||||
def test_no_exc():
|
||||
'''Tests that a command which does not raise an exception does not
|
||||
report an exception.
|
||||
|
||||
'''
|
||||
|
||||
args = ['dcos']
|
||||
env = _env_reporting()
|
||||
version = 'release'
|
||||
|
||||
with patch('sys.argv', args), \
|
||||
patch.dict(os.environ, env), \
|
||||
patch('dcoscli.version', version):
|
||||
assert main() == 0
|
||||
|
||||
assert rollbar.report_message.call_count == 0
|
||||
|
||||
|
||||
@_mock
|
||||
def test_exc():
|
||||
'''Tests that a command which does raise an exception does report an
|
||||
exception.
|
||||
|
||||
'''
|
||||
|
||||
args = ['dcos']
|
||||
env = _env_reporting()
|
||||
version = 'release'
|
||||
with patch('sys.argv', args), \
|
||||
patch('dcoscli.version', version), \
|
||||
patch.dict(os.environ, env), \
|
||||
patch('dcoscli.subcommand.SubcommandMain.run_and_capture',
|
||||
return_value=(1, "Traceback")), \
|
||||
patch('dcoscli.analytics._segment_track') as track:
|
||||
|
||||
assert main() == 1
|
||||
assert track.call_count == 2
|
||||
assert rollbar.report_message.call_count == 1
|
||||
|
||||
|
||||
def _env_reporting():
|
||||
path = os.path.join('tests', 'data', 'analytics', 'dcos_reporting.toml')
|
||||
return {constants.DCOS_CONFIG_ENV: path}
|
||||
|
||||
|
||||
@_mock
|
||||
def test_config_reporting_false():
|
||||
'''Test that "core.reporting = false" blocks exception reporting.'''
|
||||
|
||||
args = ['dcos']
|
||||
env = _env_no_reporting()
|
||||
version = 'release'
|
||||
|
||||
with patch('sys.argv', args), \
|
||||
patch('dcoscli.version', version), \
|
||||
patch.dict(os.environ, env), \
|
||||
patch('dcoscli.subcommand.SubcommandMain.run_and_capture',
|
||||
return_value=(1, "Traceback")), \
|
||||
patch('dcoscli.analytics._segment_track') as track:
|
||||
|
||||
assert main() == 1
|
||||
assert track.call_count == 0
|
||||
|
||||
|
||||
def _env_no_reporting():
|
||||
path = os.path.join('tests', 'data', 'analytics', 'dcos_no_reporting.toml')
|
||||
return {constants.DCOS_CONFIG_ENV: path}
|
||||
|
||||
|
||||
def test_command_always_returns_str():
|
||||
"""Test that _command() returns str even if not subcommand specified"""
|
||||
args = ['dcos']
|
||||
with patch('sys.argv', args):
|
||||
assert dcoscli.analytics._command() == ""
|
||||
Reference in New Issue
Block a user