Skip to content

Commit cef1533

Browse files
f5-rahmcaphrim007
authored andcommitted
Issue #538 - Adding bash utility (#690)
* Issue #538 - Adding bash utility * Issue #538 - Restricted utilCmdArgs submissions to "-c" bash argument * Issue #538 - fixed flake8 errors * Issue #538 - Added 2 test cases for test coverage
1 parent b78859e commit cef1533

4 files changed

Lines changed: 185 additions & 0 deletions

File tree

f5/bigip/tm/util/Bash.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# coding=utf-8
2+
#
3+
# Copyright 2016 F5 Networks Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
"""BIG-IP® utility module
18+
19+
REST URI
20+
``http://localhost/mgmt/tm/util/bash``
21+
22+
GUI Path
23+
N/A
24+
25+
REST Kind
26+
``tm:util:bash:*``
27+
"""
28+
29+
from f5.bigip.mixins import CommandExecutionMixin
30+
from f5.bigip.resource import UnnamedResource
31+
from f5.utils.util_exceptions import UtilError
32+
33+
34+
class Bash(UnnamedResource, CommandExecutionMixin):
35+
"""BIG-IP® utility command
36+
37+
.. note::
38+
39+
This is an unnamed resource so it has not ~Partition~Name pattern
40+
at the end of its URI.
41+
"""
42+
43+
def __init__(self, util):
44+
super(Bash, self).__init__(util)
45+
self._meta_data['required_command_parameters'].update(('utilCmdArgs',))
46+
self._meta_data['required_json_kind'] = 'tm:util:bash:runstate'
47+
self._meta_data['allowed_commands'].append('run')
48+
49+
def _exec_cmd(self, command, **kwargs):
50+
kwargs['command'] = command
51+
self._check_exclusive_parameters(**kwargs)
52+
requests_params = self._handle_requests_params(kwargs)
53+
self._check_command_parameters(**kwargs)
54+
55+
if not kwargs['utilCmdArgs'].startswith("-c"):
56+
raise UtilError(
57+
'Required format is "-c <bash command and arguments>"')
58+
59+
session = self._meta_data['bigip']._meta_data['icr_session']
60+
response = session.post(
61+
self._meta_data['uri'], json=kwargs, **requests_params)
62+
self._local_update(response.json())
63+
64+
if 'commandResult' in self.__dict__:
65+
if self.commandResult.startswith('/bin/bash'):
66+
raise UtilError('%s' % self.commandResult.split(' ', 1)[1])
67+
else:
68+
return self
69+
else:
70+
return self

f5/bigip/tm/util/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"""
2929

3030
from f5.bigip.resource import PathElement
31+
from f5.bigip.tm.util.Bash import Bash
3132
from f5.bigip.tm.util.Unix_Ls import Unix_Ls
3233
from f5.bigip.tm.util.Unix_Mv import Unix_Mv
3334
from f5.bigip.tm.util.Unix_Rm import Unix_Rm
@@ -37,6 +38,7 @@ class Util(PathElement):
3738
def __init__(self, bigip):
3839
super(Util, self).__init__(bigip)
3940
self._meta_data['allowed_lazy_attributes'] = [
41+
Bash,
4042
Unix_Ls,
4143
Unix_Mv,
4244
Unix_Rm

f5/bigip/tm/util/test/test_bash.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2016 F5 Networks Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
import mock
17+
import pytest
18+
19+
from f5.bigip import ManagementRoot
20+
from f5.bigip.tm.util.Bash import Bash
21+
22+
23+
@pytest.fixture
24+
def FakeBash():
25+
fake_sys = mock.MagicMock()
26+
fake_bash = Bash(fake_sys)
27+
return fake_bash
28+
29+
30+
@pytest.fixture
31+
def FakeiControl(fakeicontrolsession):
32+
mr = ManagementRoot('host', 'fake_admin', 'fake_admin')
33+
mock_session = mock.MagicMock()
34+
mock_session.post.return_value.json.return_value = {}
35+
mr._meta_data['icr_session'] = mock_session
36+
return mr.tm.util.bash
37+
38+
39+
class TestBashCommand(object):
40+
def test_command_bash(self, FakeiControl):
41+
FakeiControl.exec_cmd('run',
42+
utilCmdArgs='-c "df -k"')
43+
session = FakeiControl._meta_data['bigip']._meta_data['icr_session']
44+
assert session.post.call_args == mock.call(
45+
'https://host:443/mgmt/tm/util/bash/',
46+
json={'utilCmdArgs': '-c "df -k"', 'command': 'run'}
47+
)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
# Copyright 2016 F5 Networks Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
import pytest
18+
19+
from f5.bigip.resource import MissingRequiredCommandParameter
20+
from f5.utils.util_exceptions import UtilError
21+
from icontrol.session import iControlUnexpectedHTTPError
22+
23+
24+
def test_E_bash(mgmt_root):
25+
26+
with pytest.raises(MissingRequiredCommandParameter) as err:
27+
mgmt_root.tm.util.bash.exec_cmd('run')
28+
assert "Missing required params: ['utilCmdArgs']" in err.response.text
29+
30+
# use bash to create a test file
31+
bash1 = mgmt_root.tm.util.bash.exec_cmd(
32+
'run',
33+
utilCmdArgs='-c "echo hello >> /var/tmp/test.txt"')
34+
35+
# commandResult should not be present if this was successful
36+
assert 'commandResult' not in bash1.__dict__
37+
38+
bash2 = mgmt_root.tm.util.bash.exec_cmd(
39+
'run',
40+
utilCmdArgs='-c df -k')
41+
42+
# commandResult should b present with data from 'df -k'
43+
assert 'commandResult' in bash2.__dict__
44+
45+
# UtilError should be raised if -c is not specified
46+
with pytest.raises(UtilError) as err:
47+
mgmt_root.tm.util.bash.exec_cmd('run',
48+
utilCmdArgs='-9 hello')
49+
assert 'Required format is "-c <bash command and arguments>"'\
50+
in err.response.text
51+
52+
# UtilError should be raised if command isn't found
53+
with pytest.raises(UtilError) as err:
54+
mgmt_root.tm.util.bash.exec_cmd('run',
55+
utilCmdArgs='-c hello')
56+
assert 'command not found' in err.response.text
57+
58+
# clean up test file
59+
mgmt_root.tm.util.unix_rm.exec_cmd('run', utilCmdArgs='/var/tmp/test.txt')
60+
61+
# iControlUnexpectedHTTPError should be raised if quotes don't match
62+
with pytest.raises(iControlUnexpectedHTTPError) as err:
63+
mgmt_root.tm.util.bash.exec_cmd('run',
64+
utilCmdArgs='-c "df -k')
65+
assert err.response.status_code == 400
66+
assert 'quotes are not balanced' in err.response.text

0 commit comments

Comments
 (0)