PowerShell: grep

2024-10-16

Oj, brakuje pod Windows prostej funkcji grep, ale skoro jest PowerShell, to można sobie taką funkcję zrobić samemu!

Get-ChildItem c:\temp\*.* -Recurse -File | Select-String -Pattern "TODO:"

By Rafał Kraik in Power Shell, SQL

GitHub: Pull Request review w GitHub CLI

2024-10-15

GitHub jest super, ale kiedy chcesz zautomatyzować co nieco w zakresie pracy z kodem, to przyda się praca z linii komend. Jeśli jeszcze nie masz zainstalowanego GitHub CLI to pobierz je.

Pełna lista komend gh znajduje się tutaj: GitHub CLI | Take GitHub to the command line

Zaczynamy w cmd od przejścia do właściwego repo. To ważne, bo chociaż komendy gh pozwalają przy pomocy parametru wskazać, na którym repo będą wykonywane pewne czynności, to wygodnie jest korzystać z domyślnego repo, którym jest to, gdzie uruchomiło się gh

Pracę zaczyna się od zalogowania.

gh auth login

Teraz zależnie od konfiguracji mogą wydarzyć się różne rzeczy, ale np. autoryzować można się za pomocą Personal Access Token (PAT). Wystarczy wkleić PAT i już zostaniemy uwierzytelnieni jako właściwy użytkownik z właściwym dostępem.

Aby wyświetlić oczekujące Pull Request uruchom

gh pr list

W odpowiedzi wyświetli się lista oczekujących pull requestów. Można podejrzeć metadane uruchamiając polecenie view z odpowiednim numerem pr:

gh pr view 2

Żeby zobaczyć jakie zmiany idą za pull request uruchom

gh pr diff 2

Jednym z zadań do wykonania podczas akceptacji pull request jest wykonanie review, a w tym review często dodajemy komentarz (opcja c odpowiada za komentarz, a opcja b za blok tekstu):

gh pr review 2 -c -b „good idea!”

Oprócz review można też zaakceptować request:

gh pr review 2 –approve

No i wreszcie pull request można dołączyć do brancha:

gh pr merge 2

Do kompletu zostałoby jeszcze tylko może w jaki sposób utworyć nowy pull request. Zrobisz to tak:

gh pr create –base main –head „automation”–title „Automation added” –body „Added automation to data processing”

By Rafał Kraik in Git

Hyper-V: Konfiguracja sieci „internal” z dostępem do Internetu

2024-10-08

Hyper-V pozwala utworzyć Virtual Switch typu:

  • private – wyizolowana sieć, bez dostępu do sieci hosta
  • internal – wewnętrzna wirtualna sieć, do której dostęp może mieć komputer wirtualny, jak i system hosta, co za tym idzie, można udostępnić Internet
  • external – maszyna wirtualna „widzi” tę samą sieć, co host

Jeśli chcesz, aby maszyna wirtualna miała dostęp do Internetu, to przez eliminację odpada sieć private. Można by skorzystać z sieci external, ale wtedy jest delikatny problem z adresem IP komputera wirtualnego, bo jest on przyznawany przez DHCP, a możesz preferować jednak stały adres IP. Ewentualne skonfigurowanie statycznego adresu IP na maszynie wirtualnej może doprowadzić do konfliktów, a tego byśmy nie chcieli. I tak zostaje sieć internal.

No to po kolei:

  • Otwórz managera Hyper-V i w Virtual Switch Manager utwórz sieć typu internal. Możemy ją nazwać np. InternalSwitch. Można to też zrobić poleceniem PowerShell:
New-VMSwitch -SwitchName "InternalSwitch" -SwitchType Internal
  • Na komputerze hosta w PowerShell uruchomionym jako administrator uruchom kolejno polecenia, które skonfigurują tę maszynę do NAT:
    • Przypisanie do interfejsu hosta odpowiadającego za komunikację z siecią wewnętrzną stałego adresu IP (tutaj 192.168.0.1). Jeśli nie lubisz PowerShella, można to też wyklikać
New-NetNAT -Name "InternalNAT" -InternalIPInterfaceAddressPrefix "192.168.0.0/24"
  • Nadal w Powershell na hoście
    • Konfiguracja wewnętrznej sieci do NAT. Tutaj trzeba użyć PowerShella, lub ewentualnie dodać do hosta rolę RRAS (Routing and Remote Access Server), ale to dość dużo pracy w porównaniu do jednej komendy
New-NetIPAddress -IPAddress 192.168.0.1 -PrefixLength 24 -InterfaceAlias "vEthernet (InternalSwitch)"
  • W ustawieniach maszyny wirtualnej podłącz interfejs sieciowy do sieci internal
  • Skonfiguruj maszynę wirtualną ze stałym adresem IP:
    • Adres IP – dowolny nieużywany jeszcze adres IP z zakresu 192.168.0.0/24
    • Brama domyślna – 192.168.0.1 – czyli ten sam adres IP, co używany na wewnętrznym interfejsie hosta
    • DNS – dowolny, np googlowski 8.8.8.8

U mnie działa!

By Rafał Kraik in Helpdesk

Linux: Kilka połączeń na jednym interfejsie

2024-10-08

Network manager to sprytna bestia. Można dla jednego interfejsu sieciowego zdefiniowac np. polaczenie net-static, ktore przypisuje temu interfejsowi adres statyczny  oraz polaczenie net-dynamic, ktore przydziela interfejsowi adres z DHCP. W danej chwili może być aktywne tylko jedno połącznie, ale w ramach potrzeb można się przełączyć z jednej konfiguracji do drugiej. Zwykle jedno połączenie odpowiada jednemu interfejsowi, ale… warto wiedzieć, że się da 🙂

Tak można by zdefiniować net-static:

nmcli connection add type ethernet con-name net-static ifname eth0 ip4 192.168.1.100/24 gw4 192.168.1.1

a tak można zdefiniować net-dynamic:

nmcli connection add type ethernet con-name net-dynamic ifname eth0

Potem można aktywować net-static:

nmcli connection up net-static

A tak można aktywować net-dynamic:

nmcli connection up net-dynamic

Żeby zobaczyć listę dostępnych połączeń dla jednego interfejsu użyj:

nmcli connection show --active

By Rafał Kraik in Linuxy

Azure: MS Fabric i Spark Notebooks

2024-10-03

Apache Spark (spark) pracuje na obiektach data frame podobnych do tych z Pandas, ale zoptymalizowanych do pracy z Spark engine. Tutaj wczytujemy (read) plik csv (format), który w pierwszym wierszu ma nagłówek (option) z pliku (load):

df = spark.read.format("csv").option("header","true").load("Files/orders/2019.csv")
# df now is a Spark DataFrame containing CSV data from "Files/orders/2019.csv".
display(df)

Plik CSV może nie mieć nagłówka i wtedy po jego wczytaniu kolumny będą miały nazwy _c01, _c02, … Dlatego można „nałożyć” na plik CSV strukturę. W takim przypadku będziemy pracować z czymś, co przypomina tabelę SQL. Rzeczywiście w spark mamy definicje typów SQL. Tutaj wczytujemy bibliotekę pyspark.sql.types, a następnie tworzymy strukturę (StructType), która składa się z kolumn (StructField), a te kolumny mogą być różnego typu, np.: StringType, IntegerType, DateType, FloatType. Zmienia się też sposób wczytania danych. Nadal czytamy (read) dane w postaci csv (format), ale teraz nadajemy im strukturę (schema), a same dane pobieramy z pliku (load).

from pyspark.sql.types import *

orderSchema = StructType([
    StructField("SalesOrderNumber", StringType()),
    StructField("SalesOrderLineNumber", IntegerType()),
    StructField("OrderDate", DateType()),
    StructField("CustomerName", StringType()),
    StructField("Email", StringType()),
    StructField("Item", StringType()),
    StructField("Quantity", IntegerType()),
    StructField("UnitPrice", FloatType()),
    StructField("Tax", FloatType())
    ])

df = spark.read.format("csv").schema(orderSchema).load("Files/orders/2019.csv")
display(df)

Jeśli masz kilka plików CSV o takiej samej strukturze, to możesz je załadować jednocześnie zamieniając nazwę pliku na maskę:

df = spark.read.format("csv").schema(orderSchema).load("Files/orders/*.csv")

W oparciu o jeden data frame można tworzyć kolejne. Można np. wybrać tylko niektóre kolumny wymieniając je w nawiasach kwadratowych. Data frame ma automatycznie kilka przydatnych funkcji. Np. count() policzy ile wierszy ma data frame, distinct() zwróci tylko wiersze unikalne, a co za tym idzie distinct().count() policzy ile wierszy jest unikalnych:

customers = df['CustomerName', 'Email']
print(customers.count())
print(customers.distinct().count())
display(customers.distinct())

Zamiast wymieniania nazw kolumn w nawiasie kwadratowym, można też użyć jawnej funkcji select:

customers = df.select("CustomerName", "Email")

Data frame ma metodę where, która pozwala odfiltrować wiersze spełniające określone warunki. Warunek definiuje się odwołując się do kolumn danych. Np. tutaj filtr wskazuje, że należy wyświetlić te wiersze, które w kolumnie Item mają określony tekst:

customers = df.select("CustomerName", "Email").where(df['Item']=='Road-250 Red, 52')
print(customers.count())
print(customers.distinct().count())
display(customers.distinct())

Dane z data frame można grupować. Wystarczy wskazać, które kolumny zawierają dane „po których” ma się odbywać grupowanie, oraz wybrać funkcję agregującą:

productSales = df.select("Item", "Quantity").groupBy("Item").sum()

Spark może się posługiwać funkcjami występującymi w SQL. Te funkcje znajdują się w module pyspark.sql.functions. Dzięki temu można uruchomić funkcję, np. tutaj year, przekazać do niej kolumnę (col), oraz nadać jej alias (alias). Dodatkowo dane można posortować (orderBy):

from pyspark.sql.functions import *yearlySales = df.select(year(col("OrderDate")).alias("Year")).groupBy("Year").count().orderBy("Year")display(yearlySales)

Podczas transformacji można wyznaczać nowe kolumny, których wartość jest zbudowana w oparciu o istniejące kolumny. W tym celu korzysta się z withColumn, której argumentem jest nazwa nowo tworzonej kolumny oraz wyrażenie wyznaczające wartość tej kolumny. W wyrażeniach można używać funkcji takich, jak: year, month, split, getItem. Kolumny można eliminować lub zmieniać ich kolejność korzystając z metody select lub z nawiasów kwadratowych, w których zostaną wymienione we właściwej kolejności tylko te kolumny, które mają pozostać.

from pyspark.sql.functions import *

transformed_df = df.withColumn("Year", year(col("OrderDate"))).withColumn("Month", month(col("OrderDate")))

transformed_df = transformed_df.withColumn("FirstName", split(col("CustomerName"), " ").getItem(0)).withColumn("LastName", split(col("CustomerName"), " ").getItem(1))

transformed_df = transformed_df["SalesOrderNumber", "SalesOrderLineNumber", "OrderDate", "Year", "Month", "FirstName", "LastName", "Email", "Item", "Quantity", "UnitPrice", "Tax"]

display(transformed_df.limit(5))

Przetworzone dane można „zwrócić” zapisując je (write) do pliku. Można zdecydować, że plik ma być nadpisany (mode), oraz wskazać na format tworzonego pliku (parquet).

transformed_df.write.mode("overwrite").parquet('Files/transformed_data/orders')
print ("Transformed data saved!")

Dane zapisane w formacie parquet można wczytać, wskazując na format i lokalizację. Słowo order w ścieżce oznacza nazwę katalogu. W tym katalogu w formacie parquet są przechowywane dane.

orders_df = spark.read.format("parquet").load("Files/transformed_data/orders")
display(orders_df)

Podczas zapisu danych, można je partycjonować. Tutaj we wskazanym w metodzie parquet katalogu powstaną podkatalogi Year i Month wskazujące na konkretną partycję, np. Year=2022/Month=1:

orders_df.write.partitionBy("Year","Month").mode("overwrite").parquet("Files/partitioned_data")
print ("Transformed data saved!")

Następnie w intuicyjny sposób można wczytać tylko część danych:

orders_2021_df = spark.read.format("parquet").load("Files/partitioned_data/Year=2021/Month=*")
display(orders_2021_df)

Jeśli preferujesz pracować z danymi z wykorzystaniem SQL, to możesz zapisać dane „jako tabela” (saveAsTable). Opisanie danych strukturą SQL może być wykonane w wewnętrznym metastore Sparka (tzw managed table) lub może odnosić się do pliku, który znajduje się w data lake (external table). W tej metodzie nie podajemy pełnej ścieżki dostępu do pliku, co oznacza, że będzie on przechowywany w metastore. „delta” to jeden z dostępnych formatów, inne to: csv, parquet, avro itd. Te formaty mogą wprowadzać typowo bazo-danowe funkcjonalności jak transakcje, wersjonowanie rekordów itp.

df.write.format("delta").saveAsTable("salesorders")spark.sql("DESCRIBE EXTENDED salesorders").show(truncate=False)

Mając tak zdefiniowaną tabelę, można pisać do niej zapytania SQL i zapisywać wynik w data frame spark:

df = spark.sql("SELECT * FROM xyz_lakehouse.salesorders LIMIT 1000")
display(df)

Ponieważ spark pozwala na pracę z różnymi językami, to można przełączyć się do języka SQL komendą %%sql

%%sql
SELECT YEAR(OrderDate) AS OrderYear,
       SUM((UnitPrice * Quantity) + Tax) AS GrossRevenue
       FROM salesorders       
       GROUP BY YEAR(OrderDate)
       ORDER BY OrderYear;

Przełączając się między widokami, można dokonać wstępnej analizy danych na automatycznie generowanych wykresach:

Z drugiej strony Spark to nadal notebook, w którym jest uruchamiany kod Python, dlatego można korzystać z licznych funkcjonalności, które występują w tym języku, np. można tworzyć wykresy z wykorzystaniem popularnego modułu matplotlib:

sqlQuery = "SELECT CAST(YEAR(OrderDate) AS CHAR(4)) AS OrderYear, \
                SUM((UnitPrice * Quantity) + Tax) AS GrossRevenue \
                FROM salesorders \
                GROUP BY CAST(YEAR(OrderDate) AS CHAR(4)) \
               ORDER BY OrderYear"
df_spark = spark.sql(sqlQuery)
df_spark.show()

from matplotlib import pyplot as pl

tdf_sales = df_spark.toPandas()
plt.bar(x=df_sales['OrderYear'], height=df_sales['GrossRevenue'])
plt.show()

Mając pythonowe funkcje możemy wpływać na to, jak taki wykres ma wyglądać:

from matplotlib import pyplot as plt

plt.clf()
plt.bar(x=df_sales['OrderYear'], height=df_sales['GrossRevenue'], color='orange')
plt.title('Revenue by Year')
plt.xlabel('Year')
plt.ylabel('Revenue')
plt.grid(color='#95a5a6', linestyle='--', linewidth=2, axis='y', alpha=0.7)
plt.xticks(rotation=45)
plt.show()

Matplotlib ma dużo możliwości pod względem formatowania wykresów i generalnie można korzystać ze wszystkich z nich. Tu np. tworzymy dwa wykresy w jednym:

from matplotlib import pyplot as plt

plt.clf()
fig, ax = plt.subplots(1, 2, figsize = (10,4))

ax[0].bar(x=df_sales['OrderYear'], height=df_sales['GrossRevenue'], color='orange')
ax[0].set_title('Revenue by Year')

yearly_counts = df_sales['OrderYear'].value_counts()

ax[1].pie(yearly_counts)
ax[1].set_title('Orders per Year')
ax[1].legend(yearly_counts.keys().tolist())
fig.suptitle('Sales Data')

plt.show()

Matplotlib jest uniwersalny, ale też pracochłonny. Równolegle powstają też inne biblioteki do tworzenia wykresów, jak np. seaborn:

import seaborn as sns

plt.clf()
ax = sns.barplot(x="OrderYear", y="GrossRevenue", data=df_sales)
plt.show()

Seaborn pozwala na zastosowanie „tematów” definiujących z grubsza wygląd wykresu:

import seaborn as sns

plt.clf()
sns.set_theme(style="whitegrid")
ax = sns.barplot(x="OrderYear", y="GrossRevenue", data=df_sales)
plt.show()

Do dyspozycji mamy wiele typów wykresów, w tym również liniowy:

import seaborn as sns

plt.clf()
ax = sns.lineplot(x="OrderYear", y="GrossRevenue", data=df_sales)
plt.show()
By Rafał Kraik in SQL

Linux: Konfiguracja podtrzymania połączeń SSH

2024-09-09

Domyślnie, kiedy połączenie SSH pozostanie otwarte przez dłuższy czas bez żadnej aktywności, to serwer takie połączenie zamknie. Można skonfigurować to zachowanie:

Na serwerze w pliku /etc/ssh/sshd_config dodaj linie:

ClientAliveInterval 120
ClientAliveCountMax 720
  • ClientAliveInterval określa czas w sekundach między wysyłaniem przez serwer pakietów keep-alive do klienta. W tym przypadku jest to 120 sekund.
  • ClientAliveCountMax określa maksymalną liczbę pakietów keep-alive, które serwer wyśle bez odpowiedzi od klienta, zanim zakończy połączenie. W tym przypadku jest to 720, co daje łącznie 24 godziny (120 sekund * 720).

Po wykonaniu zmian zrestartuj SSHD:

sudo systemctl restart sshd

Potem zmiany można wykonać jeszcze na kliencie. Zmiany wprowadzamy w pliku sudo nano /etc/ssh/ssh_config:

ServerAliveInterval 120
ServerAliveCountMax 720
By Rafał Kraik in Linuxy

Linux: Ubuntu 24: Stały adres IP

2024-09-09

W Ubuntu 24 do konfiguracji sieci nie używa się Network Managera, tylko NetPlan https://netplan.io/

Konfiguracja znajduje sie w plikach yaml w /etc/netplan

Najprawdopodobniej po instalacji znajdziesz w tym katalogu jakiś plik konfiguracyjny. U mnie był to 50-cloud-init.yaml

Domyślnie w tym pliku znajduje się zapis:

network:
    ethernets:
        eth0:
            dhcp4: true
    version: 2

co oznacza, że adres IP i ustawienia sieciowe dla interfejsu sieciowego mają być pobierane z DHCP

Jeśli chcesz mieć stały adres IP, zmień zawartość pliku np. na:

network:
    ethernets:
        eth0:
            dhcp4: no
            addresses:
              - 192.168.100.100/24
            gateway4: 192.168.100.1
            nameservers:
              addresses:
                - 8.8.8.8
    version: 2

Jeśli serwer korzysta z cloud-init, to zgodnie z instrukcją należy jeszcze wyłączyć automatyczą konfiguracje sieci, co robi się umieszczając w pliku /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg

network: {config: disabled}

Potem pozostaje jeszcze zaaplikować zmiany:

sudo netplan apply
By Rafał Kraik in Linuxy