Repository: python-gitlab/python-gitlab

Issue:
No way to download artifacts using CLI
This code: https://github.com/python-gitlab/python-gitlab/blob/master/gitlab/v4/cli.py#L447

has zero chance of working.

I.e. if you do this:

```
gitlab project-job artifacts --id XXX --project-id YYY
```

it will absolutely never work. Why did you believe that you can call `decode()` on the contents of ZIP file?


Discussion:

Resolving patch:
diff --git a/gitlab/v4/cli.py b/gitlab/v4/cli.py
--- a/gitlab/v4/cli.py
+++ b/gitlab/v4/cli.py
@@ -85,11 +85,7 @@ def do_project_export_download(self):
         try:
             project = self.gl.projects.get(int(self.args["project_id"]), lazy=True)
             data = project.exports.get().download()
-            if hasattr(sys.stdout, "buffer"):
-                # python3
-                sys.stdout.buffer.write(data)
-            else:
-                sys.stdout.write(data)
+            sys.stdout.buffer.write(data)
 
         except Exception as e:
             cli.die("Impossible to download the export", e)
@@ -440,5 +436,7 @@ def run(gl, what, action, args, verbose, output, fields):
         printer.display(get_dict(data, fields), verbose=verbose, obj=data)
     elif isinstance(data, str):
         print(data)
+    elif isinstance(data, bytes):
+        sys.stdout.buffer.write(data)
     elif hasattr(data, "decode"):
         print(data.decode())
diff --git a/tools/functional/conftest.py b/tools/functional/conftest.py
--- a/tools/functional/conftest.py
+++ b/tools/functional/conftest.py
@@ -132,6 +132,36 @@ def gl(gitlab_config):
     return instance
 
 
+@pytest.fixture(scope="session")
+def gitlab_runner(gl):
+    container = "gitlab-runner-test"
+    runner_name = "python-gitlab-runner"
+    token = "registration-token"
+    url = "http://gitlab"
+
+    docker_exec = ["docker", "exec", container, "gitlab-runner"]
+    register = [
+        "register",
+        "--run-untagged",
+        "--non-interactive",
+        "--registration-token",
+        token,
+        "--name",
+        runner_name,
+        "--url",
+        url,
+        "--clone-url",
+        url,
+        "--executor",
+        "shell",
+    ]
+    unregister = ["unregister", "--name", runner_name]
+
+    yield check_output(docker_exec + register).decode()
+
+    check_output(docker_exec + unregister).decode()
+
+
 @pytest.fixture(scope="module")
 def group(gl):
     """Group fixture for group API resource tests."""


Test patch:
diff --git a/tools/functional/cli/test_cli_artifacts.py b/tools/functional/cli/test_cli_artifacts.py
new file mode 100644
--- /dev/null
+++ b/tools/functional/cli/test_cli_artifacts.py
@@ -0,0 +1,52 @@
+import subprocess
+import sys
+import textwrap
+import time
+from io import BytesIO
+from zipfile import is_zipfile
+
+import pytest
+
+
+content = textwrap.dedent(
+    """\
+    test-artifact:
+      script: echo "test" > artifact.txt
+      artifacts:
+        untracked: true
+    """
+)
+data = {
+    "file_path": ".gitlab-ci.yml",
+    "branch": "master",
+    "content": content,
+    "commit_message": "Initial commit",
+}
+
+
+@pytest.mark.skipif(sys.version_info < (3, 8), reason="I am the walrus")
+def test_cli_artifacts(capsysbinary, gitlab_config, gitlab_runner, project):
+    project.files.create(data)
+
+    while not (jobs := project.jobs.list(scope="success")):
+        time.sleep(0.5)
+
+    job = project.jobs.get(jobs[0].id)
+    cmd = [
+        "gitlab",
+        "--config-file",
+        gitlab_config,
+        "project-job",
+        "artifacts",
+        "--id",
+        str(job.id),
+        "--project-id",
+        str(project.id),
+    ]
+
+    with capsysbinary.disabled():
+        artifacts = subprocess.check_output(cmd)
+    assert isinstance(artifacts, bytes)
+
+    artifacts_zip = BytesIO(artifacts)
+    assert is_zipfile(artifacts_zip)