Na tym i następnych zajęciach omówimy w jaki sposób automatyzujemy budowę oprogramowania.
Mówimy o trzech procesach:
- Ciągła integracja (Continuous Integration)
- Ciągłe dostarczanie (Continuous Delivery)
- Ciągła instalacja (Continuous Deployment)
flowchart LR
CI(Continuous\nIntegration) --> CDel(Continuous\nDelivery) --> CD(Continuous\nDeployment)
Naszym celem dzisiaj jest zaimplementowania następującej automatyzacji (Continuous Integration i Continuous Delivery):
flowchart LR
Dev(Tester/Programista) -- git\npush --> Github(Repozytorium\nGithub) -- wyzwala --> GAction(Automatycznie:\n1. linter\n2. testy\n3. buduje pakiet)
-
Patrz instrukcja o podstawach gita.
-
Sprawdź czy masz dobrze skonfigurowanego gita:
git config -l
Powinieneś zobaczyć wyświetlenie konfiguracji z github username i email.
W czasie tego ćwiczenia, przygotujemy kod źródłowy nad którym będziemy pracować. Jak to w każdej firmie, po krótkim wprowadzeniu, dostajemy adres git-a z projektem, który ma ocalić firmę od bankructwa. Deweloper, biegnąć na kolejne telco, powiedział, że wszystko jest w README.
-
Utwórz fork z repozytorium: https://github.com/wojciech11/se_hello_printer_app
-
Sklonuj repozytorium:
# utworzmy katalog dla naszej pracy mkdir -p workspace cd workspace
# gdzie znaleźć URL? # zobacz poniżej git clone https://github.com/nazwa_uzytkownika/se_hello_printer_app
-
Przygotuj sobie środowisko do pracy:
pwd ls cd se_hello_printer_app atom .
-
Uruchom aplikację, według instrukcji README.md
-
Po uruchomieniu aplikacji otwórz w przeglądarce adres 127.0.0.1:5000
-
Zamiast przeglądarki wykorzystaj narzędzie curl w konsoli bash:
curl 127.0.0.1:5000 # w jakich formatach mamy dostępne hallo world curl 127.0.0.1:5000/outputs # zobczmy jak to działa: curl '127.0.0.1:5000/?output=json'
-
Uruchom testy według poleceń z README.md.
Najpopularniejszym formatem dla README jest Markdown, coraz rzadziej spotykamy RestructuredText – README.rst.
Jeśli potrzebujesz inspiracje jak modyfikować Markdown, skorzystaj z tutoriala.
-
Czas zanurzyć się w kodzie...
- Wyszukaj plik python zawierający imię, które widziałaś przy powitaniu,
- Zmień wartość tej zmienną na swoje imie,
- Uruchom aplikację i zobacz czy widzisz swoje imie,
- Uruchom testy i je popraw.
-
Wypchnij zmiany do swojego repozytorium gita na Githubie.
-
[Dodatkowe 1] Znajdź bug w formacie json, popraw (kod i testy), umieść zmiany na githubie:
curl '127.0.0.1:5000/?output=json'
Wyszukaj i zanotuj, czy charakteryzuje się format JSON, porównaj z XML czy YAML.
-
[Dodatkowe 2] Wykorzystaj czas, żeby dowolnie zmodyfikować program na podstawie wskazówek prowadzącego.
-
Co mógłbyś dodać do pliku README, aby móc samodzielnie uruchomić aplikację?
Każdy projekt powinien mieć a single point of entry. Jest to skrypt albo Makefile, który zawiera najczęściej wykonywane polecenia. Polecenia te powinny być wspólne zarówno w czasie budowania oprogramowania, testowania, jak i budowania pakietu.
Niestety, nasz projekt nie ma takiego skryptu. W tym ćwiczeniu wykorzystamy narzędzie make. Jest to bardzo narzędzie dostarczające wiele funkcjonalności, w naszym projekcie wykorzystamy jego bardzo podstawową funkcjonalność. Cała logika będzie w kodzie bash wywoływanym przez make.
-
Utwórz plik Makefile (przykład). Zauważ, że białe znaki to tabulatory, jeśli korzystasz z Atoma, aktywuj Show Invisibles w Preferences.
.PHONY: deps test deps: pip install -r requirements.txt; \ pip install -r test_requirements.txt
deps – instalacja wszystkich pakietów wymaganych dla naszego programu.
-
Naszym zadaniem jest rozszerzenie Makefile, wykorzystując komendy z README.md:
- lint – uruchomienie lintera (patrz: https://www.python.org/dev/peps/pep-0008/)
- test - testy
- run – uruchomienie aplikacji na maszynie dev
Nasz cel, nowa osoba musi zacząć pracować z projektem, będzie korzystać ze wspólnej konwencji dla wszystkich projektów w naszej firmie.
# < aktywacja venv make deps make lint make test make run
-
Zacznij od linta:
lint: flake8 hello_world test
Dodaj teraz targety test i run.
-
Przetestuj czy komenda deps działa w wierszu lini komend:
# powinienes zobaczyc instalacje bibliotek make deps # a tu testy uruchomione make test # aplikacja powinna wystartowac make run
-
Zobacz co nam powie linter, poczekaj na instruktora, aby omówić problemy:
make lint
Wykorzystaj
# noqa
, aby zignorować błąd w ostatniej linii__init__.py
-
Odpowiedź na pytanie: dlaczego ważne jest single point of entry i README.md?
Jako że czas na otrzymania serwera do ustawienia automatyzacji przedłuża się, postanowiłaś wziąść sprawę w swoje ręce i skorzystać z usługi Github Actions. Przecież projekt może zmienić los firmy.
Posiadanie własnej instalacji Jenkins-a daje nam duże możliwości konfiguracji, jednak ceną jest czas niezbędny do operowania serwera, update-owania pluginów, etc. Dlatego atrakcyjną opcją są usługi CI w chmurze - GitlabCI, Github Actions, CircleCI, etc.
-
Omówienie podstawowych koncepcji z wykładowcą w opraciu o dokumentacje:
- Event - wyzwalanie akcji
- Workflow -
.github/workflows
- Jobs
- Steps
- Actions - reużywalne funcjonalności
-
Uttórz katalog:
# na wszelki, upewnij się # ze jestes w glownym katalogu # projektu pwd mkdir -p .github/workflows
-
Utwórz plik -
.github/workflows/ci.yaml
:touch .github/workflows/ci.yaml
-
Wpisz do niego następującą definicję workflowu (na podstawie dokumentaji githuba):
name: Package Project # kiedy mam uruchomic automatyzacje on: [ push ] jobs: build_and_test: runs-on: ubuntu-latest steps: # pobierz kod - uses: actions/checkout@v3 # moja aplikacja jest w Pythonie - name: Set up Python 3 uses: actions/setup-python@v3 # instalacja wymaganych bibliotek - name: Install deps run: make deps # testy!!!!! - name: Tests run: make test
-
Umieść
ci.yaml
w repozytorium githuba. -
Przejdź do interfejsu webowego githuba i przejdź do Actions:
-
[Dodatkowe] Dodaj krok, który uruchomi lintera.
Idziemy za ciosem. Mamy automatyczne testy, teraz chcemy dodać budowę pakietu dla naszego projektu. W ćwiczeniu pokażemy, jak przygotować komponenty gotowe do dostarczenia do klienta w ramach Continuous Delivery.
Celem Continuous Delivery jest przygotowanie artefaktu gotowego do dostarczenia do klienta (Continuous Deployment). Artefaktem może być pakiet (.deb, .rpm), instalator (.msi), w naszym przypadku będzie to obraz Dockera (Docker image).
-
Utwórzmy przepis na nasz pakiet (obraz dockera/Docker image) - plik
Dockerfile
:FROM python:3 ARG APP_DIR=/usr/src/hello_world_printer WORKDIR /tmp ADD requirements.txt /tmp/requirements.txt RUN pip install -r /tmp/requirements.txt RUN mkdir -p $APP_DIR ADD hello_world/ $APP_DIR/hello_world/ ADD main.py $APP_DIR CMD PYTHONPATH=$PYTHONPATH:/usr/src/hello_world_printer \ FLASK_APP=hello_world flask run --host=0.0.0.0
Uwaga: Zanim zaczniesz to ćwiczenie, przygotuj sobie drugą zakładkę terminala (ctr-shift-t) lub nowe okno do uruchamiania komend jako root. W nowym terminalu, wywołaj sudo su
(czym różni się od sudo su -
?). W tej nowej zakładce, tam gdzie jesteś rootem, wykonaj komendę docker ps
, aby się upewnić, że możesz wykonać ćwiczenie.
-
Wypieczmy obraz dockera w oknie gdzie jesteś rootem:
docker build -t hello-world-printer .
# zobaczmy czy sie zbudowal nasz obraz docker images
-
Jeśli komponent jest prosty, możemy weryfikować poprawność definicji w czasie etapu budowy:
Zmień CMD na RUN i ponownie zbuduj komponent. Jeśli aplikacja się uruchomiła, powróć do poprzedniej wersji Dockerfile.
-
Dodaj target w Makefile-u dla budowy pakietu, żeby nie musieć pamiętać komendy:
docker_build: docker build -t hello-world-printer .
Sprawdź w terminalu czy działa:
# sprawdz czy dziala make docker_build
-
Co to jest dependency hell? Wyszukaj w Google-u.
docker run --name hello-world-printer-dev -p 5000:5000 -d hello-world-printer
docker ps
curl 127.0.0.1:5000
# wyswietl logi
docker logs hello-world-printer-dev
# jeśli nie widzisz dockera, kiedy uruchamiasz z docker ps dodaj -a
docker ps -a
docker logs hello-world-printer-dev
# zakoncz program
docker stop hello-world-printer-dev
Docker zazwyczaj nie restartujemy, kasujemy i uruchamiamy na nowo:
docker stop hello-world-printer-dev
docker rm hello-world-printer-dev
make docker_run
Zauważ, mamy też:
docker restart NAZWA_DOCKERA
Przydatne komendy dockera (pamiętamy o sudo), wychodzimy wpisując exit :
# możemy uruchomić basha w naszym dockerze image
docker run -it hello-world-printer /bin/bash
# uruchamiamy bash-a w działającym dockerze z naszą aplikacją:
docker exec -it hello-world-printer-dev /bin/bash
flowchart LR
Github(Repozytorium\nGithub) -- wyzwala --> GAction(Automatycznie:\n1. linter\n2. testy\n3. buduje pakiet) -- 4. publikuje --> DH(Repozytorium\nobrazów\nDockera)
W dwóch iteracjach, opierając się na dokumentacji:
- budowa pakietu,
- publikowanie pakietu na hub.docker.com.
Wymagane jest założenie konta na hub.docker.com.
Mamy pakiet w repozytorium, warto się zastanowić jak poprawnie nadać mu numer wersji. W tym celu poznajmy najpopularniejszą obecnie metodologie Semantic Versioning - semver.
-
Zapoznaj się z opisem i napisz w swoich słowach co to jest Semantic Versioning:
-
Dlaczego MAY i MUST są z dużej litery?
-
Co to jest RFC 2119?
-
Opisz każde z pól następującej wersji:
- 1.0.1
- 1.3.2-1
- 1.4.5+1
-
Jaką wersję mają obecnie poniższe projekty i czy stosują semver?
- biblioteka flask
- Firefox
- Chrome
- Libreoffice
- Android
- iOS
-
Co to jest Calver (https://calver.org/)?
-
Zanotuj co oznaczają kody:
- 200
- 301
- 302
- 400
- 403
- 404
- 405
- 422
- 500
- 418
-
Jaki kod http otrzymujemy? Dlaczego?
curl google.com curl -I google.com # <- i z dużej litery curl -I google.com -L
Więcej na testowanie http API w pigułce.
Dodaj do Makefile, prosty smoke test (aka happy-path test):
test_smoke:
curl --fail 127.0.0.1:5000
alternatywne rozwiązanie - wyświetlenie tylko kodu http:
test_smoke:
curl -s -o /dev/null -w "%{http_code}" --fail 127.0.0.1:5000
Dowiedzieliśmy się, że nasza aplikacja ma również wspierać format XML.
Wymagania, dla wywołania aplikacji z argumentem output=xml
:
curl '127.0.0.1:5000?output=xml'
Powinniśmy zwrócić:
<greetings>
<name>Natalia</name>
<msg>Hello World!</msg>
</greetings>
Nie musisz korzystać od samego początku z biblioteki, możesz w pierwszej wersji posklejać stringi.
Opcjonalnie, możesz utworzyć branch dla tej implementacji add_xml_output, który później zmergujemy z main/master.
Dodaj testy,
Wszystko działa? Tak. Zmerguj z masterem.
Dowiedzieliśmy się, że nasza aplikacja ma obsługiwać imię wpisane w argumencie.
Wymagnia, dla podania argumentu name:
curl '127.0.0.1:5000?name=apolonia&output=json'
Powinniśmy zobaczyć:
{
"name": "apolonia", "msg": "Hello World"
}
Zauważ: Jeśli użytkownik nie poda imienia, użyj jako domyśle moje_imie
.
Nie zapomnij o testach.
Tworzenie JSONa w stringu nie jest poprawne i bardzo podatne na błędy. Refaktoruj aplikację, żeby używała biblioteki json to tworzenia JSONa.
-
Zacznij od zmiany generowania JSONa wraz z poprawą testów:
import json
-
Popraw generacje XMLa, w tym celu użyj biblioteki
lxml
.
Testy formatera (test_formater.py
) nie są najlepsze, proszę poprawić i objąć testami pozostałem metody outputu.
- Budowanie Docker Image za pomocą Github Actions - dokumentacja
- Lepsze opisy git commitów:
- Imperative commit messages
- semantic commit messages
- interesujące: gitmoji-cli