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

Add docker build with all formatting tools #82

Merged
merged 9 commits into from
Mar 3, 2025
Merged
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
105 changes: 105 additions & 0 deletions .github/workflows/docker-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Build and push docker image

on:
workflow_dispatch:
push:
branches: [main]

env:
CARGO_TERM_COLOR: always
REGISTRY_IMAGE: ghcr.io/svix/openapi-codegen

jobs:
build:
strategy:
matrix:
platform:
- runner: ubuntu-24.04
name: amd64
- runner: ubuntu-24.04-arm
name: arm64
name: Build and publish ${{ matrix.platform.name }} docker image
if: github.ref == 'refs/heads/main'
runs-on: "${{ matrix.platform.runner }}"
steps:
- uses: actions/checkout@v4

- name: Login to ghcr
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
tags: ${{ env.REGISTRY_IMAGE }}
file: Dockerfile.${{ matrix.platform.name }}
cache-from: type=gha
cache-to: type=gha
platforms: linux/${{ matrix.platform.name }}
outputs: type=image,push-by-digest=true,name-canonical=true,push=true

- name: Export digest
# we create empty files with the sha256 digest of the docker image as the filename
# since we did not push with a tag, the only way to identify the image is with the digest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Down the line we should probably do releases for openapi-codegen and its Docker image, but for now this seems like a good start 👍

run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.platform.name }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1

publish-merged-manifest:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
needs:
- build
steps:
- uses: actions/checkout@v4

- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true

- name: Login to ghcr
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- run: echo "IMAGE_TAG=$(date +%Y%m%d)-$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV"

- name: Create manifest list and push
# inside the ${{ runner.temp }}/digests we downloaded empty files with the sha256 digest of the image as the filename
# using printf we get the digest from the filename and we add the digest to the manifest
# this is the recommend way of doing things :(
# https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create \
-t ${{ env.REGISTRY_IMAGE }}:latest \
-t ${{ env.REGISTRY_IMAGE }}:${{ env.IMAGE_TAG }} \
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)

- name: Inspect image
run: |
docker buildx imagetools inspect "${{ env.REGISTRY_IMAGE }}:latest"
102 changes: 102 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# build openapi-codegen
FROM docker.io/lukemathwalker/cargo-chef:latest-rust-1.85 AS chef
WORKDIR /app

FROM chef AS planner

COPY Cargo.toml .
COPY Cargo.lock .
COPY build.rs .
COPY src /app/src

RUN cargo chef prepare --recipe-path recipe.json

FROM chef AS openapi-codegen-builder

COPY --from=planner /app/recipe.json recipe.json

RUN cargo chef cook --release --recipe-path recipe.json

COPY Cargo.toml .
COPY Cargo.lock .
COPY build.rs .
COPY src /app/src

RUN cargo build --release --bin openapi-codegen

# build goimports
FROM docker.io/golang:1.24-bookworm AS goimports-builder
RUN go install golang.org/x/tools/cmd/goimports@latest

# build rubyfmt
FROM docker.io/rust:1.85 AS rubyfmt-builder
WORKDIR /app

RUN apt-get update && \
apt-get install -y --no-install-recommends ruby bison && \
apt-get clean

RUN git clone https://github.com/fables-tales/rubyfmt.git \
--recurse-submodules --shallow-submodules /app && \
git checkout 71cbb4adc53d3d8b36a6f1b3dcff87865d0204b8

RUN cargo build --release

# main container
FROM docker.io/ubuntu:noble

ENV DEBIAN_FRONTEND=noninteractive
ENV PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.cargo/bin/:/root/.dotnet/tools"

RUN apt-get update && \
apt-get install -y --no-install-recommends curl default-jre-headless dotnet8 && \
apt-get clean


# C#
RUN dotnet tool install csharpier --version 0.30.6 -g

# # Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \
-y \
--profile minimal \
--no-modify-path \
--no-update-default-toolchain \
--default-toolchain nightly-2025-02-27 \
--component rustfmt


# Javascript
COPY --from=ghcr.io/biomejs/biome:1.9.4 /usr/local/bin/biome /usr/bin/biome

# Python
COPY --from=ghcr.io/astral-sh/ruff:0.9.8 /ruff /usr/bin/ruff

# Java
RUN echo "25157797a0a972c2290b5bc71530c4f7ad646458025e3484412a6e5a9b8c9aa6 google-java-format-1.25.2-all-deps.jar" > google-java-format-1.25.2-all-deps.jar.sha256 && \
curl -fsSL --output google-java-format-1.25.2-all-deps.jar "https://github.com/google/google-java-format/releases/download/v1.25.2/google-java-format-1.25.2-all-deps.jar" && \
sha256sum google-java-format-1.25.2-all-deps.jar.sha256 -c && \
rm google-java-format-1.25.2-all-deps.jar.sha256 && \
mv google-java-format-1.25.2-all-deps.jar /usr/bin/ && \
echo '#!/usr/bin/bash\njava -jar /usr/bin/google-java-format-1.25.2-all-deps.jar $@' > /usr/bin/google-java-format && \
chmod +x /usr/bin/google-java-format

# Kotlin
RUN echo "5e7eb28a0b2006d1cefbc9213bfc73a8191ec2f85d639ec4fc4ec0cd04212e82 ktfmt-0.54-jar-with-dependencies.jar" > ktfmt-0.54-jar-with-dependencies.jar.sha256 && \
curl -fsSL --output ktfmt-0.54-jar-with-dependencies.jar "https://github.com/facebook/ktfmt/releases/download/v0.54/ktfmt-0.54-jar-with-dependencies.jar" && \
sha256sum ktfmt-0.54-jar-with-dependencies.jar.sha256 -c && \
rm ktfmt-0.54-jar-with-dependencies.jar.sha256 && \
mv ktfmt-0.54-jar-with-dependencies.jar /usr/bin/ && \
echo '#!/usr/bin/bash\njava -jar /usr/bin/ktfmt-0.54-jar-with-dependencies.jar $@' > /usr/bin/ktfmt && \
chmod +x /usr/bin/ktfmt

# Go
COPY --from=goimports-builder /go/bin/goimports /usr/bin
COPY --from=goimports-builder /usr/local/go/bin/gofmt /usr/bin

# openapi-codegen
COPY --from=openapi-codegen-builder /app/target/release/openapi-codegen /usr/bin/

# Ruby
COPY --from=rubyfmt-builder /app/target/release/rubyfmt-main /usr/bin/rubyfmt

14 changes: 9 additions & 5 deletions src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
postprocessing::Postprocessor,
template,
types::Types,
GenerateFlags,
};

#[derive(Default, Deserialize)]
Expand All @@ -29,7 +30,7 @@ pub(crate) fn generate(
types: Types,
tpl_name: String,
output_dir: &Utf8Path,
no_postprocess: bool,
flags: GenerateFlags,
) -> anyhow::Result<()> {
let (name_without_jinja_suffix, tpl_path) = match tpl_name.strip_suffix(".jinja") {
Some(basename) => (basename, &tpl_name),
Expand Down Expand Up @@ -63,14 +64,16 @@ pub(crate) fn generate(
minijinja_env.add_template(tpl_path, &tpl_source)?;
let tpl = minijinja_env.get_template(tpl_path)?;

fs::create_dir_all(output_dir)?;

let postprocessor = Postprocessor::from_ext(tpl_file_ext, output_dir);

let generator = Generator {
tpl,
tpl_file_ext,
output_dir,
postprocessor: &postprocessor,
no_postprocess,
flags,
};

match tpl_kind {
Expand All @@ -80,7 +83,7 @@ pub(crate) fn generate(
TemplateKind::Summary => generator.generate_summary(types, api)?,
}

if !no_postprocess {
if !flags.no_postprocess {
postprocessor.run_postprocessor();
}

Expand All @@ -92,7 +95,7 @@ struct Generator<'a> {
tpl_file_ext: &'a str,
output_dir: &'a Utf8Path,
postprocessor: &'a Postprocessor,
no_postprocess: bool,
flags: GenerateFlags,
}

impl Generator<'_> {
Expand Down Expand Up @@ -172,11 +175,12 @@ impl Generator<'_> {
};

let file_path = self.output_dir.join(format!("{basename}.{tpl_file_ext}"));

let out_file = BufWriter::new(File::create(&file_path)?);

self.tpl.render_to_write(ctx, out_file)?;

if !self.no_postprocess {
if !self.flags.no_postprocess {
self.postprocessor.add_path(&file_path);
}

Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ fn analyze_and_generate(
writeln!(types_file, "{types:#?}")?;
}

generate(api, types, template, path, flags.no_postprocess)?;
generate(api, types, template, path, flags)?;
}

println!("done! output written to {path}");
Expand Down
2 changes: 1 addition & 1 deletion src/postprocessing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ impl PostprocessorLanguage {
Self::Rust => &[(
"rustfmt",
&[
"+nightly",
"+nightly-2025-02-27",
"--unstable-features",
"--skip-children",
"--edition",
Expand Down