CI: Continuous Integration in 5 Minutes
After reading the article to the very end, you will guess why the beaver in the box was chosen as the KDPV.
To all health, comrades habrozhiteli. More recently, I was faced with the need to raise and configure the “Continuous Integration” service (hereinafter CI) on one very small project, very indirectly related to my work. Time was running out, so I decided to try something new (previously I used only Travis and Jenkins). The main selection criterion was: "simplicity and speed of deployment of the system on the integration server."
Under the cut is a short story and the resulting tool for CI, written in two evenings on Bash.
Idea
Maybe I searched poorly, maybe I wanted to make a sacrifice to the god of bicycles, but I didn’t catch any CI service that could just be “thrown” to the server, put a couple of webhooks in the Github configuration and forget about DevOps. Therefore, I decided not to waste time searching anymore (and I spent about 1-2 hours), and to write something simple on Bash.
The idea was this: write the simplest CI-trigger on Bash, which will go through a chain (pipeline, pipe) of functions that are configured from another (configuration) script.
The advantages of this solution are obvious:
- You can write it in ten minutes
- It will be very flexible.
- Nothing is needed to deploy CI, except to transfer the CI-trigger file and configuration to the integration server
I’m thinking about the disadvantages of this decision, you yourself have already guessed. I will give only a few:
- No user-friendly and customizable GUI
- There are no ready-made solutions and plug-ins, everything will have to be written on Bash or available to him to call
- Some knowledge of project preparation, deployment and testing is required.
Implementation
The CI-trigger itself is elementary:
Ci trigger
config=`pwd`/ci.config # Адрес конфигурационного скрипта
log=`pwd`/ci.log # Адрес лог-файла
# Разбор аргументов
# ...
# Дефолтные реализации функций-интеграции
function ci_bootstrap {
return 0
}
function ci_update {
return 0
}
function ci_analyse {
return 0
}
function ci_build {
return 0
}
function ci_unit_test {
return 0
}
function ci_deploy {
return 0
}
function ci_test {
return 0
}
function ci_archive {
return 0
}
function ci_report {
return 0
}
function ci_error {
return 0
}
# Подключение конфигурации и выполнение интеграции
. $config &&\
( \
ci_bootstrap &&\
ci_update &&\
ci_analyse &&\
ci_build &&\
ci_unit_test &&\
ci_deploy &&\
ci_test &&\
ci_archive &&\
ci_report || ci_error
) 1>>$log 2>&1
I did not cite the full script code, omitting the logic of parsing command line arguments, since this is trivial and not related to the task (at the end of the article there will be a link to the project repository in which you can see the source code without abbreviations).
As you can see, it all comes down to declaring 9 integration functions that are called pipelined to perform the integration when the CI-trigger is launched. Both output stream of the pipeline are combined in one log file , which acts as a report on the results of integration.
Before executing the integration pipeline, the configuration script is called on behalf of CI-tirgger (
. $config
), allowing it to override any functions. This is the whole “magic” of the solution. Due to the fact that the configuration script is written in Bash, we can use any logic to perform the integration by simply grouping it into functions.Configuration example
# Переход в каталог проекта
cd my-project
# Подготовка проекта к сборке
function ci_bootstrap {
mysql -uadmin -pmy_pass -e "DROP DATABASE db; CREATE DATABASE db"
}
# Загрузка изменений исходных кодов
function ci_update {
if test -d .git; then
return git pull
else
return git clone https://github.com/vendor/project ./
fi
}
# Сборка
function ci_build {
return npm install && npm run build
}
# Запуск модульных тестов
function ci_unit_test {
return npm run unit_test
}
# Развертывание проекта
function ci_deploy {
return mysql -uadmin -pmy_pass db < migration/schema.sql &&\
mysql -uadmin -pmy_pass db < migration/data.sql
}
# Уведомление о результатах интеграции
function ci_report {
return mail -s "CI report" my@mail.com < $log
}
# Уведомление об ошибке
function ci_error {
echo "== Error =="
return mail -s "CI report" my@mail.com < $log
}
Now we only need to configure the CI-trigger call logic in accordance with our requirements.
Periodic call
To do this, just configure Cron, for example, like this:
crontab
0 0 * * * /home/user/ci/trigger
Change Call
This solution requires the implementation of a mechanism responsible for listening to the port of the integration server with a CI-trigger call when accessing it. I suggest using netcat for this and the following simple Bash script:
Listening port
while true; do
{ echo -ne "HTTP/1.0 200 OK\r\n\r\n"; } | nc -v -l -p 8000 && /home/user/ci/trigger
done
Now you need to configure the version control system that we use to perform an HTTP request to this port with each push commits, for example using Curl:
.git / hooks / post-commit
curl -X POST http://ci-server.com:8000
Links and all that
Naturally, this solution is far from ideal, you will have to “work on your hands” a lot in order to use it in a large project, but for a quick launch of the CI service it is quite suitable (I think so!).