Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/init-action.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

761 changes: 456 additions & 305 deletions lib/start-proxy-action.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/init-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
initConfig,
runDatabaseInitCluster,
} from "./init";
import { KnownLanguage } from "./languages";
import { JavaEnvVars, KnownLanguage } from "./languages";
import { getActionsLogger, Logger } from "./logging";
import {
downloadOverlayBaseDatabaseFromCache,
Expand Down Expand Up @@ -756,13 +756,13 @@ async function run(startedAt: Date) {

// Enable Java network debugging if the FF is enabled.
if (await features.getValue(Feature.JavaNetworkDebugging)) {
// Get the existing value of `JAVA_OPTS`, if any.
// Get the existing value of `JAVA_TOOL_OPTIONS`, if any.
const existingJavaToolOptions =
getOptionalEnvVar("JAVA_TOOL_OPTIONS") || "";
getOptionalEnvVar(JavaEnvVars.JAVA_TOOL_OPTIONS) || "";

// Add the network debugging options.
core.exportVariable(
"JAVA_TOOL_OPTIONS",
JavaEnvVars.JAVA_TOOL_OPTIONS,
`${existingJavaToolOptions} -Djavax.net.debug=all`,
);
}
Expand Down
8 changes: 8 additions & 0 deletions src/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,11 @@ export enum KnownLanguage {
rust = "rust",
swift = "swift",
}

/** Java-specific environment variable names that we may care about. */
export enum JavaEnvVars {
JAVA_HOME = "JAVA_HOME",
JAVA_TOOL_OPTIONS = "JAVA_TOOL_OPTIONS",
JDK_JAVA_OPTIONS = "JDK_JAVA_OPTIONS",
_JAVA_OPTIONS = "_JAVA_OPTIONS",
}
14 changes: 14 additions & 0 deletions src/start-proxy-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
ProxyConfig,
} from "./start-proxy";
import { generateCertificateAuthority } from "./start-proxy/ca";
import { checkProxyEnvironment } from "./start-proxy/environment";
import { checkConnections } from "./start-proxy/reachability";
import { ActionName, sendUnhandledErrorStatusReport } from "./status-report";
import * as util from "./util";
Expand Down Expand Up @@ -76,6 +77,19 @@ async function run(startedAt: Date) {
.join("\n")}`,
);

// Check the environment for any configurations which may affect the proxy.
// This is a best effort process to give us insights into potential factors
// which may affect the operation of our proxy.
if (core.isDebug() || util.isInTestMode()) {
try {
await checkProxyEnvironment(logger, language);
} catch (err) {
logger.debug(
`Unable to inspect runner environment: ${util.getErrorMessage(err)}`,
);
}
}

const ca = generateCertificateAuthority(
await features.getValue(Feature.ImprovedProxyCertificates),
);
Expand Down
213 changes: 213 additions & 0 deletions src/start-proxy/environment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import * as fs from "fs";
import * as os from "os";
import path from "path";

import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as io from "@actions/io";
import test, { ExecutionContext } from "ava";
import sinon from "sinon";

import { JavaEnvVars, KnownLanguage } from "../languages";
import {
checkExpectedLogMessages,
getRecordingLogger,
LoggedMessage,
setupTests,
} from "../testing-utils";
import { withTmpDir } from "../util";

import {
checkJavaEnvVars,
checkJdkSettings,
checkProxyEnvironment,
checkProxyEnvVars,
discoverActionsJdks,
JAVA_PROXY_ENV_VARS,
ProxyEnvVars,
} from "./environment";

setupTests(test);

function stubToolrunner() {
sinon.stub(io, "which").throws(new Error("Java not installed"));
sinon.stub(toolrunner, "ToolRunner").returns({
exec: async () => {
return 0;
},
});
}

function assertEnvVarLogMessages(
t: ExecutionContext<any>,
envVars: string[],
messages: LoggedMessage[],
expectSet: boolean | string,
) {
const template = (envVar: string) => {
if (typeof expectSet === "string") {
return `Environment variable '${envVar}' is set to '${expectSet}'`;
}
return expectSet
? `Environment variable '${envVar}' is set to '${envVar}'`
: `Environment variable '${envVar}' is not set`;
};

const expected: string[] = [];

for (const envVar of envVars) {
expected.push(template(envVar));
}

checkExpectedLogMessages(t, messages, expected);
}

test("checkJavaEnvironment - none set", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

checkJavaEnvVars(logger);
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
});

test("checkJavaEnvironment - logs values when variables are set", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

for (const envVar of Object.values(JavaEnvVars)) {
process.env[envVar] = envVar;
}

checkJavaEnvVars(logger);
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, true);
});

test("discoverActionsJdks - discovers JDK paths", (t) => {
// Clear GHA variables that may interfere with this test in CI.
for (const envVar of Object.keys(process.env)) {
if (envVar.startsWith("JAVA_HOME_")) {
delete process.env[envVar];
}
}

const jdk8 = "/usr/lib/jvm/temurin-8-jdk-amd64";
const jdk17 = "/usr/lib/jvm/temurin-17-jdk-amd64";
const jdk21 = "/usr/lib/jvm/temurin-21-jdk-amd64";

process.env[JavaEnvVars.JAVA_HOME] = jdk17;
process.env["JAVA_HOME_8_X64"] = jdk8;
process.env["JAVA_HOME_17_X64"] = jdk17;
process.env["JAVA_HOME_21_X64"] = jdk21;

const results = discoverActionsJdks();
t.is(results.size, 3);
t.true(results.has(jdk8));
t.true(results.has(jdk17));
t.true(results.has(jdk21));
});

test("checkJdkSettings - does not throw for an empty directory", async (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

await withTmpDir(async (tmpDir) => {
t.notThrows(() => checkJdkSettings(logger, tmpDir));
});
});

test("checkJdkSettings - finds files and logs relevant properties", async (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

await withTmpDir(async (tmpDir) => {
const dir = path.join(tmpDir, "conf");
fs.mkdirSync(dir);

const file = path.join(dir, "net.properties");
fs.writeFileSync(
file,
[
"irrelevant.property=foo",
"http.proxyHost=proxy.example.com",
"http.unrelated=bar",
].join(os.EOL),
{},
);
checkJdkSettings(logger, tmpDir);

checkExpectedLogMessages(t, messages, [
`Found '${file}'.`,
`Found 'http.proxyHost=proxy.example.com' in '${file}'`,
]);
});
});

test("checkProxyEnvVars - none set", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

checkProxyEnvVars(logger);
assertEnvVarLogMessages(t, Object.values(ProxyEnvVars), messages, false);
});

test("checkProxyEnvVars - logs values when variables are set", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

for (const envVar of Object.values(ProxyEnvVars)) {
process.env[envVar] = envVar;
}

checkProxyEnvVars(logger);
assertEnvVarLogMessages(t, Object.values(ProxyEnvVars), messages, true);
});

test("checkProxyEnvVars - credentials are removed from URLs", (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

for (const envVar of Object.values(ProxyEnvVars)) {
process.env[envVar] = "https://secret:password@proxy.local";
}

checkProxyEnvVars(logger);
assertEnvVarLogMessages(
t,
Object.values(ProxyEnvVars),
messages,
"https://proxy.local/",
);
});

test("checkProxyEnvironment - includes base checks for all known languages", async (t) => {
stubToolrunner();

for (const language of Object.values(KnownLanguage)) {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

await checkProxyEnvironment(logger, language);
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
}
});

test("checkProxyEnvironment - includes Java checks for Java", async (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

stubToolrunner();

await checkProxyEnvironment(logger, KnownLanguage.java);
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
});

test("checkProxyEnvironment - includes language-specific checks if the language is undefined", async (t) => {
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages);

stubToolrunner();

await checkProxyEnvironment(logger, undefined);
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
});
Loading
Loading