Skip to content

Commit 1683f45

Browse files
ik1nenvbn
authored andcommitted
Update docker commands. (#940)
* Add: Tests for newer version docker support. * Add: Support for newer versions of docker (Modified rules.docker_not_command). * Fix: Updated disabling memoize. * Change: removed empty list check. * Fix: _parse_commands now uses line.strip() internally and ends_with arg now doesn't end with newline. * Change: Replaced disable_memoize in favor of no_memoize fixture. * Fix: removed unused import.
1 parent d88454a commit 1683f45

File tree

2 files changed

+189
-7
lines changed

2 files changed

+189
-7
lines changed

tests/rules/test_docker_not_command.py

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,46 @@
44
from thefuck.rules.docker_not_command import get_new_command, match
55

66

7+
_DOCKER_SWARM_OUTPUT = '''
8+
Usage: docker swarm COMMAND
9+
10+
Manage Swarm
11+
12+
Commands:
13+
ca Display and rotate the root CA
14+
init Initialize a swarm
15+
join Join a swarm as a node and/or manager
16+
join-token Manage join tokens
17+
leave Leave the swarm
18+
unlock Unlock swarm
19+
unlock-key Manage the unlock key
20+
update Update the swarm
21+
22+
Run 'docker swarm COMMAND --help' for more information on a command.
23+
'''
24+
_DOCKER_IMAGE_OUTPUT = '''
25+
Usage: docker image COMMAND
26+
27+
Manage images
28+
29+
Commands:
30+
build Build an image from a Dockerfile
31+
history Show the history of an image
32+
import Import the contents from a tarball to create a filesystem image
33+
inspect Display detailed information on one or more images
34+
load Load an image from a tar archive or STDIN
35+
ls List images
36+
prune Remove unused images
37+
pull Pull an image or a repository from a registry
38+
push Push an image or a repository to a registry
39+
rm Remove one or more images
40+
save Save one or more images to a tar archive (streamed to STDOUT by default)
41+
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
42+
43+
Run 'docker image COMMAND --help' for more information on a command.
44+
'''
45+
46+
747
@pytest.fixture
848
def docker_help(mocker):
949
help = b'''Usage: docker [OPTIONS] COMMAND [arg...]
@@ -104,6 +144,94 @@ def docker_help(mocker):
104144
return mock
105145

106146

147+
@pytest.fixture
148+
def docker_help_new(mocker):
149+
helptext_new = b'''
150+
Usage: docker [OPTIONS] COMMAND
151+
152+
A self-sufficient runtime for containers
153+
154+
Options:
155+
--config string Location of client config files (default "/Users/ik1ne/.docker")
156+
-c, --context string Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var
157+
and default context set with "docker context use")
158+
-D, --debug Enable debug mode
159+
-H, --host list Daemon socket(s) to connect to
160+
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
161+
--tls Use TLS; implied by --tlsverify
162+
--tlscacert string Trust certs signed only by this CA (default "/Users/ik1ne/.docker/ca.pem")
163+
--tlscert string Path to TLS certificate file (default "/Users/ik1ne/.docker/cert.pem")
164+
--tlskey string Path to TLS key file (default "/Users/ik1ne/.docker/key.pem")
165+
--tlsverify Use TLS and verify the remote
166+
-v, --version Print version information and quit
167+
168+
Management Commands:
169+
builder Manage builds
170+
config Manage Docker configs
171+
container Manage containers
172+
context Manage contexts
173+
image Manage images
174+
network Manage networks
175+
node Manage Swarm nodes
176+
plugin Manage plugins
177+
secret Manage Docker secrets
178+
service Manage services
179+
stack Manage Docker stacks
180+
swarm Manage Swarm
181+
system Manage Docker
182+
trust Manage trust on Docker images
183+
volume Manage volumes
184+
185+
Commands:
186+
attach Attach local standard input, output, and error streams to a running container
187+
build Build an image from a Dockerfile
188+
commit Create a new image from a container's changes
189+
cp Copy files/folders between a container and the local filesystem
190+
create Create a new container
191+
diff Inspect changes to files or directories on a container's filesystem
192+
events Get real time events from the server
193+
exec Run a command in a running container
194+
export Export a container's filesystem as a tar archive
195+
history Show the history of an image
196+
images List images
197+
import Import the contents from a tarball to create a filesystem image
198+
info Display system-wide information
199+
inspect Return low-level information on Docker objects
200+
kill Kill one or more running containers
201+
load Load an image from a tar archive or STDIN
202+
login Log in to a Docker registry
203+
logout Log out from a Docker registry
204+
logs Fetch the logs of a container
205+
pause Pause all processes within one or more containers
206+
port List port mappings or a specific mapping for the container
207+
ps List containers
208+
pull Pull an image or a repository from a registry
209+
push Push an image or a repository to a registry
210+
rename Rename a container
211+
restart Restart one or more containers
212+
rm Remove one or more containers
213+
rmi Remove one or more images
214+
run Run a command in a new container
215+
save Save one or more images to a tar archive (streamed to STDOUT by default)
216+
search Search the Docker Hub for images
217+
start Start one or more stopped containers
218+
stats Display a live stream of container(s) resource usage statistics
219+
stop Stop one or more running containers
220+
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
221+
top Display the running processes of a container
222+
unpause Unpause all processes within one or more containers
223+
update Update configuration of one or more containers
224+
version Show the Docker version information
225+
wait Block until one or more containers stop, then print their exit codes
226+
227+
Run 'docker COMMAND --help' for more information on a command.
228+
'''
229+
mock = mocker.patch('subprocess.Popen')
230+
mock.return_value.stdout = BytesIO(b'')
231+
mock.return_value.stderr = BytesIO(helptext_new)
232+
return mock
233+
234+
107235
def output(cmd):
108236
return "docker: '{}' is not a docker command.\n" \
109237
"See 'docker --help'.".format(cmd)
@@ -113,17 +241,53 @@ def test_match():
113241
assert match(Command('docker pes', output('pes')))
114242

115243

244+
# tests docker (management command)
245+
@pytest.mark.usefixtures('no_memoize')
246+
@pytest.mark.parametrize('script, output', [
247+
('docker swarn', output('swarn')),
248+
('docker imge', output('imge'))])
249+
def test_match_management_cmd(script, output):
250+
assert match(Command(script, output))
251+
252+
253+
# tests docker (management cmd) (management subcmd)
254+
@pytest.mark.usefixtures('no_memoize')
255+
@pytest.mark.parametrize('script, output', [
256+
('docker swarm int', _DOCKER_SWARM_OUTPUT),
257+
('docker image la', _DOCKER_IMAGE_OUTPUT)])
258+
def test_match_management_subcmd(script, output):
259+
assert match(Command(script, output))
260+
261+
116262
@pytest.mark.parametrize('script, output', [
117263
('docker ps', ''),
118264
('cat pes', output('pes'))])
119265
def test_not_match(script, output):
120266
assert not match(Command(script, output))
121267

122268

123-
@pytest.mark.usefixtures('docker_help')
269+
@pytest.mark.usefixtures('no_memoize', 'docker_help')
124270
@pytest.mark.parametrize('wrong, fixed', [
125271
('pes', ['ps', 'push', 'pause']),
126272
('tags', ['tag', 'stats', 'images'])])
127273
def test_get_new_command(wrong, fixed):
128274
command = Command('docker {}'.format(wrong), output(wrong))
129275
assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]
276+
277+
278+
@pytest.mark.usefixtures('no_memoize', 'docker_help_new')
279+
@pytest.mark.parametrize('wrong, fixed', [
280+
('swarn', ['swarm', 'start', 'search']),
281+
('inage', ['image', 'images', 'rename'])])
282+
def test_get_new_management_command(wrong, fixed):
283+
command = Command('docker {}'.format(wrong), output(wrong))
284+
assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]
285+
286+
287+
@pytest.mark.usefixtures('no_memoize', 'docker_help_new')
288+
@pytest.mark.parametrize('wrong, fixed, output', [
289+
('swarm int', ['swarm init', 'swarm join', 'swarm join-token'], _DOCKER_SWARM_OUTPUT),
290+
('image la', ['image load', 'image ls', 'image tag'], _DOCKER_IMAGE_OUTPUT)])
291+
def test_get_new_management_command_subcommand(wrong, fixed, output):
292+
command = Command('docker {}'.format(wrong), output)
293+
assert get_new_command(command) == ['docker {}'.format(x) for x in fixed]

thefuck/rules/docker_not_command.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,42 @@
88
@sudo_support
99
@for_app('docker')
1010
def match(command):
11-
return 'is not a docker command' in command.output
11+
return 'is not a docker command' in command.output or 'Usage: docker' in command.output
1212

1313

14-
def get_docker_commands():
15-
proc = subprocess.Popen('docker', stdout=subprocess.PIPE)
16-
lines = [line.decode('utf-8') for line in proc.stdout.readlines()]
17-
lines = dropwhile(lambda line: not line.startswith('Commands:'), lines)
14+
def _parse_commands(lines, starts_with):
15+
lines = dropwhile(lambda line: not line.startswith(starts_with), lines)
1816
lines = islice(lines, 1, None)
19-
lines = list(takewhile(lambda line: line != '\n', lines))
17+
lines = list(takewhile(lambda line: line.strip(), lines))
2018
return [line.strip().split(' ')[0] for line in lines]
2119

2220

21+
def get_docker_commands():
22+
proc = subprocess.Popen('docker', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
23+
24+
# Old version docker returns its output to stdout, while newer version returns to stderr.
25+
lines = proc.stdout.readlines() or proc.stderr.readlines()
26+
lines = [line.decode('utf-8') for line in lines]
27+
28+
# Only newer versions of docker have management commands in the help text.
29+
if 'Management Commands:\n' in lines:
30+
management_commands = _parse_commands(lines, 'Management Commands:')
31+
else:
32+
management_commands = []
33+
regular_commands = _parse_commands(lines, 'Commands:')
34+
return management_commands + regular_commands
35+
36+
2337
if which('docker'):
2438
get_docker_commands = cache(which('docker'))(get_docker_commands)
2539

2640

2741
@sudo_support
2842
def get_new_command(command):
43+
if 'Usage:' in command.output and len(command.script_parts) > 1:
44+
management_subcommands = _parse_commands(command.output.split('\n'), 'Commands:')
45+
return replace_command(command, command.script_parts[2], management_subcommands)
46+
2947
wrong_command = re.findall(
3048
r"docker: '(\w+)' is not a docker command.", command.output)[0]
3149
return replace_command(command, wrong_command, get_docker_commands())

0 commit comments

Comments
 (0)