Skip to content

Commit 6ad20ec

Browse files
author
Vladimir Kotal
authored
add option to insert XML snippet into web.xml (#2881)
fixes #2877
1 parent 9ce281f commit 6ad20ec

File tree

7 files changed

+297
-33
lines changed

7 files changed

+297
-33
lines changed

opengrok-tools/src/main/python/opengrok_tools/deploy.py

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# CDDL HEADER END
1919

2020
#
21-
# Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
21+
# Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
2222
# Portions Copyright (c) 2017-2018, Chris Fraire <[email protected]>.
2323
#
2424

@@ -31,73 +31,95 @@
3131
from .utils.log import get_console_logger, get_class_basename, \
3232
fatal
3333
from .utils.parsers import get_baseparser
34+
from .utils.xml import insert_file, XMLProcessingException
35+
36+
WEB_XML = 'WEB-INF/web.xml'
37+
DEFAULT_CONFIG_FILE = '/var/opengrok/etc/configuration.xml'
3438

3539
"""
3640
deploy war file
3741
3842
"""
3943

4044

41-
def repack_war(logger, sourceWar, targetWar, configFile, defaultConfigFile):
45+
def repack_war(logger, source_war, target_war, default_config_file,
46+
config_file=None, insert_path=None):
4247
"""
43-
Repack sourceWar into targetWar, performing substitution of configFile
44-
in the process.
48+
Repack source_war into target_war, performing substitution of config_file
49+
and/or inserting XML snippet to the 'web.xml' file in the process.
50+
51+
:param logger: logger object
52+
:param source_war: path to the original WAR file
53+
:param target_war: path to the destination WAR file
54+
:param default_config_file: path to default configuration file
55+
:param config_file: path to new configuration file
56+
:param insert_path: path to XML file to insert
4557
"""
4658

47-
WEB_XML = 'WEB-INF/web.xml'
48-
49-
with ZipFile(sourceWar, 'r') as infile, ZipFile(targetWar, 'w') as outfile:
59+
with ZipFile(source_war, 'r') as infile, \
60+
ZipFile(target_war, 'w') as outfile:
5061
for item in infile.infolist():
5162
data = infile.read(item.filename)
63+
5264
if item.filename == WEB_XML:
53-
logger.debug("Performing substitution of '{}' with '{}'".
54-
format(defaultConfigFile, configFile))
55-
defaultConfigFile = defaultConfigFile.encode()
56-
configFile = configFile.encode()
57-
data = data.replace(defaultConfigFile, configFile)
65+
if config_file:
66+
logger.debug("Performing substitution of '{}' with '{}'".
67+
format(default_config_file, config_file))
68+
default_config_file = default_config_file.encode()
69+
config_file = config_file.encode()
70+
data = data.replace(default_config_file, config_file)
71+
72+
if insert_path:
73+
logger.debug("Inserting contents of file '{}'".
74+
format(insert_path))
75+
data = insert_file(data, insert_path)
76+
5877
outfile.writestr(item, data)
5978

6079

61-
def deploy_war(logger, sourceWar, targetWar, configFile=None):
80+
def deploy_war(logger, source_war, target_war, config_file=None,
81+
insert_path=None):
6282
"""
6383
Copy warSource to warTarget (checking existence of both), optionally
6484
repacking the warTarget archive if configuration file resides in
6585
non-default location.
6686
"""
6787

68-
if not os.path.isfile(sourceWar):
69-
logger.error("{} is not a file".format(sourceWar))
88+
if not os.path.isfile(source_war):
89+
logger.error("{} is not a file".format(source_war))
7090

71-
if os.path.isdir(targetWar):
72-
orig = targetWar
73-
targetWar = os.path.join(targetWar, os.path.basename(sourceWar))
91+
if os.path.isdir(target_war):
92+
orig = target_war
93+
target_war = os.path.join(target_war, os.path.basename(source_war))
7494
logger.debug("Target {} is directory, will use {}".
75-
format(orig, targetWar))
95+
format(orig, target_war))
7696

7797
# If user does not use default configuration file location then attempt to
7898
# extract WEB-INF/web.xml from the war file using jar or zip utility,
7999
# update the hardcoded values and then update source.war with the new
80100
# WEB-INF/web.xml.
81-
tmpWar = None
82-
DEFAULT_CONFIG_FILE = '/var/opengrok/etc/configuration.xml'
83-
if configFile and configFile != DEFAULT_CONFIG_FILE:
101+
tmp_war = None
102+
103+
if (config_file and config_file != DEFAULT_CONFIG_FILE) or insert_path:
104+
84105
# Resolve the path to be absolute so that webapp can find the file.
85-
configFile = os.path.abspath(configFile)
106+
if config_file:
107+
config_file = os.path.abspath(config_file)
86108

87109
with tempfile.NamedTemporaryFile(prefix='OpenGroktmpWar',
88110
suffix='.war',
89-
delete=False) as tmpWar:
111+
delete=False) as tmp_war:
90112
logger.info('Repacking {} with custom configuration path to {}'.
91-
format(sourceWar, tmpWar.name))
92-
repack_war(logger, sourceWar, tmpWar.name, configFile,
93-
DEFAULT_CONFIG_FILE)
94-
sourceWar = tmpWar.name
113+
format(source_war, tmp_war.name))
114+
repack_war(logger, source_war, tmp_war.name, DEFAULT_CONFIG_FILE,
115+
config_file, insert_path)
116+
source_war = tmp_war.name
95117

96-
logger.info("Installing {} to {}".format(sourceWar, targetWar))
97-
copyfile(sourceWar, targetWar)
118+
logger.info("Installing {} to {}".format(source_war, target_war))
119+
copyfile(source_war, target_war)
98120

99-
if tmpWar:
100-
os.remove(tmpWar.name)
121+
if tmp_war:
122+
os.remove(tmp_war.name)
101123

102124

103125
def main():
@@ -106,6 +128,9 @@ def main():
106128

107129
parser.add_argument('-c', '--config',
108130
help='Path to OpenGrok configuration file')
131+
parser.add_argument('-i', '--insert',
132+
help='Path to XML file to insert into the {} file'.
133+
format(WEB_XML))
109134
parser.add_argument('source_war', nargs=1,
110135
help='Path to war file to deploy')
111136
parser.add_argument('target_war', nargs=1,
@@ -118,7 +143,14 @@ def main():
118143

119144
logger = get_console_logger(get_class_basename(), args.loglevel)
120145

121-
deploy_war(logger, args.source_war[0], args.target_war[0], args.config)
146+
if args.insert and not os.path.isfile(args.insert):
147+
fatal("File '{}' does not exist".format(args.insert))
148+
149+
try:
150+
deploy_war(logger, args.source_war[0], args.target_war[0], args.config,
151+
args.insert)
152+
except XMLProcessingException as e:
153+
fatal(e)
122154

123155
print("Start your application server (if it is not already running) "
124156
"or wait until it loads the just installed web application.\n"
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env python3
2+
3+
# CDDL HEADER START
4+
#
5+
# The contents of this file are subject to the terms of the
6+
# Common Development and Distribution License (the "License").
7+
# You may not use this file except in compliance with the License.
8+
#
9+
# See LICENSE.txt included in this distribution for the specific
10+
# language governing permissions and limitations under the License.
11+
#
12+
# When distributing Covered Code, include this CDDL HEADER in each
13+
# file and include the License file at LICENSE.txt.
14+
# If applicable, add the following below this CDDL HEADER, with the
15+
# fields enclosed by brackets "[]" replaced with your own identifying
16+
# information: Portions Copyright [yyyy] [name of copyright owner]
17+
#
18+
# CDDL HEADER END
19+
20+
#
21+
# Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
22+
#
23+
24+
import xml.etree.ElementTree as ET
25+
26+
27+
class XMLProcessingException(Exception):
28+
pass
29+
30+
31+
def insert_file(input_xml, insert_xml_file):
32+
"""
33+
inserts sub-root elements of XML file under root of input XML
34+
:param input_xml: input XML string
35+
:param insert_xml_file: path to file to insert
36+
:return: string with resulting XML
37+
"""
38+
39+
# This avoids resulting XML to have namespace prefixes in elements.
40+
ET.register_namespace('', "http://xmlns.jcp.org/xml/ns/javaee")
41+
42+
root = ET.fromstring(input_xml)
43+
try:
44+
insert_tree = ET.parse(insert_xml_file)
45+
except ET.ParseError as e:
46+
raise XMLProcessingException("Cannot parse file '{}' as XML".
47+
format(insert_xml_file)) from e
48+
except (PermissionError, IOError) as e:
49+
raise XMLProcessingException("Cannot read file '{}'".
50+
format(insert_xml_file)) from e
51+
52+
insert_root = insert_tree.getroot()
53+
54+
for elem in list(insert_root.findall('.')):
55+
root.extend(list(elem))
56+
57+
return ET.tostring(root, encoding="utf8", method='xml').decode("utf-8")
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<web-app>
3+
<security-constraint>
4+
<web-resource-collection>
5+
<web-resource-name>API endpoints are checked separately by the web app</web-resource-name>
6+
<url-pattern>/api/*</url-pattern>
7+
</web-resource-collection>
8+
</security-constraint>
9+
10+
<security-constraint>
11+
<web-resource-collection>
12+
<web-resource-name>In general everything needs to be authenticated</web-resource-name>
13+
<url-pattern>/api/v1/search</url-pattern> <!-- protect search endpoint whitelisted above -->
14+
<url-pattern>/api/v1/suggest/*</url-pattern> <!-- protect suggest endpoint whitelisted above -->
15+
<url-pattern>/*</url-pattern> <!-- protect the whole application -->
16+
</web-resource-collection>
17+
18+
<auth-constraint>
19+
<role-name>*</role-name>
20+
</auth-constraint>
21+
22+
<user-data-constraint>
23+
<transport-guarantee>NONE</transport-guarantee>
24+
</user-data-constraint>
25+
</security-constraint>
26+
27+
<security-role>
28+
<role-name>*</role-name>
29+
</security-role>
30+
31+
<login-config>
32+
<auth-method>BASIC</auth-method>
33+
</login-config>
34+
35+
</web-app>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<web-app>
3+
<security-constraint>
4+
<web-resource-collection>
5+
<web-resource-name>API endpoints are checked separately by the web app</web-resource-name>
6+
<url-pattern>/api/*</url-pattern>
7+
</web-resource-collection>
8+
</security-constraint>
9+
10+
<security-constraint>
11+
<web-resource-collection>
12+
<web-resource-name>In general everything needs to be authenticated</web-resource-name>
13+
<url-pattern>/*</url-pattern> <!-- protect the whole application -->
14+
</web-resource-collection>
15+
16+
<auth-constraint>
17+
<role-name>*</role-name>
18+
</auth-constraint>
19+
20+
<user-data-constraint>
21+
<transport-guarantee>NONE</transport-guarantee>
22+
</user-data-constraint>
23+
</security-constraint>
24+
25+
<security-role>
26+
<role-name>*</role-name>
27+
</security-role>
28+
29+
<login-config>
30+
<auth-method>BASIC</auth-method>
31+
32+
</web-app>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version='1.0' encoding='utf8'?>
2+
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
3+
4+
<display-name>OpenGrok</display-name>
5+
<description>A wicked fast source browser</description>
6+
<context-param>
7+
<description>Full path to the configuration file where OpenGrok can read its configuration</description>
8+
<param-name>CONFIGURATION</param-name>
9+
<param-value>/var/opengrok/etc/configuration.xml</param-value>
10+
</context-param>
11+
12+
13+
<security-constraint>
14+
<web-resource-collection>
15+
<web-resource-name>API endpoints are checked separately by the web app</web-resource-name>
16+
<url-pattern>/api/*</url-pattern>
17+
</web-resource-collection>
18+
</security-constraint>
19+
20+
<security-constraint>
21+
<web-resource-collection>
22+
<web-resource-name>In general everything needs to be authenticated</web-resource-name>
23+
<url-pattern>/api/v1/search</url-pattern>
24+
<url-pattern>/api/v1/suggest/*</url-pattern>
25+
<url-pattern>/*</url-pattern>
26+
</web-resource-collection>
27+
28+
<auth-constraint>
29+
<role-name>*</role-name>
30+
</auth-constraint>
31+
32+
<user-data-constraint>
33+
<transport-guarantee>NONE</transport-guarantee>
34+
</user-data-constraint>
35+
</security-constraint>
36+
37+
<security-role>
38+
<role-name>*</role-name>
39+
</security-role>
40+
41+
<login-config>
42+
<auth-method>BASIC</auth-method>
43+
</login-config>
44+
45+
</web-app>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
3+
#
4+
# CDDL HEADER START
5+
#
6+
# The contents of this file are subject to the terms of the
7+
# Common Development and Distribution License (the "License").
8+
# You may not use this file except in compliance with the License.
9+
#
10+
# See LICENSE.txt included in this distribution for the specific
11+
# language governing permissions and limitations under the License.
12+
#
13+
# When distributing Covered Code, include this CDDL HEADER in each
14+
# file and include the License file at LICENSE.txt.
15+
# If applicable, add the following below this CDDL HEADER, with the
16+
# fields enclosed by brackets "[]" replaced with your own identifying
17+
# information: Portions Copyright [yyyy] [name of copyright owner]
18+
#
19+
# CDDL HEADER END
20+
#
21+
22+
#
23+
# Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
24+
#
25+
26+
import os
27+
import pytest
28+
29+
from opengrok_tools.utils.xml import insert_file, XMLProcessingException
30+
31+
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
32+
33+
34+
def test_xml_insert():
35+
with open(os.path.join(DIR_PATH, "web.xml")) as base_xml:
36+
out = insert_file(base_xml.read(),
37+
os.path.join(DIR_PATH, "insert.xml"))
38+
with open(os.path.join(DIR_PATH, "new.xml")) as expected_xml:
39+
assert out == expected_xml.read()
40+
41+
42+
def test_invalid_xml():
43+
with open(os.path.join(DIR_PATH, "web.xml")) as base_xml:
44+
with pytest.raises(XMLProcessingException):
45+
insert_file(base_xml.read(),
46+
os.path.join(DIR_PATH, "invalid.xml"))

0 commit comments

Comments
 (0)