diff --git a/Makefile b/Makefile index 773edb95bf..932debe011 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,6 @@ LOGIN_DEPENDENCIES_TAG ?= "zitadel-login-dependencies:local" LOGIN_IMAGE_TAG ?= "zitadel-login:local" CORE_MOCK_TAG ?= "zitadel-core-mock:local" LOGIN_INTEGRATION_TESTSUITE_TAG ?= "zitadel-login-integration-testsuite:local" -CORE_MOCK_CONTAINER_NAME ?= zitadel-mock-grpc-server -LOGIN_CONTAINER_NAME ?= zitadel-login XDG_CACHE_HOME ?= $(HOME)/.cache export CACHE_DIR ?= $(XDG_CACHE_HOME)/zitadel-make @@ -12,21 +10,18 @@ export CACHE_DIR ?= $(XDG_CACHE_HOME)/zitadel-make help: @echo "Makefile for the login service" @echo "Available targets:" - @echo " help - Show this help message" - @echo " login - Start the login service" - @echo " login-lint - Run linting and formatting checks" - @echo " login-lint-force - Force run linting and formatting checks" - @echo " login-unit - Run unit tests" - @echo " login-unit-force - Force run unit tests" - @echo " login-integration - Run integration tests" - @echo " login-integration-force - Force run integration tests" - @echo " login-standalone - Build the docker image for production login containers" - @echo " login-quality - Run all quality checks (login-lint, unit, integration)" - @echo " login-ci - Run all CI tasks. Run it with the -j flag to parallelize. make -j ci" - @echo " show-cache-keys - Show all cache keys with image ids and exit codes" - @echo " clean-cache-keys - Remove all cache keys" - @echo " core-mock - Start the core mock server" - @echo " core-mock-stop - Stop the core mock server" + @echo " help - Show this help message." + @echo " login-lint - Run linting and formatting checks. FORCE=true prevents skipping." + @echo " login-lint-force - Force run linting and formatting checks." + @echo " login-unit - Run unit tests. FORCE=true prevents skipping." + @echo " login-unit-force - Force run unit tests." + @echo " login-integration - Run integration tests. FORCE=true prevents skipping." + @echo " login-integration-force - Force run integration tests." + @echo " login-standalone-build - Build the docker image for production login containers." + @echo " login-quality - Run all quality checks (login-lint, unit, integration)." + @echo " login-ci - Run all CI tasks. Run it with the -j flag to parallelize: make -j ci." + @echo " show-cache-keys - Show all cache keys with image ids and exit codes." + @echo " clean-cache-keys - Remove all cache keys." .PHONY: login-lint-force @@ -47,13 +42,12 @@ login-unit: ./scripts/run_or_skip.sh login-unit-force $(LOGIN_DEPENDENCIES_TAG) .PHONY: login-integration-force -login-integration-force: login core-mock login-integration-testsuite - docker run --rm $(LOGIN_INTEGRATION_TESTSUITE_TAG) - $(MAKE) core-mock-stop +login-integration-force: login-standalone-build core-mock-build login-integration-testsuite-build + docker compose --file ./apps/login-integration-testsuite/docker-compose.yaml run --rm integration-testsuite .PHONY: login-integration login-integration: - ./scripts/run_or_skip.sh login-integration-force '$(LOGIN_DEPENDENCIES_TAG);$(CORE_MOCK_TAG);$(LOGIN_INTEGRATION_TESTSUITE_TAG)' + ./scripts/run_or_skip.sh login-integration-force '$(LOGIN_IMAGE_TAG);$(LOGIN_INTEGRATION_TESTSUITE_TAG);$(CORE_MOCK_TAG)' .PHONY: login-quality login-quality: core-mock-build login-quality-after-build @@ -62,37 +56,22 @@ login-quality-after-build: login-lint login-unit login-integration .PHONY: login-ci login-ci: core-mock-build login-ci-after-build -login-ci-after-build: login-quality-after-build login-standalone +login-ci-after-build: login-quality-after-build login-standalone-build @: login-dependencies: docker buildx bake login-dependencies --set login-dependencies.tags=$(LOGIN_DEPENDENCIES_TAG); -.PHONY: login-standalone -login-standalone: +.PHONY: login-standalone-build +login-standalone-build: docker buildx bake login-standalone --set login-standalone.tags=$(LOGIN_IMAGE_TAG); -.PHONY: login -login: login-standalone login-stop - docker run --detach --rm --name $(LOGIN_CONTAINER_NAME) --publish 3000:3000 $(LOGIN_IMAGE_TAG) - -login-stop: - docker rm --force $(LOGIN_CONTAINER_NAME) 2>/dev/null || true - core-mock-build: docker buildx bake core-mock --set core-mock.tags=$(CORE_MOCK_TAG); -login-integration-testsuite: login-dependencies +login-integration-testsuite-build: login-dependencies docker buildx bake login-integration-testsuite --set login-integration-testsuite.tags=$(LOGIN_INTEGRATION_TESTSUITE_TAG) -.PHONY: core-mock -core-mock: core-mock-build core-mock-stop - docker run --detach --rm --name $(CORE_MOCK_CONTAINER_NAME) --publish 22221:22221 --publish 22222:22222 $(CORE_MOCK_TAG) - -.PHONY: core-mock-stop -core-mock-stop: - docker rm --force $(CORE_MOCK_CONTAINER_NAME) 2>/dev/null || true - .PHONY: clean-cache-keys clean-cache-keys: @echo "Removing cache directory: $(CACHE_DIR)" diff --git a/apps/core-mock/Dockerfile b/apps/core-mock/Dockerfile index fff98a50c7..c9c77e5afd 100644 --- a/apps/core-mock/Dockerfile +++ b/apps/core-mock/Dockerfile @@ -6,4 +6,4 @@ COPY mocked-services.cfg . COPY initial-stubs initial-stubs COPY --from=protos . . -ENTRYPOINT [ "sh", "-c", "grpc-mock -v 1 -protos $(tr '\n' ',' < ./mocked-services.cfg) -stub-dir ./initial-stubs" ] +ENTRYPOINT [ "sh", "-c", "grpc-mock -v 1 -proto $(tr '\n' ',' < ./mocked-services.cfg) -stub-dir ./initial-stubs" ] diff --git a/apps/login-integration-testsuite/cypress.config.ts b/apps/login-integration-testsuite/cypress.config.ts index 855eda22e1..080cb31bc6 100644 --- a/apps/login-integration-testsuite/cypress.config.ts +++ b/apps/login-integration-testsuite/cypress.config.ts @@ -2,9 +2,11 @@ import { defineConfig } from "cypress"; export default defineConfig({ reporter: "list", + e2e: { - baseUrl: "http://localhost:3000", + baseUrl: process.env.LOGIN_BASE_URL || "http://localhost:3000", specPattern: "integration/**/*.cy.{js,jsx,ts,tsx}", + supportFile: "support/e2e.{js,jsx,ts,tsx}", setupNodeEvents(on, config) { // implement node event listeners here }, diff --git a/apps/login-integration-testsuite/docker-compose.yaml b/apps/login-integration-testsuite/docker-compose.yaml new file mode 100644 index 0000000000..96d3fc9b59 --- /dev/null +++ b/apps/login-integration-testsuite/docker-compose.yaml @@ -0,0 +1,31 @@ +services: + core-mock: + image: ${CORE_MOCK_TAG:-zitadel-core-mock:local} + container_name: core-mock + ports: + - 22220:22220 + - 22222:22222 + + login: + image: ${LOGIN_IMAGE_TAG:-zitadel-login:local} + container_name: login + ports: + - 3000:3000 + environment: + - ZITADEL_API_URL=http://core-mock:22222 + - ZITADEL_SERVICE_USER_TOKEN="yolo" + - EMAIL_VERIFICATION=true + - DEBUG=true + - NEXT_PUBLIC_BASE_PATH="" + + integration-testsuite: + image: ${LOGIN_INTEGRATION_TESTSUITE_TAG:-zitadel-login-integration-testsuite:local} + container_name: integration-testsuite + environment: + - LOGIN_BASE_URL=http://login:3000 + - CYPRESS_CORE_MOCK_STUBS_URL=http://core-mock:22220/v1/stubs + depends_on: + login: + condition: service_started + core-mock: + condition: service_started diff --git a/apps/login-integration-testsuite/integration/invite.cy.ts b/apps/login-integration-testsuite/integration/invite.cy.ts index 3014f5a2e5..7765dae827 100644 --- a/apps/login-integration-testsuite/integration/invite.cy.ts +++ b/apps/login-integration-testsuite/integration/invite.cy.ts @@ -1,4 +1,4 @@ -import { stub } from "../support/mock"; +import { stub } from "../support/e2e"; describe("verify invite", () => { beforeEach(() => { diff --git a/apps/login-integration-testsuite/integration/login.cy.ts b/apps/login-integration-testsuite/integration/login.cy.ts index 3e74c0f7fe..46710a4d37 100644 --- a/apps/login-integration-testsuite/integration/login.cy.ts +++ b/apps/login-integration-testsuite/integration/login.cy.ts @@ -1,4 +1,4 @@ -import { stub } from "../support/mock"; +import { stub } from "../support/e2e"; describe("login", () => { beforeEach(() => { diff --git a/apps/login-integration-testsuite/integration/register-idp.cy.ts b/apps/login-integration-testsuite/integration/register-idp.cy.ts index 6b320f1775..73a0c32e00 100644 --- a/apps/login-integration-testsuite/integration/register-idp.cy.ts +++ b/apps/login-integration-testsuite/integration/register-idp.cy.ts @@ -1,4 +1,4 @@ -import { stub } from "../support/mock"; +import { stub } from "../support/e2e"; const IDP_URL = "https://example.com/idp/url"; diff --git a/apps/login-integration-testsuite/integration/register.cy.ts b/apps/login-integration-testsuite/integration/register.cy.ts index 262302c4c3..0fbb9fd447 100644 --- a/apps/login-integration-testsuite/integration/register.cy.ts +++ b/apps/login-integration-testsuite/integration/register.cy.ts @@ -1,4 +1,4 @@ -import { stub } from "../support/mock"; +import { stub } from "../support/e2e"; describe("register", () => { beforeEach(() => { diff --git a/apps/login-integration-testsuite/integration/verify.cy.ts b/apps/login-integration-testsuite/integration/verify.cy.ts index 464bf02e59..5d19b2f8ad 100644 --- a/apps/login-integration-testsuite/integration/verify.cy.ts +++ b/apps/login-integration-testsuite/integration/verify.cy.ts @@ -1,4 +1,4 @@ -import { stub } from "../support/mock"; +import { stub } from "../support/e2e"; describe("verify email", () => { beforeEach(() => { diff --git a/apps/login-integration-testsuite/support/commands.ts b/apps/login-integration-testsuite/support/commands.ts deleted file mode 100644 index 95857aea4c..0000000000 --- a/apps/login-integration-testsuite/support/commands.ts +++ /dev/null @@ -1,37 +0,0 @@ -/// -// *********************************************** -// This example commands.ts shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -// -// declare global { -// namespace Cypress { -// interface Chainable { -// login(email: string, password: string): Chainable -// drag(subject: string, options?: Partial): Chainable -// dismiss(subject: string, options?: Partial): Chainable -// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable -// } -// } -// } diff --git a/apps/login-integration-testsuite/support/e2e.ts b/apps/login-integration-testsuite/support/e2e.ts index 6a173d6fcb..e9f1f34030 100644 --- a/apps/login-integration-testsuite/support/e2e.ts +++ b/apps/login-integration-testsuite/support/e2e.ts @@ -1,20 +1,29 @@ -// *********************************************************** -// This example support/e2e.ts is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** +const url = Cypress.env('CORE_MOCK_STUBS_URL') || "http://localhost:22220/v1/stubs" -// Import commands.js using ES2015 syntax: -import "./commands"; +function removeStub(service: string, method: string) { + return cy.request({ + url, + method: "DELETE", + qs: { + service, + method, + }, + }); +} -// Alternatively you can use CommonJS syntax: -// require('./commands') +export function stub(service: string, method: string, out?: any) { + removeStub(service, method); + return cy.request({ + url, + method: "POST", + body: { + stubs: [ + { + service, + method, + out, + }, + ], + }, + }); +} diff --git a/apps/login-integration-testsuite/support/mock.ts b/apps/login-integration-testsuite/support/mock.ts deleted file mode 100644 index 84c33b8c2d..0000000000 --- a/apps/login-integration-testsuite/support/mock.ts +++ /dev/null @@ -1,27 +0,0 @@ -function removeStub(service: string, method: string) { - return cy.request({ - url: "http://localhost:22220/v1/stubs", - method: "DELETE", - qs: { - service, - method, - }, - }); -} - -export function stub(service: string, method: string, out?: any) { - removeStub(service, method); - return cy.request({ - url: "http://localhost:22220/v1/stubs", - method: "POST", - body: { - stubs: [ - { - service, - method, - out, - }, - ], - }, - }); -} diff --git a/apps/login-integration-testsuite/tsconfig.json b/apps/login-integration-testsuite/tsconfig.json index 830efdd0ba..18edb199ac 100644 --- a/apps/login-integration-testsuite/tsconfig.json +++ b/apps/login-integration-testsuite/tsconfig.json @@ -1,5 +1,4 @@ { - "extends": "../tsconfig.json", "compilerOptions": { "target": "es5", "lib": ["es5", "dom"], diff --git a/docker-bake.hcl b/docker-bake.hcl index 2bc70b681d..f91e1a2c6f 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -46,6 +46,9 @@ target "core-mock" { target "login-integration-testsuite" { dockerfile = "dockerfiles/login-integration-testsuite.Dockerfile" + contexts = { + login-base = "target:login-base" + } } # We run integration and acceptance tests against the next standalone server for docker. diff --git a/dockerfiles/login-base.Dockerfile b/dockerfiles/login-base.Dockerfile index 5039b4b7f9..2362b39c0c 100644 --- a/dockerfiles/login-base.Dockerfile +++ b/dockerfiles/login-base.Dockerfile @@ -3,4 +3,4 @@ ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable RUN apk add --no-cache libc6-compat bash git -WORKDIR /app +WORKDIR /build diff --git a/dockerfiles/login-integration-testsuite.Dockerfile b/dockerfiles/login-integration-testsuite.Dockerfile index c32cf2d7f2..e0b53e6af9 100644 --- a/dockerfiles/login-integration-testsuite.Dockerfile +++ b/dockerfiles/login-integration-testsuite.Dockerfile @@ -1,15 +1,16 @@ -FROM cypress/factory AS login-integration-testsuite -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable -WORKDIR /opt/app +FROM login-base AS integration-dependencies COPY \ pnpm-lock.yaml \ pnpm-workspace.yaml \ ./ COPY ./apps/login-integration-testsuite/package.json ./apps/login-integration-testsuite/package.json RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ - pnpm install --frozen-lockfile -RUN pnpm exec cypress install -COPY ./apps/login-integration-testsuite/ . -CMD ["pnpm", "exec", "cypress", "run"] + pnpm install --no-frozen-lockfile --filter=login-integration-testsuite + +FROM cypress/factory AS login-integration-testsuite +WORKDIR /opt/app +COPY --from=integration-dependencies /build/apps/login-integration-testsuite . +RUN npm install cypress +RUN npx cypress install +COPY ./apps/login-integration-testsuite . +CMD ["npx", "cypress", "run"] diff --git a/dockerfiles/login-standalone.Dockerfile b/dockerfiles/login-standalone.Dockerfile index 05a6c1846a..0e758dac04 100644 --- a/dockerfiles/login-standalone.Dockerfile +++ b/dockerfiles/login-standalone.Dockerfile @@ -3,20 +3,20 @@ RUN pnpm install turbo --global COPY . . RUN turbo prune @zitadel/login --docker FROM login-base AS installer -COPY --from=prune-for-docker /app/out/json/ . +COPY --from=prune-for-docker /build/out/json/ . RUN pnpm install --frozen-lockfile -COPY --from=prune-for-docker /app/out/full/ . +COPY --from=prune-for-docker /build/out/full/ . RUN NEXT_PUBLIC_BASE_PATH=/ui/v2/login NEXT_OUTPUT_MODE=standalone pnpm exec turbo run build FROM login-platform AS login-standalone -WORKDIR /app +WORKDIR /runtime RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs # If /.env-file/.env is mounted into the container, its variables are made available to the server before it starts up. RUN mkdir -p /.env-file && touch /.env-file/.env && chown -R nextjs:nodejs /.env-file -COPY --chown=nextjs:nodejs --from=installer /app/apps/login/.next/standalone ./ -COPY --chown=nextjs:nodejs --from=installer /app/apps/login/.next/static ./apps/login/.next/static -COPY --chown=nextjs:nodejs --from=installer /app/apps/login/public ./apps/login/public +COPY --chown=nextjs:nodejs --from=installer /build/apps/login/.next/standalone ./ +COPY --chown=nextjs:nodejs --from=installer /build/apps/login/.next/static ./apps/login/.next/static +COPY --chown=nextjs:nodejs --from=installer /build/apps/login/public ./apps/login/public USER nextjs ENV HOSTNAME="0.0.0.0" CMD ["/bin/sh", "-c", " set -o allexport && . /.env-file/.env && set +o allexport && node apps/login/server.js"] diff --git a/dockerfiles/typescript-proto-client.Dockerfile b/dockerfiles/typescript-proto-client.Dockerfile index d73b2559e5..2cf47e632f 100644 --- a/dockerfiles/typescript-proto-client.Dockerfile +++ b/dockerfiles/typescript-proto-client.Dockerfile @@ -3,4 +3,4 @@ COPY packages/zitadel-proto packages/zitadel-proto RUN pnpm generate FROM scratch -COPY --from=zitadel-proto /app/packages/zitadel-proto / +COPY --from=zitadel-proto /build/packages/zitadel-proto / diff --git a/scripts/run_or_skip.sh b/scripts/run_or_skip.sh index ad238039e9..20c45d2ec2 100755 --- a/scripts/run_or_skip.sh +++ b/scripts/run_or_skip.sh @@ -12,6 +12,7 @@ fi MAKE_TARGET=$1 IMAGES=$2 +FORCE=${FORCE:-false} DIGEST_FILE="$CACHE_DIR/$MAKE_TARGET.digests" mkdir -p "$CACHE_DIR" @@ -31,14 +32,18 @@ OLD_DIGEST=$(cat "$DIGEST_FILE" 2>/dev/null || echo "") OLD_STATUS=$(echo "$OLD_DIGEST" | cut -d ';' -f1) OLD_IDS=$(echo "$OLD_DIGEST" | cut -d ';' -f2-9) if [[ "$OLD_IDS" == "$(get_image_ids)" ]]; then - echo "Skipping $MAKE_TARGET – all images unchanged, returning cached status $OLD_STATUS" - exit $OLD_STATUS -else - echo "Running $MAKE_TARGET..." - set +e - make $MAKE_TARGET - STATUS=$? - set -e - echo "${STATUS};$(get_image_ids)" > $DIGEST_FILE - exit $STATUS + if [[ "$FORCE" == "true" ]]; then + echo "\$FORCE=$FORCE - Running $MAKE_TARGET despite unchanged images." + else + echo "Skipping $MAKE_TARGET – all images unchanged, returning cached status $OLD_STATUS" + exit $OLD_STATUS + fi fi + +echo "Running $MAKE_TARGET..." +set +e +make -j $MAKE_TARGET +STATUS=$? +set -e +echo "${STATUS};$(get_image_ids)" > $DIGEST_FILE +exit $STATUS