Merge lp:~free.ekanayaka/charm-helpers/relation-set-file into lp:charm-helpers

Proposed by Free Ekanayaka on 2015-05-29
Status: Merged
Merged at revision: 381
Proposed branch: lp:~free.ekanayaka/charm-helpers/relation-set-file
Merge into: lp:charm-helpers
Diff against target: 155 lines (+91/-10)
2 files modified
charmhelpers/core/hookenv.py (+27/-6)
tests/core/test_hookenv.py (+64/-4)
To merge this branch: bzr merge lp:~free.ekanayaka/charm-helpers/relation-set-file
Reviewer Review Type Date Requested Status
Chris Glass (community) 2015-05-29 Approve on 2015-05-29
Björn Tillenius (community) Approve on 2015-05-29
Review via email: mp+260594@code.launchpad.net

Description of the Change

This branch re-introduces the code that was added in:

https://code.launchpad.net/~bjornt/charm-helpers/relation-set-file/+merge/259090

but backed out in r379 due to the bug linked to this MP.

The solution is to the describe by Bjorn in comment #5 of the bug linked to this branch (Bug #1459175). Essentially we do the same conversion to string as we do with the command-line-args version.

To post a comment you must log in.
Björn Tillenius (bjornt) wrote :

Thanks for readding this feature in a working state! You have one bug in there. I fixed it in lp:~bjornt/charm-helpers/relation-set-file, so you can merge that one and things should be good to go.

review: Approve
382. By Free Ekanayaka on 2015-05-29

Properly handle None

Free Ekanayaka (free.ekanayaka) wrote :

> Thanks for readding this feature in a working state! You have one bug in
> there. I fixed it in lp:~bjornt/charm-helpers/relation-set-file, so you can
> merge that one and things should be good to go.

Good catch. Merged and pushed.

Chris Glass (tribaal) wrote :

This looks good, and should address the linked bug.

Note: this branch incorporates the changes in https://code.launchpad.net/~stub/charm-helpers/bug-1458546-fix-relation-set-py3/+merge/260048

Merging.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/core/hookenv.py'
2--- charmhelpers/core/hookenv.py 2015-05-27 09:46:36 +0000
3+++ charmhelpers/core/hookenv.py 2015-05-29 15:19:31 +0000
4@@ -28,6 +28,7 @@
5 import subprocess
6 import sys
7 import errno
8+import tempfile
9 from subprocess import CalledProcessError
10
11 import six
12@@ -362,14 +363,34 @@
13 """Set relation information for the current unit"""
14 relation_settings = relation_settings if relation_settings else {}
15 relation_cmd_line = ['relation-set']
16+ accepts_file = "--file" in subprocess.check_output(
17+ relation_cmd_line + ["--help"], universal_newlines=True)
18 if relation_id is not None:
19 relation_cmd_line.extend(('-r', relation_id))
20- for k, v in (list(relation_settings.items()) + list(kwargs.items())):
21- if v is None:
22- relation_cmd_line.append('{}='.format(k))
23- else:
24- relation_cmd_line.append('{}={}'.format(k, v))
25- subprocess.check_call(relation_cmd_line)
26+ settings = relation_settings.copy()
27+ settings.update(kwargs)
28+ for key, value in settings.items():
29+ # Force value to be a string: it always should, but some call
30+ # sites pass in things like dicts or numbers.
31+ if value is not None:
32+ settings[key] = "{}".format(value)
33+ if accepts_file:
34+ # --file was introduced in Juju 1.23.2. Use it by default if
35+ # available, since otherwise we'll break if the relation data is
36+ # too big. Ideally we should tell relation-set to read the data from
37+ # stdin, but that feature is broken in 1.23.2: Bug #1454678.
38+ with tempfile.NamedTemporaryFile(delete=False) as settings_file:
39+ settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
40+ subprocess.check_call(
41+ relation_cmd_line + ["--file", settings_file.name])
42+ os.remove(settings_file.name)
43+ else:
44+ for key, value in settings.items():
45+ if value is None:
46+ relation_cmd_line.append('{}='.format(key))
47+ else:
48+ relation_cmd_line.append('{}={}'.format(key, value))
49+ subprocess.check_call(relation_cmd_line)
50 # Flush cache of any relation-gets for local unit
51 flush(local_unit())
52
53
54=== modified file 'tests/core/test_hookenv.py'
55--- tests/core/test_hookenv.py 2015-05-27 09:46:36 +0000
56+++ tests/core/test_hookenv.py 2015-05-29 15:19:31 +0000
57@@ -915,6 +915,7 @@
58 hookenv.relation_get(attribute='baz_scope', unit='baz_unit')
59 hookenv.relation_get(attribute='bar_scope')
60 self.assertTrue(len(hookenv.cache) == 2)
61+ check_output.return_value = ""
62 hookenv.relation_set(baz_scope='hello')
63 # relation_set should flush any entries for local_unit
64 self.assertTrue(len(hookenv.cache) == 1)
65@@ -930,27 +931,86 @@
66 check_output.assert_called_with(['relation-get', '--format=json', '-r',
67 123, 'baz-scope', 'baz-unit'])
68
69+ @patch('subprocess.check_output')
70 @patch('subprocess.check_call')
71- def test_sets_relation_with_kwargs(self, check_call_):
72+ def test_sets_relation_with_kwargs(self, check_call_, check_output):
73 hookenv.relation_set(foo="bar")
74 check_call_.assert_called_with(['relation-set', 'foo=bar'])
75
76+ @patch('subprocess.check_output')
77 @patch('subprocess.check_call')
78- def test_sets_relation_with_dict(self, check_call_):
79+ def test_sets_relation_with_dict(self, check_call_, check_output):
80 hookenv.relation_set(relation_settings={"foo": "bar"})
81 check_call_.assert_called_with(['relation-set', 'foo=bar'])
82
83+ @patch('subprocess.check_output')
84 @patch('subprocess.check_call')
85- def test_sets_relation_with_relation_id(self, check_call_):
86+ def test_sets_relation_with_relation_id(self, check_call_, check_output):
87 hookenv.relation_set(relation_id="foo", bar="baz")
88 check_call_.assert_called_with(['relation-set', '-r', 'foo',
89 'bar=baz'])
90
91+ @patch('subprocess.check_output')
92 @patch('subprocess.check_call')
93- def test_sets_relation_with_missing_value(self, check_call_):
94+ def test_sets_relation_with_missing_value(self, check_call_, check_output):
95 hookenv.relation_set(foo=None)
96 check_call_.assert_called_with(['relation-set', 'foo='])
97
98+ @patch('os.remove')
99+ @patch('subprocess.check_output')
100+ @patch('subprocess.check_call')
101+ def test_relation_set_file(self, check_call, check_output, remove):
102+ """If relation-set accepts a --file parameter, it's used.
103+
104+ Juju 1.23.2 introduced a --file parameter, which means you can
105+ pass the data through a file. Not using --file would make
106+ relation_set break if the relation data is too big.
107+ """
108+ # check_output(["relation-set", "--help"]) is used to determine
109+ # whether we can pass --file to it.
110+ check_output.return_value = "--file"
111+ hookenv.relation_set(foo="bar")
112+ check_output.assert_called_with(
113+ ["relation-set", "--help"], universal_newlines=True)
114+ # relation-set is called with relation-set --file <temp_file>
115+ # with data as YAML and the temp_file is then removed.
116+ self.assertEqual(1, len(check_call.call_args[0]))
117+ command = check_call.call_args[0][0]
118+ self.assertEqual(3, len(command))
119+ self.assertEqual("relation-set", command[0])
120+ self.assertEqual("--file", command[1])
121+ temp_file = command[2]
122+ with open(temp_file, "r") as f:
123+ self.assertEqual("{foo: bar}", f.read().strip())
124+ remove.assert_called_with(temp_file)
125+
126+ @patch('os.remove')
127+ @patch('subprocess.check_output')
128+ @patch('subprocess.check_call')
129+ def test_relation_set_file_non_str(self, check_call, check_output, remove):
130+ """If relation-set accepts a --file parameter, it's used.
131+
132+ Any value that is not a string is converted to a string before encoding
133+ the settings to YAML.
134+ """
135+ # check_output(["relation-set", "--help"]) is used to determine
136+ # whether we can pass --file to it.
137+ check_output.return_value = "--file"
138+ hookenv.relation_set(foo={"bar": 1})
139+ check_output.assert_called_with(
140+ ["relation-set", "--help"], universal_newlines=True)
141+ # relation-set is called with relation-set --file <temp_file>
142+ # with data as YAML and the temp_file is then removed.
143+ self.assertEqual(1, len(check_call.call_args[0]))
144+ command = check_call.call_args[0][0]
145+ self.assertEqual(3, len(command))
146+ self.assertEqual("relation-set", command[0])
147+ self.assertEqual("--file", command[1])
148+ temp_file = command[2]
149+ with open(temp_file, "r") as f:
150+ self.assertEqual("{foo: '{''bar'': 1}'}", f.read().strip())
151+ remove.assert_called_with(temp_file)
152+
153 def test_lists_relation_types(self):
154 open_ = mock_open()
155 open_.return_value = io.BytesIO(CHARM_METADATA)

Subscribers

People subscribed via source and target branches