Automatyzowanie wszystkiego (tworzenie aplikacji w chmurze Real-World za pomocą platformy Azure)

Autor : Rick Anderson, Tom Dykstra

Pobierz naprawę projektu lub pobierz książkę elektroniczną

Książka elektroniczna Building Real World Cloud Apps with Azure (Tworzenie rzeczywistych aplikacji w chmurze za pomocą platformy Azure ) jest oparta na prezentacji opracowanej przez Scotta Guthrie. Wyjaśniono w nim 13 wzorców i rozwiązań, które mogą pomóc w pomyślnym tworzeniu aplikacji internetowych dla chmury. Aby zapoznać się z wprowadzeniem do książki elektronicznej, zobacz pierwszy rozdział.

Pierwsze trzy wzorce, które faktycznie zastosujemy do dowolnego projektu tworzenia oprogramowania, ale szczególnie w projektach w chmurze. Ten wzorzec dotyczy automatyzacji zadań programistycznych. Jest to ważny temat, ponieważ procesy ręczne są powolne i podatne na błędy; automatyzowanie jak największej liczby z nich ułatwia skonfigurowanie szybkiego, niezawodnego i elastycznego przepływu pracy. Jest to niezwykle ważne w przypadku programowania w chmurze, ponieważ można łatwo zautomatyzować wiele zadań, które są trudne lub niemożliwe do zautomatyzowania w środowisku lokalnym. Można na przykład skonfigurować całe środowiska testowe, w tym nowe serwery internetowe i maszyny wirtualne zaplecza, bazy danych, magazyn obiektów blob (magazyn plików), kolejki itp.

Przepływ pracy metodyki DevOps

Coraz częściej słyszysz termin "DevOps". Termin opracowany z uznaniem, że należy zintegrować zadania programistyczne i operacyjne w celu wydajnego opracowywania oprogramowania. Rodzaj przepływu pracy, który chcesz włączyć, to taki, w którym można utworzyć aplikację, wdrożyć ją, nauczyć się jej użycia w środowisku produkcyjnym, zmienić go w odpowiedzi na zdobytą wiedzę i powtórzyć cykl szybko i niezawodnie.

Niektóre udane zespoły programistyczne w chmurze wdrażają wiele razy dziennie w środowisku na żywo. Zespół platformy Azure wdrażał główną aktualizację co 2–3 miesiące, ale teraz publikuje drobne aktualizacje co 2–3 dni i główne wersje co 2–3 tygodnie. Wprowadzenie do tego tempa naprawdę pomaga reagować na opinie klientów.

Aby to zrobić, należy włączyć cykl programowania i wdrażania, który jest powtarzalny, niezawodny, przewidywalny i ma krótki czas cyklu.

Przepływ pracy metodyki DevOps

Innymi słowy, okres między określeniem funkcji a czasem korzystania z niej przez klientów i przekazywaniem opinii musi być możliwie najkrótszy. Pierwsze trzy wzorce — automatyzowanie wszystkich, kontrola źródła oraz ciągła integracja i ciągłe dostarczanie — to najlepsze rozwiązania, które zalecamy w celu włączenia tego rodzaju procesu.

Skrypty zarządzania platformą Azure

W ramach wprowadzenia do tej książki e-book zobaczyliśmy konsolę internetową Portal zarządzania Azure. Portal zarządzania umożliwia monitorowanie wszystkich zasobów wdrożonych na platformie Azure i zarządzanie nimi. Jest to prosty sposób tworzenia i usuwania usług, takich jak aplikacje internetowe i maszyny wirtualne, konfigurowanie tych usług, monitorowanie operacji usługi itd. Jest to doskonałe narzędzie, ale jego użycie jest procesem ręcznym. Jeśli zamierzasz opracować aplikację produkcyjną o dowolnym rozmiarze, a zwłaszcza w środowisku zespołowym, zalecamy zapoznanie się z interfejsem użytkownika portalu, aby nauczyć się i eksplorować platformę Azure, a następnie zautomatyzować procesy, które będziesz wykonywać wielokrotnie.

Niemal wszystko, co można zrobić ręcznie w portalu zarządzania lub z poziomu programu Visual Studio, można również wykonać przez wywołanie interfejsu API zarządzania REST. Skrypty można pisać przy użyciu Windows PowerShell lub użyć platformy open source, takiej jak Chef lub Puppet. Możesz również użyć narzędzia wiersza polecenia Bash w środowisku dla komputerów Mac lub Linux. Platforma Azure ma interfejsy API tworzenia skryptów dla wszystkich tych różnych środowisk i ma interfejs API zarządzania platformą .NET na wypadek, gdy chcesz napisać kod zamiast skryptu.

W przypadku aplikacji Fix It utworzyliśmy kilka skryptów Windows PowerShell, które automatyzują procesy tworzenia środowiska testowego i wdrażania projektu w tym środowisku, a następnie zapoznamy się z zawartością tych skryptów.

Skrypt tworzenia środowiska

Pierwszy skrypt, na który przyjrzymy się, nosi nazwę New-AzureWebsiteEnv.ps1. Tworzy środowisko platformy Azure, w którym można wdrożyć aplikację Fix It na potrzeby testowania. Główne zadania wykonywane przez ten skrypt są następujące:

  • Utwórz aplikację internetową.
  • Tworzenie konta magazynu (Wymagane w przypadku obiektów blob i kolejek, jak pokazano w kolejnych rozdziałach).
  • Utwórz serwer SQL Database i dwie bazy danych: bazę danych aplikacji i bazę danych członkostwa.
  • Przechowuj ustawienia na platformie Azure, których aplikacja będzie używać do uzyskiwania dostępu do konta magazynu i baz danych.
  • Utwórz pliki ustawień, które będą używane do automatyzacji wdrażania.

Uruchamianie skryptu

Uwaga

W tej części rozdziału przedstawiono przykłady skryptów i wprowadzone polecenia w celu ich uruchomienia. To pokaz i nie zapewnia wszystkiego, co musisz wiedzieć, aby uruchomić skrypty. Aby uzyskać instrukcje krok po kroku, zobacz Dodatek: Poprawka przykładowej aplikacji.

Aby uruchomić skrypt programu PowerShell, który zarządza usługami platformy Azure, należy zainstalować konsolę Azure PowerShell i skonfigurować ją do pracy z subskrypcją platformy Azure. Po skonfigurowaniu możesz uruchomić skrypt tworzenia fix it środowiska za pomocą polecenia podobnego do następującego:

.\New-AzureWebsiteEnv.ps1 -Name <websitename> -SqlDatabasePassword <password>

Parametr Name określa nazwę, która ma być używana podczas tworzenia bazy danych i kont magazynu, a SqlDatabasePassword parametr określa hasło dla konta administratora, które zostanie utworzone dla SQL Database. Istnieją inne parametry, których można użyć, aby przyjrzeć się później.

Okno programu PowerShell

Po zakończeniu działania skryptu w portalu zarządzania zobaczysz, co zostało utworzone. Znajdziesz dwie bazy danych:

Bazy danych

Konto magazynu:

Konto magazynu

A aplikacja internetowa:

Witryna sieci Web

Na karcie Konfigurowanie aplikacji internetowej widać, że ma ona ustawienia konta magazynu i parametry połączenia bazy danych SQL skonfigurowane dla aplikacji Fix It.

appSettings i connectionStrings

Folder usługi Automation zawiera <teraz również plik websitename.pubxml>. Ten plik przechowuje ustawienia używane przez program MSBuild do wdrożenia aplikacji w właśnie utworzonym środowisku platformy Azure. Na przykład:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <WebPublishMethod>MSDeploy</WebPublishMethod>
    <SiteUrlToLaunchAfterPublish>http://fixitdemo.azurewebsites.net</SiteUrlToLaunchAfterPublish>
    <ExcludeApp_Data>False</ExcludeApp_Data>
    <MSDeployServiceURL>waws-prod-bay-003.publish.azurewebsites.windows.net:443</MSDeployServiceURL>
    <DeployIisAppPath>fixitdemo</DeployIisAppPath>
    <RemoteSitePhysicalPath />
    <SkipExtraFilesOnServer>True</SkipExtraFilesOnServer>
    <MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
    <EnableMSDeployBackup>True</EnableMSDeployBackup>
    <UserName>$fixitdemo</UserName>
    <PublishDatabaseSettings></PublishDatabaseSettings>
  </PropertyGroup>
</Project>

Jak widać, skrypt utworzył kompletne środowisko testowe, a cały proces jest wykonywany w około 90 sekundach.

Jeśli ktoś inny w Twoim zespole chce utworzyć środowisko testowe, może po prostu uruchomić skrypt. Nie tylko jest to szybkie, ale także mogą mieć pewność, że używają środowiska identycznego z używanym środowiskiem. Nie można mieć pewności co do tego, jeśli wszyscy konfigurują elementy ręcznie przy użyciu interfejsu użytkownika portalu zarządzania.

Spojrzenie na skrypty

W rzeczywistości istnieją trzy skrypty, które wykonują tę pracę. Wywołasz jedną z wiersza polecenia i automatycznie użyjesz dwóch pozostałych do wykonania niektórych zadań:

  • New-AzureWebSiteEnv.ps1 jest głównym skryptem.

    • New-AzureStorage.ps1 tworzy konto magazynu.
    • New-AzureSql.ps1 tworzy bazy danych.

Parametry w skry skrycie głównym

Główny skrypt, New-AzureWebSiteEnv.ps1, definiuje kilka parametrów:

[CmdletBinding(PositionalBinding=$True)]
Param(
    [Parameter(Mandatory = $true)]
    [ValidatePattern("^[a-z0-9]*$")]
    [String]$Name,                             
    [String]$Location = "West US",             
    [String]$SqlDatabaseUserName = "dbuser",   
    [String]$SqlDatabasePassword,              
    [String]$StartIPAddress,                   
    [String]$EndIPAddress                      
    )

Wymagane są dwa parametry:

  • Nazwa aplikacji internetowej tworzonej przez skrypt. (Jest to również używane dla adresu URL: <name>.azurewebsites.net.)
  • Hasło dla nowego użytkownika administracyjnego serwera bazy danych tworzonego przez skrypt.

Parametry opcjonalne umożliwiają określenie lokalizacji centrum danych (wartość domyślna to "Zachodnie stany USA"), nazwę administratora serwera bazy danych (domyślnie wartość "dbuser") i regułę zapory dla serwera bazy danych.

Tworzenie aplikacji internetowej

Pierwszą rzeczą, jaką wykonuje skrypt, jest utworzenie aplikacji internetowej przez wywołanie New-AzureWebsite polecenia cmdlet, przekazanie do niej wartości parametrów nazwy aplikacji internetowej i lokalizacji:

# Create a new website
$website = New-AzureWebsite -Name $Name -Location $Location -Verbose

Tworzenie konta magazynu

Następnie skrypt główny uruchamia skrypt New-AzureStorage.ps1 , określając "*<nazwę> witryny internetowej*magazyn" dla nazwy konta magazynu i tę samą lokalizację centrum danych co aplikacja internetowa.

$storageAccountName = $Name + "storage"
 
$storage = $scriptPath\New-AzureStorage.ps1" -Name $storageAccountName -Location $Location

New-AzureStorage.ps1 wywołuje polecenie cmdlet w New-AzureStorageAccount celu utworzenia konta magazynu i zwraca wartości nazwy konta i klucza dostępu. Aplikacja będzie potrzebować tych wartości, aby uzyskać dostęp do obiektów blob i kolejek na koncie magazynu.

# Create a new storage account
New-AzureStorageAccount -StorageAccountName $Name -Location $Location -Verbose
 
# Get the access key of the storage account
$key = Get-AzureStorageKey -StorageAccountName $Name
 
# Generate the connection string of the storage account
$connectionString = "BlobEndpoint=http://$Name.blob.core.windows.net/;QueueEndpoint=http://$Name.queue.core.windows.net/;TableEndpoint=http://$Name.table.core.windows.net/;AccountName=$Name;AccountKey=$primaryKey"
 
#Return a hashtable of storage account values
Return @{AccountName = $Name; AccessKey = $key.Primary; ConnectionString = $connectionString}

Nie zawsze warto utworzyć nowe konto magazynu; Skrypt można ulepszyć, dodając parametr, który opcjonalnie kieruje go do korzystania z istniejącego konta magazynu.

Tworzenie baz danych

Następnie skrypt główny uruchamia skrypt tworzenia bazy danych ,New-AzureSql.ps1, po skonfigurowaniu domyślnych nazw baz danych i reguł zapory:

$sqlAppDatabaseName = "appdb"
$sqlMemberDatabaseName = "memberdb"
$sqlDatabaseServerFirewallRuleName = $Name + "rule"
# Create a SQL Azure database server, app and member databases
$sql = $scriptPath\New-AzureSql.ps1 `
    -AppDatabaseName $sqlAppDatabaseName `
    -MemberDatabaseName $sqlMemberDatabaseName `
    -UserName $SqlDatabaseUserName `
    -Password $SqlDatabasePassword `
    -FirewallRuleName $sqlDatabaseServerFirewallRuleName `
    -StartIPAddress $StartIPAddress `
    -EndIPAddress $EndIPAddress `
    -Location $Location

Skrypt tworzenia bazy danych pobiera adres IP maszyny deweloperskiej i ustawia regułę zapory, aby maszyna deweloperzy mogła nawiązać połączenie z serwerem i zarządzać nim. Następnie skrypt tworzenia bazy danych wykonuje kilka kroków konfigurowania baz danych:

  • Tworzy serwer przy użyciu New-AzureSqlDatabaseServer polecenia cmdlet .

    $databaseServer = New-AzureSqlDatabaseServer -AdministratorLogin $UserName -AdministratorLoginPassword $Password -Location $Location
    
  • Tworzy reguły zapory, aby umożliwić maszynie deweloperów zarządzanie serwerem i umożliwienie aplikacji internetowej nawiązywania z nim połączenia.

    # Create a SQL Azure database server firewall rule for the IP address of the machine in which this script will run
    # This will also allowlist all the Azure IP so that the website can access the database server
    New-AzureSqlDatabaseServerFirewallRule -ServerName $databaseServerName -RuleName $FirewallRuleName -StartIpAddress $StartIPAddress 
    -EndIpAddress $EndIPAddress -Verbose
    New-AzureSqlDatabaseServerFirewallRule -ServerName $databaseServer.ServerName -AllowAllAzureServices 
    -RuleName "AllowAllAzureIP" -Verbose
    
  • Tworzy kontekst bazy danych zawierający nazwę serwera i poświadczenia przy użyciu New-AzureSqlDatabaseServerContext polecenia cmdlet .

    # Create a database context which includes the server name and credential
    # These are all local operations. No API call to Azure
    $credential = New-PSCredentialFromPlainText -UserName $UserName -Password $Password
    $context = New-AzureSqlDatabaseServerContext -ServerName $databaseServer.ServerName -Credential $credential
    

    New-PSCredentialFromPlainText jest funkcją w skrypcie, która wywołuje ConvertTo-SecureString polecenie cmdlet w celu zaszyfrowania hasła i zwraca PSCredential obiekt, tego samego typu, który Get-Credential zwraca polecenie cmdlet.

  • Tworzy bazę danych aplikacji i bazę danych członkostwa przy użyciu New-AzureSqlDatabase polecenia cmdlet .

    # Use the database context to create app database
    New-AzureSqlDatabase -DatabaseName $AppDatabaseName -Context $context -Verbose
     
    # Use the database context to create member database
    New-AzureSqlDatabase -DatabaseName $MemberDatabaseName -Context $context -Verbose
    
  • Wywołuje lokalnie zdefiniowaną funkcję, aby utworzyć parametry połączenia dla każdej bazy danych. Aplikacja będzie używać tych parametrów połączenia do uzyskiwania dostępu do baz danych.

    $appDatabaseConnectionString = Get-SQLAzureDatabaseConnectionString -DatabaseServerName $databaseServerName -DatabaseName $AppDatabaseName -UserName $UserName -Password $Password
    $memberDatabaseConnectionString = Get-SQLAzureDatabaseConnectionString -DatabaseServerName $databaseServerName -DatabaseName $MemberDatabaseName -UserName $UserName -Password $Password
    

    Get-SQLAzureDatabaseConnectionString jest funkcją zdefiniowaną w skry skrycie, która tworzy parametry połączenia z podanych wartości parametrów.

    Function Get-SQLAzureDatabaseConnectionString
    {
        Param(
            [String]$DatabaseServerName,
            [String]$DatabaseName,
            [String]$UserName,
            [String]$Password
        )
    
        Return "Server=tcp:$DatabaseServerName.database.windows.net,1433;Database=$DatabaseName;User ID=$UserName@$DatabaseServerName;Password=$Password;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;"
    }
    
  • Zwraca tabelę skrótów z nazwą serwera bazy danych i parametrami połączenia.

    Return @{ `
        Server = $databaseServer.ServerName; UserName = $UserName; Password = $Password; `
        AppDatabase = @{Name = $AppDatabaseName; ConnectionString = $appDatabaseConnectionString}; `
        MemberDatabase = @{Name = $MemberDatabaseName; ConnectionString = $memberDatabaseConnectionString} `
    }
    

Aplikacja Fix It używa oddzielnych baz danych członkostwa i aplikacji. Istnieje również możliwość umieszczania zarówno danych członkostwa, jak i aplikacji w pojedynczej bazie danych.

Ustawienia aplikacji ze sklepu i parametry połączenia

Platforma Azure ma funkcję umożliwiającą przechowywanie ustawień i parametrów połączenia, które automatycznie przesłaniają zawartość zwracaną do aplikacji podczas próby odczytania appSettings kolekcji lub connectionStrings w pliku Web.config. Jest to alternatywa dla stosowania przekształceńWeb.config podczas wdrażania. Aby uzyskać więcej informacji, zobacz Przechowywanie poufnych danych na platformie Azure w dalszej części tej książki elektronicznej.

Skrypt tworzenia środowiska przechowuje na platformie Azure wszystkie appSettings wartości i connectionStrings , które aplikacja musi uzyskać dostęp do konta magazynu i baz danych podczas uruchamiania na platformie Azure.

# Configure app settings for storage account and New Relic
$appSettings = @{ `
    "StorageAccountName" = $storageAccountName; `
    "StorageAccountAccessKey" = $storage.AccessKey; `
    "COR_ENABLE_PROFILING" = "1"; `
    "COR_PROFILER" = "{71DA0A04-7777-4EC6-9643-7D28B46A8A41}"; `
    "COR_PROFILER_PATH" = "C:\Home\site\wwwroot\newrelic\NewRelic.Profiler.dll"; `
    "NEWRELIC_HOME" = "C:\Home\site\wwwroot\newrelic" `
}
# Configure connection strings for appdb and ASP.NET member db
$connectionStrings = ( `
    @{Name = $sqlAppDatabaseName; Type = "SQLAzure"; ConnectionString = $sql.AppDatabase.ConnectionString}, `
    @{Name = "DefaultConnection"; Type = "SQLAzure"; ConnectionString = $sql.MemberDatabase.ConnectionString}
)
# Add the connection string and storage account name/key to the website
Set-AzureWebsite -Name $Name -AppSettings $appSettings -ConnectionStrings $connectionStrings

New Relic to struktura telemetrii, którą demonstrujemy w rozdziale Monitorowanie i telemetria . Skrypt tworzenia środowiska uruchamia również ponownie aplikację internetową, aby upewnić się, że pobiera nowe ustawienia Relic.

# Restart the website to let New Relic hook kick in
Restart-AzureWebsite -Name $websiteName

Przygotowywanie do wdrożenia

Na końcu procesu skrypt tworzenia środowiska wywołuje dwie funkcje w celu utworzenia plików, które będą używane przez skrypt wdrażania.

Jedna z tych funkcji tworzy profil publikowania (<plik websitename.pubxml>). Kod wywołuje interfejs API REST platformy Azure w celu pobrania ustawień publikowania i zapisuje informacje w pliku publishsettings . Następnie używa informacji z tego pliku wraz z plikiem szablonu (pubxml.template) do utworzenia pliku pubxml zawierającego profil publikowania. Ten dwuetapowy proces symuluje działania w programie Visual Studio: pobierz plik publishsettings i zaimportuj go, aby utworzyć profil publikowania.

Druga funkcja używa innego pliku szablonu (website-environment.template) do utworzenia pliku website-environment.xml zawierającego ustawienia używane przez skrypt wdrażania wraz z plikiem pubxml .

Rozwiązywanie problemów i obsługa błędów

Skrypty są podobne do programów: mogą zakończyć się niepowodzeniem, a kiedy chcesz wiedzieć, jak bardzo możesz o awarii i przyczynach. Z tego powodu skrypt tworzenia środowiska zmienia wartość zmiennej VerbosePreference z SilentlyContinue na Continue tak, aby wszystkie pełne komunikaty zostały wyświetlone. Zmienia również wartość zmiennej ErrorActionPreference z Continue na Stop, aby skrypt zatrzymał się nawet wtedy, gdy wystąpią błędy niepowodujące zakończenia:

# Set the output level to verbose and make the script stop on error
$VerbosePreference = "Continue"
$ErrorActionPreference = "Stop"

Przed wykonaniem jakiejkolwiek pracy skrypt przechowuje godzinę rozpoczęcia, aby można było obliczyć czas, który upłynął po zakończeniu:

# Mark the start time of the script execution
$startTime = Get-Date

Po zakończeniu pracy skrypt wyświetla czas, który upłynął:

# Mark the finish time of the script execution
$finishTime = Get-Date
# Output the time consumed in seconds
Write-Output ("Total time used (seconds): {0}" -f ($finishTime - $startTime).TotalSeconds)

Dla każdej operacji klucza skrypt zapisuje pełne komunikaty, na przykład:

Write-Verbose "[Start] creating $websiteName website in $Location location"
$website = New-AzureWebsite -Name $websiteName -Location $Location -Verbose
Write-Verbose "[Finish] creating $websiteName website in $Location location"

Skrypt wdrażania

Co skrypt New-AzureWebsiteEnv.ps1 wykonuje podczas tworzenia środowiska, skrypt Publish-AzureWebsite.ps1 wykonuje wdrożenie aplikacji.

Skrypt wdrażania pobiera nazwę aplikacji internetowej z pliku website-environment.xml utworzonego przez skrypt tworzenia środowiska.

[Xml]$envXml = Get-Content "$scriptPath\website-environment.xml"
$websiteName = $envXml.environment.name

Pobiera hasło użytkownika wdrożenia z pliku publishsettings :

[Xml]$xml = Get-Content $scriptPath\$websiteName.publishsettings 
$password = $xml.publishData.publishProfile.userPWD[0]
$publishXmlFile = Join-Path $scriptPath -ChildPath ($websiteName + ".pubxml")

Wykonuje polecenie MSBuild , które kompiluje i wdraża projekt:

& "$env:windir\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" $ProjectFile `
    /p:VisualStudioVersion=12.0 `
    /p:DeployOnBuild=true `
    /p:PublishProfile=$publishXmlFile `
    /p:Password=$password

Jeśli określono Launch parametr w wierszu polecenia, wywołuje Show-AzureWebsite polecenie cmdlet, aby otworzyć domyślną przeglądarkę pod adresem URL witryny internetowej.

If ($Launch)
{
    Show-AzureWebsite -Name $websiteName
}

Skrypt wdrażania można uruchomić za pomocą polecenia w następujący sposób:

.\Publish-AzureWebsite.ps1 ..\MyFixIt\MyFixIt.csproj -Launch

Po zakończeniu przeglądarka zostanie otwarta z witryną działającą w chmurze pod adresem <websitename>.azurewebsites.net URL.

Poprawka aplikacji wdrożonej na platformie Windows Azure

Podsumowanie

Dzięki tym skryptom można mieć pewność, że te same kroki będą zawsze wykonywane w tej samej kolejności przy użyciu tych samych opcji. Pomaga to zagwarantować, że każdy deweloper w zespole nie przegapi czegoś lub coś nie zadziera lub wdroży coś niestandardowego na własnej maszynie, która nie będzie działać tak samo w środowisku innego członka zespołu lub w środowisku produkcyjnym.

W podobny sposób można zautomatyzować większość funkcji zarządzania platformy Azure, które można wykonać w portalu zarządzania przy użyciu interfejsu API REST, skryptów Windows PowerShell, interfejsu API języka platformy .NET lub narzędzia Bash, które można uruchomić w systemie Linux lub Mac.

W następnym rozdziale przyjrzymy się kodowi źródłowemu i wyjaśnimy, dlaczego ważne jest uwzględnienie skryptów w repozytorium kodu źródłowego.

Zasoby