npm package diff
Package: @forge/cli-shared
Versions: 8.8.2-next.0 - 8.8.2-next.1
File: package/out/tunnel/docker-compose-lifecycle.js
Index: package/out/tunnel/docker-compose-lifecycle.js
===================================================================
--- package/out/tunnel/docker-compose-lifecycle.js
+++ package/out/tunnel/docker-compose-lifecycle.js
@@ -0,0 +1,160 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.stopDockerComposeStack = exports.startDockerComposeStack = exports.deleteDockerComposeFile = exports.generateContainersDockerComposeFile = exports.DockerUnableToStartError = exports.DockerUnableToPullProxySidecarImage = exports.InvalidContainerServicePort = void 0;
+const tslib_1 = require("tslib");
+const fs = tslib_1.__importStar(require("fs"));
+const path = tslib_1.__importStar(require("path"));
+const yaml = tslib_1.__importStar(require("yaml"));
+const docker_compose_1 = require("docker-compose");
+const shared_1 = require("../shared");
+const text_1 = require("../ui/text");
+const tunnel_options_1 = require("./tunnel-options");
+const HIDDEN_DIR = '.services';
+class InvalidContainerServicePort extends shared_1.UserError {
+    constructor(serviceKey) {
+        super(text_1.Text.error.invalidServicePort(serviceKey));
+    }
+}
+exports.InvalidContainerServicePort = InvalidContainerServicePort;
+class DockerUnableToPullProxySidecarImage extends shared_1.UserError {
+    constructor(err) {
+        super(text_1.Text.tunnel.unableToPullProxySidecarImage(err?.message ?? 'Unknown Error Occurred.'));
+    }
+}
+exports.DockerUnableToPullProxySidecarImage = DockerUnableToPullProxySidecarImage;
+class DockerUnableToStartError extends shared_1.UserError {
+    constructor(err) {
+        super(text_1.Text.tunnel.unableToStartDockerComposeStack(err?.message ?? 'Unknown Error Occurred.'));
+    }
+}
+exports.DockerUnableToStartError = DockerUnableToStartError;
+const generateContainersDockerComposeFile = async (services, appId, envId) => {
+    const filesGenerated = {};
+    for (const service of services) {
+        const { key: serviceKey, containers } = service;
+        const containersWithTunnelConfig = containers.filter((container) => !!container.tunnel);
+        if (containersWithTunnelConfig.length > 0) {
+            const port = await (0, tunnel_options_1.getServicePort)(services, serviceKey);
+            if (!port || port < 1024 || port > 49152) {
+                throw new InvalidContainerServicePort(serviceKey);
+            }
+            const containerConfig = Object.fromEntries(containersWithTunnelConfig.map((container) => {
+                const config = {
+                    container_name: container.key,
+                    ...container.tunnel?.docker
+                };
+                const envArray = container?.tunnel?.docker.environment ?? [];
+                const filteredEnvArray = envArray.filter((envVar) => !envVar.startsWith('FORGE_EGRESS_PROXY_URL='));
+                filteredEnvArray.push('FORGE_EGRESS_PROXY_URL=http://proxy-sidecar:7072');
+                config.environment = filteredEnvArray;
+                return [container.key, config];
+            }));
+            const dockerComposeConfig = {
+                services: {
+                    ...containerConfig,
+                    ...(await getProxySidecarConfig(serviceKey, Object.keys(containerConfig), port, appId, envId))
+                }
+            };
+            const yamlString = yaml.stringify(dockerComposeConfig);
+            const filePath = getContainerDockerComposePath(serviceKey);
+            fs.writeFileSync(filePath, yamlString);
+            filesGenerated[serviceKey] = filePath;
+        }
+    }
+    return filesGenerated;
+};
+exports.generateContainersDockerComposeFile = generateContainersDockerComposeFile;
+const getProxySidecarConfig = async (serviceKey, containerKeys, port, appId, envId) => {
+    let fopBaseUrl = 'https://forge-outbound-proxy.services.atlassian.com';
+    let jwksUrl = 'https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json';
+    let proxySidecarImage = 'forge-ecr.services.atlassian.com/forge-platform/proxy-sidecar:latest';
+    if (process.env.FORGE_GRAPHQL_GATEWAY?.startsWith('https://api-private.stg.atlassian.com/graphql')) {
+        fopBaseUrl = 'https://forge-outbound-proxy.stg.services.atlassian.com';
+        jwksUrl = 'https://forge.cdn.stg.atlassian-dev.net/.well-known/jwks.json';
+        proxySidecarImage = 'forge-ecr.stg.services.atlassian.com/forge-platform/proxy-sidecar:latest';
+    }
+    let appIdShort = appId;
+    if (appId.startsWith('ari:cloud:ecosystem::app/')) {
+        appIdShort = appId.split('/')[1];
+    }
+    return {
+        'proxy-sidecar': {
+            image: proxySidecarImage,
+            container_name: `proxy-sidecar-${serviceKey}`,
+            environment: [
+                `SERVICE_URL=http://${containerKeys[0]}:8080`,
+                `FOP_BASE_URL=${fopBaseUrl}`,
+                `APP_ID=ari:cloud:ecosystem::app/${appIdShort}`,
+                `ENV_ID=ari:cloud:ecosystem::environment/${appIdShort}/${envId}`,
+                `JWKS_URL=${jwksUrl}`,
+                `IS_LOCAL_DEV=true`
+            ],
+            ports: [`${port}:${tunnel_options_1.DEFAULT_PROXY_INGRESS_PORT}`],
+            depends_on: containerKeys
+        }
+    };
+};
+const getContainerDockerComposePath = (serviceKey) => {
+    const hiddenDir = path.join(process.cwd(), HIDDEN_DIR);
+    if (!fs.existsSync(hiddenDir)) {
+        fs.mkdirSync(hiddenDir, { recursive: true });
+    }
+    return path.join(hiddenDir, `docker-compose-${serviceKey}.yml`);
+};
+const deleteDockerComposeFile = async (composeFile) => {
+    if (fs.existsSync(composeFile)) {
+        fs.unlinkSync(composeFile);
+    }
+    const hiddenDir = path.join(process.cwd(), HIDDEN_DIR);
+    if (fs.existsSync(hiddenDir)) {
+        const files = fs.readdirSync(hiddenDir);
+        if (files.length === 0) {
+            fs.rmdirSync(hiddenDir);
+        }
+    }
+};
+exports.deleteDockerComposeFile = deleteDockerComposeFile;
+const startDockerComposeStack = async (dockerComposeFilePath, serviceKey) => {
+    try {
+        await (0, docker_compose_1.pullOne)('proxy-sidecar', {
+            cwd: path.dirname(dockerComposeFilePath),
+            log: true,
+            config: dockerComposeFilePath
+        });
+    }
+    catch (err) {
+        throw new DockerUnableToPullProxySidecarImage(err);
+    }
+    try {
+        await (0, docker_compose_1.upAll)({
+            cwd: path.dirname(dockerComposeFilePath),
+            log: true,
+            config: dockerComposeFilePath,
+            composeOptions: [`-p${serviceKey}`]
+        });
+    }
+    catch (err) {
+        throw new DockerUnableToStartError(err);
+    }
+};
+exports.startDockerComposeStack = startDockerComposeStack;
+const stopDockerComposeStack = async (configFile, composeFiles) => {
+    if (!composeFiles || Object.keys(composeFiles).length === 0)
+        return;
+    const { services } = await configFile.readConfig();
+    const serviceWithTunnelConfigExists = services?.some((service) => service.containers?.some((container) => {
+        return !!container.tunnel;
+    }));
+    if (!services || services.length === 0 || !serviceWithTunnelConfigExists)
+        return;
+    await Promise.all(Object.entries(composeFiles).map(async ([serviceKey, file]) => {
+        try {
+            await (0, docker_compose_1.downAll)({ cwd: '.', log: true, config: file, composeOptions: [`-p${serviceKey}`] });
+            await (0, exports.deleteDockerComposeFile)(file);
+        }
+        catch (err) {
+            throw new Error(text_1.Text.tunnel.unableToStopDockerComposeStack(serviceKey, err.message ?? 'Unknown Error Occurred.'));
+        }
+    }));
+};
+exports.stopDockerComposeStack = stopDockerComposeStack;