feat(deploy): commit-status reporting to Git providers
Report deploy status back to the Git provider as a commit status (pending/success/failure) for git-sourced workloads (static + dockerfile). - GitProvider.SetCommitStatus on gitea/github/gitlab over the existing SSRF-safe client; fixed "tinyforge" context so redeploys update one row. postJSON returns status-code-only errors (never echoes the upstream body, which a hostile provider could use to reflect the auth token into the best-effort log line). - Best-effort deploy hook: pending on deploy start, success/failure on outcome, gated on a per-workload report_commit_status flag. Never fails or blocks a deploy; emits nothing on the unchanged-SHA short-circuit. - UI ToggleSwitch (create + edit) + reportCommitStatus in sourceForms.ts + en/ru i18n. - Tests: per-provider state mapping + request shape; reporter gating (enabled/disabled/empty-SHA/nil/error-swallow). Reviewed via go-reviewer + security-reviewer (0 CRITICAL/HIGH; one MEDIUM body-echo log-leak fixed).
This commit is contained in:
@@ -59,6 +59,8 @@ export interface StaticFormState extends GitSourceState {
|
||||
folderPath: string;
|
||||
mode: 'static' | 'deno';
|
||||
renderMarkdown: boolean;
|
||||
/** Report deploy outcome back to the git provider as a commit status. */
|
||||
reportCommitStatus: boolean;
|
||||
}
|
||||
|
||||
/** Dockerfile source: build an image from a Dockerfile in a repo. */
|
||||
@@ -66,6 +68,8 @@ export interface DockerfileFormState extends GitSourceState {
|
||||
contextPath: string;
|
||||
dockerfilePath: string;
|
||||
port: number;
|
||||
/** Report deploy outcome back to the git provider as a commit status. */
|
||||
reportCommitStatus: boolean;
|
||||
}
|
||||
|
||||
// ── Defaults ────────────────────────────────────────────────────────
|
||||
@@ -99,11 +103,23 @@ function emptyGitSourceState(): GitSourceState {
|
||||
}
|
||||
|
||||
export function emptyStaticState(): StaticFormState {
|
||||
return { ...emptyGitSourceState(), folderPath: '', mode: 'static', renderMarkdown: false };
|
||||
return {
|
||||
...emptyGitSourceState(),
|
||||
folderPath: '',
|
||||
mode: 'static',
|
||||
renderMarkdown: false,
|
||||
reportCommitStatus: false
|
||||
};
|
||||
}
|
||||
|
||||
export function emptyDockerfileState(): DockerfileFormState {
|
||||
return { ...emptyGitSourceState(), contextPath: '', dockerfilePath: 'Dockerfile', port: 0 };
|
||||
return {
|
||||
...emptyGitSourceState(),
|
||||
contextPath: '',
|
||||
dockerfilePath: 'Dockerfile',
|
||||
port: 0,
|
||||
reportCommitStatus: false
|
||||
};
|
||||
}
|
||||
|
||||
// ── Parse helpers ───────────────────────────────────────────────────
|
||||
@@ -186,7 +202,9 @@ export function seedStaticState(jsonText: string): StaticFormState {
|
||||
accessToken: strOr(o.access_token, ''),
|
||||
folderPath: strOr(o.folder_path, ''),
|
||||
mode: o.mode === 'deno' ? 'deno' : 'static',
|
||||
renderMarkdown: typeof o.render_markdown === 'boolean' ? o.render_markdown : false
|
||||
renderMarkdown: typeof o.render_markdown === 'boolean' ? o.render_markdown : false,
|
||||
reportCommitStatus:
|
||||
typeof o.report_commit_status === 'boolean' ? o.report_commit_status : false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -201,7 +219,9 @@ export function seedDockerfileState(jsonText: string): DockerfileFormState {
|
||||
accessToken: strOr(o.access_token, ''),
|
||||
contextPath: strOr(o.context_path, ''),
|
||||
dockerfilePath: strOrTruthy(o.dockerfile_path, 'Dockerfile'),
|
||||
port: numOr(o.port, 0)
|
||||
port: numOr(o.port, 0),
|
||||
reportCommitStatus:
|
||||
typeof o.report_commit_status === 'boolean' ? o.report_commit_status : false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -260,7 +280,11 @@ export function staticToConfig(s: StaticFormState, existingJson: string): Record
|
||||
folder_path: s.folderPath,
|
||||
access_token: s.accessToken,
|
||||
mode: s.mode,
|
||||
render_markdown: s.renderMarkdown
|
||||
render_markdown: s.renderMarkdown,
|
||||
// New key appended at the END so existing byte-shape assertions for
|
||||
// the other keys are minimally affected. Storage_* keys (added below
|
||||
// only when present in the existing config) trail this on edit.
|
||||
report_commit_status: s.reportCommitStatus
|
||||
};
|
||||
// Preserve storage_* keys set via the raw JSON editor (not yet surfaced
|
||||
// as form controls) so a form round-trip doesn't silently drop them.
|
||||
@@ -289,6 +313,7 @@ const DOCKERFILE_OWNED_KEYS: ReadonlySet<string> = new Set([
|
||||
'context_path',
|
||||
'dockerfile_path',
|
||||
'port',
|
||||
'report_commit_status',
|
||||
'folder_path',
|
||||
'mode',
|
||||
'render_markdown',
|
||||
@@ -317,6 +342,9 @@ export function dockerfileToConfig(
|
||||
context_path: s.contextPath,
|
||||
dockerfile_path: s.dockerfilePath || 'Dockerfile',
|
||||
port: s.port || 0,
|
||||
// New owned key appended at the END of the owned block (before any
|
||||
// preserved unknown keys) so existing byte-shape assertions hold.
|
||||
report_commit_status: s.reportCommitStatus,
|
||||
...preserved
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user