This project is a custom-built Continuous Integration (CI) server developed for the DD2480 Software Engineering course at KTH Royal Institute of Technology. The server automates the build process by listening for GitHub webhooks, triggering automated compilation and testing upon every push. It provides immediate feedback to developers by updating commit statuses directly on GitHub.
This is the repository that has our dummy project : https://github.com/IK1203-Group11/demo-for-ci It will be the repository which the user commits to in order to trigger the CI pipeline.
This project was built using a lightweight Java stack, centered around an embedded Jetty server for high performance and Jackson for efficient data handling.
- Runtime: Java JDK 17 (LTS)
- Build Tool: Maven 3.9.12
- Web Server: Jetty Server/Servlet (v${jetty.version}) - Used as the embedded container.
- Servlet API: Java Servlet API (v3.1.0) - Standard interface for web components.
- JSON Processor: Jackson Databind (v2.17.1) - Handles data binding and JSON serialization.
- Testing Framework: JUnit 4 (v4.13.2) - Utilized for unit testing and ensuring code reliability during the Performing state.
- Mocking Framework: Mockito (v5.11.0) - Used for creating mock objects in unit tests to isolate behavior.
.
├── .github/
│ ├── ISSUE_TEMPLATE/ # Templates for standardized bug and feature reports
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── pull_request_template.md # Standard template for all Pull Requests
├── src/
│ ├── main/java/ # Core CI Server implementation
│ │ ├── BuildExecutor.java
│ │ ├── ContinuousIntegrationServer.java
│ │ ├── GitHubPayloadParser.java
│ │ ├── GitHubStatusNotifier.java
│ │ └── GitHubWebhookVerifier.java
│ └── test/java/ # Unit and integration test suites
│ ├── BuildExecutorTest.java
│ ├── ContinuousIntegrationServerTest.java
│ ├── GitHubPayloadParserTest.java
│ ├── GitHubStatusNotifierTest.java
│ └── GitHubWebhookVerifierTest.java
├── builds/ # Local storage for build history and logs
├── .gitignore # Files and directories for Git to ignore
├── LICENSE # Project licensing information
├── pom.xml # Maven Project Object Model and dependencies
└── README.md # Project documentation
To bridge the gap between GitHub and your local Mac (the "Cloud Server" from our plan), you need to install ngrok. This tool creates a secure tunnel to your local port :
brew install ngrok/ngrok/ngrok
Before you can use the tunnel, you must link your local installation to your ngrok account. Create a ngrok account if you dont already have one. Replace <YOUR_TOKEN_HERE> with the token from your ngrok dashboard:
ngrok config add-authtoken <YOUR_TOKEN_HERE>
Finally, start the tunnel to make your local server visible to the internet:
ngrok http 8080
Before starting the CI server, you must set your secrets as environment variables. This allows the server to authenticate with GitHub and verify incoming webhooks.
# 1. Generate a token at: GitHub Settings > Developer Settings > Personal access tokens > Tokens (classic)
# Required scopes: 'repo:status'
export GITHUB_TOKEN=YOUR_TOKEN_HERE
# 2. Create any string (e.g., 'mysecret') to use for webhook verification
export GITHUB_WEBHOOK_SECRET=mysecret
# 3. Set the public ngrok URL (Copy the Forwarding URL from your ngrok terminal (e.g., https://xxxx.ngrok-free.dev).)
export CI_PUBLIC_URL=https://xxxx.ngrok-free.dev/
Compile and launch the Java Jetty server:
mvn compile exec:java-
Persistent logs: The persistence log can be found in the following link : https://flukeless-horacio-unhawked.ngrok-free.dev/builds
-
Webhook Signature Verification: To enhance security, the server implements cryptographic verification of incoming GitHub payloads.
- Implementation: The
GitHubWebhookVerifier.javaclass computes a HMAC SHA-256 hash of the request body using a shared secret (GITHUB_WEBHOOK_SECRET). - Security: It performs a timing-safe comparison using
MessageDigest.isEqualto prevent side-channel attacks. - Verification: Any request missing a valid
X-Hub-Signature-256header or failing the signature check is automatically rejected by the server.
- Implementation: The
This section details how the server fulfills the core requirements for compilation and automated testing as defined in the project assessment.
- Implementation: The server identifies the push event and extracts the branch name and clone URL using
GitHubPayloadParser.java. - Workspace Isolation: To ensure a clean build environment, the server creates an isolated temporary directory for every build using
Files.createTempDirectory. - Execution: The
BuildExecutor.javausesProcessBuilderto executegit cloneandgit checkouton the specified branch. It then executesmvn test, which implicitly performs a compilation and static syntax check of the Java 17 source code. - Trigger: The process is triggered via a GitHub Webhook configured to send a POST request to the
ContinuousIntegrationServer.java. - Verification: The grader can observe the compilation progress in the server console, as all process output is redirected to
System.outvia aBufferedReader.
- Implementation: Once the project is cloned and checked out,
BuildExecutor.javainvokes the commandmvn testwhich both compiles and tests the demo project. - Automated Feedback: The server captures the exit code of the Maven process using
p.waitFor(). An exit code of0indicates success, while any other value (such as a failed test assertion) is interpreted as a failure. We are therefore able to capture error in both the test as well as any compilation failures. - Assessment Strategy: To verify this, the grader can change an assertion oracle in the
assessmentbranch. The CI server will detect the failure via the exit code and print "Tests failed" to the console. - Internal Testing: The parsing logic in
GitHubPayloadParser.javaand the command execution logic inBuildExecutor.javaare themselves unit-tested to ensure the CI server operates reliably.
- Implementation: The server utilizes the
GitHubStatusNotifier.javaclass to communicate build results back to GitHub via the REST API. - Authentication: The system authenticates requests using a Personal Access Token (PAT) stored in the
GITHUB_TOKENenvironment variable. - Feedback Loop: Upon completion of the build and test process, the server sends a POST request to GitHub's statuses endpoint (
/repos/{owner}/{repo}/statuses/{sha}). - State Mapping: The server maps the build outcome to GitHub states: a successful Maven execution results in a
"success"status, while a failed execution or test assertion sends a"failure"status. - Deep Linking (Details Link): If a
CI_PUBLIC_URLis configured, the notification includes atarget_urlthat creates a "Details" button in the GitHub UI. - Log Accessibility: Clicking "Details" redirects the developer to the CI server's log endpoint (
/build/<buildId>), allowing for immediate inspection of build logs.
The notification logic is verified in GitHubStatusNotifierTest.java using a "safe behavior" strategy to ensure reliability without making real network calls:
- Console Capture: The tests use
ByteArrayOutputStreamto capture and verifySystem.outlog messages produced by the notifier. - Safe Execution: Tests verify that the system handles missing environment variables (like
GITHUB_TOKEN) gracefully without throwing exceptions. - Input Validation: Unit tests exercise early-return paths for invalid data, such as improperly formatted repository names or missing commit SHAs.
- Environment Awareness: Uses
Assume.assumeTrueto skip tests that might trigger real HTTP calls if a production token is detected in the local environment.
The project utilizes Javadoc to maintain formal technical documentation of the CI server's internal architecture, utility classes, and API endpoints.
Documentation generation is automated via the maven-javadoc-plugin. To generate the HTML documentation set, execute the following command in your terminal:
mvn javadoc:javadoc- Location: The generated HTML files are stored in the target/site/apidocs/ directory.
- Entry Point: Open index.html in any web browser to explore the class hierarchies, method descriptions, and parameter requirements.
| Team member | GitHub username | Responsibility | Tasks |
|---|---|---|---|
| Dawa | Dawacode & 41w1 |
Initial integration | Set up GitHub Webhooks, ngrok tunnel, initial repo structure and initial Jetty server request handling. Create ReadMe instructions on how to setup the environment |
| Amanda | Amanda-zakir |
Logic and Test | Developed dummy.java and core application logic for the demo and its subsequent tests. |
| Edvin | Edvin-Livak & Livak |
Parser | Implemented JSON parsing to extract branch info and commit SHAs and created the test for the parsing. |
| Yusuf | yusufcanekin |
Automation | Built the ProcessBuilder logic to automate git clone, git checkout, and mvn test execution. |
| Jafar | sund02 & Jafar |
Notification | Implemented GitHub Commit Status notifications (REST API) for success/failure updates. Added webhook signature verification (X-Hub-Signature-256). Created PR and issue templates for traceability. |
|
Our team has officially reached the Collaborating state. Having completed the project and delivered all final requirements, we have fulfilled the team mission defined in the Seeded phase. We worked through the Seeded and Formed state by holding initial meetings where we lay the groundwork for the work distribution as well as group commitment.
Communication within the team is characterized by openness and honesty, allowing us to address technical hurdles and dependencies fluidly. We are no longer just a collection of individuals; we are a synchronized team focused entirely on achieving our mission. Mutual trust is high, and every member is fully committed to the collective goals and the working agreements we’ve established."
All team members have now completed their individual tasks. While some of the points of the next states checklist have been reached such as the team adressing probelms and discussing among themself, not enough time has been spent working on the project in order to have multiple meetings.
This project is licensed under the MIT License — see the LICENSE file.