Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Swift version management, Docker multi-stage build #41

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
5 changes: 3 additions & 2 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
.git
.gitignore

.DS_Store
**/.DS_Store

.build
.build/
.build-ubuntu
.build-ubuntu/
OnlinePlayground/**/.build
lib

Toolchains/
node_modules/

config/
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
.build/
.build-ubuntu
Toolchains/
lib

config/github.json
*.xcodeproj
.vscode/launch.json
Expand Down
148 changes: 111 additions & 37 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,50 +1,124 @@
FROM --platform=$BUILDPLATFORM swift:5.7-jammy
LABEL maintainer="[email protected]"
LABEL Description="SwiftPlayground.run docker image"
WORKDIR /swiftplayground
# ================================
# Build BE image
# ================================
FROM swift:5.7-jammy AS build-be

# Install OS updates and, if needed, sqlite3
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update \
&& apt-get -q dist-upgrade -y \
&& apt-get -q install -y \
libsqlite3-dev \
libcurl4 \
libxml2 libxml2-dev \
zlib1g-dev zlib1g \
&& rm -rf /var/lib/apt/lists/*

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve

COPY ./OnlinePlayground/Package.* ./OnlinePlayground/
RUN swift package --package-path OnlinePlayground resolve

# Copy entire repo into container
COPY . .

# Build everything, with optimizations
RUN swift build -c release -Xswiftc -static-stdlib -Xswiftc -Xclang-linker -Xswiftc -fuse-ld=lld
RUN swift build -c release --package-path OnlinePlayground -Xswiftc -static-stdlib -Xswiftc -Xclang-linker -Xswiftc -fuse-ld=lld

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/PlaygroundServer" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -name '*.resources' -exec cp -Ra {} ./ \;

# We can replace this port with what the user wants
EXPOSE 8080

# Default user if not provided
ARG bx_dev_user=root
ARG bx_dev_userid=1000
# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

SHELL ["/bin/bash", "-c"]
WORKDIR /staging/lib

# Install system level packages
RUN apt-get update
# Copy linked libraries
RUN cp -R "$(swift build --package-path /build/OnlinePlayground -c release --show-bin-path)"/. .

# Create user if not root
RUN if [ $bx_dev_user != "root" ]; then useradd -ms /bin/bash -u $bx_dev_userid $bx_dev_user; fi
# Get 3rd party dependencies
WORKDIR /vendor
RUN git clone https://github.com/kylef/swiftenv.git .swiftenv

# Bundle application source & binaries
COPY . /swiftplayground

# Install dependencies
RUN apt-get -qq -y install libz-dev curl build-essential libssl-dev libsqlite3-dev python3
# ================================
# Build FE image
# ================================
FROM node:16 AS build-fe

# NVM
ENV NODE_VERSION 16.17.0
ENV NVM_DIR /usr/local/nvm
RUN mkdir /usr/local/nvm
WORKDIR /build

RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
COPY ./package.* ./

# install node and npm
RUN source $NVM_DIR/nvm.sh \
&& nvm install $NODE_VERSION \
&& nvm alias default $NODE_VERSION \
&& nvm use default
RUN npm install --quiet

ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH:node_modules/.bin
COPY . .

# Bootstrap
RUN ./bootstrap.sh
RUN npx webpack --display errors-only --output-path /staging


# ================================
# Run image
# ================================
FROM ubuntu:jammy
LABEL maintainer="[email protected]"
LABEL Description="SwiftPlayground.run docker image"

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update \
&& apt-get -q dist-upgrade -y \
&& apt-get -q install -y \
build-essential \
curl \
libsqlite3-dev \
ca-certificates \
tzdata \
libxml2 \
#, import FoundationNetworking -> libcurl4 \
&& rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directorysd
WORKDIR /app

# Copy built executable, staged resources and dependencies from builder
COPY --from=build-be --chown=vapor:vapor /staging /app
COPY --from=build-be --chown=vapor:vapor /vendor /app
COPY --from=build-fe --chown=vapor:vapor /staging /app/Public/static/app

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Setup swiftenv environment
ENV PATH "/app/.swiftenv/bin:/app/.swiftenv/shims:$PATH"
ENV SWIFTENV_PLATFORM ubuntu20.04
RUN swiftenv install 5.7
RUN swiftenv install 5.6.3

# Let Docker bind to port 8080
EXPOSE 8080

# Command to start Swift application
# CMD export PATH="$PATH:node_modules/.bin"
# CMD export NVM_DIR="$HOME/.nvm"
# CMD $NVM_DIR/nvm.sh
CMD Toolchains/swift-5.7-RELEASE.xctoolchain/usr/bin/swift run -c release --scratch-path .build/swift-5.7-RELEASE PlaygroundServer serve --hostname 0.0.0.0
# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./PlaygroundServer"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
1 change: 0 additions & 1 deletion OnlinePlayground/OnlinePlayground-5.7-RELEASE/Sources

This file was deleted.

16 changes: 8 additions & 8 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/async-http-client.git",
"state" : {
"revision" : "03b3e7b34153299e9b4c4b5c2a6ac790a582a3ac",
"version" : "1.12.0"
"revision" : "fc510a39cff61b849bf5cdff17eb2bd6d0777b49",
"version" : "1.11.5"
}
},
{
Expand Down Expand Up @@ -149,8 +149,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "a16e2f54a25b2af217044e5168997009a505930f",
"version" : "2.42.0"
"revision" : "ece5057615d1bee848341eceafdf04ca54d60177",
"version" : "2.41.0"
}
},
{
Expand All @@ -176,17 +176,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "ba7c0d7f82affc518147ea61d240330bf7f7ea9b",
"version" : "2.22.1"
"revision" : "0265283d3539ced108b9b8287eb3328c9d85acdc",
"version" : "2.21.0"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "b6e37a0d442745760d6ed0195d8f283d3ce0414a",
"version" : "1.14.1"
"revision" : "4e02d9cf35cabfb538c96613272fb027dd0c8692",
"version" : "1.13.1"
}
},
{
Expand Down
7 changes: 5 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,21 @@ var targets:[Target] = [
.product(name: "Leaf", package: "leaf"),
.product(name: "Xgen", package: "xgen"),
.product(name: "ZIPFoundation", package: "ZIPFoundation"),
.product(name: "SwiftToolsSupport", package: "swift-tools-support-core")
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core")
]),
.target(
name: "BuildToolchainEngine",
dependencies: [
.product(name: "SwiftToolsSupport", package: "swift-tools-support-core")
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core")
]),
.executableTarget(
name: "PlaygroundServer",
dependencies: [
"App",
.product(name: "Vapor", package: "vapor")
],
swiftSettings: [
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]),
.testTarget(
name: "AppTests",
Expand Down
38 changes: 38 additions & 0 deletions Sources/App/Controllers/APIController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// File.swift
//
//
// Created by Josef Dolezal on 26.09.2022.
//

import Vapor

struct APIController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let api = routes.grouped("api")

// TODO: Move shell invocation to service
api.get("versions") { (req: Request) async throws -> Response in
let task = Process()
let pipe = Pipe()

task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", "swiftenv versions --bare"]
task.launchPath = "/bin/sh"
task.standardInput = nil
task.launch()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = String(data: data, encoding: .utf8) else {
return Response(status: .internalServerError)
}

return try await output.split(separator: "\n").map { String($0) }.encodeResponse(for: req)
}

api.webSocket("terminal") { req, websocket in
req.terminal.connected(on: websocket)
}
}
}
39 changes: 39 additions & 0 deletions Sources/App/Controllers/FrontEndController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Vapor

struct FrontEndController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.get(use: index)
routes.get("privacy-polic", use: privacyPolicy)
routes.get("letsplay", use: letsPlay)
routes.get("logout", use: logout)
routes.get("download", use: download)
}

func index(req: Request) async throws -> View {
if req.session.data["playground"] == "true" {
return try await req.view.render("playground")
} else {
return try await req.view.render("index")
}
}

func privacyPolicy(req: Request) async throws -> View {
try await req.view.render("privacy-policy")
}

func letsPlay(req: Request) async throws -> Response {
req.session.data["playground"] = "true"
return req.redirect(to: "/")
}

func logout(req: Request) async throws -> View {
req.session.destroy()
return try await req.view.render("logout")
}

func download(req: Request) async throws -> Response {
let content = try req.content.decode(DownloadRequest.self)
let playgroundPath = try generataPlayground(code: content.code)
return req.fileio.streamFile(at: playgroundPath.pathString)
}
}
33 changes: 2 additions & 31 deletions Sources/App/routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,7 @@ import Vapor

extension Application {
func routes() throws {
get { req async throws -> View in
if req.session.data["playground"] == "true" {
return try await req.view.render("playground")
} else {
return try await req.view.render("index")
}
}

get("privacy-policy") { req async throws -> View in
return try await req.view.render("privacy-policy")
}

get("letsplay") { req -> Response in
req.session.data["playground"] = "true"
return req.redirect(to: "/")
}

get("logout") { req async throws -> View in
req.session.destroy()
return try await req.view.render("logout")
}

post("download") { req async throws -> Response in
let content = try req.content.decode(DownloadRequest.self)
let playgroundPath = try generataPlayground(code: content.code)
return req.fileio.streamFile(at: playgroundPath.pathString)
}

webSocket("terminal") { req, ws in
req.terminal.connected(on: ws)
}
try register(collection: APIController())
try register(collection: FrontEndController())
}
}
Loading