Cake Build ile Cross-Platform DevOps

Deniz İrgin
Codefiction
Published in
11 min readJul 18, 2018

Günümüzde artık build otomasyonu, CI/CD süreçleri her geliştirilen projenin olmazsa olmazı. Çoğu projede artık benzer adımlar otomatize ediliyor.

  1. Projenin son halinin source control’dan (Git, Svn, Tfs vb.) indirilmesi.
  2. Bağımlılıklarının (dependencies, packages) indirilmesi.
  3. Projenin build edilmesi.
  4. Unit testlerin çalıştırılması.
  5. Versiyonlanıp çeşitli ortamlara (Test, Stage, Live vb.) deploy edilmesi

Bunları yaparken çeşitli CI/CD araçlarından yararlanıyoruz, Jenkins, Travis CI, AppVeyor, TeamCity ve Bamboo ilk aklıma gelenler. Bu araçların çoğunu kullanmış biri olarak diyebilirim ki, evet çoğunun çok güzel eklentileri var, bazılarında arayüzde sürükle bırak ile çoğu şeyi yapabiliyorsunuz. Ama eninde sonunda custom script’ler (Powershell, Bash) yazmanız gerekiyor.

Eğer cross-platform bir framework üzerinde (Ör: .NET Core) library’ler geliştiriyorsanız, yazdığınız kodun desteklenen bütün platformlarda (Windows, Linux, macOS) çalışıyor olduğundan emin olmanız gerekiyor.

Open-source bir .NET Standard library’si geliştirdiğinizi düşünelim. Library’nin .NET Framework, Mono ve .NET Core desteği olmasını istiyorsunuz. Yani yazdığınız library hem farklı runtime’ları desteklemeli hem de Windows, Linux ve macOS gibi işletim sistemlerinde de çalışmalı. Bugün open-source dünyasında CI/CD tool’u olarak genellikle Travis (Linux ve macOS) ve AppVeyor kullanılıyor. Şimdi diyelim ki projeniz bitti, unit test’leri hazırladınız ve bu araçları kullanarak yukarıda bahsettiğim adımları gerçekleştirmek istiyorsunuz.

Hem Travis hem de AppVeyor sizden build, unit test ve deploy için çalıştırılacak script’ler istiyor. Biri Linux diğeri Windows olduğu için aynı işi yapan bir Bash script’i ve bir Powershell script’i yazmanız lazım. Dolayısıyla hem Bash hem de Powershell bilmeniz gerekiyor, ayrıca aynı iş mantığını iki script’de de tekrarlamak durumundasınız.

İlk bakışta bütün bunlar çok zor gelmiyor olabilir, sonuç olarak dotnet cli komutlarını çağırarak bunların çoğunu yapabiliyoruz. Ama bazen daha fazlasını da yapmak istiyoruz. Örneğin sadece nuget paketi çıkartmasın, zip olarak da çıktı alsın ve bir yere upload etsin, release notlarını çıkartsın, versiyonlamayı yapsın, yeni versiyonun çıktığını Twitter’da paylaşsın, Slack’e de göndersin, AWS veya Azure’a deployment işlemlerini yapsın vb.

Bütün bunları Powershell ve Bash kullanarak da yapmak mümkün tabiki ama hem iki kat iş hem de daha karmaşık işler yapmak istediğiniz zaman oldukça zorlanabilirsiniz.

Peki Cake Nedir?

Cake (C# Make) is a cross platform build automation system with a C# DSL to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages. — http://cakebuild.net/

Cake Build aslında yukarıda bahsettiğim bütün işlemleri C# kullanarak yapmanıza olanak sağlayan bir araç. Yani siz C# script’inizi yazıyorsunuz ve o script Windows, Linux ve Mac Os da aynı şekilde çalışıyor. En büyük avantajı bu aslında cross-platform çalışabilmesi. Ama bir diğer avantajı da tahmin edebileceğiniz gibi bu tür işleri C# ile yapmanın çok daha kolay olması.

Cake aslında C#’ı bir script dili olarak kullanıyor ve script’leri compile etmek için arka planda Roslyn alt yapısından yararlanıyor. Cake kendisini bootstrap etmek için ve build scripti’ini (build.cake) çalıştırmak için bir PowerShell script’ini (build.ps1) veya Bash (build.sh) script’ini kullanır. Bu script’lerin içinde yapılanlar aslında basit, yazdığınız cake dosyasının çalışması için gerekli olan nuget paketlerini indiriyor ve son olarak Cake.exe’yi veya Cake.CoreCLR.dll’i çalıştırıyor ve sonrasında cake script’iniz çalışıyor. Bu bootstrapper script’lerinin (hem ps1 hem sh) Cake Community’si tarafından hazırlanmışı var, indirip sadece C# dosyasınıza odaklanarak hemen çalışmaya başlayabiliyorsunuz. Malesef bu hazır script biraz eski kalmış durumda ve Cake.exe’yi kullanıyor. Yani Windows ortamında .NET Framework’ün Linux ortamında Mono Runtime’ın yüklü olması lazım. Cake Build uzun zamandır .NET Core runtime’ını da destekliyor. Cake.exe yerine Cake.CoreCLR.dll’i destekleyen bootstrapper script’lerini yazının ilerleyen kısımlarında paylaşacağım, hem benim yazdığım hem de başkalarının yazdığı bir sürü CoreCLR bootstrapper script’ini ufak bir aramayla bulmanız mümkün.

Bootstrapper script’inin yaptıklarından birininin de yazdığınız cake dosyasının bağımlı olduğu nuget paketlerini indirmek olduğundan bahsetmiştim. Cake için yazılmış 300’e yakın add-in var ve cake dosyalarınızın içerisinde bunları nuget paketleri olarak ekleyerek kullanabiliyorsunuz. AWS’den Azure’a, Docker’dan çeşitli database’lere erişim library’lerine kadar yazılmış bir sürü add-in var. Çok aktif bir community’si olduğundan dolayı her ihtiyaca uygun bir add-in yazılmış.

Bir Cake Dosyasının Anatomisi

Cake Build’in çok başarılı bir dökümantasyonu var. Aradığınız herşeyi bulabiliyorsunuz. Özellike başlamadan önce Getting Started dökümanını okumanızı tavsiye ederim.

Kafanızda biraz daha oturması için aşağıdaki örnek proje yapısını inceleyebilirsiniz.

Aslında üç temel dosya var. Bunlar build.ps1, build.sh ve build.cake. ps1 ve sh dosyaları daha önce de bahsettiğim gibi bootstrap görevini üstleniyorlar ve aslında build.cake dosyasını çalıştırıyorlar. Örnek projede travis.yml (Linux ve Mac Os) build.sh dosyasını appveyor.yml (Windows) build.ps1 dosyasını çalıştırıyor.

Peki ne var bu build.cake dosyasının içinde?

Gördüğünüz gibi C# var :) . Yukarıdan aşağıya inceleyeme başlayalım.

En yukarıda nuget paket referanslarının verildiğini göreceksiniz. Daha önce de bahsettiğim gibi cake dünyasında 300'e yakın add-in yazılmış. Bunları ve diğer .Net ortamındaki bütün paketleri ekleyip kullanabiliyorsunuz ki bu çok büyük bir güç aslında. Bootstrapper script’lerinin bir görevi de bu nuget paketlerini indirilip kullanıma hazır hale getirmesi.

Arguments kısmında bootstrap script’ine dışarıdan geçmek istediğiniz parametreleri tanımlıyorsunuz, örnek dosyada hangi task’ın çalıştırılacağı ve build configurasyonu geçilmiş. Siz de örneğin CI/CD ortamınızda tuttuğunuz AWS, Azure key’lerini veya benzeri credential’ları geçebilirsiniz.

Task bölümü ise aslında cake’in merkezinde yer alıyor. Yapmak istediğiniz şeyleri adım adım task’lara bölüyorsunuz ve task’ların birbirine depend olmasını sağlayabiliyorsunuz. Örneğin yukarıdaki cake dosyasında başlangıç task’ı olarak “Default” verilmiş, en alttaki RunTarget komutu parametre olarak bir task ismi alıyor. Yukarıdaki örnekte akış, şöyle ilerliyor.

Clean -> Restore-Nuget-Packages -> Build -> Run Unit Tests -> Default

Build task’ını incelersek eğer ilk gözüme çarpan “Restore-Nuget-Packages” task’ına depeden olduğu olacatır. Yani build task çalışmadan önce nuget task’ı çalışacak. Task’ın içeriğini incelediğinizde bir if ifadesinde IsRunningOnWindows metodunun çağrıldığını göreceksiniz. Bu bize cake tarafından sağlanan metodlardan birtanesi. Eğer Windows ortamındaysak MsBuild, Linux ortamındaysak Mono XBuild çalıştırılıyor. Her ne kadar Mono’nun yeni versiyonları da artık MsBuild kullansa da Cake ile yapabileceklerinizi anlamanız açısından güzel bir örnek.

Yukarıdaki örnekte en basit haliyle bir .Net projesinin cake kullanılarak nasıl build işlemi yapıldığını görüyoruz. Paketler indiriliyor, build ediliyor ve unit test’ler çalıştırılıyor. Bir library olsaydı nuget paketinin oluşturulup, nuget.org a upload edilmesini de ekleyebilirdik. Ama dediğim gibi bundan çok daha karmaşık işlemleri C#’ın gücünü kullanarak yapabilirsiniz.

Neler yapılabileceğini daha iyi anlamamız açısından iki tane daha örnek proje üzerinden ilerlemek istiyorum.

Biri çalıştığım firma olan Armut.com’un reposundan. Cake kullanarak nasıl docker container’larını build edip, testlerini çalıştırıp, docker hub’a upload ettiğimizi gösteriyor.

Diğeri ise benim Cake’e yazdığım bir add-in, Cake.Electron.Net. Electron cross-platform bir desktop uygulama geliştirme platformu. Visual Studio Code, Slack, Spotify gibi uygulamalar Electron ile yazılmış uygulamalara verilebilecek örneklerden. Electron node.js‘in özelleştirilmiş bir runtime’ını kullanıyor ve javascript, html tabanlı desktop uygulamları yazmanıza olanak sağlıyor. Electron.NET ise Electron etrafına yazılmış bir wrapper. Yani .NET Core ile geliştirdiğiniz web uygulamalarını da (buna Angular, React gibi SPA’ler de dahil) desktop uygulaması olarak çalıştırmanıza imkan veriyor. Electron.NET geliştirilirken yanında build, deploy işlemlerini kolaylaştırmak için CLI (command-line interface) da eklemişler. Benim yazdığım Cake add-in’i ise bu komutları cake ile kullanabilmenizi sağlıyor. Tabi ben de bu add-in’in CI/CD pipeline’ını cake kullanarak geliştirdim :)

Cake.Electron.Net

Yazının başında bootstrapper script’inden ve ne yaptığından bahsetmiştim, o yüzden bu script’lerden birini anlatarak başlamak istiyorum. Aşağıdaki bootsrapper script’i Cake Community’si tarafından geliştirilmiş bootstrapper script’inin Cake CoreClr’a uyarlanmış hali.

Dosyayı yukarıdan aşağıya incelediğimizde sırasıyla yapılan işlemler ;

  1. Tools directory’si oluşturuluyor, bu directory cake’in bağımlı olduğu nuget paketlerini tutmak için kullanılıyor.
  2. .Net Core yüklü mü değil mi kontrolü yapılıyor.
  3. tools.csproj dosyası oluşturuluyor. Bu dosyanın amacı aslında cake’in dotnet add kullanılarak kolay bir şekilde indirilmesi. Alternatif olarak doğrudan nuget.org’a request atılıp, ardından inen dosya unzip edilip tools’un altına atılabilirdi.
  4. Bootstrapper script’ine geçilen parametreler kontrol edilip, uygun formata getiriliyor ve az önce indirdiğimiz cake’i dotnet komutu kullanılarak çalıştırıyor.

Bu işlemlerden sonra cake çalışıp bizim yazdığımız build.cake dosyasını bulup çalıştırıyor.

Yukarıdaki bootstrapper script’inin, .NET Framework ile çalışan versiyonundan (Cake.exe) farkı, csproj yerine eski packages.config kullanması ve Cake.CoreClr.dll yerine Cake.exe’nin çağırılması. Ama dediğim gibi bunun dezavantajı cake dosyasınızı Linux‘da çalıştıracaksanız mono runtime’ının yüklü olması gerekliliği.

Şimdi gelelim asıl işi yaptığımız build.cake dosyasına.

Test task’ını inceleyerek başlayalım. appveyor değişkenine Linux’da çalışıyorsa empty string, Windows’da çalışıyorsa appveyor ile ilgili bir string değer atandığını göreceksiniz. Buradaki ön kabul aslında şu, eğer Linux ortamında çalışıyorsa TravisCI, Windows ortamında çalışıyorsa AppVeyor kullanılıyor. AppVeyor’un .Net projelerindeki unit test sonuçlarını toplamak için özel bir adapter’ı var ve bu bir nuget paketi olarak sunuluyor. Bu paketi projenizde kullanıp, dotnet test komutuna argüman olarak geçerseniz, test sonuçları bir port üzerinden AppVeyor’un rapor sistemine aktarılıyor.

Sonrasında bize cake tarafından sağlanan DotNetCoreTest metodunun framework parametresi verilerek (burada .netcoreapp2.0) çağırıldığını göreceksiniz. Bu metod aslında dotnet test CLI komutuna yazılmış bir wrapper ve yaptığı işin bir cmd (veya sh) process’i başlatıp komutu çalıştırmaktan farkı yok. Alternatif olarak şöyle de yazılabilirdi ;

StartProcess(“dotnet”, new ProcessSettings {
Arguments = $”test ./src/Tests/Cake.Electron.Net.Tests/Cake.Electron.Net.Tests.csproj -c {configuration} -f {netCoreTarget}{appveyor}”
});

Ama bence cake’in gücü bu tür wrapper’larının kullanılmasından geçiyor, işimizi çok daha basitleştiriyor. .NET Core CLI haricinde özellikle docker, AWS, Azure, API çağırımları gibi add-in’leri, yapılmak istenilen işleri çok daha basitleştiriyor.

Kodu incelemeye devam edersek, bir if ifadesinde Windows ortamında çalışılıyorsak tekrar DotNetCoreTest methodunun çağırılıyor ama farklı olarak framework parametresine net46 değeri geçiliyor. Aslında buraya kadar yapılan, .NET Standard ile yazılmış bir library’nin unit test’lerinin hem .NET Core’da hem de .NET Framework’de ayrı ayrı çalıştırılması.

Fakat else ifadesinde, net46 testlerinin Linux ortamında çalıştırılırken daha farklı bir yol izlendiğini göreceksiniz. Bunun sebebi, Linux ortamında dotnet test komutu net46 için çalışıtırılırken haliyle mono runtime’ına ihtiyaç duyuyor fakat xunit, dotnet test ve mono arasında bu noktada bir uyumsuzluk var. Bununla ilgili github’da dotnet/cli reposuna bir issue açılmış durumda. Bu sorunu aşmak için önceden indirilmiş olan xunit.testrunner.console.exe’yi mono runtime’ını kullanarak çalıştırıyoruz.Aslında dotnet test’in yapması gereken şeyi biz başka bir yöntemle yapıyoruz gibi düşünebilirsiniz, sonuç aynı.

Build task’ında da önce nuget restore sonrasında da build işleminin yapıldığını görebilirsiniz.

Cake’in asıl gücünün cross-platform olmasından bahsetmiştim. Hem TravisCI hem de AppVeyor konfigurasyon dosyalarını inceleyelim.

Görüldüğü üzere iki dosya da sadece bootstrapper script’leri çalıştırılıyor. Travis için fazladan .NET Core 2.0 ve Mono 5.10.0 versiyonlarının yükleneceği bilgisi eklenmiş. Mono’yu yüklememizin sebebi cake’i çalıştırmak değil, net46 testlerini çalıştırabilmek. Eğer böyle bir ihtiyacımız olmasaydı, sadece .NET Core’un yüklenmesi yeterli olacaktı.

Diğer örneğe geçmeden önce son olarak cake.config dosyasından bahsetmek istiyorum.

[Nuget]
Source=https://api.nuget.org/v3/index.json
UseInProcessClient=true
LoadDependencies=false
[Paths]
Tools=./tools
Addins=./tools/Addins
Modules=./tools/Modules
[Settings]
SkipVerification=false

Yukarıdaki ayarlar cake’in biri hariç default ayarları. Cake normalde Nuget Api’nin v2'sini kullanıyor, v3'ü kullanması için yukarıdaki gibi bir değişiklik yapmanız lazım. Onun haricinde Paths içerisinde de özelleştirmeler yapabilirsiniz.

Bu örnek aslında bir .NET Standard library’sinin build süreci için standart bir örnek.

Sonraki örneğimizde tamamen farklı bir iş yaparak, docker image’ları oluşturulup, docker hub’a upload edilmesini göstermek istiyorum.

armutcom/docker-dotnet-core-images

Armut.com’da geliştireceğimiz bir Angular Universal uygulaması için official docker image’ını özelleştirmemiz gerekti. .NET Core server-side rendering’i destekliyor fakat bu tip uygulamalar .NET Core run-time’ının yanı sıra, Node.js run-time’ını da ihtiyaç duyuyorlar çünkü bir angular (veya başka bir SPA) uygulmasının server-side rendering işlemini yapan library’ler arka planda node.js kullanıyor.

Docker image’ına fazladan node.js de yükleriz diye başladığımız proje, bir anda Ubuntu (16.04 Xenial ve 18.04 Bionic) tabanlı kendi dockerfile’larımızı oluşturduğumuz ve CI/CD pipeline’ını Cake ile yaptığımız bir open-source projeye dönüştü.

İşin ilginci tarafı, hem bazı problemler yaşadığımızdan hem de image’ların boyutu iki ayrı runtime’ı yükleyince çok büyüdüğünden dolayı, sonradan o angular universal uygulamasını node.js ile yapmaya karar verdik. Yine de bu projeyi temel alarak kendi .Net Core projelerimizi de bu image’lara geçirdik ve şuan canlıda da kullandığımız docker image’ları bunlar.

Temel olarak dört farklı tipte image var. Bunlar ;

  1. .NET Core Runtime -> En temel image, örneğin console uygulmaları için bunu kullanıyoruz.
  2. ASP.NET Core -> .NET Core üzerine ASP.NET Core bağımlıklarının yüklenmiş hali, web ve api uygulamaları için kullanıyoruz.
  3. ASP.NET Core SSR-SPA -> ASP.NET Core üzerine node.js runtime yüklenmiş hali, angular universal veya diğer server-side rendering gerektiren uygulamalar için kullanıyoruz.
  4. .NET Core Sdk -> İçerisinde .NET Core runtime + sdk yüklü. Ayrıca npm, yarn çalıştırmak için node.js de yükledik. Bu image daha çok build işlemleri için ara container olarak kullanılıyor.

Cake ise bu projede şu amaçlarla kullanıldı.

  1. Dockerfile’lardan docker image’larının oluşturulması ve tag’lenmesi.
  2. Testlerin çalıştırılması. Testler için dummy işler yapan Console, ASP.NET Core API ve MVC ve ASP.NET Core SSR-SPA uygulamaları oluşturduk. Image’ları oluşturduktan sonra bunlardan container oluşturup, dummy uygulamaları bu container’larda çalıştırıyoruz, örneğin web uygulamalarının çalıştığından emin olmak için belli bir port’dan http request atıyoruz, cevap 200 ise çalışıyordur diye varsayıyoruz.
  3. Bütün herşey yolunda gitmişse image’ları docker hub’daki armutcom reposuna atıyoruz.

Docker image’larının, tag’lerinin ve test’lerinin nasıl yapılacağı ile ilgili bir manifest.json dosyamız var. Cake script’imiz bu dosyayı okuyup ona göre işlemler yapıyor. O yüzden manifest.json dosyasını inceleyerek başlayalım.

Yukarıda manifest.json’ın oldukça kısaltımış bir halini görüyorsunuz. Json dosyası iki bölüme ayrılmış durumda. Repos bölümünde hangi dockerfile’ının build edileceği ve nasıl tag’leneceği bilgisi var. Tests kısmında ise bu image’ın hangi uygulamayla test edileceği ve hangi port’dan dışarı açılacağı, build için hangi image’ın kullanılacağı gibi bilgiler var.

Yeni bir .NET Core versiyonu çıktığı zaman veya başka bir Linux distro’su kullanmak istediğimizde tek yapmamız gereken buradaki manifest.json dosyasına yeni bilgiler eklemek. Zaten yakında zamanda da .NET Core 2.1 image’larını da bu şekilde ekledik.

Peki cake dosyasında neler yaptık?

En yukarıdaki nuget paketlerinden başlayalım. Cake.Json, Cake.Docker ve Cake.Http paketlerini kullanık, Cake.Json paketi, bir dosyadan json okumayı kolaylaştırıyor. Cake.Docker ise Docker CLI’a yazılmış bir wrapper. Json deserialize işlemi için Newtonsoft.Json’ı kullandık, testleri yaparken http request’leri atmak için Sytem.Net.Http library’sini kullandık.

Daha aşağıda manifest.json dosyasını deserialize etmek için kullandığımız modellerin tanımlamaları var.

“Build-Container” taks’ı manifest.json’da tanımlı docker image bilgilerini iterate ederek ve Cake.Docker library’sini kullanarak image’ları build ediyor ve tag’liyor.

“Tests” task’ı ise yine manifest.json içerisindeki bilgileri kullanarak, build aşamasında oluşturduğumuz image’lardan, container’lar oluşturuyor ve içerisine test uygulamalarımızı koyup çalıştırıyor. Ardından uygulamanın ayağa kalkması için 5 sn bekledikten sonra ilgili port’lardan http request’i atıyor. Eğer cevap 200 ise herşey yolunda, değilse exception fırlatıyoruz ve çalışmayı durduruyoruz.

“Publish” task’ı ise diğer task’lardan bağımsız, bootsrapper script’ine belli bir parametre geçilerek çalıştırılan bir task. Bu şekilde tasarlamamızın sebebi, her pull request için veya master branch’indeki her değişiklik için publish edilmesini istemememizden dolayı. Publish etmek istiyorsak, commit mesajına “trigger deploy” yazmamız gerekiyor. Bu task’ında aslında tek yaptığı build task’ı sonucunda build edilmiş image’ları alarak docker hub’a push etmek.

Docker image’larını build ederken sadece Linux’a ihtiyaç duyduğumuzdan, bu projede sadece TravisCI kullandık.

Önceki projedekinden farklı bir travis konfigurasyonu olduğunu fark edeceksiniz. Öncelik burada mono’ya ihtiyacımız olmadığından dolayı yüklemedik. Ama services kısmını incelerseniz, travis’e bu makinaya docker yüklemesi gerektiğini belirtiyoruz. Ayrıca bu sefer deploy ayarlarını da yaptık, before_deploy kısmında docker login’i çalıştırarak kendi repo’umuza login oluyoruz, bunu yapmadan image’ları push edemeyiz. $REGISTERY_USER ve $REGISTERY_PASS travis tarafından environment variable olarak bize veriliyor. deploy kısmında ise build.sh bootsrapper scripti’nin target=Publish argümanıyla çalıştırıldığını göreceksiniz. Cake size istediğiniz task’ı çalıştırma imkanı veriyor. Böylece build ve test task’larını çalıştırmadan doğrudan publish task’ını çalıştırabiliyoruz. Deploy işleminin sadece master branch’ine yeni birşey geldiği zaman çalışması gerektiğini belirttik ama dediğim gibi biz publish task’ı içerisinde commit mesajını da kontrol ediyoruz.

Cake Geliştirme Araçları

Cake içerisinde C# yazıyoruz ama normal şartlarda ne Visual Studio ne de Visual Studio Code bu dosya uzantısını tanımıyor. Cake takımı her ikisine de bir extension yazmış, bunları yüklerseniz hem debugging hem de intellisense desteği de geliyor.

Ben VS Code extension’ını kullanmayı tercih ediyorum. Bunun için öncelikle Cake.Bakery diye ayrı bir paketi yüklemeniz lazım, burada nasıl yüklenip kullanılacağı ile ilgili güzel bir dökümantasyon var.

Son Olarak

Bu yazıda cake‘i nasıl kullanabileceğinize dair iki farklı alanda iki farklı örnek verdim. Yazıdaki projelerin hepsini github’dan clone’layıp inceleyebilirsiniz.

Cake Build oldukça güçlü bir tool ve özellikle .NET dünyasındaki open-source projelerde oldukça popüler. Biz şirket içerisindeki CI/CD süreçlerimizde de kullanmaya karar verdik. Dediğim gibi custom PowerShell veya Bash yazmanız gereken her yerde Cake kullanabilir ve daha fazlasını çok daha az bir efor ve vakitle yapabilirsiniz.

Umarım yararlı bir yazı olmuştur.

Bir sonraki yazımda görüşünceye dek hepinize iyilikler dilerim.

--

--