Merge lp:~bcsaller/pyjuju/relation-get-shell into lp:pyjuju

Proposed by Benjamin Saller
Status: Merged
Merged at revision: 303
Proposed branch: lp:~bcsaller/pyjuju/relation-get-shell
Merge into: lp:pyjuju
Diff against target: 148 lines (+101/-1)
2 files modified
ensemble/hooks/commands.py (+38/-1)
ensemble/hooks/tests/test_invoker.py (+63/-0)
To merge this branch: bzr merge lp:~bcsaller/pyjuju/relation-get-shell
Reviewer Review Type Date Requested Status
William Reade (community) Approve
Gustavo Niemeyer Approve
Review via email: mp+60334@code.launchpad.net

Description of the change

Implements relation-get --format=shell
adds --shell-prefix option to relation-get

To post a comment you must log in.
Revision history for this message
Benjamin Saller (bcsaller) wrote :

This version implicitly sets VAR_xxx for shell output as was mentioned around UDS

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

[1]

+ print >>stream, 'VAR_%s="%s"' % (k.upper(), v)

What if there are quotes and special characters in the value, and
what if the name contains '-', '.', etc?

[2]

Would be good to verify the environment and blow up if there are any
variables starting with "VAR_". This way we'll know hooks will never
pick up variables from somewhere else and consider them as part of
the relation.

review: Needs Fixing
Revision history for this message
Benjamin Saller (bcsaller) wrote :

> [1]
>
> + print >>stream, 'VAR_%s="%s"' % (k.upper(), v)
>
> What if there are quotes and special characters in the value, and
> what if the name contains '-', '.', etc?

Bad chars are mapped to '_' now.

>
> [2]
>
> Would be good to verify the environment and blow up if there are any
> variables starting with "VAR_". This way we'll know hooks will never
> pick up variables from somewhere else and consider them as part of
> the relation.

It now un-sets such variables and logs this to stderr (which appears in the hook log).

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

[1]

> Bad chars are mapped to '_' now.

What about values?

review: Needs Fixing
Revision history for this message
Benjamin Saller (bcsaller) wrote :

> [1]
>
> > Bad chars are mapped to '_' now.
>
> What about values?

Fair enough, this uses Python's pipe.quote to quote the shell values as well now

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

[3]

43 + for k, v in sorted(result.items()):
44 + if k.startswith("VAR_"):

We just debated live. +1 as long as this is changed to nuke variables
from the _environment_, not from the relation.

review: Approve
234. By Benjamin Saller

review changes, filter env, not relation results

Revision history for this message
William Reade (fwereade) wrote :

Doesn't quite match the bug; it might be good to document on the bug that we have decided against adding --shell-prefix, and we just use VAR_. Otherwise LGTM, +1.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ensemble/hooks/commands.py'
2--- ensemble/hooks/commands.py 2011-06-02 19:29:20 +0000
3+++ ensemble/hooks/commands.py 2011-08-09 18:46:23 +0000
4@@ -1,6 +1,8 @@
5+import logging
6 import os
7+import pipes
8+import re
9 import sys
10-import logging
11
12 from twisted.internet.defer import inlineCallbacks, returnValue
13
14@@ -8,11 +10,15 @@
15 CommandLineClient, parse_log_level, parse_port_protocol)
16
17
18+BAD_CHARS = re.compile("[\-\./:()<>|?*]|(\\\)")
19+
20+
21 class RelationGetCli(CommandLineClient):
22 keyvalue_pairs = False
23
24 def customize_parser(self):
25 remote_unit = os.environ.get("ENSEMBLE_REMOTE_UNIT")
26+
27 self.parser.add_argument("settings_name", default="", nargs="?")
28 self.parser.add_argument("unit_name", default=remote_unit, nargs="?")
29
30@@ -26,6 +32,37 @@
31 self.options.settings_name)
32 returnValue(result)
33
34+ def format_shell(self, result, stream):
35+ options = self.options
36+ settings_name = options.settings_name
37+
38+ if settings_name and settings_name != "-":
39+ # result should be a single value
40+ result = {settings_name.upper(): result}
41+
42+ if result:
43+ errs = []
44+ for k, v in sorted(os.environ.items()):
45+ if k.startswith("VAR_"):
46+ print >>stream, "%s=" % (k.upper(), )
47+ errs.append(k)
48+
49+ for k, v in sorted(result.items()):
50+ k = BAD_CHARS.sub("_", k.upper())
51+ v = pipes.quote(v)
52+ print >>stream, 'VAR_%s=%s' % (k.upper(), v)
53+
54+ # order of output within streams is assured but we
55+ # output on (commonly) two streams here and the
56+ # ordering of those messages are significant to the user
57+ stream.flush()
58+
59+ if errs:
60+ print >>sys.stderr, "The following were omitted from " \
61+ "the environment. VAR_ prefixed variables indicate a " \
62+ "usage error."
63+ print >> sys.stderr, "".join(errs)
64+
65
66 def relation_get():
67 """Entry point for relation-get"""
68
69=== modified file 'ensemble/hooks/tests/test_invoker.py'
70--- ensemble/hooks/tests/test_invoker.py 2011-07-29 18:14:21 +0000
71+++ ensemble/hooks/tests/test_invoker.py 2011-08-09 18:46:23 +0000
72@@ -289,6 +289,68 @@
73 self.assertEqual(data["forgotten"], "lyrics")
74
75 @defer.inlineCallbacks
76+ def test_spawn_cli_get_format_shell(self):
77+ """Validate the get hook can transmit values to the hook."""
78+ yield self.build_default_relationships()
79+
80+ hook_log = self.capture_logging("hook")
81+
82+ # Populate and verify some data we will
83+ # later extract with the hook
84+ expected = {"name": "rabbit",
85+ "forgotten": "lyrics",}
86+ yield self.wordpress_relation.set_data(expected)
87+
88+ exe = self.ua.get_invoker("db", "add", "mysql/0", self.mysql_relation,
89+ client_id="client_id")
90+ exe.environment["ENSEMBLE_REMOTE_UNIT"] = "wordpress/0"
91+
92+ # invoke relation-get and verify the result
93+ result = yield exe(self.create_hook("relation-get", "--format=shell -"))
94+ self.assertEqual(result, 0)
95+ data = hook_log.getvalue()
96+
97+ self.assertEqual('VAR_FORGOTTEN=lyrics\nVAR_NAME=rabbit\n\n', data)
98+
99+ # and with a single value request
100+ hook_log.truncate(0)
101+ result = yield exe(self.create_hook("relation-get", "--format=shell name"))
102+ self.assertEqual(result, 0)
103+ data = hook_log.getvalue()
104+ self.assertEqual('VAR_NAME=rabbit\n\n', data)
105+
106+ @defer.inlineCallbacks
107+ def test_relation_get_format_shell_bad_vars(self):
108+ """If illegal values are make somehow available warn."""
109+ yield self.build_default_relationships()
110+ hook_log = self.capture_logging("hook")
111+
112+ # Populate and verify some data we will
113+ # later extract with the hook
114+ expected = {"BAR": "none", "funny-chars*": "should work"}
115+ yield self.wordpress_relation.set_data(expected)
116+
117+ exe = self.ua.get_invoker("db", "add", "mysql/0", self.mysql_relation,
118+ client_id="client_id")
119+ exe.environment["ENSEMBLE_REMOTE_UNIT"] = "wordpress/0"
120+ exe.environment["VAR_FOO"] = "jungle"
121+
122+ result = yield exe(self.create_hook("relation-get", "--format=shell -"))
123+ self.assertEqual(result, 0)
124+
125+ data = hook_log.getvalue()
126+ self.assertIn('VAR_BAR=none', data)
127+ # Verify that illegal shell variable names get converted
128+ # in an expected way
129+ self.assertIn("VAR_FUNNY_CHARS_='should work'", data)
130+
131+ # Verify that it sets VAR_FOO to null because it shouldn't
132+ # exist in the environment
133+ self.assertIn("VAR_FOO=", data)
134+ self.assertIn("The following were omitted from", data)
135+ self.assertEqual(data.strip().split("\n")[-1], "VAR_FOO")
136+
137+ @defer.inlineCallbacks
138 def test_hook_exec_in_formula_directory(self):
139 """Hooks are executed in the formula directory."""
140 yield self.build_default_relationships()
141@@ -317,6 +379,7 @@
142 self.assertEqual(hook_log.getvalue().strip(),
143 os.path.join(exe.unit_path, "formula"))
144
145+
146 @defer.inlineCallbacks
147 def test_spawn_cli_config_get(self):
148 """Validate that config-get returns expected values."""

Subscribers

People subscribed via source and target branches

to status/vote changes: