Skip to content

Instantly share code, notes, and snippets.

@kerimovscreations
Last active June 24, 2022 20:04
Show Gist options
  • Save kerimovscreations/1ebbb05cf2ac78f29757a3f75c6302f0 to your computer and use it in GitHub Desktop.
Save kerimovscreations/1ebbb05cf2ac78f29757a3f75c6302f0 to your computer and use it in GitHub Desktop.

CI/CD Workshop 1

Setup code

  1. Clone the seed project https://github.com/kerimovscreations/cicdworkshop1

  2. Check the classes and tests work correctly

    BookingService, TimeManager, BookingDatabase, Models, Main.main()

  3. Create a public repo in your GitHub account with repo name cicdworkshop1

  4. Remove the old remote git from repo and add your new created remote repo

    git remote remove origin

    git remote add {your new remote}

    git push origin

  5. Checkout a new branch develop

    git checkout -b develop

GitHub Actions

Info: https://docs.github.com/en/actions/using-workflows/about-workflows

File directory: .github/workflows

Note: Fix the intendation of workflow text after copy/pasting.

  1. "Demo" workflow

    1.1. Checkout a new branch feature/demo-workflow from develop

    1.2. Create a yaml file develop-pull-analyze.yml:

    name: Demo pull check
    
    on:
    pull_request:
        branches:
        - develop
    
    jobs:
    Explore-GitHub-Actions:
        runs-on: ubuntu-latest
        steps:
        - run: echo "πŸŽ‰ The job was automatically triggered by a ${{ github.event_name }} event."
        - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
        - run: echo "πŸ”Ž The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
        - name: Check out repository code
            uses: actions/checkout@v3
        - run: echo "πŸ’‘ The ${{ github.repository }} repository has been cloned to the runner."
        - run: echo "πŸ–₯️ The workflow is now ready to test your code on the runner."
        - name: List files in the repository
            run: |
            ls ${{ github.workspace }}
        - run: echo "🍏 This job's status is ${{ job.status }}."
    

    1.3. Push remote and create PR from feature/demo-workflow to develop

    1.4. Check pipeline completed successfully and check logs

  2. "Build and test" workflow (CI)

    2.1. Checkout new branch feature/ci-step from develop

    2.2. Create build-and-test.yml

    name: Build and test
    
    on:
      pull_request:
        branches:
        - develop
    
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
        - name: Check out repository code
            uses: actions/checkout@v3
    
        - name: Setup Java 17
            uses: actions/setup-java@v3
            with:
            distribution: temurin
            java-version: 17
    
        - name: Setup Gradle
            uses: gradle/gradle-build-action@v2
    
        - name: Build
            run: ./gradlew build
    
      test:
        runs-on: ubuntu-latest
        steps:
        - name: Check out repository code
            uses: actions/checkout@v3
    
        - name: Setup Java 17
            uses: actions/setup-java@v3
            with:
            distribution: temurin
            java-version: 17
    
        - name: Setup Gradle
            uses: gradle/gradle-build-action@v2
    
        - name: Test
            run: ./gradlew test
    

    2.3. Push remote and create a new PR from feature/ci-step to develop

    2.4. Check the pipeline workflow completed successfully

Code coverage

Continue to work on feature/ci-step branch

  1. Kover setup (more at https://github.com/Kotlin/kotlinx-kover)

    1.1. Add build.gradle.kts:

    plugins {
        ...
        id("org.jetbrains.kotlinx.kover") version("0.5.1") apply(true)
    }
    
    tasks.test {
        ...
        extensions.configure(kotlinx.kover.api.KoverTaskExtension::class) {
            isDisabled = false
            binaryReportFile.set(file("$buildDir/custom/result.bin"))
            includes = listOf("*")
            excludes = listOf()
        }
    }
    
    kover {
        isDisabled = false
        coverageEngine.set(kotlinx.kover.api.CoverageEngine.JACOCO)
        jacocoEngineVersion.set("0.8.7")
    }
    
    tasks.withType<KotlinCompile> {
        kotlinOptions.jvmTarget = "11"
    }
    
    val koverOutputDir = "${rootProject.buildDir}/kover-reports"
    
    tasks.koverHtmlReport {
        isEnabled = true
        htmlReportDir.set(layout.buildDirectory.dir("${koverOutputDir}/html-result"))
    
        includes = listOf("*")
        excludes = listOf()
    }
    
    tasks.koverXmlReport {
        isEnabled = true
        xmlReportFile.set(layout.buildDirectory.file("${koverOutputDir}/result.xml"))
    
        includes = listOf("*")
        excludes = listOf()
    }
    

    1.2. Run following commands in the terminal:

    ./gradlew test

    ./gradlew koverHtmlReport

    ./gradlew koverXmlReport

  2. Check the generated HTML and XML reports in build/kover-reports directory

Analyze

Continue to work on feature/ci-step branch

  1. Sonar project setup

    1.1. Open https://sonarcloud.io

    1.2. Register your personal GitHub account and repo newly created

    1.3. Copy SONAR_TOKEN secret (Sonar project web page -> Administration -> Analysis method -> GitHub Actions -> Follow the tutorial)

    1.3.1 setup with the same name in GitHub repo secrets (Repo web page -> Settings -> Secrets -> Action)

    1.4. Disable Automatic Analysis (Sonar project web page -> Administration -> Analysis method -> SonarCloud Automatic Analysis -> toggle off)

    1.5. Extract Project Key and Organization Key (Sonar project web page -> Information)

  2. Sonar setup

    2.1. Add build.gradle.kts:

    plugins {
        ...
        id("org.sonarqube") version("3.3") apply(true)
    }
    
    sonarqube {
        properties {
            property("sonar.host.url", "https://sonarcloud.io")
            property("sonar.organization", "{YOUR_ORGANIZATION_KEY}")
            property("sonar.projectKey", "{YOUR_PROJECT_KEY}")
            property(
                "sonar.coverage.jacoco.xmlReportPaths",
                "${koverOutputDir}/result.xml"
            )
        }
    }
    

    2.2. Add build-and-test.yml:

    test:
      runs-on: ubuntu-latest
      steps:
      ...
    
      - name: Generate coverage reports
        run: ./gradlew koverXmlReport
       
      - name: Analyze
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
        run: ./gradlew sonarqube
    
  3. Push remote and check Sonar analyze completed successfully

    3.1. You will see

    Bug A 0 Bugs
    Vulnerability A 0 Vulnerabilities
    Security Hotspot A 0 Security Hotspots
    Code Smell A 0 Code Smells
    
    No Coverage Information
    No Duplication Information
    
  4. Add new code and tests

    4.1. Add to file TimeManager.kt:

    interface TimeManager {
        ...
        fun endOfWorkDay(date: Date): Date
    }
    
    class TimeManagerImpl : TimeManager {
        ...
        override fun endOfWorkDay(date: Date): Date {
            val dateFormat = SimpleDateFormat("dd.MM.yyyy")
            val strDate: String = dateFormat.format(date)
            val endDateTime = "${strDate}/18:00"
            return getDate(endDateTime, format = "dd.MM.yyyy/HH:mm")
        }
    }
    

    4.2. Add to file BookingService.kt:

    interface BookingService {
        ...
        fun suggestNextPeriod(period: Period): Period
    }
    
    class BookingServiceImpl() : BookingService {
        ...
        override fun suggestNextPeriod(period: Period): Period {
            var newStartTime = period.startTime.time
            val endLimit = timeManager.endOfWorkDay(period.startTime).time - period.unit.toMillis(period.duration)
    
            while (newStartTime < endLimit) {
                newStartTime += TimeUnit.MINUTES.toMillis(5) // skip each 5 minutes
    
                val newPeriod =  Period(
                    startTime = Date(newStartTime),
                    duration = period.duration,
                    unit = period.unit
                )
    
                if (isAvailable(newPeriod)) {
                    return newPeriod
                }
            }
    
            throw NotAvailableTimeSlotException
        }
    }
    

    4.3. Add to file BookingServiceImplTest.kt:

    @Test
    fun testSuggestNextPeriod() {
        val title = "Test meeting 1"
        val invitees = listOf<Attendee>(Attendee.Required(fullName = "John Doe", email = "john@company.com"))
        val period = Period(
            startTime = timeManager.getDate("10.01.2022/09:00", format = "dd.MM.yyyy/HH:mm"),
            duration = 45,
            unit = TimeUnit.MINUTES
        )
    
        val meeting = service.book(
            title = title,
            invitees = invitees,
            period = period
        )
    
        assertNotNull(meeting.id)
        assertEquals(meeting.title, title)
        assertEquals(meeting.invitees, invitees)
        assertEquals(meeting.period, period)
    
        val title2 = "Test meeting 2"
        val invitees2 = listOf<Attendee>(Attendee.Required(fullName = "Sarah Doe", email = "sarah@company.com"))
        var period2 = Period(
            startTime = timeManager.getDate("10.01.2022/09:30", format = "dd.MM.yyyy/HH:mm"),
            duration = 25,
            unit = TimeUnit.MINUTES
        ) // conflicting time slot
    
        period2 = service.suggestNextPeriod(period2)
    
        val meeting2 = service.book(
            title = title2,
            invitees = invitees2,
            period = period2
        )
    
        assertNotNull(meeting2.id)
        assertEquals(meeting2.title, title2)
        assertEquals(meeting2.invitees, invitees2)
        assertEquals(meeting2.period, period2)
    }
    
  5. Push latest changes and check code coverage and duplication metrics

    5.1. You should get:

    89.5% Coverage
    0.0% Duplication
    
  6. Add new test to increase coverage to 100%

    Line Coverage - The percent of lines executed by this test run.

    Branch coverage - The percent of branches executed by this test run.

Protect branch

  1. Add protection rules (Repo web page -> Settings -> Branches -> Add Rule)

    1.1. Config:

    Branch name: develop

    βœ… Require a pull request before merging

    βœ… Require approvals: 1

    βœ… Dismiss stale pull request approvals when new commits are pushed

    βœ… Require status checks to pass before merging

    βœ… Require conversation resolution before merging

  2. Check the last open PR and you will observe the review requirement in the Status Check section at bottom

  3. Merge the PR feature/ci-setup with administrator access

Deployment (CD)

  1. Checkout develop branch, pull it

    git checkout develop
    git pull
    
  2. Checkout a new branch feature/cd-setup from develop

  3. Create a new workflow file deploy-internal.yml:

name: Deploy internal artifact

on:
  push:
    branches:
      - develop

jobs:
  deploy-internal:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code
        uses: actions/checkout@v3

      - name: Setup Java 17
        uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 17

      - name: Setup Gradle
        uses: gradle/gradle-build-action@v2

      - name: Assemble output jar
        run: ./gradlew assemble

      - name: Upload output jar
        uses: actions/upload-artifact@v3
        with:
          name: cicdworkshop-output
          path: build/libs/
  1. Push remote and create a PR from feature/cd-setup to develop

  2. Merge the PR with administrator access and check the workflow star to run in Actions tab

  3. After completed workflow task, artifact should be visible in Overview of workflow

πŸ₯³ Congratulations!

You have managed to implement

  • initial CI/CD pipeline using GitHub actions
  • code coverage
  • code quality analizer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment