Github Actions orqali .NET CI/CD

2023 Mar 19, 14:20 • Wahid Abduhakimov (@wahid)

Asosiy postga havola 👉: https://www.notion.so/wahids/Github-Actions-orqali-NET-CI-CD-66d97197bc924008a4dfaf6198b8671c

Bu galgi postimizda .NET dasturiga CI/CD (Continues Ingegration/Continues Delivery) qurishni ko’rib chiqamiz. Ushbu postda aynan Github Actions workflowlar orqali CI/CD yasaymiz.

Avvalo CI/CD nimalarni o’z ichiga olishini aniqlab olamiz. CI/CD tushunchasi juda keng qamrovli tushincha va dastur murakkabligiga juda ko’plab yumushlarni o’z ichiga qamrab oladi.

Qisqacha qilib aytganda esa, CI/CD - dasturdagi yangi o’zgarishlarni silliqqina bitta oqimda foydalanuvchiga yetib borishini ta’minlab beruvchi avtomatlashtirilgan yumushlar ketma-ketligi. Yangi kod yetib kelishi bilanoq minimal inson omili orqali shu kod foydalanuvchiga yetib borishini taminlaydi.

Building, Unit Testing, Integration Testing va Deployment kabilar eng ko’p ishlatiladigan CI/CD yumushlar hisoblanadi. Bugungi postda biz Github Actions orqali yangi qo’shilgan kodlarni silliqlik bilan Digital Ocean bulut servisidagi (🤡) Linux container ichiga deploy qilamiz.

Agar meni kontentlarim sizlarga yoqayotgan bolsa, meni ijtimoiy tarmoqlarimga qo’shilishni unutmang!

  • Telegram kanal:
  • Facebook:
  • LinkedIn:
  • Github:

.NET API va xUnit proyektlar yaratish

Bu qisimda oddiygina matematik amal bajaradigan API va shu amal to’g’ri bajarilayotganini tekshiradigan Unit Test yozib olamiz.

Qoyidagi komandalarni terib loyihani boshlaymiz.

mkdir MathApi; cd MathApi           # yangi papka yaratib olish uchun
dotnet new gitignore                # gitignore file ni birinchi yaratib olamiz
dotnet new sln                      # solution file yaratib olamiz
dotnet new webapi -o MathApi -f net6.0       # api project
dotnet new xunit -o MathApi.Tests -f net6.0  # test project

dotnet sln add MathApi/MathApi.csproj              # API ni solution ga qo'shamiz
dotnet sln add MathApi.Tests/MathApi.Tests.csproj  # testni ham solutionga qo'shamiz

dotnet build            # hammasi ko'ngildagiday kechgani tekshiramiz

# test project ga API project ni ulash va kerakli kutubxonalarni o'rnatish
dotnet add MathApi.Tests/MathApi.Tests.csproj reference MathApi/MathApi.csproj
dotnet add MathApi.Tests/MathApi.Tests.csproj package Microsoft.Extensions.DependencyInjection -v 6.0.0

<aside> 💡 Loyihalar yaratilgandan keyin API va Test loyihalar ichiga kirib template kodlarni o’chirib tashlashni unutmang.

</aside>

MathApi

Birinchi navbatda MathApi loyihani tayyor holatga keltiramiz. Buning uchun avval template kodlarni o’chirib tashlagach bittagina MathController.cs nomli kontroller yaratamiz. Bu controller MathService orqali berilgan ikkita long tipidagi sonlarni yig’inidisini qaytaradi.

IMathService.cs kontrakti.

namespace MathApi.Services;

public interface IMathService
{
    Task<long> AddAsync(long a, long b);
}

MathService.cs klasi.

namespace MathApi.Services;

public class MathService : IMathService
{
    public Task<long> AddAsync(long a, long b)
    {
        try
        {
            var result = checked(a + b);
            return Task.FromResult<long>(result);
        }
        catch(Exception)
        {
            throw new OverflowException($"The sum of {a} and {b} can't fit in long datatype.");
        }
    }
}

<aside> 💡 Etibor bering, natija checked kalit so’zi orqali hisoblanyapti. Sababi agar ikkala sonning yig’indisi long tipiga sig’maydigan bo’lsa shunchaki tuncated ya’ni qirqib tashlangan son joylamasdan exception otiladi.

</aside>

MathController.cs kontrolleri.

using MathApi.Services;
using Microsoft.AspNetCore.Mvc;

namespace MathApi.Controllers;

[ApiController]
[Route("[controller]")]
public class MathController : ControllerBase
{
    [HttpGet("add")]
    public async Task<IActionResult> AddAsync(
        [FromQuery] long a,
        [FromQuery] long b,
        [FromServices] IMathService mathService)
        {
            var result = await mathService.AddAsync(a, b);
            return Ok(new { result = result });
        }
}

Program.cs klasi.

using MathApi.Services;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddTransient<IMathService, MathService>();

var app = builder.Build();
app.MapControllers();
app.Run();

MathApi.Tests

Endi navbat dunyodagi eng sodda unit testni yozishga. Bunda biz MathService klasi ikkala sonni to’g’ri qo’sha olishiga ishonch hosil qilamiz.

Services/MathServiceTests.cs klasi.

using MathApi.Services;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace MathApi.Tests.Services;

public class MathServiceTests
{
    private readonly ServiceProvider services;

    public MathServiceTests()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddTransient<IMathService, MathService>();

        this.services = serviceCollection.BuildServiceProvider();
    }

    [Theory]
    [InlineData(long.MaxValue, 1)]
    [InlineData(long.MinValue, -1)]
    public async Task ThrowsAnExceptionWhenResultDoesNotFitLong(long a, long b)
    { 
        // givenvar mathService = services.GetRequiredService<IMathService>();

        // when var task = async () => await mathService.AddAsync(a, b);

        // thenawait Assert.ThrowsAsync<OverflowException>(task);
    }

    [Fact]
    public async Task AddsTwoNumbersCorrectly()
    {
        // givenvar mathService = services.GetRequiredService<IMathService>();
        var a = 1;
        var b = 3;
        
        // when var result = await mathService.AddAsync(a, b);
        
        // then
        Assert.Equal(3, result);
    }
}

Endi bemalol solution papkaga borib dotnet test komandasi orqali hamma testlar to’g’ri o’tishini tekshiring.

API proyekti uchun Dockerfile fayl yozish

Buning uchun MathApi proyektini root papkasida Dockerfile nomli fayl yaratib quyidagi kodni yozamiz.

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /App

# Copy everything
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /App
COPY --from=build-env /App/out .
EXPOSE 5000
ENV ASPNETCORE_URLS=http://+:5000
ENTRYPOINT ["dotnet", "MathApi.dll"]

Barcha kodni ishga tushuruvchi docker-compose.yml

.sln fayl mavjud papka ichida docker-compose.yml nomli fayl yarating va quyidagi kodni kodni yozing.

version: '3'

services:
  mathapi:
    image: davidwahid/mathapi-test
    container_name: "mathapi"
    restart: always
    ports:
      - "80:5000"
    networks:
      - web

networks:
  web:
    external: true

Github Actions workflow

Endi kodimizni Build, Test, Dockerize va Deploy qiladigan workflow fayl yozib olamiz.

.github/workflows/cicd.yml faylni aynan shunday papka ichida yarating va quyidagi kodlarni ichiga to’ldiring.

Workflow bir nechta ketma-ket bajariladigan job ya’ni ishlardan iborat bo’ladi.

  1. .NET build job
name: Build, Test and Deploy
on:push:branches:- main
      
jobs:build:runs-on: ubuntu-lateststeps:- name: Checkout codeuses: actions/checkout@v2- name: Setup .NET 6uses: actions/setup-dotnet@v1with:dotnet-version: '6.0.x'- name: Restore dependenciesrun: dotnet restore- name: Buildrun: dotnet build --configuration Release

Github Actions workflow biron bir trigger orqali ishga tushiriladi. Bizni misolda esa main branchga yangi kod push qilinganda workflow ishga tushadi.

jobs: qismida ko’rib turganingiz bitta build nomli job yaratilgan. U job .NET 6 SDK o’rnatilgan contrainer ichida ishga tushadi. Kodni restore va build qiladi.

  1. .NET test job
  test:needs: buildruns-on: ubuntu-lateststeps:- name: Checkout codeuses: actions/checkout@v2- name: Setup .NET 6uses: actions/setup-dotnet@v1with:dotnet-version: '6.0.x'- name: Restore dependenciesrun: dotnet restore- name: Testrun: dotnet test --configuration Release

Yuqoridagi kod .NET proyektimizni test qiladi. Hamma unit testlarni ishga tushiradi.

<aside> 💡 1. Yuqoridagi kodni .github/workflows/cicd.yml fayl davomiga yozamiz. 2. needs: build qismiga e’tibor bering. U build job tugamasidan test job boshlanmasligini taminlaydi. Ya’ni test job build jobga bog’liq.

</aside>

  1. Dockerize
  dockerize:needs: testruns-on: ubuntu-latestenv:DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}steps:- name: Checkout codeuses: actions/checkout@v2- name: Build Docker imagerun: docker build -t $DOCKER_USERNAME/mathapi -f MathApi/Dockerfile .- name: Push Docker imagerun: |
        docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
        docker push $DOCKER_USERNAME/mathapi

Ushbu kod biz yuqorida yaratgan Dockerfile orqali koddan docker image yasaydi va uni dockerhubga push qiladi.

<aside> 💡 Quyidagi action secretlarni Repo>Settings>Secrets and Variables>Actions tabga borib qo’shib kelish kerak.

</aside>

  • DOCKER_USERNAME — sizning dockerhub.io saytidagi login ismingiz
  • DOCKER_PASSWORD — sizning dockerhub.io saytidagi parolingiz
  1. Deploy to Digital Ocean

Oxirgi bosqichda Digital Ocean cloud servisida yangi Droplet (Linux machine) yaratimiz. Unga SSH connection qilamiz va Github Actions Workflowimizga kodni automatik deploy qilish qismini qo’shamiz.

A. SSH kalit yaratish

  • cd ~/.ssh komandasi orqali o’z mashinangizda SSH papkasiga kiring. Agar u yo’q bo’lsa uni yarating.
  • ~/.ssh papkasi ichida ssh-keygen komandasi orqali yangi ssh kalit yarating. Komanda terilganda kalitni saqlash uchun fayl nomini so’raydi. Unga istagan nomni bering. Qolgan qismlariga faqatgina enter tugmasini bosing.
  • cat {kalit_nomi}.pub komandasi orqali public kalitni terminalga chop eting va uni ko’chirib oling.

B. Droplet yaratish

  • Rasmdagiday Create>Droplet tugamsini bosib droplet yarating.
  • Untitled
  • Barcha kerakli parametrlarni o’zingizga yoqqanday to’ldiring.
  • Choose Authentication Method qismiga yetganda esa SSH Key tugmasini bosib keyin New SSH Key tugmasini bosing.
  • Rasmda ko’rsatilgan joyga avvalroq ko’chirib olingan SSH public kalitini joylashtiring. Unga nom bering va dropletni yakunlang.

C. Linux mashinani sozlash

O’zmashinangizdan endi SSH orqali bemalol linux mashinaga ulana olasiz. ssh root@{IP_ADDRESS} komandasi orqali terminalda hozirgina yaratilgan linux mashinaga ulaning.


319 marta ko'rildi
Wahid Abduhakimov - uzbekdevs photo

Wahid Abduhakimov

@wahid

Telegram Post

@uzbekdevs
“uzbekdevs.uz” saytida eʼlon qilingan materiallardan nusxa koʻchirish, tarqatish va boshqa shakllarda foydalanish faqat manba ko'rsatilishi orqali amalga oshirilishi mumkin.
© UzbekDevs