首页 > 解决方案 > jenkins plugin - 从插件内部启动和停止舞台

问题描述

首先,一些背景为什么我想要这个疯狂的东西。我在 Jenkins 中构建了一个插件,它为从管道脚本启动的脚本提供了一个 API,以便与 jenkins 独立通信。例如,一个 shell 脚本可以告诉 jenkins 从正在运行的脚本开始一个新阶段。

我已经在脚本和 Jenkins 之间进行了通信,但问题是我现在想尝试从我的代码中的回调开始一个阶段,但我似乎无法弄清楚如何去做。

我尝试过但失败的东西:

开始一个新的StageStep.java
我似乎找不到正确实例化步骤并将其注入生命周期的方法。我已经调查过DSL.java,但似乎无法找到要调用的实例invokeStep(),也无法找出如何DSL.java使用正确的环境进行实例化。

看看StageStepExecution.java并做它所做的。
它似乎要么使用环境变量调用主体,而没有其他任何东西,或者在没有主体时设置一些操作并将状态保存在配置文件中。我不知道 Pipeline: Stage View Plugin 如何与此挂钩,但它似乎没有读取配置文件。我试过设置动作(甚至是通过反射的内部类),但这似乎没有做任何事情。

将自定义字符串作为 Groovy 主体注入并调用它csc.newBodyInvoker()
我想出的一个 hacky 解决方案只是生成 groovy 脚本并像 ParallelStep 一样运行它。但是沙盒不允许我调用new GroovyShell().evaluate(""),如果我批准该调用,“阶段”步骤会引发 MissingMethodException。所以我也没有用正确的环境来实例化脚本。提供 EnvironmentExpander 没有任何区别。

引用和修改工作流/{n}.xml
更改相关阶段的名称workflow/{n}.xml并重新启动服务器会更新阶段的名称,但是将我的自定义阶段修改为看起来像常规阶段似乎并没有将步骤添加为阶段。

我研究过的东西:

我在这个看似微不足道的电话上花了几天时间,但我似乎无法弄清楚。

标签: javajenkinsjenkins-pipelinejenkins-plugins

解决方案


所以我尝试的最新的东西确实有效,并且显示正确,但它并不漂亮。我基本上重新实现了 的实现DSL.invokeStep(),这需要我大量使用反射。这是不安全的,当然会因任何更改而中断,因此我将在 Jenkins 的票证系统中打开一个问题,希望他们会为此添加一个公共界面。我只是希望这不会给我任何奇怪的副作用。

// First, get some environment stuff
CpsThread cpsThread = CpsThread.current();
CpsFlowExecution currentFlowExecution = (CpsFlowExecution) getContext().get(FlowExecution.class);

// instantiate the stage's descriptor
StageStep.DescriptorImpl stageStepDescriptor = new StageStep.DescriptorImpl();

// now we need to put a new FlowNode as the head of the step-stack. This is of course not possible directly,
// but everything is also outside of the sandbox, so putting the class in the same package doesn't work
// get the 'head' field
Field cpsHeadField = CpsThread.class.getDeclaredField("head");
cpsHeadField.setAccessible(true);
Object headValue = cpsHeadField.get(cpsThread);
// get it's value
Method head_get = headValue.getClass().getDeclaredMethod("get");
head_get.setAccessible(true);
FlowNode currentHead = (FlowNode) head_get.invoke(headValue);

// crate a new StepAtomNode starting at the current value of 'head'.
FlowNode an = new StepAtomNode(currentFlowExecution, stageStepDescriptor, currentHead);

// now set this as the new head.
Method head_setNewHead = headValue.getClass().getDeclaredMethod("setNewHead", FlowNode.class);
head_setNewHead.setAccessible(true);
head_setNewHead.invoke(headValue, an);

// Create a new CpsStepContext, and as the constructor is protected, use reflection again
Constructor<?> declaredConstructor = CpsStepContext.class.getDeclaredConstructors()[0];
declaredConstructor.setAccessible(true);
CpsStepContext context = (CpsStepContext) declaredConstructor.newInstance(stageStepDescriptor,cpsThread,currentFlowExecution.getOwner(),an,null);

stageStepDescriptor.checkContextAvailability(context); // Good to check stuff I guess

// Create a new instance of the step, passing in arguments as a Map
Map<String, Object> stageArguments = new HashMap<>();
stageArguments.put("name", "mynutest");
Step stageStep = stageStepDescriptor.newInstance(stageArguments);

// so start the damd thing
StepExecution execution = stageStep.start(context);

// now that we have a callable instance, we set the step on the Cps Thread. Reflection to the rescue
Method mSetStep = cpsThread.getClass().getDeclaredMethod("setStep", StepExecution.class);
mSetStep.setAccessible(true);
mSetStep.invoke(cpsThread, execution);

// Finally. Start running the step
execution.start();

推荐阅读