fix(deploy): fix render-worker build context + migration 040 idempotency
- docker-compose.yml: change render-worker build context from ./render-worker to . (project root) so pyproject.toml is accessible; update dockerfile path - render-worker/Dockerfile: update COPY paths for new build context; install Python 3.11 via deadsnakes PPA (Ubuntu 22.04 ships 3.10 which fails the >=3.11 requirement in pyproject.toml) - 040_media_assets.py: rewrite upgrade() with raw idempotent SQL (CREATE TYPE inside DO $$ EXCEPTION WHEN duplicate_object $$; CREATE TABLE IF NOT EXISTS; CREATE INDEX IF NOT EXISTS) to handle pre-existing enum from partial runs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,56 +14,58 @@ depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Create enum type
|
||||
op.execute(
|
||||
"CREATE TYPE media_asset_type AS ENUM ("
|
||||
"'thumbnail','still','turntable','stl_low','stl_high',"
|
||||
"'gltf_geometry','gltf_production','blend_production')"
|
||||
)
|
||||
# Use raw SQL throughout for full idempotency (enum may exist from a partial run)
|
||||
op.execute("""
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE media_asset_type AS ENUM (
|
||||
'thumbnail','still','turntable','stl_low','stl_high',
|
||||
'gltf_geometry','gltf_production','blend_production'
|
||||
);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
""")
|
||||
|
||||
op.create_table(
|
||||
'media_assets',
|
||||
sa.Column('id', UUID(as_uuid=True), primary_key=True, server_default=sa.text('gen_random_uuid()')),
|
||||
sa.Column('tenant_id', UUID(as_uuid=True), sa.ForeignKey('tenants.id', ondelete='CASCADE'), nullable=True),
|
||||
sa.Column('product_id', UUID(as_uuid=True), sa.ForeignKey('products.id', ondelete='CASCADE'), nullable=True),
|
||||
sa.Column('cad_file_id', UUID(as_uuid=True), sa.ForeignKey('cad_files.id', ondelete='SET NULL'), nullable=True),
|
||||
sa.Column('order_line_id', UUID(as_uuid=True), sa.ForeignKey('order_lines.id', ondelete='SET NULL'), nullable=True),
|
||||
sa.Column('workflow_run_id', UUID(as_uuid=True), sa.ForeignKey('workflow_runs.id', ondelete='SET NULL'), nullable=True),
|
||||
sa.Column(
|
||||
'asset_type',
|
||||
sa.Enum(
|
||||
'thumbnail', 'still', 'turntable',
|
||||
'stl_low', 'stl_high',
|
||||
'gltf_geometry', 'gltf_production', 'blend_production',
|
||||
name='media_asset_type',
|
||||
),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column('storage_key', sa.Text, nullable=False),
|
||||
sa.Column('file_size_bytes', sa.BigInteger, nullable=True),
|
||||
sa.Column('mime_type', sa.String(100), nullable=True),
|
||||
sa.Column('width', sa.Integer, nullable=True),
|
||||
sa.Column('height', sa.Integer, nullable=True),
|
||||
sa.Column('duration_s', sa.Float, nullable=True),
|
||||
sa.Column('render_config', JSONB, nullable=True),
|
||||
sa.Column('is_archived', sa.Boolean, nullable=False, server_default='false'),
|
||||
sa.Column('created_at', sa.DateTime, nullable=False, server_default=sa.text('NOW()')),
|
||||
)
|
||||
op.create_index('ix_media_assets_product', 'media_assets', ['product_id'])
|
||||
op.create_index('ix_media_assets_tenant', 'media_assets', ['tenant_id'])
|
||||
op.create_index('ix_media_assets_order_line', 'media_assets', ['order_line_id'])
|
||||
op.create_index('ix_media_assets_asset_type', 'media_assets', ['asset_type'])
|
||||
op.execute("""
|
||||
CREATE TABLE IF NOT EXISTS media_assets (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
|
||||
product_id UUID REFERENCES products(id) ON DELETE CASCADE,
|
||||
cad_file_id UUID REFERENCES cad_files(id) ON DELETE SET NULL,
|
||||
order_line_id UUID REFERENCES order_lines(id) ON DELETE SET NULL,
|
||||
workflow_run_id UUID REFERENCES workflow_runs(id) ON DELETE SET NULL,
|
||||
asset_type media_asset_type NOT NULL,
|
||||
storage_key TEXT NOT NULL,
|
||||
file_size_bytes BIGINT,
|
||||
mime_type VARCHAR(100),
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
duration_s FLOAT,
|
||||
render_config JSONB,
|
||||
is_archived BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
)
|
||||
""")
|
||||
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_media_assets_product ON media_assets (product_id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_media_assets_tenant ON media_assets (tenant_id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_media_assets_order_line ON media_assets (order_line_id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_media_assets_asset_type ON media_assets (asset_type)")
|
||||
|
||||
# RLS
|
||||
op.execute("ALTER TABLE media_assets ENABLE ROW LEVEL SECURITY")
|
||||
op.execute(
|
||||
"""CREATE POLICY tenant_isolation ON media_assets
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid)"""
|
||||
)
|
||||
op.execute(
|
||||
"""CREATE POLICY admin_bypass ON media_assets
|
||||
USING (current_setting('app.current_tenant_id', true) = 'bypass')"""
|
||||
)
|
||||
op.execute("""
|
||||
DO $$ BEGIN
|
||||
CREATE POLICY tenant_isolation ON media_assets
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
""")
|
||||
op.execute("""
|
||||
DO $$ BEGIN
|
||||
CREATE POLICY admin_bypass ON media_assets
|
||||
USING (current_setting('app.current_tenant_id', true) = 'bypass');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
""")
|
||||
|
||||
|
||||
def downgrade():
|
||||
|
||||
+2
-2
@@ -114,8 +114,8 @@ services:
|
||||
|
||||
render-worker:
|
||||
build:
|
||||
context: ./render-worker
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
dockerfile: render-worker/Dockerfile
|
||||
args:
|
||||
- BLENDER_VERSION=${BLENDER_VERSION:-5.0.1}
|
||||
environment:
|
||||
|
||||
@@ -11,9 +11,17 @@ ENV PYOPENGL_PLATFORM=osmesa
|
||||
ENV VTK_DEFAULT_EGL=0
|
||||
|
||||
# Runtime libraries for cadquery/OCC + Blender 5.x headless
|
||||
# Also installs Python 3.11 via deadsnakes PPA (Ubuntu 22.04 ships 3.10)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
software-properties-common gnupg gpg-agent \
|
||||
&& add-apt-repository ppa:deadsnakes/ppa -y \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||
python3.11 \
|
||||
python3.11-dev \
|
||||
python3.11-distutils \
|
||||
python3-pip \
|
||||
python3-dev \
|
||||
curl \
|
||||
libpq-dev \
|
||||
gcc \
|
||||
libxrender1 \
|
||||
@@ -40,21 +48,26 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Use Python 3.11 as the default pip target
|
||||
RUN curl -sS https://bootstrap.pypa.io/get-pip.py | python3.11 \
|
||||
&& update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 \
|
||||
&& update-alternatives --install /usr/bin/pip3 pip3 /usr/local/bin/pip3.11 1
|
||||
|
||||
# Install backend Python dependencies (includes celery, sqlalchemy, fastapi, etc.)
|
||||
COPY pyproject.toml .
|
||||
COPY backend/pyproject.toml .
|
||||
RUN pip3 install --no-cache-dir -e .
|
||||
|
||||
# Install cadquery (heavy — installed after backend deps for better layer caching)
|
||||
RUN pip3 install --no-cache-dir "cadquery>=2.4.0"
|
||||
|
||||
# Copy render scripts
|
||||
COPY scripts/ /render-scripts/
|
||||
COPY render-worker/scripts/ /render-scripts/
|
||||
|
||||
# Version check script — fails fast if Blender < 5.0.1
|
||||
COPY check_version.py /check_version.py
|
||||
COPY render-worker/check_version.py /check_version.py
|
||||
|
||||
# Copy app code (overridden by volume mount in docker-compose)
|
||||
COPY . .
|
||||
# Copy backend app code (overridden by volume mount in docker-compose)
|
||||
COPY backend/ .
|
||||
|
||||
# Verify Blender version at build time if binary is available
|
||||
# (skipped during build since /opt/blender is a host mount)
|
||||
|
||||
Reference in New Issue
Block a user