Merge remote-tracking branch 'origin/master' into feat/pixel_art
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled

This commit is contained in:
2026-08-15 10:29:35 +00:00
5 changed files with 79 additions and 9 deletions
+8 -4
View File
@@ -47,6 +47,10 @@ pipeline {
steps {
sh '''
set -euo pipefail
ACTION="${ACTION:-}"
SOURCE_BRANCH="${SOURCE_BRANCH:-}"
COMMIT_HASH="${COMMIT_HASH:-}"
DEPLOYMENT_ID="${DEPLOYMENT_ID:-}"
case "${ACTION}" in DEPLOY|STATUS|UNINSTALL) ;; *) exit 1 ;; esac
if [ -n "${SOURCE_BRANCH}" ]; then
git check-ref-format --branch "${SOURCE_BRANCH}" >/dev/null
@@ -89,13 +93,13 @@ pipeline {
withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) {
sh '''
set -euo pipefail
SOURCE_BRANCH="${SOURCE_BRANCH}" \
COMMIT_HASH="${COMMIT_HASH}" \
GIT_REMOTE_URL="${GIT_REMOTE_URL}" \
SOURCE_BRANCH="${SOURCE_BRANCH:-}" \
COMMIT_HASH="${COMMIT_HASH:-}" \
GIT_REMOTE_URL="${GIT_REMOTE_URL:-}" \
SOURCE_COMMIT_FILE=".jenkins-source-commit" \
GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="true" \
GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \
"${WORKSPACE}/scripts/jenkins-checkout-source.sh"
bash "${WORKSPACE}/scripts/jenkins-checkout-source.sh"
'''
}
}
+31 -1
View File
@@ -5,7 +5,37 @@
<keepDependencies>false</keepDependencies>
<properties>
<hudson.model.ParametersDefinitionProperty>
<parameterDefinitions/>
<parameterDefinitions>
<hudson.model.ChoiceParameterDefinition>
<name>ACTION</name>
<description>部署、查询状态或卸载分支预览容器</description>
<choices class="java.util.Arrays$ArrayList">
<a class="string-array">
<string>DEPLOY</string>
<string>STATUS</string>
<string>UNINSTALL</string>
</a>
</choices>
</hudson.model.ChoiceParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>SOURCE_BRANCH</name>
<description>源码分支;DEPLOY 和 UNINSTALL 必填</description>
<defaultValue></defaultValue>
<trim>true</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>COMMIT_HASH</name>
<description>可选;必须是 SOURCE_BRANCH 历史中的 commit</description>
<defaultValue></defaultValue>
<trim>true</trim>
</hudson.model.StringParameterDefinition>
<hudson.model.StringParameterDefinition>
<name>DEPLOYMENT_ID</name>
<description>预览实例稳定 ID</description>
<defaultValue></defaultValue>
<trim>true</trim>
</hudson.model.StringParameterDefinition>
</parameterDefinitions>
</hudson.model.ParametersDefinitionProperty>
</properties>
<definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition" plugin="workflow-cps">
+20
View File
@@ -70,6 +70,21 @@ assertIncludes(
'disableConcurrentBuilds()',
'Jenkins Job 必须禁止并发构建。',
);
assertIncludes(
jenkinsfile,
'COMMIT_HASH="${COMMIT_HASH:-}"',
'Jenkins Checkout 阶段必须允许 COMMIT_HASH 未导出或为空。',
);
assertIncludes(
jenkinsfile,
'bash "${WORKSPACE}/scripts/jenkins-checkout-source.sh"',
'Jenkins Checkout 阶段必须显式使用 bash 执行源码检出脚本。',
);
assertExcludes(
jenkinsfile,
'COMMIT_HASH="${COMMIT_HASH}"',
'Jenkins Checkout 阶段不得在 set -u 下直接展开未导出的 COMMIT_HASH。',
);
assertIncludes(deployer, 'flock -x 9', '部署脚本必须使用跨进程独占锁。');
assertIncludes(
deployer,
@@ -132,6 +147,11 @@ assertIncludes(
'<scriptPath>jenkins/Jenkinsfile.preview-deployer</scriptPath>',
'Jenkins Job 必须从受保护 master 读取固定流水线。',
);
assertIncludes(
jobConfig,
'<hudson.model.ChoiceParameterDefinition>',
'Jenkins Job 首次触发前必须已经声明 buildWithParameters 参数。',
);
assertIncludes(
nginx,
'location ^~ /api/preview-deployer/',
@@ -274,11 +274,27 @@ impl JenkinsClient {
}
fn resolve_trusted_url(&self, value: &str) -> Result<Url, String> {
let url = Url::parse(value)
let returned = Url::parse(value)
.or_else(|_| self.job_url.join(value))
.map_err(|_| "Jenkins 返回了无效 URL".to_string())?;
self.ensure_same_origin(&url)?;
Ok(url)
if !returned.username().is_empty()
|| returned.password().is_some()
|| !returned.path().starts_with(self.root_url.path())
{
return Err("Jenkins 返回了非受信源 URL".to_string());
}
let relative_path = returned
.path()
.strip_prefix(self.root_url.path())
.ok_or_else(|| "Jenkins 返回了非受信路径".to_string())?;
let mut trusted = self
.root_url
.join(relative_path)
.map_err(|_| "Jenkins 返回了无效 URL".to_string())?;
trusted.set_query(returned.query());
trusted.set_fragment(None);
self.ensure_same_origin(&trusted)?;
Ok(trusted)
}
fn ensure_same_origin(&self, url: &Url) -> Result<(), String> {
@@ -95,7 +95,7 @@ async fn mock_trigger(
async fn mock_queue(axum::extract::Path(id): axum::extract::Path<u64>) -> Json<Value> {
Json(
json!({"cancelled": false, "executable": {"url": format!("/jenkins/job/shared/job/Genarrative-Preview-Deployer/{id}/")}}),
json!({"cancelled": false, "executable": {"url": format!("http://192.168.35.82:8080/jenkins/job/shared/job/Genarrative-Preview-Deployer/{id}/")}}),
)
}