Skip to content

Commit e44aa98

Browse files
RemoteOperations::exec_command explicitly transfers LANG, LANGUAGE and LC_* envvars to the server side (#187)
* RemoteOperations::exec_command updated - Exact enumeration of supported 'cmd' types - Refactoring * RemoteOperations::exec_command explicitly transfers LANG, LANGUAGE and LC_* envvars to the server side It should help resolve a problem with replacing a LANG variable by ssh-server. History. On our internal tests we got a problem on the Debian 11 and PostgresPro STD-13. One test returned the error from initdb: initdb: error: collations with different collate and ctype values ("en_US.UTF-8" and "C.UTF-8" accordingly) are not supported by ICU - TestRunner set variable LANG="C" - Python set variable LC_CTYPE="C.UTF-8" - Test call inidb through command "ssh test@localhost inidb -D ...." - SSH-server replaces LANG with value "en_US.UTF-8" (from etc/default/locale) - initdb calculate collate through this value of LANG variable and get en_US.UTF-8 So we have that: - ctype is C.UTF-8 - collate is en_US.UTF-8 ICU on the Debuan-11 (uconv v2.1 ICU 67.1) does not suppot this combination and inidb rturns the error. This patch generates a new command line for ssh: ssh test@localhost "LANG=\"...\";LC_xxx=\"...\";<command>" It resolves this problem with initdb and should help resolve other problems with execution of command through SSH. Amen. * New tests in TestgresRemoteTests are added New tests: - test_init__LANG_С - test_init__unk_LANG_and_LC_CTYPE * TestgresRemoteTests.test_init__unk_LANG_and_LC_CTYPE is updated Let's test bad data with '\' and '"' symbols. * Static methods are marked with @staticmethod [thanks to Victoria Shepard] The following methods of RemoteOperations were corrected: - _make_exec_env_list - _does_put_envvar_into_exec_cmd - _quote_envvar * TestRemoteOperations::_quote_envvar is updated (typification)
1 parent 7fd2f07 commit e44aa98

File tree

2 files changed

+124
-1
lines changed

2 files changed

+124
-1
lines changed

testgres/operations/remote_ops.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@ def exec_command(self, cmd, wait_exit=False, verbose=False, expect_error=False,
8787

8888
assert type(cmd_s) == str # noqa: E721
8989

90-
ssh_cmd = ['ssh', self.ssh_dest] + self.ssh_args + [cmd_s]
90+
cmd_items = __class__._make_exec_env_list()
91+
cmd_items.append(cmd_s)
92+
93+
env_cmd_s = ';'.join(cmd_items)
94+
95+
ssh_cmd = ['ssh', self.ssh_dest] + self.ssh_args + [env_cmd_s]
9196

9297
process = subprocess.Popen(ssh_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
9398
assert not (process is None)
@@ -510,6 +515,45 @@ def db_connect(self, dbname, user, password=None, host="localhost", port=5432):
510515
)
511516
return conn
512517

518+
@staticmethod
519+
def _make_exec_env_list() -> list[str]:
520+
result = list[str]()
521+
for envvar in os.environ.items():
522+
if not __class__._does_put_envvar_into_exec_cmd(envvar[0]):
523+
continue
524+
qvalue = __class__._quote_envvar(envvar[1])
525+
assert type(qvalue) == str # noqa: E721
526+
result.append(envvar[0] + "=" + qvalue)
527+
continue
528+
529+
return result
530+
531+
sm_envs_for_exec_cmd = ["LANG", "LANGUAGE"]
532+
533+
@staticmethod
534+
def _does_put_envvar_into_exec_cmd(name: str) -> bool:
535+
assert type(name) == str # noqa: E721
536+
name = name.upper()
537+
if name.startswith("LC_"):
538+
return True
539+
if name in __class__.sm_envs_for_exec_cmd:
540+
return True
541+
return False
542+
543+
@staticmethod
544+
def _quote_envvar(value: str) -> str:
545+
assert type(value) == str # noqa: E721
546+
result = "\""
547+
for ch in value:
548+
if ch == "\"":
549+
result += "\\\""
550+
elif ch == "\\":
551+
result += "\\\\"
552+
else:
553+
result += ch
554+
result += "\""
555+
return result
556+
513557

514558
def normalize_error(error):
515559
if isinstance(error, bytes):

tests/test_simple_remote.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,79 @@ def test_custom_init(self):
119119
# there should be no trust entries at all
120120
self.assertFalse(any('trust' in s for s in lines))
121121

122+
def test_init__LANG_С(self):
123+
# PBCKP-1744
124+
prev_LANG = os.environ.get("LANG")
125+
126+
try:
127+
os.environ["LANG"] = "C"
128+
129+
with get_remote_node(conn_params=conn_params) as node:
130+
node.init().start()
131+
finally:
132+
__class__.helper__restore_envvar("LANG", prev_LANG)
133+
134+
def test_init__unk_LANG_and_LC_CTYPE(self):
135+
# PBCKP-1744
136+
prev_LANG = os.environ.get("LANG")
137+
prev_LANGUAGE = os.environ.get("LANGUAGE")
138+
prev_LC_CTYPE = os.environ.get("LC_CTYPE")
139+
prev_LC_COLLATE = os.environ.get("LC_COLLATE")
140+
141+
try:
142+
# TODO: Pass unkData through test parameter.
143+
unkDatas = [
144+
("UNKNOWN_LANG", "UNKNOWN_CTYPE"),
145+
("\"UNKNOWN_LANG\"", "\"UNKNOWN_CTYPE\""),
146+
("\\UNKNOWN_LANG\\", "\\UNKNOWN_CTYPE\\"),
147+
("\"UNKNOWN_LANG", "UNKNOWN_CTYPE\""),
148+
("\\UNKNOWN_LANG", "UNKNOWN_CTYPE\\"),
149+
("\\", "\\"),
150+
("\"", "\""),
151+
]
152+
153+
for unkData in unkDatas:
154+
logging.info("----------------------")
155+
logging.info("Unk LANG is [{0}]".format(unkData[0]))
156+
logging.info("Unk LC_CTYPE is [{0}]".format(unkData[1]))
157+
158+
os.environ["LANG"] = unkData[0]
159+
os.environ.pop("LANGUAGE", None)
160+
os.environ["LC_CTYPE"] = unkData[1]
161+
os.environ.pop("LC_COLLATE", None)
162+
163+
assert os.environ.get("LANG") == unkData[0]
164+
assert not ("LANGUAGE" in os.environ.keys())
165+
assert os.environ.get("LC_CTYPE") == unkData[1]
166+
assert not ("LC_COLLATE" in os.environ.keys())
167+
168+
while True:
169+
try:
170+
with get_remote_node(conn_params=conn_params):
171+
pass
172+
except testgres.exceptions.ExecUtilException as e:
173+
#
174+
# Example of an error message:
175+
#
176+
# warning: setlocale: LC_CTYPE: cannot change locale (UNKNOWN_CTYPE): No such file or directory
177+
# postgres (PostgreSQL) 14.12
178+
#
179+
errMsg = str(e)
180+
181+
logging.info("Error message is: {0}".format(errMsg))
182+
183+
assert "LC_CTYPE" in errMsg
184+
assert unkData[1] in errMsg
185+
assert "warning: setlocale: LC_CTYPE: cannot change locale (" + unkData[1] + "): No such file or directory" in errMsg
186+
assert "postgres" in errMsg
187+
break
188+
raise Exception("We expected an error!")
189+
finally:
190+
__class__.helper__restore_envvar("LANG", prev_LANG)
191+
__class__.helper__restore_envvar("LANGUAGE", prev_LANGUAGE)
192+
__class__.helper__restore_envvar("LC_CTYPE", prev_LC_CTYPE)
193+
__class__.helper__restore_envvar("LC_COLLATE", prev_LC_COLLATE)
194+
122195
def test_double_init(self):
123196
with get_remote_node(conn_params=conn_params).init() as node:
124197
# can't initialize node more than once
@@ -994,6 +1067,12 @@ def test_child_process_dies(self):
9941067
# try to handle children list -- missing processes will have ptype "ProcessType.Unknown"
9951068
[ProcessProxy(p) for p in children]
9961069

1070+
def helper__restore_envvar(name, prev_value):
1071+
if prev_value is None:
1072+
os.environ.pop(name, None)
1073+
else:
1074+
os.environ[name] = prev_value
1075+
9971076

9981077
if __name__ == '__main__':
9991078
if os_ops.environ('ALT_CONFIG'):

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy