Skip to content

Commit e462c67

Browse files
authored
Merge pull request #730 from sassoftware/opensearch-path-based-fix
Opensearch path based fix
2 parents 69fae92 + 8cace25 commit e462c67

File tree

1 file changed

+55
-32
lines changed

1 file changed

+55
-32
lines changed

logging/bin/getlogs.py

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#! /usr/bin/python3
1+
#! /usr/bin/python3
22

33
# Copyright © 2023, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
44
# SPDX-License-Identifier: Apache-2.0
@@ -13,7 +13,7 @@
1313
from subprocess import run
1414
##v 0.2.0
1515

16-
def validate_input(checkInput):
16+
def validate_input(checkInput):
1717
""" Validate the arguments passed by the user to ensure script will function properly"""
1818

1919
##Set maximum log limit for output
@@ -46,20 +46,20 @@ def validate_input(checkInput):
4646
if (checkInput['force'] == False):
4747
print("\nUser specified output file already exists. Use -f to overwrite the file.\n")
4848
sys.exit()
49-
49+
5050
safe_dir = os.getcwd() ## Check for path traversal attack
5151
if os.path.commonprefix((os.path.realpath(checkInput['out-filename']),safe_dir)) != safe_dir:
5252
print("Error: Out-file path must be in the current working directory.")
5353
sys.exit()
5454

5555
try:
56-
x = open(checkInput['out-filename'], 'w')
57-
x.close()
56+
x = open(checkInput['out-filename'], 'w')
57+
x.close()
5858
except FileNotFoundError as e:
5959
print("Error: Output file path not found. Please verify output file path. ")
6060
sys.exit()
6161

62-
if checkInput['savequery']: ##Find saved query path location
62+
if checkInput['savequery']: ##Find saved query path location
6363
if(type(checkInput['savequery']) == list):
6464
checkInput['savequery']= " ".join(checkInput['savequery'])
6565
if (checkInput['savequery'].find('.') == -1):
@@ -77,8 +77,8 @@ def validate_input(checkInput):
7777
if (not os.path.isfile(checkInput['query-filename'])):
7878
print("Error: Invalid query file path.")
7979
sys.exit()
80-
81-
##Time Validator - Verifies input, and converts it to UTC
80+
81+
##Time Validator - Verifies input, and converts it to UTC
8282
if (type(checkInput['dateTimeStart']) ==list):
8383
checkInput['dateTimeStart'] = " ".join(checkInput['dateTimeStart'])
8484
if (type(checkInput['dateTimeEnd']) == list):
@@ -93,7 +93,7 @@ def validate_input(checkInput):
9393
checkInput['dateTimeStart'] = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(time.mktime(checkInput['dateTimeStart'])))
9494
checkInput['dateTimeEnd'] = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(time.mktime(checkInput['dateTimeEnd'])))
9595
except ValueError:
96-
print("One or more date(s) have been formatted incorrectly. Correct format is Y-M-D H:M:S. Ex: 1999-02-16 10:00:00")
96+
print("One or more date(s) have been formatted incorrectly. Correct format is Y-M-D H:M:S. Ex: 1999-02-16 10:00:00")
9797
sys.exit()
9898

9999
if (checkInput['message']): ## Argument formatting for query builder
@@ -125,16 +125,16 @@ def open_port():
125125
time.sleep(5)
126126

127127
return port
128-
129-
def build_query(args):
128+
129+
def build_query(args):
130130
"""Generates Query using Opensearch DSL"""
131131
"""Takes arguments from user and returns a JSON-format query to pass to OpenSearch API"""
132-
first = True
132+
first = True
133133
sourcerequested = False
134134
argcounter=0 ##Counts unique options entered by user, sets min_match to this number
135-
if (not args['query-filename']):
135+
if (not args['query-filename']):
136136
tfile = tempfile.NamedTemporaryFile(delete = False) ##If User has not specified query file, create temp file for one.
137-
temp = open(tfile.name, 'w')
137+
temp = open(tfile.name, 'w')
138138

139139
temp.write('{"size": ' + str(args['maxInt']) + ',"sort": [{"@timestamp": {"order": "desc","unmapped_type": "boolean"} }]') ## Establish size of query, remove scoring
140140
temp.write(', "query": {"bool": {"must":[ {"range": {"@timestamp": {"gte": "' + args['dateTimeStart'] + '","lt": "' + args['dateTimeEnd'] + '"} } }], ') ##Establish Query with Time Range Requirements
@@ -143,9 +143,9 @@ def build_query(args):
143143
if (("kube" in argname or "level" in argname or "mes" in argname or "log" in argname) and (args[argname]) and argname.find('-ex')==-1): ##Looking for non-exclusion options that are not NoneType in args argsionary
144144
argcounter+=1
145145
if (first == True):
146-
first = False
146+
first = False
147147
else:
148-
temp.write(',')
148+
temp.write(',')
149149
if argname!='message':
150150
for i in range(len(args[argname])):
151151
temp.write('{"match_phrase": { "' + argname + '":"' + args[argname][i] + '" } }')
@@ -183,30 +183,30 @@ def build_query(args):
183183
else:
184184
temp.write('"_source": false }')
185185
temp.close()
186-
186+
187187
temp = open(tfile.name, 'r')
188-
query = " ".join([line.strip() for line in temp]) ## Turns query into string
188+
query = " ".join([line.strip() for line in temp]) ## Turns query into string
189189
temp.close()
190190
tfile.close()
191191

192192
else: ##Open existing query
193193
x = open(args['query-filename'], 'rt')
194194
query = " ".join([line.strip() for line in x]) ## Turn file into string, return.
195195
x.close()
196-
196+
197197
return query
198198

199199
def get_arguments():
200200
"""List of valid arguments that are read from user as soon as program is run, nargs=+ indicates that argument takes multiple whitespace separated values. """
201-
201+
202202
parser = argparse.ArgumentParser(prog='getLogs.py', usage='\n%(prog)s [options]', description="""This program generates OpenSearch DSL Queries from user specified parameters, and submits them to a database to retrieve logs. The flags below provide specifications for your Query, and can be placed in any order. \n
203203
\033[1m NOTES: *All default values for username, password, host, and port, are derived from the ENV variables OSUSER, OSPASSWD, OSHOST, OSPORT in that respective order. '\033[0m' \n
204204
\033[1m If you have default connections set in your environment variables, you can call this program without arguments and get the latest 10 logs from the target API in the default CSV format. \033[0m \n
205205
Getlogs has a default set of fields that runs with every query (seen below). You can replace the default fields with your own space-separated set of fields using --fields. Ex: --fields kube.labels.sas_com/deployment properties.appname \n
206-
*The NAMESPACE*, POD*, CONTAINER*, LOGSOURCE* and LEVEL* options accept multiple, space-separated, values (e.g. --level INFO NONE). Please refrain from passing single quotes ('') into arguments. \n
207-
*All Generated files are placed in the current working directory.
206+
*The NAMESPACE*, POD*, CONTAINER*, LOGSOURCE* and LEVEL* options accept multiple, space-separated, values (e.g. --level INFO NONE). Please refrain from passing single quotes ('') into arguments. \n
207+
*All Generated files are placed in the current working directory.
208208
Don't include https:\\ in the HOST connection setting \n\n\n \t\t\t\t QUERY SEARCH PARAMETERS: \n\n""", formatter_class=argparse.RawTextHelpFormatter)
209-
209+
210210
##Search Params
211211
parser.add_argument('-n', '--namespace', required=False, dest="kube.namespace", nargs='*', metavar="NAMESPACE", help="\nOne or more Viya deployments/Kubernetes Namespace for which logs are sought\n\n")
212212
parser.add_argument('-nx', '--namespace-exclude', required=False, dest="kube.namespace-ex", nargs='*', metavar="NAMESPACE", help='\nOne or more namespaces for which logs should be excluded from the output\n\n')
@@ -231,7 +231,7 @@ def get_arguments():
231231
parser.add_argument('-fi','--fields', required=False, dest="fields", nargs="*", metavar= "FIELDS", default=['@timestamp', 'level', 'kube.pod', 'message'], help = "\n Specify desired output columns from query. If a matching log is returned that does not have the specified field, a NULL value will be used as a placeholder. The _id field is always provided for every log message. \n Default fields: @timestamp level kube.pod message _id\n\n")
232232
parser.add_argument('-st', '--start', required=False, dest="dateTimeStart", nargs='*', metavar="DATETIME", default = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.mktime(time.localtime()) - 3600)), help = "\nDatetime for start of period for which logs are sought (default: 1 hour ago). Correct format is Y-M-D H:M:S. Ex: 2023-02-16 10:00:00\n\n")
233233
parser.add_argument('-en', '--end', required=False, dest="dateTimeEnd",nargs='*', metavar="DATETIME", default = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), help = "\nDatetime for end of period for which logs are sought (default: now). \n\n\n \t\t\t CONNECTION SETTINGS: \n\n")
234-
234+
235235
parser.add_argument('-i', '--index', required=False, dest="index", metavar="INDEX", default="viya_logs-*") ## help = "\nDetermine which index to perform the search in. Default: viya-logs-*\n\n
236236
##Connection settings
237237
parser.add_argument('-pf','--port-forward', required=False, dest="portforward", action = 'store_true', help = "\n If this option is provided, getlogs will use the value in your KUBECONFIG (case-sensitive) environment variable to port-forward and connect to the OpenSearch API. This skips OSHOST and OSPORT, but OSUSER and OSPASSWD are stil required to authenticate and connect to the database. \n\n")
@@ -240,21 +240,45 @@ def get_arguments():
240240
parser.add_argument('-ho', '--host', required=False, dest="host", default=os.environ.get("OSHOST"), help = "\nHostname for connection to OpenSearch Please ensure that host does not contain 'https://' (default: $OSHOST)\n\n")
241241
parser.add_argument('-po', '--port', required=False, dest="port", default=os.environ.get("OSPORT"), help = "\nPort number for connection to OpenSearch (default: $OSPORT)\n\n")
242242
parser.add_argument('-nossl', '--disable-ssl', required=False, dest = "ssl", action= "store_false", help = "\n If this option is provided, SSL will not be used to connect to the database.\n\n")
243+
244+
# Add arguments for path-based ingress configuration
245+
parser.add_argument('--path-based', required=False, dest="path_based", nargs='?',
246+
const="opensearch", default=None,
247+
help="Specify if path-based ingress is used. Without a value, defaults to 'opensearch' prefix. "
248+
"You can also specify a custom prefix, e.g. --path-based my-custom-prefix")
249+
243250
return parser.parse_args().__dict__
244251

245252
def main():
246-
253+
url_prefix = None
247254
args = get_arguments() ##Creates "args" dictionary that contains all user submitted options. Print "args" to debug values. Note that the 'dest' value for each argument in argparser object is its key.
248255
validate_input(args) ##Pass args dictionary for input validation
249256

250257
if args['portforward']: ##Modify connection settings if port forward is selected
251258
args['host'] = 'localhost'
252259
args['port'] = open_port()
253260

261+
if args['path_based']:
262+
# User specified path-based ingress - either default or custom
263+
url_prefix = args['path_based'] # Will be "opensearch" or custom value
264+
254265
# Establish Client Using User Authorization and Connection Settings
266+
267+
# Create hosts configuration
268+
host_config = {
269+
'host': args['host'],
270+
'port': args['port']
271+
}
272+
if url_prefix:
273+
host_config['url_prefix'] = url_prefix
274+
print(f"Using path-based ingress with prefix: {url_prefix}")
275+
else:
276+
print("Using host-based ingress (no URL prefix)")
277+
255278
auth = (args['userName'], args['password'])
256279
client = OpenSearch(
257-
hosts = [{'host': args['host'], 'port': args['port']}],
280+
# hosts = [{'host': args['host'], 'url_prefix': 'opensearch','port': args['port']}],
281+
hosts = [host_config],
258282
http_compress = True, # enables gzip compression for request bodies
259283
http_auth = auth,
260284
# client_cert = client_cert_path,
@@ -270,7 +294,7 @@ def main():
270294
x = build_query(args)
271295
index_name = args['index']
272296

273-
if (args['showquery'] == True): ##Print Query if user asks.
297+
if (args['showquery'] == True): ##Print Query if user asks.
274298
print("The following query will be submitted:\n\n", json.dumps(json.loads(x), indent=2))
275299

276300
if(args['savequery']): ##Save Query if user asks.
@@ -284,11 +308,11 @@ def main():
284308
print("Error: Saved query must be written to current working directory.")
285309
sys.exit()
286310
print("\nQuery saved to " + args['savequery'])
287-
311+
288312
print('\nSearching index: ')
289313
try:
290314
response = client.search(body=x, index=index_name)
291-
except Exception as e:
315+
except Exception as e:
292316
print(e)
293317
if ("getaddrinfo" in str(e)):
294318
print("Connection Failed. Please verify the host and port values. ")
@@ -298,7 +322,7 @@ def main():
298322
print("Username:", args['userName'], " Password:", args['password'], " Host:", args['host'], " Port:", args['port'])
299323
else:
300324
print("Connection error. Please verify connection values. ")
301-
print("Username:", args['userName'], " Password:", args['password'], " Host:", args['host'], " Port:", args['port'])
325+
print("Username:", args['userName'], " Password:", args['password'], " Host:", args['host'], " Port:", args['port'])
302326
sys.exit()
303327

304328
if response['hits']['total']['value'] == 0:
@@ -352,7 +376,7 @@ def main():
352376
writer.writeheader()
353377
for fieldDict in hitsList:
354378
writer.writerow(fieldDict)
355-
print("Search complete. Results printed to " + args['out-filename'])
379+
print("Search complete. Results printed to " + args['out-filename'])
356380
else:
357381
print("Search complete")
358382
with sys.stdout as csvfile:
@@ -365,4 +389,3 @@ def main():
365389

366390
if __name__ == "__main__":
367391
main()
368-

0 commit comments

Comments
 (0)