10

I need to copy a file from a pod to the host by using kubernetes python client. It would be something like kubectl cp pod:file file.

I'm testing the code from: https://github.com/prafull01/Kubernetes-Utilities/blob/master/kubectl_cp_as_python_client.py.

Specifically this code:

command_copy = ['tar', 'cf', '-', source_path]
with TemporaryFile() as tar_buffer:
    exec_stream = stream(self.coreClient.connect_get_namespaced_pod_exec, pod_name, name_space,
                         command=command_copy, stderr=True, stdin=True, stdout=True, tty=False,
                         _preload_content=False)
    # Copy file to stream

    try:
        while exec_stream.is_open():
            exec_stream.update(timeout=1)
            if exec_stream.peek_stdout():
                out = exec_stream.read_stdout()
                tar_buffer.write(out.encode('utf-8'))
            if exec_stream.peek_stderr():
                logger.debug("STDERR: %s" % exec_stream.read_stderr())
        exec_stream.close()
        tar_buffer.flush()
        tar_buffer.seek(0)
        with tarfile.open(fileobj=tar_buffer, mode='r:') as tar:
            member = tar.getmember(source_path)
            tar.makefile(member, destination_path)
            return True
    except Exception as e:
        raise manage_kubernetes_exception(e)

I'm using the oficial Kubernetes Python library version 10.0.1 stable with Python 3.6.8

But it is not working properly:

  • It is working when I copy small text files
  • but it is not working for other files such as a tar or zip file. It copies a corrupted file with same size that the original.

Is there any mistake in the code? Do you have any other way to do it by using kubernetes python client?

All the best.

Thanks.

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Jorgese
  • 551
  • 6
  • 14
  • `tar_buffer.write(out.encode('utf-8'))` is almost certainly incorrect, as tar files are **not** UTF-8 and I'm surprised it didn't blow up with a `UnicodeEncodeError` when it encountered null bytes (which a tar file for sure contains) – mdaniel Jan 12 '20 at 19:43
  • 1
    I have solved it by using data.decode("CP437") in WSClient and out.encode("CP437") in my copy metod. – Jorgese Jan 13 '20 at 13:20
  • How did you set the data.decode in WSClient? – CrazyDoggg Sep 18 '20 at 03:50

3 Answers3

9

I did it by using the following code:

def stream_copy_from_pod(self, pod_name, name_space, source_path, destination_path):
    """
    Copy file from pod to the host.

    :param pod_name: String. Pod name
    :param name_space: String. Namespace
    :param source_path: String. Pod destination file path
    :param destination_path: Host destination file path
    :return: bool
    """
    command_copy = ['tar', 'cf', '-', source_path]
    with TemporaryFile() as tar_buffer:
        exec_stream = stream(self.coreClient.connect_get_namespaced_pod_exec, pod_name, name_space,
                             command=command_copy, stderr=True, stdin=True, stdout=True, tty=False,
                             _preload_content=False)
        # Copy file to stream
        try:
            reader = WSFileManager(exec_stream)
            while True:
                out, err, closed = reader.read_bytes()
                if out:
                    tar_buffer.write(out)
                elif err:
                    logger.debug("Error copying file {0}".format(err.decode("utf-8", "replace")))
                if closed:
                    break
            exec_stream.close()
            tar_buffer.flush()
            tar_buffer.seek(0)
            with tarfile.open(fileobj=tar_buffer, mode='r:') as tar:
                member = tar.getmember(source_path)
                tar.makefile(member, destination_path)
                return True
        except Exception as e:
            raise manage_kubernetes_exception(e)

Using this web socket file manager for read it.

class WSFileManager:
"""
WS wrapper to manage read and write bytes in K8s WSClient
"""

def __init__(self, ws_client):
    """

    :param wsclient: Kubernetes WSClient
    """
    self.ws_client = ws_client

def read_bytes(self, timeout=0):
    """
    Read slice of bytes from stream

    :param timeout: read timeout
    :return: stdout, stderr and closed stream flag
    """
    stdout_bytes = None
    stderr_bytes = None

    if self.ws_client.is_open():
        if not self.ws_client.sock.connected:
            self.ws_client._connected = False
        else:
            r, _, _ = select.select(
                (self.ws_client.sock.sock, ), (), (), timeout)
            if r:
                op_code, frame = self.ws_client.sock.recv_data_frame(True)
                if op_code == ABNF.OPCODE_CLOSE:
                    self.ws_client._connected = False
                elif op_code == ABNF.OPCODE_BINARY or op_code == ABNF.OPCODE_TEXT:
                    data = frame.data
                    if len(data) > 1:
                        channel = data[0]
                        data = data[1:]
                        if data:
                            if channel == STDOUT_CHANNEL:
                                stdout_bytes = data
                            elif channel == STDERR_CHANNEL:
                                stderr_bytes = data
    return stdout_bytes, stderr_bytes, not self.ws_client._connected
Jorgese
  • 551
  • 6
  • 14
  • 1
    There is an undefined function `manage_kubernetes_exception` and some missing imports. Can these can be added to provide a minimal working example of this code? – Jordan May 07 '23 at 03:44
  • @Jordan, I found the following: ``` from kubernetes.stream import stream # type: ignore import tarfile import select from tempfile import TemporaryFile ``` But I do not know what is ABNF, what is STDOUT_CHANNEL – Eric Chow Aug 09 '23 at 05:28
  • finally found it: from websocket._abnf import ABNF – Bridger Aug 24 '23 at 17:52
1

Do you have any other way to do it by using kubernetes python client?

If you just want one file from the Pod, then don't use tar but rather /bin/cat instead, with the added benefit that you can just write directly to the local file, without having to deal with the tar file format. The disadvantage to that approach is that you would be responsible for setting the permissions on the local file to match what you expected them to be, which is something that tar -xf does for you. But if you're copying a remote tar file or zip file, that limitation wouldn't apply anyway, and may make the code a lot easier to reason about

mdaniel
  • 31,240
  • 5
  • 55
  • 58
  • 1
    yes cat is good for the regular file.but it does not work for the zip file.any ideas for copying zip files from pod to local? – focus zheng Nov 25 '20 at 08:16
  • `/bin/cat` is binary safe, although as the commentary at the top of the page mentions: it's the programmer's responsibility to open the streams in binary format, or face the wrath of encoding hell – mdaniel Nov 25 '20 at 08:18
0

This is a known issue with ICMP/MTU and tar.

See issue #60140 on github.

There are a number of workarounds, some of which might not apply to you.

  1. On AWS cp to bastion host and then scp from bastion.
  2. Allow ICMP on remote.
  3. Use --retries=n in kubectl cp

Unfortunately, the --retries (which actually resumes from where it left) does not come out of the box with tar or kubectl exec and has to be implemented in the code, in case of the python kubernetes client API.

Alain Pannetier
  • 9,315
  • 3
  • 41
  • 46