Jak spowodować aby błąd programu(exit code != 0) nie powodował przerwania CI

Hej,
Mam uruchomiony jako część github workspace(CI) program, który zwraca wartość inną od 0, przez co całe CI po prostu zostaje zatrzymane.

W jaki sposób mógłbym obsłużyć taki wynik?

To raczej pytanie do działu “programowanie” albo na jakąś grupę devops na FB :wink:

A ta wartość inna niż 0 jest poprawna czy to błąd?

Nigdy nie robiłem Continous Integration. Może napiszesz, czy korzystasz z GNU/Make czy Basha, albo innego ustrojstwa.

Nie korzystałem z CI w Github więc niezbyt wiem jak to tam działa, ale myślę że w ten sposób można wymusić zawsze status 0.
(/path/to/script.sh; exit 0)

To raczej pytanie do działu “programowanie” albo na jakąś grupę devops na FB :wink:

Żaden z tutejszych “ekspertów” nie potrafi odpowiedzieć na proste pytanie dotyczące podstaw Linuksa/Uniksa? W takim razie po co w ogóle jest to forum?
Jestem w stanie zgodzić się z jednym: na Stack Overflow czy porządnym kanale IRC na Freenode z “devel” w nazwie autor uzyskałby odpowiedź w góra kilka minut, bez żadnego wodolejstwa.

A ta wartość inna niż 0 jest poprawna czy to błąd?

A jakie to ma znaczenie?! W systemach uniksowych i uniksopodobnych przyjęło się, że wartość zero oznacza powodzenie operacji, zaś każda inna (1…255) - niepowodzenie. Oczywiście, są od tego wyjątki. Przykładowo, find zwróci zero nawet jeśli nic nie znajdzie. Dzieje się tak dlatego, że za sukces przyjął poprawne przetworzenie wszystkich plików. Wartość niezerowa pojawi się więc tylko wtedy, gdy po drodze pojawi się jakiś błąd. Przyjmując takie wytłumaczenie, to narzędzie nie łamie wspomnianej reguły. Oczywiście są też takie, które nie działają zgodnie ze standardem, ale to temat na inną dyskusję.
Wracając do tematu, niepowodzenie jednej operacji wcale nie musi oznaczać, że chcemy przerwać proces budowania. Powody mogą być różne:

  • Jeden z testów jednostkowych zawodzi. Może być to problem związany z daną platformą i jesteśmy w trakcie oczekiwania na rozwiązanie ze strony osób trzecich. W każdym razie, jeśli o tym wiemy i się na to godzimy, możemy to tymczasowo zignorować. Oczywiście, najbardziej eleganckim rozwiązaniem byłoby wyłączenie tego felernego unit testu, ale nie zawsze jest na to czas.
  • Niektóre testy mogą być zależne od komponentów i usług niedostępnych na serwerze. Przykładowo, aplikacje desktopowe mogą wymagać X11. Oczywiście, w obecnych czasach odpowiedni obraz dockera to nie problem, a ponadto zawsze powinna istnieć możliwość wyłączenia problematycznych testów, ale znowu - nie zawsze jest na to czas.
  • Niektóre narzędzia mogą wymagać dostępu do Internetu, a gdy właściwe budowanie odbywa się w trybie offline (wcześniej wszystkie zależności zostają pobrane), może ono zwrócić kod niepowodzenia.
  • Czasami przy walidacji plików chcemy zezwolić na drobne niepowodzenia. Przykład: walidacja plików AppData przez AppStream-Glib.

Na dobrą sprawę, powodów może być nieskończenie wiele. Wtedy zachodzi potrzeba obsłużenia lub zignorowania takiego błędu.

@nintyfan

Nigdy nie robiłem Continous Integration.

W takim razie po co w ogóle zabierasz głos w temacie? Czemu ma to służyć? Nabijaniu postów?

Może napiszesz, czy korzystasz z GNU/Make czy Basha, albo innego ustrojstwa.

Po co dopytujesz o Basha, skoro i tak go nie znasz? Gdybyś znał podstawy powłok uniksowych, już dawno udzieliłbyś odpowiedzi na to pytanie.

@Bradlee

Nie korzystałem z CI w Github więc niezbyt wiem jak to tam działa, ale myślę że w ten sposób można wymusić zawsze status 0.
(/path/to/script.sh; exit 0)

Raczej nie korzystałeś z żadnego narzędzia CI ani Build Server, gdyż w przeciwnym wypadku nie proponowałbyś takiego rozwiązania. Od razu odpowiem, że to nic nie da. Wykonywanie zostanie przerwane po wywołaniu felernego programu i CI nawet nie dojdzie do komendy exit 0. Tak jest niemalże wszędzie.

Co można zrobić w takim przypadku? Dla uproszczenia załóżmy, że za nasz program posłuży komenda false, która zawsze zwraca wartość 1.

$ false; echo $?
1

Jeśli chcemy, aby nie przerywała ona procesu budowania, możemy posłużyć się następującą konstrukcją:

false || true
$ false || true; echo $?
0

Działa to na takiej zasadzie, że w przypadku niepowodzenia, wywoływana jest komenda true, zawsze zwracająca wartość zerową. Należy to interpretować jako instrukcję warunkową:

if ! false; then
  true
fi

Aby skrócić zapis, można się posłużyć czymś takim:

false ||:
$ false ||:; echo $?
0

Działanie jest praktycznie identyczne, chociaż użyte mechanizmy nieco inne. Tak się bowiem składa, że false i true to standardowe programy, które jednak mogą być zastąpione przez odpowiadające im polecenia powłoki. Dwukropek natomiast to wbudowane polecenie shella oznaczające brak operacji (no operation, tzw. NOP). Podobnie jak true, zawsze zwraca 0.

Alternatywą dla powyższych jest wykorzystanie “set +e”:

set +e
false
set -e

Jak można się domyślić, ta konstrukcja będzie szczególnie pożądana w przypadku gdy mamy do czynienia z większą liczbą komend.

Aby lepiej to wszystko zobrazować, pokusiłem się o stworzenie dwóch prostych skryptów imitujących CI.

fake_ci.bash:
https://pastebin.com/raw/fADAGRVy

#!/usr/bin/env bash

# exit immediately if a pipeline exits with a non-zero status
set -e
source "$( dirname "$( readlink -f "${0}" )" )"/fake_pipeline.bash
fake_pipeline

fake_pipeline.bash:
https://pastebin.com/raw/Xha2B29t

#!/usr/bin/env bash

fake_job_0()
{
  echo "Job 0: Hello!"
  # a command that may fail
  if ! false; then
    true
  fi
  echo "Job 0: All done!"
}

fake_job_1()
{
  echo "Job 1: Hello!"
  # a command that may fail
  false ||:
  echo "Job 1: All done!"
}

fake_job_2()
{
  echo "Job 2: Hello!"
  set +e
  # a command that may fail
  false
  set -e
  echo "Job 2: All done!"
}

fake_job_3()
{
  echo "Job 3: Hello!"
  # a command that may fail
  false
  echo "Job 3: All done!"
}

fake_pipeline()
{
  fake_job_0
  fake_job_1
  fake_job_2
  fake_job_3
}

Po nadaniu uprawnień (chmod u+x fake_ci.bash) i uruchomieniu mamy:
https://pastebin.com/raw/U5uyMzBV

$ ./fake_ci.bash 
Job 0: Hello!
Job 0: All done!
Job 1: Hello!
Job 1: All done!
Job 2: Hello!
Job 2: All done!
Job 3: Hello!

Jak widać, wyłącznie ostatnie zadanie przerwało pracę naszego pseudo-CI.

Realne przykłady wykorzystania “|| :”:
https://paste.ubuntu.com/p/YZyJQBfK5p/

Realne przykłady wykorzystania “|| true”:
https://paste.ubuntu.com/p/86tSq9XHVC/

Realne przykłady wykorzystania “set +e”:
https://paste.ubuntu.com/p/9t4NNRm6tb/
https://git.centos.org/rpms/firefox/blob/8b0f1bd7bb5c152cd54c248893c4d6a8717ea01c/f/SPECS/firefox.spec#_682-691

Oczywiście, poszczególne rozwiązania CI mogą też posiadać własne opcje dotyczące tolerancji niepowodzeń (allow_failure dla pojedynczego pipeline/job w GitLab CI), ale to zazwyczaj stosuje się, gdy np. wprowadzamy eksperymentalne wsparcie dla nowej architektury i tymczasowo godzimy się na to, że nie zawsze uda nam się zbudować nasze oprogramowanie pod AArch64 czy PPC64LE, bo najbardziej zależy nam na binarkach dla x86_64. Rzecz jasna, zastosowań jest znacznie więcej, a to jest tylko przykład. Chciałem tylko zaznaczyć, że niekoniecznie jest to właściwe rozwiązanie gdy chcemy tylko uciszyć pojedynczą komendę. W takim wypadku lepiej korzystać z przytoczonych przeze mnie wcześniej konstrukcji.

1lajk

Mógłbyś z tego wpisu spokojnie zrobić serię na Bloga. Myślę, że wiele osób podejrzewa, że takie narzędzia jak CI w jakiś sposób mogłyby się im przydać ale brakuje im inspiracji albo nie do końca rozumieją jak to działa. Owszem w sieci jest masa tutoriali ale moim zdaniem takich materiałów nigdy za wiele. Do jednego trafia suchy manual, do innego wpis na Blogu.

Oczywiście to tylko propozycja, nic na siłę. Pozdrawiam.

Pomijając, że pewnie się znasz na tym o czym piszesz (nie moja dziedzina) - czy jesteś doświadczonym użytkownikiem forum Elektroda? :wink:

1lajk

Mógłbyś z tego wpisu spokojnie zrobić serię na Bloga. Myślę, że wiele osób podejrzewa, że takie narzędzia jak CI w jakiś sposób mogłyby się im przydać ale brakuje im inspiracji albo nie do końca rozumieją jak to działa. Owszem w sieci jest masa tutoriali ale moim zdaniem takich materiałów nigdy za wiele. Do jednego trafia suchy manual, do innego wpis na Blogu.

To co tu naskrobałem tyczy się bardziej samych podstaw uniksowych powłok. Jeśli zaś chodzi o CI, to te osoby, które faktycznie tego potrzebują, zazwyczaj mają na tyle zapału, żeby to ogarnąć. Zresztą, jest tu tyle ścieżek w zależności od stacku technologicznego i konkretnych rozwiązań, że nawet nie wiedziałbym od której strony ugryźć ten temat. Dla osoby początkującej myślę, że bardziej przydatny okazałoby się poradnik opisujący tworzenie paczek RPM lub DEB dla oprogramowania rozwijanego w C/C++ i Pythonie. Biorąc na warsztat rozwiązania Red Hata można by to opisać zaczynając od rpmbuild, poprzez mock, kończąc na COPR i Koji. Na razie jednak nie mam na to czasu, ale powiedzmy, że mógłbym to rozważyć w bliżej nieokreślonej przyszłości.

Pomijając, że pewnie się znasz na tym o czym piszesz (nie moja dziedzina) - czy jesteś doświadczonym użytkownikiem forum Elektroda? :wink:

Niezbyt. Miałem tam konto, ale generalnie rzecz biorąc elektryka i elektronika to nie moja działka. Za to swego czasu udzielałem się trochę na forum.linux.pl, jednakże to było naprawdę dawno temu.

Jeżeli znajdziesz czas, to ja jestem całkowicie za powstaniem takiego poradnika. Wiele osób tu prowadzi bloga na dany temat dodając kolejną część gdy ma na to czas. Np raz na miesiąc czy nawet raz na dwa miesiące.

Sorry ziom, ale jeśli CI/CD i developerkę nazywasz podstawami znajomosci Linux czy Unix to pomyliłeś fora. Nie wszyscy użytkownicy są programistami, tak samo nie wszyscy są administratorami serwerów. Ja na programowaniu się nie znam, poza tym co potrzebne mi w administracji serwerami w bashu ewentualnie pythonie. Na tym forum więcej jest zwykłych użytkowników Linuksa, zawodowców będzie mniej. To forum to nie Stack Overflow. Przykro mi.

Tutaj już zostały dzieci Neo i wybitki starej gwardii DP.

Do której grupy ty się zaliczasz?

Bo z takim tekstem to na pewno zatrzymałeś się w czasie ze 20 lat temu :smiley:

@roobal

Sorry ziom, ale jeśli CI/CD i developerkę nazywasz podstawami znajomosci Linux czy Unix to pomyliłeś fora.

Pytanie tylko pozornie traktuje o CI. W praktyce wszystko rozbija się o podstawy uniksowych powłok, przede wszystkim “set -e”. To nawet nie jest żadna nowość wprowadzona w Bashu, tylko coś co było z nami co najmniej od wczesnych lat 80-tych, gdy Bash jeszcze nie istniał.
https://www.in-ulm.de/~mascheck/various/set-e/
Ba, zostało to nawet ujęte w drugiej wersji SUS (Single UNIX Specification) z 1997 roku.
https://pubs.opengroup.org/onlinepubs/007908799/xcu/chap2.html#tag_001_014_011_002

Co więcej, to jest coś na tyle powszechnie stosowane, że aż wstyd o tym nie wiedzieć.

Nie wszyscy użytkownicy są programistami, tak samo nie wszyscy są administratorami serwerów. Ja na programowaniu się nie znam, poza tym co potrzebne mi w administracji serwerami w bashu ewentualnie pythonie.

Wybacz, ale ciężko uwierzyć w to, że administrator nigdy nie stanął przed koniecznością zbudowania choćby pakietu RPM - jeśli nie od zera, to przynajmniej poprawiając istniejący. Tam bowiem mamy do czynienia z identyczną kwestią: gdy polecenie zwraca niezerowy status, proces budowania jest przerywany (“RPM build errors:”, “Bad exit status from (…) (%build)”). W przypadku pakietów DEB również ma to znaczenie (“dpkg-buildpackage: error: debian/rules build gave error exit status 2”).

Widocznie lepiej zrozumiałeś temat. Z racji, że nie znam się na programowaniu CI/CD kompletnie tego nie skojarzyłem z bashem.

W każdym razie większość użytkowników Linuksa tutaj rzadko dotyka terminala, o bashu już nie wspominam. Kilka lat temu było tu kilku speców, głównie programiści. Niestety, jak pisałem, to nie stackoverflow, to bardziej forum dla pasjonatów.