Skip to content

Commit

Permalink
Use shorter build_key
Browse files Browse the repository at this point in the history
Fixes #611.
  • Loading branch information
Nikita Karetnikov committed Nov 5, 2023
1 parent 0760fa2 commit 00ee1ca
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""add build_key_version
Revision ID: 64a749e87619
Revises: b387747ca9b7
Create Date: 2023-11-05 00:59:57.132192
"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "64a749e87619"
down_revision = "b387747ca9b7"
branch_labels = None
depends_on = None


def upgrade():
op.add_column(
"build",
sa.Column(
"build_key_version", sa.Integer(), nullable=False, server_default="1"
),
)


def downgrade():
op.drop_column("build", "build_key_version")
32 changes: 27 additions & 5 deletions conda-store-server/conda_store_server/orm.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ class Build(Base):
started_on = Column(DateTime, default=None)
ended_on = Column(DateTime, default=None)
deleted_on = Column(DateTime, default=None)
build_key_version = Column(Integer, default=2, nullable=False)

@validates("build_key_version")
def validate_build_key_version(self, key, build_key_version):
if build_key_version not in [1, 2]:
raise ValueError(f"invalid build_key_version={build_key_version}")

return build_key_version

build_artifacts = relationship(
"BuildArtifact", back_populates="build", cascade="all, delete-orphan"
Expand Down Expand Up @@ -225,15 +233,29 @@ def build_key(self):
The build key should be a key that allows for the environment
build to be easily identified and found in the database.
"""
datetime_format = "%Y%m%d-%H%M%S-%f"
return f"{self.specification.sha256}-{self.scheduled_on.strftime(datetime_format)}-{self.id}-{self.specification.name}"
if self.build_key_version == 1:
datetime_format = "%Y%m%d-%H%M%S-%f"
return f"{self.specification.sha256}-{self.scheduled_on.strftime(datetime_format)}-{self.id}-{self.specification.name}"
elif self.build_key_version == 2:
hash = self.specification.sha256[:4]
timestamp = int(self.scheduled_on.timestamp())
id = self.id
name = self.specification.name[:16]
return f"{hash}-{timestamp}-{id}-{name}"
else:
raise ValueError(f"invalid build key version: {self.build_key_version}")

@staticmethod
def parse_build_key(key):
parts = key.split("-")
if len(parts) < 5:
return None
return int(parts[4]) # build_id
# Note: cannot rely on the number of dashes to differentiate between
# versions because name can contain dashes. Instead, this relies on the
# hash size to infer the format. The name is the last field, so indexing
# to find the id is okay.
if key[4] == "-": # v2
return int(parts[2]) # build_id
else: # v1
return int(parts[4]) # build_id

@property
def log_key(self):
Expand Down
43 changes: 33 additions & 10 deletions conda-store-server/tests/test_actions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import datetime
import pathlib
import re
import sys
Expand Down Expand Up @@ -224,17 +225,20 @@ def test_add_lockfile_packages(


@pytest.mark.parametrize(
"is_legacy_build",
"is_legacy_build, build_key_version",
[
False,
True,
(False, 0), # invalid
(False, 1), # long (legacy)
(False, 2), # short (default)
(True, 1), # build_key_version doesn't matter because there's no lockfile
],
)
def test_api_get_build_lockfile(
request, conda_store, db, simple_specification_with_pip, conda_prefix, is_legacy_build
request, conda_store, db, simple_specification_with_pip, conda_prefix, is_legacy_build, build_key_version
):
# initializes data needed to get the lockfile
specification = simple_specification_with_pip
specification.name = "this-is-a-long-environment-name-to-test-truncation"
namespace = "pytest"

class MyAuthentication(DummyAuthentication):
Expand All @@ -250,6 +254,16 @@ def authorize_request(self, *args, **kwargs):
)
db.commit()
build = api.get_build(db, build_id=build_id)
# makes this more visible in the lockfile
build_id = 12345678
build.id = build_id
if build_key_version is 0: # invalid
with pytest.raises(ValueError, match=r"invalid build_key_version=0"):
build.build_key_version = build_key_version
return # invalid, nothing more to test
build.build_key_version = build_key_version
# makes sure the timestamp in build_key is always the same
build.scheduled_on = datetime.datetime(2023, 11, 5, 3, 54, 10, 510258)
environment = api.get_environment(db, namespace=namespace)

# adds packages (returned in the lockfile)
Expand Down Expand Up @@ -293,12 +307,21 @@ def authorize_request(self, *args, **kwargs):
assert re.match("http.*//.*tar.bz2#.*", lines[2]) is not None
else:
# new build: redirects to lockfile generated by conda-lock
lockfile_url_pattern = (
"lockfile/"
"89e5a99aa094689b7aafc66c47987fa186e08f9d619a02ab1a469d0759da3b8b-"
".*-test.yml"
)
def lockfile_url(build_key):
return f"lockfile/{build_key}.yml"
if build_key_version is 1:
build_key = (
"7a0c9317530e3732a25f22c2017a881dcd6f84ff85c96a609210168deea280ef-"
"20231105-035410-510258-12345678-this-is-a-long-environment-name-to-test-truncation"
)
elif build_key_version is 2:
build_key = "7a0c-1699156450-12345678-this-is-a-long-e"
else:
raise ValueError(f"unexpected build_key_version: {build_key_version}")
assert type(res) is RedirectResponse
assert key == res.headers['location']
assert re.match(lockfile_url_pattern, res.headers['location']) is not None
assert build.build_key == build_key
assert build.parse_build_key(build_key) == 12345678
assert lockfile_url(build_key) == build.conda_lock_key
assert lockfile_url(build_key) == res.headers['location']
assert res.status_code == 307

0 comments on commit 00ee1ca

Please sign in to comment.