diff --git a/.dockerignore b/.dockerignore index 85dcc16df6..e7203924a6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,23 @@ .git +.DS_Store node_modules +.turbo +*.log +.next +dist +dist-ssr +*.local +.env +.cache +server/dist +public/dist +.vscode +.idea +.vercel +.env*.local +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/out +/docker diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index b8f37c0ce1..0000000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Docker - -on: - push: - branches: - - main - - qa - workflow_dispatch: - -permissions: - packages: write - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Check out code - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Cache turbo build setup - uses: actions/cache@v4 - with: - path: .turbo - key: ${{ runner.os }}-turbo-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-turbo- - - - name: Setup Node.js environment - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver: docker-container - - - name: Login Public - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login Private - uses: docker/login-action@v3 - with: - registry: ${{ secrets.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKER_REGISTRY_USERNAME }} - password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ghcr.io/zitadel/login - ${{ secrets.DOCKER_IMAGE }} - tags: | - type=edge - type=ref,event=branch - type=ref,event=tag - type=ref,event=pr - type=sha - - - name: Install dependencies - run: pnpm install - - - name: Generate stubs - run: pnpm generate - - - name: Build for Docker - run: NEXT_PUBLIC_BASE_PATH=/ui/v2/login pnpm build:docker - - - name: Build and Push Image - id: build - uses: docker/build-push-action@v5 - timeout-minutes: 10 - with: - context: . - push: true - platforms: linux/amd64,linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Export digest - run: | - mkdir -p /tmp/digests/app - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/app/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests - path: /tmp/digests - if-no-files-found: error - retention-days: 1 diff --git a/.gitignore b/.gitignore index cedeed9b03..90be94765b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,13 +7,9 @@ dist dist-ssr *.local .env -apps/login/.env.local -apps/login/.env.acceptance .cache server/dist public/dist -.turbo -packages/zitadel-server/src/app/proto .vscode .idea .vercel diff --git a/.prettierrc b/.prettierrc index 6d0c388d7a..ba42405b03 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "printWidth": 125, "trailingComma": "all", - "plugins": ["prettier-plugin-organize-imports"] + "plugins": ["prettier-plugin-organize-imports"], + "filepath": "" } - \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..3b617e4e4d --- /dev/null +++ b/Makefile @@ -0,0 +1,104 @@ +LOGIN_BASE_TAG ?= "zitadel-login-base:local" +CORE_MOCK_TAG ?= "zitadel-core-mock:local" +XDG_CACHE_HOME ?= $(HOME)/.cache +CACHE_DIR ?= $(XDG_CACHE_HOME)/zitadel-make + +.PHONY: help +help: + @echo "Makefile for the login service" + @echo "Available targets:" + @echo " help - Show this help message" + @echo " lint - Run linting and formatting checks" + @echo " lint-force - Force run linting and formatting checks" + @echo " unit - Run unit tests" + @echo " unit-force - Force run unit tests" + @echo " integration - Run integration tests" + @echo " integration-force - Force run integration tests" + @echo " login-image - Build the login image" + @echo " quality - Run all quality checks (lint, unit, integration)" + @echo " ci - Run all CI tasks. Run it with the -j flag to parallelize. make -j ci" + +.PHONY: lint-force +lint-force: + docker run --rm $(LOGIN_BASE_TAG) lint + docker run --rm $(LOGIN_BASE_TAG) format --check + +.PHONY: lint +lint: + $(call run_or_skip,lint-force,lint,$(LOGIN_BASE_TAG)) + +unit-run: login-base + docker run --rm $(LOGIN_BASE_TAG) test:unit + +.PHONY: unit-force +unit-force: + docker run --rm $(LOGIN_BASE_TAG) test:unit + +.PHONY: unit +unit: + $(call run_or_skip,unit-force,unit,$(LOGIN_BASE_TAG)) + +.PHONY: integration-force +integration-force: + docker run --rm $(CORE_MOCK_TAG) test:integration + +.PHONY: integration +integration: + $(call run_or_skip,integration-force,integration,$(CORE_MOCK_TAG)) + +.PHONY: login-image +login-image: + docker buildx bake login-image + +.PHONY: quality +quality: lint unit integration + +.PHONY: ci +ci: core-mock ci-after-build +ci-after-build: quality login-image + @: + +login-base: + docker buildx bake login-base --set login-base.tags=$(LOGIN_BASE_TAG); + +core-mock: + docker buildx bake core-mock --set login-base.tags=$(CORE_MOCK_TAG); + +.PHONY: clean-cache +clean-cache: + @echo "Removing cache directory: $(CACHE_DIR)" + @rm -rf "$(CACHE_DIR)" + +.PHONY: show-cache +show-cache: + @echo "Showing cached digests and exit codes in $(CACHE_DIR):" + @find "$(CACHE_DIR)" -type f 2>/dev/null | while read file; do \ + echo "$$file: $$(cat $$file)"; \ + done + +# run_or_skip: runs a task only if the Docker image has changed and caches the result +# $(1): Taskname (e.g. "lint-force") +# $(2): Cache-ID (e.g. "lint") +# $(3): Docker-Image (e.g. "zitadel-login-base:local") +define run_or_skip + @digest_file="$(CACHE_DIR)/$(2).$(3)"; \ + mkdir -p $(CACHE_DIR); \ + if [ -f "$$digest_file" ]; then \ + digest_before=$$(cut -d',' -f1 "$$digest_file"); \ + status_before=$$(cut -d',' -f2 "$$digest_file"); \ + else \ + digest_before=""; \ + status_before=1; \ + fi; \ + current_digest=$$(docker image inspect $(3) --format='{{.Id}}'); \ + if [ "$$digest_before" = "$$current_digest" ]; then \ + echo "Skipping $(1) – image unchanged, returning cached status $$status_before"; \ + exit $$status_before; \ + else \ + echo "Running $(1)..."; \ + $(MAKE) $(1); \ + status=$$?; \ + echo "$$current_digest,$$status" > "$$digest_file"; \ + exit $$status; \ + fi +endef diff --git a/apps/login/.dockerignore b/apps/login/.dockerignore new file mode 100644 index 0000000000..05b505239f --- /dev/null +++ b/apps/login/.dockerignore @@ -0,0 +1,3 @@ +custom-config.js +.env.local +.env.acceptance diff --git a/apps/login/.gitignore b/apps/login/.gitignore index 63ddd0c9eb..05b505239f 100644 --- a/apps/login/.gitignore +++ b/apps/login/.gitignore @@ -1,2 +1,3 @@ custom-config.js -.env.local \ No newline at end of file +.env.local +.env.acceptance diff --git a/apps/login/mock/Dockerfile b/apps/login/mock/Dockerfile deleted file mode 100644 index 9f08b20bae..0000000000 --- a/apps/login/mock/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM bufbuild/buf:1.21.0 as protos - -RUN buf export https://github.com/envoyproxy/protoc-gen-validate.git --path validate --output /proto -RUN buf export https://github.com/grpc-ecosystem/grpc-gateway.git --path protoc-gen-openapiv2 --output /proto -RUN buf export https://github.com/googleapis/googleapis.git --path google/api/annotations.proto --path google/api/http.proto --path google/api/field_behavior.proto --output /proto -RUN buf export https://github.com/zitadel/zitadel.git --path ./proto/zitadel --output /proto - -FROM scratch AS config - -COPY mocked-services.cfg . -COPY initial-stubs initial-stubs -COPY --from=protos /proto . - -FROM golang:1.20.5-alpine3.18 as grpc-mock - -RUN go install github.com/eliobischof/grpc-mock/cmd/grpc-mock@01b09f60db1b501178af59bed03b2c22661df48c - -COPY --from=config / . - -ENTRYPOINT [ "sh", "-c", "grpc-mock -v 1 -proto $(tr '\n' ',' < ./mocked-services.cfg) -stub-dir ./initial-stubs" ] diff --git a/bake/proto.Dockerfile b/bake/proto.Dockerfile deleted file mode 100644 index da4f241504..0000000000 --- a/bake/proto.Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -# BUILD STAGE -FROM base - -RUN pnpm generate diff --git a/docker-bake.hcl b/docker-bake.hcl index 289b6ce8d7..5f5c59788b 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,37 +1,44 @@ -variable "tags" { +variable "release_tags" { default = ["zitadel-login:local"] } -variable "login-context" { - default = "." -} - group "default" { - targets = ["login-docker-image"] + targets = ["login-generate"] } -target "typescript-base" { - context = "${login-context}" - dockerfile = "bake/base.Dockerfile" +target "login-base" { + context = "." + dockerfile = "dockerfiles/login-base.Dockerfile" } -target "proto" { - context = "${login-context}" - dockerfile = "bake/proto.Dockerfile" - output = ["type=local,dest=./packages/zitadel-proto"] +target "download-protos" { + dockerfile = "dockerfiles/download-protos.Dockerfile" + contexts = { + base = "target:login-base" + } +} + +target "core-mock" { + dockerfile = "dockerfiles/core-mock.Dockerfile" contexts = { - base = "target:typescript-base" + protos = "target:download-protos" } } -target "login-docker-image" { - context = "${login-context}" - dockerfile = "bake/login-for-docker.Dockerfile" - tags = "${tags}" +target "login-generate" { + dockerfile = "dockerfiles/login-generate.Dockerfile" + contexts = { + base = "target:login-base" + } +} + +target "login-image" { + dockerfile = "dockerfiles/login-image.Dockerfile" + tags = "${release_tags}" args = { NODE_ENV = "production" } contexts = { - proto = "target:proto" + generated = "target:login-generate" } } diff --git a/dockerfiles/core-mock.Dockerfile b/dockerfiles/core-mock.Dockerfile new file mode 100644 index 0000000000..8033e8b211 --- /dev/null +++ b/dockerfiles/core-mock.Dockerfile @@ -0,0 +1,13 @@ +FROM scratch AS config + +COPY mocked-services.cfg . +COPY initial-stubs initial-stubs +COPY --from=protos /proto . + +FROM golang:1.20.5-alpine3.18 + +RUN go install github.com/eliobischof/grpc-mock/cmd/grpc-mock@01b09f60db1b501178af59bed03b2c22661df48c + +COPY --from=config / . + +ENTRYPOINT [ "sh", "-c", "grpc-mock -v 1 -proto $(tr '\n' ',' < ./mocked-services.cfg) -stub-dir ./initial-stubs" ] diff --git a/dockerfiles/download-protos.Dockerfile b/dockerfiles/download-protos.Dockerfile new file mode 100644 index 0000000000..d83e1b0eff --- /dev/null +++ b/dockerfiles/download-protos.Dockerfile @@ -0,0 +1,7 @@ +FROM base + +RUN cd packages/zitadel-proto && \ + pnpm buf export https://github.com/envoyproxy/protoc-gen-validate.git --path validate --output /proto && \ + pnpm buf export https://github.com/grpc-ecosystem/grpc-gateway.git --path protoc-gen-openapiv2 --output /proto && \ + pnpm buf export https://github.com/googleapis/googleapis.git --path google/api/annotations.proto --path google/api/http.proto --path google/api/field_behavior.proto --output /proto && \ + pnpm buf export https://github.com/zitadel/zitadel.git --path ./proto/zitadel --output /proto diff --git a/bake/base.Dockerfile b/dockerfiles/login-base.Dockerfile similarity index 60% rename from bake/base.Dockerfile rename to dockerfiles/login-base.Dockerfile index fd2b61653c..ee7dea7469 100644 --- a/bake/base.Dockerfile +++ b/dockerfiles/login-base.Dockerfile @@ -1,19 +1,22 @@ -# BUILD STAGE -FROM node:20-alpine +FROM node:20-alpine AS base + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" + +RUN corepack enable + +RUN apk add --no-cache libc6-compat bash git WORKDIR /app -RUN apk add --no-cache libc6-compat bash git -RUN corepack enable && corepack prepare pnpm@latest --activate +COPY \ + turbo.json \ + .npmrc \ + package.json \ + pnpm-lock.yaml \ + pnpm-workspace.yaml \ + ./ -# Copy remote turbo.json config for pruning -COPY turbo.json ./ -COPY .npmrc ./ - -# pnpm store + turbo build cache -RUN mkdir -p .pnpm-store .next - -# Copy just lockfile & manifests for better cache-hit COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY packages/zitadel-client/package.json ./packages/zitadel-client/ COPY packages/zitadel-eslint-config/package.json ./packages/zitadel-eslint-config/ @@ -23,8 +26,9 @@ COPY packages/zitadel-tailwind-config/package.json ./packages/zitadel-tailwind-c COPY packages/zitadel-tsconfig/package.json ./packages/zitadel-tsconfig/ COPY apps/login/package.json ./apps/login/ -RUN --mount=type=cache,target=/app/.pnpm-store \ - pnpm install --frozen-lockfile --store-dir .pnpm-store +RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ + pnpm install --frozen-lockfile -# Full source COPY . . + +ENTRYPOINT ["pnpm"] diff --git a/dockerfiles/login-generate.Dockerfile b/dockerfiles/login-generate.Dockerfile new file mode 100644 index 0000000000..4ea4ddf59a --- /dev/null +++ b/dockerfiles/login-generate.Dockerfile @@ -0,0 +1,3 @@ +FROM base AS generated + +RUN pnpm generate diff --git a/bake/login-for-docker.Dockerfile b/dockerfiles/login-image.Dockerfile similarity index 93% rename from bake/login-for-docker.Dockerfile rename to dockerfiles/login-image.Dockerfile index 38df779127..a67fb54327 100644 --- a/bake/login-for-docker.Dockerfile +++ b/dockerfiles/login-image.Dockerfile @@ -1,9 +1,7 @@ -# BUILD STAGE -FROM proto AS build-for-docker +FROM generated AS build-for-docker RUN NEXT_PUBLIC_BASE_PATH=/ui/v2/login pnpm build:docker -# RUNTIME FROM node:20-alpine WORKDIR /app diff --git a/packages/zitadel-proto/.dockerignore b/packages/zitadel-proto/.dockerignore new file mode 100644 index 0000000000..93276fc105 --- /dev/null +++ b/packages/zitadel-proto/.dockerignore @@ -0,0 +1,4 @@ +zitadel +google +protoc-gen-openapiv2 +validate