/
library.py
1552 lines (1240 loc) · 69.1 KB
/
library.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2008-2015 Nokia Networks
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
try:
from robot.api import logger
except ImportError:
logger = None
from robot.utils import ConnectionCache
from robot.utils import is_string, is_bytes
from .abstractclient import SSHClientException
from .client import SSHClient
from .config import (Configuration, IntegerEntry, LogLevelEntry, NewlineEntry,
StringEntry, TimeEntry)
from .version import VERSION
__version__ = VERSION
plural_or_not = lambda count: '' if count == 1 else 's'
class SSHLibrary(object):
"""Robot Framework test library for SSH and SFTP.
The library has the following main usages:
- Executing commands on the remote machine, either with blocking or
non-blocking behaviour (see `Execute Command` and `Start Command`,
respectively).
- Writing and reading in an interactive shell (e.g. `Read` and `Write`).
- Transferring files and directories over SFTP (e.g. `Get File` and
`Put Directory`).
- Ensuring that files or directories exist on the remote machine
(e.g. `File Should Exist` and `Directory Should Not Exist`).
This library works both with Python and Jython, but uses different
tools internally depending on the interpreter. See
[http://code.google.com/p/robotframework-sshlibrary/wiki/InstallationInstructions|installation instructions]
for more details about the dependencies. IronPython is unfortunately not
supported.
== Table of contents ==
- `Connections and login`
- `Configuration`
- `Executing commands`
- `Interactive shells`
- `Pattern matching`
- `Example`
- `Importing`
- `Shortcuts`
- `Keywords`
= Connections and login =
The library supports multiple connections to different hosts.
New connections are opened with `Open Connection`.
Logging into the host is done either with username and password
(`Login`) or with public/private key pair (`Login With Public key`).
Only one connection can be active at a time. This means that most of the
keywords only affect the active connection. Active connection can be
changed with `Switch Connection`.
= Configuration =
Default settings for all the upcoming connections can be configured on
`library importing` or later with `Set Default Configuration`.
All the settings are listed further below.
Using `Set Default Configuration` does not affect the already open
connections. Settings of the current connection can be configured
with `Set Client Configuration`. Settings of another, non-active connection,
can be configured by first using `Switch Connection` and then
`Set Client Configuration`.
Most of the defaults can be overridden per connection by defining them
as arguments to `Open Connection`. Otherwise the defaults are used.
== Configurable per connection ==
=== Default prompt ===
Argument `prompt` defines the character sequence used by `Read Until Prompt`
and must be set before that keyword can be used.
If you know the prompt on the remote machine, it is recommended to set it
to ease reading output from the server after using `Write`. In addition to
that, `Login` and `Login With Public Key` can read the server output more
efficiently when the prompt is set.
=== Default encoding ===
Argument `encoding` defines the
[http://docs.python.org/2/library/codecs.html#standard-encodings|
character encoding] of input and output sequences.
Starting from SSHLibrary 2.0, the default value is `UTF-8`.
=== Default path separator ===
Argument `path_separator` must be set to the one known by the operating
system and the SSH server on the remote machine. The path separator is
used by keywords `Get File`, `Put File`, `Get Directory` and
`Put Directory` for joining paths correctly on the remote host.
The default path separator is forward slash (`/`) which works on
Unix-like machines. On Windows the path separator to use depends on
the SSH server. Some servers use forward slash and others backslash,
and users need to configure the `path_separator` accordingly. Notice
that using a backslash in Robot Framework test data requires doubling
it like `\\\\`.
Configuring the library and connection specific path separator is a new
feature in SSHLibrary 2.0. Prior to it `Get File` and `Put File` had
their own `path_separator` arguments. These keyword specific arguments
were deprecated in 2.0 and will be removed in the future.
=== Default timeout ===
Argument `timeout` is used by `Read Until` variants. The default value is
`3 seconds`.
Value must be in Robot Framework's time format, e.g. `3`, `4.5s`, `1 minute`
and `2 min 3 s` are all accepted. See section `Time Format` in the
Robot Framework User Guide for details.
=== Default newline ===
Argument `newline` is the line break sequence used by `Write` keyword and
must be set according to the operating system on the remote machine.
The default value is `LF` (same as `\\n`) which is used on Unix-like
operating systems. With Windows remote machines, you need to set this to
`CRLF` (`\\r\\n`).
=== Default terminal settings ===
Argument `term_type` defines the virtual terminal type, and arguments
`width` and `height` can be used to control its virtual size.
== Not configurable per connection ==
=== Default loglevel ===
Argument `loglevel` sets the log level used to log the output read by
`Read`, `Read Until`, `Read Until Prompt`, `Read Until Regexp`, `Write`,
`Write Until Expected Output`, `Login` and `Login With Public Key`.
The default level is `INFO`.
`loglevel` is not configurable per connection but can be overridden by
passing it as an argument to the most of the mentioned keywords.
Possible argument values are `TRACE`, `DEBUG`, `INFO` and `WARN`.
= Executing commands =
For executing commands on the remote machine, there are two possibilities:
- `Execute Command` and `Start Command`.
The command is executed in a new shell on the remote machine,
which means that possible changes to the environment
(e.g. changing working directory, setting environment variables, etc.)
are not visible to the subsequent keywords.
- `Write`, `Write Bare`, `Write Until Expected Output`, `Read`,
`Read Until`, `Read Until Prompt` and `Read Until Regexp`.
These keywords operate in an interactive shell, which means that changes
to the environment are visible to the subsequent keywords.
= Interactive shells =
`Write`, `Write Bare`, `Write Until Expected Output`, `Read`,
`Read Until`, `Read Until Prompt` and `Read Until Regexp` can be used
to interact with the server within the same shell.
== Consumed output ==
All of these keywords, except `Write Bare`, consume the read or the written
text from the server output before returning. In practice this means that
the text is removed from the server output, i.e. subsequent calls to
`Read` keywords do not return text that was already read. This is
illustrated by the example below.
| `Write` | echo hello | | # consumes written `echo hello` |
| ${stdout}= | `Read Until` | hello | # consumes read `hello` and everything before it |
| `Should Contain` | ${stdout} | hello |
| ${stdout}= | `Read` | | # consumes everything available |
| `Should Not Contain` | ${stdout} | hello | # `hello` was already consumed earlier |
The consumed text is logged by the keywords and their argument `loglevel`
can be used to override [#Default loglevel|the default log level].
`Login` and `Login With Public Key` consume everything on the server output
or if [#Default prompt|the prompt is set], everything until the prompt.
== Reading ==
`Read`, `Read Until`, `Read Until Prompt` and `Read Until Regexp` can be
used to read from the server. The read text is also consumed from
the server output.
`Read` reads everything available on the server output, thus clearing it.
`Read Until` variants read output up until and *including* `expected` text.
These keywords will fail if [#Default timeout|the timeout] expires before
`expected` is found.
== Writing ==
`Write` and `Write Until Expected Output` consume the written text
from the server output while `Write Bare` does not.
These keywords do not return any output triggered by the written text.
To get the output, one of the `Read` keywords must be explicitly used.
= Pattern matching =
Some keywords allow their arguments to be specified as _glob patterns_
where:
| * | matches anything, even an empty string |
| ? | matches any single character |
| [chars] | matches any character inside square brackets (e.g. `[abc]` matches either `a`, `b` or `c`) |
| [!chars] | matches any character not inside square brackets |
Pattern matching is case-sensitive regardless the local or remote
operating system. Matching is implemented using Python's
[http://docs.python.org/library/fnmatch.html|fnmatch module].
= Example =
| ***** Settings *****
| Documentation This example demonstrates executing commands on a remote machine
| ... and getting their output and the return code.
| ...
| ... Notice how connections are handled as part of the suite setup and
| ... teardown. This saves some time when executing several test cases.
|
| Library `SSHLibrary`
| Suite Setup `Open Connection And Log In`
| Suite Teardown `Close All Connections`
|
| ***** Variables *****
| ${HOST} localhost
| ${USERNAME} test
| ${PASSWORD} test
|
| ***** Test Cases *****
| Execute Command And Verify Output
| [Documentation] `Execute Command` can be used to ran commands on the remote machine.
| ... The keyword returns the standard output by default.
| ${output}= `Execute Command` echo Hello SSHLibrary!
| `Should Be Equal` ${output} Hello SSHLibrary!
|
| Execute Command And Verify Return Code
| [Documentation] Often getting the return code of the command is enough.
| ... This behaviour can be adjusted as `Execute Command` arguments.
| ${rc}= `Execute Command` echo Success guaranteed. return_stdout=False return_rc=True
| `Should Be Equal` ${rc} ${0}
|
| Executing Commands In An Interactive Session
| [Documentation] `Execute Command` always executes the command in a new shell.
| ... This means that changes to the environment are not persisted
| ... between subsequent `Execute Command` keyword calls.
| ... `Write` and `Read Until` variants can be used to operate in the same shell.
| `Write` cd ..
| `Write` echo Hello from the parent directory!
| ${output}= `Read Until` directory!
| `Should End With` ${output} Hello from the parent directory!
|
| ***** Keywords *****
| Open Connection And Log In
| `Open Connection` ${HOST}
| `Login` ${USERNAME} ${PASSWORD}
Save the content as file `executing_command.txt` and run:
| pybot executing_commands.txt
You may want to override the variables from commandline to try this out on
your remote machine:
| pybot -v HOST:my.server.com -v USERNAME:johndoe -v PASSWORD:secretpasswd executing_commands.txt
"""
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
ROBOT_LIBRARY_VERSION = __version__
DEFAULT_TIMEOUT = '3 seconds'
DEFAULT_NEWLINE = 'LF'
DEFAULT_PROMPT = None
DEFAULT_LOGLEVEL = 'INFO'
DEFAULT_TERM_TYPE = 'vt100'
DEFAULT_TERM_WIDTH = 80
DEFAULT_TERM_HEIGHT = 24
DEFAULT_PATH_SEPARATOR = '/'
DEFAULT_ENCODING = 'UTF-8'
def __init__(self,
timeout=DEFAULT_TIMEOUT,
newline=DEFAULT_NEWLINE,
prompt=DEFAULT_PROMPT,
loglevel=DEFAULT_LOGLEVEL,
term_type=DEFAULT_TERM_TYPE,
width=DEFAULT_TERM_WIDTH,
height=DEFAULT_TERM_HEIGHT,
path_separator=DEFAULT_PATH_SEPARATOR,
encoding=DEFAULT_ENCODING):
"""SSHLibrary allows some import time `configuration`.
If the library is imported without any arguments, the library
defaults are used:
| Library | SSHLibrary |
Only arguments that are given are changed. In this example,
[#Default timeout|the timeout] is changed to `10 seconds` but
other settings are left to the library defaults:
| Library | SSHLibrary | 10 seconds |
[#Default prompt|Prompt] does not have a default value and
must be explicitly set to use `Read Until Prompt`.
In this example, the prompt is set to `$`:
| Library | SSHLibrary | prompt=$ |
Multiple settings are possible. In this example, the library is brought
into use with [#Default newline|newline] and [#Default path separator|
path_separator] known by Windows:
| Library | SSHLibrary | newline=CRLF | path_separator=\\\\ |
Arguments [#Default terminal settings|`term_type`],
[#Default terminal settings|`width`],
[#Default terminal settings|`height`],
[#Default path separator|`path separator`] and
[#Default encoding|`encoding`]
were added in SSHLibrary 2.0.
"""
self._connections = ConnectionCache()
self._config = _DefaultConfiguration(
timeout or self.DEFAULT_TIMEOUT,
newline or self.DEFAULT_NEWLINE,
prompt or self.DEFAULT_PROMPT,
loglevel or self.DEFAULT_LOGLEVEL,
term_type or self.DEFAULT_TERM_TYPE,
width or self.DEFAULT_TERM_WIDTH,
height or self.DEFAULT_TERM_HEIGHT,
path_separator or self.DEFAULT_PATH_SEPARATOR,
encoding or self.DEFAULT_ENCODING
)
@property
def current(self):
return self._connections.current
def set_default_configuration(self, timeout=None, newline=None, prompt=None,
loglevel=None, term_type=None, width=None,
height=None, path_separator=None,
encoding=None):
"""Update the default `configuration`.
Please note that using this keyword does not affect the already
open connections. Use `Set Client Configuration` to configure the
active connection.
Only parameters whose value is other than `None` are updated.
This example sets [#Default prompt|`prompt`] to `$`:
| Set Default Configuration | prompt=$ |
This example sets [#Default newline|`newline`] and [#Default path
separator| `path_separator`] to the ones known by Windows:
| Set Default Configuration | newline=CRLF | path_separator=\\\\ |
Sometimes you might want to use longer [#Default timeout|`timeout`]
for all the subsequent connections without affecting the existing ones:
| Set Default Configuration | timeout=5 seconds |
| Open Connection | local.server.com |
| Set Default Configuration | timeout=20 seconds |
| Open Connection | emea.server.com |
| Open Connection | apac.server.com |
| ${local} | ${emea} | ${apac}= | Get Connections |
| Should Be Equal As Integers | ${local.timeout} | 5 |
| Should Be Equal As Integers | ${emea.timeout} | 20 |
| Should Be Equal As Integers | ${apac.timeout} | 20 |
Arguments [#Default terminal settings|`term_type`],
[#Default terminal settings|`width`],
[#Default terminal settings|`height`],
[#Default path separator|`path_separator`] and
[#Default encoding|`encoding`]
were added in SSHLibrary 2.0.
"""
self._config.update(timeout=timeout, newline=newline, prompt=prompt,
loglevel=loglevel, term_type=term_type, width=width,
height=height, path_separator=path_separator,
encoding=encoding)
def set_client_configuration(self, timeout=None, newline=None, prompt=None,
term_type=None, width=None, height=None,
path_separator=None, encoding=None):
"""Update the `configuration` of the current connection.
Only parameters whose value is other than `None` are updated.
In the following example, [#Default prompt|`prompt`] is set for
the current connection. Other settings are left intact:
| Open Connection | my.server.com |
| Set Client Configuration | prompt=$ |
| ${myserver}= | Get Connection |
| Should Be Equal | ${myserver.prompt} | $ |
Using keyword does not affect the other connections:
| Open Connection | linux.server.com | |
| Set Client Configuration | prompt=$ | | # Only linux.server.com affected |
| Open Connection | windows.server.com | |
| Set Client Configuration | prompt=> | | # Only windows.server.com affected |
| ${linux} | ${windows}= | Get Connections |
| Should Be Equal | ${linux.prompt} | $ |
| Should Be Equal | ${windows.prompt} | > |
Multiple settings are possible. This example updates [#Default terminal
settings|the terminal settings] of the current connection:
| Open Connection | 192.168.1.1 |
| Set Client Configuration | term_type=ansi | width=40 |
Arguments [#Default path separator|`path_separator`] and
[#Default encoding|`encoding`]
were added in SSHLibrary 2.0.
"""
self.current.config.update(timeout=timeout, newline=newline,
prompt=prompt, term_type=term_type,
width=width, height=height,
path_separator=path_separator,
encoding=encoding)
def enable_ssh_logging(self, logfile):
"""Enables logging of SSH protocol output to given `logfile`.
All the existing and upcoming connections are logged onwards from
the moment the keyword was called.
`logfile` is path to a file that is writable by the current local user.
If the file already exists, it will be overwritten.
*Note:* This keyword only works with Python, i.e. when executing tests
with `pybot`.
Example:
| Open Connection | my.server.com | # Not logged |
| Enable SSH Logging | myserver.log |
| Login | johndoe | secretpasswd |
| Open Connection | build.local.net | # Logged |
| # Do something with the connections |
| # Check myserver.log for detailed debug information |
"""
if SSHClient.enable_logging(logfile):
self._log('SSH log is written to <a href="%s">file</a>.' % logfile,
'HTML')
def open_connection(self, host, alias=None, port=22, timeout=None,
newline=None, prompt=None, term_type=None, width=None,
height=None, path_separator=None, encoding=None):
"""Opens a new SSH connection to the given `host` and `port`.
The new connection is made active. Possible existing connections
are left open in the background.
Note that on Jython this keyword actually opens a connection and
will fail immediately on unreachable hosts. On Python the actual
connection attempt will not be done until `Login` is called.
This keyword returns the index of the new connection which can be used
later to switch back to it. Indices start from `1` and are reset
when `Close All Connections` is used.
Optional `alias` can be given for the connection and can be used for
switching between connections, similarly as the index.
See `Switch Connection` for more details.
Connection parameters, like [#Default timeout|`timeout`] and
[#Default newline|`newline`] are documented in `configuration`.
If they are not defined as arguments, [#Configuration|the library
defaults] are used for the connection.
All the arguments, except `host`, `alias` and `port`
can be later updated with `Set Client Configuration`.
Starting from SSHLibrary 1.1, a shell is automatically opened
by this keyword.
Port `22` is assumed by default:
| ${index}= | Open Connection | my.server.com |
Non-standard port may be given as an argument:
| ${index}= | Open Connection | 192.168.1.1 | port=23 |
Aliases are handy, if you need to switch back to the connection later:
| Open Connection | my.server.com | alias=myserver |
| # Do something with my.server.com |
| Open Connection | 192.168.1.1 |
| Switch Connection | myserver | | # Back to my.server.com |
Settings can be overridden per connection, otherwise the ones set on
`library importing` or with `Set Default Configuration` are used:
| Open Connection | 192.168.1.1 | timeout=1 hour | newline=CRLF |
| # Do something with the connection |
| Open Connection | my.server.com | # Default timeout | # Default line breaks |
[#Default terminal settings|The terminal settings] are also configurable
per connection:
| Open Connection | 192.168.1.1 | term_type=ansi | width=40 |
Arguments [#Default path separator|`path_separator`] and
[#Default encoding|`encoding`]
were added in SSHLibrary 2.0.
"""
timeout = timeout or self._config.timeout
newline = newline or self._config.newline
prompt = prompt or self._config.prompt
term_type = term_type or self._config.term_type
width = width or self._config.width
height = height or self._config.height
path_separator = path_separator or self._config.path_separator
encoding = encoding or self._config.encoding
client = SSHClient(host, alias, port, timeout, newline, prompt,
term_type, width, height, path_separator, encoding)
connection_index = self._connections.register(client, alias)
client.config.update(index=connection_index)
return connection_index
def switch_connection(self, index_or_alias):
"""Switches the active connection by index or alias.
`index_or_alias` is either connection index (an integer) or alias
(a string). Index is got as the return value of `Open Connection`.
Alternatively, both index and alias can queried as attributes
of the object returned by `Get Connection`.
This keyword returns the index of the previous active connection,
which can be used to switch back to that connection later.
Example:
| ${myserver}= | Open Connection | my.server.com |
| Login | johndoe | secretpasswd |
| Open Connection | build.local.net | alias=Build |
| Login | jenkins | jenkins |
| Switch Connection | ${myserver} | | # Switch using index |
| ${username}= | Execute Command | whoami | # Executed on my.server.com |
| Should Be Equal | ${username} | johndoe |
| Switch Connection | Build | | # Switch using alias |
| ${username}= | Execute Command | whoami | # Executed on build.local.net |
| Should Be Equal | ${username} | jenkins |
"""
old_index = self._connections.current_index
if index_or_alias is None:
self.close_connection()
else:
self._connections.switch(index_or_alias)
return old_index
def close_connection(self):
"""Closes the current connection.
No other connection is made active by this keyword. Manually use
`Switch Connection` to switch to another connection.
Example:
| Open Connection | my.server.com |
| Login | johndoe | secretpasswd |
| Get File | results.txt | /tmp |
| Close Connection |
| # Do something with /tmp/results.txt |
"""
self.current.close()
self._connections.current = self._connections._no_current
def close_all_connections(self):
"""Closes all open connections.
This keyword is ought to be used either in test or suite teardown to
make sure all the connections are closed before the test execution
finishes.
After this keyword, the connection indices returned by `Open Connection`
are reset and start from `1`.
Example:
| Open Connection | my.server.com |
| Open Connection | build.local.net |
| # Do something with the connections |
| [Teardown] | Close all connections |
"""
self._connections.close_all()
def get_connection(self, index_or_alias=None, index=False, host=False,
alias=False, port=False, timeout=False, newline=False,
prompt=False, term_type=False, width=False, height=False,
encoding=False):
"""Return information about the connection.
Connection is not changed by this keyword, use `Switch Connection` to
change the active connection.
If `index_or_alias` is not given, the information of the current
connection is returned.
This keyword returns an object that has the following attributes:
| = Name = | = Type = | = Explanation = |
| index | integer | Number of the connection. Numbering starts from `1`. |
| host | string | Destination hostname. |
| alias | string | An optional alias given when creating the connection. |
| port | integer | Destination port. |
| timeout | string | [#Default timeout|Timeout] length in textual representation. |
| newline | string | [#Default newline|The line break sequence] used by `Write` keyword. |
| prompt | string | [#Default prompt|Prompt character sequence] for `Read Until Prompt`. |
| term_type | string | Type of the [#Default terminal settings|virtual terminal]. |
| width | integer | Width of the [#Default terminal settings|virtual terminal]. |
| height | integer | Height of the [#Default terminal settings|virtual terminal]. |
| path_separator | string | [#Default path separator|The path separator] used on the remote host. |
| encoding | string | [#Default encoding|The encoding] used for inputs and outputs. |
If there is no connection, an object having `index` and `host` as `None`
is returned, rest of its attributes having their values as configuration
defaults.
If you want the information for all the open connections, use
`Get Connections`.
Getting connection information of the current connection:
| Open Connection | far.server.com |
| Open Connection | near.server.com | prompt=>> | # Current connection |
| ${nearhost}= | Get Connection | |
| Should Be Equal | ${nearhost.host} | near.server.com |
| Should Be Equal | ${nearhost.index} | 2 |
| Should Be Equal | ${nearhost.prompt} | >> |
| Should Be Equal | ${nearhost.term_type} | vt100 | # From defaults |
Getting connection information using an index:
| Open Connection | far.server.com |
| Open Connection | near.server.com | # Current connection |
| ${farhost}= | Get Connection | 1 |
| Should Be Equal | ${farhost.host} | far.server.com |
Getting connection information using an alias:
| Open Connection | far.server.com | alias=far |
| Open Connection | near.server.com | # Current connection |
| ${farhost}= | Get Connection | far |
| Should Be Equal | ${farhost.host} | far.server.com |
| Should Be Equal | ${farhost.alias} | far |
This keyword can also return plain connection attributes instead of
the whole connection object. This can be adjusted using the boolean
arguments `index`, `host`, `alias`, and so on, that correspond to
the attribute names of the object. If such arguments are given, and
they evaluate to true (e.g. any non-empty string except `false` or
`False`), only the respective connection attributes are returned.
Note that attributes are always returned in the same order arguments
are specified in the signature.
| Open Connection | my.server.com | alias=example |
| ${host}= | Get Connection | host=True |
| Should Be Equal | ${host} | my.server.com |
| ${host} | ${alias}= | Get Connection | host=yes | alias=please |
| Should Be Equal | ${host} | my.server.com |
| Should Be Equal | ${alias} | example |
Getting only certain attributes is especially useful when using this
library via the Remote library interface. This interface does not
support returning custom objects, but individual attributes can be
returned just fine.
This keyword logs the connection information with log level `INFO`.
New in SSHLibrary 2.0.
"""
if not index_or_alias:
index_or_alias = self._connections.current_index
try:
config = self._connections.get_connection(index_or_alias).config
except RuntimeError:
config = SSHClient(None).config
self._info(str(config))
return_values = tuple(self._get_config_values(config, index, host,
alias, port, timeout,
newline, prompt,
term_type, width, height,
encoding))
if not return_values:
return config
if len(return_values) == 1:
return return_values[0]
return return_values
def _info(self, msg):
self._log(msg, 'INFO')
def _log(self, msg, level=None):
level = self._active_loglevel(level)
msg = msg.strip()
if not msg:
return
if logger:
logger.write(msg, level)
else:
print('*%s* %s' % (level, msg))
def _active_loglevel(self, level):
if level is None:
return self._config.loglevel
if is_string(level) and \
level.upper() in ['TRACE', 'DEBUG', 'INFO', 'WARN', 'HTML']:
return level.upper()
raise AssertionError("Invalid log level '%s'." % level)
def _get_config_values(self, config, index, host, alias, port, timeout,
newline, prompt, term_type, width, height, encoding):
if self._output_wanted(index):
yield config.index
if self._output_wanted(host):
yield config.host
if self._output_wanted(alias):
yield config.alias
if self._output_wanted(port):
yield config.port
if self._output_wanted(timeout):
yield config.timeout
if self._output_wanted(newline):
yield config.newline.decode()
if self._output_wanted(prompt):
yield config.prompt
if self._output_wanted(term_type):
yield config.term_type
if self._output_wanted(width):
yield config.width
if self._output_wanted(height):
yield config.height
if self._output_wanted(encoding):
yield config.encoding
def _output_wanted(self, value):
return value and str(value).lower() != 'false'
def get_connections(self):
"""Return information about all the open connections.
This keyword returns a list of objects that are identical to the ones
returned by `Get Connection`.
Example:
| Open Connection | near.server.com | timeout=10s |
| Open Connection | far.server.com | timeout=5s |
| ${nearhost} | ${farhost}= | Get Connections |
| Should Be Equal | ${nearhost.host} | near.server.com |
| Should Be Equal As Integers | ${nearhost.timeout} | 10 |
| Should Be Equal As Integers | ${farhost.port} | 22 |
| Should Be Equal As Integers | ${farhost.timeout} | 5 |
This keyword logs the information of connections with log level `INFO`.
"""
configs = [c.config for c in self._connections._connections]
for c in configs:
self._info(str(c))
return configs
def login(self, username, password, delay='0.5 seconds'):
"""Logs into the SSH server with the given `username` and `password`.
Connection must be opened before using this keyword.
This keyword reads, returns and logs the server output after logging in.
If the [#Default prompt|prompt is set], everything until the prompt
is read. Otherwise the output is read using the `Read` keyword with
the given `delay`. The output is logged using the [#Default loglevel|
default log level].
Example that logs in and returns the output:
| Open Connection | linux.server.com |
| ${output}= | Login | johndoe | secretpasswd |
| Should Contain | ${output} | Last login at |
Example that logs in and returns everything until the prompt:
| Open Connection | linux.server.com | prompt=$ |
| ${output}= | Login | johndoe | secretpasswd |
| Should Contain | ${output} | johndoe@linux:~$ |
Argument `delay` was added in SSHLibrary 2.0.
"""
return self._login(self.current.login, username, password, delay)
def login_with_public_key(self, username, keyfile, password='',
delay='0.5 seconds'):
"""Logs into the SSH server using key-based authentication.
Connection must be opened before using this keyword.
`username` is the username on the remote machine.
`keyfile` is a path to a valid OpenSSH private key file on the local
filesystem.
`password` is used to unlock the `keyfile` if unlocking is required.
This keyword reads, returns and logs the server output after logging in.
If the [#Default prompt|prompt is set], everything until the prompt
is read. Otherwise the output is read using the `Read` keyword with
the given `delay`. The output is logged using the [#Default loglevel|
default log level].
Example that logs in using a private key and returns the output:
| Open Connection | linux.server.com |
| ${output}= | Login With Public Key | johndoe | /home/johndoe/.ssh/id_rsa |
| Should Contain | ${motd} | Last login at |
With locked private keys, the keyring `password` is required:
| Open Connection | linux.server.com |
| Login With Public Key | johndoe | /home/johndoe/.ssh/id_dsa | keyringpasswd |
Argument `delay` was added in SSHLibrary 2.0.
"""
return self._login(self.current.login_with_public_key, username,
keyfile, password, delay)
def _login(self, login_method, username, *args):
self._info("Logging into '%s:%s' as '%s'."
% (self.current.config.host, self.current.config.port,
username))
try:
login_output = login_method(username, *args)
self._log('Read output: %s' % login_output)
return login_output
except SSHClientException as e:
raise RuntimeError(e)
def execute_command(self, command, return_stdout=True, return_stderr=False,
return_rc=False):
"""Executes `command` on the remote machine and returns its outputs.
This keyword executes the `command` and returns after the execution
has been finished. Use `Start Command` if the command should be
started on the background.
By default, only the standard output is returned:
| ${stdout}= | Execute Command | echo 'Hello John!' |
| Should Contain | ${stdout} | Hello John! |
Arguments `return_stdout`, `return_stderr` and `return_rc` are used
to specify, what is returned by this keyword.
If several arguments evaluate to true, multiple values are returned.
Non-empty strings, except `false` and `False`, evaluate to true.
If errors are needed as well, set the respective argument value to true:
| ${stdout} | ${stderr}= | Execute Command | echo 'Hello John!' | return_stderr=True |
| Should Be Empty | ${stderr} |
Often checking the return code is enough:
| ${rc}= | Execute Command | echo 'Hello John!' | return_stdout=False | return_rc=True |
| Should Be Equal As Integers | ${rc} | 0 | # succeeded |
The `command` is always executed in a new shell. Thus possible changes
to the environment (e.g. changing working directory) are not visible
to the later keywords:
| ${pwd}= | Execute Command | pwd |
| Should Be Equal | ${pwd} | /home/johndoe |
| Execute Command | cd /tmp |
| ${pwd}= | Execute Command | pwd |
| Should Be Equal | ${pwd} | /home/johndoe |
`Write` and `Read` can be used for
[#Interactive shells|running multiple commands in the same shell].
This keyword logs the executed command and its exit status with
log level `INFO`.
"""
self._info("Executing command '%s'." % command)
opts = self._legacy_output_options(return_stdout, return_stderr,
return_rc)
stdout, stderr, rc = self.current.execute_command(command)
return self._return_command_output(stdout, stderr, rc, *opts)
def start_command(self, command):
"""Starts execution of the `command` on the remote machine and returns immediately.
This keyword returns nothing and does not wait for the `command`
execution to be finished. If waiting for the output is required,
use `Execute Command` instead.
This keyword does not return any output generated by the started
`command`. Use `Read Command Output` to read the output:
| Start Command | echo 'Hello John!' |
| ${stdout}= | Read Command Output |
| Should Contain | ${stdout} | Hello John! |
The `command` is always executed in a new shell, similarly as with
`Execute Command`. Thus possible changes to the environment
(e.g. changing working directory) are not visible to the later keywords:
| Start Command | pwd |
| ${pwd}= | Read Command Output |
| Should Be Equal | ${pwd} | /home/johndoe |
| Start Command | cd /tmp |
| Start Command | pwd |
| ${pwd}= | Read Command Output |
| Should Be Equal | ${pwd} | /home/johndoe |
`Write` and `Read` can be used for
[#Interactive shells|running multiple commands in the same shell].
This keyword logs the started command with log level `INFO`.
"""
self._info("Starting command '%s'." % command)
self._last_command = command
self.current.start_command(command)
def read_command_output(self, return_stdout=True, return_stderr=False,
return_rc=False):
"""Returns outputs of the most recent started command.
At least one command must have been started using `Start Command`
before this keyword can be used.
By default, only the standard output of the started command is returned:
| Start Command | echo 'Hello John!' |
| ${stdout}= | Read Command Output |
| Should Contain | ${stdout} | Hello John! |
Arguments `return_stdout`, `return_stderr` and `return_rc` are used
to specify, what is returned by this keyword.
If several arguments evaluate to true, multiple values are returned.
Non-empty strings, except `false` and `False`, evaluate to true.
If errors are needed as well, set the argument value to true:
| Start Command | echo 'Hello John!' |
| ${stdout} | ${stderr}= | Read Command Output | return_stderr=True |
| Should Be Empty | ${stderr} |
Often checking the return code is enough:
| Start Command | echo 'Hello John!' |
| ${rc}= | Read Command Output | return_stdout=False | return_rc=True |
| Should Be Equal As Integers | ${rc} | 0 | # succeeded |
Using `Start Command` and `Read Command Output` follows
'last in, first out' (LIFO) policy, meaning that `Read Command Output`
operates on the most recent started command, after which that command
is discarded and its output cannot be read again.
If several commands have been started, the output of the last started
command is returned. After that, a subsequent call will return the
output of the new last (originally the second last) command:
| Start Command | echo 'HELLO' |
| Start Command | echo 'SECOND' |
| ${stdout}= | Read Command Output |
| Should Contain | ${stdout} | 'SECOND' |
| ${stdout}= | Read Command Output |
| Should Contain | ${stdout} | 'HELLO' |
This keyword logs the read command with log level `INFO`.
"""
self._info("Reading output of command '%s'." % self._last_command)
opts = self._legacy_output_options(return_stdout, return_stderr,
return_rc)
try:
stdout, stderr, rc = self.current.read_command_output()
except SSHClientException as msg:
raise RuntimeError(msg)
return self._return_command_output(stdout, stderr, rc, *opts)
def _legacy_output_options(self, stdout, stderr, rc):
if not (is_string(stdout) or is_bytes(stdout)):
return stdout, stderr, rc
stdout = stdout.lower()
if stdout == 'stderr':
return False, True, rc
if stdout == 'both':
return True, True, rc
return stdout, stderr, rc
def _return_command_output(self, stdout, stderr, rc, return_stdout,
return_stderr, return_rc):
self._info("Command exited with return code %d." % rc)
ret = []
if self._output_wanted(return_stdout):
ret.append(stdout.rstrip('\n'))
if self._output_wanted(return_stderr):
ret.append(stderr.rstrip('\n'))
if self._output_wanted(return_rc):
ret.append(rc)
if len(ret) == 1:
return ret[0]
return ret
def write(self, text, loglevel=None):
"""Writes the given `text` on the remote machine and appends a newline.
Appended [#Default newline|newline] can be configured.