Support Podman style of accessing the host network in Cypress tests (#11421)
* Supply '--network slirp4netns:allow_host_loopback=true' argument when launching via Podman This means that the host.containers.internal address will work as a way to access the host machine's network from within the container in Podman. This is eqivalent to '--add-host host.docker.internal:host-gateway' in Docker. * Log the locations of generated files for Cypress tests * Use 'host.containers.internal' to access the host network when using Podman * Support Podman in email Cypress tests too * Restrict code that decides between Docker and Podman to run in Cypress plugins Because it can't run in the browser - it needs to run a command line command to find out. * Move logic for HOST_DOCKER_INTERNAL into cfgDirFromTemplate
This commit is contained in:
parent
ff9d4905d5
commit
3d2d08b132
5 changed files with 66 additions and 25 deletions
|
@ -29,7 +29,7 @@ describe("Email Registration", () => {
|
||||||
cy.startHomeserver({
|
cy.startHomeserver({
|
||||||
template: "email",
|
template: "email",
|
||||||
variables: {
|
variables: {
|
||||||
SMTP_HOST: "host.docker.internal",
|
SMTP_HOST: "{{HOST_DOCKER_INTERNAL}}", // This will get replaced in synapseStart
|
||||||
SMTP_PORT: _mailhog.instance.smtpPort,
|
SMTP_PORT: _mailhog.instance.smtpPort,
|
||||||
},
|
},
|
||||||
}).then((_homeserver) => {
|
}).then((_homeserver) => {
|
||||||
|
|
|
@ -156,6 +156,14 @@ export function isPodman(): Promise<boolean> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supply the right hostname to use to talk to the host machine. On Docker this
|
||||||
|
* is "host.docker.internal" and on Podman this is "host.containers.internal".
|
||||||
|
*/
|
||||||
|
export async function hostContainerName() {
|
||||||
|
return (await isPodman()) ? "host.containers.internal" : "host.docker.internal";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Cypress.PluginConfig}
|
* @type {Cypress.PluginConfig}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -24,7 +24,7 @@ import * as fse from "fs-extra";
|
||||||
import PluginEvents = Cypress.PluginEvents;
|
import PluginEvents = Cypress.PluginEvents;
|
||||||
import PluginConfigOptions = Cypress.PluginConfigOptions;
|
import PluginConfigOptions = Cypress.PluginConfigOptions;
|
||||||
import { getFreePort } from "../utils/port";
|
import { getFreePort } from "../utils/port";
|
||||||
import { dockerExec, dockerLogs, dockerRun, dockerStop } from "../docker";
|
import { dockerExec, dockerLogs, dockerRun, dockerStop, hostContainerName, isPodman } from "../docker";
|
||||||
import { HomeserverConfig, HomeserverInstance } from "../utils/homeserver";
|
import { HomeserverConfig, HomeserverInstance } from "../utils/homeserver";
|
||||||
import { StartHomeserverOpts } from "../../support/homeserver";
|
import { StartHomeserverOpts } from "../../support/homeserver";
|
||||||
|
|
||||||
|
@ -58,27 +58,41 @@ async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Homeserver
|
||||||
const baseUrl = `http://localhost:${port}`;
|
const baseUrl = `http://localhost:${port}`;
|
||||||
|
|
||||||
// now copy homeserver.yaml, applying substitutions
|
// now copy homeserver.yaml, applying substitutions
|
||||||
console.log(`Gen ${path.join(templateDir, "homeserver.yaml")}`);
|
const templateHomeserver = path.join(templateDir, "homeserver.yaml");
|
||||||
let hsYaml = await fse.readFile(path.join(templateDir, "homeserver.yaml"), "utf8");
|
const outputHomeserver = path.join(tempDir, "homeserver.yaml");
|
||||||
|
console.log(`Gen ${templateHomeserver} -> ${outputHomeserver}`);
|
||||||
|
let hsYaml = await fse.readFile(templateHomeserver, "utf8");
|
||||||
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
|
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
|
||||||
hsYaml = hsYaml.replace(/{{MACAROON_SECRET_KEY}}/g, macaroonSecret);
|
hsYaml = hsYaml.replace(/{{MACAROON_SECRET_KEY}}/g, macaroonSecret);
|
||||||
hsYaml = hsYaml.replace(/{{FORM_SECRET}}/g, formSecret);
|
hsYaml = hsYaml.replace(/{{FORM_SECRET}}/g, formSecret);
|
||||||
hsYaml = hsYaml.replace(/{{PUBLIC_BASEURL}}/g, baseUrl);
|
hsYaml = hsYaml.replace(/{{PUBLIC_BASEURL}}/g, baseUrl);
|
||||||
hsYaml = hsYaml.replace(/{{OAUTH_SERVER_PORT}}/g, opts.oAuthServerPort?.toString());
|
hsYaml = hsYaml.replace(/{{OAUTH_SERVER_PORT}}/g, opts.oAuthServerPort?.toString());
|
||||||
|
hsYaml = hsYaml.replace(/{{HOST_DOCKER_INTERNAL}}/g, await hostContainerName());
|
||||||
if (opts.variables) {
|
if (opts.variables) {
|
||||||
|
let fetchedHostContainer = null;
|
||||||
for (const key in opts.variables) {
|
for (const key in opts.variables) {
|
||||||
hsYaml = hsYaml.replace(new RegExp("%" + key + "%", "g"), String(opts.variables[key]));
|
let value = String(opts.variables[key]);
|
||||||
|
|
||||||
|
if (value === "{{HOST_DOCKER_INTERNAL}}") {
|
||||||
|
if (!fetchedHostContainer) {
|
||||||
|
fetchedHostContainer = await hostContainerName();
|
||||||
|
}
|
||||||
|
value = fetchedHostContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
hsYaml = hsYaml.replace(new RegExp("%" + key + "%", "g"), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await fse.writeFile(path.join(tempDir, "homeserver.yaml"), hsYaml);
|
await fse.writeFile(outputHomeserver, hsYaml);
|
||||||
|
|
||||||
// now generate a signing key (we could use synapse's config generation for
|
// now generate a signing key (we could use synapse's config generation for
|
||||||
// this, or we could just do this...)
|
// this, or we could just do this...)
|
||||||
// NB. This assumes the homeserver.yaml specifies the key in this location
|
// NB. This assumes the homeserver.yaml specifies the key in this location
|
||||||
const signingKey = randB64Bytes(32);
|
const signingKey = randB64Bytes(32);
|
||||||
console.log(`Gen ${path.join(templateDir, "localhost.signing.key")}`);
|
const outputSigningKey = path.join(tempDir, "localhost.signing.key");
|
||||||
await fse.writeFile(path.join(tempDir, "localhost.signing.key"), `ed25519 x ${signingKey}`);
|
console.log(`Gen -> ${outputSigningKey}`);
|
||||||
|
await fse.writeFile(outputSigningKey, `ed25519 x ${signingKey}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
port,
|
port,
|
||||||
|
@ -88,27 +102,38 @@ async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Homeserver
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a synapse instance: the template must be the name of
|
/**
|
||||||
// one of the templates in the cypress/plugins/synapsedocker/templates
|
* Start a synapse instance: the template must be the name of
|
||||||
// directory
|
* one of the templates in the cypress/plugins/synapsedocker/templates
|
||||||
|
* directory.
|
||||||
|
*
|
||||||
|
* Any value in opts.variables that is set to `{{HOST_DOCKER_INTERNAL}}'
|
||||||
|
* will be replaced with 'host.docker.internal' (if we are on Docker) or
|
||||||
|
* 'host.containers.interal' if we are on Podman.
|
||||||
|
*/
|
||||||
async function synapseStart(opts: StartHomeserverOpts): Promise<HomeserverInstance> {
|
async function synapseStart(opts: StartHomeserverOpts): Promise<HomeserverInstance> {
|
||||||
const synCfg = await cfgDirFromTemplate(opts);
|
const synCfg = await cfgDirFromTemplate(opts);
|
||||||
|
|
||||||
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
|
console.log(`Starting synapse with config dir ${synCfg.configDir}...`);
|
||||||
|
|
||||||
|
const dockerSynapseParams = ["--rm", "-v", `${synCfg.configDir}:/data`, "-p", `${synCfg.port}:8008/tcp`];
|
||||||
|
|
||||||
|
if (await isPodman()) {
|
||||||
|
// Make host.containers.internal work to allow Synapse to talk to the
|
||||||
|
// test OIDC server.
|
||||||
|
dockerSynapseParams.push("--network");
|
||||||
|
dockerSynapseParams.push("slirp4netns:allow_host_loopback=true");
|
||||||
|
} else {
|
||||||
|
// Make host.docker.internal work to allow Synapse to talk to the test
|
||||||
|
// OIDC server.
|
||||||
|
dockerSynapseParams.push("--add-host");
|
||||||
|
dockerSynapseParams.push("host.docker.internal:host-gateway");
|
||||||
|
}
|
||||||
|
|
||||||
const synapseId = await dockerRun({
|
const synapseId = await dockerRun({
|
||||||
image: "matrixdotorg/synapse:develop",
|
image: "matrixdotorg/synapse:develop",
|
||||||
containerName: `react-sdk-cypress-synapse`,
|
containerName: `react-sdk-cypress-synapse`,
|
||||||
params: [
|
params: dockerSynapseParams,
|
||||||
"--rm",
|
|
||||||
"-v",
|
|
||||||
`${synCfg.configDir}:/data`,
|
|
||||||
"-p",
|
|
||||||
`${synCfg.port}:8008/tcp`,
|
|
||||||
// make host.docker.internal work to allow Synapse to talk to the test OIDC server
|
|
||||||
"--add-host",
|
|
||||||
"host.docker.internal:host-gateway",
|
|
||||||
],
|
|
||||||
cmd: ["run"],
|
cmd: ["run"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -81,9 +81,10 @@ oidc_providers:
|
||||||
issuer: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth"
|
issuer: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth"
|
||||||
authorization_endpoint: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth/auth.html"
|
authorization_endpoint: "http://localhost:{{OAUTH_SERVER_PORT}}/oauth/auth.html"
|
||||||
# the token endpoint receives requests from synapse, rather than the webapp, so needs to escape the docker container.
|
# the token endpoint receives requests from synapse, rather than the webapp, so needs to escape the docker container.
|
||||||
# Hence, host.docker.internal rather than localhost.
|
# Hence, HOST_DOCKER_INTERNAL rather than localhost. This is set to
|
||||||
token_endpoint: "http://host.docker.internal:{{OAUTH_SERVER_PORT}}/oauth/token"
|
# host.docker.internal on Docker and host.containers.internal on Podman.
|
||||||
userinfo_endpoint: "http://host.docker.internal:{{OAUTH_SERVER_PORT}}/oauth/userinfo"
|
token_endpoint: "http://{{HOST_DOCKER_INTERNAL}}:{{OAUTH_SERVER_PORT}}/oauth/token"
|
||||||
|
userinfo_endpoint: "http://{{HOST_DOCKER_INTERNAL}}:{{OAUTH_SERVER_PORT}}/oauth/userinfo"
|
||||||
client_id: "synapse"
|
client_id: "synapse"
|
||||||
discover: false
|
discover: false
|
||||||
scopes: ["profile"]
|
scopes: ["profile"]
|
||||||
|
|
|
@ -39,15 +39,22 @@ declare global {
|
||||||
interface Chainable {
|
interface Chainable {
|
||||||
/**
|
/**
|
||||||
* Start a homeserver instance with a given config template.
|
* Start a homeserver instance with a given config template.
|
||||||
|
*
|
||||||
* @param opts: either the template path (within cypress/plugins/{homeserver}docker/template/), or
|
* @param opts: either the template path (within cypress/plugins/{homeserver}docker/template/), or
|
||||||
* an options object
|
* an options object
|
||||||
|
*
|
||||||
|
* If any of opts.variables has the special value
|
||||||
|
* '{{HOST_DOCKER_INTERNAL}}', it will be replaced by
|
||||||
|
* 'host.docker.interal' if we are on Docker, or
|
||||||
|
* 'host.containers.internal' on Podman.
|
||||||
*/
|
*/
|
||||||
startHomeserver(opts: string | StartHomeserverOpts): Chainable<HomeserverInstance>;
|
startHomeserver(opts: string | StartHomeserverOpts): Chainable<HomeserverInstance>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom command wrapping task:{homeserver}Stop whilst preventing uncaught exceptions
|
* Custom command wrapping task:{homeserver}Stop whilst preventing uncaught exceptions
|
||||||
* for if Homeserver stopping races with the app's background sync loop.
|
* for if Homeserver stopping races with the app's background sync loop.
|
||||||
* @param homeserver the homeserver instance returned by start{Homeserver}
|
*
|
||||||
|
* @param homeserver the homeserver instance returned by {homeserver}Start (e.g. synapseStart).
|
||||||
*/
|
*/
|
||||||
stopHomeserver(homeserver: HomeserverInstance): Chainable<AUTWindow>;
|
stopHomeserver(homeserver: HomeserverInstance): Chainable<AUTWindow>;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue