Podstawy języka Python na Linuksie, jakie chciałem przedstawić w tym mini kursie, opisałem w czterech poprzednich częściach. Nie jest to wszystko, bo jak wiadomo o każdym z języków programowania napisano już tomy książek. Starając się wyjaśnić jak najwięcej, zmieściłem się w pięciu artykułach. Dlatego na pewno nie udało mi się opisać wszystkich zagadnień, jakie bym chciał oraz jakie warte są uwagi. Niemniej jednak uważam, że jest to wstęp, dzięki któremu wprowadziłem Cię w fascynujący świat programowania.

Jeżeli czytałeś wszystkie wspomniane cztery części, to wiesz w jaki sposób możesz wywołać polecenia systemowe z użyciem Pythona. Celem tego rozdziału jest pokazanie, w jaki sposób można lepiej grupować kod oraz tworzyć funkcje tak, aby móc wykorzystać je w innych skryptach. Ta uniwersalność w programowaniu jest bardzo istotna. To, co napiszesz w jednym skrypcie możesz skopiować do drugiego. Dokonać w nim delikatnych zmian lub wykorzystać w formie w jakiej został napisany. Choć organizowanie tego wymaga większego przemyślenia jednak z czasem stwierdzisz, że warto.

Napiszemy nasz skrypt prawie, że od początku. Jednak teraz nie będę wielu rzeczy tłumaczył i pokazywał krok po kroku do czego służą. W tym miejscu bardziej chciałbym, abyś zerknął na logikę jaką się kieruję. Jednak pamiętaj, jestem tylko człowiekiem. W swojej karierze programistycznej pisałem tylko skrypty. Nie nazywam się profesjonalistą, a nawet jakbym możesz coś wymyślić, napisać lepiej. Jeżeli coś takiego zrobisz, miło będzie jak się tym podzielisz.

Teraz przeanalizujemy każdy z poszczególnych bloków kodu.

 

Zapis na newsletter!

Zanim jednak rozwinę temat dalej to jedna informacja na początek. Prowadzę regularny, cotygodniowy newsletter z poradami związanymi z Zabbixem, monitoringiem oraz dobrymi praktykami w IT. Jeżeli nie chcesz przegapić kolejnych części oraz zaproszeń na nasze darmowe szkolenia to zapraszam do zapisu. Wystarczy pobrać mój poradnik. Monitoring IT – Dobre Praktyki. Wystarczy wejść klikając na obrazek:

__________________________________________

 

Cały kod – podstawy języka Python na Linuksie

Nim przejdziemy do szczegółów na początku, chcę pokazać jak cały nowy kod będzie wyglądał:

#!/usr/bin/env python3
import subprocess
import argparse

# from 0 to 7 - CentOS
# from 0 to 9 - Fedora
# from 0 to end - RHEL
# from 0 to 7 and 11 - SLES

systemList = {'centos' : 'yum', 'fedora' : 'dnf', 'rhel' : 'yum', 'sles' : 'zypper', 'debian' : 'apt', 'ubuntu' : 'apt'}

dockerRemoveDEB = ['docker', 'docker-engine', 'docker.io', 'containerd', 'runc']

dockerRemoveRH = ['docker', 'docker-client', 'docker-client-latest', 'docker-common', 'docker-latest', 'docker-latest-logrotate', \
'docker-logrotate', 'docker-engine', 'docker-selinux', 'docker-engine-selinux', 'podman', 'runc']

dockerInstalaction = ['docker-ce', 'docker-ce-cli', 'containerd.io', 'docker-compose-plugin']

def callOptions():
    system = argparse.ArgumentParser()
    system.add_argument("-s", "--system", dest="chosenSystem", help="Write a name of system witch you are using. \
   	 You can choose from: CentOS, Debian, Fedora, RHEL, SLES and Ubuntu")
    options = system.parse_args()
    if not options.chosenSystem:
   	 system.error('You haven\'t specified the system you are using!')
    return options

def systemService(systemName):
    if systemName in systemList:
   	 return True
    else:
   	 return False

def lisOfPackets(fromTo, listInstallRemove):
    i = 1
    packets = ""
    while i != fromTo:
         packets +=  listInstallRemove[i] + " "
         i += 1
    return packets

def systemCli(systemName, function, apps):
    return "sudo " + systemList[systemName] + " " + function + " " + apps + " -y"

def dockerRemove(systemName):
    if systemName == 'centos':
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(8, dockerRemoveRH)),shell=True)
    elif systemName == 'fedora':
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(10, dockerRemoveRH)),shell=True)
    elif systemName == 'rhel':
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(len(dockerRemoveRH), dockerRemoveRH)),shell=True)
    elif systemName == 'sles':
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(8, dockerRemoveRH) + " " + dockerRemoveRH[-1]),shell=True)
    elif (systemName == 'debian') or (systemName == 'ubuntu'):
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(len(dockerRemoveDEB), dockerRemoveDEB)),shell=True)

def debUpdate():
    subprocess.call("sudo apt update", shell=True)

def RepoAdd(systemName):
    if (systemName == 'debian') or (systemName == 'ubuntu'):
   	 subprocess.call("sudo mkdir -p /etc/apt/keyrings", shell=True)
   	 subprocess.call('curl -fsSL https://download.docker.com/linux/' + systemName + '/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg', shell=True)
   	 subprocess.call('echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/"' \
   		 + systemName +'" $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null', shell=True)
   	 debUpdate()
    elif (systemName == 'centos') or (systemName == 'rhel'):
   	 subprocess.call("sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo -y", shell=True)
    elif systemName == 'fedora':
   	 subprocess.call("sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo -y", shell=True)

def dockerInstall(systemName):
    if (systemName == 'centos') or (systemName == 'rhel'):
   	 subprocess.call(systemCli(systemName, 'install', 'yum-utils'), shell=True)
   	 RepoAdd(systemName)
   	 subprocess.call(systemCli(systemName, 'install', lisOfPackets(len(dockerInstalaction), dockerInstalaction)), shell=True)
    elif systemName == 'fedora':
   	 subprocess.call(systemCli(systemName, 'install', 'dnf-plugins-core'), shell=True)
   	 RepoAdd(systemName)
   	 subprocess.call(systemCli(systemName, 'install', lisOfPackets(len(dockerInstalaction), dockerInstalaction)), shell=True)
    elif systemName == 'sles':
   	 pass
    elif systemName == 'debian' or systemName == "ubuntu":
   	 debUpdate()
   	 subprocess.call(systemCli(systemName, 'install', 'ca-certificates curl gnupg lsb-release'), shell=True)
   	 RepoAdd(systemName)
   	 subprocess.call(systemCli(systemName, 'install', lisOfPackets(len(dockerInstalaction), dockerInstalaction)), shell=True)

if systemService(callOptions().chosenSystem.lower()):
    dockerRemove(callOptions().chosenSystem.lower())
    dockerInstall(callOptions().chosenSystem.lower())
else:
    print("Script does not support system you choose. See help -h for more.")

Nim przystąpisz do dalszego czytania, proponuję chociaż zerknąć na przedstawiony powyżej kod. Zastanów się na czym polegają różnice i co ważniejsze, dlaczego tak zrobiłem, a nie inaczej. Następnie pomyśl jak sam byś to zrobił. Jak już stwierdzisz, że poddałeś analizie wszystko przejdź do dalszego czytania.

Elementy programu które pozostaną bez zmian

Są elementy, które chcę pozostawić bez zmian. Moim zdaniem zostały dobrze napisane i jestem z nich zadowolony. W związku z tym nie będę ich modyfikował. Jednak by było łatwiej postaram się podzielić je na grupy i dodać krótki opis w ramach powtórki.

Nagłówek i biblioteki

#!/usr/bin/env python3
import subprocess
import argparse

W przypadku dwóch bloków kodu nie zostały wprowadzone żadne zmiany. W skrypcie określamy, że będziemy korzystali z języki Python w wersji 3. Importujemy dwie biblioteki. Pierwsza z nich subprocess umożliwia wywołanie poleceń systemowych. Druga, przekazanie argumentu do skryptu podczas jego uruchomienia.

Listy oraz słowniki

systemList = {'centos' : 'yum', 'fedora' : 'dnf', 'rhel' : 'yum', 'sles' : 'zypper', 'debian' : 'apt', 'ubuntu' : 'apt'}

dockerRemoveDEB = ['docker', 'docker-engine', 'docker.io', 'containerd', 'runc']

dockerRemoveRH = ['docker', 'docker-client', 'docker-client-latest', 'docker-common', 'docker-latest', 'docker-latest-logrotate', \
'docker-logrotate', 'docker-engine', 'docker-selinux', 'docker-engine-selinux', 'podman', 'runc']

W słowniku systemList przypisujemy do dystrybucji linuksowych odpowiedni menedżer pakietów. W dwóch listach wypisujemy oprogramowanie, które musi zostać usunięte przed instalacją nowego.

Funkcja odpowiedzialna za argumenty podczas uruchomienia skryptu

def callOptions():
    system = argparse.ArgumentParser()
    system.add_argument("-s", "--system", dest="chosenSystem", help="Write a name of system witch you are using. \
   	 You can choose from: CentOS, Debian, Fedora, RHEL, SLES and Ubuntu")
    options = system.parse_args()
    if not options.chosenSystem:
   	 system.error('You haven\'t specified the system you are using!')
    return options

Nie uległa ona żadnym zmianom. Sam nagłówek opisuje dokładnie do czego służy.

Funkcja sprawdzająca

def systemService(systemName):
    if systemName in systemList:
   	 return True
    else:
   	 return False

To funkcja, dzięki której sprawdzamy czy argument przesyłany podczas uruchomienia skryptu znajduje się we wcześniej omawianym słowniku.

Funkcja aktualizacji repozytorium debian i ubuntu

def debUpdate():
    subprocess.call("sudo apt update", shell=True)

Ostatnia z niemodyfikowanych funkcji. Wyjaśnienie znajduje się w nagłówku, dlatego pomijam wyjaśnienie.

Lista instalacyjna

W porównaniu do poprzedniego skryptu postanowiłem zmienić zmienną na listę. W dalszej części artykułu stworzymy funkcje, dzięki której wypisane zostaną odpowiednie dane.

dockerInstalaction = ['docker-ce', 'docker-ce-cli', 'containerd.io', 'docker-compose-plugin']

Funkcja do obsługi pakietów

W pierwotnej formie wszystko wykonywaliśmy bezpośrednio w redHatRemove i debianRemove. Moim zdaniem w ten sposób stworzyliśmy dużo powtarzającego się kodu. Sprawiło to, że stał się mało czytelny. W związku z tym postanowiłem, że rozdzielę niektóre możliwości funkcji na mniejsze i wywołam je z odpowiednimi parametrami.

def lisOfPackets(fromTo, listInstallRemove):
    i = 1
    packets = ""
    while i != fromTo:
         packets +=  listInstallRemove[i] + " "
         i += 1
    return packets

Jeżeli porównasz ten przykład do poprzedniego skryptu zauważysz, że jest on identyczny. Różnica jednak jest taka, że zrobiliśmy z tamtego bloku kodu funkcję do której przekazujemy argumenty. Pierwszy argument jaki przekazujemy jest to liczba która wyznacza zakres, do którego miejsca w tablicy ma zapisać pakiety. Natomiast wspomnianą tablicę przekazujemy jako drugi argument. W związku z tym nie ma tutaj znaczenia czy chcemy przeprowadzić instalację, czy też usunąć jakieś pakiety. Funkcja gromadzi wyznaczone przez nas dane, a następnie przy pomocy instrukcji return przekazuje. W ten sposób uzyskujemy właściwą listę pakietów do usunięcia lub instalacji.

Polecenie do usuwania oraz instalacji pakietów

Jest to drugi element, który postanowiłem przekształcić w funkcję.

def systemCli(systemName, function, apps):
    return "sudo " + systemList[systemName] + " " + function + " " + apps + " -y"

Dzięki niej możemy zarówno instalować, jak i usuwać pakiety. Jako pierwszy argument przekazujemy nazwę systemu. Następnie, funkcję jaką ma pełnić czyli install lub remove. Na samym końcu podajemy pakiety jakie chcemy usunąć lub zainstalować.

Usuwanie niepotrzebnych pakietów

Teraz zerknij na funkcję odpowiedzialną za usuwanie starych pakietów:

def dockerRemove(systemName):
    if systemName == 'centos':
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(8, dockerRemoveRH)),shell=True)
    elif systemName == 'fedora':
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(10, dockerRemoveRH)),shell=True)
    elif systemName == 'rhel':
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(len(dockerRemoveRH), dockerRemoveRH)),shell=True)
    elif systemName == 'sles':
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(8, dockerRemoveRH) + " " + dockerRemoveRH[-1]),shell=True)
    elif (systemName == 'debian') or (systemName == 'ubuntu'):
   	 subprocess.call(systemCli(systemName, 'remove', lisOfPackets(len(dockerRemoveDEB), dockerRemoveDEB)),shell=True)

Pierwsze o czym chcę powiedzieć to to, że na końcu nie korzystam z else, ponieważ w ten sposób wskazuję miejsce, w którym mam możliwość dopisania do kodu nowej dystrybucji. Tym razem nie korzystamy z dwóch oddzielnych funkcji tylko w jednej usuwamy zbędne pakiety z systemów Red Hat oraz Debian.

Zerknijmy jeszcze na instrukcje jaka zostanie wywołana po spełnieniu warunku:

subprocess.call(systemCli(systemName, 'remove', lisOfPackets(8, dockerRemoveRH)),shell=True)

Wewnątrz funkcji subprocess.call przekazujemy jako argument funkcję systemCli. W funkcji systemCli przekazujemy jako pierwszy argument nazwę systemu. Po nazwie systemu ze słownika zostanie pobrana nazwa menadżera pakietów. Następnie przekazujemy instrukcję jaka ma być wykonana. W tym wypadku usuwamy, dlatego przesłaliśmy słowo remove. I na samym końcu korzystamy z funkcji listOfPackets. W niej jako pierwszy argument przekazujemy liczbę pakietów jaka ma być pobrana z listy, którą przekazujemy jako drugi argument.

W przypadku gdy przekażemy przy wywołaniu skryptu nazwę centos to otrzymamy następujący ciąg znaków:

sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine

Czyli w ten sposób skonstruowaliśmy kompletne polecenie do usunięcia pakietów z systemu CentOS. I tak samo jest w przypadku każdej z dystrybucji znajdujących się w słowniku. Natomiast dodatkowo w przypadku sles dodaliśmy ostatnią pozycję z listy. Jeżeli w liście użyjesz liczby minusowej, wypisywane zostaną wartości końcowe. Czyli w przypadku gdy wpisaliśmy -1 to otrzymamy ostatnią wartość listy, -2 przedostatnią i tak dalej.

Repozytoria dockera

W poprzednim skrypcie dodawanie repozytorium było porozrzucane jako klasyczne wywołanie polecenia jak i również jako oddzielna funkcja. Teraz chcę to wszystko pogrupować w jedną. Zrobiłem to w następujący sposób:

def RepoAdd(systemName):
    if (systemName == 'debian') or (systemName == 'ubuntu'):
   	 subprocess.call("sudo mkdir -p /etc/apt/keyrings", shell=True)
   	 subprocess.call('curl -fsSL https://download.docker.com/linux/' + systemName + '/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg', shell=True)
   	 subprocess.call('echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/"' \
   		 + systemName +'" $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null', shell=True)
   	 debUpdate()
    elif (systemName == 'centos') or (systemName == 'rhel'):
   	 subprocess.call("sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo -y", shell=True)
    elif systemName == 'fedora':
   	 subprocess.call("sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo -y", shell=True)

Przyznam, że można to rozwiązać jeszcze w jeden sposób. Stworzyć oddzielne funkcje, w których przekazywane byłyby tylko adresy do repozytoriów. Chociaż może wystarczyłaby tylko nazwa systemu. Jeżeli chcesz jakieś ćwiczenie to właśnie je znalazłeś.

Instalacja dockera

def dockerInstall(systemName):
    if (systemName == 'centos') or (systemName == 'rhel'):
   	 subprocess.call(systemCli(systemName, 'install', 'yum-utils'), shell=True)
   	 RepoAdd(systemName)
   	 subprocess.call(systemCli(systemName, 'install', lisOfPackets(len(dockerInstalaction), dockerInstalaction)), shell=True)
    elif systemName == 'fedora':
   	 subprocess.call(systemCli(systemName, 'install', 'dnf-plugins-core'), shell=True)
   	 RepoAdd(systemName)
   	 subprocess.call(systemCli(systemName, 'install', lisOfPackets(len(dockerInstalaction), dockerInstalaction)), shell=True)
    elif systemName == 'sles':
   	 pass
    elif systemName == 'debian' or systemName == "ubuntu":
   	 debUpdate()
   	 subprocess.call(systemCli(systemName, 'install', 'ca-certificates curl gnupg lsb-release'), shell=True)
   	 RepoAdd(systemName)
   	 subprocess.call(systemCli(systemName, 'install', lisOfPackets(len(dockerInstalaction), dockerInstalaction)), shell=True)

Jeżeli dokładnie przeanalizujesz kod z przykładu, to zauważysz, że jest on prawie identyczny jak ten, który tworzyliśmy w poprzednim skrypcie. W związku z tym nie pozostaje mi nic do komentowania.

Końcowa instrukcja warunkowa

Na samym końcu wywołujemy odpowiednie funkcje w instrukcji warunkowej. Robiliśmy to już w poprzednim skrypcie, jednak zerknijmy na to jak wygląda to w obecnej chwili.

if systemService(callOptions().chosenSystem.lower()):
    dockerRemove(callOptions().chosenSystem.lower())
    dockerInstall(callOptions().chosenSystem.lower())
else:
    print("Script does not support system you choose. See help -h for more.")

Przy przekazywaniu argumentów do funkcji skorzystałem z opcji lower() , która spowoduje, że to co przekażemy będzie zapisane małymi literami. W związku z tym przy przekazywaniu w trakcie wywołania skryptu wielkość liter nie będzie miała znaczenia.

Podsumowanie – podstawy języka Python na Linuksie

Tworzenie skryptów jest bardzo przydatną techniką, jaką każdy użytkownik systemu Linux powinien się nauczyć. To, co przeczytałeś w tych pięciu artykułach jest jedynie wstępem do programowania. Nie opisywałem wszystkich możliwości biblioteki subprosess, a jest ich bardzo dużo. Natomiast wiele wbudowanych narzędzi opisałem w stopniu mocno okrojonym. Jednak pomimo tej prostoty napisaliśmy skrypt, dzięki któremu przeprowadzimy instalację dockera w kilku dystrybucjach. Myślę, że sama umiejętność stworzenia takiego jest bardzo cenna. Szczególnie, że wprowadziła Cię w język programowania jakim jest Python. Mam nadzieję, że przedstawione podstawy języka Python na Linuksie będą dla Ciebie przydatne.

Natomiast ważna dla mnie jest twoja opinia. Jestem w stanie zmodyfikować to, co przeczytałeś. Dlatego jeżeli czegoś nie rozumiesz lub uważasz, że coś jest źle wytłumaczone, daj znać. Napisz do nas, a gdy dowiem się o co chodzi, postaram się napisać to tak, abyś zrozumiał.

Nowe pomysły, też są mile widziane. Mam nadzieję, że w przyszłości będę mógł kontynuować ten mini kurs o Pythonie. A już na pewno szybciej to wykonam jak dowiem się co jeszcze brakuje w tym kursie. Lecz pamiętaj, tutaj chcę pokazać podstawy, a nie zaawansowane zagadnienia.

Chcesz więcej?

Oczywiście, jeżeli nie chcesz przegapić tego typu materiałów, to zapisz się na mój newsletter i przy okazji odbierz darmowe bonusy, np. e-booka ze zdjęcia poniżej! Prowadzę regularny, cotygodniowy newsletter z poradami związanymi z automatyzacją, monitoringiem oraz dobrymi praktykami w IT.

Zapisy TUTAJ

Zapisz się na Newsletter i odbierz za darmo e-book "Monitoring IT"!

X