Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ ASALocalRun/
healthchecksdb

# Exclude tools folder
src/MSBuild.Sdk.SqlProj/tools/
src/MSBuild.Sdk.SqlProj/tools/*
!src/MSBuild.Sdk.SqlProj/tools/container/

# Exclude generated Dacpac's
*.dacpac
Expand Down
1 change: 1 addition & 0 deletions src/MSBuild.Sdk.SqlProj/Sdk/Sdk.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<MSBuildAllProjects Condition="'$(MSBuildToolsVersion)' != 'Current'">$(MSBuildAllProjects);$(MsBuildThisFileFullPath)</MSBuildAllProjects>
<SqlServerVersion>Sql150</SqlServerVersion>
<TargetExt>.dacpac</TargetExt>
<SqlPackageVersion>170.2.70</SqlPackageVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
70 changes: 70 additions & 0 deletions src/MSBuild.Sdk.SqlProj/Sdk/Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,74 @@
</ItemGroup>
<Message Importance="Low" Text="CopyDacpacFiles: @(CopyDacpacFiles)" />
</Target>

<!-- ===== Container publish (experimental) : configuration ===== -->
<PropertyGroup>
<!-- Dockerfile that builds the publisher image (installs sqlpackage, copies dacpacs) -->
<ContainerDockerfile>$(MSBuildThisFileDirectory)..\tools\container\sqlpackage.Dockerfile</ContainerDockerfile>

<!-- Default image name and tag. Override name & tag in CI as needed.
Lowercased and trimmed to comply with Docker naming rules. -->
<ContainerImageName Condition="'$(ContainerImageName)'==''">$([System.String]::Copy('$(MSBuildProjectName)').ToLowerInvariant().Trim())-publisher</ContainerImageName>
<ContainerImageTag Condition="'$(ContainerImageTag)'==''">$([System.String]::Copy('$(Configuration)').ToLowerInvariant().Trim())</ContainerImageTag>

<!--
Passed to the Dockerfile as build-arg SQLPACKAGE_VERSION.
Using SqlPackageVersion which is set in Sdk.props
-->
<SqlPackageVersion>$(SqlPackageVersion)</SqlPackageVersion>

<!-- Local staging area for the project dacpac and referenced dacpacs -->
<ContainerStagingDir>$(MSBuildProjectDirectory)\.container</ContainerStagingDir>
<ContainerDacpacName>$(MSBuildProjectName).dacpac</ContainerDacpacName>
<ContainerDacpacPath>$(ContainerStagingDir)\$(ContainerDacpacName)</ContainerDacpacPath>
</PropertyGroup>

<!-- ===== Container publish (experimental) : targets ===== -->

<!--
PrepareContainerDacpac
Purpose: Build and stage the project dacpac and place all referenced dacpacs next to it for convenience.
Runs after a successful Build
Guards: Fails fast if the dacpac or Dockerfile is missing.
Inputs: $(TargetPath) (built dacpac), @(IncludedDacpacReferenceFiles)
Outputs/Side effects: Recreates $(ContainerStagingDir), copies dacpac(s) into it.
-->
<Target Name="PrepareContainerDacpac"
DependsOnTargets="Build;ResolveDatabaseReferences">

<Error Condition="!Exists('$(TargetPath)')"
Text="DACPAC '$(TargetPath)' not found. Build failed." />
<Error Condition="!Exists('$(ContainerDockerfile)')"
Text="Dockerfile '$(ContainerDockerfile)' not found. Check ContainerDockerfile property." />

<!-- Reset staging area -->
<RemoveDir Directories="$(ContainerStagingDir)" Condition="Exists('$(ContainerStagingDir)')" />
<MakeDir Directories="$(ContainerStagingDir)" />

<!-- Copy the project dacpac to the known staging path -->
<Copy SourceFiles="$(TargetPath)"
DestinationFiles="$(ContainerDacpacPath)"
OverWriteReadOnlyFiles="true" />

<!-- Also stage all referenced dacpacs beside the project dacpac (useful for Docker COPY patterns) -->
<Copy SourceFiles="@(IncludedDacpacReferenceFiles)"
DestinationFolder="$(ContainerStagingDir)"
SkipUnchangedFiles="true" />
</Target>

<!--
PublishContainer
Purpose: Build the publisher container image from the staged dacpac(s).
Runs after PrepareContainerDacpac
Behavior: Executes '<runtime> build' with tag and SQLPACKAGE_VERSION build-arg.
Output: Local container image $(ContainerImageName):$(ContainerImageTag)
-->
<Target Name="PublishContainer"
DependsOnTargets="PrepareContainerDacpac">
<Message Text="Building container $(ContainerImageName):$(ContainerImageTag)"
Importance="high" />
<Exec Command='docker build -f "$(ContainerDockerfile)" -t "$(ContainerImageName):$(ContainerImageTag)" --build-arg SQLPACKAGE_VERSION=$(SqlPackageVersion) --build-arg DACPAC_NAME=$(ContainerDacpacName) "$(MSBuildProjectDirectory)"'
IgnoreExitCode="false" />
</Target>
</Project>
46 changes: 46 additions & 0 deletions src/MSBuild.Sdk.SqlProj/tools/container/sqlpackage.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Base image: ships with the SDK
ARG BASE_IMAGE=mcr.microsoft.com/dotnet/runtime:8.0
FROM ${BASE_IMAGE}
ARG SQLPACKAGE_VERSION
ARG DACPAC_NAME
ENV DACPAC_NAME=${DACPAC_NAME}

# Install sqlpackage dependencies and pull from NuGet
# - Supports native (platform zip) and dotnet tool layouts
# - Installs a stable wrapper in /usr/local/bin/sqlpackage
RUN set -eux; \
apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl unzip \
&& curl -L -o /tmp/sqlpackage.nupkg "https://globalcdn.nuget.org/packages/microsoft.sqlpackage.${SQLPACKAGE_VERSION}.nupkg" \
&& mkdir -p /opt/sqlpackage-src /opt/sqlpackage \
&& unzip -q /tmp/sqlpackage.nupkg -d /opt/sqlpackage-src \
&& inner="$(find /opt/sqlpackage-src -type f -iname '*linux-x64*.zip' | head -n1 || true)" \
&& if [ -n "$inner" ]; then \
unzip -q "$inner" -d /opt/sqlpackage; \
else \
cp -R /opt/sqlpackage-src/tools/*/any/* /opt/sqlpackage/; \
fi \
&& printf '#!/usr/bin/env bash\nset -euo pipefail\nif [ -x /opt/sqlpackage/sqlpackage ]; then exec /opt/sqlpackage/sqlpackage "$@"; elif [ -f /opt/sqlpackage/sqlpackage.dll ]; then exec dotnet /opt/sqlpackage/sqlpackage.dll "$@"; else echo "sqlpackage not found"; exit 127; fi\n' > /usr/local/bin/sqlpackage \
&& chmod +x /usr/local/bin/sqlpackage \
&& rm -rf /var/lib/apt/lists/* /tmp/* /opt/sqlpackage-src \
# Create entrypoint wrapper as root:
# - Bakes in `-Action:Publish` and `-SourceFile:/work/$DACPAC_NAME`
# - Forwards user-supplied args to sqlpackage
&& install -d /usr/local/bin \
&& printf '%s\n' \
'#!/usr/bin/env bash' \
'set -euo pipefail' \
'exec sqlpackage -Action:Publish -SourceFile:/work/"${DACPAC_NAME}" "$@"' \
| tee /usr/local/bin/docker-entrypoint >/dev/null \
&& chmod +x /usr/local/bin/docker-entrypoint

# Staging: the SDK copies all dacpacs into /work at build time
WORKDIR /work
COPY ./.container/*.dacpac /work/

# Drop privileges to a non-root user
RUN useradd -m runner
USER runner

# Exec-form ENTRYPOINT for proper signal handling and linter compliance
ENTRYPOINT ["/usr/local/bin/docker-entrypoint"]