Skip to content

Commit 51d81f5

Browse files
authored
Merge pull request #139 from lsst-sqre/tickets/DM-54426
DM-54426: Implement build uploads
2 parents 2225974 + 04a2d08 commit 51d81f5

File tree

94 files changed

+7643
-364
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+7643
-364
lines changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This repository was originally created for LTD Keeper. During the codebase migra
1414
- **Lint a specific file**: `uv run --only-group=lint ruff check path/to/file.py`
1515
- **Format a specific file**: `uv run --only-group=lint ruff format path/to/file.py`
1616
- **Type checking**: `uv run --only-group=nox nox -s typing`
17-
- **Server tests**: `uv run --only-group=nox nox -s test`
17+
- **Server tests**: `TC_HOST=localhost TESTCONTAINERS_RYUK_DISABLED=true uv run --only-group=nox nox -s test`
1818
- **Client tests**: `uv run --only-group=nox nox -s client_test`
1919
- **Running specific tests**: pass pytest args after `--`, e.g. `uv run --only-group=nox nox -s test -- tests/path/to/test_file.py`
2020

alembic.ini

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,36 @@ ruff_check.options = check --fix REVISION_SCRIPT_FILENAME
1212
ruff_format.type = exec
1313
ruff_format.executable = ruff
1414
ruff_format.options = format REVISION_SCRIPT_FILENAME
15+
16+
[loggers]
17+
keys = root,sqlalchemy,alembic
18+
19+
[handlers]
20+
keys = console
21+
22+
[formatters]
23+
keys = generic
24+
25+
[logger_root]
26+
level = WARN
27+
handlers = console
28+
29+
[logger_sqlalchemy]
30+
level = WARN
31+
handlers =
32+
qualname = sqlalchemy.engine
33+
34+
[logger_alembic]
35+
level = INFO
36+
handlers =
37+
qualname = alembic
38+
39+
[handler_console]
40+
class = StreamHandler
41+
args = (sys.stderr,)
42+
level = NOTSET
43+
formatter = generic
44+
45+
[formatter_generic]
46+
format = %(levelname)-5.5s [%(name)s] %(message)s
47+
datefmt = %H:%M:%S
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Add organization_credentials table and credential label columns.
2+
3+
Revision ID: e4f5a6b7c8d9
4+
Revises: d3e4f5a6b7c8
5+
Create Date: 2026-03-18 00:00:00.000000+00:00
6+
"""
7+
8+
import sqlalchemy as sa
9+
10+
from alembic import op
11+
12+
# revision identifiers, used by Alembic.
13+
revision: str = "e4f5a6b7c8d9"
14+
down_revision: str | None = "d3e4f5a6b7c8"
15+
branch_labels: str | None = None
16+
depends_on: str | None = None
17+
18+
19+
def upgrade() -> None:
20+
op.create_table(
21+
"organization_credentials",
22+
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
23+
sa.Column(
24+
"organization_id",
25+
sa.Integer,
26+
sa.ForeignKey("organizations.id", ondelete="CASCADE"),
27+
nullable=False,
28+
),
29+
sa.Column("label", sa.String(128), nullable=False),
30+
sa.Column("service_type", sa.String(32), nullable=False),
31+
sa.Column("encrypted_credential", sa.LargeBinary, nullable=False),
32+
sa.Column(
33+
"date_created",
34+
sa.DateTime(timezone=True),
35+
server_default=sa.func.now(),
36+
nullable=False,
37+
),
38+
sa.Column(
39+
"date_updated",
40+
sa.DateTime(timezone=True),
41+
server_default=sa.func.now(),
42+
nullable=False,
43+
),
44+
sa.UniqueConstraint(
45+
"organization_id", "label", name="uq_org_credential_label"
46+
),
47+
)
48+
49+
op.add_column(
50+
"organizations",
51+
sa.Column(
52+
"publishing_credential_label", sa.String(128), nullable=True
53+
),
54+
)
55+
op.add_column(
56+
"organizations",
57+
sa.Column("staging_credential_label", sa.String(128), nullable=True),
58+
)
59+
60+
61+
def downgrade() -> None:
62+
op.drop_column("organizations", "staging_credential_label")
63+
op.drop_column("organizations", "publishing_credential_label")
64+
op.drop_table("organization_credentials")
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"""Add three-layer infrastructure model (credentials, services, slots).
2+
3+
Replaces the single organization_credentials table (which bundled
4+
secrets and config) with:
5+
- organization_credentials: provider-level encrypted secrets only
6+
- organization_services: non-secret config + credential reference
7+
- Organization slot columns: publishing_store_label, staging_store_label,
8+
cdn_service_label, dns_service_label
9+
10+
Revision ID: f4a5b6c7d8e9
11+
Revises: e4f5a6b7c8d9
12+
Create Date: 2026-03-19 00:00:00.000000+00:00
13+
"""
14+
15+
import sqlalchemy as sa
16+
from sqlalchemy.dialects import postgresql
17+
18+
from alembic import op
19+
20+
# revision identifiers, used by Alembic.
21+
revision: str = "f4a5b6c7d8e9"
22+
down_revision: str | None = "e4f5a6b7c8d9"
23+
branch_labels: str | None = None
24+
depends_on: str | None = None
25+
26+
27+
def upgrade() -> None:
28+
# --- Recreate organization_credentials with new schema ---
29+
# Drop old table (pre-production, no data to migrate)
30+
op.drop_table("organization_credentials")
31+
32+
# Create new credentials table with provider-level auth
33+
op.create_table(
34+
"organization_credentials",
35+
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
36+
sa.Column(
37+
"organization_id",
38+
sa.Integer,
39+
sa.ForeignKey("organizations.id", ondelete="CASCADE"),
40+
nullable=False,
41+
),
42+
sa.Column("label", sa.String(128), nullable=False),
43+
sa.Column("provider", sa.String(32), nullable=False),
44+
sa.Column("encrypted_credentials", sa.LargeBinary, nullable=False),
45+
sa.Column(
46+
"date_created",
47+
sa.DateTime(timezone=True),
48+
server_default=sa.func.now(),
49+
nullable=False,
50+
),
51+
sa.Column(
52+
"date_updated",
53+
sa.DateTime(timezone=True),
54+
server_default=sa.func.now(),
55+
nullable=False,
56+
),
57+
sa.UniqueConstraint(
58+
"organization_id", "label", name="uq_org_credential_label"
59+
),
60+
)
61+
62+
# --- Create organization_services table ---
63+
op.create_table(
64+
"organization_services",
65+
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
66+
sa.Column(
67+
"organization_id",
68+
sa.Integer,
69+
sa.ForeignKey("organizations.id", ondelete="CASCADE"),
70+
nullable=False,
71+
),
72+
sa.Column("label", sa.String(128), nullable=False),
73+
sa.Column("category", sa.String(32), nullable=False),
74+
sa.Column("provider", sa.String(32), nullable=False),
75+
sa.Column(
76+
"config",
77+
postgresql.JSONB,
78+
nullable=False,
79+
server_default="{}",
80+
),
81+
sa.Column("credential_label", sa.String(128), nullable=False),
82+
sa.Column(
83+
"date_created",
84+
sa.DateTime(timezone=True),
85+
server_default=sa.func.now(),
86+
nullable=False,
87+
),
88+
sa.Column(
89+
"date_updated",
90+
sa.DateTime(timezone=True),
91+
server_default=sa.func.now(),
92+
nullable=False,
93+
),
94+
sa.UniqueConstraint(
95+
"organization_id", "label", name="uq_org_service_label"
96+
),
97+
)
98+
99+
# --- Update organization slot columns ---
100+
# Remove old credential label columns
101+
op.drop_column("organizations", "publishing_credential_label")
102+
op.drop_column("organizations", "staging_credential_label")
103+
104+
# Add new service slot columns
105+
op.add_column(
106+
"organizations",
107+
sa.Column("publishing_store_label", sa.String(128), nullable=True),
108+
)
109+
op.add_column(
110+
"organizations",
111+
sa.Column("staging_store_label", sa.String(128), nullable=True),
112+
)
113+
op.add_column(
114+
"organizations",
115+
sa.Column("cdn_service_label", sa.String(128), nullable=True),
116+
)
117+
op.add_column(
118+
"organizations",
119+
sa.Column("dns_service_label", sa.String(128), nullable=True),
120+
)
121+
122+
123+
def downgrade() -> None:
124+
# Remove new slot columns
125+
op.drop_column("organizations", "dns_service_label")
126+
op.drop_column("organizations", "cdn_service_label")
127+
op.drop_column("organizations", "staging_store_label")
128+
op.drop_column("organizations", "publishing_store_label")
129+
130+
# Restore old credential label columns
131+
op.add_column(
132+
"organizations",
133+
sa.Column(
134+
"publishing_credential_label", sa.String(128), nullable=True
135+
),
136+
)
137+
op.add_column(
138+
"organizations",
139+
sa.Column("staging_credential_label", sa.String(128), nullable=True),
140+
)
141+
142+
# Drop services table
143+
op.drop_table("organization_services")
144+
145+
# Recreate old credentials table
146+
op.drop_table("organization_credentials")
147+
op.create_table(
148+
"organization_credentials",
149+
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
150+
sa.Column(
151+
"organization_id",
152+
sa.Integer,
153+
sa.ForeignKey("organizations.id", ondelete="CASCADE"),
154+
nullable=False,
155+
),
156+
sa.Column("label", sa.String(128), nullable=False),
157+
sa.Column("service_type", sa.String(32), nullable=False),
158+
sa.Column("encrypted_credential", sa.LargeBinary, nullable=False),
159+
sa.Column(
160+
"date_created",
161+
sa.DateTime(timezone=True),
162+
server_default=sa.func.now(),
163+
nullable=False,
164+
),
165+
sa.Column(
166+
"date_updated",
167+
sa.DateTime(timezone=True),
168+
server_default=sa.func.now(),
169+
nullable=False,
170+
),
171+
sa.UniqueConstraint(
172+
"organization_id", "label", name="uq_org_credential_label"
173+
),
174+
)

client/pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ license = "MIT"
55
requires-python = ">=3.12"
66
dynamic = ["version"]
77
dependencies = [
8-
"base32-lib>=1",
98
"click>=8",
109
"httpx>=0.25",
1110
"pydantic>=2.5",
1211
]
1312

13+
[project.scripts]
14+
docverse = "docverse.client._cli:main"
15+
1416
[build-system]
1517
requires = [
1618
"setuptools>=75",

client/src/docverse/client/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22

33
from importlib.metadata import PackageNotFoundError, version
44

5-
__all__ = ["__version__"]
5+
from ._client import DocverseClient
6+
from ._exceptions import BuildProcessingError, DocverseClientError
7+
from ._tar import create_tarball
8+
9+
__all__ = [
10+
"BuildProcessingError",
11+
"DocverseClient",
12+
"DocverseClientError",
13+
"__version__",
14+
"create_tarball",
15+
]
616

717
try:
818
__version__ = version("docverse-client")

0 commit comments

Comments
 (0)