Merge ~powersj/cloud-init:cii-strings into cloud-init:master

Proposed by Joshua Powers
Status: Merged
Merged at revision: 1ac4bc2a4758d330bb94cd1b2391121cf461ff6a
Proposed branch: ~powersj/cloud-init:cii-strings
Merge into: cloud-init:master
Diff against target: 135 lines (+22/-13)
4 files modified
tests/cloud_tests/bddeb.py (+1/-2)
tests/cloud_tests/instances/base.py (+6/-4)
tests/cloud_tests/instances/lxd.py (+9/-1)
tests/cloud_tests/setup_image.py (+6/-6)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
cloud-init Commiters Pending
Review via email: mp+330535@code.launchpad.net

Commit message

tests: execute: support command as string

If a string is passed to execute, then invoke 'bash', '-c',
'string'. That allows the less verbose execution of simple
commands:
  image.execute("ls /run")
compared to the more explicit but longer winded:
  image.execute(["ls", "/run"])

If 'env' was ever modified in execute or a method that it called,
then the next invocation's default value would be changed. Instead
use None and then set to a new empty dict in the method.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:2f2717cc364a7ab2d7ff129f5b862643ab24af3f
https://jenkins.ubuntu.com/server/job/cloud-init-ci/283/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/283/rebuild

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

I'd like 2 things changed
a.) do not use /bin/bash or require it by (Instance/Image).execute.
   That is not python's default shell, unless /bin/sh == /bin/bash on the system.
b.) lets only fix strings where we were already using sh (via sh -c).

throwing stuff to sh just allows for errors and unexpected behavior.

I realize this is a contrived example, but its the kind of unexpected results you can get when you hand things off to shell interpretation and are not careful about quoting

## pretend tmp=$(mktemp -d) happens to return the literal name '/tmp/foo?'
## simulate this behavior by the 'tmp=; mkdir -p'
$ touch /tmp/foo1 /tmp/foo2
$ tmp=/tmp/foo?; mkdir -p "/tmp/foo?"
# ls /tmp/
foo1 foo2 foo?
# rm -Rvf $tmp
removed `/tmp/foo1'
removed `/tmp/foo2'
removed directory: `/tmp/foo?'

## YIKES!

I'll give you a diff or a MP with the changes i'd like.

Revision history for this message
Scott Moser (smoser) wrote :

Note above, 'mktemp -d' is probably not going to give you 'foo?'. However, nothing that i'm aware of promises that it's returned filenames will not have any characters that may be "special" to /bin/sh (or /usr/bin/perl or /usr/bin/python or gcc).

Revision history for this message
Joshua Powers (powersj) :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:2be9c0f1418b76b2a65198f35588e3f24d7b5f89
https://jenkins.ubuntu.com/server/job/cloud-init-ci/287/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/287/rebuild

review: Approve (continuous-integration)

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/tests/cloud_tests/bddeb.py b/tests/cloud_tests/bddeb.py
index fe80535..fba8a0c 100644
--- a/tests/cloud_tests/bddeb.py
+++ b/tests/cloud_tests/bddeb.py
@@ -28,8 +28,7 @@ def build_deb(args, instance):
28 # update remote system package list and install build deps28 # update remote system package list and install build deps
29 LOG.debug('installing pre-reqs')29 LOG.debug('installing pre-reqs')
30 pkgs = ' '.join(pre_reqs)30 pkgs = ' '.join(pre_reqs)
31 cmd = 'apt-get update && apt-get install --yes {}'.format(pkgs)31 instance.execute('apt-get update && apt-get install --yes {}'.format(pkgs))
32 instance.execute(['/bin/sh', '-c', cmd])
3332
34 # local tmpfile that must be deleted33 # local tmpfile that must be deleted
35 local_tarball = tempfile.NamedTemporaryFile().name34 local_tarball = tempfile.NamedTemporaryFile().name
diff --git a/tests/cloud_tests/instances/base.py b/tests/cloud_tests/instances/base.py
index 959e9cc..58f45b1 100644
--- a/tests/cloud_tests/instances/base.py
+++ b/tests/cloud_tests/instances/base.py
@@ -23,7 +23,7 @@ class Instance(object):
23 self.config = config23 self.config = config
24 self.features = features24 self.features = features
2525
26 def execute(self, command, stdout=None, stderr=None, env={},26 def execute(self, command, stdout=None, stderr=None, env=None,
27 rcs=None, description=None):27 rcs=None, description=None):
28 """Execute command in instance, recording output, error and exit code.28 """Execute command in instance, recording output, error and exit code.
2929
@@ -31,6 +31,8 @@ class Instance(object):
31 target filesystem being available at /.31 target filesystem being available at /.
3232
33 @param command: the command to execute as root inside the image33 @param command: the command to execute as root inside the image
34 if command is a string, then it will be executed as:
35 ['sh', '-c', command]
34 @param stdout, stderr: file handles to write output and error to36 @param stdout, stderr: file handles to write output and error to
35 @param env: environment variables37 @param env: environment variables
36 @param rcs: allowed return codes from command38 @param rcs: allowed return codes from command
@@ -137,9 +139,9 @@ class Instance(object):
137 tests.append(self.config['cloud_init_ready_script'])139 tests.append(self.config['cloud_init_ready_script'])
138140
139 formatted_tests = ' && '.join(clean_test(t) for t in tests)141 formatted_tests = ' && '.join(clean_test(t) for t in tests)
140 test_cmd = ('for ((i=0;i<{time};i++)); do {test} && exit 0; sleep 1; '142 cmd = ('i=0; while [ $i -lt {time} ] && i=$(($i+1)); do {test} && '
141 'done; exit 1;').format(time=time, test=formatted_tests)143 'exit 0; sleep 1; done; exit 1').format(time=time,
142 cmd = ['/bin/bash', '-c', test_cmd]144 test=formatted_tests)
143145
144 if self.execute(cmd, rcs=(0, 1))[-1] != 0:146 if self.execute(cmd, rcs=(0, 1))[-1] != 0:
145 raise OSError('timeout: after {}s system not started'.format(time))147 raise OSError('timeout: after {}s system not started'.format(time))
diff --git a/tests/cloud_tests/instances/lxd.py b/tests/cloud_tests/instances/lxd.py
index b9c2cc6..a43918c 100644
--- a/tests/cloud_tests/instances/lxd.py
+++ b/tests/cloud_tests/instances/lxd.py
@@ -31,7 +31,7 @@ class LXDInstance(base.Instance):
31 self._pylxd_container.sync()31 self._pylxd_container.sync()
32 return self._pylxd_container32 return self._pylxd_container
3333
34 def execute(self, command, stdout=None, stderr=None, env={},34 def execute(self, command, stdout=None, stderr=None, env=None,
35 rcs=None, description=None):35 rcs=None, description=None):
36 """Execute command in instance, recording output, error and exit code.36 """Execute command in instance, recording output, error and exit code.
3737
@@ -39,6 +39,8 @@ class LXDInstance(base.Instance):
39 target filesystem being available at /.39 target filesystem being available at /.
4040
41 @param command: the command to execute as root inside the image41 @param command: the command to execute as root inside the image
42 if command is a string, then it will be executed as:
43 ['sh', '-c', command]
42 @param stdout: file handler to write output44 @param stdout: file handler to write output
43 @param stderr: file handler to write error45 @param stderr: file handler to write error
44 @param env: environment variables46 @param env: environment variables
@@ -46,6 +48,12 @@ class LXDInstance(base.Instance):
46 @param description: purpose of command48 @param description: purpose of command
47 @return_value: tuple containing stdout data, stderr data, exit code49 @return_value: tuple containing stdout data, stderr data, exit code
48 """50 """
51 if env is None:
52 env = {}
53
54 if isinstance(command, str):
55 command = ['sh', '-c', command]
56
49 # ensure instance is running and execute the command57 # ensure instance is running and execute the command
50 self.start()58 self.start()
51 res = self.pylxd_container.execute(command, environment=env)59 res = self.pylxd_container.execute(command, environment=env)
diff --git a/tests/cloud_tests/setup_image.py b/tests/cloud_tests/setup_image.py
index 8053a09..3c0fff6 100644
--- a/tests/cloud_tests/setup_image.py
+++ b/tests/cloud_tests/setup_image.py
@@ -49,8 +49,8 @@ def install_deb(args, image):
49 LOG.debug(msg)49 LOG.debug(msg)
50 remote_path = os.path.join('/tmp', os.path.basename(args.deb))50 remote_path = os.path.join('/tmp', os.path.basename(args.deb))
51 image.push_file(args.deb, remote_path)51 image.push_file(args.deb, remote_path)
52 cmd = 'dpkg -i {} || apt-get install --yes -f'.format(remote_path)52 cmd = 'dpkg -i {}; apt-get install --yes -f'.format(remote_path)
53 image.execute(['/bin/sh', '-c', cmd], description=msg)53 image.execute(cmd, description=msg)
5454
55 # check installed deb version matches package55 # check installed deb version matches package
56 fmt = ['-W', "--showformat='${Version}'"]56 fmt = ['-W', "--showformat='${Version}'"]
@@ -113,7 +113,7 @@ def upgrade(args, image):
113113
114 msg = 'upgrading cloud-init'114 msg = 'upgrading cloud-init'
115 LOG.debug(msg)115 LOG.debug(msg)
116 image.execute(['/bin/sh', '-c', cmd], description=msg)116 image.execute(cmd, description=msg)
117117
118118
119def upgrade_full(args, image):119def upgrade_full(args, image):
@@ -134,7 +134,7 @@ def upgrade_full(args, image):
134134
135 msg = 'full system upgrade'135 msg = 'full system upgrade'
136 LOG.debug(msg)136 LOG.debug(msg)
137 image.execute(['/bin/sh', '-c', cmd], description=msg)137 image.execute(cmd, description=msg)
138138
139139
140def run_script(args, image):140def run_script(args, image):
@@ -165,7 +165,7 @@ def enable_ppa(args, image):
165 msg = 'enable ppa: "{}" in target'.format(ppa)165 msg = 'enable ppa: "{}" in target'.format(ppa)
166 LOG.debug(msg)166 LOG.debug(msg)
167 cmd = 'add-apt-repository --yes {} && apt-get update'.format(ppa)167 cmd = 'add-apt-repository --yes {} && apt-get update'.format(ppa)
168 image.execute(['/bin/sh', '-c', cmd], description=msg)168 image.execute(cmd, description=msg)
169169
170170
171def enable_repo(args, image):171def enable_repo(args, image):
@@ -188,7 +188,7 @@ def enable_repo(args, image):
188188
189 msg = 'enable repo: "{}" in target'.format(args.repo)189 msg = 'enable repo: "{}" in target'.format(args.repo)
190 LOG.debug(msg)190 LOG.debug(msg)
191 image.execute(['/bin/sh', '-c', cmd], description=msg)191 image.execute(cmd, description=msg)
192192
193193
194def setup_image(args, image):194def setup_image(args, image):

Subscribers

People subscribed via source and target branches