Type that registries must have either an url or a host

This commit is contained in:
Michael B. Gale
2026-02-10 17:05:44 +00:00
parent 700fc11b44
commit 4d44b570d2
6 changed files with 126 additions and 47 deletions
+4 -4
View File
@@ -11,16 +11,16 @@ import { KnownLanguage } from "./languages";
import { getActionsLogger, Logger } from "./logging";
import { getRepositoryNwo } from "./repository";
import {
Credential,
credentialToStr,
getCredentials,
getProxyBinaryPath,
getSafeErrorMessage,
parseLanguage,
ProxyInfo,
Registry,
sendFailedStatusReport,
sendSuccessStatusReport,
ValidCredential,
ValidRegistry,
} from "./start-proxy";
import { checkConnections } from "./start-proxy/reachability";
import { ActionName, sendUnhandledErrorStatusReport } from "./status-report";
@@ -40,7 +40,7 @@ type BasicAuthCredentials = {
};
type ProxyConfig = {
all_credentials: Credential[];
all_credentials: ValidCredential[];
ca: CertificateAuthority;
proxy_auth?: BasicAuthCredentials;
};
@@ -243,7 +243,7 @@ async function startProxy(
core.setOutput("proxy_port", port.toString());
core.setOutput("proxy_ca_certificate", config.ca.cert);
const registry_urls: Registry[] = config.all_credentials
const registry_urls: ValidRegistry[] = config.all_credentials
.filter((credential) => credential.url !== undefined)
.map((credential) => ({
type: credential.type,
+35 -11
View File
@@ -13,7 +13,12 @@ import { Config } from "./config-utils";
import * as defaults from "./defaults.json";
import { KnownLanguage } from "./languages";
import { Logger } from "./logging";
import { Credential } from "./start-proxy/types";
import {
Address,
Credential,
Registry,
ValidCredential,
} from "./start-proxy/types";
import {
ActionName,
createStatusReportBase,
@@ -223,6 +228,31 @@ const LANGUAGE_TO_REGISTRY_TYPE: Partial<Record<KnownLanguage, string[]>> = {
go: ["goproxy_server", "git_source"],
} as const;
/**
* Extracts an `Address` value from the given `Registry` value by determining whether it has
* a `url` value, or no `url` value but a `host` value.
*
* @throws A `ConfigurationError` if the `Registry` value contains neither a `url` or `host` field.
*/
function getRegistryAddress(registry: Registry): Address {
if (isDefined(registry.url)) {
return {
url: registry.url,
host: registry.host,
};
} else if (isDefined(registry.host)) {
return {
url: undefined,
host: registry.host,
};
} else {
// The proxy needs one of these to work. If both are defined, the url has the precedence.
throw new ConfigurationError(
"Invalid credentials - must specify host or url",
);
}
}
// getCredentials returns registry credentials from action inputs.
// It prefers `registries_credentials` over `registry_secrets`.
// If neither is set, it returns an empty array.
@@ -231,7 +261,7 @@ export function getCredentials(
registrySecrets: string | undefined,
registriesCredentials: string | undefined,
language: KnownLanguage | undefined,
): Credential[] {
): ValidCredential[] {
const registryTypeForLanguage = language
? LANGUAGE_TO_REGISTRY_TYPE[language]
: undefined;
@@ -265,7 +295,7 @@ export function getCredentials(
);
}
const out: Credential[] = [];
const out: ValidCredential[] = [];
for (const e of parsed) {
if (e === null || typeof e !== "object") {
throw new ConfigurationError("Invalid credentials - must be an object");
@@ -279,12 +309,7 @@ export function getCredentials(
core.setSecret(e.token);
}
if (!isDefined(e.url) && !isDefined(e.host)) {
// The proxy needs one of these to work. If both are defined, the url has the precedence.
throw new ConfigurationError(
"Invalid credentials - must specify host or url",
);
}
const address = getRegistryAddress(e);
// Filter credentials based on language if specified. `type` is the registry type.
// E.g., "maven_feed" for Java/Kotlin, "nuget_repository" for C#.
@@ -327,11 +352,10 @@ export function getCredentials(
out.push({
type: e.type,
host: e.host,
url: e.url,
username: e.username,
password: e.password,
token: e.token,
...address,
});
}
return out;
+4 -4
View File
@@ -11,22 +11,22 @@ import {
ReachabilityBackend,
ReachabilityError,
} from "./reachability";
import { ProxyInfo, Registry } from "./types";
import { ProxyInfo, ValidRegistry } from "./types";
setupTests(test);
class MockReachabilityBackend implements ReachabilityBackend {
public async checkConnection(_registry: Registry): Promise<number> {
public async checkConnection(_registry: ValidRegistry): Promise<number> {
return 200;
}
}
const mavenRegistry: Registry = {
const mavenRegistry: ValidRegistry = {
type: "maven_registry",
url: "https://repo.maven.apache.org/maven2/",
};
const nugetFeed: Registry = {
const nugetFeed: ValidRegistry = {
type: "nuget_feed",
url: "https://api.nuget.org/v3/index.json",
};
+11 -10
View File
@@ -5,7 +5,7 @@ import { HttpsProxyAgent } from "https-proxy-agent";
import { Logger } from "../logging";
import { getErrorMessage } from "../util";
import { ProxyInfo, Registry } from "./types";
import { getAddressString, ProxyInfo, Registry, ValidRegistry } from "./types";
export class ReachabilityError extends Error {
constructor(
@@ -34,7 +34,7 @@ export interface ReachabilityBackend {
* @param registry The registry to try and reach.
* @returns The successful status code (in the `<400` range).
*/
checkConnection: (registry: Registry) => Promise<number>;
checkConnection: (registry: ValidRegistry) => Promise<number>;
}
class NetworkReachabilityBackend implements ReachabilityBackend {
@@ -47,10 +47,10 @@ class NetworkReachabilityBackend implements ReachabilityBackend {
this.agent = new HttpsProxyAgent(`http://${proxy.host}:${proxy.port}`);
}
public async checkConnection(registry: Registry): Promise<number> {
public async checkConnection(registry: ValidRegistry): Promise<number> {
return new Promise((resolve, reject) => {
const req = https.request(
registry.url as string,
getAddressString(registry),
{ agent: this.agent, method: "HEAD", ca: this.proxy.cert },
(res) => {
res.destroy();
@@ -83,8 +83,8 @@ export async function checkConnections(
logger: Logger,
proxy: ProxyInfo,
backend?: ReachabilityBackend,
): Promise<Set<Registry>> {
const result: Set<Registry> = new Set();
): Promise<Set<ValidRegistry>> {
const result: Set<ValidRegistry> = new Set();
// Don't do anything if there are no registries.
if (proxy.registries.length === 0) return result;
@@ -96,22 +96,23 @@ export async function checkConnections(
}
for (const registry of proxy.registries) {
const address = getAddressString(registry);
try {
logger.debug(`Testing connection to ${registry.url}...`);
logger.debug(`Testing connection to ${address}...`);
const statusCode = await backend.checkConnection(registry);
logger.info(
`Successfully tested connection to ${registry.url} (${statusCode})`,
`Successfully tested connection to ${address} (${statusCode})`,
);
result.add(registry);
} catch (e) {
if (e instanceof ReachabilityError && e.statusCode !== undefined) {
logger.error(
`Connection test to ${registry.url} failed. (${e.statusCode})`,
`Connection test to ${address} failed. (${e.statusCode})`,
);
} else {
logger.error(
`Connection test to ${registry.url} failed: ${getErrorMessage(e)}`,
`Connection test to ${address} failed: ${getErrorMessage(e)}`,
);
}
}
+33 -1
View File
@@ -10,9 +10,41 @@ export interface Registry {
url?: string;
}
// If a registry has an `url`, then that takes precedence over the `host` which may or may
// not be defined.
interface HasUrl {
url: string;
host?: string;
}
// If a registry does not have an `url`, then it must have a `host`.
interface WithoutUrl {
url: undefined;
host: string;
}
/**
* A valid `Registry` value must either have a `url` or a `host` value. If it has a `url` value,
* then that takes precedence over the `host` value. If there is no `url` value, then it must
* have a `host` value.
*/
export type Address = HasUrl | WithoutUrl;
/** Gets the address as a string. This will either be the `url` if present, or the `host` if not. */
export function getAddressString(address: Address): string {
if (address.url === undefined) {
return address.host;
} else {
return address.url;
}
}
export type ValidRegistry<T extends Registry = Registry> = T & Address;
export type ValidCredential = ValidRegistry<Credential>;
export interface ProxyInfo {
host: string;
port: number;
cert: string;
registries: Registry[];
registries: ValidRegistry[];
}