Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
FROM openjdk:11
WORKDIR /app

# get chromedriver& pipenv & unzip then delete cache
RUN apt-get update && apt-get install -y \
chromium chromium-driver \
python3 python3-pip pipenv \
wget unzip xvfb fonts-liberation && \
rm -rf /var/lib/apt/lists/*

# Start Xvfb and set DISPLAY
ENV DISPLAY=:99
# Set up Python environment
RUN pipenv install --python 3 \
&& pipenv install pytest requests behave behave2cucumber selenium PyHamcrest



# gradle copy and set permission
COPY gradlew .
COPY gradle ./gradle
RUN chmod +x ./gradlew

COPY build.gradle .

COPY src ./src

# build project
RUN ./gradlew clean build

# install tomcat
RUN wget https://downloads.apache.org/tomcat/tomcat-9/v9.0.100/bin/apache-tomcat-9.0.100.tar.gz \
&& tar -xzf apache-tomcat-9.0.100.tar.gz \
&& mv apache-tomcat-9.0.100 tomcat \
&& rm apache-tomcat-9.0.100.tar.gz



RUN mkdir -p tomcat/webapps/

# copy war file to tomcat webapps
RUN cp build/libs/app.war tomcat/webapps/demo.war



EXPOSE 8080

# run tomcat server
CMD Xvfb :99 -screen 0 1920x1080x24 & \
sh -c "./tomcat/bin/catalina.sh run"
48 changes: 48 additions & 0 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
pipeline {
agent none
options {
skipStagesAfterUnstable()
}
stages {
stage('Build') {
agent {
docker {
image 'python:2-alpine'
}
}
steps {
sh 'python -m py_compile sources/add2vals.py sources/calc.py'
}
}
stage('Test') {
agent {
docker {
image 'qnib/pytest'
}
}
steps {
sh 'py.test --verbose --junit-xml test-reports/results.xml sources/test_calc.py'
}
post {
always {
junit 'test-reports/results.xml'
}
}
}
stage('Deliver') { //1
agent {
docker {
image 'cdrx/pyinstaller-linux:python2' //2
}
}
steps {
sh '/root/.pyenv/shims/pyinstaller --onefile sources/add2vals.py' //3
}
post {
success {
archiveArtifacts 'dist/add2vals' //4
}
}
}
}
}
58 changes: 58 additions & 0 deletions Proejct_Instruction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# ENSF 400 - Winter 2025 - Course Project

## Project Overview

In this project, you will work based on a software project by incorporating/extending a complete CI/CD (Continuous Integration/Continuous Deployment) pipeline. This is based on an open-source sample application: https://github.com/7ep/demo

This project can also be any application that requires the project of build, test, and deployment.
You will leverage GitHub for source control, Docker for containerizing your application, and a CI/CD tool (Jenkins) to automate the build, testing, and verification process. The goal is to validate every code change automatically through container builds, unit tests, code quality checks, and end-to-end functional tests.


## Project Requirements

By the end of this project, your group must deliver the following:

1. Manage your project on GitHub and follow proper Git workflows (branching, pull requests, code reviews). Document the process of how you use Git workflows to collaborate with your team members.

1. Containerize your application for builds and deployments. Upload and download your container images to a public or private image repository (e.g., Docker Hub or GitHub Container Registry). Ensure a container image is built with unique build tag(s) matching the triggering commit from any branch.

1. Set up an automated CI/CD with Jenkins in a Codespace environment. Configure the pipeline to trigger upon pull requests merging changes into the main branch.

1. Document the CI/CD process and provide clear instructions on replicating your environment. Submit a video demo at the end of the project.

### Existing Pipelines
You will also demonstrate the delivery of the following process and artifacts that come with the project.

1. Run static analysis quality-gating using SonarQube
1. Performance testing with Jmeter
1. Security analysis with OWASP's "DependencyCheck"
1. Build Javadocs


## Evaluation Criteria

Your project will be assessed on the following criteria:

### GitHub Repository & Git Workflow (15%)
1. Project on GitHub in a public repository with all team members participating in the development and maintenance of the project (5%).
1. Demonstrate the process practicing Git workflows (branching, pull requests, code reviews) (10%).

### Containerization (20%)
1. Dockerfile to containerize the project (5%).
1. Use of container image repository to upload and download images (5%).
1. Effective tagging mechanism for each building matching the commits/branches/pull requests (10%).

### CI/CD Pipeline Automation (40%)
1. Jenkins integration with GitHub in Codespace (10%).
1. Triggering automated checks upon pull request to the main branch (10%).
1. Deployment process to automatically deploy the application in the Codespace environment upon a build (10%).
1. Be able to run items 5-8 in **Existing Pipelines** (10%).

### Testing & Code Quality (10%)
1. Generate test coverage reports upon each automated build (5%).
1. Generate code quality report using SonarQube reports upon each automated build (5%).

### Documentation & Demo (15%)
1. Clarity and completeness of README and other documentation. The documentation must demonstrate the team’s collaboration process (5%).
1. Demonstration video with a length not exceeding 10 minutes, showing a clear understanding of the pipeline and its benefits. The documentation must demonstrate the team’s collaboration process (10%).

5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ plugins {

// gretty is a gradle plugin to make it easy to run a server and hotswap code at runtime.
// https://plugins.gradle.org/plugin/org.gretty
id 'org.gretty' version '3.0.4'

id 'org.gretty' version '3.1.5'

// provides access to a database versioning tool.
id "org.flywaydb.flyway" version "6.0.8"
Expand Down Expand Up @@ -219,7 +220,7 @@ cucumberReports {
// merge together all the cucumber reports with a suffix of "json"
reports = files(fileTree(dir: "build/bdd", include: '*.json'))
testTasksFinalizedByReport = false
projectNameOverride = "$projectname"
projectNameOverride = 'demo-app'
}

flyway {
Expand Down
Binary file modified docs/BDD_video.mp4
Binary file not shown.
53 changes: 35 additions & 18 deletions src/ui_tests/python/basic_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.chrome.options import Options
import requests
from selenium.webdriver.common.proxy import Proxy, ProxyType
from hamcrest import *
Expand All @@ -23,7 +24,23 @@
class TestBasic():

def setup_class(self):
self.driver = webdriver.Chrome()
chrome_options = Options()
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--headless")
chrome_options.add_argument("--remote-debugging-port=9222")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--disable-software-rasterizer")
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--disable-background-networking")
chrome_options.add_argument("--disable-background-timer-throttling")
chrome_options.add_argument("--disable-backgrounding-occluded-windows")
chrome_options.add_argument("--disable-breakpad")
chrome_options.add_argument("--disable-component-extensions-with-background-pages")
chrome_options.add_argument("--disable-features=TranslateUI,BlinkGenPropertyTrees")
chrome_options.add_argument("--disable-ipc-flooding-protection")
chrome_options.add_argument("--disable-renderer-backgrounding")
self.driver = webdriver.Chrome(options=chrome_options)
self.vars = {}

def teardown_class(self):
Expand Down Expand Up @@ -86,15 +103,15 @@ def __init__(self, driver):
self.driver = driver

def enter_username(self, text):
login_username_field = self.driver.find_element_by_id("login_username")
login_username_field = self.driver.find_element(By.ID,"login_username")
login_username_field.send_keys(text)

def enter_password(self, text):
login_password_field = self.driver.find_element_by_id("login_password")
login_password_field = self.driver.find_element(By.ID,"login_password")
login_password_field.send_keys(text)

def enter(self):
login_button = self.driver.find_element_by_id("login_submit")
login_button = self.driver.find_element(By.ID,"login_submit")
login_button.click()


Expand All @@ -104,15 +121,15 @@ def __init__(self, driver):
self.driver = driver

def enter_username(self, text):
register_username_field = self.driver.find_element_by_id("register_username")
register_username_field = self.driver.find_element(By.ID,"register_username")
register_username_field.send_keys(text)

def enter_password(self, text):
register_password_field = self.driver.find_element_by_id("register_password")
register_password_field = self.driver.find_element(By.ID,"register_password")
register_password_field.send_keys(text)

def enter(self):
register_button = self.driver.find_element_by_id("register_submit")
register_button = self.driver.find_element(By.ID,"register_submit")
register_button.click()


Expand All @@ -122,11 +139,11 @@ def __init__(self, driver):
self.driver = driver

def register_book(self, text):
register_book_field = self.driver.find_element_by_id("register_book")
register_book_field = self.driver.find_element(By.ID,"register_book")
register_book_field.send_keys(text)

def enter(self):
register_button = self.driver.find_element_by_id("register_book_submit")
register_button = self.driver.find_element(By.ID,"register_book_submit")
register_button.click()


Expand All @@ -136,11 +153,11 @@ def __init__(self, driver):
self.driver = driver

def register_borrower(self, text):
register_borrower_field = self.driver.find_element_by_id("register_borrower")
register_borrower_field = self.driver.find_element(By.ID,"register_borrower")
register_borrower_field.send_keys(text)

def enter(self):
register_button = self.driver.find_element_by_id("register_borrower_submit")
register_button = self.driver.find_element(By.ID,"register_borrower_submit")
register_button.click()


Expand All @@ -150,15 +167,15 @@ def __init__(self, driver):
self.driver = driver

def enter_book(self, text):
book_field = self.driver.find_element_by_id("lend_book")
book_field = self.driver.find_element(By.ID,"lend_book")
book_field.send_keys(text)

def enter_borrower(self, text):
borrower_field = self.driver.find_element_by_id("lend_borrower")
borrower_field = self.driver.find_element(By.ID,"lend_borrower")
borrower_field.send_keys(text)

def enter(self):
lend_button = self.driver.find_element_by_id("lend_book_submit")
lend_button = self.driver.find_element(By.ID,"lend_book_submit")
lend_button.click()


Expand All @@ -168,15 +185,15 @@ def __init__(self, driver):
self.driver = driver

def enter_addend_a(self, text):
addend_a = self.driver.find_element_by_id("addend_a")
addend_a = self.driver.find_element(By.ID,"addend_a")
addend_a.send_keys(text)

def enter_addend_b(self, text):
addend_b = self.driver.find_element_by_id("addend_b")
addend_b = self.driver.find_element(By.ID,"addend_b")
addend_b.send_keys(text)

def enter(self):
lend_button = self.driver.find_element_by_id("math_submit")
lend_button = self.driver.find_element(By.ID,"math_submit")
lend_button.click()

# all the important capabilities for the Result page
Expand All @@ -185,7 +202,7 @@ def __init__(self, driver):
self.driver = driver

def get_result_text(self):
return self.driver.find_element_by_id("result").text
return self.driver.find_element(By.ID,"result").text

class LibraryPageObjectModel:

Expand Down