Restore realm configuration

This commit is contained in:
Joseph Garrone 2024-05-20 15:34:07 +02:00
parent 64fe15cf8c
commit 7a89888d11
8 changed files with 2390 additions and 34 deletions

View File

@ -9,17 +9,17 @@
"scripts": { "scripts": {
"prepare": "ts-node --skipProject scripts/generate-i18n-messages.ts && patch-package", "prepare": "ts-node --skipProject scripts/generate-i18n-messages.ts && patch-package",
"build": "ts-node --skipProject scripts/build.ts", "build": "ts-node --skipProject scripts/build.ts",
"watch": "chokidar './src/**/*' -c 'yarn build'", "storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && start-storybook -p 6006",
"link-in-starter": "ts-node --skipProject scripts/link-in-starter.ts",
"test": "yarn test:types && vitest run", "test": "yarn test:types && vitest run",
"test:types": "tsc -p test/tsconfig.json --noEmit", "test:types": "tsc -p test/tsconfig.json --noEmit",
"_format": "prettier '**/*.{ts,tsx,json,md}'", "_format": "prettier '**/*.{ts,tsx,json,md}'",
"format": "yarn _format --write", "format": "yarn _format --write",
"format:check": "yarn _format --list-different", "format:check": "yarn _format --list-different",
"link-in-app": "ts-node --skipProject scripts/link-in-app.ts", "link-in-app": "ts-node --skipProject scripts/link-in-app.ts",
"link-in-starter": "ts-node --skipProject scripts/link-in-starter.ts",
"copy-keycloak-resources-to-storybook-static": "PUBLIC_DIR_PATH=.storybook/static node dist/bin/main.js copy-keycloak-resources-to-public", "copy-keycloak-resources-to-storybook-static": "PUBLIC_DIR_PATH=.storybook/static node dist/bin/main.js copy-keycloak-resources-to-public",
"storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && start-storybook -p 6006", "build-storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && build-storybook",
"build-storybook": "yarn build && yarn copy-keycloak-resources-to-storybook-static && build-storybook" "dump-keycloak-realm": "ts-node --skipProject scripts/dump-keycloak-realm.ts"
}, },
"bin": { "bin": {
"keycloakify": "dist/bin/main.js" "keycloakify": "dist/bin/main.js"

View File

@ -1,6 +1,6 @@
import * as child_process from "child_process"; import * as child_process from "child_process";
import * as fs from "fs"; import * as fs from "fs";
import { join } from "path"; import { join, relative } from "path";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { transformCodebase } from "../src/bin/tools/transformCodebase"; import { transformCodebase } from "../src/bin/tools/transformCodebase";
import chalk from "chalk"; import chalk from "chalk";
@ -21,7 +21,9 @@ if (fs.existsSync(join("dist", "bin", "main.original.js"))) {
run(`npx tsc -p ${join("src", "bin", "tsconfig.json")}`); run(`npx tsc -p ${join("src", "bin", "tsconfig.json")}`);
if (!fs.readFileSync(join("dist", "bin", "main.js")).toString("utf8").includes("ncc")) {
fs.cpSync(join("dist", "bin", "main.js"), join("dist", "bin", "main.original.js")); fs.cpSync(join("dist", "bin", "main.js"), join("dist", "bin", "main.original.js"));
}
run(`npx ncc build ${join("dist", "bin", "main.js")} -o ${join("dist", "ncc_out")}`); run(`npx ncc build ${join("dist", "bin", "main.js")} -o ${join("dist", "ncc_out")}`);
@ -58,7 +60,9 @@ if (fs.existsSync(join("dist", "vite-plugin", "index.original.js"))) {
run(`npx tsc -p ${join("src", "vite-plugin", "tsconfig.json")}`); run(`npx tsc -p ${join("src", "vite-plugin", "tsconfig.json")}`);
if (!fs.readFileSync(join("dist", "vite-plugin", "index.js")).toString("utf8").includes("ncc")) {
fs.cpSync(join("dist", "vite-plugin", "index.js"), join("dist", "vite-plugin", "index.original.js")); fs.cpSync(join("dist", "vite-plugin", "index.js"), join("dist", "vite-plugin", "index.original.js"));
}
run(`npx ncc build ${join("dist", "vite-plugin", "index.js")} -o ${join("dist", "ncc_out")}`); run(`npx ncc build ${join("dist", "vite-plugin", "index.js")} -o ${join("dist", "ncc_out")}`);
@ -92,7 +96,7 @@ function patchDeprecatedBufferApiUsage(filePath: string) {
`var buffer = Buffer.allocUnsafe ? Buffer.allocUnsafe(toRead) : new Buffer(toRead);` `var buffer = Buffer.allocUnsafe ? Buffer.allocUnsafe(toRead) : new Buffer(toRead);`
); );
assert(after !== before); assert(after !== before, `Patch failed for ${relative(process.cwd(), filePath)}`);
fs.writeFileSync(filePath, Buffer.from(after, "utf8")); fs.writeFileSync(filePath, Buffer.from(after, "utf8"));
} }

View File

@ -0,0 +1,26 @@
import { containerName } from "../src/bin/shared/constants";
import child_process from "child_process";
import { SemVer } from "../src/bin/tools/SemVer";
import { join as pathJoin, relative as pathRelative } from "path";
import chalk from "chalk";
run([`docker exec -it ${containerName}`, `/opt/keycloak/bin/kc.sh export`, `--dir /tmp`, `--realm myrealm`, `--users realm_file`].join(" "));
const keycloakMajorVersionNumber = SemVer.parse(
child_process.execSync(`docker inspect --format '{{.Config.Image}}' ${containerName}`).toString("utf8").trim().split(":")[1]
).major;
const targetFilePath = pathRelative(
process.cwd(),
pathJoin(__dirname, "..", "src", "bin", "start-keycloak", `myrealm-realm-${keycloakMajorVersionNumber}.json`)
);
run(`docker cp ${containerName}:/tmp/myrealm-realm.json ${targetFilePath}`);
console.log(`${chalk.green(`✓ Exported realm to`)} ${chalk.bold(targetFilePath)}`);
function run(command: string) {
console.log(chalk.grey(`$ ${command}`));
return child_process.execSync(command, { "stdio": "inherit" });
}

View File

@ -68,7 +68,11 @@ program
}); });
program program
.command<{ port: number; keycloakVersion: string | undefined }>({ .command<{
port: number;
keycloakVersion: string | undefined;
realmJsonFilePath: string | undefined;
}>({
"name": "start-keycloak", "name": "start-keycloak",
"description": "Spin up a pre configured Docker image of Keycloak to test your theme." "description": "Spin up a pre configured Docker image of Keycloak to test your theme."
}) })
@ -81,7 +85,7 @@ program
return name; return name;
})(), })(),
"description": "Keycloak server port.", "description": ["Keycloak server port.", "Example `--port 8085`"].join(" "),
"defaultValue": 8080 "defaultValue": 8080
}) })
.option({ .option({
@ -93,9 +97,21 @@ program
return name; return name;
})(), })(),
"description": "Use a specific version of Keycloak.", "description": ["Use a specific version of Keycloak.", "Example `--keycloak-version 21.1.1`"].join(" "),
"defaultValue": undefined "defaultValue": undefined
}) })
.option({
"key": "realmJsonFilePath",
"name": (() => {
const name = "import";
optionsKeys.push(name);
return name;
})(),
"defaultValue": undefined,
"description": ["Import your own realm configuration file", "Example `--import path/to/myrealm-realm.json`"].join(" ")
})
.task({ .task({
skip, skip,
"handler": async cliCommandOptions => { "handler": async cliCommandOptions => {

View File

@ -65,3 +65,5 @@ export const accountThemePageIds = [
export type LoginThemePageId = (typeof loginThemePageIds)[number]; export type LoginThemePageId = (typeof loginThemePageIds)[number];
export type AccountThemePageId = (typeof accountThemePageIds)[number]; export type AccountThemePageId = (typeof accountThemePageIds)[number];
export const containerName = "keycloak-keycloakify";

View File

@ -0,0 +1 @@
export * from "./start-keycloak";

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
import { readBuildOptions } from "./shared/buildOptions"; import { readBuildOptions } from "../shared/buildOptions";
import type { CliCommandOptions as CliCommandOptions_common } from "./main"; import type { CliCommandOptions as CliCommandOptions_common } from "../main";
import { promptKeycloakVersion } from "./shared/promptKeycloakVersion"; import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
import { readMetaInfKeycloakThemes } from "./shared/metaInfKeycloakThemes"; import { readMetaInfKeycloakThemes } from "../shared/metaInfKeycloakThemes";
import { accountV1ThemeName, skipBuildJarsEnvName } from "./shared/constants"; import { accountV1ThemeName, skipBuildJarsEnvName, containerName } from "../shared/constants";
import { SemVer } from "./tools/SemVer"; import { SemVer } from "../tools/SemVer";
import type { KeycloakVersionRange } from "./shared/KeycloakVersionRange"; import type { KeycloakVersionRange } from "../shared/KeycloakVersionRange";
import { getJarFileBasename } from "./shared/getJarFileBasename"; import { getJarFileBasename } from "../shared/getJarFileBasename";
import { assert, type Equals } from "tsafe/assert"; import { assert, type Equals } from "tsafe/assert";
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, sep as pathSep, posix as pathPosix } from "path"; import { join as pathJoin, relative as pathRelative, sep as pathSep, posix as pathPosix } from "path";
@ -13,12 +13,16 @@ import * as child_process from "child_process";
import chalk from "chalk"; import chalk from "chalk";
import chokidar from "chokidar"; import chokidar from "chokidar";
import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce"; import { waitForDebounceFactory } from "powerhooks/tools/waitForDebounce";
import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath"; import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
import { Deferred } from "evt/tools/Deferred"; import { Deferred } from "evt/tools/Deferred";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import cliSelect from "cli-select";
import { isInside } from "../tools/isInside";
export type CliCommandOptions = CliCommandOptions_common & { export type CliCommandOptions = CliCommandOptions_common & {
port: number; port: number;
keycloakVersion: string | undefined; keycloakVersion: string | undefined;
realmJsonFilePath: string | undefined;
}; };
export async function command(params: { cliCommandOptions: CliCommandOptions }) { export async function command(params: { cliCommandOptions: CliCommandOptions }) {
@ -84,7 +88,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const doesImplementAccountTheme = metaInfKeycloakThemes.themes.some(({ name }) => name === accountV1ThemeName); const doesImplementAccountTheme = metaInfKeycloakThemes.themes.some(({ name }) => name === accountV1ThemeName);
const { keycloakVersion, keycloakMajorNumber } = await (async function getKeycloakMajor(): Promise<{ const { keycloakVersion, keycloakMajorNumber: keycloakMajorVersionNumber } = await (async function getKeycloakMajor(): Promise<{
keycloakVersion: string; keycloakVersion: string;
keycloakMajorNumber: number; keycloakMajorNumber: number;
}> { }> {
@ -122,13 +126,13 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const keycloakVersionRange: KeycloakVersionRange = (() => { const keycloakVersionRange: KeycloakVersionRange = (() => {
if (doesImplementAccountTheme) { if (doesImplementAccountTheme) {
const keycloakVersionRange = (() => { const keycloakVersionRange = (() => {
if (keycloakMajorNumber <= 21) { if (keycloakMajorVersionNumber <= 21) {
return "21-and-below" as const; return "21-and-below" as const;
} }
assert(keycloakMajorNumber !== 22); assert(keycloakMajorVersionNumber !== 22);
if (keycloakMajorNumber === 23) { if (keycloakMajorVersionNumber === 23) {
return "23" as const; return "23" as const;
} }
@ -140,7 +144,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
return keycloakVersionRange; return keycloakVersionRange;
} else { } else {
const keycloakVersionRange = (() => { const keycloakVersionRange = (() => {
if (keycloakMajorNumber <= 21) { if (keycloakMajorVersionNumber <= 21) {
return "21-and-below" as const; return "21-and-below" as const;
} }
@ -187,13 +191,46 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
}) })
.flat(); .flat();
const containerName = "keycloak-keycloakify";
try { try {
child_process.execSync(`docker rm --force ${containerName}`, { "stdio": "ignore" }); child_process.execSync(`docker rm --force ${containerName}`, { "stdio": "ignore" });
} catch {} } catch {}
const spawnParams = [ const realmJsonFilePath = await (async () => {
if (cliCommandOptions.realmJsonFilePath !== undefined) {
console.log(chalk.green(`Using realm json file: ${cliCommandOptions.realmJsonFilePath}`));
return getAbsoluteAndInOsFormatPath({
"pathIsh": cliCommandOptions.realmJsonFilePath,
"cwd": process.cwd()
});
}
const dirPath = pathJoin(getThisCodebaseRootDirPath(), "src", "bin", "start-keycloak");
const filePath = pathJoin(dirPath, `myrealm-realm-${keycloakMajorVersionNumber}.json`);
if (fs.existsSync(filePath)) {
return filePath;
}
console.log(`${chalk.yellow(`Keycloakify do not have a realm configuration for Keycloak ${keycloakMajorVersionNumber} yet.`)}`);
console.log(chalk.cyan("Select what configuration to use:"));
const { value } = await cliSelect<string>({
"values": [...fs.readdirSync(dirPath).filter(fileBasename => fileBasename.endsWith(".json")), "none"]
}).catch(() => {
process.exit(-1);
});
if (value === "none") {
return undefined;
}
return pathJoin(dirPath, value);
})();
const spawnArgs = [
"docker", "docker",
[ [
"run", "run",
@ -201,19 +238,23 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
...["--name", containerName], ...["--name", containerName],
...["-e", "KEYCLOAK_ADMIN=admin"], ...["-e", "KEYCLOAK_ADMIN=admin"],
...["-e", "KEYCLOAK_ADMIN_PASSWORD=admin"], ...["-e", "KEYCLOAK_ADMIN_PASSWORD=admin"],
...(realmJsonFilePath === undefined ? [] : ["-v", `${realmJsonFilePath}:/opt/keycloak/data/import/myrealm-realm.json`]),
...["-v", `${pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename)}:/opt/keycloak/providers/keycloak-theme.jar`], ...["-v", `${pathJoin(buildOptions.keycloakifyBuildDirPath, jarFileBasename)}:/opt/keycloak/providers/keycloak-theme.jar`],
...(keycloakMajorNumber <= 20 ? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"] : []), ...(keycloakMajorVersionNumber <= 20 ? ["-e", "JAVA_OPTS=-Dkeycloak.profile=preview"] : []),
...mountTargets.map(({ localPath, containerPath }) => ["-v", `${localPath}:${containerPath}:rw`]).flat(), ...mountTargets.map(({ localPath, containerPath }) => ["-v", `${localPath}:${containerPath}:rw`]).flat(),
`quay.io/keycloak/keycloak:${keycloakVersion}`, `quay.io/keycloak/keycloak:${keycloakVersion}`,
"start-dev", "start-dev",
...(21 <= keycloakMajorNumber && keycloakMajorNumber < 24 ? ["--features=declarative-user-profile"] : []) ...(21 <= keycloakMajorVersionNumber && keycloakMajorVersionNumber < 24 ? ["--features=declarative-user-profile"] : []),
...(realmJsonFilePath === undefined ? [] : ["--import-realm"])
], ],
{ {
"cwd": buildOptions.keycloakifyBuildDirPath "cwd": buildOptions.keycloakifyBuildDirPath
} }
] as const; ] as const;
const child = child_process.spawn(...spawnParams); console.log(JSON.stringify(spawnArgs, null, 2));
const child = child_process.spawn(...spawnArgs);
child.stdout.on("data", data => process.stdout.write(data)); child.stdout.on("data", data => process.stdout.write(data));
@ -247,7 +288,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
`- user: ${chalk.cyan.bold("admin")}`, `- user: ${chalk.cyan.bold("admin")}`,
`- password: ${chalk.cyan.bold("admin")}`, `- password: ${chalk.cyan.bold("admin")}`,
"", "",
`Watching for changes in ${chalk.bold(`.${pathSep}${pathRelative(process.cwd(), srcDirPath)}`)} ...` `Watching for changes in ${chalk.bold(`.${pathSep}${pathRelative(process.cwd(), srcDirPath)}`)}`
].join("\n") ].join("\n")
); );
}; };
@ -258,7 +299,20 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
{ {
const { waitForDebounce } = waitForDebounceFactory({ "delay": 400 }); const { waitForDebounce } = waitForDebounceFactory({ "delay": 400 });
chokidar.watch([srcDirPath, getThisCodebaseRootDirPath()], { "ignoreInitial": true }).on("all", async () => { chokidar.watch([srcDirPath, getThisCodebaseRootDirPath()], { "ignoreInitial": true }).on("all", async (...[, filePath]) => {
if (
isInside({
"dirPath": pathJoin(getThisCodebaseRootDirPath(), "src", "bin"),
filePath
}) ||
isInside({
"dirPath": pathJoin(getThisCodebaseRootDirPath(), "bin"),
filePath
})
) {
return;
}
await waitForDebounce(); await waitForDebounce();
console.log(chalk.cyan("Detected changes in the theme. Rebuilding ...")); console.log(chalk.cyan("Detected changes in the theme. Rebuilding ..."));
@ -271,7 +325,13 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
"env": process.env "env": process.env
}); });
child.stdout.on("data", data => process.stdout.write(data)); child.stdout.on("data", data => {
if (data.toString("utf8").includes("gzip:")) {
return;
}
process.stdout.write(data);
});
child.stderr.on("data", data => process.stderr.write(data)); child.stderr.on("data", data => process.stderr.write(data));