Merge pull request #2020 from pikasTech/fix/agentrun-private-pr-auth
fix: 投影 Artificer 私有仓 GitHub 凭据
This commit is contained in:
@@ -313,6 +313,7 @@ controlPlane:
|
|||||||
localPostgresExpectedAbsent: true
|
localPostgresExpectedAbsent: true
|
||||||
toolCredentials:
|
toolCredentials:
|
||||||
- extends: controlPlane.templates.toolCredentials.githubPr
|
- extends: controlPlane.templates.toolCredentials.githubPr
|
||||||
|
- extends: controlPlane.templates.toolCredentials.githubRepositoryOverridePikaoa
|
||||||
- extends: controlPlane.templates.toolCredentials.unideskSsh
|
- extends: controlPlane.templates.toolCredentials.unideskSsh
|
||||||
- extends: controlPlane.templates.toolCredentials.githubSsh
|
- extends: controlPlane.templates.toolCredentials.githubSsh
|
||||||
secrets:
|
secrets:
|
||||||
@@ -462,6 +463,7 @@ controlPlane:
|
|||||||
providerCredential:
|
providerCredential:
|
||||||
profile: dsflash-go
|
profile: dsflash-go
|
||||||
- extends: controlPlane.templates.secrets.githubPrRawToken
|
- extends: controlPlane.templates.secrets.githubPrRawToken
|
||||||
|
- extends: controlPlane.templates.secrets.githubRepositoryOverridePikaoaToken
|
||||||
- extends: controlPlane.templates.secrets.unideskSshToken
|
- extends: controlPlane.templates.secrets.unideskSshToken
|
||||||
- extends: controlPlane.templates.secrets.githubSshPrivateKey
|
- extends: controlPlane.templates.secrets.githubSshPrivateKey
|
||||||
- extends: controlPlane.templates.secrets.githubSshKnownHosts
|
- extends: controlPlane.templates.secrets.githubSshKnownHosts
|
||||||
@@ -534,6 +536,19 @@ controlPlane:
|
|||||||
kind: env
|
kind: env
|
||||||
envName: GH_TOKEN
|
envName: GH_TOKEN
|
||||||
secretKey: GH_TOKEN
|
secretKey: GH_TOKEN
|
||||||
|
githubRepositoryOverridePikaoa:
|
||||||
|
id: github-repository-override-pikaoa
|
||||||
|
tool: github
|
||||||
|
purpose: repository-override-pikainc-pikaoa
|
||||||
|
secretRef:
|
||||||
|
namespace: agentrun-v02
|
||||||
|
name: agentrun-v02-tool-github-repository-overrides
|
||||||
|
keys:
|
||||||
|
- PIKAINC_PIKAOA_GH_TOKEN
|
||||||
|
projection:
|
||||||
|
kind: env
|
||||||
|
envName: UNIDESK_GH_TOKEN_PIKAINC_PIKAOA
|
||||||
|
secretKey: PIKAINC_PIKAOA_GH_TOKEN
|
||||||
unideskSsh:
|
unideskSsh:
|
||||||
id: unidesk-ssh
|
id: unidesk-ssh
|
||||||
tool: unidesk-ssh
|
tool: unidesk-ssh
|
||||||
@@ -570,6 +585,15 @@ controlPlane:
|
|||||||
namespace: agentrun-v02
|
namespace: agentrun-v02
|
||||||
name: agentrun-v01-tool-github-pr
|
name: agentrun-v01-tool-github-pr
|
||||||
key: GH_TOKEN
|
key: GH_TOKEN
|
||||||
|
githubRepositoryOverridePikaoaToken:
|
||||||
|
id: tool-github-repository-override-pikaoa-token
|
||||||
|
sourceRef: /root/.unidesk/.env/pikainc-selfmedia-gh-token.txt
|
||||||
|
sourceKey: GH_TOKEN
|
||||||
|
sourceFormat: raw-token
|
||||||
|
targetRef:
|
||||||
|
namespace: agentrun-v02
|
||||||
|
name: agentrun-v02-tool-github-repository-overrides
|
||||||
|
key: PIKAINC_PIKAOA_GH_TOKEN
|
||||||
unideskSshToken:
|
unideskSshToken:
|
||||||
id: tool-unidesk-ssh-token
|
id: tool-unidesk-ssh-token
|
||||||
sourceRef: /root/.unidesk/.state/docker-compose.env
|
sourceRef: /root/.unidesk/.state/docker-compose.env
|
||||||
|
|||||||
@@ -21,18 +21,13 @@ github:
|
|||||||
sourceRef: pikainc-selfmedia-gh-token.txt
|
sourceRef: pikainc-selfmedia-gh-token.txt
|
||||||
sourceKey: GH_TOKEN
|
sourceKey: GH_TOKEN
|
||||||
format: raw-token
|
format: raw-token
|
||||||
|
projectedEnv: UNIDESK_GH_TOKEN_PIKAINC_PIKAOA
|
||||||
- repository: pikainc/selfmedia
|
- repository: pikainc/selfmedia
|
||||||
priority: before-env
|
priority: before-env
|
||||||
root: /root/.unidesk/.env
|
root: /root/.unidesk/.env
|
||||||
sourceRef: pikainc-selfmedia-gh-token.txt
|
sourceRef: pikainc-selfmedia-gh-token.txt
|
||||||
sourceKey: GH_TOKEN
|
sourceKey: GH_TOKEN
|
||||||
format: raw-token
|
format: raw-token
|
||||||
- repository: pikainc/pikaoa
|
|
||||||
priority: before-env
|
|
||||||
root: /root/.unidesk/.env
|
|
||||||
sourceRef: pikainc-selfmedia-gh-token.txt
|
|
||||||
sourceKey: GH_TOKEN
|
|
||||||
format: raw-token
|
|
||||||
prMerge:
|
prMerge:
|
||||||
unknownRetry:
|
unknownRetry:
|
||||||
maxAttempts: 5
|
maxAttempts: 5
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|||||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { resolveToken, type GitHubYamlAuthConfig } from "./gh/auth-and-safety";
|
import { resolveToken, type GitHubYamlAuthConfig } from "./gh/auth-and-safety";
|
||||||
|
import { authRequired } from "./gh/client";
|
||||||
import { resolvePositionalIssueReference, resolvePositionalPrReference } from "./gh/refs";
|
import { resolvePositionalIssueReference, resolvePositionalPrReference } from "./gh/refs";
|
||||||
import type { GitHubOptions, GitHubResolvedNumberReference } from "./gh/types";
|
import type { GitHubOptions, GitHubResolvedNumberReference } from "./gh/types";
|
||||||
|
|
||||||
const originalGhToken = process.env.GH_TOKEN;
|
const originalGhToken = process.env.GH_TOKEN;
|
||||||
const originalGithubToken = process.env.GITHUB_TOKEN;
|
const originalGithubToken = process.env.GITHUB_TOKEN;
|
||||||
|
const projectedEnv = "UNIDESK_TEST_GH_REPOSITORY_OVERRIDE";
|
||||||
|
const originalProjectedToken = process.env[projectedEnv];
|
||||||
const temporaryRoots: string[] = [];
|
const temporaryRoots: string[] = [];
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -14,6 +17,8 @@ afterEach(() => {
|
|||||||
else process.env.GH_TOKEN = originalGhToken;
|
else process.env.GH_TOKEN = originalGhToken;
|
||||||
if (originalGithubToken === undefined) delete process.env.GITHUB_TOKEN;
|
if (originalGithubToken === undefined) delete process.env.GITHUB_TOKEN;
|
||||||
else process.env.GITHUB_TOKEN = originalGithubToken;
|
else process.env.GITHUB_TOKEN = originalGithubToken;
|
||||||
|
if (originalProjectedToken === undefined) delete process.env[projectedEnv];
|
||||||
|
else process.env[projectedEnv] = originalProjectedToken;
|
||||||
while (temporaryRoots.length > 0) rmSync(temporaryRoots.pop()!, { recursive: true, force: true });
|
while (temporaryRoots.length > 0) rmSync(temporaryRoots.pop()!, { recursive: true, force: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,6 +89,67 @@ describe("GitHub repository token overrides", () => {
|
|||||||
expect(selected.probe).toMatchObject({ source: "yaml-token-source", scope: "repository-override", yamlSourcePriority: "before-env" });
|
expect(selected.probe).toMatchObject({ source: "yaml-token-source", scope: "repository-override", yamlSourcePriority: "before-env" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("uses the YAML-declared projected environment for repository overrides", () => {
|
||||||
|
process.env.GH_TOKEN = "global-environment-token";
|
||||||
|
process.env[projectedEnv] = "projected-repository-token";
|
||||||
|
const authConfig = testAuthConfig();
|
||||||
|
authConfig.repositoryOverrides[0] = {
|
||||||
|
...authConfig.repositoryOverrides[0],
|
||||||
|
sourceRef: "missing-token.txt",
|
||||||
|
projectedEnv,
|
||||||
|
};
|
||||||
|
|
||||||
|
const selected = resolveToken("pikainc/selfmedia", false, authConfig);
|
||||||
|
|
||||||
|
expect(selected.token).toBe("projected-repository-token");
|
||||||
|
expect(selected.probe).toMatchObject({
|
||||||
|
source: "yaml-token-projection",
|
||||||
|
scope: "repository-override",
|
||||||
|
sourceExists: false,
|
||||||
|
sourceKeyPresent: false,
|
||||||
|
projectedEnv,
|
||||||
|
projectedEnvPresent: true,
|
||||||
|
valuesPrinted: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("fails closed when a declared repository override is missing", () => {
|
||||||
|
process.env.GH_TOKEN = "global-environment-token";
|
||||||
|
delete process.env[projectedEnv];
|
||||||
|
const authConfig = testAuthConfig();
|
||||||
|
authConfig.repositoryOverrides[0] = {
|
||||||
|
...authConfig.repositoryOverrides[0],
|
||||||
|
sourceRef: "missing-token.txt",
|
||||||
|
projectedEnv,
|
||||||
|
};
|
||||||
|
|
||||||
|
const selected = resolveToken("pikainc/selfmedia", true, authConfig);
|
||||||
|
const failure = authRequired("pikainc/selfmedia", "pr view", selected.probe);
|
||||||
|
|
||||||
|
expect(selected.token).toBeNull();
|
||||||
|
expect(selected.probe).toMatchObject({
|
||||||
|
present: false,
|
||||||
|
scope: "repository-override",
|
||||||
|
sourceExists: false,
|
||||||
|
sourceKeyPresent: false,
|
||||||
|
projectedEnv,
|
||||||
|
projectedEnvPresent: false,
|
||||||
|
ghFallbackAttempted: false,
|
||||||
|
valuesPrinted: false,
|
||||||
|
});
|
||||||
|
expect(failure).toMatchObject({
|
||||||
|
ok: false,
|
||||||
|
degradedReason: "missing-token",
|
||||||
|
runnerDisposition: "infra-blocked",
|
||||||
|
token: {
|
||||||
|
scope: "repository-override",
|
||||||
|
projectedEnvPresent: false,
|
||||||
|
valuesPrinted: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(JSON.stringify(failure)).not.toContain("global-environment-token");
|
||||||
|
});
|
||||||
|
|
||||||
test("matches repository overrides without case sensitivity", () => {
|
test("matches repository overrides without case sensitivity", () => {
|
||||||
delete process.env.GH_TOKEN;
|
delete process.env.GH_TOKEN;
|
||||||
delete process.env.GITHUB_TOKEN;
|
delete process.env.GITHUB_TOKEN;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface GitHubYamlTokenSourceConfig {
|
|||||||
sourceRef: string;
|
sourceRef: string;
|
||||||
sourceKey: string;
|
sourceKey: string;
|
||||||
format: "env" | "raw-token";
|
format: "env" | "raw-token";
|
||||||
|
projectedEnv?: string;
|
||||||
scope: "repository-override" | "global";
|
scope: "repository-override" | "global";
|
||||||
configRef: string;
|
configRef: string;
|
||||||
repository?: string;
|
repository?: string;
|
||||||
@@ -88,13 +89,16 @@ export function tokenFromYamlSource(repo: string, authConfig: GitHubYamlAuthConf
|
|||||||
const sourcePath = resolveYamlSourcePath(sourceRoot, config.sourceRef);
|
const sourcePath = resolveYamlSourcePath(sourceRoot, config.sourceRef);
|
||||||
const sourceExists = existsSync(sourcePath);
|
const sourceExists = existsSync(sourcePath);
|
||||||
const values = sourceExists ? parseYamlTokenSource(readFileSync(sourcePath, "utf8"), config) : {};
|
const values = sourceExists ? parseYamlTokenSource(readFileSync(sourcePath, "utf8"), config) : {};
|
||||||
const token = values[config.sourceKey] ?? null;
|
const sourceToken = values[config.sourceKey] ?? null;
|
||||||
|
const projectedToken = config.projectedEnv === undefined ? null : process.env[config.projectedEnv] ?? null;
|
||||||
|
const token = sourceToken ?? projectedToken;
|
||||||
const present = token !== null && token.length > 0;
|
const present = token !== null && token.length > 0;
|
||||||
|
const projectedEnvPresent = projectedToken !== null && projectedToken.length > 0;
|
||||||
return {
|
return {
|
||||||
token: present ? token : null,
|
token: present ? token : null,
|
||||||
probe: {
|
probe: {
|
||||||
present,
|
present,
|
||||||
source: present ? "yaml-token-source" : null,
|
source: sourceToken !== null ? "yaml-token-source" : projectedEnvPresent ? "yaml-token-projection" : null,
|
||||||
scope: config.scope,
|
scope: config.scope,
|
||||||
ghFallbackAttempted: false,
|
ghFallbackAttempted: false,
|
||||||
yamlSourceAttempted: true,
|
yamlSourceAttempted: true,
|
||||||
@@ -104,7 +108,8 @@ export function tokenFromYamlSource(repo: string, authConfig: GitHubYamlAuthConf
|
|||||||
sourceRef: config.sourceRef,
|
sourceRef: config.sourceRef,
|
||||||
sourceKey: config.sourceKey,
|
sourceKey: config.sourceKey,
|
||||||
sourceExists,
|
sourceExists,
|
||||||
sourceKeyPresent: present,
|
sourceKeyPresent: sourceToken !== null && sourceToken.length > 0,
|
||||||
|
...(config.projectedEnv === undefined ? {} : { projectedEnv: config.projectedEnv, projectedEnvPresent }),
|
||||||
tokenFingerprint: present ? fingerprintToken(token) : null,
|
tokenFingerprint: present ? fingerprintToken(token) : null,
|
||||||
valuesPrinted: false,
|
valuesPrinted: false,
|
||||||
},
|
},
|
||||||
@@ -132,6 +137,7 @@ export function ghAuthToken(): string | null {
|
|||||||
export function resolveToken(repo: string, allowGhFallback: boolean, authConfig?: GitHubYamlAuthConfig): { token: string | null; probe: GitHubTokenProbe } {
|
export function resolveToken(repo: string, allowGhFallback: boolean, authConfig?: GitHubYamlAuthConfig): { token: string | null; probe: GitHubTokenProbe } {
|
||||||
const yamlToken = tokenFromYamlSource(repo, authConfig);
|
const yamlToken = tokenFromYamlSource(repo, authConfig);
|
||||||
if (yamlToken.probe.yamlSourcePriority === "before-env" && yamlToken.probe.present) return yamlToken;
|
if (yamlToken.probe.yamlSourcePriority === "before-env" && yamlToken.probe.present) return yamlToken;
|
||||||
|
if (yamlToken.probe.scope === "repository-override" && !yamlToken.probe.present) return yamlToken;
|
||||||
const envProbe = tokenFromEnvironment();
|
const envProbe = tokenFromEnvironment();
|
||||||
if (envProbe.present) {
|
if (envProbe.present) {
|
||||||
const token = envProbe.source === "GH_TOKEN" ? process.env.GH_TOKEN ?? null : process.env.GITHUB_TOKEN ?? null;
|
const token = envProbe.source === "GH_TOKEN" ? process.env.GH_TOKEN ?? null : process.env.GITHUB_TOKEN ?? null;
|
||||||
@@ -179,6 +185,7 @@ function readGitHubYamlAuthConfig(): GitHubYamlAuthConfig {
|
|||||||
sourceRef: stringField(override, "sourceRef", configRef),
|
sourceRef: stringField(override, "sourceRef", configRef),
|
||||||
sourceKey: envKeyField(override, "sourceKey", configRef),
|
sourceKey: envKeyField(override, "sourceKey", configRef),
|
||||||
format: tokenSourceFormatField(override, "format", configRef),
|
format: tokenSourceFormatField(override, "format", configRef),
|
||||||
|
...(override.projectedEnv === undefined ? {} : { projectedEnv: envKeyField(override, "projectedEnv", configRef) }),
|
||||||
scope: "repository-override" as const,
|
scope: "repository-override" as const,
|
||||||
configRef,
|
configRef,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -388,6 +388,8 @@ function renderAuthStatus(result: GitHubCommandResult): string {
|
|||||||
`priority=${ghText(token.yamlSourcePriority)}`,
|
`priority=${ghText(token.yamlSourcePriority)}`,
|
||||||
`sourceRef=${ghText(token.sourceRef)}`,
|
`sourceRef=${ghText(token.sourceRef)}`,
|
||||||
`key=${ghText(token.sourceKey)}`,
|
`key=${ghText(token.sourceKey)}`,
|
||||||
|
`projectedEnv=${ghText(token.projectedEnv)}`,
|
||||||
|
`projectedEnvPresent=${ghText(token.projectedEnvPresent)}`,
|
||||||
`fingerprint=${ghText(token.tokenFingerprint)}`,
|
`fingerprint=${ghText(token.tokenFingerprint)}`,
|
||||||
`ghFallback=${ghText(token.ghFallbackAttempted)}`,
|
`ghFallback=${ghText(token.ghFallbackAttempted)}`,
|
||||||
].filter((item) => !item.endsWith("=-") && !item.endsWith("=undefined")).join(" ");
|
].filter((item) => !item.endsWith("=-") && !item.endsWith("=undefined")).join(" ");
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ export type GitHubCommandFailure = GitHubCommandResult & { ok: false };
|
|||||||
|
|
||||||
export interface GitHubTokenProbe {
|
export interface GitHubTokenProbe {
|
||||||
present: boolean;
|
present: boolean;
|
||||||
source: "GH_TOKEN" | "GITHUB_TOKEN" | "yaml-token-source" | "gh-auth-token" | null;
|
source: "GH_TOKEN" | "GITHUB_TOKEN" | "yaml-token-source" | "yaml-token-projection" | "gh-auth-token" | null;
|
||||||
scope?: "repository-override" | "global";
|
scope?: "repository-override" | "global";
|
||||||
ghFallbackAttempted: boolean;
|
ghFallbackAttempted: boolean;
|
||||||
ghBinaryFound?: boolean;
|
ghBinaryFound?: boolean;
|
||||||
@@ -423,6 +423,8 @@ export interface GitHubTokenProbe {
|
|||||||
sourceKey?: string;
|
sourceKey?: string;
|
||||||
sourceExists?: boolean;
|
sourceExists?: boolean;
|
||||||
sourceKeyPresent?: boolean;
|
sourceKeyPresent?: boolean;
|
||||||
|
projectedEnv?: string;
|
||||||
|
projectedEnvPresent?: boolean;
|
||||||
tokenFingerprint?: string | null;
|
tokenFingerprint?: string | null;
|
||||||
valuesPrinted?: false;
|
valuesPrinted?: false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user