This article covers the implementation of a simple example of CICD for a .Net Core project on GitLab with windows runner. In practice, we want to implement a service that automatically does what we do manually to publish the project. What do we do to publish a .NET project on IIS?

  1. Build of the project
  2. Publish from the project
  3. Stop WebSite on IIS
  4. Stop the WebSite Application Pool
  5. Transfer published files to the server
  6. Start the relevant Application Pool
  7. Start the relevant WebSite

We can do the written work using powershell as follows:

dotnet build
dotnet publish -c Release  
Stop-WebSite -Name $application_pool_name
Stop-WebAppPool -Name $application_pool_name
Copy-Item $publish_path -Destination $iis_worker_path -Force
Start-WebAppPool -Name $application_pool_name
Start-WebSite -Name $application_pool_name

The $application_pool_name  variable is the name of the application on IIS that must be stopped. The $publish_path variable is the path of the published files to be copied, and the $iis_worker_path variable is the path of the program running on IIS to which the files should be copied. We need to do something to execute these commands automatically.

GitLab contains a Pipeline consisting of one or more Jobs. Pipeline using the gitlab-ci.yml file. Made to be in the project root in the relevant Repository. In each of the jobs related to Pipeline, we can enter our commands and we can execute all the above commands using Pipeline. 

To use Pipeline, we need to use GitLab-Runner. All Pipeline work is done by GitLab-Runner. In this example, we are using a Windows server, so you must install the Windows Runner. To install Runner, you need to download the appropriate file for your operating system from this link: https://docs.gitlab.com/runner/install/windows.html

Then you need to create a folder called GitLab-Runner in root driver C. Then copy the downloaded file into this folder and rename the file to gitlab-runner.exe.

Note that the server where you install GitLab-Runner must have git installed and have access to your project to be able to clone the project.

Next we need to introduce Runner to GitLab so we can execute pipeline commands. To do this, we need to get information about the Project  Runner in GitLab. A token and an address in the GitLab site:

Settings > CICD > Runners > Specific runners

After receiving the token and the relevant address, we must send this information to Runner. To do this, run CMD in C:\Gitlab-Runner via Admin and enter the following commands:

  1. gitlab-runner.exe register
  2. https://gitlab.com/
  3. Your token
  4. DotNetRunner
  5. dotnet
  6. shell

In the first part, you enter the registration command.

In the second part, you enter the address you received from GitLab.

Then in the third step, you enter the token received from GitLab.

In the fourth step, you enter the name of the desired Runner, where DotNetRunner is entered.

In step five, enter the tags you want, for this Runner, which does the work of .NET programs, we have written dotnet. (These names are optional)

Finally, in section six, you need to introduce an executer to specify the environment in which your commands will run, and since our Runner is on Windows and we use the powershell commands, we have entered the shell option.

Next we need to run the following commands to install and run Runner using CMD

gitlab-runner.exe install
gitlab-runner.exe start

 Then you need to change the pwsh value to powershell in the config.toml file in the Gitlab-Runner folder for the shell parameter:

[[runners]]
  name = "DotNetRunner"
  url = https://gitlab.com/
  token = "YourToken"
  executor = "shell"
  shell = "powershell"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]

Now the desired Runner has been created and you can see it on the page related to Runners. By doing this, all the commands written in the .gitlab-ci.yml file will be executed using this Runner and through powershell.

Now if a change is made to the Repository, it will automatically publish to IIS.

The final created yml file with the new changes added is as follows:

variables:
  publish_path: '.\DotNetDocs\publish\$CI_PIPELINE_ID\'
  published_data_path: '.\DotNetDocs\publish\$CI_PIPELINE_ID\*.*'
  application_pool_name: 'FarhadZamani'
  iis_worker_path: 'C:\Publish\FarhadZamani'
stages:
  - build
  - tests
  - deploy
  
build:
  stage: build
  script:
    - dotnet restore
    - dotnet build --no-restore
    - dotnet publish .\DotNetDocs\DotNetDocs.csproj -c Release -o $publish_path
  artifacts:
    paths:
      - $publish_path
    expire_in: '1 hrs'
  tags:
    - dotnet  
  only:
    - develop
    - master

unit-test:
  stage: tests
  script:
    - dotnet test --no-build --verbosity normal
  tags:
    - dotnet
  only:
    - master
    - develop
  needs: [build]

production:
  stage: deploy
  script:
    - |
      if((Get-WebSiteState -Name $application_pool_name).Value -ne 'Stopped'){
      Write-Output ('Stopping WebSite: {0}' -f $application_pool_name)
      Stop-WebSite -Name $application_pool_name
      }
    - |
      if((Get-WebAppPoolState -Name $application_pool_name).Value -ne 'Stopped'){
      Write-Output ('Stopping Application Pool: {0}' -f $application_pool_name)
      Stop-WebAppPool -Name $application_pool_name
      }
    - "Copy-Item $published_data_path -Destination $iis_worker_path -Force"
    - "Start-Sleep -s 5"
    - |
      if((Get-WebAppPoolState -Name $application_pool_name).Value -ne 'Started'){
      Write-Output ('Starting Application Pool: {0}' -f $application_pool_name)
      Start-WebAppPool -Name $application_pool_name
      }
    - |
      if((Get-WebSiteState -Name $application_pool_name).Value -ne 'Started'){
      Write-Output ('Starting WebSite: {0}' -f $application_pool_name)
      Start-WebSite -Name $application_pool_name
      }
  only:
    - master
  tags:
    - dotnet
  needs: [unit-test, build]
  when: manual
  dependencies:
    - build

The submitted yml file includes three stages.

1 - build: In this step, first the dotnet restore command is executed to restore packages. Then the dotnet build command is executed without restoring. Then the program publisher command is executed and its path is specified in the $publish_path variable .

This operation is executed on master and develop brunges, that is, this build is executed every time an operation is performed on each of the brunch.

only:
  - develop
  - master

After the publishing operation is completed, the published files are uploaded as artifact. 

You can also specify a time for Artifact to expire, which is set to 1 hour here:

artifacts:
  paths:
    - $publish_path
  expire_in: '1 hrs'

In the above code, all published files are created as an artifact.

If the created artifact is the last artifact, it will not expire until a new artifact is created, even if it expired. This will always make an artifact available.

2 - tests: In this step, we run the written tests with the dotnet test command. which we have specified with the following command: the job related to unit-test is in the test stage 

unit-test:
  stage: tests

Prerequisite for this step is the build step, which uses the command

needs: [build]

Has been specified.

3- Deploy: In this stage for publishing the project on the production server, is done manually with one click. The script written in the production section does the following:

  • First it checks that the WebSite is not Stopped and when it is not Stopped it first stops it and goes to the next step.
  • It then checks the AppPool for Stop, which it stops if it is not Stopped.
  • Downloads the files published in the build step through the artifacts and copies them to the path where the project is on IIS.
  • Starts AppPool if it is not Started.
  • Finally, if WebSite is not Started, it will start it.

Because we are using an artifact created in the build step, we need to enter the dependency associated with it:

dependencies:
  - build

With this command we can access the artifact created in the build stage.

Also, the prerequisites for this step are the three previous jobs that must be executed correctly and without errors, which we have specified with the following command:

needs: [unit-test,build]

Because of its dependency on the build phase, we have to write the build phase as needed, otherwise we only need unit-test.

This job only runs when a change is made to the mastermind specified by the following command:

only:
  - master

In the production related job, we specified with the following command that the job must be executed manually:

when: manual

:)

Comments