плагин jenkins - запуск и остановка сцены изнутри плагина - PullRequest
0 голосов
/ 16 октября 2019

Сначала немного предыстории, почему я хочу эту сумасшедшую вещь. Я создаю плагин в Jenkins, который предоставляет API для скриптов, которые запускаются из конвейерного скрипта для независимой связи с jenkins. Например, shell-скрипт может затем сказать jenkins начать новый этап с запущенного скрипта.

У меня есть связь между скриптом и работающим Jenkins, но проблема в том, что я теперь хочу попробовать иначать этап с обратного вызова в моем коде, но я не могу понять, как это сделать.

Материал, который я пробовал и потерпел неудачу в:

Начать новый StageStep.java
Кажется, я не могу найти способ правильно создать экземпляр и ввести шаг в жизненный цикл. Я изучил DSL.java, но, похоже, не могу найти экземпляр для вызова invokeStep(), и при этом я не смог выяснить, как создать экземпляр DSL.java в правильной среде.

Посмотрите на StageStepExecution.java и сделайте то, что он делает.
Кажется, что он либо вызывает тело с переменной среды и ничего больше, либо устанавливает некоторые действия и сохраняет состояние в файле конфигурации, когда у него нет тела. Я не мог выяснить, как подключается плагин Pipeline: Stage View, но, похоже, он не читает файл конфигурации. Я попытался установить действия (даже внутренний класс с помощью отражения), но это, похоже, ничего не делало.

Вставить пользовательскую строку как тело Groovy и вызвать ее с помощью csc.newBodyInvoker()
Хакерским решением, которое я придумал, было просто сгенерировать скрипт groovy и запустить его, как это делает ParallelStep. Но песочница не позволяет мне вызывать new GroovyShell().evaluate(""), и если я одобряю этот вызов, шаг 'stage' вызывает исключение MissingMethodException. Поэтому я также не навязываю сценарий с правильной средой. Предоставление EnvironmentExpander не имеет никакого значения.

Ссылка и изменение рабочего процесса / {n} .xml
Изменение имени этапа в соответствующем workflow/{n}.xml и перезагрузка сервераобновляет имя сцены, но изменение моего пользовательского этапа, чтобы оно выглядело как обычное, похоже, не добавляет шаг в качестве этапа.

Материал, который я исследовал:

  • Если какой-то другой плагин делает что-то подобное, но я не смог найти ни одного примера плагинов, запускающих другие шаги.
  • Как Дженкинс обрабатывает сценарии и запускает шаги, но кажется, чтокаждый шаг вызывается напрямую через имя метода после разбора скрипта, и я не нашел способа подключиться к этому.
  • Другие плагины, использующие StageView через другие методы, но я не смог найти ни одного.
  • добавить AtomNode в качестве заголовка на работающий поток, но я не смог найти, как заменить / добавить головку, и не решаюсь возиться с потоками Дженкинса.

Я провел несколько дней за этим, казалось бы, тривиальным звонком, но, похоже, не могу понять это.

1 Ответ

0 голосов
/ 17 октября 2019

Так что последнее, что я попробовал, на самом деле сработало и отображается правильно, но это не красиво. Я в основном переопределил реализацию 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();
...