Skip to content

Commit

Permalink
feat: add special case for python fastapi (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredLunde authored Aug 21, 2024
1 parent fbd43d3 commit d886a8f
Show file tree
Hide file tree
Showing 25 changed files with 407 additions and 92 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ In order of precedence:

#### Detected Files
- `requirements.txt`
- `uv.lock`
- `poetry.lock`
- `Pipefile.lock`
- `pyproject.toml`
Expand Down Expand Up @@ -447,14 +448,16 @@ In order of precedence:
- `START_CMD` - The command to start the project (default: detected from source code)

#### Install Command
- If Poetry: `poetry install --no-dev --no-interactive --no-ansi`
- If Pipenv: `PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy`
- If PDM: `pdm install --prod`
- If Poetry: `pip install poetry && poetry install --no-dev --no-ansi --no-root`
- If Pipenv: `pipenv install --dev --system --deploy`
- If uv: `pip install uv && uv sync --python-preference=only-system --no-cache --no-dev`
- If PDM: `pip install pdm && pdm install --prod`
- If `pyproject.toml` exists: `pip install --upgrade build setuptools && pip install .`
- If `requirements.txt` exists: `pip install -r requirements.txt`

#### Start Command
- If Django is detected: `python manage.py runserver 0.0.0.0:${PORT}`
- If FastAPI is detected: `fastapi run [main.py, app.py, application.py, app/main.py, app/application.py, app/__init__.py] --port ${PORT}`
- If `pyproject.toml` exists: `python -m ${projectName}`
- Otherwise: `python [main.py, app.py, application.py, app/main.py, app/application.py, app/__init__.py]`

Expand Down
16 changes: 11 additions & 5 deletions runtime/bun.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"log/slog"
"maps"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -37,7 +38,7 @@ func (d *Bun) Match(path string) bool {
return false
}

func (d *Bun) GenerateDockerfile(path string) ([]byte, error) {
func (d *Bun) GenerateDockerfile(path string, data ...map[string]string) ([]byte, error) {
tmpl, err := template.New("Dockerfile").Parse(bunTemplate)
if err != nil {
return nil, fmt.Errorf("Failed to parse template")
Expand Down Expand Up @@ -125,11 +126,15 @@ func (d *Bun) GenerateDockerfile(path string) ([]byte, error) {
}

var buf bytes.Buffer
if err := tmpl.Option("missingkey=zero").Execute(&buf, map[string]string{
templateData := map[string]string{
"Version": *version,
"BuildCMD": buildCMD,
"StartCMD": startCMD,
}); err != nil {
}
if len(data) > 0 {
maps.Copy(templateData, data[0])
}
if err := tmpl.Option("missingkey=zero").Execute(&buf, templateData); err != nil {
return nil, fmt.Errorf("Failed to execute template")
}

Expand All @@ -145,15 +150,15 @@ FROM base AS deps
WORKDIR /app
COPY package.json bun.lockb ./
ARG INSTALL_CMD="bun install"
RUN if [ ! -z "${INSTALL_CMD}" ]; then sh -c "$INSTALL_CMD"; fi
RUN {{.InstallMounts}}if [ ! -z "${INSTALL_CMD}" ]; then sh -c "$INSTALL_CMD"; fi
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules* ./node_modules
COPY . .
ENV NODE_ENV=production
ARG BUILD_CMD={{.BuildCMD}}
RUN if [ ! -z "${BUILD_CMD}" ]; then sh -c "$BUILD_CMD"; fi
RUN {{.BuildMounts}}if [ ! -z "${BUILD_CMD}" ]; then sh -c "$BUILD_CMD"; fi
FROM ${BUILDER}:${VERSION}-slim AS runtime
WORKDIR /app
Expand All @@ -168,6 +173,7 @@ COPY --chown=nonroot:nonroot --from=builder /app .
USER nonroot:nonroot
ENV PORT=8080
EXPOSE ${PORT}
ENV NODE_ENV=production
ARG START_CMD={{.StartCMD}}
ENV START_CMD=${START_CMD}
Expand Down
17 changes: 16 additions & 1 deletion runtime/bun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func TestBunGenerateDockerfile(t *testing.T) {
tests := []struct {
name string
path string
data map[string]string
expected []any
}{
{
Expand All @@ -57,6 +58,20 @@ func TestBunGenerateDockerfile(t *testing.T) {
path: "../testdata/bun-bunfig",
expected: []any{`ARG VERSION=1.1.4`, `ARG INSTALL_CMD="bun install"`, `ARG BUILD_CMD="bun run build:prod"`, `ARG START_CMD="bun run start:production"`},
},
{
name: "Bun project with build mounts",
path: "../testdata/bun-bunfig",
data: map[string]string{"BuildMounts": `--mount=type=secret,id=_env,target=/app/.env \
`},
expected: []any{regexp.MustCompile(`^RUN --mount=type=secret,id=_env,target=/app/.env \\$`)},
},
{
name: "Bun project with install mounts",
path: "../testdata/bun-bunfig",
data: map[string]string{"InstallMounts": `--mount=type=secret,id=_env,target=/app/.env \
`},
expected: []any{regexp.MustCompile(`^RUN --mount=type=secret,id=_env,target=/app/.env \\$`)},
},
{
name: "Not a Bun project",
path: "../testdata/deno",
Expand All @@ -67,7 +82,7 @@ func TestBunGenerateDockerfile(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
bun := &runtime.Bun{Log: logger}
dockerfile, err := bun.GenerateDockerfile(test.path)
dockerfile, err := bun.GenerateDockerfile(test.path, test.data)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
Expand Down
13 changes: 9 additions & 4 deletions runtime/deno.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io/fs"
"log/slog"
"maps"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -70,7 +71,7 @@ func (d *Deno) Match(path string) bool {
return detected
}

func (d *Deno) GenerateDockerfile(path string) ([]byte, error) {
func (d *Deno) GenerateDockerfile(path string, data ...map[string]string) ([]byte, error) {
tmpl, err := template.New("Dockerfile").Parse(denoTemplate)
if err != nil {
return nil, fmt.Errorf("Failed to parse template")
Expand Down Expand Up @@ -156,11 +157,15 @@ func (d *Deno) GenerateDockerfile(path string) ([]byte, error) {
}

var buf bytes.Buffer
if err := tmpl.Option("missingkey=zero").Execute(&buf, map[string]string{
templateData := map[string]string{
"Version": *version,
"InstallCMD": installCMD,
"StartCMD": startCMD,
}); err != nil {
}
if len(data) > 0 {
maps.Copy(templateData, data[0])
}
if err := tmpl.Option("missingkey=zero").Execute(&buf, templateData); err != nil {
return nil, fmt.Errorf("Failed to execute template")
}

Expand Down Expand Up @@ -192,7 +197,7 @@ USER nonroot:nonroot
ENV PORT=8080
EXPOSE ${PORT}
ARG INSTALL_CMD={{.InstallCMD}}
RUN if [ ! -z "${INSTALL_CMD}" ]; then sh -c "$INSTALL_CMD"; fi
RUN {{.InstallMounts}}if [ ! -z "${INSTALL_CMD}" ]; then sh -c "$INSTALL_CMD"; fi
ARG START_CMD={{.StartCMD}}
ENV START_CMD=${START_CMD}
Expand Down
10 changes: 9 additions & 1 deletion runtime/deno_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func TestDenoGenerateDockerfile(t *testing.T) {
tests := []struct {
name string
path string
data map[string]string
expected []any
}{
{
Expand All @@ -57,6 +58,13 @@ func TestDenoGenerateDockerfile(t *testing.T) {
path: "../testdata/deno-jsonc",
expected: []any{`ARG VERSION=1.43.3`, `ARG INSTALL_CMD="deno task cache"`, `ARG START_CMD="deno task start"`},
},
{
name: "Deno project with install mounts",
path: "../testdata/deno-jsonc",
data: map[string]string{"InstallMounts": `--mount=type=secret,id=_env,target=/app/.env \
`},
expected: []any{regexp.MustCompile(`^RUN --mount=type=secret,id=_env,target=/app/.env \\$`)},
},
{
name: "Not a Deno project",
path: "../testdata/ruby",
Expand All @@ -67,7 +75,7 @@ func TestDenoGenerateDockerfile(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
deno := &runtime.Deno{Log: logger}
dockerfile, err := deno.GenerateDockerfile(test.path)
dockerfile, err := deno.GenerateDockerfile(test.path, test.data)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
Expand Down
12 changes: 9 additions & 3 deletions runtime/elixir.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"fmt"
"log/slog"
"maps"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -35,7 +36,7 @@ func (d *Elixir) Match(path string) bool {
return false
}

func (d *Elixir) GenerateDockerfile(path string) ([]byte, error) {
func (d *Elixir) GenerateDockerfile(path string, data ...map[string]string) ([]byte, error) {
tmpl, err := template.New("Dockerfile").Parse(elixirTemplate)
if err != nil {
return nil, fmt.Errorf("Failed to parse template")
Expand Down Expand Up @@ -68,11 +69,15 @@ func (d *Elixir) GenerateDockerfile(path string) ([]byte, error) {
)

var buf bytes.Buffer
if err := tmpl.Option("missingkey=zero").Execute(&buf, map[string]string{
templateData := map[string]string{
"ElixirVersion": *elixirVersion,
"OTPVersion": strings.Split(*otpVersion, ".")[0],
"BinName": binName,
}); err != nil {
}
if len(data) > 0 {
maps.Copy(templateData, data[0])
}
if err := tmpl.Option("missingkey=zero").Execute(&buf, templateData); err != nil {
return nil, fmt.Errorf("Failed to execute template")
}

Expand Down Expand Up @@ -129,6 +134,7 @@ COPY --from=build --chown=nonroot:nonroot /app/_build/${MIX_ENV}/rel/${BIN_NAME}
RUN cp /app/bin/${BIN_NAME} /app/bin/server
ENV PORT=8080
EXPOSE ${PORT}
USER nonroot:nonroot
CMD ["/app/bin/server", "start"]
Expand Down
11 changes: 8 additions & 3 deletions runtime/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"bytes"
"fmt"
"log/slog"
"maps"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -36,7 +37,7 @@ func (d *Golang) Match(path string) bool {
return false
}

func (d *Golang) GenerateDockerfile(path string) ([]byte, error) {
func (d *Golang) GenerateDockerfile(path string, data ...map[string]string) ([]byte, error) {
tmpl, err := template.New("Dockerfile").Parse(golangTemplate)
if err != nil {
return nil, fmt.Errorf("Failed to parse template")
Expand Down Expand Up @@ -84,10 +85,14 @@ func (d *Golang) GenerateDockerfile(path string) ([]byte, error) {

d.Log.Info("Using package: " + pkg)
var buf bytes.Buffer
if err := tmpl.Option("missingkey=zero").Execute(&buf, map[string]string{
templateData := map[string]string{
"Version": *version,
"Package": pkg,
}); err != nil {
}
if len(data) > 0 {
maps.Copy(templateData, data[0])
}
if err := tmpl.Option("missingkey=zero").Execute(&buf, templateData); err != nil {
return nil, fmt.Errorf("Failed to execute template")
}

Expand Down
14 changes: 10 additions & 4 deletions runtime/java.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"log/slog"
"maps"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -46,7 +47,7 @@ func (d *Java) Match(path string) bool {
return false
}

func (d *Java) GenerateDockerfile(path string) ([]byte, error) {
func (d *Java) GenerateDockerfile(path string, data ...map[string]string) ([]byte, error) {
version, err := findJDKVersion(path, d.Log)
if err != nil {
return nil, err
Expand Down Expand Up @@ -124,14 +125,17 @@ func (d *Java) GenerateDockerfile(path string) ([]byte, error) {
startCMDJSON, _ := json.Marshal(startCMD)
startCMD = string(startCMDJSON)
}

if err := tmpl.Option("missingkey=zero").Execute(&buf, map[string]string{
templateData := map[string]string{
"Version": *version,
"GradleVersion": gradleVersion,
"MavenVersion": mavenVersion,
"BuildCMD": buildCMD,
"StartCMD": startCMD,
}); err != nil {
}
if len(data) > 0 {
maps.Copy(templateData, data[0])
}
if err := tmpl.Option("missingkey=zero").Execute(&buf, templateData); err != nil {
return nil, fmt.Errorf("Failed to execute template")
}

Expand Down Expand Up @@ -165,6 +169,7 @@ RUN chown -R nonroot:nonroot /app
COPY --from=build --chown=nonroot:nonroot /app/target/*.jar /app/target/
ENV PORT=8080
EXPOSE ${PORT}
USER nonroot:nonroot
ARG JAVA_OPTS=
Expand Down Expand Up @@ -200,6 +205,7 @@ RUN chown -R nonroot:nonroot /app
COPY --from=build --chown=nonroot:nonroot /app/build/libs/*.jar /app/build/libs/
ENV PORT=8080
EXPOSE ${PORT}
USER nonroot:nonroot
ARG JAVA_OPTS=
Expand Down
2 changes: 1 addition & 1 deletion runtime/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type Runtime interface {
// Returns true if the runtime can be used for the given path.
Match(path string) bool
// Generates a Dockerfile for the given path.
GenerateDockerfile(path string) ([]byte, error)
GenerateDockerfile(path string, data ...map[string]string) ([]byte, error)
}

type RuntimeName string
Expand Down
Loading

0 comments on commit d886a8f

Please sign in to comment.