Continuos Integration – temat znany i lubiany, chociaż w wielu firmach temat traktowany po macoszemu (zwłaszcza tych mniejszych). Jeżeli zapyta się ktoś o dostępne na rynku rozwiązania, to bez zastanowienia odpowiemy: Jenkins, GitLab CI, Travis CI. Ostatnio zainteresowało mnie bardziej rozwiązanie udostępniane przez GitLaba i chciałem przetestować jego działanie w praktyce. Odkąd znam trochę Dockera to nie potrafię tworzyć projektów deweloperskich bez niego, dlatego chciałem spróbować oba światy połączyć i zobaczyć czy zaiskrzy. Na szczęście się to udało, co chcę wam dzisiaj pokazać.
Stworzymy dzisiaj bardzo prostą (jak zwykle, ale tak najłatwiej pokazać jakąś ideę!) aplikację, jak zwykle w PHP.
Przygotowanie lokalnego środowiska pod development
Stworzę sobie prostą konfigurację docker-compose, aby móc serwować projekt PHP na Dockerze. Tworzę plik docker-compose:
version: '3'
services:
image-server-php:
image: php:7.4.0RC5-apache
container_name: php7.4.0
volumes:
- ./:/var/www/html/
ports:
- "80:80"
stdin_open: true
tty: true
Operuję na obrazie PHP w wersji 7.4 (nie mogę się doczekać stabilnej wersji 😊) . Cały bieżący katalog podmontuję do katalogu var/www/html w kontenerze.
Teraz tworzę przykładowy skrypt zwracający prostego JSONa (takie mega proste API). Zrobię to dlatego, że będziemy to weryfikować odpowiedź serwera za pomocą Cypressa :):
<?php
declare(strict_types=1);
echo (new class{
private array $data = [
'id' => 1,
'code' => 'DESK-101A',
'name' => 'desktop',
];
public function getData(): string
{
return json_encode($this->data);
}
})->getData();
Teraz z terminala uruchamiam:
docker-compose up -d
i sprawdzam IP maszyny Dockera, aby móc wejść na stronę:
docker-machine ip
w końcu wchodzę w przeglądarce pod jej adres. Widzę teraz wynik:
{"id":1,"code":"DESK-101A","name":"desktop"}
Aplikacja działa jak należy, teraz dodam drugi serwis odpowiedzialny za testy w Cypressie. Dodaję serwis do pliku docker-compose:
version: '3'
services:
image-server-php:
image: php:7.4.0RC5-apache
container_name: php7.4.0
volumes:
- ./:/var/www/html/
ports:
- "80:80"
stdin_open: true
tty: true
image-server-cypress:
image: cypress/base:12.13.0
container_name: cypress12.13.0
volumes:
- ./:/var/www/html/
environment:
- CYPRESS_CACHE_FOLDER=/var/www/html/.cache/cypress
stdin_open: true
tty: true
Jak widzisz powyżej, podmontowałem ten sam katalog do Cypressa jak i PHP. Dzięki temu oba kontenery będą widzieć pliki projektu. Dodatkowo skonfigurowałem zmienną środowiskową tak, aby przenieść katalog z cache Cypressa do workspace projektu (przyda się to podczas konfigurowania cache w Gitlab CI).
Robię znowu docker-compose up -d.
Teraz oba kontenery stoją. Dodaję test integracyjny w katalogu test o nazwie test.spec.js:
context('Request validation', () => {
it('Should return json', () => {
cy.request('/').then(response => {
expect(response.status).to.eq(200)
expect(response.headers['content-type']).to.eq('text/json')
})
})
})
Dodaję konfigurację dla Cypressa w głównym katalogu projektu (plik cypress.json):
{
"baseUrl": "http://image-server-php",
"integrationFolder": "test"
}
Dzięki temu Cypress będzie wiedział, że domyślnie ma łączyć się z hostem pod adresem image-server-php (adresem wewnętrznym jest nazwa serwisu z pliku docker-compose.yml), a testy są przechowywane w katalogu test.
Teraz możemy sprobować odpalić testy:
docker exec -i -w /var/www/html cypress12.13.0 node_modules/.bin/cypress run
Jednak cypress nie jest zainstalowany i to nie działa! Wynika to z tego, że obraz dockerowy Cypressa zawiera wszystkie zależności wymagane przez cypressa, ale jego samego trzeba doinstalować osobno. Zrobimy to teraz:
docker exec -i -w /var/www/html cypress12.13.0 npm install cypress --save-dev
docker exec -i -w /var/www/html cypress12.13.0 node_modules/.bin/cypress cypress install
docker exec -i -w /var/www/html cypress12.13.0 node_modules/.bin/cypress run
otrzymałem wynik negatywny testu. Z treści widać, że oczekiwano nagłówka Content Type text/json, a jest text/html:
Running: test.spec.js (1 of 1)
127 Request validation
128 1) Should return json
129 0 passing (804ms)
130 1 failing
131 1) Request validation Should return json:
132 AssertionError: expected 'text/html; charset=UTF-8' to equal 'text/json'
133 + expected - actual
134 -text/html; charset=UTF-8
135 +text/json
Tworzymy konfigurację CI
Nasz pipeline będzie składał się z dwóch zadań – budowy środowiska oraz testów integracyjnych. Tworzymy plik konfiguracji .gitlab-ci.yml:
image: tiangolo/docker-with-compose:latest
services:
- docker:dind
stages:
- build
- test
cache:
key: "$CI_COMMIT_REF_SLUG"
untracked: true
policy: pull
paths:
- .cache/cypress/
buildApplication:
stage: build
only:
- master
timeout: 10m
cache:
key: "$CI_COMMIT_REF_SLUG"
untracked: true
policy: pull-push
paths:
- .cache/cypress/
script:
- docker-compose pull
- docker-compose up --no-start
- docker-compose start
- docker exec -i -w "/var/www/html" cypress12.13.0 npm install cypress
- docker exec -i -w "/var/www/html" cypress12.13.0 ./node_modules/.bin/cypress install
- docker-compose stop
makeIntegrationTests:
stage: test
only:
- master
dependencies:
- buildApplication
artifacts:
paths:
- cypress/screenshots/
- cypress/videos/
expire_in: 1 hour
timeout: 10m
script:
- docker-compose pull
- docker-compose up --no-start
- docker-compose start
- docker exec -i -w "/var/www/html" cypress12.13.0 ./node_modules/.bin/cypress run
- docker-compose stop
Następnie robimy push do GitLaba i gotowe :D. Dzięki powyższej konfiguracji zostanie zbudowane podczas wykonywania pipeline środowisko Dockerowe i uruchomione testy integracyjne (ale uwaga - tylko na branchu master, co jest ustawione pod kluczem only).
Jak możesz zauważyć, podczas pipeline wykonywane są te same komendy co podczas pracy na lokalnym środowisku. Rozdzieliłem komendy na dwa zadania, ale tak na prawdę bez tego także by zadziałało.
Pliki z powyższym projektem możesz pobrać stąd i sobie poćwiczyć na własną rękę, co gorąco polecam.
Komentarze
Ten wpis nie posiada komentarzy.
Dodaj komentarz
Pola oznaczone gwiazdką (*) są wymagane. Komentarze są wstępnie moderowane i mogą nie pojawić się na stronie.