Skip to content

Commit 5371582

Browse files
Merge pull request #559 from Backblaze/fix-account-info-bw-compat
Address account info bw compatibility issue caused by multi-bucket keys
2 parents 2b5447f + d6c7ef9 commit 5371582

File tree

7 files changed

+146
-74
lines changed

7 files changed

+146
-74
lines changed

.github/workflows/cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: Display Python version
3030
run: python -c "import sys; print(sys.version)"
3131
- name: Install dependencies
32-
run: python -m pip install --upgrade nox pdm
32+
run: python -m pip install --upgrade nox pdm==2.26.2
3333
- name: Build the distribution
3434
id: build
3535
run: nox -vs build

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
python-version: ${{ env.PYTHON_DEFAULT_VERSION }}
2727
cache: "pip"
2828
- name: Install dependencies
29-
run: python -m pip install --upgrade nox pdm
29+
run: python -m pip install --upgrade nox pdm==2.26.2
3030
- name: Run linters
3131
run: nox -vs lint
3232
- name: Validate new changelog entries
@@ -49,7 +49,7 @@ jobs:
4949
python-version: ${{ env.PYTHON_DEFAULT_VERSION }}
5050
cache: "pip"
5151
- name: Install dependencies
52-
run: python -m pip install --upgrade nox pdm
52+
run: python -m pip install --upgrade nox pdm==2.26.2
5353
- name: Build the distribution
5454
run: nox -vs build
5555
cleanup_buckets:
@@ -72,7 +72,7 @@ jobs:
7272
cache: "pip"
7373
- name: Install dependencies
7474
if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' }} # TODO: skip this whole job instead
75-
run: python -m pip install --upgrade nox pdm
75+
run: python -m pip install --upgrade nox pdm==2.26.2
7676
- name: Find and remove old buckets
7777
if: ${{ env.B2_TEST_APPLICATION_KEY != '' && env.B2_TEST_APPLICATION_KEY_ID != '' }} # TODO: skip this whole job instead
7878
run: nox -vs cleanup_old_buckets
@@ -109,7 +109,7 @@ jobs:
109109
python-version: ${{ matrix.python-version }}
110110
cache: "pip"
111111
- name: Install dependencies
112-
run: python -m pip install --upgrade nox pdm
112+
run: python -m pip install --upgrade nox pdm==2.26.2
113113
- name: Run unit tests
114114
run: nox -vs unit -- -v
115115
- name: Run integration tests
@@ -134,6 +134,6 @@ jobs:
134134
run: |
135135
sudo apt-get update -y
136136
sudo apt-get install -y graphviz plantuml
137-
python -m pip install --upgrade nox pdm
137+
python -m pip install --upgrade nox pdm==2.26.2
138138
- name: Build the docs
139139
run: nox --non-interactive -vs doc

b2sdk/_internal/account_info/sqlite_account_info.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def _create_tables(self, conn, last_upgrade_to_run):
280280
"""
281281
)
282282
# By default, we run all the upgrades
283-
last_upgrade_to_run = 5 if last_upgrade_to_run is None else last_upgrade_to_run
283+
last_upgrade_to_run = 6 if last_upgrade_to_run is None else last_upgrade_to_run
284284
# Add the 'allowed' column if it hasn't been yet.
285285
if 1 <= last_upgrade_to_run:
286286
self._ensure_update(1, ['ALTER TABLE account ADD COLUMN allowed TEXT;'])
@@ -385,13 +385,17 @@ def _create_tables(self, conn, last_upgrade_to_run):
385385
)
386386

387387
if 5 <= last_upgrade_to_run:
388-
self._migrate_allowed_to_multi_bucket()
388+
self._migrate_allowed_to_multi_bucket(version=5)
389389

390-
def _migrate_allowed_to_multi_bucket(self):
390+
# There were reported cases of users with db schema upgraded to version 5 and still containing old single-bucket style allowed dict. This was possible because users of sdk apiver <= v2 still received old-styled allowed dicts (from v3 remote API) and there was no proper transformation mechanism when setting the auth data. The bug has now been addressed, but we need an additional migration to address aforementioned cases.
391+
if 6 <= last_upgrade_to_run:
392+
self._migrate_allowed_to_multi_bucket(version=6)
393+
394+
def _migrate_allowed_to_multi_bucket(self, *, version: int):
391395
"""
392396
Migrate existing allowed json dict to a new multi-bucket keys format
393397
"""
394-
if self._get_update_count(5) > 0:
398+
if self._get_update_count(version) > 0:
395399
return
396400

397401
try:
@@ -400,11 +404,15 @@ def _migrate_allowed_to_multi_bucket(self):
400404
allowed_json = None
401405

402406
if allowed_json is None:
403-
self._perform_update(5, [])
407+
self._perform_update(version, [])
404408
return
405409

406410
allowed = json.loads(allowed_json)
407411

412+
if 'buckets' in allowed:
413+
self._perform_update(version, [])
414+
return
415+
408416
bucket_id = allowed.pop('bucketId')
409417
bucket_name = allowed.pop('bucketName')
410418

@@ -416,7 +424,7 @@ def _migrate_allowed_to_multi_bucket(self):
416424
allowed_text = json.dumps(allowed)
417425
stmt = f"UPDATE account SET allowed = ('{allowed_text}');"
418426

419-
self._perform_update(5, [stmt])
427+
self._perform_update(version, [stmt])
420428

421429
def _ensure_update(self, update_number, update_commands: list[str]):
422430
"""
@@ -469,7 +477,6 @@ def _set_auth_data(
469477
allowed,
470478
application_key_id,
471479
):
472-
assert self.allowed_is_valid(allowed)
473480
with self._get_connection() as conn:
474481
conn.execute('DELETE FROM account;')
475482
conn.execute('DELETE FROM bucket;')

b2sdk/v2/account_info.py

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
######################################################################
1010
from __future__ import annotations
1111

12-
import json
1312

13+
from copy import copy
1414
from b2sdk import v3
1515
from .exception import MissingAccountData
1616

@@ -33,49 +33,22 @@ def allowed_is_valid(cls, allowed):
3333
and ('namePrefix' in allowed)
3434
)
3535

36+
@classmethod
37+
def _convert_allowed_single_to_multi_bucket(cls, allowed):
38+
new = copy(allowed)
3639

37-
class AbstractAccountInfo(_OldAllowedMixin, v3.AbstractAccountInfo):
38-
def list_bucket_names_ids(self):
39-
return [] # Removed @abstractmethod decorator
40-
41-
42-
class UrlPoolAccountInfo(_OldAllowedMixin, v3.UrlPoolAccountInfo):
43-
pass
40+
bucket_id = new.pop('bucketId')
41+
bucket_name = new.pop('bucketName')
4442

43+
if bucket_id is not None:
44+
new['buckets'] = [{'id': bucket_id, 'name': bucket_name}]
45+
else:
46+
new['buckets'] = None
4547

46-
class InMemoryAccountInfo(_OldAllowedMixin, v3.InMemoryAccountInfo):
47-
pass
48+
return new
4849

49-
50-
class SqliteAccountInfo(_OldAllowedMixin, v3.SqliteAccountInfo):
5150
def get_allowed(self):
52-
"""
53-
Return 'allowed' dictionary info.
54-
Example:
55-
56-
.. code-block:: python
57-
58-
{
59-
"bucketId": null,
60-
"bucketName": null,
61-
"capabilities": [
62-
"listKeys",
63-
"writeKeys"
64-
],
65-
"namePrefix": null
66-
}
67-
68-
The 'allowed' column was not in the original schema, so it may be NULL.
69-
70-
:rtype: dict
71-
"""
72-
allowed_json = self._get_account_info_or_raise('allowed')
73-
if allowed_json is None:
74-
return self.DEFAULT_ALLOWED
75-
76-
allowed = json.loads(allowed_json)
77-
78-
# convert a multi-bucket key to a single bucket
51+
allowed = super().get_allowed()
7952

8053
if 'buckets' in allowed:
8154
buckets = allowed.pop('buckets')
@@ -89,6 +62,53 @@ def get_allowed(self):
8962

9063
return allowed
9164

65+
def _set_auth_data(
66+
self,
67+
account_id,
68+
auth_token,
69+
api_url,
70+
download_url,
71+
recommended_part_size,
72+
absolute_minimum_part_size,
73+
application_key,
74+
realm,
75+
s3_api_url,
76+
allowed,
77+
application_key_id,
78+
):
79+
new_allowed = self._convert_allowed_single_to_multi_bucket(allowed)
80+
81+
super()._set_auth_data(
82+
account_id,
83+
auth_token,
84+
api_url,
85+
download_url,
86+
recommended_part_size,
87+
absolute_minimum_part_size,
88+
application_key,
89+
realm,
90+
s3_api_url,
91+
new_allowed,
92+
application_key_id,
93+
)
94+
95+
96+
class AbstractAccountInfo(_OldAllowedMixin, v3.AbstractAccountInfo):
97+
def list_bucket_names_ids(self):
98+
return [] # Removed @abstractmethod decorator
99+
100+
101+
class UrlPoolAccountInfo(_OldAllowedMixin, v3.UrlPoolAccountInfo):
102+
pass
103+
104+
105+
class InMemoryAccountInfo(_OldAllowedMixin, v3.InMemoryAccountInfo):
106+
pass
107+
108+
109+
class SqliteAccountInfo(_OldAllowedMixin, v3.SqliteAccountInfo):
110+
pass
111+
92112

93113
class StubAccountInfo(_OldAllowedMixin, v3.StubAccountInfo):
94114
pass
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Address backwards compatibility issue for sqlite account info caused by the migration of schema to a new multi-bucket format.

test/unit/account_info/fixtures.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,9 @@ def in_memory_account_info(in_memory_account_info_factory):
6969

7070
@pytest.fixture
7171
def sqlite_account_info_factory(tmpdir):
72-
def get_account_info(file_name=None, schema_0=False):
72+
def get_account_info(file_name=None, last_upgrade_to_run: int | None = None):
7373
if file_name is None:
7474
file_name = str(tmpdir.join('b2_account_info'))
75-
if schema_0:
76-
last_upgrade_to_run = 0
77-
else:
78-
last_upgrade_to_run = None
7975
return SqliteAccountInfo(file_name, last_upgrade_to_run)
8076

8177
return get_account_info

0 commit comments

Comments
 (0)