jenkins插件-从插件内部启动和停止舞台

时间:2019-10-16 09:17:35

标签: java jenkins jenkins-pipeline jenkins-plugins

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

我已经在脚本和Jenkins之间进行了通信,但是问题是我现在想尝试从代码中的回调开始一个阶段,但是我似乎不知道该怎么做。

我尝试过但失败的东西:

开始新的StageStep.java
我似乎找不到正确实例化并将步骤注入生命周期的方法。我研究了DSL.java,但似乎无法到达实例来调用invokeStep(),也无法找出如何在正确的环境下实例化DSL.java的情况。

查看StageStepExecution.java,然后执行操作。
似乎不是通过环境变量调用主体,而是在没有主体时设置一些操作并将状态保存在配置文件中。我无法确定“管道:舞台视图插件”是如何挂钩的,但是似乎无法读取配置文件。我尝试设置动作(甚至通过反射设置内部类),但似乎没有任何作用。

将自定义字符串作为Groovy主体注入,并使用csc.newBodyInvoker()
进行调用 我想到的一个骇人解决方案只是生成groovy脚本并像ParallelStep一样运行它。但是沙盒不允许我调用new GroovyShell().evaluate(""),如果我批准该调用,则“阶段”步骤将引发MissingMethodException。因此,我也不会在正确的环境中使脚本实例化。提供EnvironmentExpander并没有什么不同。

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

我研究过的东西

  • 如果其他某个插件执行了类似的操作,但是我找不到启动其他步骤的插件示例。
  • Jenkins如何处理脚本并开始执行步骤,但是似乎在解析脚本之后似乎直接通过方法名称调用了每个步骤,而我发现没有任何办法可以挂钩。
  • 其他通过其他方法使用StageView的插件,但找不到任何插件。
  • 在正在运行的线程上添加一个AtomNode作为头部,但是我找不到如何替换/添加头部的方法,犹豫着是否让jenkins的线程陷入混乱。

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

1 个答案:

答案 0 :(得分:0)

因此,我尝试过的最新操作实际上可以正常工作,并且可以正确显示,但是效果不佳。 我基本上重新实现了DSL.invokeStep()的实现,这需要我使用反射A LOT。这是不安全的,并且会随着任何变化而中断,因此我将在詹金斯的票务系统中打开一个问题,希望他们会为此添加一个公共接口。我只是希望这不会给我带来任何奇怪的副作用。

// 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();