blob: 1ced25a15addffbeedb16307c399afc888de086d [file] [log] [blame]
# Copyright 2014 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
"""Generates the swarming_bot.zip archive for the bot.
Unlike the other source files, this file can be run from ../tools/bot_archive.py
stand-alone to generate a swarming_bot.zip for local testing so it doesn't
import anything from the AppEngine SDK.
The hash of the content of the files in the archive is used to define the
current version of the swarming bot code.
"""
import hashlib
import io
import json
import logging
import os
import zipfile
# List of files needed by the swarming bot.
# TODO(maruel): Make the list automatically generated?
FILES = (
'adb/adb_commands.py',
'adb/adb_protocol.py',
'adb/common.py',
'adb/contrib/adb_commands_safe.py',
'adb/contrib/high.py',
'adb/contrib/__init__.py',
'adb/contrib/parallel.py',
'adb/fastboot.py',
'adb/filesync_protocol.py',
'adb/__init__.py',
'adb/sign_pythonrsa.py',
'adb/usb_exceptions.py',
'api/bot.py',
'api/__init__.py',
'api/oauth.py',
'api/os_utilities.py',
'api/platforms/android.py',
'api/platforms/common.py',
'api/platforms/gce.py',
'api/platforms/gpu.py',
'api/platforms/__init__.py',
'api/platforms/aix.py',
'api/platforms/linux.py',
'api/platforms/osx.py',
'api/platforms/posix.py',
'api/platforms/win.py',
'bb/go/chromium/__init__.py',
'bb/go/chromium/org/__init__.py',
'bb/go/chromium/org/luci/buildbucket/__init__.py',
'bb/go/chromium/org/luci/buildbucket/proto/builder_common_pb2.py',
'bb/go/chromium/org/luci/buildbucket/proto/build_field_visibility_pb2.py',
'bb/go/chromium/org/luci/buildbucket/proto/build_pb2.py',
'bb/go/chromium/org/luci/buildbucket/proto/common_pb2.py',
'bb/go/chromium/org/luci/buildbucket/proto/field_option_pb2.py',
'bb/go/chromium/org/luci/buildbucket/proto/__init__.py',
'bb/go/chromium/org/luci/buildbucket/proto/launcher_pb2.py',
'bb/go/chromium/org/luci/buildbucket/proto/project_config_pb2.py',
'bb/go/chromium/org/luci/buildbucket/proto/step_pb2.py',
'bb/go/chromium/org/luci/buildbucket/proto/task_pb2.py',
'bb/go/chromium/org/luci/common/__init__.py',
'bb/go/chromium/org/luci/common/proto/__init__.py',
'bb/go/chromium/org/luci/common/proto/options_pb2.py',
'bb/go/chromium/org/luci/__init__.py',
'bb/go/chromium/org/luci/resultdb/__init__.py',
'bb/go/chromium/org/luci/resultdb/proto/__init__.py',
'bb/go/chromium/org/luci/resultdb/proto/v1/common_pb2.py',
'bb/go/chromium/org/luci/resultdb/proto/v1/__init__.py',
'bb/go/chromium/org/luci/resultdb/proto/v1/instruction_pb2.py',
'bb/go/chromium/org/luci/resultdb/proto/v1/invocation_pb2.py',
'bb/go/chromium/org/luci/resultdb/proto/v1/predicate_pb2.py',
'bb/go/__init__.py',
'bb/__init__.py',
'bot_code/bot_auth.py',
'bot_code/bot_main.py',
'bot_code/clock.py',
'bot_code/common.py',
'bot_code/file_reader.py',
'bot_code/file_refresher.py',
'bot_code/__init__.py',
'bot_code/remote_client_errors.py',
'bot_code/remote_client.py',
'bot_code/singleton.py',
'bot_code/task_runner.py',
'client/auth.py',
'client/cipd.py',
'client/errors.py',
'client/local_caching.py',
'client/run_isolated.py',
'config/__init__.py',
'libs/__init__.py',
'libs/luci_context/__init__.py',
'libs/luci_context/luci_context.py',
'__main__.py',
'python_libusb1/__init__.py',
'python_libusb1/libusb1.py',
'python_libusb1/usb1.py',
'signal_trace.py',
'third_party/cachetools/abc.py',
'third_party/cachetools/cache.py',
'third_party/cachetools/func.py',
'third_party/cachetools/__init__.py',
'third_party/cachetools/keys.py',
'third_party/cachetools/lfu.py',
'third_party/cachetools/lru.py',
'third_party/cachetools/rr.py',
'third_party/cachetools/ttl.py',
'third_party/certifi/cacert.pem',
'third_party/certifi/__init__.py',
'third_party/chardet/__init__.py',
'third_party/colorama/ansi.py',
'third_party/colorama/ansitowin32.py',
'third_party/colorama/initialise.py',
'third_party/colorama/__init__.py',
'third_party/colorama/win32.py',
'third_party/colorama/winterm.py',
'third_party/depot_tools/fix_encoding.py',
'third_party/depot_tools/__init__.py',
'third_party/depot_tools/subcommand.py',
'third_party/distro/__init__.py',
'third_party/googleapiclient/channel.py',
'third_party/googleapiclient/discovery_cache/appengine_memcache.py',
'third_party/googleapiclient/discovery_cache/base.py',
'third_party/googleapiclient/discovery_cache/file_cache.py',
'third_party/googleapiclient/discovery_cache/__init__.py',
'third_party/googleapiclient/discovery.py',
'third_party/googleapiclient/errors.py',
'third_party/googleapiclient/http.py',
'third_party/googleapiclient/__init__.py',
'third_party/googleapiclient/mimeparse.py',
'third_party/googleapiclient/model.py',
'third_party/googleapiclient/sample_tools.py',
'third_party/googleapiclient/schema.py',
'third_party/google/api/field_behavior_pb2.py',
'third_party/google/api/__init__.py',
'third_party/google/auth/app_engine.py',
'third_party/google/auth/_cloud_sdk.py',
'third_party/google/auth/compute_engine/credentials.py',
'third_party/google/auth/compute_engine/__init__.py',
'third_party/google/auth/compute_engine/_metadata.py',
'third_party/google/auth/credentials.py',
'third_party/google/auth/crypt/base.py',
'third_party/google/auth/crypt/_cryptography_rsa.py',
'third_party/google/auth/crypt/__init__.py',
'third_party/google/auth/crypt/_python_rsa.py',
'third_party/google/auth/crypt/rsa.py',
'third_party/google/auth/_default.py',
'third_party/google/auth/environment_vars.py',
'third_party/google/auth/exceptions.py',
'third_party/google/auth/_helpers.py',
'third_party/google/auth/iam.py',
'third_party/google/auth/impersonated_credentials.py',
'third_party/google/auth/__init__.py',
'third_party/google/auth/jwt.py',
'third_party/google/auth/_oauth2client.py',
'third_party/google/auth/_service_account_info.py',
'third_party/google/auth/transport/grpc.py',
'third_party/google/auth/transport/_http_client.py',
'third_party/google/auth/transport/__init__.py',
'third_party/google/auth/transport/requests.py',
'third_party/google/auth/transport/urllib3.py',
'third_party/google/__init__.py',
'third_party/google/oauth2/_client.py',
'third_party/google/oauth2/credentials.py',
'third_party/google/oauth2/id_token.py',
'third_party/google/oauth2/__init__.py',
'third_party/google/oauth2/service_account.py',
'third_party/google/protobuf/any_pb2.py',
'third_party/google/protobuf/descriptor_database.py',
'third_party/google/protobuf/descriptor_pb2.py',
'third_party/google/protobuf/descriptor_pool.py',
'third_party/google/protobuf/descriptor.py',
'third_party/google/protobuf/duration_pb2.py',
'third_party/google/protobuf/empty_pb2.py',
'third_party/google/protobuf/field_mask_pb2.py',
'third_party/google/protobuf/__init__.py',
'third_party/google/protobuf/internal/api_implementation.py',
'third_party/google/protobuf/internal/containers.py',
'third_party/google/protobuf/internal/decoder.py',
'third_party/google/protobuf/internal/encoder.py',
'third_party/google/protobuf/internal/enum_type_wrapper.py',
'third_party/google/protobuf/internal/extension_dict.py',
'third_party/google/protobuf/internal/__init__.py',
'third_party/google/protobuf/internal/message_listener.py',
'third_party/google/protobuf/internal/python_message.py',
'third_party/google/protobuf/internal/type_checkers.py',
'third_party/google/protobuf/internal/well_known_types.py',
'third_party/google/protobuf/internal/wire_format.py',
'third_party/google/protobuf/json_format.py',
'third_party/google/protobuf/message_factory.py',
'third_party/google/protobuf/message.py',
'third_party/google/protobuf/reflection.py',
'third_party/google/protobuf/struct_pb2.py',
'third_party/google/protobuf/symbol_database.py',
'third_party/google/protobuf/text_encoding.py',
'third_party/google/protobuf/text_format.py',
'third_party/google/protobuf/timestamp_pb2.py',
'third_party/google/protobuf/wrappers_pb2.py',
'third_party/httplib2/python2/httplib2/cacerts.txt',
'third_party/httplib2/python2/httplib2/certs.py',
'third_party/httplib2/python2/httplib2/__init__.py',
'third_party/httplib2/python2/httplib2/iri2uri.py',
'third_party/httplib2/python2/httplib2/socks.py',
'third_party/httplib2/python3/httplib2/cacerts.txt',
'third_party/httplib2/python3/httplib2/certs.py',
'third_party/httplib2/python3/httplib2/__init__.py',
'third_party/httplib2/python3/httplib2/iri2uri.py',
'third_party/httplib2/python3/httplib2/socks.py',
'third_party/idna/__init__.py',
'third_party/infra_libs/_command_line_linux.py',
'third_party/infra_libs/_command_line_stub.py',
'third_party/infra_libs/httplib2_utils.py',
'third_party/infra_libs/__init__.py',
'third_party/infra_libs/ts_mon/common/distribution.py',
'third_party/infra_libs/ts_mon/common/errors.py',
'third_party/infra_libs/ts_mon/common/helpers.py',
'third_party/infra_libs/ts_mon/common/http_metrics.py',
'third_party/infra_libs/ts_mon/common/__init__.py',
'third_party/infra_libs/ts_mon/common/interface.py',
'third_party/infra_libs/ts_mon/common/metrics.py',
'third_party/infra_libs/ts_mon/common/metric_store.py',
'third_party/infra_libs/ts_mon/common/monitors.py',
'third_party/infra_libs/ts_mon/common/pb_to_popo.py',
'third_party/infra_libs/ts_mon/common/standard_metrics.py',
'third_party/infra_libs/ts_mon/common/targets.py',
'third_party/infra_libs/ts_mon/config.py',
'third_party/infra_libs/ts_mon/__init__.py',
'third_party/infra_libs/ts_mon/protos/acquisition_network_device_pb2.py',
'third_party/infra_libs/ts_mon/protos/acquisition_task_pb2.py',
'third_party/infra_libs/ts_mon/protos/any_pb2.py',
'third_party/infra_libs/ts_mon/protos/__init__.py',
'third_party/infra_libs/ts_mon/protos/metrics_pb2.py',
'third_party/infra_libs/ts_mon/protos/timestamp_pb2.py',
'third_party/infra_libs/utils.py',
'third_party/oauth2client/client.py',
'third_party/oauth2client/clientsecrets.py',
'third_party/oauth2client/contrib/appengine.py',
'third_party/oauth2client/contrib/dictionary_storage.py',
'third_party/oauth2client/contrib/gce.py',
'third_party/oauth2client/contrib/__init__.py',
'third_party/oauth2client/contrib/keyring_storage.py',
'third_party/oauth2client/contrib/locked_file.py',
'third_party/oauth2client/contrib/multistore_file.py',
'third_party/oauth2client/contrib/xsrfutil.py',
'third_party/oauth2client/crypt.py',
'third_party/oauth2client/file.py',
'third_party/oauth2client/_helpers.py',
'third_party/oauth2client/__init__.py',
'third_party/oauth2client/_openssl_crypt.py',
'third_party/oauth2client/_pure_python_crypt.py',
'third_party/oauth2client/_pycrypto_crypt.py',
'third_party/oauth2client/service_account.py',
'third_party/oauth2client/tools.py',
'third_party/oauth2client/util.py',
'third_party/pyasn1_modules/pyasn1_modules/__init__.py',
'third_party/pyasn1_modules/pyasn1_modules/pem.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc1155.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc1157.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc1901.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc1902.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc1905.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc2251.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc2314.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc2315.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc2437.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc2459.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc2511.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc2560.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc2986.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc3279.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc3280.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc3281.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc3412.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc3414.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc3447.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc3852.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc4210.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc4211.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc5083.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc5084.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc5208.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc5280.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc5652.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc6402.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc8103.py',
'third_party/pyasn1_modules/pyasn1_modules/rfc8226.py',
'third_party/pyasn1/pyasn1/codec/ber/decoder.py',
'third_party/pyasn1/pyasn1/codec/ber/encoder.py',
'third_party/pyasn1/pyasn1/codec/ber/eoo.py',
'third_party/pyasn1/pyasn1/codec/ber/__init__.py',
'third_party/pyasn1/pyasn1/codec/cer/decoder.py',
'third_party/pyasn1/pyasn1/codec/cer/encoder.py',
'third_party/pyasn1/pyasn1/codec/cer/__init__.py',
'third_party/pyasn1/pyasn1/codec/der/decoder.py',
'third_party/pyasn1/pyasn1/codec/der/encoder.py',
'third_party/pyasn1/pyasn1/codec/der/__init__.py',
'third_party/pyasn1/pyasn1/codec/__init__.py',
'third_party/pyasn1/pyasn1/codec/native/decoder.py',
'third_party/pyasn1/pyasn1/codec/native/encoder.py',
'third_party/pyasn1/pyasn1/codec/native/__init__.py',
'third_party/pyasn1/pyasn1/compat/binary.py',
'third_party/pyasn1/pyasn1/compat/calling.py',
'third_party/pyasn1/pyasn1/compat/dateandtime.py',
'third_party/pyasn1/pyasn1/compat/__init__.py',
'third_party/pyasn1/pyasn1/compat/integer.py',
'third_party/pyasn1/pyasn1/compat/octets.py',
'third_party/pyasn1/pyasn1/compat/string.py',
'third_party/pyasn1/pyasn1/debug.py',
'third_party/pyasn1/pyasn1/error.py',
'third_party/pyasn1/pyasn1/__init__.py',
'third_party/pyasn1/pyasn1/type/base.py',
'third_party/pyasn1/pyasn1/type/char.py',
'third_party/pyasn1/pyasn1/type/constraint.py',
'third_party/pyasn1/pyasn1/type/error.py',
'third_party/pyasn1/pyasn1/type/__init__.py',
'third_party/pyasn1/pyasn1/type/namedtype.py',
'third_party/pyasn1/pyasn1/type/namedval.py',
'third_party/pyasn1/pyasn1/type/opentype.py',
'third_party/pyasn1/pyasn1/type/tagmap.py',
'third_party/pyasn1/pyasn1/type/tag.py',
'third_party/pyasn1/pyasn1/type/univ.py',
'third_party/pyasn1/pyasn1/type/useful.py',
'third_party/requests/adapters.py',
'third_party/requests/api.py',
'third_party/requests/auth.py',
'third_party/requests/certs.py',
'third_party/requests/compat.py',
'third_party/requests/cookies.py',
'third_party/requests/exceptions.py',
'third_party/requests/help.py',
'third_party/requests/hooks.py',
'third_party/requests/__init__.py',
'third_party/requests/_internal_utils.py',
'third_party/requests/models.py',
'third_party/requests/packages.py',
'third_party/requests/README.swarming',
'third_party/requests/sessions.py',
'third_party/requests/status_codes.py',
'third_party/requests/structures.py',
'third_party/requests/update.sh',
'third_party/requests/utils.py',
'third_party/requests/__version__.py',
'third_party/rsa/rsa/asn1.py',
'third_party/rsa/rsa/bigfile.py',
'third_party/rsa/rsa/cli.py',
'third_party/rsa/rsa/common.py',
'third_party/rsa/rsa/_compat.py',
'third_party/rsa/rsa/core.py',
'third_party/rsa/rsa/__init__.py',
'third_party/rsa/rsa/key.py',
'third_party/rsa/rsa/parallel.py',
'third_party/rsa/rsa/pem.py',
'third_party/rsa/rsa/pkcs1.py',
'third_party/rsa/rsa/prime.py',
'third_party/rsa/rsa/randnum.py',
'third_party/rsa/rsa/transform.py',
'third_party/rsa/rsa/util.py',
'third_party/rsa/rsa/varblock.py',
'third_party/rsa/rsa/_version133.py',
'third_party/rsa/rsa/_version200.py',
'third_party/six/__init__.py',
'third_party/uritemplate/__init__.py',
'third_party/urllib3/_base_connection.py',
'third_party/urllib3/_collections.py',
'third_party/urllib3/connectionpool.py',
'third_party/urllib3/connection.py',
'third_party/urllib3/contrib/__init__.py',
'third_party/urllib3/contrib/pyopenssl.py',
'third_party/urllib3/contrib/_securetransport/bindings.py',
'third_party/urllib3/contrib/_securetransport/__init__.py',
'third_party/urllib3/contrib/_securetransport/low_level.py',
'third_party/urllib3/contrib/securetransport.py',
'third_party/urllib3/contrib/socks.py',
'third_party/urllib3/exceptions.py',
'third_party/urllib3/fields.py',
'third_party/urllib3/filepost.py',
'third_party/urllib3/__init__.py',
'third_party/urllib3/poolmanager.py',
'third_party/urllib3/post_handshake_auth.patch',
'third_party/urllib3/py.typed',
'third_party/urllib3/_request_methods.py',
'third_party/urllib3/response.py',
'third_party/urllib3/util/connection.py',
'third_party/urllib3/util/__init__.py',
'third_party/urllib3/util/proxy.py',
'third_party/urllib3/util/request.py',
'third_party/urllib3/util/response.py',
'third_party/urllib3/util/retry.py',
'third_party/urllib3/util/ssl_match_hostname.py',
'third_party/urllib3/util/ssl_.py',
'third_party/urllib3/util/ssltransport.py',
'third_party/urllib3/util/timeout.py',
'third_party/urllib3/util/url.py',
'third_party/urllib3/util/util.py',
'third_party/urllib3/util/wait.py',
'third_party/urllib3/_version.py',
'utils/authenticators.py',
'utils/auth_server.py',
'utils/cacert.pem',
'utils/configs.py',
'utils/file_path.py',
'utils/fs.py',
'utils/__init__.py',
'utils/large.py',
'utils/logging_utils.py',
'utils/lru.py',
'utils/net.py',
'utils/oauth.py',
'utils/on_error.py',
'utils/subprocess42.py',
'utils/threading_utils.py',
'utils/tools.py',
'utils/zip_package.py',
)
def is_windows():
"""Returns True if this code is running under Windows."""
return os.__file__[0] != '/'
def resolve_symlink(path):
"""Processes path containing symlink on Windows.
This is needed to make ../swarming_bot/main_test.py pass on Windows because
git on Windows renders symlinks as normal files.
"""
if not is_windows():
# Only does this dance on Windows.
return path
parts = os.path.normpath(path).split(os.path.sep)
for i in range(2, len(parts)):
partial = os.path.sep.join(parts[:i])
if os.path.isfile(partial):
with open(partial) as f:
link = f.read()
assert '\n' not in link and link, link
parts[i-1] = link
return os.path.normpath(os.path.sep.join(parts))
def yield_swarming_bot_files(
root_dir, host, host_version, additionals, settings):
"""Yields all the files to map as tuple(filename, content).
config.json is injected with json data about the server.
This function guarantees that the output is sorted by filename.
"""
items = {i: None for i in FILES}
items.update(additionals)
items['config/config.json'] = _make_config_json(host, host_version,
settings).encode()
logging.debug('Bot config.json: %s', items['config/config.json'])
for item, content in sorted(items.items()):
if content is not None:
yield item, content
else:
with open(resolve_symlink(os.path.join(root_dir, item)), 'rb') as f:
yield item, f.read()
def get_swarming_bot_zip(
root_dir, host, host_version, additionals, settings):
"""Returns a zipped file of all the files a bot needs to run.
Arguments:
root_dir: directory swarming_bot.
additionals: dict(filepath: content) of additional items to put into the zip
file, in addition to FILES and MAPPED. In practice, it's going to be a
custom bot_config.py.
enable_ts_monitoring: bool if ts_mon should be enabled on the bot.
Returns:
Tuple(str being the zipped file's content, bot version (SHA256) it
represents).
"""
zip_memory_file = io.BytesIO()
h = hashlib.sha256()
with zipfile.ZipFile(zip_memory_file, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for name, content in yield_swarming_bot_files(
root_dir, host, host_version, additionals, settings):
# We must pass ZipInfo object, otherwise zipfile will generate ZipInfo
# on its own, setting date_time to the current time, thus making the zip
# archive non-deterministic. See zipfile.py source for where external_attr
# comes from. It is copy-pasta from there.
zinfo = zipfile.ZipInfo(filename=name)
zinfo.compress_type = zip_file.compression
if zinfo.filename.endswith('/'):
zinfo.external_attr = 0o40775 << 16 # drwxrwxr-x
zinfo.external_attr |= 0x10 # MS-DOS directory flag
else:
zinfo.external_attr = 0o600 << 16 # ?rw-------
zip_file.writestr(zinfo, content)
h.update(str(len(name)).encode())
h.update(name.encode())
h.update(str(len(content)).encode())
h.update(content)
data = zip_memory_file.getvalue()
bot_version = h.hexdigest()
logging.info(
'get_swarming_bot_zip(%s) is %d bytes; %s',
additionals.keys(), len(data), bot_version)
return data, bot_version
def get_swarming_bot_version(
root_dir, host, host_version, additionals, settings):
"""Returns the SHA256 hash of the bot code, representing the version.
Arguments:
root_dir: directory swarming_bot.
additionals: See get_swarming_bot_zip's doc.
enable_ts_monitoring: bool if ts_mon should be enabled on the bot.
Returns:
The SHA256 hash of the bot code.
"""
h = hashlib.sha256()
try:
# TODO(maruel): Deduplicate from zip_package.generate_version().
for name, content in yield_swarming_bot_files(
root_dir, host, host_version, additionals, settings):
h.update(str(len(name)))
h.update(name)
h.update(str(len(content)))
h.update(content)
except IOError:
logging.warning('Missing expected file. Hash will be invalid.')
bot_version = h.hexdigest()
logging.info(
'get_swarming_bot_version(%s) = %s', sorted(additionals), bot_version)
return bot_version
## Private stuff.
def _make_config_json(host, host_version, settings):
"""Generates a config.json to embed in swarming_bot.zip"""
# The keys must match ../swarming_bot/config/config.json.
config = {
'enable_ts_monitoring': False,
'server': host.rstrip('/'),
'server_version': host_version,
}
if settings:
config['enable_ts_monitoring'] = settings.enable_ts_monitoring
return json.dumps(config)