Это последний из серии сообщений о непрерывной доставке, основанных на моем локальном стеке Docker (см. Первый и второй посты здесь). В этом посте я использую простой проект Spring Boot, чтобы показать, как использовать концепцию « конвейер как код ». Пожалуйста, обратите внимание, что это только пример, и гораздо больше возможно. Приложение, которое я использую, взято с сайта Spring Boot . Jenkinsfile вдохновлен тем, что в этом посте, но мне пришлось изменить некоторые вещи, чтобы он работал с моим стеком. Источники моего проекта можно найти здесь . Я объясню наиболее важные фрагменты в этом посте.
Конвейер, который я использую, содержит следующие этапы:
этап строительства
На этапе сборки я использую плагин GitLab для проверки источников моего проекта. Я также поместил текущий commitId в textFile в рабочем каталоге. Затем я использую Maven (тот, который мы назвали «M3» в конфигурации Jenkins, как я описал здесь ) для упаковки кода. Я также проверяю, что commitId передается в качестве параметра в Maven.
этап развертывания
на этапе развертывания я закрываю работающий экземпляр приложения, отправляя «true» в путь / shutdown. Затем я просто запускаю банку, которую я построил на предыдущем шаге. После этого задание ожидает, пока приложение ответит на простой запрос.
тест на дым
На этом простом шаге тестирования я сравниваю возвращенный commitId моего развернутого сервиса с commitId, который мы получили, когда я извлек последний код фиксации. Если все прошло хорошо, эти два идентификатора должны совпадать, если не что-то в цепочке пошло не так.
Вот и все для этого примера. Давайте посмотрим, что это значит для исходного кода. Поскольку это проект Maven, я начинаю с pom.xml:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <!-- used for metrics like status, health etc --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <!-- used for unit tests --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies> |
Никаких особых зависимостей для этого проекта не требуется. Spring-boot-starter-web используется для нашего REST-контроллера. « Sprint-boot-starter-привод » может использоваться для проверки работоспособности и многого другого .
Наконец, ‘spring-boot-starter-test’ используется для того, чтобы иметь возможность (модульно) протестировать контроллер.
Давайте посмотрим на источники Java. Приложение просто запускает приложение Spring Boot. Класс Controller также очень прост:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
package hello;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.RequestMapping;@RestControllerpublic class HelloController { @RequestMapping("/") public String index() { return "Greetings from Spring Boot!"; } } |
Как видите, я просто возвращаю фиксированную строку, когда GET-запрос поступает в ‘/’. Тестовый класс имеет следующий тестовый код:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
/** * Created by pascal on 19/01/2017. */@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvcpublic class HelloControllerTest { @Autowired private MockMvc mvc; @Test public void getHello() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string(equalTo("Greetings from Spring Boot!"))); }} |
Это также просто, я думаю, я ожидаю, что фиксированная строка будет ответом на запрос GET. Рядом с кодом Java находится файл application.properties:
|
1
2
3
4
5
6
|
server.port=8888info.app.name=@project.name@info.app.description=@project.description@info.app.version=@project.version@info.app.commitid=@commitid@endpoints.shutdown.enabled=true |
Помимо двух функциональных свойств: порта, на котором мы запускаем приложение (8888), и возможности завершать работу приложения, вызывая конечную точку (endpoints.shutdown.enabled = true), остальные должны отображаться при вызове конечной точки ‘/ Информация’. Параметры @… @ будут заменены реальными значениями на Maven, поскольку мы фильтруем ресурсы:
|
01
02
03
04
05
06
07
08
09
10
|
...<resources> <!-- used for variable substitution in application.properties --> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource></resources>... |
Наконец у нас есть Jenkinsfile в проекте:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
import groovy.json.JsonSlurper; properties([[$class: 'GitLabConnectionProperty', gitLabConnection: 'my-gitlab-connection']])node{ stage 'Build, Test and Package' env.PATH = "${tool 'M3'}/bin:${env.PATH}" checkout scm // workaround, taken from https://github.com/jenkinsci/pipeline-examples/blob/master/pipeline-examples/gitcommit/gitcommit.groovy def commitid = sh(returnStdout: true, script: 'git rev-parse HEAD').trim() def workspacePath = pwd() sh "echo ${commitid} > ${workspacePath}/expectedCommitid.txt" withMaven( maven: 'M3', mavenSettingsConfig: 'a1adf035-653b-410d-b5a6-16b6da77b322', mavenLocalRepo: '.repository') { // Run the maven build sh "mvn clean package -Dcommitid=${commitid}" }} node{ stage 'Stop, Deploy and Start' // shutdown // copy file to target location sh 'cp target/*.jar /tmp/' // start the application sh 'nohup java -jar /tmp/*.jar &' // wait for application to respond} node{ stage 'Smoketest' def workspacePath = pwd() if (deploymentOk()){ return 0 } else { return 1 }} def deploymentOk(){ def workspacePath = pwd() expectedCommitid = new File("${workspacePath}/expectedCommitid.txt").text.trim() actualCommitid = readCommitidFromJson() println "expected commitid from txt: ${expectedCommitid}" println "actual commitid from json: ${actualCommitid}" return expectedCommitid == actualCommitid} def readCommitidFromJson() { def workspacePath = pwd() def slurper = new JsonSlurper() def json = slurper.parseText(new File("${workspacePath}/info.json").text) def commitid = json.app.commitid return commitid} |
Я описал работу сценария ранее. Есть три важных константы, которые должны соответствовать нашей установке Jenkins:
- В операторе:
properties([[$class: 'GitLabConnectionProperty', gitLabConnection: 'my-gitlab-connection']])‘ my-gitlab-connection ‘ соответствует имени, которое я дал своему gitlabConnection в плагине Jenkins, как я описал здесь . - Как я описал перед «M3» в заявлении:
env.PATH = "${tool 'M3'}/bin:${env.PATH}"должен соответствовать установке Maven в Jenkins, как я описал здесь . - Наконец, есть строка
mavenSettingsConfig: 'a1adf035-653b-410d-b5a6-16b6da77b322'. Упомянутый здесь идентификатор скопирован из файла настроек, который я настроил с помощью плагина Config File Provider, как описано здесь .
Это все об источниках проекта. Позвольте мне показать вам, как создать конвейерную работу в Jenkins. В панели управления выберите создание нового задания типа «конвейер»:

Далее настройте эту работу, где самое важное — использовать Jenkinsfile, полученный из git. Чтобы настроить это, мы должны использовать имя пользователя / пароль для входа в Gitlab (я пока не нашел способа использовать плагин Gitlab. Вы также можете использовать другое хранилище здесь, если вы хотите, чтобы ваши Jenkinsfiles были отделены от вашего проекта. источники):

Теперь, когда я запускаю задание, на последнем шаге произойдет сбой со следующей ошибкой:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: сценариям не разрешено использовать новый java.io.File java.lang.String
в org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist.rejectNew (StaticWhitelist.java:187)
….
Существует одна последняя настройка, чтобы сделать эту работу успешной. По умолчанию определенные действия не разрешены конвейерным заданием, поэтому я должен сказать Дженкинсу, что в этом случае они разрешены.
Для этого перейдите к «Управление Jenkins» и перейдите к «Утверждение сценариев в процессе»:

Существует упоминание о возможной уязвимости безопасности, которую вы должны утвердить, прежде чем задание позволит выполнить действие:

После нажатия кнопки «Подтвердить» и повторного запуска задания будет еще одна уязвимость, которая должна быть утверждена для успешного завершения задания.
Теперь сборка будет успешной для всех трех этапов, как показано на панели инструментов:

На этом завершается пример непрерывной доставки и конвейерной обработки в виде кода. Как упоминалось ранее, это просто очень простой пример конвейера, но вы можете использовать его, чтобы начать работу с концепцией и извлечь из нее гораздо больше.
| Ссылка: | Передайте в виде кода приложение Spring Boot от нашего партнера по JCG Паскаля Альмы в блоге Pragmatic Integrator . |