|
10 | 10 | from typing import List, Dict, Union, Optional, Iterable |
11 | 11 | from ipaddress import ip_address, IPv4Address, IPv6Address, IPv4Interface, IPv6Interface |
12 | 12 |
|
13 | | -from netmiko import ConnectHandler, FileTransfer |
14 | | -from netmiko.cisco import CiscoAsaSSH |
| 13 | +from netmiko import ConnectHandler |
| 14 | +from netmiko.cisco import CiscoAsaSSH, CiscoAsaFileTransfer |
15 | 15 |
|
16 | 16 | from pyntc.utils import get_structured_data |
17 | 17 | from .base_device import BaseDevice, fix_docs |
@@ -61,11 +61,35 @@ def _enter_config(self): |
61 | 61 | self.enable() |
62 | 62 | self.native.config_mode() |
63 | 63 |
|
64 | | - def _file_copy_instance(self, src, dest=None, file_system="flash:"): |
| 64 | + def _file_copy(self, src: str, dest: str, file_system: str) -> None: |
| 65 | + self.enable() |
| 66 | + |
| 67 | + if not self.file_copy_remote_exists(src, dest, file_system): |
| 68 | + fc: CiscoAsaFileTransfer = self._file_copy_instance(src, dest, file_system) |
| 69 | + |
| 70 | + try: |
| 71 | + fc.establish_scp_conn() |
| 72 | + fc.transfer_file() |
| 73 | + # Allow EOFErrors to be caught and only raise an error if the file is not actually on the device |
| 74 | + except EOFError: |
| 75 | + self.open() |
| 76 | + except Exception: |
| 77 | + raise FileTransferError |
| 78 | + finally: |
| 79 | + fc.close_scp_chan() |
| 80 | + |
| 81 | + if not self.file_copy_remote_exists(src, dest, file_system): |
| 82 | + raise FileTransferError( |
| 83 | + message="Attempted file copy, but could not validate file existed after transfer" |
| 84 | + ) |
| 85 | + |
| 86 | + def _file_copy_instance( |
| 87 | + self, src: str, dest: Optional[str] = None, file_system: str = "flash:" |
| 88 | + ) -> CiscoAsaFileTransfer: |
65 | 89 | if dest is None: |
66 | 90 | dest = os.path.basename(src) |
67 | 91 |
|
68 | | - fc = FileTransfer(self.native, src, dest, file_system=file_system) |
| 92 | + fc = CiscoAsaFileTransfer(self.native, src, dest, file_system=file_system) |
69 | 93 | return fc |
70 | 94 |
|
71 | 95 | def _get_file_system(self): |
@@ -355,34 +379,86 @@ def enable(self): |
355 | 379 | if self.native.check_config_mode(): |
356 | 380 | self.native.exit_config_mode() |
357 | 381 |
|
| 382 | + def enable_scp(self) -> None: |
| 383 | + """ |
| 384 | + Enable SCP on device by configuring "ssh scopy enable". |
| 385 | +
|
| 386 | + The command is ran on the active device; if the device is |
| 387 | + currently standby, then a new connection is created to the |
| 388 | + active device. The configuration is saved after to sync to peer. |
| 389 | +
|
| 390 | + Raises: |
| 391 | + FileTransferError: When unable to configure scopy on the active device. |
| 392 | +
|
| 393 | + Example: |
| 394 | + >>> device = ASADevice(**connection_args) |
| 395 | + >>> device.show("show run ssh | i scopy") |
| 396 | + '' |
| 397 | + >>> device.enable_scp() |
| 398 | + >>> device.show("show run ssh | i scopy") |
| 399 | + 'ssh scopy enable' |
| 400 | + >>> |
| 401 | + """ |
| 402 | + if self.is_active(): |
| 403 | + device: ASADevice = self |
| 404 | + else: |
| 405 | + device = self.peer_device |
| 406 | + |
| 407 | + if not device.is_active(): |
| 408 | + raise FileTransferError("Unable to establish a connection with the active device") |
| 409 | + |
| 410 | + try: |
| 411 | + device.config("ssh scopy enable") |
| 412 | + except CommandError: |
| 413 | + raise FileTransferError("Unable to enable scopy on the device") |
| 414 | + device.save() |
| 415 | + |
358 | 416 | @property |
359 | 417 | def facts(self): |
360 | 418 | """Implement this once facts' re-factor is done. """ |
361 | 419 | return {} |
362 | 420 |
|
363 | | - def file_copy(self, src, dest=None, file_system=None): |
364 | | - self.enable() |
365 | | - if file_system is None: |
366 | | - file_system = self._get_file_system() |
| 421 | + def file_copy( |
| 422 | + self, |
| 423 | + src: str, |
| 424 | + dest: Optional[str] = None, |
| 425 | + file_system: Optional[str] = None, |
| 426 | + peer: Optional[bool] = False, |
| 427 | + ) -> None: |
| 428 | + """ |
| 429 | + Copy ``src`` file to device. |
367 | 430 |
|
368 | | - if not self.file_copy_remote_exists(src, dest, file_system): |
369 | | - fc = self._file_copy_instance(src, dest, file_system=file_system) |
370 | | - # if not self.fc.verify_space_available(): |
371 | | - # raise FileTransferError('Not enough space available.') |
| 431 | + The ``src`` file can be copied to both the device and its peer by |
| 432 | + setting ``peer`` to True. If transferring to the peer device, the |
| 433 | + transfer will use the address associated with the ``peer_interface`` |
| 434 | + from "show failover" output. |
372 | 435 |
|
373 | | - try: |
374 | | - fc.enable_scp() |
375 | | - fc.establish_scp_conn() |
376 | | - fc.transfer_file() |
377 | | - except: # noqa E722 |
378 | | - raise FileTransferError |
379 | | - finally: |
380 | | - fc.close_scp_chan() |
| 436 | + Args: |
| 437 | + src (str): The path to the file to be copied to the device. |
| 438 | + dest (str): The name to use for storing the file on the device. |
| 439 | + Default is to use the name of the ``src`` file. |
| 440 | + file_system (str): The directory to store the file on the device. |
| 441 | + Default will use ``_get_file_system()`` to determine the default file_system. |
| 442 | + peer (bool): Whether to transfer the ``src`` file to the peer device. |
381 | 443 |
|
382 | | - if not self.file_copy_remote_exists(src, dest, file_system): |
383 | | - raise FileTransferError( |
384 | | - message="Attempted file copy, but could not validate file existed after transfer" |
385 | | - ) |
| 444 | + Raises: |
| 445 | + FileTransferError: When the ``src`` file is unable to transfer the file to any device. |
| 446 | +
|
| 447 | + Example: |
| 448 | + >>> dev = ASADevice(**connection_args) |
| 449 | + >>> dev.file_copy("path/to/asa-image.bin", peer=True) |
| 450 | + """ |
| 451 | + if dest is None: |
| 452 | + dest = os.path.basename(src) |
| 453 | + |
| 454 | + if file_system is None: |
| 455 | + file_system = self._get_file_system() |
| 456 | + |
| 457 | + # netmiko's enable_scp |
| 458 | + self.enable_scp() |
| 459 | + self._file_copy(src, dest, file_system) |
| 460 | + if peer: |
| 461 | + self.peer_device._file_copy(src, dest, file_system) |
386 | 462 |
|
387 | 463 | # TODO: Make this an internal method since exposing file_copy should be sufficient |
388 | 464 | def file_copy_remote_exists(self, src, dest=None, file_system=None): |
|
0 commit comments