https://github.com/willie-conway/devops-capstone-project
π DevOps Capstone Project for CI/CD, Kubernetes, Docker & OpenShift π π οΈ Build, containerize, deploy, and automate microservices in the cloud! π§ͺ
https://github.com/willie-conway/devops-capstone-project
automation ci-cd devops docker flask kubernetes microservices openshift tekton
Last synced: about 2 months ago
JSON representation
π DevOps Capstone Project for CI/CD, Kubernetes, Docker & OpenShift π π οΈ Build, containerize, deploy, and automate microservices in the cloud! π§ͺ
- Host: GitHub
- URL: https://github.com/willie-conway/devops-capstone-project
- Owner: Willie-Conway
- License: apache-2.0
- Created: 2025-03-16T21:34:38.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-05-25T15:12:37.000Z (5 months ago)
- Last Synced: 2025-06-12T08:44:58.328Z (4 months ago)
- Topics: automation, ci-cd, devops, docker, flask, kubernetes, microservices, openshift, tekton
- Language: Python
- Homepage:
- Size: 500 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# DevOps Capstone Project

# π Lab: Agile Planning Using GitHub
β³ **Estimated time needed:** 60 minutes
## π Welcome
Welcome to the hands-on **Agile Planning Using GitHub** lab! In this lab, you'll build a **Sprint Plan (Sprint 0)** for a **Customer Accounts Microservice** project.**Project Goal:** Develop an account microservice with REST APIs to:
- β¨ Create
- π Read
- π Update
- ποΈ Delete
- π List customer accounts> **Note:** This lab uses **GitHub only** (no lab environment).
## π― Objectives
By the end of this lab, you will:
- βοΈ Create a GitHub repository
- βοΈ Set up a GitHub Kanban board
- βοΈ Develop a user story template
- βοΈ Add and prioritize user stories
- βοΈ Refine a product backlog
- βοΈ Build a sprint plan## πΈ Screenshot Requirements
You'll need to save these screenshots:| File Name | Description |
|-----------------------------------|--------------------------------------|
| `planning-repository-done.png` | GitHub repo created |
| `planning-storytemplate-done.png` | User story template |
| `planning-userstories-done.png` | 7 user stories added |
| `planning-productbacklog-done.png`| Backlog triaged |
| `planning-labels-done.png` | Stories labeled & refined |
| `planning-kanban-done.png` | Final sprint backlog |**Screenshot Shortcuts:**
- **Mac:** `Shift + Cmd + 3` (full screen) or `Shift + Cmd + 4` (area)
- **Windows:** `Alt + Print Screen` β Paste into image editor## π οΈ Exercises
### Exercise 1: Create GitHub Repository
1. Use [this template](https://github.com/ibm-developer-skills-network/aolwx-devops-capstone-template) (click "Use this template")
2. Name: `devops-capstone-project` (set to **Public**)
3. **Evidence:** Save repo URL + `planning-repository-done.png`---
### Exercise 2: Create Kanban Board
Set up board with 7 columns:
1. New issues
2. Icebox βοΈ
3. Product Backlog π
4. Sprint Backlog π
5. In progress π
6. Review/QA βοΈ
7. Done β---
### Exercise 3: User Story Template
Create `.github/ISSUE_TEMPLATE/user-story.md`:```markdown
**As a** [role]
**I need** [function]
**So that** [benefit]### Details and Assumptions
* [document what you know]### Acceptance Criteria
```gherkin
Given [context]
When [action]
Then [outcome]
```**Evidence:** `planning-storytemplate-done.png`
---
### Exercise 4: Product Backlog
Create 7 user stories:
1. Set up dev environment
2. Read an account
3. Update an account
4. Delete an account
5. List accounts
6. Containerize with Docker π³
7. Deploy to Kubernetes βΈοΈ**Evidence:** `planning-userstories-done.png`
---
### Exercise 5: Triage Issues
- Move 5 stories to **Product Backlog**
- Move 2 stories (Docker/K8s) to **Icebox** βοΈ**Evidence:** `planning-productbacklog-done.png`
---
### Exercise 6: Refine Backlog
1. Add details/acceptance criteria
2. Create `technical debt` label (yellow)
3. Apply labels:
- `enhancement` (customer features)
- `technical debt` (e.g., setup)
4. Rank by priority**Evidence:** `planning-labels-done.png`
---
### Exercise 7: Sprint Plan
1. Create 3 sprints (1 week each)
2. Assign 5 stories to **Sprint 1**
3. Add story points (3=S, 5=M, 8=L, 13=XL)
4. Move to Sprint Backlog**Evidence:** `planning-kanban-done.png`
# Lab: π¦ Account Microservice - RESTful API with TDD
## π Lab Overview
**Develop a RESTful Service Using Test-Driven Development**
*Estimated time: 90 minutes*### π― Objectives
- Follow Agile plan from Kanban board
- Implement REST API endpoints using TDD
- Achieve 95%+ test coverage
- Conduct sprint review## β οΈ Important Notes
- **Ephemeral environment**: Cloud IDE may reset - push changes to GitHub!
- **Security**: Never store secrets in code
- **GitHub PAT**: Required for pushing changes (repo+write permissions)## π οΈ Setup Instructions
### 1οΈβ£ Initialize Environment
```bash
export GITHUB_ACCOUNT=your_username
git clone https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git
cd devops-capstone-project
bash ./bin/setup.sh
exit # Must reopen terminal!
```### 2οΈβ£ Validate Setup
```bash
which python # Should show venv path
python --version # Verify Python 3.9+
```### 3οΈβ£ Install Dependencies
```bash
pip install -r requirements.txt
```
### π§ͺ TDD Workflow
#### π΄ Write Failing Test
```python
# tests/test_routes.py
def test_get_account(client):
response = client.get("/accounts/1")
assert response.status_code == 200
```
### π’ Implement Minimal Code
```python
# service/routes.py
@app.route("/accounts/", methods=["GET"])
def get_account(id):
account = Account.find(id)
return jsonify(account.serialize()), 200
```### π Run Tests
```bash
nosetests --with-coverage --cover-package=service
```### π Coverage Verification
```bash
coverage report -m # CLI report
coverage html # HTML report
open htmlcov/index.html
```### π Required Endpoints
| Method | Endpoint | Description |
|--------|------------------|--------------------|
| GET | /accounts/ | Get single account |
| PUT | /accounts/ | Update account |
| DELETE | /accounts/ | Delete account |
| GET | /accounts | List all accounts |### πΈ Evidence Checklist
1. **`tests-passing.png`** - All tests green
2. **`coverage-report.png`** - Coverage β₯95%
3. **`api-working.png`** - curl/Postman demo
### π Project Structure
```bash
.
βββ service/
β βββ models.py # DB models
β βββ routes.py # API endpoints
βββ tests/
β βββ test_routes.py # Unit tests
βββ requirements.txt
βββ bin/setup.sh # Setup script
```### π‘ Pro Tips
- Use mocks in tests:
```python
from unittest.mock import patch
```- Test edge cases:
```python
def test_get_nonexistent_account(client):
response = client.get("/accounts/999")
assert response.status_code == 404
```- Commit often to avoid losing work
# π REST API Guidelines ReviewThis document summarizes the REST API design for your lab, including testable behaviors, HTTP status codes, and implementation hints for endpoints. Use it as a cheat sheet during development and test writing! β
---
## π RESTful API Endpoints
| π οΈ Action | π’ Method | π URL Endpoint | π¦ Return Code | π§Ύ Body |
|----------|------------|------------------------|-------------------|----------------------------|
| List | `GET` | `/accounts` | `200 OK` | Array of accounts `[{}]` |
| Create | `POST` | `/accounts` | `201 CREATED` | A new account `{}` |
| Read | `GET` | `/accounts/{id}` | `200 OK` or `404` | Account as JSON `{}` |
| Update | `PUT` | `/accounts/{id}` | `200 OK` or `404` | Updated account as JSON |
| Delete | `DELETE` | `/accounts/{id}` | `204 NO CONTENT` | Empty string `""` |π§ **Note:** These standard behaviors enable consistent testing and API usage. If no accounts exist, always return `[]` with `200 OK` instead of a `404`.
---
## π’ HTTP Status Codes
| π Code | π§Ύ Status | π Description |
|--------|------------------------|--------------------------------------------|
| 200 | `HTTP_200_OK` | β Request successful |
| 201 | `HTTP_201_CREATED` | π Resource successfully created |
| 204 | `HTTP_204_NO_CONTENT` | ποΈ Resource deleted / no body in response |
| 404 | `HTTP_404_NOT_FOUND` | π« Resource not found |
| 405 | `HTTP_405_METHOD_NOT_ALLOWED` | β Method not allowed on endpoint |
| 409 | `HTTP_409_CONFLICT` | β οΈ Conflict with the current state |These codes are available via:
```python
from service.common import status
````---
## π§ͺ Sample Unit Tests
### π Test: Read Account
```python
def test_get_account(self):
response = self.client.get("/accounts/1")
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.get_json()
self.assertEqual(data["id"], 1)
```### π Test: Update Account
```python
def test_update_account(self):
updated = {"name": "New Name", "address": "New Address"}
response = self.client.put("/accounts/1", json=updated)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.get_json()
self.assertEqual(data["name"], "New Name")
```### β Test: Delete Account
```python
def test_delete_account(self):
response = self.client.delete("/accounts/1")
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
```### π Test: List All Accounts
```python
def test_list_accounts(self):
response = self.client.get("/accounts")
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = response.get_json()
self.assertIsInstance(data, list)
```### π Test: Create Account
```python
def test_create_account(self):
new_account = {"name": "Jane Doe", "address": "123 Elm St"}
response = self.client.post("/accounts", json=new_account)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
data = response.get_json()
self.assertEqual(data["name"], "Jane Doe")
```---
## π‘ TDD Workflow Tips
* β **Write the test first** (Red)
* π§βπ» **Write the code to pass the test** (Green)
* β»οΈ **Refactor if needed**
* π₯ Use the constants like `status.HTTP_200_OK`
* π Use `.get()`, `.post()`, `.put()`, `.delete()` on `self.client` for endpoint tests---
## βοΈ `setup.cfg` Configuration for `nosetests`
```ini
[nosetests]
verbosity=2
with-spec=1
spec-color=1
with-coverage=1
cover-erase=1
cover-package=service
```π Now, running `nosetests` will include color, spec-style output, and coverage!
---
## πΈ Submission Checklist
* β Screenshot of `setup.cfg`: `rest-setupcfg-done.jpg` / `.png`
* β Screenshot of Kanban board (Story in Done): `rest-techdebt-done.jpg` / `.png`---
# π Lab: Develop a RESTful Service Using Test-Driven Development (TDD)
Welcome to the **DevOps Capstone Lab** focused on developing a secure and test-driven RESTful service using Flask! This guide outlines everything you need, from environment setup to deployment, in a clear, emoji-enhanced format π.
---
## π Note: Important Security Information
- Always handle sensitive data securely.
- Never commit secrets or API keys into version control.
- Ensure all APIs have proper validation and error handling.---
## π οΈ Initialize Development Environment
1. Clone the repository.
2. Set up your virtual environment:
```bash
python3 -m venv venv
source venv/bin/activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```---
## π¦ Project Overview
This project implements a RESTful service with full CRUD operations for managing **accounts**, built using **Flask** and tested with **nose** using TDD principles.
---
## π REST API Guidelines Review
- Use appropriate HTTP status codes.
- Use JSON for all responses.
- Follow RESTful conventions:
- `GET /accounts` β List all accounts
- `GET /accounts/` β Read an account
- `POST /accounts` β Create an account
- `PUT /accounts/` β Update an account
- `DELETE /accounts/` β Delete an account---
## π§ͺ Exercise 1: Implement Your First User Story
Start with the "Create an Account" story. Use TDD:
1. Write a failing test.
2. Implement the feature.
3. Make it pass.
4. Refactor.
5. Maintain 95%+ test coverage.---
## π§ Reference: RESTful Service
Refer to `service/routes.py` and `tests/test_routes.py` for routing logic and test cases. Always write your test **before** implementing the logic!
---
## π§° Exercise 2: Create a REST API with Flask
Implement the remaining user stories: **Read, List, Update, Delete**
### π§Ύ Read an Account
**Test**
```python
def test_get_account(self):
"""It should Read a single Account"""
account = self._create_accounts(1)[0]
resp = self.client.get(f"{BASE_URL}/{account.id}")
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.get_json()["name"], account.name)
````**Route**
```python
@app.route("/accounts/", methods=["GET"])
def get_accounts(account_id):
account = Account.find(account_id)
if not account:
abort(status.HTTP_404_NOT_FOUND)
return account.serialize(), status.HTTP_200_OK
```---
### π List All Accounts
**Test**
```python
def test_get_account_list(self):
"""It should Get a list of Accounts"""
self._create_accounts(5)
resp = self.client.get(BASE_URL)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(len(resp.get_json()), 5)
```**Route**
```python
@app.route("/accounts", methods=["GET"])
def list_accounts():
accounts = Account.all()
return jsonify([a.serialize() for a in accounts]), status.HTTP_200_OK
```---
### βοΈ Update an Account
**Test**
```python
def test_update_account(self):
"""It should Update an existing Account"""
account = AccountFactory()
resp = self.client.post(BASE_URL, json=account.serialize())
new_account = resp.get_json()
new_account["name"] = "Updated Name"
resp = self.client.put(f"{BASE_URL}/{new_account['id']}", json=new_account)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.get_json()["name"], "Updated Name")
```**Route**
```python
@app.route("/accounts/", methods=["PUT"])
def update_accounts(account_id):
account = Account.find(account_id)
if not account:
abort(status.HTTP_404_NOT_FOUND)
account.deserialize(request.get_json())
account.update()
return account.serialize(), status.HTTP_200_OK
```---
### ποΈ Delete an Account
**Test**
```python
def test_delete_account(self):
"""It should Delete an Account"""
account = self._create_accounts(1)[0]
resp = self.client.delete(f"{BASE_URL}/{account.id}")
self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
```**Route**
```python
@app.route("/accounts/", methods=["DELETE"])
def delete_accounts(account_id):
account = Account.find(account_id)
if account:
account.delete()
return "", status.HTTP_204_NO_CONTENT
```---
## π‘ Hints and Solutions
Find detailed breakdowns, hints, and solutions for each user story in the `docs/` folder or reference materials.
---
## π§ͺ Exercise 3: Run the REST Service
Start the development server:
```bash
flask run
```Run tests:
```bash
nosetests --with-coverage
```---
## β Exercise 4: Sprint Review
1. Verify acceptance criteria are met.
2. Confirm test coverage β₯ 95%.
3. Review your Kanban board for completeness.
4. Capture screenshots of progress.---
## π Conclusion
π You've now completed the full lifecycle of building and testing a RESTful service using Flask and TDD! Pat yourself on the back!
> π§ Keep exploring by adding auth, pagination, or filtering!
---
## π Screenshots for Evidence
* `read-accounts.jpg`
* `list-accounts.jpg`
* `update-accounts.jpg`
* `delete-accounts.jpg`---
## π§ Bonus: Error Handling
Test edge cases like unsupported methods or invalid routes:
```python
def test_method_not_allowed(self):
"""It should not allow an illegal method call"""
resp = self.client.delete(BASE_URL)
self.assertEqual(resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
```# Lab: π§Ύ Account REST API - Sprint 1 β
Welcome to the **Account Microservice**! This service supports full CRUD operations for managing customer accounts. Below is a breakdown of functionality, tests, and routes implemented in Sprint 1.
---
## π Features Implemented
### 1. β Create an Account
- **Route:** `POST /accounts`
- **Demo Command:**
```bash
curl -i -X POST http://127.0.0.1:5000/accounts \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"john@doe.com","address":"123 Main St.","phone_number":"555-1212"}'
````* **Screenshot:** `rest-create-done.jpg`
---
### 2. π Update an Account
* **Route:** `PUT /accounts/`
* **Functionality:** Modify an existing account by providing updated fields.
* **Demo Command:**```bash
curl -i -X PUT http://127.0.0.1:5000/accounts/1 \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","email":"john@doe.com","address":"123 Main St.","phone_number":"555-1111"}'
```
* **Screenshot:** `rest-update-done.jpg`#### π§ͺ Test Case
```python
def test_update_account(self):
"""It should Update an existing Account"""
test_account = AccountFactory()
resp = self.client.post(BASE_URL, json=test_account.serialize())
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)new_account = resp.get_json()
new_account["name"] = "Something Known"resp = self.client.put(f"{BASE_URL}/{new_account['id']}", json=new_account)
self.assertEqual(resp.status_code, status.HTTP_200_OK)updated_account = resp.get_json()
self.assertEqual(updated_account["name"], "Something Known")
```#### π Flask Route
```python
@app.route("/accounts/", methods=["PUT"])
def update_accounts(account_id):
"""Update an Account"""
app.logger.info(f"Request to update an Account with id: {account_id}")
account = Account.find(account_id)
if not account:
abort(status.HTTP_404_NOT_FOUND, f"Account with id [{account_id}] could not be found.")
account.deserialize(request.get_json())
account.update()
return account.serialize(), status.HTTP_200_OK
```---
### 3. ποΈ Delete an Account
* **Route:** `DELETE /accounts/`
* **Demo Command:**```bash
curl -i -X DELETE http://127.0.0.1:5000/accounts/1
```
* **Screenshot:** `rest-delete-done.jpg`#### π§ͺ Test Case
```python
def test_delete_account(self):
"""It should Delete an Account"""
account = self._create_accounts(1)[0]
resp = self.client.delete(f"{BASE_URL}/{account.id}")
self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)
```#### π Flask Route
```python
@app.route("/accounts/", methods=["DELETE"])
def delete_accounts(account_id):
"""Delete an Account"""
app.logger.info(f"Request to delete an Account with id: {account_id}")
account = Account.find(account_id)
if account:
account.delete()
return "", status.HTTP_204_NO_CONTENT
```---
### 4. β Method Not Allowed
* **Route:** `DELETE /accounts`
* **Demo:** Sending an invalid method to an unsupported route.#### π§ͺ Test Case
```python
def test_method_not_allowed(self):
"""It should not allow an illegal method call"""
resp = self.client.delete(BASE_URL) # DELETE not allowed on /accounts
self.assertEqual(resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
```---
## π· Sprint Demo Artifacts
| Action | Screenshot |
| ------ | ---------------------- |
| Create | `rest-create-done.jpg` |
| List | `rest-list-done.jpg` |
| Read | `rest-read-done.jpg` |
| Update | `rest-update-done.jpg` |
| Delete | `rest-delete-done.jpg` |---
## π§ Reflections: Sprint Retrospective
### β What went well?
* Full CRUD endpoints tested and validated.
* REST service ran smoothly during local demo.### β What could be improved?
* Initial test data setup was manual β can be streamlined.
* Better error handling could be implemented.### π What to change next sprint?
* Add automated CI test runs for PRs.
* Explore pagination or filtering on list endpoint.---
## π Tech Stack
* Flask π
* Pytest π§ͺ
* curl π
* SQLAlchemy π’οΈ
* Docker (optional) π³
---# π Sprint Retrospective: Capstone Project Sprint 1
## β What Went Right
- π Successfully implemented **CRUD operations** for the Account microservice.
- π§ͺ All **tests passed**, ensuring high code quality and functionality.
- π€ Smooth **collaboration with the product owner** during the sprint review.
- π Clear and detailed **documentation** guided development and testing effectively.---
## β οΈ What Went Wrong
- π€ Initial confusion around handling **HTTP status codes** correctly.
- π Spent more time than expected debugging **update and delete endpoints**.
- π§ Could have **planned testing earlier** to catch bugs before implementation.---
## π What to Improve Next Sprint
- π§ͺ Write **more comprehensive tests before coding** new features.
- π§ Allocate time upfront for **API design discussions** and clarity.
- βοΈ **Automate deployment and integration** steps for faster feedback.
- β±οΈ Schedule **regular check-ins** to detect and resolve issues early.---
## π Final Thoughts
Reflecting on your sprint helps you improve as a developer and teammate. Keep these lessons in mind for Sprint 2! π‘> βAgile is not a destinationβitβs a journey of continuous improvement.β πΆββοΈ
# Lab: π Sprint 2 Planning β Capstone Project
π **Estimated Time Needed:** 30 minutes
Welcome to **Sprint 2 Planning**! With Sprint 1 successfully wrapped up π, itβs time to level up our microservice by adding **continuous integration** and **security enhancements**.
---
## π― Objectives
In this sprint, you will:
- π Create stories for Sprint 2
- ποΈ Add them to your **Kanban board**
- π·οΈ Apply appropriate **labels** and **estimates**
- π Build and prioritize your **Sprint Backlog**---
## πΈ Screenshot Requirements
π· Throughout this lab, you'll need to take screenshots (`.jpg` or `.png`) to document your progress. Use built-in OS tools:
- **Mac**: `β§ + β + 3` or `β§ + β + 4`
- **Windows**: `Alt + Print Screen`, then paste into Paint or Snipping Tool---
## π¦ New Requirements from Management
Management has requested:
- π€ **Automated CI** using GitHub Actions
- π‘οΈ **Security upgrades** including security headers and CORS policy---
## π§© User Stories for Sprint 2
### β Story 1: Automate Continuous Integration Checks
**Title:** Need the ability to automate continuous integration checks
**As a** Developer
**I need** automation to build and test every pull request
**So that** I don't rely on manual testing#### π§ Assumptions
- Use **GitHub Actions** for automation
- Include **linting** and **unit testing**
- Use **`postgres:alpine`** as the DB image
- Add a **build status badge** to the `README.md`#### β Acceptance Criteria (Gherkin)
```gherkin
Given code is ready to be merged
When a pull request is created
Then GitHub Actions should run linting and unit tests
And the badge should show that the build is passing
````* π **Label:** `technical debt`
* π **Estimate:** `Small (3)`
* π¦ **Status:** Move to **Sprint Backlog** (ranked 1st)---
### π Story 2: Add Security Headers and CORS Policies
**Title:** Need to add security headers and CORS policies
**As a** service provider
**I need** security headers and CORS in place
**So that** my site is protected from attacks#### π§ Assumptions
* Use `Flask-Talisman` for **security headers**
* Use `Flask-CORS` for **CORS policies**#### β Acceptance Criteria (Gherkin)
```gherkin
Given the site is secured
When a REST API request is made
Then secure headers and a CORS policy should be returned
```* π **Label:** `security`
* π **Estimate:** `Medium (5)`
* π¦ **Status:** Move to **Sprint Backlog** (ranked 2nd)---
## ποΈ Finalizing the Sprint Backlog
* β Add both stories to **Sprint 2**
* π’ Rank story 1 (CI automation) **first**
* π’ Rank story 2 (Security) **second**
* πΌοΈ Take a screenshot of your Kanban board as `sprint2-plan.jpg` or `sprint2-plan.png`---
## π Conclusion
π **Congratulations!** You've completed your Sprint 2 planning. Your team is now ready to:
* π Start building automation
* π Secure your microservice
* β Improve development workflow> βPlanning is bringing the future into the present so that you can do something about it now.β β Alan Lakein
### π Exercise 1: Pick Up the First Story
β± **Estimated Time:** 10 minutes
π **Objective:** Start working on your first story by properly updating the kanban board.---
### π Your Task
Before you begin coding, follow these steps:
1. π§ Navigate to your **kanban board**.
2. π Find the **first story** at the top of the **Sprint Backlog**:
> **"Need the ability to automate continuous integration checks"**
3. π Move the story to the **In Progress** column.
4. π Assign the story to **yourself**.
5. π Open and **read the full contents** of the story.---
### π§Ύ Story Details
> **Title:** Need the ability to automate continuous integration checks
>
> **As a** Developer
> **I need** automation to build and test every pull request
> **So that** I do not have to rely on manual testing of each request, which is time-consuming### π Assumptions
- βοΈ GitHub Actions will be used for the automation workflow
- π§ͺ Workflow must include **code linting** and **testing**
- π The Docker image used for the database should be `postgres:alpine`
- π· A GitHub Actions **badge** should be added to the `README.md` to reflect the build status---
### β Acceptance Criteria (Gherkin)
```gherkin
Given code is ready to be merged
When a pull request is created
Then GitHub Actions should run linting and unit tests
And the badge should show that the build is passing
````---
### π Results
β The story should now:
* Be visible in the **In Progress** column
* Be assigned to **you**
* Be **fully reviewed** so you're ready to begin development---
### πΈ Don't Forget: Screenshot Reminder
πΌ Take a screenshot of your kanban board after assigning and moving the story. Save it as:
```
sprint2-story-inprogress.jpg
```Here's a well-structured `README.md` with appropriate sections, helpful emojis, and your GitHub Actions build badge included.
---
## π¦ Workflow Overview
This project uses GitHub Actions to automate the following CI pipeline:
1. β **Checkout the code**
2. π₯ **Install Python dependencies**
3. π§Ό **Lint the code with Flake8**
4. π§ͺ **Run unit tests with Nose**
5. π **Display build status with a badge**---
### βοΈ GitHub Actions Workflow Configuration
The workflow file is located at:
`.github/workflows/ci-build.yaml`### π³ Build Job Configuration
```yaml
jobs:
build:
runs-on: ubuntu-latest
container: python:3.9-slim
services:
postgres:
image: postgres:alpine
ports:
- 5432:5432
env:
POSTGRES_PASSWORD: pgs3cr3t
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
````### π₯ Steps to Run
```yaml
steps:
- name: Checkout
uses: actions/checkout@v2- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel
pip install -r requirements.txt- name: Lint with flake8
run: |
flake8 service --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 service --count --max-complexity=10 --max-line-length=127 --statistics- name: Run unit tests with nose
run: nosetests
env:
DATABASE_URI: "postgresql://postgres:pgs3cr3t@postgres:5432/testdb"
```---
### π§ͺ Testing & Coverage
* **Testing Tool:** `nose`
* **Linter:** `flake8`
* **Coverage:** Automatically integrated via `nose` configuration in `setup.cfg`---
### π·οΈ Badge
The badge above π automatically updates with the latest build status of your default branch (`main`).
Itβs a great way to communicate your projectβs CI health! β---
### πΈ Evidence (Screenshots to Submit)
* `ci-workflow-done.jpg` β Workflow run from GitHub Actions
* `ci-badge-done.jpg` β README showing the CI badge
* `ci-kanban-done.jpg` β Kanban board showing story in "Done" column---
## β Final Steps
βοΈ Create a Pull Request
βοΈ Merge once the checks pass
βοΈ Celebrate! π---
> Replace `` in the badge URL with your actual GitHub username to activate the badge!
# π‘οΈ Add Security to Your RESTful Service
Welcome to the **"Add Security to Your RESTful Service"** hands-on lab! In this lab, you'll enhance your Flask microservice to improve its security posture using HTTP security headers and CORS policies.
---
## β±οΈ Estimated Time
**60 minutes**
---
## π― Objectives
By the end of this lab, you will:
β Take the next story from the Sprint Backlog
π Add `Flask-Talisman` for security headers
π Add `Flask-CORS` to enable cross-origin requests
π View the results of your security enhancements
π₯ Make a pull request and merge after CI tests pass
ποΈ Move the story to the β **Done** column on your kanban board---
## π§° Tools & Libraries
- π Flask
- π‘οΈ [Flask-Talisman](https://github.com/GoogleCloudPlatform/flask-talisman) β Automatically adds security headers like HSTS, CSP, and X-Frame-Options
- π [Flask-CORS](https://flask-cors.readthedocs.io/en/latest/) β Enables CORS in your Flask app---
## ποΈ Setup Instructions
1. π Pull the latest story from your Sprint Backlog
2. ποΈ Create a new branch:
```bash
git checkout -b add-security-headers
```3. β Add packages to your `requirements.txt`:
```txt
Flask-Talisman
Flask-Cors
```4. π¦ Install the new dependencies:
```bash
pip install -r requirements.txt
```5. π Update your `service/__init__.py` or wherever your app is created:
```python
from flask_talisman import Talisman
from flask_cors import CORSapp = Flask(__name__)
Talisman(app)
CORS(app)
```---
## π Validation Steps
After making your changes:
β Run your unit tests locally or via `make test`
β Check headers via browser dev tools or `curl -I http://localhost:5000`
β Confirm CORS headers like `Access-Control-Allow-Origin` are present
β Commit changes:```bash
git add .
git commit -m "π Added security headers and CORS policies"
```---
## π Push & Pull Request
π€ Push your branch:
```bash
git push origin add-security-headers
```π Create a pull request on GitHub
β Wait for CI to pass
π Merge when ready---
## π§Ύ Evidence to Submit
πΈ Take screenshots of the following and save them as:
* β `security-headers-terminal.png` β terminal showing security headers present
* β `ci-pipeline-passed.png` β CI pipeline success in GitHub Actions
* β `security-readme-badge.png` β (optional) README with updated security note
* β `kanban-security-done.png` β Kanban board with story in **Done**---
## β οΈ Notes on Environment
βοΈ Your Cloud IDE is **ephemeral** (short-lived).
Make sure to:* π Re-run setup after each reset:
```bash
export GITHUB_ACCOUNT=your_github_account
git clone https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git
cd devops-capstone-project
bash ./bin/setup.sh
exit
```* β Open a new terminal and verify Python is using the virtual environment:
```bash
which python
python --version
```---
## π¬ Summary
With these changes, your RESTful service is now:
π Secured with HTTP headers
π Accessible with CORS
π οΈ Integrated into CI/CD
π Documented and validated!π Great job making your service more production-ready!
---
### π Exercise 1: Pick Up the Next Story
Welcome to **Exercise 1** of your DevOps Sprint! It's time to grab the next story from your **Sprint Backlog** and begin working. This step sets the stage for improving the security of your RESTful microservice.
---
### β Your Task
Follow these steps to kick off development:
1οΈβ£ Open your **Kanban board** (e.g., Trello, GitHub Projects, or Jira).
2οΈβ£ Find the story titled:> π **"Need to add security headers and CORS policies"**
3οΈβ£ π οΈ Move the story to the **In Progress** column.
4οΈβ£ π Assign the story to yourself.
5οΈβ£ π Open the story and **read its full contents**.---
### π Story Details
### π― Title:
**Need to add security headers and CORS policies**### π€ As a:
Service provider### π§© I need:
My service to use **security headers** and **CORS policies**### π‘οΈ So that:
My website is not vulnerable to **CORS attacks**---
### π Assumptions
- β `Flask-Talisman` will be used for adding **security headers**
- β `Flask-CORS` will be used for managing **cross-origin resource sharing**---
### π Acceptance Criteria
| #οΈβ£ | Given | When | Then |
|----|-------|------|------|
| 1οΈβ£ | The site is secured | A REST API request is made | Secure headers and a valid CORS policy are returned |---
### π Results
Once complete, your **Kanban board** should reflect:
- β Story in **In Progress** column
- β Story **assigned to you**
- β Story content **reviewed and understood**---
### π Next Step
You're now ready to:
β‘οΈ Proceed to **Exercise 2**: Observe the Current Behavior
This will help you compare the response **before and after** implementing Flask-Talisman!π§ Pro Tip: Screenshot your Kanban board with the story in **In Progress** β you'll need it later!
---
π‘ *"Start with clarity. Deliver with security."*
Hereβs a complete `README.md` in **Markdown format with emojis** that documents **Exercise 4: Add Security Headers** as part of your DevOps Capstone Lab workflow:
### π Exercise 4: Add Security Headers
In this exercise, you'll begin improving the security of your microservice by adding **Flask-Talisman**, which injects essential HTTP security headers into your REST API responses.
---
### π§ Objective
Apply security best practices to your Flask application by:
- β Adding `Flask-Talisman` to your `requirements.txt`
- β Installing the dependency
- β Modifying your Flask app to include Talisman
- β Validating the expected security headers using automated tests---
### π οΈ Your Task
### π¦ 1. Add Flask-Talisman to `requirements.txt`
At the bottom of the file, add:
```txt
Flask-Talisman
````---
### π₯ 2. Install Requirements
Use the following command in your terminal:
```bash
pip install -r requirements.txt
```---
### π§© 3. Update the Flask App
Open the file:
```
./service/__init__.py
```Then, do the following:
#### π Import Talisman
Add this import:
```python
from flask_talisman import Talisman
```#### 𧬠Create Talisman Instance
After your Flask app is created, add:
```python
talisman = Talisman(app)
```This wraps your Flask app with Talisman, enabling automatic security headers π
---
### π§ͺ 4. Run Unit Tests
Test only your routes:
```bash
nosetests tests/test_routes.py
```β Your **security headers test** should now **pass**
β Other route tests may **fail** due to enforced HTTPS (we'll fix this in Exercise 5)---
### π§Ύ Results
You should observe:
* β `test_security_headers` **passes**
* β Other tests may fail due to 302 redirects or missing HTTPS handlingNo worries β you're on the right path! π―
We'll adjust Talisman to disable forced HTTPS during tests in the next exercise.---
### π¬ Commit Your Work
Once this step is complete, don't forget to save your progress:
```bash
git commit -am "Added security headers"
```---
### π Next Up
β‘οΈ [Exercise 5: Disable Forced HTTPS](#) β Fix your broken tests so they pass during local testing.
---
π§ *βSecure early, test always, and deploy with confidence.β*
### π Exercise 7: Add CORS Policies
Now that your microservice is secure with HTTP headers via Flask-Talisman, it's time to allow **Cross-Origin Resource Sharing (CORS)** using Flask-Cors β enabling your frontend or other services to interact with your API!
---
### β Objectives
- βοΈ Write a unit test to validate CORS headers
- π¦ Add `Flask-Cors` to your project
- π§ Enable CORS in your Flask app
- π§ͺ Run tests and verify success
- πΈ Capture evidence for screenshots---
### π§ͺ Step 1: Write the Test Case
Add the following to `tests/test_routes.py`:
```python
def test_cors_security(self):
"""It should return a CORS header"""
response = self.client.get('/', environ_overrides=HTTPS_ENVIRON)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Check for the CORS header
self.assertEqual(response.headers.get('Access-Control-Allow-Origin'), '*')
````𧨠This test will **fail initially** because Flask hasn't been configured with CORS yet.
---
### π¦ Step 2: Add Flask-Cors
Update `requirements.txt`:
```txt
Flask-Cors
```Then install the new dependency:
```bash
pip install -r requirements.txt
```---
### π§ Step 3: Enable CORS in Your App
Edit `service/__init__.py`:
1. Import the library:
```python
from flask_cors import CORS
```2. Enable CORS **after Talisman is initialized**:
```python
CORS(app)
```This will allow cross-origin requests from **any domain** (using `*` wildcard).
---
### β Step 4: Run All Tests
Run all tests again to verify:
```bash
nosetests
```β Now **all tests should pass**, including your new `test_cors_security`.
---
### πΎ Step 5: Commit Your Changes
```bash
git commit -am "Added CORS headers"
```---
### π§ͺ Exercise 8: Validate CORS Headers
Letβs manually confirm that your service returns CORS headers!
---
### π Step 1: Start the Service
In one terminal, run:
```bash
honcho start
```---
## π Step 2: Use curl to Check Headers
In a second terminal:
```bash
curl -I localhost:5000
```You should see output like this:
```
HTTP/1.1 302 FOUND
Server: gunicorn
Date: Thu, 13 Oct 2022 20:18:54 GMT
Content-Type: text/html; charset=utf-8
Location: https://localhost:5000/
Access-Control-Allow-Origin: *
Permissions-Policy: browsing-topics=()
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; object-src 'none'
Referrer-Policy: strict-origin-when-cross-origin
```β Confirm the presence of the `Access-Control-Allow-Origin: *` header.
---
### π Step 3: Stop the Service
In the first terminal, stop the service by pressing:
```
Ctrl+C
```(You might need to press it twice.)
---
### π Exercise 8 Continued: Make a Pull Request
Youβve now implemented and verified CORS headers β letβs commit, push, and make a PR.
---
### π Step 1: Confirm All Changes Are Committed
```bash
git status
```If anything is uncommitted, do:
```bash
git add .
git commit -m "Added CORS headers"
```---
### βοΈ Step 2: Push to Remote
```bash
git push -u origin
```---
### π Step 3: Open a Pull Request
1. Go to your GitHub repo
2. Click **Compare & pull request**
3. Add a meaningful title and description
4. Click **Create pull request**π GitHub Actions will now run your tests automatically.
---
### πΈ Step 4: Take Screenshots for Submission
* `security-code-done.jpg`: Screenshot of `service/__init__.py`
* `security-headers-done.jpg`: Screenshot of terminal showing the curl output
* `security-kanban-done.jpg`: Screenshot of your Kanban board with the story in **Done**---
### π Conclusion
Well done! π You've:
* β Enabled CORS policies using Flask-Cors
* π§ͺ Verified via tests and curl
* π Opened a pull request with CI
* πΈ Captured evidence for your submissionYou're ready to move on to **Sprint 3** where youβll tackle Docker, Kubernetes, and Tekton CD pipelines.
---
### π Sprint 3: Stories Overview & Actions
Welcome to Sprint 3! In this sprint, you'll take your microservice to the next level by containerizing it, deploying it to Kubernetes, and automating the deployment with a CD pipeline using Tekton.
---
### π§© Story 1: Containerize Your Microservice Using Docker
π **Markdown Description:**
```markdown
Title: Containerize your microservice using Docker
**As a** developer
**I need** to containerize my microservice using Docker
**So that** I can deploy it easily with all of its dependencies### Assumptions
* Create a `Dockerfile` for repeatable builds
* Use a `Python:3.9-slim` image as the base
* It must install all of the Python requirements
* It should not run as `root`
* It should use the `gunicorn` wsgi server as an entry point### Acceptance Criteria
Given the Docker image named accounts has been created
When I use `docker run accounts`
Then I should see the accounts service running in Docker
````π· **Label:** Technical Debt
π **Estimate:** Small (3) or Medium (5)
π **Sprint:** Assign to Sprint 3
π¦ **Rank:** Top of Sprint Backlog β---
## βΈοΈ Story 2: Deploy Your Docker Image to Kubernetes
π **Markdown Description:**
```markdown
Title: Deploy your Docker image to Kubernetes
**As a** service provider
**I need** my service to run on Kubernetes
**So that** I can easily scale and manage the service### Assumptions
* Kubernetes manifests will be created in yaml format
* These manifests could be useful to create a CD pipeline
* The actual deployment will be to OpenShift### Acceptance Criteria
Given the Kubernetes manifests have been created
When I use the oc command to apply the manifests
Then the service should be deployed and run in Kubernetes
```π· **Label:** Enhancement
π **Estimate:** Medium (5) or Large (8)
π **Sprint:** Assign to Sprint 3
π¦ **Rank:** 2nd in Sprint Backlog β¬οΈ---
## π Story 3: Create a CD Pipeline to Automate Deployment to Kubernetes
π **Markdown Description:**
```markdown
Title: Create a CD pipeline to automate deployment to Kubernetes
**As a** developer
**I need** to create a CD pipeline to automate deployment to Kubernetes
**So that** the developers are not wasting their time doing it manually### Assumptions
* Use Tekton to define the pipeline
* It should clone, lint, test, build, and deploy the service
* Deployment should be to OpenShift
* It can use a manual trigger for this MVP### Acceptance Criteria
Given the CD pipeline has been created
When I trigger the pipeline run
Then I should see the accounts service deployed to OpenShift
```π· **Label:** Enhancement or Technical Debt
π **Estimate:** Large (8) or Extra Large (13)
π **Sprint:** Assign to Sprint 3
π¦ **Rank:** 3rd in Sprint Backlog β¬οΈ---
## π Final Checklist
β Add the 3 stories above to your **Kanban board**
β‘οΈ Move from **Product Backlog** β **Sprint Backlog**
π· Apply correct **labels** and **estimates**
π Rank them in order:1. π³ Containerize Microservice
2. βΈοΈ Deploy to Kubernetes
3. π Create CD PipelineπΌ **Take a screenshot of your Kanban board** with Sprint 3 planned
πΈ Save it as: `sprint3-plan.jpg` or `sprint3-plan.png`---
## π§ Next Steps
Once Sprint 3 is planned, start the **"Containerize"** story first:
* Create your `Dockerfile` π³
* Build & test the image locally
* Commit your changes and make a PR
---### π Important Security Information & Setup Guide
Welcome to the **Cloud IDE with OpenShift**! This is where all your development will take place. It includes the tools you need to use Docker and deploy a PostgreSQL database.
---
### β οΈ Environment Notice
π§ͺ The **lab environment is ephemeral**, meaning it can be **deleted at any time**.
π You must **push your work frequently** to your own GitHub repository so you can recreate it later.
π« This is a **shared environment** β **do NOT store** personal info, passwords, or tokens here.---
### π€ GitHub Personal Access Token (PAT)
If you haven't generated a GitHub **Personal Access Token**, do it now.
β It should have:
- **repo** and **write** permissions
- Expiration set to **60 days**π Use this token **in place of your GitHub password** when pushing code from the Cloud IDE.
---
### πΎ Save Your Work β Always Push to GitHub
Use these Git commands to commit and push changes:
```bash
git add .
git commit -m "Meaningful message"
git push origin
````---
### π» Initialize Development Environment
Each time the lab environment is recreated, follow these steps to initialize:
### π§° Step-by-Step Setup
```bash
# 1. Set your GitHub username as an environment variable
export GITHUB_ACCOUNT={your_github_account}# 2. Clone your project repository
git clone https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git# 3. Move into the project directory
cd devops-capstone-project# 4. Run the setup script
bash ./bin/setup.sh# β You should see: capstone_setup_complete
# 5. Exit the terminal
exit
```---
### β Validate the Environment
Open a **new terminal** to activate the Python virtual environment and run:
```bash
which python
python --version
```βοΈ You should see a path to your virtualenv and a Python 3.9.x version.
---
## π§Ύ Screenshot Instructions
πΈ Youβll need to **take screenshots** during the lab for quizzes or submissions.
Ensure screenshots are saved as `.jpg` or `.png`.### π· Screenshot Shortcuts:
**Mac:**
* Full screen: `Shift + Command + 3`
* Selection: `Shift + Command + 4`**Windows:**
* Active window: `Alt + Print Screen` β paste into editor β save image
---
### π¨πΏβπ» Exercise 1: Pick Up the First Story
Your task is to start your first development story.
### β Steps:
1. Go to your **Kanban board**
2. Find the top story in the **Sprint Backlog**:
**π "Containerize your microservice using Docker"**
3. Move it to **In Progress**
4. Assign it to yourself
5. Open and read the story content### π Story Overview:
```
As a developer
I need to containerize my microservice using Docker
So that I can deploy it easily with all of its dependenciesAssumptions:
- Create a Dockerfile for repeatable builds
- Use a Python:3.9-slim image as the base
- It must install all Python requirements
- It should not run as root
- It should use gunicorn as the WSGI entry pointAcceptance Criteria:
Given the Docker image named accounts has been created
When I use docker run accounts
Then I should see the accounts service running in Docker
```β Youβre now ready to start implementing this story!
---
# π³ Exercise 2: Create a Dockerfile
Welcome to **Exercise 2** of the DevOps Capstone Project! In this step, youβll create a `Dockerfile` to containerize your microservice. Let's make sure your app runs in any environment β consistently and securely. πͺ
---
## π§ Story Context
**Story:** `Containerize your microservice using Docker`
π― **Goal:**
> As a developer, I need to containerize my microservice using Docker so that I can deploy it easily with all its dependencies.---
## β Your Tasks
### ποΈ 1. Change into your project directory
```bash
cd devops-capstone-project
````---
### π± 2. Create a new branch for Docker work
```bash
git checkout -b add-docker
```---
### π§ͺ 3. Run tests to verify current functionality
```bash
nosetests
```> β All tests should pass before continuing. Fix any issues if needed.
---
### π 4. Create your Dockerfile
In the **root** of your project, create a new file named `Dockerfile` with the following contents:
```Dockerfile
# πΉ Use a minimal Python 3.9 image
FROM python:3.9-slim# π Set the working directory and install dependencies
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt# π Copy the application code
COPY service/ ./service/# π€ Switch to a non-root user for better security
RUN useradd --uid 1000 theia && chown -R theia /app
USER theia# π Run the app using Gunicorn on port 8080
EXPOSE 8080
CMD ["gunicorn", "--bind=0.0.0.0:8080", "--log-level=info", "service:app"]
```> π‘ This setup ensures repeatable builds, installs Python requirements, avoids root user risks, and runs via `gunicorn`.
---
## π Result
If you've followed the steps above, your `Dockerfile` should look like this:
```Dockerfile
FROM python:3.9-slimWORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txtCOPY service/ ./service/
RUN useradd --uid 1000 theia && chown -R theia /app
USER theiaEXPOSE 8080
CMD ["gunicorn", "--bind=0.0.0.0:8080", "--log-level=info", "service:app"]
```---
## πΈ Evidence
Take a screenshot of your completed `Dockerfile` and the passing test output.
β Save it as: `dockerfile-setup.jpg` or `dockerfile-setup.png`
---
## π Next Up
Ready for **Exercise 3: Create a Docker Image**?
Letβs build and run your containerized microservice! ποΈπ§---
### π Exercise 4: Make a Pull Request
You're almost done containerizing your microservice! π
Now let's integrate your work into the main branch by creating a pull request (PR) on GitHub.---
## π Step-by-Step Instructions
### π§© 1. Check Your Git Status
```bash
git status
````Make sure your changes are committed. If not, continue below. β
---
### β 2. Add the `Dockerfile` to Staging
```bash
git add Dockerfile
```---
### π¬ 3. Commit Your Changes
```bash
git commit -m "Added docker support"
```---
### βοΈ 4. Push Your Branch to GitHub
First-time Git setup (if needed):
```bash
git config --local user.email "you@example.com"
git config --local user.name "Your Name"
```Then push:
```bash
git push --set-upstream origin add-docker
```> π‘οΈ Use your GitHub Personal Access Token when prompted for a password.
---
### π 5. Create a Pull Request (PR)
1. Go to your repository on GitHub.
2. You should see a banner prompting you to **Compare & Pull Request**.
3. Review the changes and submit the PR.
4. GitHub Actions π€ will automatically run your tests.
5. β Once the tests pass, **Merge** the pull request into the main branch.---
### ποΈ 6. Update Your Kanban Board
Move the story βAdd Docker Supportβ to the β **Done** column on your Kanban board.
πΈ **Evidence**: Take a screenshot of the Done column.
πΎ Save as: `kube-docker-done.jpg` or `kube-docker-done.png`---
### π§Ή 7. Clean Up Your Local Branch
```bash
git checkout main
git pull
git branch -d add-docker
```π§Ό This deletes the old working branch after merging.
---
## βοΈ What's Next?
π― **Exercise 5: Pick Up the Next Story**
Head to your Kanban board and take the next story from the Sprint Backlog:### β¨ "Deploy your Docker image to Kubernetes"
π¦ Move the story to **In Progress**, assign it to yourself, and read through it carefully.
Youβll create Kubernetes manifests, deploy to OpenShift, and expose your microservice! π---
# β Final Evidence Collection πΈ
Before wrapping up the lab, youβll need to collect and submit the following proof of your work.
---
## π 1. Save the Dockerfile URL
π Navigate to your Dockerfile on GitHub.
π Open the file in your browser.
π Copy the full URL from the address bar.π Example:
```[https://github.com/](https://github.com/)/devops-capstone-project/blob/main/Dockerfile
````
π‘ Youβll submit this link as part of your final deliverables.
---
## π³ 2. Capture Docker Image List
π¦ Run the following command in your terminal:
```bash
docker image ls
````πΈ Take a screenshot of the output. Make sure your image (e.g., `accounts:1`) is clearly visible.
πΎ Save the screenshot as:
```
kube-images.jpg
```or
```
kube-images.png
```---
## βΈοΈ 3. Capture Kubernetes Deployment
π Check that your `accounts` app is deployed and running:
```bash
oc get all -l app=accounts
```πΈ Take a screenshot of the full output.
Make sure it shows:* π§± Deployment
* π ReplicaSet
* π³ Pod
* π Service
* π£οΈ Route (if applicable)πΎ Save the screenshot as:
```
kube-deploy-accounts.jpg
```or
```
kube-deploy-accounts.png
```---
## π Conclusion
π **Congratulations!** Youβve completed a major step in your DevOps journey.
π What you accomplished:
* β Built a Docker image from a secure, reusable Dockerfile.
* β Deployed your microservice to an OpenShift Kubernetes cluster.
* β Authored Kubernetes manifests for repeatable deployments.
* β Practiced modern CI/CD workflows using GitHub and pull requests.
* β Prepared for automation via Tekton pipelines and CD tooling.---
## π‘οΈ Security Reminder
> β οΈ Your cloud environment is **ephemeral** and **shared**:
* Always push your work to GitHub so you can restore it later.
* **Never** store sensitive data like tokens or passwords in this environment.
* Use a **GitHub Personal Access Token (PAT)** when prompted for a password in Git.---
## π Screenshot Tips
π· Youβll need screenshots for quizzes or peer review:
### Mac
* Capture full screen: `Shift + Command + 3`
* Capture selection: `Shift + Command + 4`### Windows
* Active window: `Alt + Print Screen`
* Paste into an image editor and save as `.jpg` or `.png`π§Ύ Ensure your screenshots are clear, relevant, and saved with correct filenames.
---
Let me know if you need help with:
* β Tekton CD pipeline setup
* β YAML templates
* β Submitting your final lab work
* β Peer review tipsπ― You're on track to mastering modern DevOps workflows!
---
# π Tekton CD Pipeline Lab Progress Guide
You're making great progress! Below is a helpful summary and checklist to ensure you stay on track as you build your **Tekton CD pipeline** using **OpenShift** and **GitHub**.
---
## π Security & Environment Reminders
β οΈ **Ephemeral Environment**
Your Cloud IDE and OpenShift environment can be reset at any time. Always:- β Push code to **GitHub** frequently
- β Never store **credentials**, **tokens**, or **personal info** in the lab environment
- π Use a **GitHub Personal Access Token (PAT)** when pushing codeπ PAT requirements:
- Must have **repo** and **write** access
- Should **expire in 60 days or less**---
## πΌοΈ Screenshot Instructions & Naming Convention
You'll be asked to provide screenshots as **evidence** for your work. Keep file names consistent:
| Step | Filename Suggestion |
|----------------------------|------------------------------|
| Tekton pipeline run | `cd-pipeline-run.jpg` |
| OpenShift deployment view | `tekton-deploy.jpg` |
| Kanban board story done | `cd-pipeline-done.jpg` |### π· Screenshot Shortcuts
**Mac:**
- π₯οΈ Full screen: `Shift + Cmd + 3`
- π² Select area: `Shift + Cmd + 4`**Windows:**
- πͺ Active window: `Alt + Print Screen`
- ποΈ Paste in Paint or editor, then **save as `.jpg` or `.png`**---
## π§ͺ Development Environment Setup
Your lab environment may be reset. Use this process every time to reinitialize:
### 1. π₯οΈ Open a New Terminal
```bash
export GITHUB_ACCOUNT=your_github_username
git clone https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git
cd devops-capstone-project
bash ./bin/setup.sh
````You should see:
```text
capstone_setup_complete
```### 2. β Exit the Terminal
```bash
exit
```This step ensures your virtual environment will activate correctly.
### 3. π Open a New Terminal
Then validate your setup:
```bash
which python
python --version
```Expected Output:
* `/home/theia/.venv/bin/python`
* `Python 3.9.x`You're now fully set up to continue!
---
## π Your Current Story: Create a CD Pipeline
π **Title**: *Create a CD pipeline to automate deployment to Kubernetes*
**As a** developer
**I need** to create a CD pipeline
**So that** deployments arenβt manual anymore### π§ Assumptions:
* Use **Tekton** to define the pipeline
* Pipeline stages: **clone β lint β test β build β deploy**
* Deployment should be to **OpenShift**
* Can be triggered manually### β Acceptance Criteria:
1. CD pipeline has been created
2. When triggered, it runs the full pipeline
3. It deploys the `accounts` service to OpenShift---
## π οΈ Next Steps
* π Finish creating Tekton `Task` and `Pipeline` YAMLs
* π Apply them using `oc apply -f`
* β Trigger a pipeline run and observe the deployment
* πΈ Capture the required screenshots
* β Move your Kanban story to **Done**---
# π οΈ Tekton CD Pipeline Lab: What's Next?
Welcome back! You're continuing your DevOps journey by automating your deployment using Tekton and OpenShift. This section focuses on setting up your pipeline and adding your first functional task: **Linting** with `flake8`.
---
## π Create the CD Pipeline Directory Structure
Run the following to organize your Tekton files:
```bash
mkdir -p tekton/cd-pipeline
cd tekton/cd-pipeline
````All your Tasks, Pipeline, PipelineRun, and Secrets will live here.
---
## π§ͺ Exercise 2: Overview & Setup
You're creating a CD pipeline that will:
* β Clone your repo
* β Lint your code
* β Run unit tests
* β Build a Docker image
* β Deploy to OpenShift---
### π Step 1: Switch to a New Git Branch
```bash
cd devops-capstone-project
git checkout -b cd-pipeline
```---
### π§ͺ Step 2: Run Unit Tests
```bash
nosetests
```Fix any failing tests before proceeding.
---
### π¦ Step 3: Apply Existing Pipeline Files
```bash
oc create -f tekton/pvc.yaml # Create workspace PVC
oc apply -f tekton/tasks.yaml # Apply starter tasks
oc apply -f tekton/pipeline.yaml # Apply initial pipeline
```---
### β¬οΈ Step 4: Install Required Tekton Tasks
```bash
tkn hub install task git-clone
```π If that fails due to a version mismatch:
```bash
kubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/task/git-clone/0.9/git-clone.yaml
```---
### βΆοΈ Step 5: Run the Initial Pipeline
```bash
tkn pipeline start cd-pipeline \
-p repo-url="https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git" \
-p branch="main" \
-w name=pipeline-workspace,claimName=pipelinerun-pvc \
-s pipeline \
--showlog
```---
### β Step 6: Verify Pipeline Run
```bash
tkn pipelinerun ls # Check STATUS column
tkn pipelinerun logs --last # View logs of the latest run
```---
## π§Ή Exercise 3: Create the Lint Task with flake8
You're going to use `flake8` to lint your code using an official task from Tekton Hub.
---
### π§° Step 1: Install the flake8 Task
```bash
tkn hub install task flake8
```---
### π οΈ Step 2: Add Lint Task to Your Pipeline
Edit your `tekton/pipeline.yaml` file and add the following **lint task** definition after the `clone` task:
```yaml
- name: lint
workspaces:
- name: source
workspace: pipeline-workspace
taskRef:
name: flake8
params:
- name: image
value: "python:3.9-slim"
- name: args
value: ["--count","--max-complexity=10","--max-line-length=127","--statistics"]
runAfter:
- clone
```---
### π Step 3: Apply the Updated Pipeline
```bash
oc apply -f tekton/pipeline.yaml
```---
### βΆοΈ Step 4: Re-run the Pipeline with Lint Task
```bash
tkn pipeline start cd-pipeline \
-p repo-url="https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git" \
-p branch="main" \
-w name=pipeline-workspace,claimName=pipelinerun-pvc \
-s pipeline \
--showlog
```---
### β Step 5: Confirm It Worked
Use:
```bash
tkn pipelinerun ls
```Check `STATUS` is `Succeeded`.
To see logs:
```bash
tkn pipelinerun logs --last
```---
## πΎ Step 6: Commit & Push Your Work
Because the Cloud IDE is **ephemeral**, **always** commit and push after any milestone:
```bash
git add tekton/pipeline.yaml
git commit -m "Added lint task to Tekton pipeline"
git push --set-upstream origin cd-pipeline
```---
## π Up Next
Youβre ready to create the **test** task to run your Python unit tests using `nosetests`.
---
# π§ͺ Tekton CD Pipeline: Lint & Test Tasks
Welcome to Exercises 3β5 of your Tekton pipeline! In this phase, you'll integrate code **linting** and **testing** steps into your pipeline using `flake8` and `nosetests`.
---
## β Exercise 3: Add the Lint Task
### π§° Step 1: Install the `flake8` Task from Tekton Hub
```bash
tkn hub install task flake8
````---
### βοΈ Step 2: Edit `tekton/pipeline.yaml`
Add this `lint` task after the `clone` step:
```yaml
- name: lint
workspaces:
- name: source
workspace: pipeline-workspace
taskRef:
name: flake8
params:
- name: image
value: "python:3.9-slim"
- name: args
value: ["--count", "--max-complexity=10", "--max-line-length=127", "--statistics"]
runAfter:
- clone
```π Key Points:
* Use `source` as the workspace name (required by `flake8`).
* Run after the `clone` task.
* Pass `flake8` arguments via `args`.---
### π Step 3: Apply the Updated Pipeline
```bash
oc apply -f tekton/pipeline.yaml
```---
### βΆοΈ Step 4: Start the Pipeline and Watch Logs
```bash
tkn pipeline start cd-pipeline \
-p repo-url="https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git" \
-p branch="main" \
-w name=pipeline-workspace,claimName=pipelinerun-pvc \
-s pipeline \
--showlog
```---
### π Step 5: Check Pipeline Run Status
```bash
tkn pipelinerun ls
tkn pipelinerun logs --last
```---
### πΎ Step 6: Commit Your Work
```bash
git commit -am 'added lint task'
git push --set-upstream origin cd-pipeline
```---
## π§ͺ Exercise 4: Create the Test Task (`nose`)
### βοΈ Step 1: Add a `nose` Task to `tekton/tasks.yaml`
```yaml
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: nose
spec:
description: This task will run nosetests on the provided input.
workspaces:
- name: source
params:
- name: args
description: Arguments to pass to nose
type: string
default: "-v"
- name: database_uri
description: Database connection string
type: string
default: "sqlite:///test.db"
steps:
- name: nosetests
image: python:3.9-slim
workingDir: $(workspaces.source.path)
env:
- name: DATABASE_URI
value: $(params.database_uri)
script: |
#!/bin/bash
set -e
echo "***** Installing dependencies *****"
python -m pip install --upgrade pip wheel
pip install -qr requirements.txt
echo "***** Running nosetests with: $(params.args)"
nosetests $(params.args)
```---
### π Step 2: Apply the Task
```bash
oc apply -f tekton/tasks.yaml
```---
### πΎ Step 3: Commit Your Changes
```bash
git commit -am 'added nose task'
git push
```---
## π§ͺ Exercise 5: Add the Test Task to the Pipeline
### βοΈ Step 1: Update `tekton/pipeline.yaml`
Add the `tests` task using the `nose` task:
```yaml
- name: tests
workspaces:
- name: source
workspace: pipeline-workspace
taskRef:
name: nose
params:
- name: database_uri
value: "sqlite:///test.db"
- name: args
value: "-v --with-spec --spec-color"
runAfter:
- clone
```π§ Note: This runs in **parallel** with `lint` since both run after `clone`.
---
### π Step 2: Apply the Pipeline
```bash
oc apply -f tekton/pipeline.yaml
```---
### βΆοΈ Step 3: Start and Watch the Pipeline
```bash
tkn pipeline start cd-pipeline \
-p repo-url="https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git" \
-p branch="main" \
-w name=pipeline-workspace,claimName=pipelinerun-pvc \
-s pipeline \
--showlog
```---
### β Step 4: Confirm Execution
```bash
tkn pipelinerun ls
tkn pipelinerun logs --last
```---
### πΎ Step 5: Commit the Pipeline Update
```bash
git commit -am 'added test pipeline task'
git push
```---
### π― You're All Set!
You've now added both **linting** and **testing** steps to your Tekton pipeline. Next up: **build** and **deploy**.
---
### π DevOps Capstone CI/CD Pipeline with Tekton
This guide walks you through adding essential tasks to your `pipeline.yaml` for a full CI/CD pipeline using **Tekton** on **OpenShift**.
---
### β Task 1: Add the `tests` Task π§ͺ
Add this block under `spec.tasks:` in your `pipeline.yaml`:
```yaml
- name: tests
workspaces:
- name: source
workspace: pipeline-workspace
taskRef:
name: nose
params:
- name: database_uri
value: "sqlite:///test.db"
- name: args
value: "-v --with-spec --spec-color"
runAfter:
- clone
````### πΎ Apply the changes:
```bash
oc apply -f tekton/pipeline.yaml
```### π€ Commit and Push:
```bash
git commit -am 'added test pipeline task'
git push
```---
## β Task 2: Add the `build` Task ποΈ
Update `spec.params` at the top of your pipeline:
```yaml
spec:
params:
- name: repo-url
- name: branch
default: main
- name: build-image
```Add the `build` task under `spec.tasks:`:
```yaml
- name: build
workspaces:
- name: source
workspace: pipeline-workspace
taskRef:
name: buildah
kind: ClusterTask
params:
- name: IMAGE
value: "$(params.build-image)"
runAfter:
- tests
- lint
```### πΎ Apply the changes:
```bash
oc apply -f tekton/pipeline.yaml
```### π€ Commit and Push:
```bash
git commit -am 'added build task'
git push
```---
## β Task 3: Add the `deploy` Task π’
Add the following task under `spec.tasks:`:
```yaml
- name: deploy
workspaces:
- name: manifest-dir
workspace: pipeline-workspace
taskRef:
name: openshift-client
kind: ClusterTask
params:
- name: SCRIPT
value: |
echo "Updating manifest..."
sed -i "s|IMAGE_NAME_HERE|$(params.build-image)|g" deploy/deployment.yaml
cat deploy/deployment.yaml
echo "Deploying to OpenShift..."
oc apply -f deploy/
oc get pods -l app=accounts
runAfter:
- build
```### π οΈ Modify `deploy/deployment.yaml`:
Make sure the `image:` tag contains this placeholder:
```yaml
image: IMAGE_NAME_HERE
```### πΎ Apply the changes:
```bash
oc apply -f tekton/pipeline.yaml
```### π€ Commit and Push:
```bash
git commit -am 'added deploy task'
git push
```---
## βΆοΈ Run the Pipeline π
Make sure required environment variables are set:
```bash
export GITHUB_ACCOUNT=your_github_username
export SN_ICR_NAMESPACE=your_project_namespace
```Start the pipeline:
```bash
tkn pipeline start cd-pipeline \
-p repo-url="https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git" \
-p branch=main \
-p build-image=image-registry.openshift-image-registry.svc:5000/$SN_ICR_NAMESPACE/accounts:1 \
-w name=pipeline-workspace,claimName=pipelinerun-pvc \
-s pipeline \
--showlog
```π **Note**: `tests` and `lint` tasks run in parallel, so their logs may be intermixed.
---
## π§ Troubleshooting Tips
* Make sure your PostgreSQL pod is running:
```bash
oc get pods
```
* If `postgresql` service is missing:```bash
oc new-app postgresql-ephemeral
```---
## π Summary
β Tests
β Build
β Deploy
β All tasks added and integrated into Tekton pipeline!---
# π Lab: Add Security to Your RESTful ServiceWelcome to the **Security Lab** for your RESTful API! In this lab, youβll learn how to **protect your application** by adding **authentication** and **authorization** to secure your endpoints and control access.
---
## π― Objectives
β Understand common REST API security patterns
β Add authentication (e.g., API Key, JWT, or Basic Auth)
β Restrict access using role-based authorization
β Test the API to ensure it's secure---
## π§ Prerequisites
Before starting, make sure you have:
- A working RESTful API (e.g., Flask, FastAPI, Express.js)
- Experience from the TDD and API development labs
- A tool to test your API (like `curl`, `Postman`, or Swagger UI)---
## π οΈ Step-by-Step Instructions
### 1οΈβ£ Choose an Authentication Strategy
Pick the security method that best fits your app:
π **Basic Authentication** β Quick for local testing
π **API Keys** β Simple but less secure
π **JWT (JSON Web Token)** β Recommended for stateless APIs
π **OAuth 2.0** β For more advanced use cases with external identity providers---
### 2οΈβ£ Implement Authentication
Hereβs an example using **JWT in Flask**:
```python
from flask import request, jsonify
import jwtSECRET = "supersecretkey"
def token_required(f):
def wrapper(*args, **kwargs):
token = request.headers.get("Authorization", None)
if not token:
return jsonify({"message": "Missing token"}), 401
try:
jwt.decode(token, SECRET, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
return jsonify({"message": "Token expired"}), 401
except jwt.InvalidTokenError:
return jsonify({"message": "Invalid token"}), 401
return f(*args, **kwargs)
return wrapper
````Apply it to your endpoints:
```python
@app.route("/secure-data")
@token_required
def secure_data():
return jsonify({"message": "You are authorized!"})
```---
### 3οΈβ£ Add Authorization (Role-Based Access Control)
If your API has multiple user roles (e.g., admin, user), check the user's role inside the JWT payload:
```python
data = jwt.decode(token, SECRET, algorithms=["HS256"])
if data.get("role") != "admin":
return jsonify({"message": "Access denied"}), 403
```---
### 4οΈβ£ Test Your Secured API
Use Postman or `curl` to try different requests:
β Without Token:
```bash
curl http://localhost:5000/secure-data
```β With Valid Token:
```bash
curl -H "Authorization: your_jwt_token_here" http://localhost:5000/secure-data
```π§ͺ Also test:
* Expired or malformed tokens
* Valid tokens with insufficient privileges---
## π§Ό Step 5: Secure Your Secrets
π Use `.env` or environment variables to store secrets
π« **Never** hardcode tokens or passwords in your source code
β Use HTTPS for all production traffic---
## π Evidence to Submit
* β Screenshot of your API rejecting unauthorized requests
* β Screenshot of successful authorized requests
* β (Optional) Your API security code (e.g., `auth.py`)
* β `curl` or Postman output showing denied vs. allowed access---
## π‘ Tips
* Use `pyjwt`, `fastapi-jwt-auth`, or `flask-jwt-extended` for easier JWT handling
* Regularly rotate your secrets or API keys
* Always validate and sanitize user input β even authenticated ones!---
## π Congratulations!
You've now secured your RESTful service with proper **authentication** and **authorization**! This is a huge step in building **safe**, **production-grade** APIs.
Feel free to ask if you need help with JWT setup, token generation, or securing secrets! ππ¬