programowanie

Jak być na bieżąco z zależnościami?

6 styczeń 2019

Nie trzeba chyba nikomu tłumaczyć dlaczego warto aktualizować zależności: nowe funkcje, poprawki błędów, również tych związanych z bezpieczeństwem. W większości przypadków wystarczy zmienić numer wersji na wyższy, i po sprawie, choć nie zawsze bywa tak prosto.

Problemy zaczynają się, gdy zmiany w interfejsie programistycznym biblioteki wymagają dostosowania naszego kodu. Aktualizacja zależności raz na ruski rok może spowodować, że liczba takich zmian będzie się nawarstwiać, co tylko utrudni i wydłuży migrację do nowej wersji danej biblioteki. Dzięki regularnej aktualizacji zależności migracja bibliotek do nowych wersji powinna być łatwiejsza i mniej ryzykowna.

Sprawdzanie czy są nowe wersje bibliotek można bardzo łatwo zautomatyzować. Pokażę Ci to na przykładzie menadżera pakietów pip używanego w Pythonie, oraz GitLab CI. Znajomość Dockera również może się przydać.

Zaczynamy…

Korzystając z narzędzia pip możemy w bardzo łatwy sposób uzyskać listę zainstalowanych pakietów, które są nieaktualne. Wystarczy wywołać w terminalu poniższe polecenie.

pip list --outdated --format=columns

Powinieneś otrzymać mniej więcej taką listę jak poniżej. Znajdują się na niej wszystkie niezbędne informacje: nazwa pakietu, numer zainstalowanej wersji oraz numer najnowszego stabilnego wydania. Nazwy pakietów oraz ich ilość mogą być u Ciebie inne. Gdy wszystkie pakiety będą aktualne, wtedy na ekranie terminala nie pojawi się żadna informacja.

Package  Version  Latest  Type
-------- -------- ------- -----
amqp     1.4.9    2.3.2   wheel
billiard 3.3.0.23 3.5.0.5 sdist
celery   3.1.25   4.2.1   wheel
Django   1.10.8   2.1.4   wheel
kombu    3.0.37   4.2.2   wheel

W tym momencie moglibyśmy zakończyć całą zabawę, gdyby nie jeden mankament. Mianowicie, wyświetlona w ten sposób lista zawiera również biblioteki, które zostały zainstalowane niejawnie, jako zależności innych bibliotek. Moglibyśmy się tym nie przejmować, ale wtedy będziemy informowani o aktualizacjach na które nie mamy wpływu.

Filtrujemy zależności

Napiszmy skrypt, który pozostawi na liście tylko te pakiety, które znajdują się w wymaganiach do naszego projektu. W tym celu załóżmy, że w naszym projekcie znajduje się poniższy plik requirements.txt, zawierający wymagane pakiety.

django>=1.10,<1.11
celery==3.1.25

Wersje tych pakietów są dość leciwe. Dzięki temu będziemy mogli przetestować, czy nasz skrypt działa poprawnie. A oto i on.

#!/bin/bash

OUTDATED=`pip list --outdated --format=columns`
PACKAGE_NAMES=`awk -F"(==|<|>)" '{print $1}' requirements.txt`

UPDATES=`echo "${OUTDATED}" | grep -Fwi -e "${PACKAGE_NAMES}"`

if [[ -n "${UPDATES}" ]]; then
    echo "${OUTDATED}" | head -n 2
    echo "${UPDATES}"
    exit 1
else
    echo "THERE IS NO PACKAGES TO UPDATE"
fi

Jak działa? Na początku pobieramy listę nieaktualnych pakietów korzystając z polecenia pip. Następnie wczytujemy z pliku requirements.txt listę wymaganych pakietów w naszym projekcie. Istotnym zabiegiem, który musimy tutaj przeprowadzić jest oddzielenie nazwy pakietu od jego wersji. W tym celu skorzystamy z polecenia awk. Na koniec zostaje już tylko przefiltrować listę nieaktualnych pakietów za pomocą narzędzia grep. W ten sposób zostaną na niej tylko te, które znajdują się w zależnościach do naszego projektu. I gotowe.

Zapisujemy skrypt pod nazwą updates-check.sh i uruchamiamy. W efekcie na liście nieaktualnych pakietów pozostały tylko bezpośrednie zależności naszego projektu, reszta została odfiltrowana.

> $ ./updates-check.sh
Package  Version  Latest  Type
-------- -------- ------- -----
celery   3.1.25   4.2.1   wheel
Django   1.10.8   2.1.4   wheel

Integrujemy skrypt z GitLab CI

W celu uruchomienia naszego skryptu w ramach potoku CI, musimy dodać dodatkowe zadanie do pliku .gitlab-ci.yml. Jego definicja znajduje się poniżej.

updates:
  stage: updates
  image: $APP_IMAGE
  before_script:
    - apk update
    - apk add bash grep
  script:
    - ./updates-check.sh
  only:
    - schedules

Zakładam, że Twoja aplikacja korzysta ze środowiska Docker, a bazowym obrazem aplikacji jest alpine. Zaletą tego rozwiązania jest to, że zawsze będziemy sprawdzać wersje pakietów na obrazie aplikacji, którego aktualnie używamy w środowisku produkcyjnym. Z tego powodu wszystko, co dalej opiszę będzie wynikać z powyższych założeń.

Co takiego robi to zadanie? W pierwszej kolejności instaluje dwa wymagane pakiety: powłokę bash oraz narzędzie grep. Bez nich skrypt nie będzie działał poprawnie. Następnie, uruchamia go wewnątrz kontenera określonego w zmiennej $APP_IMAGE. Musisz ją podmienić na link do obrazu kontenera Twojej aplikacji. Nie zapomnij też upewnić się, że skrypt ma nadane prawa do uruchamiania.

Ponadto, zwróć uwagę na sekcję only. Za pomocą opcji schedules określiliśmy, że to zadanie będzie wykonywać się tylko i wyłącznie w ramach potoków CI uruchamianych poprzez harmonogram zadań. Zostaje nam go teraz tylko skonfigurować.

Zrzut ekranu z GitLab CI.
Dodanie nowego cyklicznego zadania w harmonogramie zadań w GitLab CI.

W tym celu przechodzimy do sekcji CI / CD, z której wybieramy Schedules. Następnie klikamy w New schedule i na nowej stronie wpisujemy nazwę cyklicznego zadania, określamy kiedy ma się uruchamiać (w moim przypadku ustawiłem raz na miesiąc) i na jakiej gałęzi kodu. Pozostałe opcje możemy zostawić bez zmian. Po dodaniu nowego zadania możemy je uruchomić manualnie i zobaczyć czy działa.

Kiedy pojawią się nowe wersje zależności w naszym projekcie, zadanie zakończy się wtedy błędem. Zgodnie z domyślną konfiguracją GitLab CI, powinniśmy zostać powiadomieni o tym fakcie za pomocą maila. Dzięki temu będziemy zawsze wiedzieć o nowych wersjach bibliotek.

Całe rozwiązanie możesz podejrzeć na repozytorium updately.