Это последний из серии сообщений о непрерывной доставке, основанных на моем локальном стеке 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; @RestController public 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 @AutoConfigureMockMvc public 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=8888 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 . |