diff --git a/IUM_03.CI-Jenkins.ipynb b/IUM_03.CI-Jenkins.ipynb new file mode 100644 index 0000000..fef2fc8 --- /dev/null +++ b/IUM_03.CI-Jenkins.ipynb @@ -0,0 +1,429 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Ciągła integracja \n", + " - Jest to praktyka rozwoju projektów informatycznych polegająca na bardzo częstym włączaniu nowych zmian do głównej gałęzi (branch)\n", + " - Dzięki jej stosowaniu nie dochodzi do konfliktów przy łączeniu (mergowaniu) gałęzi rozwojowej (feature branch) z główną (master)\n", + " - Żeby stosować tę technikę, zmiany muszą być za każdym razem przetestowane, we wspólnym środowisku, tak, żeby działały u innych deweloperów\n", + " - Stąd narodziła się potrzeba stosowania systemów automatycznej ciągłej integracji\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Systemy ciągłej integracji \n", + "- Umożliwiają utomatyczne :\n", + " - budowanie\n", + " - testowanie \n", + " - wydawanie \n", + "oprogramowania, oraz automatyczne (lub ręczne) wykonywanie dowolnych \"zadań\"\n", + "- Zapewniają:\n", + " - wspólne środowisko do testowania zmian, replikowania błędów\n", + " - łatwość monitorowania zmian zachodzących w oprogramowaniu\n", + " - środowisko do integracji ze sobą części składowych (np. modułów, modeli) w jedną całość\n", + " - środowisko, w którym można stworzyć prosty graficzny interfejs służący do uruchamiania zadań z poziomu przeglądarki\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Systemy ciągłej integracji\n", + " - Jenkins
\n", + " - Bamboo
\n", + " - Circle CI
\n", + " - Team City
\n", + " - Gitlab CI
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Jenkins \n", + "
\n", + "- https://www.jenkins.io/\n", + "- System ciągłej integracji napisany w języku Java\n", + "- Pierwszy release: 2011\n", + "- Licencja Open Source (MIT)\n", + "- Dużo pluginów\n", + "- Aktywny rozwój, wspierany przez The Continuous Delivery Foundation i firmę Cloud Bees \n", + "- Działa jako aplikacja webowa z graficznym interfejsem\n", + "- Posiada też REST i CLI API" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Terminologia\n", + " - **Job, aka. Pipleine (Projekt)** - podstawowa jednostka organizacji pracy wykonywanej przez Jenkinsa. \n", + " - Posiada swoją konfigurację, która określa jakie polecenia będą wykonywane w jego ramach. \n", + " - Jeden pipeline może być wykonany wiele razy, za każdym razem tworząc nowe *Zadanie* (*Build*). \n", + " Przykładowy pipeline: http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world/\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " - **Build (Zadanie)** - instancja uruchomionego projektu. Może być w trakcie wykonywania, albo zakończona z jednym z rezultatów:\n", + " - Successful \n", + " - Unstable \n", + " - Aborted \n", + " - Failed \n", + " Np: http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world/2/\n", + " - Śledzenie wyników działania buildu jak i debugowanie ewentualnych problemów ułatwiają:\n", + " - Wyjście z konsoli [(Console Output)](http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world/10/console) - tutaj widać logi wypisywane zarówno przez polecenia/funkcje Jenkinsowe jak i standardowe wyjście / wyjście błędów wykonywanych poleceń systemowych\n", + " - Workspace - to katalog roboczy, w którym uruchamiane są polecenia. Tutaj zostaje sklonowane repozytorium (jeśli je klonujemy), tu wywoływane będę polecenia systemowe. Można je przeglądać z poziomu przeglądarki, np. [tutaj](http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world-scripted/1/execution/node/3/ws/)\n", + " - Każdy uruchomiony build można zatrzymać (abort) co powoduje zaprzestanie jego wykonywania\n", + " - Build zakończony można usunąć (np. jeśli przez przypadek wypisaliśmy na konsolę nasze hasło)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Terminologia c.d.\n", + " - **Step (Krok?)** - każdy pipeline to sekwencja kroków do wykonania. \n", + " - W przypadku projektów typu Pipeline, kroki definiuje się w pliku \"Jenkinsfile\"\n", + " - Przykładowe kroki:\n", + " - wykonanie polecenia w konsoli (sh)\n", + " - sklonowanie repozytorium git (checkout)\n", + " - archiwizacja artefaktów (archiveArtifacts)\n", + " - kopiowanie artefaktów z innego zadania (copyArtifacts)\n", + " - uruchomienie innego zadania (build)\n", + " - **Stage (Etap?)** - Pozwala grupować kroki (steps). Na głównej stronie każdego pipeline możemy zobaczyć wizualizację poszczególnych etapów wraz z czasami ich wykonywania\n", + " - **Artifact (artefakt)** - plik zapisany przez zadanie do późniejszego wykorzytania (przez inne zadania, albo przez użytkownika). Jeśli jednym z kroków zdefiniowanych w projekcie będzie archiwizacja artefaktów, to każde pomyślnie zakończone zadanie będzie miało zapisane swoje artefakty\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dokumentacja\n", + "- https://www.jenkins.io/doc/book/pipeline/\n", + "- \"Pipeline syntax\" na stronie każdego projektu, np: http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world/pipeline-syntax/" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Zadania [15 pkt]\n", + "Na dzisiejszych zajęciach przygotujemy dwa połączone ze sobą zadania:\n", + "1. Zadanie \"s123456-create-dataset\":\n", + " - pobiera i wybrany na poprzednich zajęciach zbiór danych\n", + " - dokonuje \"data preprocessing\"\n", + " - zapisuje wynik jako artefakt\n", + "2. Zadanie \"s123456-dataset-stats\":\n", + " - obliczy statystyki dla tego zbioru\n", + " - zapisze je jako artefakty" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Polecenia" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 1. Zaloguj się\n", + " - zaloguj się na http://tzietkiewicz.vm.wmi.amu.edu.pl:8080 za pomocą konta wydziałowego (jak w laboratoriach WMI)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2. Utwórz nowy projekt (Job) [1pkt]\n", + "\n", + " - Istnieje kilka rodzajów projektów w Jenkinsie.\n", + "\n", + " - Po kliknięciu \"Nowy projekt\" zobaczymy listę dostępnych rodzajów projektów. Nas interesuje projekt typu \"Pipeline\".\n", + "\n", + " - Jako nazwę wpisz \"s123456-create-dataset\" (w miejsce 123456 proszę wstawić swój numer indeksu)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3. Definicja Pipeline [2pkt]\n", + "\n", + " - Projekty typu Pipeline definiuje się w pliku Jenkinsfile za pomocą skryptu napisanego w jednym z dwóch dostępnych DSL (Domain Specific Language):\n", + " - [Scripted pipeline](https://www.jenkins.io/doc/book/pipeline/syntax/#scripted-pipeline) (podzbiór języka Groovy)\n", + " - [Declarative pipeline](https://www.jenkins.io/doc/book/pipeline/syntax/#declarative-pipeline)\n", + "\n", + " - Pierwszy z nich daje większe możliwości, drugi jest łatwiejszy, lepiej udokumentowany, ale ma mniejszą siłę ekpresji.\n", + "\n", + " - Fragmenty kodu można również generować przy pomocy kreatora, dostępnego pod linkiem [Pipeline syntax](http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world/pipeline-syntax/) na stronie każdego projektu. Jest to bardzo przydatna funkcjonalność, nie tylko dla początkujących użytkowników\n", + "\n", + " - Jenkinsfile może być wprowadzony bezpośrednio z poziomu przeglądarki, albo pobrany z repozytorium.\n", + "\n", + " - Zacznijmy od pierwszej opcji. Jako przykładowego pipeline użyj przykładu Hello World podanego na https://jenkins.io/doc/book/pipeline/getting-started/\n", + "\n", + " - Zapisz projekt i spróbuj go uruchomić (przycisk \"Uruchom\"/\"Run\" na głównej stronie projektu).\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Przykładowy declarative Pipeline (http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world/):\n", + "\n", + "```groovy\n", + "pipeline {\n", + " agent any\n", + " //Definijuemy parametry, które będzie można podać podczas wywoływania zadania\n", + " parameters {\n", + " string (\n", + " defaultValue: 'Hello World!',\n", + " description: 'Tekst, którym chcesz przywitać świat',\n", + " name: 'INPUT_TEXT',\n", + " trim: false\n", + " )\n", + " }\n", + " stages {\n", + " stage('Hello') {\n", + " steps {\n", + " //Wypisz wartość parametru w konsoli (To nie jest polecenie bash, tylko groovy!)\n", + " echo \"INPUT_TEXT: ${INPUT_TEXT}\"\n", + " //Wywołaj w konsoli komendę \"figlet\", która generuje ASCI-art\n", + " sh \"figlet \\\"${INPUT_TEXT}\\\" | tee output.txt\"\n", + " }\n", + " }\n", + " stage('Goodbye!') {\n", + " steps {\n", + " echo 'Goodbye!'\n", + " //Zarchiwizuj wynik\n", + " archiveArtifacts 'output.txt'\n", + " }\n", + " }\n", + " }\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Przykładowy scripted Pipeline (http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world-scripted/):\n", + "\n", + "```groovy\n", + "node {\n", + " stage('Preparation') {\n", + " properties([\n", + " parameters([\n", + " string(\n", + " defaultValue: 'Hello World!',\n", + " description: 'Tekst do wyświetlenie',\n", + " name: 'INPUT_TEXT',\n", + " trim: false\n", + " )\n", + " ])\n", + " ])\n", + " }\n", + " stage('Hello') {\n", + " //Wypisz wartość parametru w konsoli (To nie jest polecenie bash, tylko groovy!)\n", + " echo \"INPUT_TEXT: ${INPUT_TEXT}\"\n", + " //Wywołaj w konsoli komendę \"figlet\", która generuje ASCI-art\n", + " sh \"figlet \\\"${INPUT_TEXT}\\\" | tee output.txt\"\n", + " }\n", + " stage('Goodbye') {\n", + " echo 'Goodbye!'\n", + " //Zarchiwizuj wynik\n", + " archiveArtifacts 'output.txt'\n", + " }\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 4. Repozytorium [2 pkt]\n", + "\n", + "* Jeśli jeszcze tego nie zrobiłaś/zrobiłeś, stwórz nowe _publiczne_ repozytorium *ium_s123456* na wydziałowym serwerze \n", + " (https://laboratoria.wmi.amu.edu.pl/uslugi/pozostale-uslugi/git/)\n", + " W przypadku problemów z utworzeniem repozytorium zdalnie, może być to również dodwolne inne, publicznie dostępne repozytorium, np. na [GitHub](https://github.com/).\n", + " \n", + "* Sklonuj repozytorium lokalnie zgodnie ze wskazówkami wyświetlonymi po jego utworzeniu.\n", + " \n", + "* Utwórz plik \"Jenkinsfile\" (zawartość taka sama jak w punkcie 3.) i dodaj go do repozytorium.\n", + " \n", + "* W ustawieniach (konfiguracji) projektu przełącz \"Pipeline definition\" na \"Pipeline script from SCM\" i podaj ścieżkę do nowo utworzonego repozytorim.\n", + " \n", + "* Projekt powinien się uruchamiać za każdym razem, kiedy pojawią się nowe zmiany w repozytorium:\n", + " \"Konfiguracja\" -> \"Build Triggers\" -> \"Poll SCM\"\n", + "\n", + "* W razie problemów z klonowaniem/pushowaniem repozytorium, użyj protokołu ssh zamiast https (przy kopiowaniu ścieżki do repozytorium). Będzie to wymagało skopiowania zawartości pliku ~/.ssh/id_rsa.pub i wklejenia go w ustawieniach wydziałowego gita w zakładce SSH Keys" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 5. Zapisanie zbioru danych [4 pkt]\n", + "Edytuj zawartość Jenkinsfile tak, żeby realizował on następujące zadania:\n", + " 1. Sklonowanie repozytorium git (krok \"checkout: Check out from version control\")\n", + " 2. Wywołanie skryptu shella (krok \"sh: Shell Script\"). \n", + " - Skrypt powinien pobrać zbiór danych i zapisać wyniki jako artefakty\n", + " - Proszę też zasymulować przetwarzanie pliku, np. podział na podzbiory (shuf + head/tail), przycięcie do określonej długości (head/tail) lub usunięcie niektórych kolumn (cut).\n", + " - Domyślnie tutaj będzie skrypt, który napisali Państwo jako zadanie 1., ale ze względu na brakujące zależności, zapewne nie będzie on działać (rozwiążemy to na następnych zajęciach, korzystając z Dockera). Dlatego proszę w tym celu stworzyć prosty skrypt bash (*.sh).\n", + " - Skrypt powinien być zapisany w repozytorium. Unikamy wpisywania treści skryptów bezpośrednio w Jenkinsfile!\n", + " - Skrypt powinien zapisywać wyniki swojego działania do pliku\n", + " 3. Plik powstały w kroku 2. należy zarchiwizować (krok \"archiveArtifacts\"), tak, żeby mógł być wykorzystany przez kolejne projekty.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6. Hasło do Kaggle\n", + " - Jeśli pobierasz swój zbiór z Kaggle, to możesz mieć problem z uwierzytelnieniem.\n", + " - Żeby kaggle CLI mogło połączyć się z Kaggle API, musi mieć podaną nazwę użytkownika i token.\n", + " - Kiedy używamy CLI lokalnie, korzysta ono z ściągniętego przez nas wcześniej pliku ~/.kaggle/kaggle.json, zawierającego nazwę użytkownika i hasło\n", + " - Zadania na Jenkinsie są wywoływane w systemie przez specjalnego użytkownika (jenkins). Użytkownik ten nie ma w swoim katalogu domowym pliku kaggle.json, więc wywołania kaggle CLI się nie powiodą.\n", + " - Na szczęście, Kaggle CLI umożliwia podanie danych uwierzytelniających w postaci zmiennych środowiskowych [link](https://github.com/Kaggle/kaggle-api#api-credentials):\n", + " ```bash\n", + "export KAGGLE_USERNAME=datadinosaur\n", + "export KAGGLE_KEY=xxxxxxxxxxxxxx\n", + " ```\n", + " - Jenkins natomiast umożliwia utworzenie parametru typu password, którego wartość nie jest nigdzie zapisywana (wartości pozostałych parametrów są zapisywane w zakładce \"Parameters\" każdego build-a, np. [tutaj](http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/hello-world-scripted/1/parameters/)\n", + " - konstukcja `withEnv` w Jenkinsfile, pozwala wywołać wszystkie otoczone nią polecenia z wyeksportowanymi wartościami zmiennych systemowych. Pozwala to np. przekazać wartości parametrów zadania Jenkinsowego do shella (poleceń wywoływanych z `sh`). \n", + " - ten sam rezultat co przy wykorzystaniu `withEnv` możnaby osiągnąć wywołując: `sh \"KAGGLE_USERNAME=${params.KAGGLE_USERNAME} KAGGLE_KEY=${params.KAGGLE_KEY} kaggle datasets list`, ale ten pierwszy wydahe się bardziej elegancki\n", + " - Poniżej przykładowy projekt, który pokazuje jak wywołać Kaggle CLI używając hasła podanego w parametrach zadania:\n", + " \n", + "http://tzietkiewicz.vm.wmi.amu.edu.pl:8080/job/kaggle-CLI-example/\n", + "```groovy\n", + "node {\n", + " stage('Preparation') { \n", + " properties([\n", + " parameters([\n", + " string(\n", + " defaultValue: 'tomaszzitkiewicz',\n", + " description: 'Kaggle username',\n", + " name: 'KAGGLE_USERNAME',\n", + " trim: false\n", + " ),\n", + " password(\n", + " defaultValue: '',\n", + " description: 'Kaggle token taken from kaggle.json file, as described in https://github.com/Kaggle/kaggle-api#api-credentials',\n", + " name: 'KAGGLE_KEY'\n", + " )\n", + " ])\n", + " ])\n", + " }\n", + " stage('Build') {\n", + " // Run the maven build\n", + " withEnv([\"KAGGLE_USERNAME=${params.KAGGLE_USERNAME}\",\n", + " \"KAGGLE_KEY=${params.KAGGLE_KEY}\" ]) {\n", + " sh 'echo KAGGLE_USERNAME: $KAGGLE_USERNAME'\n", + " sh 'kaggle datasets list'\n", + " }\n", + " }\n", + "}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 7. Parametry zadania [1 pkt]\n", + "1. Dodaj do projektu `s123456-create-dataset` parametr teksotwy `CUTOFF`, który umożliwi zdefiniowanie wielkości odcięcia zbioru danych (czyli obetnie liczbę przykładów do `CUTOFF` pierwszych/losowych przykładów).\n", + "2. Wykorzystaj parametr w wywołaniu skryptu tworzącego zbiór\n", + "\n", + "- generowanie kodu definiującego parametry poprzez \"Pipeline syntax -> Snippet Generator\":\n", + " - wybierzz listy pozycji \"Properties: Set job properties\"\n", + " - zaznacz \"This project is parameterized\"\n", + " - Kliknij na \"Add parameter\" i wybierz rodzaj parametru" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 6. Statystki [5 pkt]\n", + "Stwórz projekt (job) s123456-dataset-stats, który zrealizuje następujące kroki:\n", + " 1. Sklonuje repozytorium git \n", + " 2. Skopiuje zarchiwizowane pliki ze zbiorem danych z artefaktów projektu s123456-create-dataset\n", + " - użyj kroku [\"copyArtifacts\"](https://wiki.jenkins.io/display/JENKINS/Copy+Artifact+Plugin)\n", + " - możesz wygenerować potrzebny kod za pomocą generatorów w \"Pipeline Syntax\")\n", + " - dodaj paremetr typu \"Build selector for Copy artifact\", w którym będziesz mógł ustalić z którego builda zadania s123456-create-dataset chcesz skopiować artefakt. Wartość tego parametru będziesz musiał przekazać jako parametr `which build` -> \"Specified by a build parameter\"\n", + " - przykładowy kod copyArtifact:\n", + " ```groovy\n", + " copyArtifacts fingerprintArtifacts: true, projectName: 'MY_PROJECT', selector: buildParameter('BUILD_SELECTOR')\n", + " ```\n", + " - przykładowy kod definicji parametru:\n", + " ```groovy\n", + " properties([parameters([\n", + " buildSelector(\n", + " defaultSelector: lastSuccessful(),\n", + " description: 'Which build to use for copying artifacts',\n", + " name: 'BUILD_SELECTOR')\n", + " ])])\n", + " ```\n", + " 3. Wywoła skrypt shella (krok \"sh: Shell Script\"). \n", + " - Domyślnie tutaj znajdzie się wywołanie naszego skryptu liczącego statystyki.\n", + " - Ze względu na brakujące zależności, na razie wystarczy, że ten skrypt będzie \"liczył\" ilość linii w wejściowym pliku (polecenie \"wc -l\"). \n", + " - Skrypt powinien być zapisany w repozytorium. Unikamy wpisywania treści skryptów bezpośrednio w Jenkinsfile!\n", + " - Skrypt powinien zapisywać wyniki swojego działania do pliku\n", + " 4. Plik powstały w kroku 3. należy archiwizować (krok \"archiveArtifacts\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Groovy", + "language": "groovy", + "name": "groovy" + }, + "language_info": { + "codemirror_mode": "groovy", + "file_extension": ".groovy", + "mimetype": "", + "name": "Groovy", + "nbconverter_exporter": "", + "version": "2.5.6" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": false, + "sideBar": false, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": false, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/IUM_03/aborted.png b/IUM_03/aborted.png new file mode 100644 index 0000000..5c94c73 Binary files /dev/null and b/IUM_03/aborted.png differ diff --git a/IUM_03/blue.png b/IUM_03/blue.png new file mode 100644 index 0000000..811c6c4 Binary files /dev/null and b/IUM_03/blue.png differ diff --git a/IUM_03/grey.png b/IUM_03/grey.png new file mode 100644 index 0000000..0132a71 Binary files /dev/null and b/IUM_03/grey.png differ diff --git a/IUM_03/pipeline.jpg b/IUM_03/pipeline.jpg new file mode 100644 index 0000000..93639e6 Binary files /dev/null and b/IUM_03/pipeline.jpg differ diff --git a/IUM_03/red.png b/IUM_03/red.png new file mode 100644 index 0000000..d589d11 Binary files /dev/null and b/IUM_03/red.png differ diff --git a/IUM_03/yellow.png b/IUM_03/yellow.png new file mode 100644 index 0000000..e50c2c2 Binary files /dev/null and b/IUM_03/yellow.png differ