You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2018/10/11 17:11:20 UTC
[trafficcontrol] 10/21: Added -k/--insecure option; bugfixes
This is an automated email from the ASF dual-hosted git repository.
dangogh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
commit 1fe55720aa970e032021bc6ed42446669d3df569
Author: ocket8888 <Br...@comcast.com>
AuthorDate: Wed Oct 3 15:41:42 2018 +0000
Added -k/--insecure option; bugfixes
---
.../cdn-in-a-box/ort/traffic_ops_ort/__init__.py | 8 ++-
.../ort/traffic_ops_ort/config_files.py | 62 ++++++++++++++++------
.../cdn-in-a-box/ort/traffic_ops_ort/to_api.py | 47 +++++++++++-----
3 files changed, 85 insertions(+), 32 deletions(-)
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/__init__.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/__init__.py
index 1274c77..cf6d7f2 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/__init__.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/__init__.py
@@ -60,11 +60,13 @@ def doMain(args:argparse.Namespace) -> int:
logging.info("ATS root installation directory set to: '%s'", configuration.TS_ROOT)
+ configuration.VERIFY = not args.insecure
+
if not configuration.setTOURL(args.Traffic_Ops_URL):
logging.critical("Malformed or invalid Traffic_Ops_URL: '%s'", args.Traffic_Ops_URL)
return 1
- logging.info("Traffic Ops URL '%s' set and verified")
+ logging.info("Traffic Ops URL '%s' set and verified", configuration.TO_URL)
if not configuration.setTOCredentials(args.Traffic_Ops_Login):
logging.critical("Traffic Ops login credentials invalid or incorrect.")
@@ -123,6 +125,10 @@ def main():
" (e.g. '/opt/trafficserver')",
type=str,
default="/")
+ parser.add_argument("-k", "--insecure",
+ help="Skip verification of SSL certificates for Traffic Ops connections. "\
+ "DON'T use this in production!",
+ action="store_true")
parser.add_argument("-v", "--version",
action="version",
version="%(prog)s v"+__version__,
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/config_files.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/config_files.py
index 5ef3efe..06c69d8 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/config_files.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/config_files.py
@@ -22,6 +22,7 @@ presumably for a cache server
import os
import logging
+import typing
#: Holds a set of service names that need reloaded configs, mapped to a boolean which indicates
#: whether (:const:`True`) or not (:const:`False`) a full restart is required.
@@ -65,7 +66,7 @@ class ConfigFile():
self.fname = raw["fnameOnDisk"]
self.location = raw["location"]
if "apiUri" in raw:
- self.URI = '/'.join((TO_URL, raw["apiURI"].lstrip('/')))
+ self.URI = '/'.join((TO_URL, raw["apiUri"].lstrip('/')))
else:
self.URI = raw["url"]
self.scope = raw["scope"]
@@ -122,7 +123,7 @@ class ConfigFile():
else:
cookies = None
- self.contents = utils.getTextResponse(self.URI, cookies=cookies)
+ self.contents = utils.getTextResponse(self.URI, cookies=cookies, verify=conf.VERIFY)
except ValueError as e:
raise ConnectionError from e
@@ -136,23 +137,24 @@ class ConfigFile():
:raises OSError: if the backup directory does not exist, or a backup of this file
could not be written into it.
"""
- from .configuration import MODE
+ from .configuration import MODE, Modes
+ from .utils import getYesNoResponse
backupfile = os.path.join(BACKUP_DIR, self.fname)
willClobber = False
if os.path.isfile(backupfile):
willClobber = True
- if MODE == MODE.INTERACTIVE:
+ if MODE is Modes.INTERACTIVE:
prmpt = ("Write backup file %s%%s?" % backupfile)
prmpt %= " - will clobber existing file by the same name - " if willClobber else ''
- if not getYesNoResponse(prmpt, default='Y')
+ if not getYesNoResponse(prmpt, default='Y'):
return
elif willClobber:
logging.warning("Clobbering existing backup file '%s'!", backupfile)
- if MODE != MODE.REPORT:
+ if MODE is not Modes.REPORT:
with open(backupfile, 'w') as fp:
fp.write(contents)
@@ -165,15 +167,31 @@ class ConfigFile():
:raises OSError: when reading/writing files fails for some reason
"""
- from .to_api import SERVER_INFO
- from .configuration import MODE
+ from . import utils
+ from .configuration import MODE, Modes, SERVER_INFO
from .services import NEEDED_RELOADS, FILES_THAT_REQUIRE_RELOADS
finalContents = sanitizeContents(str(self))
logging.info("Sanitized output: \n%s", finalContents)
+ if not os.path.isdir(self.location):
+ if MODE is Modes.INTERACTIVE and\
+ not utils.getYesNoResponse("Create configuration directory %s?" % self.path, 'Y'):
+ logging.warning("%s will not be created - some services may not work properly!",
+ self.path)
+ return
+
+ logging.info("Directory %s will be created", self.location)
+ logging.info("File %s will be created", self.path)
+
+ if MODE is not Modes.REPORT:
+ os.makedirs(self.location)
+ with open(self.path, 'x') as fp:
+ fp.write(finalContents)
+ return
+
if not os.path.isfile(self.path):
- if MODE == MODE.INTERACTIVE and\
+ if MODE is Modes.INTERACTIVE and\
not utils.getYesNoResponse("Create configuration file %s?"%self.path, default='Y'):
logging.warning("%s will not be created - some services may not work properly!",
self.path)
@@ -181,7 +199,7 @@ class ConfigFile():
logging.info("File %s will be created", self.path)
- if MODE != MODE.REPORT:
+ if MODE is not Modes.REPORT:
with open(self.path, 'x') as fp:
fp.write(finalContents)
return
@@ -190,13 +208,18 @@ class ConfigFile():
onDiskContents = fp.readlines()
if filesDiffer(finalContents.splitlines(), onDiskContents):
self.backup(''.join(onDiskContents))
- if MODE != MODE.REPORT:
+ if MODE is not Modes.REPORT:
fp.seek(0)
fp.truncate()
+
+ # Ensure POSIX-compliant files
+ if not finalContents.endswith('\n'):
+ finalContents += '\n'
+
fp.write(finalContents)
if self.fname in FILES_THAT_REQUIRE_RELOADS:
NEEDED_RELOADS.add(FILES_THAT_REQUIRE_RELOADS[self.fname])
- logging.info("File written to %s", path)
+ logging.info("File written to %s", self.path)
else:
logging.info("File doesn't differ from disk; nothing to do")
@@ -218,7 +241,7 @@ def filesDiffer(a:typing.List[str], b:typing.List[str]) -> bool:
if len(a) != len(b):
return True
- for l, i in enumerate(a):
+ for i, l in enumerate(a):
if l != b[i]:
return True
@@ -232,9 +255,14 @@ def sanitizeContents(raw:str) -> str:
:returns: The same contents, but with special replacement strings parsed out and HTML-encoded
symbols decoded to their literal values
"""
- from .to_api import SERVER_INFO
+ from .configuration import SERVER_INFO
out = []
- for line in raw.format(SERVER_INFO).splitlines():
+
+ # These double curly braces escape the behaviour of Python's `str.format` method to attempt
+ # to use curly brace-enclosed text as a key into a dictonary of its arguments. They'll be
+ # rendered into single braces in the output of `.format`, leaving the string ultimately
+ # unchanged in that respect.
+ for line in raw.replace('{', "{{").replace('}', "}}").format(SERVER_INFO).splitlines():
tmp=(" ".join(line.split())).strip() #squeezes spaces and trims leading and trailing spaces
tmp=tmp.replace("&", '&') #decodes HTML-encoded ampersands
tmp=tmp.replace(">", '>') #decodes HTML-encoded greater-than symbols
@@ -256,8 +284,8 @@ def initBackupDir():
logging.info("Initializing backup dir %s", BACKUP_DIR)
if not os.path.isdir(BACKUP_DIR):
- if MODE != Modes.REPORT:
- os.mkdir(backupdir)
+ if conf.MODE != conf.Modes.REPORT:
+ os.mkdir(BACKUP_DIR)
else:
logging.error("Cannot create non-existent backup dir in REPORT mode!")
else:
diff --git a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/to_api.py b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/to_api.py
index cc929ab..0bc1aa1 100644
--- a/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/to_api.py
+++ b/infrastructure/cdn-in-a-box/ort/traffic_ops_ort/to_api.py
@@ -70,7 +70,7 @@ class ServerInfo():
try:
self.cdnId = raw["cdnId"]
self.cdnName = raw["cdnName"]
- self.profile = raw["profile"]
+ self.profileName = raw["profileName"]
self.profileId = raw["profileId"]
self.serverId = raw["serverId"]
self.serverIpv4 = raw["serverIpv4"]
@@ -109,7 +109,7 @@ class ServerInfo():
return fmt.replace("__SERVER_TCP_PORT__", str(self.serverTcpPort)\
if self.serverTcpPort != 80 else "")
-def TOPost(uri:str, data:dict, verify:bool = True):
+def TOPost(uri:str, data:dict) -> str:
"""
POSTs the passed data in a request to the specified API endpoint
@@ -121,12 +121,28 @@ def TOPost(uri:str, data:dict, verify:bool = True):
to the request path; callers need not worry about whether the ``uri`` ought to
begin with a slash.
- :param verify: Whether or not to verify the Traffic Ops server's SSL keys during the request
- handshake (only has meaning if Traffic Ops is serving via HTTPS)
+ :returns: The Traffic Ops server's response to the POST request - possibly empty - as a UTF-8
+ string
:raises ConnectionError: when an error occurs trying to communicate with Traffic Ops
"""
+ from . import configuration as conf
-def getTOJSONResponse(uri:str, verify:bool = True) -> dict:
+ uri = '/'.join((conf.TO_URL, uri.lstrip('/')))
+ logging.info("POSTing %r to %s", data, uri)
+
+ if datetime.datetime.now().timestamp() >= conf.TO_COOKIE:
+ try:
+ conf.getNewTOCookie()
+ except PermissionError as e:
+ raise ConnectionError from e
+
+ resp = requests.post(uri, cookies=conf.TO_COOKIE, verify=conf.VERIFY, data=data)
+
+ logging.debug("Raw response from Traffic Ops: %s\n%s\n%s", resp, resp.headers, resp.content)
+
+ return resp.text
+
+def getTOJSONResponse(uri:str) -> dict:
"""
A wrapper around :func:`traffic_ops_ort.utils.getJSONResponse` that handles cookies and
tacks on the top-level Traffic Ops URL.
@@ -138,8 +154,6 @@ def getTOJSONResponse(uri:str, verify:bool = True) -> dict:
to the request path; callers need not worry about whether the ``uri`` ought to
begin with a slash.
- :param verify: Whether or not to verify the Traffic Ops server's SSL keys during the request
- handshake (only has meaning if Traffic Ops is serving via HTTPS)
:returns: The decoded JSON response as an object
.. note:: If the API response containes a 'response' object, this function will
@@ -162,7 +176,9 @@ def getTOJSONResponse(uri:str, verify:bool = True) -> dict:
except PermissionError as e:
raise ConnectionError from e
- resp=utils.getJSONResponse(uri,cookies={conf.TO_COOKIE.name:conf.TO_COOKIE.value},verify=verify)
+ resp = utils.getJSONResponse(uri,
+ cookies = {conf.TO_COOKIE.name:conf.TO_COOKIE.value},
+ verify = conf.VERIFY)
if "response" in resp:
if "alerts" in resp:
@@ -200,7 +216,7 @@ def getUpdateStatus(host:str) -> dict:
if host in CACHED_UPDATE_STATUS:
return CACHED_UPDATE_STATUS[host]
- CACHED_UPDATE_STATUS[host] = getTOJSONResponse("api/1.3/servers/%s/updateStatus" % host)
+ CACHED_UPDATE_STATUS[host] = getTOJSONResponse("api/1.3/servers/%s/update_status" % host)
return CACHED_UPDATE_STATUS[host]
@@ -288,20 +304,23 @@ def updateTrafficOps():
"""
Updates Traffic Ops's knowledge of this server's update status.
"""
- from .configuration import MODE
+ from .configuration import MODE, Modes, HOSTNAME
from .utils import getYesNoResponse as getYN
- if MODE == MODE.INTERACTIVE and not getYN("Update Traffic Ops?", default='Y'):
+ if MODE is Modes.INTERACTIVE and not getYN("Update Traffic Ops?", default='Y'):
logging.warning("Update will not be performed; you should do this manually")
return
logging.info("Updating Traffic Ops")
- if MODE == MODE.REPORT:
+ if MODE is Modes.REPORT:
return
payload = {"updated": False, "reval_updated": False}
- repsonse = utils.
+ response = TOPost("/update/%s" % HOSTNAME[0], payload)
+
+ if response:
+ logging.info("Traffic Ops response: %s", response)
def getMyConfigFiles() -> typing.List[dict]:
"""
@@ -327,7 +346,7 @@ def getMyConfigFiles() -> typing.List[dict]:
except KeyError as e:
raise ValueError from e
-def getMyChkconfig() -> typing.List[dict]
+def getMyChkconfig() -> typing.List[dict]:
"""
Fetches the 'chkconfig' for this server