Skip to content

Commit a36715d

Browse files
new: obj subdirectory upload (#538)
## 📝 Description **What does this PR do and why is this change necessary?** Allow users to specify a prefix for file uploads. will upload all files to bucket as `{bucket_name}/{prefix}/{file_name}` ```bash lin obj put file_name1 bucket_name/prefix ``` ## ✔️ How to Test **How do I run the relevant unit/integration tests?** `make INTEGRATION_TEST_PATH=obj testint`
1 parent f80c885 commit a36715d

File tree

2 files changed

+71
-20
lines changed

2 files changed

+71
-20
lines changed

linodecli/plugins/obj/objects.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,14 @@ def upload_object(
8585

8686
chunk_size = 1024 * 1024 * parsed.chunk_size
8787

88+
prefix = None
89+
bucket = parsed.bucket
90+
if "/" in parsed.bucket:
91+
bucket = parsed.bucket.split("/")[0]
92+
prefix = parsed.bucket.lstrip(f"{bucket}/")
93+
8894
upload_options = {
89-
"Bucket": parsed.bucket,
95+
"Bucket": bucket,
9096
"Config": TransferConfig(multipart_chunksize=chunk_size * MB),
9197
}
9298

@@ -95,8 +101,10 @@ def upload_object(
95101

96102
for file_path in to_upload:
97103
print(f"Uploading {file_path.name}:")
104+
upload_options["Key"] = (
105+
file_path.name if not prefix else f"{prefix}/{file_path.name}"
106+
)
98107
upload_options["Filename"] = str(file_path.resolve())
99-
upload_options["Key"] = file_path.name
100108
upload_options["Callback"] = ProgressPercentage(
101109
file_path.stat().st_size, PROGRESS_BAR_WIDTH
102110
)

tests/integration/obj/test_obj_plugin.py

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import logging
23
from dataclasses import dataclass
34
from typing import Callable, Optional
@@ -6,16 +7,16 @@
67
import requests
78
from pytest import MonkeyPatch
89

9-
from linodecli.configuration.auth import _do_request
1010
from linodecli.plugins.obj import (
1111
ENV_ACCESS_KEY_NAME,
1212
ENV_SECRET_KEY_NAME,
1313
TRUNCATED_MSG,
1414
)
1515
from tests.integration.fixture_types import GetTestFilesType, GetTestFileType
16-
from tests.integration.helpers import BASE_URL, count_lines, exec_test_command
16+
from tests.integration.helpers import count_lines, exec_test_command
1717

1818
REGION = "us-southeast-1"
19+
CLI_CMD = ["linode-cli", "object-storage"]
1920
BASE_CMD = ["linode-cli", "obj", "--cluster", REGION]
2021

2122

@@ -50,27 +51,24 @@ def static_site_error():
5051

5152

5253
@pytest.fixture(scope="session")
53-
def keys(token: str):
54-
response = _do_request(
55-
BASE_URL,
56-
requests.post,
57-
"object-storage/keys",
58-
token,
59-
False,
60-
{"label": "cli-integration-test-obj-key"},
61-
)
62-
54+
def keys():
55+
response = json.loads(
56+
exec_test_command(
57+
CLI_CMD
58+
+ [
59+
"keys-create",
60+
"--label",
61+
"cli-integration-test-obj-key",
62+
"--json",
63+
],
64+
).stdout.decode()
65+
)[0]
6366
_keys = Keys(
6467
access_key=response.get("access_key"),
6568
secret_key=response.get("secret_key"),
6669
)
6770
yield _keys
68-
_do_request(
69-
BASE_URL,
70-
requests.delete,
71-
f"object-storage/keys/{response['id']}",
72-
token,
73-
)
71+
exec_test_command(CLI_CMD + ["keys-delete", str(response.get("id"))])
7472

7573

7674
def patch_keys(keys: Keys, monkeypatch: MonkeyPatch):
@@ -150,6 +148,51 @@ def test_obj_single_file_single_bucket(
150148
assert f1.read() == f2.read()
151149

152150

151+
def test_obj_single_file_single_bucket_with_prefix(
152+
create_bucket: Callable[[Optional[str]], str],
153+
generate_test_files: GetTestFilesType,
154+
keys: Keys,
155+
monkeypatch: MonkeyPatch,
156+
):
157+
patch_keys(keys, monkeypatch)
158+
file_path = generate_test_files()[0]
159+
bucket_name = create_bucket()
160+
exec_test_command(
161+
BASE_CMD + ["put", str(file_path), f"{bucket_name}/prefix"]
162+
)
163+
process = exec_test_command(BASE_CMD + ["la"])
164+
output = process.stdout.decode()
165+
166+
assert f"{bucket_name}/prefix/{file_path.name}" in output
167+
168+
file_size = file_path.stat().st_size
169+
assert str(file_size) in output
170+
171+
process = exec_test_command(BASE_CMD + ["ls"])
172+
output = process.stdout.decode()
173+
assert bucket_name in output
174+
assert file_path.name not in output
175+
176+
process = exec_test_command(BASE_CMD + ["ls", bucket_name])
177+
output = process.stdout.decode()
178+
assert bucket_name not in output
179+
assert "prefix" in output
180+
181+
downloaded_file_path = file_path.parent / f"downloaded_{file_path.name}"
182+
process = exec_test_command(
183+
BASE_CMD
184+
+ [
185+
"get",
186+
bucket_name,
187+
"prefix/" + file_path.name,
188+
str(downloaded_file_path),
189+
]
190+
)
191+
output = process.stdout.decode()
192+
with open(downloaded_file_path) as f2, open(file_path) as f1:
193+
assert f1.read() == f2.read()
194+
195+
153196
def test_multi_files_multi_bucket(
154197
create_bucket: Callable[[Optional[str]], str],
155198
generate_test_files: GetTestFilesType,

0 commit comments

Comments
 (0)