{"id":28208368,"url":"https://github.com/willie-conway/devops-capstone-project","last_synced_at":"2025-08-16T01:14:59.267Z","repository":{"id":282790429,"uuid":"949629554","full_name":"Willie-Conway/Devops-Capstone-Project","owner":"Willie-Conway","description":"🚀 DevOps Capstone Project for CI/CD, Kubernetes, Docker \u0026 OpenShift 🌐 🛠️ Build, containerize, deploy, and automate microservices in the cloud! 🧪","archived":false,"fork":false,"pushed_at":"2025-05-25T15:12:37.000Z","size":512,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-12T08:44:58.328Z","etag":null,"topics":["automation","ci-cd","devops","docker","flask","kubernetes","microservices","openshift","tekton"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Willie-Conway.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-03-16T21:34:38.000Z","updated_at":"2025-05-25T15:12:40.000Z","dependencies_parsed_at":"2025-05-26T12:47:43.101Z","dependency_job_id":null,"html_url":"https://github.com/Willie-Conway/Devops-Capstone-Project","commit_stats":null,"previous_names":["willie-conway/devops-capstone-project"],"tags_count":0,"template":false,"template_full_name":"ibm-developer-skills-network/aolwx-devops-capstone-template","purl":"pkg:github/Willie-Conway/Devops-Capstone-Project","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willie-Conway%2FDevops-Capstone-Project","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willie-Conway%2FDevops-Capstone-Project/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willie-Conway%2FDevops-Capstone-Project/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willie-Conway%2FDevops-Capstone-Project/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Willie-Conway","download_url":"https://codeload.github.com/Willie-Conway/Devops-Capstone-Project/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Willie-Conway%2FDevops-Capstone-Project/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270653582,"owners_count":24622790,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-15T02:00:12.559Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["automation","ci-cd","devops","docker","flask","kubernetes","microservices","openshift","tekton"],"created_at":"2025-05-17T14:12:17.551Z","updated_at":"2025-08-16T01:14:58.691Z","avatar_url":"https://github.com/Willie-Conway.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DevOps Capstone Project\n\n![Build Status](https://github.com/Willie-Conway/devops-capstone-project/actions/workflows/ci-build.yaml/badge.svg)\n\n\n# 🚀 Lab: Agile Planning Using GitHub\n\n⏳ **Estimated time needed:** 60 minutes\n\n## 🌟 Welcome\nWelcome 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.\n\n**Project Goal:** Develop an account microservice with REST APIs to:\n- ✨ Create\n- 📖 Read\n- 🔄 Update\n- 🗑️ Delete\n- 📜 List customer accounts\n\n\u003e **Note:** This lab uses **GitHub only** (no lab environment).\n\n## 🎯 Objectives\nBy the end of this lab, you will:\n- ✔️ Create a GitHub repository\n- ✔️ Set up a GitHub Kanban board\n- ✔️ Develop a user story template\n- ✔️ Add and prioritize user stories\n- ✔️ Refine a product backlog\n- ✔️ Build a sprint plan\n\n## 📸 Screenshot Requirements\nYou'll need to save these screenshots:\n\n| File Name                         | Description                          |\n|-----------------------------------|--------------------------------------|\n| `planning-repository-done.png`    | GitHub repo created                  |\n| `planning-storytemplate-done.png` | User story template                  |\n| `planning-userstories-done.png`   | 7 user stories added                 |\n| `planning-productbacklog-done.png`| Backlog triaged                      |\n| `planning-labels-done.png`        | Stories labeled \u0026 refined            |\n| `planning-kanban-done.png`        | Final sprint backlog                 |\n\n**Screenshot Shortcuts:**\n- **Mac:** `Shift + Cmd + 3` (full screen) or `Shift + Cmd + 4` (area)\n- **Windows:** `Alt + Print Screen` → Paste into image editor\n\n## 🛠️ Exercises\n\n### Exercise 1: Create GitHub Repository\n1. Use [this template](https://github.com/ibm-developer-skills-network/aolwx-devops-capstone-template) (click \"Use this template\")\n2. Name: `devops-capstone-project` (set to **Public**)\n3. **Evidence:** Save repo URL + `planning-repository-done.png`\n\n---\n\n### Exercise 2: Create Kanban Board\nSet up board with 7 columns:\n1. New issues\n2. Icebox ❄️\n3. Product Backlog 📋\n4. Sprint Backlog 🏃\n5. In progress 🔄\n6. Review/QA ✔️\n7. Done ✅\n\n---\n\n### Exercise 3: User Story Template\nCreate `.github/ISSUE_TEMPLATE/user-story.md`:\n\n```markdown\n**As a** [role]  \n**I need** [function]  \n**So that** [benefit]  \n\n### Details and Assumptions\n* [document what you know]  \n\n### Acceptance Criteria  \n```gherkin\nGiven [context]  \nWhen [action]  \nThen [outcome]\n```\n\n\n\n**Evidence:** `planning-storytemplate-done.png`\n\n---\n\n### Exercise 4: Product Backlog\nCreate 7 user stories:\n1. Set up dev environment\n2. Read an account\n3. Update an account\n4. Delete an account\n5. List accounts\n6. Containerize with Docker 🐳\n7. Deploy to Kubernetes ☸️\n\n**Evidence:** `planning-userstories-done.png`\n\n---\n\n### Exercise 5: Triage Issues\n- Move 5 stories to **Product Backlog**\n- Move 2 stories (Docker/K8s) to **Icebox** ❄️\n\n**Evidence:** `planning-productbacklog-done.png`\n\n---\n\n### Exercise 6: Refine Backlog\n1. Add details/acceptance criteria\n2. Create `technical debt` label (yellow)\n3. Apply labels:\n   - `enhancement` (customer features)\n   - `technical debt` (e.g., setup)\n4. Rank by priority\n\n**Evidence:** `planning-labels-done.png`\n\n---\n\n### Exercise 7: Sprint Plan\n1. Create 3 sprints (1 week each)\n2. Assign 5 stories to **Sprint 1**\n3. Add story points (3=S, 5=M, 8=L, 13=XL)\n4. Move to Sprint Backlog\n\n**Evidence:** `planning-kanban-done.png`\n\n# Lab: 🏦 Account Microservice - RESTful API with TDD\n\n## 🔍 Lab Overview\n**Develop a RESTful Service Using Test-Driven Development**  \n*Estimated time: 90 minutes*\n\n### 🎯 Objectives\n- Follow Agile plan from Kanban board\n- Implement REST API endpoints using TDD\n- Achieve 95%+ test coverage\n- Conduct sprint review\n\n## ⚠️ Important Notes\n- **Ephemeral environment**: Cloud IDE may reset - push changes to GitHub!\n- **Security**: Never store secrets in code\n- **GitHub PAT**: Required for pushing changes (repo+write permissions)\n\n## 🛠️ Setup Instructions\n\n### 1️⃣ Initialize Environment\n```bash\nexport GITHUB_ACCOUNT=your_username\ngit clone https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\ncd devops-capstone-project\nbash ./bin/setup.sh\nexit  # Must reopen terminal!\n```\n\n### 2️⃣ Validate Setup\n```bash\nwhich python        # Should show venv path\npython --version   # Verify Python 3.9+\n```\n\n### 3️⃣ Install Dependencies\n```bash\npip install -r requirements.txt\n```\n### 🧪 TDD Workflow\n#### 🔴 Write Failing Test\n```python\n# tests/test_routes.py\ndef test_get_account(client):\n    response = client.get(\"/accounts/1\")\n    assert response.status_code == 200\n```\n### 🟢 Implement Minimal Code\n```python\n# service/routes.py\n@app.route(\"/accounts/\u003cint:id\u003e\", methods=[\"GET\"])\ndef get_account(id):\n    account = Account.find(id)\n    return jsonify(account.serialize()), 200\n```\n\n### 🔁 Run Tests\n```bash\nnosetests --with-coverage --cover-package=service\n```\n\n### 📊 Coverage Verification\n```bash\ncoverage report -m  # CLI report\ncoverage html       # HTML report\nopen htmlcov/index.html\n```\n\n### 📋 Required Endpoints\n\n| Method | Endpoint         | Description        |\n|--------|------------------|--------------------|\n| GET    | /accounts/\u003cid\u003e   | Get single account |\n| PUT    | /accounts/\u003cid\u003e   | Update account     |\n| DELETE | /accounts/\u003cid\u003e   | Delete account     |\n| GET    | /accounts        | List all accounts  |\n\n\n### 📸 Evidence Checklist\n\n1. **`tests-passing.png`** - All tests green\n\n2. **`coverage-report.png`** - Coverage ≥95%\n\n3. **`api-working.png`** - curl/Postman demo\n\n### 📂 Project Structure\n\n```bash\n.\n├── service/\n│   ├── models.py     # DB models\n│   └── routes.py     # API endpoints\n├── tests/\n│   └── test_routes.py # Unit tests\n├── requirements.txt\n└── bin/setup.sh      # Setup script\n```\n\n### 💡 Pro Tips\n\n- Use mocks in tests:\n  ```python\n  from unittest.mock import patch\n  ```\n\n  - Test edge cases:\n    ```python\n    def test_get_nonexistent_account(client):\n    response = client.get(\"/accounts/999\")\n    assert response.status_code == 404\n    ```\n\n    - Commit often to avoid losing work\n    \n# 🚀 REST API Guidelines Review\n\nThis 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! ✅\n\n---\n\n## 📌 RESTful API Endpoints\n\n| 🛠️ Action | 🔢 Method | 🔁 URL Endpoint         | 📦 Return Code    | 🧾 Body                     |\n|----------|------------|------------------------|-------------------|----------------------------|\n| List     | `GET`      | `/accounts`            | `200 OK`          | Array of accounts `[{}]`   |\n| Create   | `POST`     | `/accounts`            | `201 CREATED`     | A new account `{}`         |\n| Read     | `GET`      | `/accounts/{id}`       | `200 OK` or `404` | Account as JSON `{}`       |\n| Update   | `PUT`      | `/accounts/{id}`       | `200 OK` or `404` | Updated account as JSON    |\n| Delete   | `DELETE`   | `/accounts/{id}`       | `204 NO CONTENT`  | Empty string `\"\"`          |\n\n🧠 **Note:** These standard behaviors enable consistent testing and API usage. If no accounts exist, always return `[]` with `200 OK` instead of a `404`.\n\n---\n\n## 🔢 HTTP Status Codes\n\n| 📟 Code | 🧾 Status              | 📖 Description                            |\n|--------|------------------------|--------------------------------------------|\n| 200    | `HTTP_200_OK`          | ✅ Request successful                       |\n| 201    | `HTTP_201_CREATED`     | 🆕 Resource successfully created            |\n| 204    | `HTTP_204_NO_CONTENT`  | 🗑️ Resource deleted / no body in response   |\n| 404    | `HTTP_404_NOT_FOUND`   | 🚫 Resource not found                       |\n| 405    | `HTTP_405_METHOD_NOT_ALLOWED` | ❌ Method not allowed on endpoint  |\n| 409    | `HTTP_409_CONFLICT`    | ⚠️ Conflict with the current state          |\n\nThese codes are available via:\n```python\nfrom service.common import status\n````\n\n---\n\n## 🧪 Sample Unit Tests\n\n### 🔍 Test: Read Account\n\n```python\ndef test_get_account(self):\n    response = self.client.get(\"/accounts/1\")\n    self.assertEqual(response.status_code, status.HTTP_200_OK)\n    data = response.get_json()\n    self.assertEqual(data[\"id\"], 1)\n```\n\n### 📝 Test: Update Account\n\n```python\ndef test_update_account(self):\n    updated = {\"name\": \"New Name\", \"address\": \"New Address\"}\n    response = self.client.put(\"/accounts/1\", json=updated)\n    self.assertEqual(response.status_code, status.HTTP_200_OK)\n    data = response.get_json()\n    self.assertEqual(data[\"name\"], \"New Name\")\n```\n\n### ❌ Test: Delete Account\n\n```python\ndef test_delete_account(self):\n    response = self.client.delete(\"/accounts/1\")\n    self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)\n```\n\n### 📜 Test: List All Accounts\n\n```python\ndef test_list_accounts(self):\n    response = self.client.get(\"/accounts\")\n    self.assertEqual(response.status_code, status.HTTP_200_OK)\n    data = response.get_json()\n    self.assertIsInstance(data, list)\n```\n\n### 🆕 Test: Create Account\n\n```python\ndef test_create_account(self):\n    new_account = {\"name\": \"Jane Doe\", \"address\": \"123 Elm St\"}\n    response = self.client.post(\"/accounts\", json=new_account)\n    self.assertEqual(response.status_code, status.HTTP_201_CREATED)\n    data = response.get_json()\n    self.assertEqual(data[\"name\"], \"Jane Doe\")\n```\n\n---\n\n## 💡 TDD Workflow Tips\n\n* ✅ **Write the test first** (Red)\n* 🧑‍💻 **Write the code to pass the test** (Green)\n* ♻️ **Refactor if needed**\n* 💥 Use the constants like `status.HTTP_200_OK`\n* 🔄 Use `.get()`, `.post()`, `.put()`, `.delete()` on `self.client` for endpoint tests\n\n---\n\n## ⚙️ `setup.cfg` Configuration for `nosetests`\n\n```ini\n[nosetests]\nverbosity=2\nwith-spec=1\nspec-color=1\nwith-coverage=1\ncover-erase=1\ncover-package=service\n```\n\n🎉 Now, running `nosetests` will include color, spec-style output, and coverage!\n\n---\n\n## 📸 Submission Checklist\n\n* ✅ Screenshot of `setup.cfg`: `rest-setupcfg-done.jpg` / `.png`\n* ✅ Screenshot of Kanban board (Story in Done): `rest-techdebt-done.jpg` / `.png`\n\n---\n\n# 🚀 Lab: Develop a RESTful Service Using Test-Driven Development (TDD)\n\nWelcome 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 😄.\n\n---\n\n## 🔐 Note: Important Security Information\n\n- Always handle sensitive data securely.\n- Never commit secrets or API keys into version control.\n- Ensure all APIs have proper validation and error handling.\n\n---\n\n## 🛠️ Initialize Development Environment\n\n1. Clone the repository.\n2. Set up your virtual environment:\n    ```bash\n    python3 -m venv venv\n    source venv/bin/activate\n    ```\n3. Install dependencies:\n    ```bash\n    pip install -r requirements.txt\n    ```\n\n---\n\n## 📦 Project Overview\n\nThis project implements a RESTful service with full CRUD operations for managing **accounts**, built using **Flask** and tested with **nose** using TDD principles.\n\n---\n\n## 📘 REST API Guidelines Review\n\n- Use appropriate HTTP status codes.\n- Use JSON for all responses.\n- Follow RESTful conventions:\n  - `GET /accounts` → List all accounts\n  - `GET /accounts/\u003cid\u003e` → Read an account\n  - `POST /accounts` → Create an account\n  - `PUT /accounts/\u003cid\u003e` → Update an account\n  - `DELETE /accounts/\u003cid\u003e` → Delete an account\n\n---\n\n## 🧪 Exercise 1: Implement Your First User Story\n\nStart with the \"Create an Account\" story. Use TDD:\n1. Write a failing test.\n2. Implement the feature.\n3. Make it pass.\n4. Refactor.\n5. Maintain 95%+ test coverage.\n\n---\n\n## 🧭 Reference: RESTful Service\n\nRefer to `service/routes.py` and `tests/test_routes.py` for routing logic and test cases. Always write your test **before** implementing the logic!\n\n---\n\n## 🧰 Exercise 2: Create a REST API with Flask\n\nImplement the remaining user stories: **Read, List, Update, Delete**\n\n### 🧾 Read an Account\n**Test**\n```python\ndef test_get_account(self):\n    \"\"\"It should Read a single Account\"\"\"\n    account = self._create_accounts(1)[0]\n    resp = self.client.get(f\"{BASE_URL}/{account.id}\")\n    self.assertEqual(resp.status_code, status.HTTP_200_OK)\n    self.assertEqual(resp.get_json()[\"name\"], account.name)\n````\n\n**Route**\n\n```python\n@app.route(\"/accounts/\u003cint:account_id\u003e\", methods=[\"GET\"])\ndef get_accounts(account_id):\n    account = Account.find(account_id)\n    if not account:\n        abort(status.HTTP_404_NOT_FOUND)\n    return account.serialize(), status.HTTP_200_OK\n```\n\n---\n\n### 📃 List All Accounts\n\n**Test**\n\n```python\ndef test_get_account_list(self):\n    \"\"\"It should Get a list of Accounts\"\"\"\n    self._create_accounts(5)\n    resp = self.client.get(BASE_URL)\n    self.assertEqual(resp.status_code, status.HTTP_200_OK)\n    self.assertEqual(len(resp.get_json()), 5)\n```\n\n**Route**\n\n```python\n@app.route(\"/accounts\", methods=[\"GET\"])\ndef list_accounts():\n    accounts = Account.all()\n    return jsonify([a.serialize() for a in accounts]), status.HTTP_200_OK\n```\n\n---\n\n### ✏️ Update an Account\n\n**Test**\n\n```python\ndef test_update_account(self):\n    \"\"\"It should Update an existing Account\"\"\"\n    account = AccountFactory()\n    resp = self.client.post(BASE_URL, json=account.serialize())\n    new_account = resp.get_json()\n    new_account[\"name\"] = \"Updated Name\"\n    resp = self.client.put(f\"{BASE_URL}/{new_account['id']}\", json=new_account)\n    self.assertEqual(resp.status_code, status.HTTP_200_OK)\n    self.assertEqual(resp.get_json()[\"name\"], \"Updated Name\")\n```\n\n**Route**\n\n```python\n@app.route(\"/accounts/\u003cint:account_id\u003e\", methods=[\"PUT\"])\ndef update_accounts(account_id):\n    account = Account.find(account_id)\n    if not account:\n        abort(status.HTTP_404_NOT_FOUND)\n    account.deserialize(request.get_json())\n    account.update()\n    return account.serialize(), status.HTTP_200_OK\n```\n\n---\n\n### 🗑️ Delete an Account\n\n**Test**\n\n```python\ndef test_delete_account(self):\n    \"\"\"It should Delete an Account\"\"\"\n    account = self._create_accounts(1)[0]\n    resp = self.client.delete(f\"{BASE_URL}/{account.id}\")\n    self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)\n```\n\n**Route**\n\n```python\n@app.route(\"/accounts/\u003cint:account_id\u003e\", methods=[\"DELETE\"])\ndef delete_accounts(account_id):\n    account = Account.find(account_id)\n    if account:\n        account.delete()\n    return \"\", status.HTTP_204_NO_CONTENT\n```\n\n---\n\n## 💡 Hints and Solutions\n\nFind detailed breakdowns, hints, and solutions for each user story in the `docs/` folder or reference materials.\n\n---\n\n## 🧪 Exercise 3: Run the REST Service\n\nStart the development server:\n\n```bash\nflask run\n```\n\nRun tests:\n\n```bash\nnosetests --with-coverage\n```\n\n---\n\n## ✅ Exercise 4: Sprint Review\n\n1. Verify acceptance criteria are met.\n2. Confirm test coverage ≥ 95%.\n3. Review your Kanban board for completeness.\n4. Capture screenshots of progress.\n\n---\n\n## 🏁 Conclusion\n\n🎉 You've now completed the full lifecycle of building and testing a RESTful service using Flask and TDD! Pat yourself on the back!\n\n\u003e 🚧 Keep exploring by adding auth, pagination, or filtering!\n\n---\n\n## 📎 Screenshots for Evidence\n\n* `read-accounts.jpg`\n* `list-accounts.jpg`\n* `update-accounts.jpg`\n* `delete-accounts.jpg`\n\n---\n\n## 🧠 Bonus: Error Handling\n\nTest edge cases like unsupported methods or invalid routes:\n\n```python\ndef test_method_not_allowed(self):\n    \"\"\"It should not allow an illegal method call\"\"\"\n    resp = self.client.delete(BASE_URL)\n    self.assertEqual(resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)\n```\n\n\n\n# Lab: 🧾 Account REST API - Sprint 1 ✅\n\nWelcome 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.\n\n---\n\n## 🚀 Features Implemented\n\n### 1. ✅ Create an Account\n- **Route:** `POST /accounts`\n- **Demo Command:**\n```bash\n  curl -i -X POST http://127.0.0.1:5000/accounts \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"John Doe\",\"email\":\"john@doe.com\",\"address\":\"123 Main St.\",\"phone_number\":\"555-1212\"}'\n````\n\n* **Screenshot:** `rest-create-done.jpg`\n\n---\n\n### 2. 🔄 Update an Account\n\n* **Route:** `PUT /accounts/\u003cid\u003e`\n* **Functionality:** Modify an existing account by providing updated fields.\n  \n* **Demo Command:**\n\n  ```bash\n  curl -i -X PUT http://127.0.0.1:5000/accounts/1 \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"John Doe\",\"email\":\"john@doe.com\",\"address\":\"123 Main St.\",\"phone_number\":\"555-1111\"}'\n  ```\n* **Screenshot:** `rest-update-done.jpg`\n\n#### 🧪 Test Case\n\n```python\ndef test_update_account(self):\n    \"\"\"It should Update an existing Account\"\"\"\n    test_account = AccountFactory()\n    resp = self.client.post(BASE_URL, json=test_account.serialize())\n    self.assertEqual(resp.status_code, status.HTTP_201_CREATED)\n\n    new_account = resp.get_json()\n    new_account[\"name\"] = \"Something Known\"\n\n    resp = self.client.put(f\"{BASE_URL}/{new_account['id']}\", json=new_account)\n    self.assertEqual(resp.status_code, status.HTTP_200_OK)\n\n    updated_account = resp.get_json()\n    self.assertEqual(updated_account[\"name\"], \"Something Known\")\n```\n\n#### 🛠 Flask Route\n\n```python\n@app.route(\"/accounts/\u003cint:account_id\u003e\", methods=[\"PUT\"])\ndef update_accounts(account_id):\n    \"\"\"Update an Account\"\"\"\n    app.logger.info(f\"Request to update an Account with id: {account_id}\")\n    account = Account.find(account_id)\n    if not account:\n        abort(status.HTTP_404_NOT_FOUND, f\"Account with id [{account_id}] could not be found.\")\n    account.deserialize(request.get_json())\n    account.update()\n    return account.serialize(), status.HTTP_200_OK\n```\n\n---\n\n### 3. 🗑️ Delete an Account\n\n* **Route:** `DELETE /accounts/\u003cid\u003e`\n* **Demo Command:**\n\n  ```bash\n  curl -i -X DELETE http://127.0.0.1:5000/accounts/1\n  ```\n* **Screenshot:** `rest-delete-done.jpg`\n\n#### 🧪 Test Case\n\n```python\ndef test_delete_account(self):\n    \"\"\"It should Delete an Account\"\"\"\n    account = self._create_accounts(1)[0]\n    resp = self.client.delete(f\"{BASE_URL}/{account.id}\")\n    self.assertEqual(resp.status_code, status.HTTP_204_NO_CONTENT)\n```\n\n#### 🛠 Flask Route\n\n```python\n@app.route(\"/accounts/\u003cint:account_id\u003e\", methods=[\"DELETE\"])\ndef delete_accounts(account_id):\n    \"\"\"Delete an Account\"\"\"\n    app.logger.info(f\"Request to delete an Account with id: {account_id}\")\n    account = Account.find(account_id)\n    if account:\n        account.delete()\n    return \"\", status.HTTP_204_NO_CONTENT\n```\n\n---\n\n### 4. ❌ Method Not Allowed\n\n* **Route:** `DELETE /accounts`\n* **Demo:** Sending an invalid method to an unsupported route.\n\n#### 🧪 Test Case\n\n```python\ndef test_method_not_allowed(self):\n    \"\"\"It should not allow an illegal method call\"\"\"\n    resp = self.client.delete(BASE_URL)  # DELETE not allowed on /accounts\n    self.assertEqual(resp.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)\n```\n\n---\n\n## 📷 Sprint Demo Artifacts\n\n| Action | Screenshot             |\n| ------ | ---------------------- |\n| Create | `rest-create-done.jpg` |\n| List   | `rest-list-done.jpg`   |\n| Read   | `rest-read-done.jpg`   |\n| Update | `rest-update-done.jpg` |\n| Delete | `rest-delete-done.jpg` |\n\n---\n\n## 🧭 Reflections: Sprint Retrospective\n\n### ✅ What went well?\n\n* Full CRUD endpoints tested and validated.\n* REST service ran smoothly during local demo.\n\n### ❗ What could be improved?\n\n* Initial test data setup was manual — can be streamlined.\n* Better error handling could be implemented.\n\n### 🔁 What to change next sprint?\n\n* Add automated CI test runs for PRs.\n* Explore pagination or filtering on list endpoint.\n\n---\n\n## 🛠 Tech Stack\n\n* Flask 🐍\n* Pytest 🧪\n* curl 🌐\n* SQLAlchemy 🛢️\n* Docker (optional) 🐳\n---\n\n# 🌀 Sprint Retrospective: Capstone Project Sprint 1\n\n## ✅ What Went Right\n- 🚀 Successfully implemented **CRUD operations** for the Account microservice.\n- 🧪 All **tests passed**, ensuring high code quality and functionality.\n- 🤝 Smooth **collaboration with the product owner** during the sprint review.\n- 📚 Clear and detailed **documentation** guided development and testing effectively.\n\n---\n\n## ⚠️ What Went Wrong\n- 🤔 Initial confusion around handling **HTTP status codes** correctly.\n- 🐞 Spent more time than expected debugging **update and delete endpoints**.\n- 🧭 Could have **planned testing earlier** to catch bugs before implementation.\n\n---\n\n## 🔄 What to Improve Next Sprint\n- 🧪 Write **more comprehensive tests before coding** new features.\n- 🧠 Allocate time upfront for **API design discussions** and clarity.\n- ⚙️ **Automate deployment and integration** steps for faster feedback.\n- ⏱️ Schedule **regular check-ins** to detect and resolve issues early.\n\n---\n\n## 📝 Final Thoughts\nReflecting on your sprint helps you improve as a developer and teammate. Keep these lessons in mind for Sprint 2! 💡\n\n\n\u003e “Agile is not a destination—it’s a journey of continuous improvement.” 🚶‍♂️\n\n\n# Lab: 🚀 Sprint 2 Planning — Capstone Project\n\n🕒 **Estimated Time Needed:** 30 minutes\n\nWelcome 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**.\n\n---\n\n## 🎯 Objectives\n\nIn this sprint, you will:\n\n- 📝 Create stories for Sprint 2  \n- 🗂️ Add them to your **Kanban board**  \n- 🏷️ Apply appropriate **labels** and **estimates**  \n- 📋 Build and prioritize your **Sprint Backlog**\n\n---\n\n## 📸 Screenshot Requirements\n\n📷 Throughout this lab, you'll need to take screenshots (`.jpg` or `.png`) to document your progress. Use built-in OS tools:\n\n- **Mac**: `⇧ + ⌘ + 3` or `⇧ + ⌘ + 4`\n- **Windows**: `Alt + Print Screen`, then paste into Paint or Snipping Tool\n\n---\n\n## 📦 New Requirements from Management\n\nManagement has requested:\n\n- 🤖 **Automated CI** using GitHub Actions  \n- 🛡️ **Security upgrades** including security headers and CORS policy  \n\n---\n\n## 🧩 User Stories for Sprint 2\n\n### ✅ Story 1: Automate Continuous Integration Checks\n\n**Title:** Need the ability to automate continuous integration checks  \n**As a** Developer  \n**I need** automation to build and test every pull request  \n**So that** I don't rely on manual testing\n\n#### 🧠 Assumptions\n- Use **GitHub Actions** for automation\n- Include **linting** and **unit testing**\n- Use **`postgres:alpine`** as the DB image\n- Add a **build status badge** to the `README.md`\n\n#### ✅ Acceptance Criteria (Gherkin)\n```gherkin\nGiven code is ready to be merged  \nWhen a pull request is created  \nThen GitHub Actions should run linting and unit tests  \nAnd the badge should show that the build is passing\n````\n\n* 📌 **Label:** `technical debt`\n* 📏 **Estimate:** `Small (3)`\n* 🚦 **Status:** Move to **Sprint Backlog** (ranked 1st)\n\n---\n\n### 🔐 Story 2: Add Security Headers and CORS Policies\n\n**Title:** Need to add security headers and CORS policies\n**As a** service provider\n**I need** security headers and CORS in place\n**So that** my site is protected from attacks\n\n#### 🧠 Assumptions\n\n* Use `Flask-Talisman` for **security headers**\n* Use `Flask-CORS` for **CORS policies**\n\n#### ✅ Acceptance Criteria (Gherkin)\n\n```gherkin\nGiven the site is secured  \nWhen a REST API request is made  \nThen secure headers and a CORS policy should be returned\n```\n\n* 📌 **Label:** `security`\n* 📏 **Estimate:** `Medium (5)`\n* 🚦 **Status:** Move to **Sprint Backlog** (ranked 2nd)\n\n---\n\n## 🗂️ Finalizing the Sprint Backlog\n\n* ✅ Add both stories to **Sprint 2**\n* 🔢 Rank story 1 (CI automation) **first**\n* 🔢 Rank story 2 (Security) **second**\n* 🖼️ Take a screenshot of your Kanban board as `sprint2-plan.jpg` or `sprint2-plan.png`\n\n---\n\n## 🏁 Conclusion\n\n🎉 **Congratulations!** You've completed your Sprint 2 planning. Your team is now ready to:\n\n* 🚀 Start building automation\n* 🔐 Secure your microservice\n* ✅ Improve development workflow\n\n\u003e “Planning is bringing the future into the present so that you can do something about it now.” – Alan Lakein\n\n\n### 🚀 Exercise 1: Pick Up the First Story\n\n⏱ **Estimated Time:** 10 minutes  \n📋 **Objective:** Start working on your first story by properly updating the kanban board.\n\n---\n\n### 📌 Your Task\n\nBefore you begin coding, follow these steps:\n\n1. 🧭 Navigate to your **kanban board**.\n2. 🔍 Find the **first story** at the top of the **Sprint Backlog**:\n   \u003e **\"Need the ability to automate continuous integration checks\"**\n3. 👉 Move the story to the **In Progress** column.\n4. 🙋 Assign the story to **yourself**.\n5. 📖 Open and **read the full contents** of the story.\n\n---\n\n### 🧾 Story Details\n\n\u003e **Title:** Need the ability to automate continuous integration checks  \n\u003e \n\u003e **As a** Developer  \n\u003e **I need** automation to build and test every pull request  \n\u003e **So that** I do not have to rely on manual testing of each request, which is time-consuming\n\n### 🔍 Assumptions\n\n- ⚙️ GitHub Actions will be used for the automation workflow  \n- 🧪 Workflow must include **code linting** and **testing**  \n- 🐘 The Docker image used for the database should be `postgres:alpine`  \n- 🏷 A GitHub Actions **badge** should be added to the `README.md` to reflect the build status\n\n---\n\n### ✅ Acceptance Criteria (Gherkin)\n\n```gherkin\nGiven code is ready to be merged  \nWhen a pull request is created  \nThen GitHub Actions should run linting and unit tests  \nAnd the badge should show that the build is passing\n````\n\n---\n\n### 🏁 Results\n\n✅ The story should now:\n\n* Be visible in the **In Progress** column\n* Be assigned to **you**\n* Be **fully reviewed** so you're ready to begin development\n\n---\n\n### 📸 Don't Forget: Screenshot Reminder\n\n🖼 Take a screenshot of your kanban board after assigning and moving the story. Save it as:\n\n```\nsprint2-story-inprogress.jpg\n```\n\n\nHere's a well-structured `README.md` with appropriate sections, helpful emojis, and your GitHub Actions build badge included.\n\n---\n\n\n\n## 📦 Workflow Overview\n\nThis project uses GitHub Actions to automate the following CI pipeline:\n\n1. ✅ **Checkout the code**\n2. 📥 **Install Python dependencies**\n3. 🧼 **Lint the code with Flake8**\n4. 🧪 **Run unit tests with Nose**\n5. 📊 **Display build status with a badge**\n\n---\n\n### ⚙️ GitHub Actions Workflow Configuration\n\nThe workflow file is located at:  \n`.github/workflows/ci-build.yaml`\n\n### 🐳 Build Job Configuration\n\n```yaml\njobs:\n  build:\n    runs-on: ubuntu-latest\n    container: python:3.9-slim\n    services:\n      postgres:\n        image: postgres:alpine\n        ports:\n          - 5432:5432\n        env:\n          POSTGRES_PASSWORD: pgs3cr3t\n          POSTGRES_DB: testdb\n        options: \u003e-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n````\n\n### 📥 Steps to Run\n\n```yaml\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip wheel\n          pip install -r requirements.txt\n\n      - name: Lint with flake8\n        run: |\n          flake8 service --count --select=E9,F63,F7,F82 --show-source --statistics\n          flake8 service --count --max-complexity=10 --max-line-length=127 --statistics\n\n      - name: Run unit tests with nose\n        run: nosetests\n        env:\n          DATABASE_URI: \"postgresql://postgres:pgs3cr3t@postgres:5432/testdb\"\n```\n\n---\n\n### 🧪 Testing \u0026 Coverage\n\n* **Testing Tool:** `nose`\n* **Linter:** `flake8`\n* **Coverage:** Automatically integrated via `nose` configuration in `setup.cfg`\n\n---\n\n### 🏷️ Badge\n\nThe badge above 👆 automatically updates with the latest build status of your default branch (`main`).\nIt’s a great way to communicate your project’s CI health! ✅\n\n---\n\n### 📸 Evidence (Screenshots to Submit)\n\n* `ci-workflow-done.jpg` – Workflow run from GitHub Actions\n* `ci-badge-done.jpg` – README showing the CI badge\n* `ci-kanban-done.jpg` – Kanban board showing story in \"Done\" column\n\n---\n\n## ✅ Final Steps\n\n✔️ Create a Pull Request\n✔️ Merge once the checks pass\n✔️ Celebrate! 🎉\n\n---\n\n\u003e Replace `\u003cOWNER\u003e` in the badge URL with your actual GitHub username to activate the badge!\n\n\n# 🛡️ Add Security to Your RESTful Service\n\nWelcome 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.\n\n---\n\n## ⏱️ Estimated Time\n\n**60 minutes**\n\n---\n\n## 🎯 Objectives\n\nBy the end of this lab, you will:\n\n✅ Take the next story from the Sprint Backlog  \n🔐 Add `Flask-Talisman` for security headers  \n🌐 Add `Flask-CORS` to enable cross-origin requests  \n👀 View the results of your security enhancements  \n📥 Make a pull request and merge after CI tests pass  \n🗂️ Move the story to the ✅ **Done** column on your kanban board  \n\n---\n\n## 🧰 Tools \u0026 Libraries\n\n- 🐍 Flask  \n- 🛡️ [Flask-Talisman](https://github.com/GoogleCloudPlatform/flask-talisman) – Automatically adds security headers like HSTS, CSP, and X-Frame-Options  \n- 🔄 [Flask-CORS](https://flask-cors.readthedocs.io/en/latest/) – Enables CORS in your Flask app\n\n---\n\n## 🏗️ Setup Instructions\n\n1. 🔃 Pull the latest story from your Sprint Backlog  \n2. 🏗️ Create a new branch:  \n```bash\n   git checkout -b add-security-headers\n```\n\n3. ➕ Add packages to your `requirements.txt`:\n\n```txt\n   Flask-Talisman\n   Flask-Cors\n```\n\n4. 📦 Install the new dependencies:\n\n   ```bash\n   pip install -r requirements.txt\n   ```\n\n5. 📝 Update your `service/__init__.py` or wherever your app is created:\n\n   ```python\n   from flask_talisman import Talisman\n   from flask_cors import CORS\n\n   app = Flask(__name__)\n\n   Talisman(app)\n   CORS(app)\n   ```\n\n---\n\n## 🔎 Validation Steps\n\nAfter making your changes:\n\n✅ Run your unit tests locally or via `make test`\n✅ Check headers via browser dev tools or `curl -I http://localhost:5000`\n✅ Confirm CORS headers like `Access-Control-Allow-Origin` are present\n✅ Commit changes:\n\n```bash\ngit add .\ngit commit -m \"🔐 Added security headers and CORS policies\"\n```\n\n---\n\n## 🚀 Push \u0026 Pull Request\n\n📤 Push your branch:\n\n```bash\ngit push origin add-security-headers\n```\n\n📋 Create a pull request on GitHub\n✅ Wait for CI to pass\n🔁 Merge when ready\n\n---\n\n## 🧾 Evidence to Submit\n\n📸 Take screenshots of the following and save them as:\n\n* ✅ `security-headers-terminal.png` – terminal showing security headers present\n* ✅ `ci-pipeline-passed.png` – CI pipeline success in GitHub Actions\n* ✅ `security-readme-badge.png` – (optional) README with updated security note\n* ✅ `kanban-security-done.png` – Kanban board with story in **Done**\n\n---\n\n## ⚠️ Notes on Environment\n\n☁️ Your Cloud IDE is **ephemeral** (short-lived).\nMake sure to:\n\n* 🔄 Re-run setup after each reset:\n\n  ```bash\n  export GITHUB_ACCOUNT=your_github_account\n  git clone https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\n  cd devops-capstone-project\n  bash ./bin/setup.sh\n  exit\n  ```\n\n* ✅ Open a new terminal and verify Python is using the virtual environment:\n\n  ```bash\n  which python\n  python --version\n  ```\n\n---\n\n## 💬 Summary\n\nWith these changes, your RESTful service is now:\n\n🔒 Secured with HTTP headers\n🌐 Accessible with CORS\n🛠️ Integrated into CI/CD\n📈 Documented and validated!\n\n👏 Great job making your service more production-ready!\n\n\n---\n\n### 📖 Exercise 1: Pick Up the Next Story\n\nWelcome 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.\n\n---\n\n### ✅ Your Task\n\nFollow these steps to kick off development:\n\n1️⃣ Open your **Kanban board** (e.g., Trello, GitHub Projects, or Jira).  \n2️⃣ Find the story titled:\n\n\u003e 📝 **\"Need to add security headers and CORS policies\"**\n\n3️⃣ 🛠️ Move the story to the **In Progress** column.  \n4️⃣ 🙋 Assign the story to yourself.  \n5️⃣ 📚 Open the story and **read its full contents**.\n\n---\n\n### 📌 Story Details\n\n### 🎯 Title:  \n**Need to add security headers and CORS policies**\n\n### 👤 As a:\nService provider\n\n### 🧩 I need:\nMy service to use **security headers** and **CORS policies**\n\n### 🛡️ So that:\nMy website is not vulnerable to **CORS attacks**\n\n---\n\n### 💭 Assumptions\n\n- ✅ `Flask-Talisman` will be used for adding **security headers**\n- ✅ `Flask-CORS` will be used for managing **cross-origin resource sharing**\n\n---\n\n### 📋 Acceptance Criteria\n\n| #️⃣ | Given | When | Then |\n|----|-------|------|------|\n| 1️⃣ | The site is secured | A REST API request is made | Secure headers and a valid CORS policy are returned |\n\n---\n\n### 🏁 Results\n\nOnce complete, your **Kanban board** should reflect:\n\n- ✅ Story in **In Progress** column  \n- ✅ Story **assigned to you**  \n- ✅ Story content **reviewed and understood**\n\n---\n\n### 🔜 Next Step\n\nYou're now ready to:\n\n➡️ Proceed to **Exercise 2**: Observe the Current Behavior  \nThis will help you compare the response **before and after** implementing Flask-Talisman!\n\n🧠 Pro Tip: Screenshot your Kanban board with the story in **In Progress** — you'll need it later!\n\n---\n\n💡 *\"Start with clarity. Deliver with security.\"*\n\n\nHere’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:\n\n\n### 🔐 Exercise 4: Add Security Headers\n\nIn 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.\n\n---\n\n### 🧠 Objective\n\nApply security best practices to your Flask application by:\n\n- ✅ Adding `Flask-Talisman` to your `requirements.txt`\n- ✅ Installing the dependency\n- ✅ Modifying your Flask app to include Talisman\n- ✅ Validating the expected security headers using automated tests\n\n---\n\n### 🛠️ Your Task\n\n### 📦 1. Add Flask-Talisman to `requirements.txt`\nAt the bottom of the file, add:\n```txt\nFlask-Talisman\n````\n\n---\n\n### 📥 2. Install Requirements\n\nUse the following command in your terminal:\n\n```bash\npip install -r requirements.txt\n```\n\n---\n\n### 🧩 3. Update the Flask App\n\nOpen the file:\n\n```\n./service/__init__.py\n```\n\nThen, do the following:\n\n#### 🔁 Import Talisman\n\nAdd this import:\n\n```python\nfrom flask_talisman import Talisman\n```\n\n#### 🧬 Create Talisman Instance\n\nAfter your Flask app is created, add:\n\n```python\ntalisman = Talisman(app)\n```\n\nThis wraps your Flask app with Talisman, enabling automatic security headers 🚀\n\n---\n\n### 🧪 4. Run Unit Tests\n\nTest only your routes:\n\n```bash\nnosetests tests/test_routes.py\n```\n\n✅ Your **security headers test** should now **pass**\n❌ Other route tests may **fail** due to enforced HTTPS (we'll fix this in Exercise 5)\n\n---\n\n### 🧾 Results\n\nYou should observe:\n\n* ✅ `test_security_headers` **passes**\n* ❌ Other tests may fail due to 302 redirects or missing HTTPS handling\n\nNo worries — you're on the right path! 🎯\nWe'll adjust Talisman to disable forced HTTPS during tests in the next exercise.\n\n---\n\n### 💬 Commit Your Work\n\nOnce this step is complete, don't forget to save your progress:\n\n```bash\ngit commit -am \"Added security headers\"\n```\n\n---\n\n### 🔜 Next Up\n\n➡️ [Exercise 5: Disable Forced HTTPS](#) — Fix your broken tests so they pass during local testing.\n\n---\n\n🧠 *“Secure early, test always, and deploy with confidence.”*\n\n\n### 🌐 Exercise 7: Add CORS Policies\n\nNow 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!\n\n---\n\n### ✅ Objectives\n\n- ✍️ Write a unit test to validate CORS headers\n- 📦 Add `Flask-Cors` to your project\n- 🔧 Enable CORS in your Flask app\n- 🧪 Run tests and verify success\n- 📸 Capture evidence for screenshots\n\n---\n\n### 🧪 Step 1: Write the Test Case\n\nAdd the following to `tests/test_routes.py`:\n\n```python\ndef test_cors_security(self):\n    \"\"\"It should return a CORS header\"\"\"\n    response = self.client.get('/', environ_overrides=HTTPS_ENVIRON)\n    self.assertEqual(response.status_code, status.HTTP_200_OK)\n    # Check for the CORS header\n    self.assertEqual(response.headers.get('Access-Control-Allow-Origin'), '*')\n````\n\n🧨 This test will **fail initially** because Flask hasn't been configured with CORS yet.\n\n---\n\n### 📦 Step 2: Add Flask-Cors\n\nUpdate `requirements.txt`:\n\n```txt\nFlask-Cors\n```\n\nThen install the new dependency:\n\n```bash\npip install -r requirements.txt\n```\n\n---\n\n### 🔧 Step 3: Enable CORS in Your App\n\nEdit `service/__init__.py`:\n\n1. Import the library:\n\n   ```python\n   from flask_cors import CORS\n   ```\n\n2. Enable CORS **after Talisman is initialized**:\n\n   ```python\n   CORS(app)\n   ```\n\nThis will allow cross-origin requests from **any domain** (using `*` wildcard).\n\n---\n\n### ✅ Step 4: Run All Tests\n\nRun all tests again to verify:\n\n```bash\nnosetests\n```\n\n✅ Now **all tests should pass**, including your new `test_cors_security`.\n\n---\n\n### 💾 Step 5: Commit Your Changes\n\n```bash\ngit commit -am \"Added CORS headers\"\n```\n\n---\n\n### 🧪 Exercise 8: Validate CORS Headers\n\nLet’s manually confirm that your service returns CORS headers!\n\n---\n\n### 🚀 Step 1: Start the Service\n\nIn one terminal, run:\n\n```bash\nhoncho start\n```\n\n---\n\n## 🔍 Step 2: Use curl to Check Headers\n\nIn a second terminal:\n\n```bash\ncurl -I localhost:5000\n```\n\nYou should see output like this:\n\n```\nHTTP/1.1 302 FOUND\nServer: gunicorn\nDate: Thu, 13 Oct 2022 20:18:54 GMT\nContent-Type: text/html; charset=utf-8\nLocation: https://localhost:5000/\nAccess-Control-Allow-Origin: *\nPermissions-Policy: browsing-topics=()\nX-Frame-Options: SAMEORIGIN\nX-Content-Type-Options: nosniff\nContent-Security-Policy: default-src 'self'; object-src 'none'\nReferrer-Policy: strict-origin-when-cross-origin\n```\n\n✅ Confirm the presence of the `Access-Control-Allow-Origin: *` header.\n\n---\n\n### 🛑 Step 3: Stop the Service\n\nIn the first terminal, stop the service by pressing:\n\n```\nCtrl+C\n```\n\n(You might need to press it twice.)\n\n---\n\n### 🚀 Exercise 8 Continued: Make a Pull Request\n\nYou’ve now implemented and verified CORS headers — let’s commit, push, and make a PR.\n\n---\n\n### 🔄 Step 1: Confirm All Changes Are Committed\n\n```bash\ngit status\n```\n\nIf anything is uncommitted, do:\n\n```bash\ngit add .\ngit commit -m \"Added CORS headers\"\n```\n\n---\n\n### ☁️ Step 2: Push to Remote\n\n```bash\ngit push -u origin \u003cyour-branch-name\u003e\n```\n\n---\n\n### 🔁 Step 3: Open a Pull Request\n\n1. Go to your GitHub repo\n2. Click **Compare \u0026 pull request**\n3. Add a meaningful title and description\n4. Click **Create pull request**\n\n🎉 GitHub Actions will now run your tests automatically.\n\n---\n\n### 📸 Step 4: Take Screenshots for Submission\n\n* `security-code-done.jpg`: Screenshot of `service/__init__.py`\n* `security-headers-done.jpg`: Screenshot of terminal showing the curl output\n* `security-kanban-done.jpg`: Screenshot of your Kanban board with the story in **Done**\n\n---\n\n### 🏁 Conclusion\n\nWell done! 🎉 You've:\n\n* ✅ Enabled CORS policies using Flask-Cors\n* 🧪 Verified via tests and curl\n* 🚀 Opened a pull request with CI\n* 📸 Captured evidence for your submission\n\nYou're ready to move on to **Sprint 3** where you’ll tackle Docker, Kubernetes, and Tekton CD pipelines.\n\n---\n\n### 🚀 Sprint 3: Stories Overview \u0026 Actions\n\nWelcome 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.\n\n---\n\n### 🧩 Story 1: Containerize Your Microservice Using Docker\n\n📌 **Markdown Description:**\n\n```markdown\nTitle: Containerize your microservice using Docker  \n**As a** developer  \n**I need** to containerize my microservice using Docker  \n**So that** I can deploy it easily with all of its dependencies  \n\n### Assumptions  \n* Create a `Dockerfile` for repeatable builds  \n* Use a `Python:3.9-slim` image as the base  \n* It must install all of the Python requirements  \n* It should not run as `root`  \n* It should use the `gunicorn` wsgi server as an entry point  \n\n### Acceptance Criteria  \nGiven the Docker image named accounts has been created  \nWhen I use `docker run accounts`  \nThen I should see the accounts service running in Docker  \n````\n\n🏷 **Label:** Technical Debt\n📏 **Estimate:** Small (3) or Medium (5)\n📆 **Sprint:** Assign to Sprint 3\n📦 **Rank:** Top of Sprint Backlog ✅\n\n---\n\n## ☸️ Story 2: Deploy Your Docker Image to Kubernetes\n\n📌 **Markdown Description:**\n\n```markdown\nTitle: Deploy your Docker image to Kubernetes  \n**As a** service provider  \n**I need** my service to run on Kubernetes  \n**So that** I can easily scale and manage the service  \n\n### Assumptions  \n* Kubernetes manifests will be created in yaml format  \n* These manifests could be useful to create a CD pipeline  \n* The actual deployment will be to OpenShift  \n\n### Acceptance Criteria  \nGiven the Kubernetes manifests have been created  \nWhen I use the oc command to apply the manifests  \nThen the service should be deployed and run in Kubernetes  \n```\n\n🏷 **Label:** Enhancement\n📏 **Estimate:** Medium (5) or Large (8)\n📆 **Sprint:** Assign to Sprint 3\n📦 **Rank:** 2nd in Sprint Backlog ⬇️\n\n---\n\n## 🔁 Story 3: Create a CD Pipeline to Automate Deployment to Kubernetes\n\n📌 **Markdown Description:**\n\n```markdown\nTitle: Create a CD pipeline to automate deployment to Kubernetes  \n**As a** developer  \n**I need** to create a CD pipeline to automate deployment to Kubernetes  \n**So that** the developers are not wasting their time doing it manually  \n\n### Assumptions  \n* Use Tekton to define the pipeline  \n* It should clone, lint, test, build, and deploy the service  \n* Deployment should be to OpenShift  \n* It can use a manual trigger for this MVP  \n\n### Acceptance Criteria  \nGiven the CD pipeline has been created  \nWhen I trigger the pipeline run  \nThen I should see the accounts service deployed to OpenShift  \n```\n\n🏷 **Label:** Enhancement or Technical Debt\n📏 **Estimate:** Large (8) or Extra Large (13)\n📆 **Sprint:** Assign to Sprint 3\n📦 **Rank:** 3rd in Sprint Backlog ⬇️\n\n---\n\n## 📋 Final Checklist\n\n✅ Add the 3 stories above to your **Kanban board**\n➡️ Move from **Product Backlog** → **Sprint Backlog**\n🏷 Apply correct **labels** and **estimates**\n📌 Rank them in order:\n\n1. 🐳 Containerize Microservice\n2. ☸️ Deploy to Kubernetes\n3. 🔁 Create CD Pipeline\n\n🖼 **Take a screenshot of your Kanban board** with Sprint 3 planned\n📸 Save it as: `sprint3-plan.jpg` or `sprint3-plan.png`\n\n---\n\n## 🧠 Next Steps\n\nOnce Sprint 3 is planned, start the **\"Containerize\"** story first:\n\n* Create your `Dockerfile` 🐳\n* Build \u0026 test the image locally\n* Commit your changes and make a PR\n---\n\n### 🔐 Important Security Information \u0026 Setup Guide\n\nWelcome 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.\n\n---\n\n### ⚠️ Environment Notice\n\n🧪 The **lab environment is ephemeral**, meaning it can be **deleted at any time**.  \n🔄 You must **push your work frequently** to your own GitHub repository so you can recreate it later.  \n🚫 This is a **shared environment** — **do NOT store** personal info, passwords, or tokens here.\n\n---\n\n### 👤 GitHub Personal Access Token (PAT)\n\nIf you haven't generated a GitHub **Personal Access Token**, do it now.\n\n✅ It should have:\n- **repo** and **write** permissions  \n- Expiration set to **60 days**\n\n🔐 Use this token **in place of your GitHub password** when pushing code from the Cloud IDE.\n\n---\n\n### 💾 Save Your Work — Always Push to GitHub\n\nUse these Git commands to commit and push changes:\n\n```bash\ngit add .\ngit commit -m \"Meaningful message\"\ngit push origin \u003cyour-branch\u003e\n````\n\n---\n\n### 💻 Initialize Development Environment\n\nEach time the lab environment is recreated, follow these steps to initialize:\n\n### 🧰 Step-by-Step Setup\n\n```bash\n# 1. Set your GitHub username as an environment variable\nexport GITHUB_ACCOUNT={your_github_account}\n\n# 2. Clone your project repository\ngit clone https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\n\n# 3. Move into the project directory\ncd devops-capstone-project\n\n# 4. Run the setup script\nbash ./bin/setup.sh\n\n# ✅ You should see: capstone_setup_complete\n\n# 5. Exit the terminal\nexit\n```\n\n---\n\n### ✅ Validate the Environment\n\nOpen a **new terminal** to activate the Python virtual environment and run:\n\n```bash\nwhich python\npython --version\n```\n\n✔️ You should see a path to your virtualenv and a Python 3.9.x version.\n\n---\n\n## 🧾 Screenshot Instructions\n\n📸 You’ll need to **take screenshots** during the lab for quizzes or submissions.\nEnsure screenshots are saved as `.jpg` or `.png`.\n\n### 📷 Screenshot Shortcuts:\n\n**Mac:**\n\n* Full screen: `Shift + Command + 3`\n* Selection: `Shift + Command + 4`\n\n**Windows:**\n\n* Active window: `Alt + Print Screen` → paste into editor → save image\n\n---\n\n### 👨🏿‍💻 Exercise 1: Pick Up the First Story\n\nYour task is to start your first development story.\n\n### ✅ Steps:\n\n1. Go to your **Kanban board**\n2. Find the top story in the **Sprint Backlog**:\n   **📝 \"Containerize your microservice using Docker\"**\n3. Move it to **In Progress**\n4. Assign it to yourself\n5. Open and read the story content\n\n### 📜 Story Overview:\n\n```\nAs a developer  \nI need to containerize my microservice using Docker  \nSo that I can deploy it easily with all of its dependencies  \n\nAssumptions:\n- Create a Dockerfile for repeatable builds  \n- Use a Python:3.9-slim image as the base  \n- It must install all Python requirements  \n- It should not run as root  \n- It should use gunicorn as the WSGI entry point  \n\nAcceptance Criteria:\nGiven the Docker image named accounts has been created  \nWhen I use docker run accounts  \nThen I should see the accounts service running in Docker\n```\n\n✅ You’re now ready to start implementing this story!\n\n---\n\n# 🐳 Exercise 2: Create a Dockerfile\n\nWelcome 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. 💪\n\n---\n\n## 🧠 Story Context\n\n**Story:** `Containerize your microservice using Docker`\n\n🎯 **Goal:**  \n\u003e As a developer, I need to containerize my microservice using Docker so that I can deploy it easily with all its dependencies.\n\n---\n\n## ✅ Your Tasks\n\n### 🗂️ 1. Change into your project directory\n```bash\ncd devops-capstone-project\n````\n\n---\n\n### 🌱 2. Create a new branch for Docker work\n\n```bash\ngit checkout -b add-docker\n```\n\n---\n\n### 🧪 3. Run tests to verify current functionality\n\n```bash\nnosetests\n```\n\n\u003e ✅ All tests should pass before continuing. Fix any issues if needed.\n\n---\n\n### 📝 4. Create your Dockerfile\n\nIn the **root** of your project, create a new file named `Dockerfile` with the following contents:\n\n```Dockerfile\n# 🔹 Use a minimal Python 3.9 image\nFROM python:3.9-slim\n\n# 📂 Set the working directory and install dependencies\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\n# 📁 Copy the application code\nCOPY service/ ./service/\n\n# 👤 Switch to a non-root user for better security\nRUN useradd --uid 1000 theia \u0026\u0026 chown -R theia /app\nUSER theia\n\n# 🚀 Run the app using Gunicorn on port 8080\nEXPOSE 8080\nCMD [\"gunicorn\", \"--bind=0.0.0.0:8080\", \"--log-level=info\", \"service:app\"]\n```\n\n\u003e 💡 This setup ensures repeatable builds, installs Python requirements, avoids root user risks, and runs via `gunicorn`.\n\n---\n\n## 🎉 Result\n\nIf you've followed the steps above, your `Dockerfile` should look like this:\n\n```Dockerfile\nFROM python:3.9-slim\n\nWORKDIR /app\nCOPY requirements.txt .\nRUN pip install --no-cache-dir -r requirements.txt\n\nCOPY service/ ./service/\n\nRUN useradd --uid 1000 theia \u0026\u0026 chown -R theia /app\nUSER theia\n\nEXPOSE 8080\nCMD [\"gunicorn\", \"--bind=0.0.0.0:8080\", \"--log-level=info\", \"service:app\"]\n```\n\n---\n\n## 📸 Evidence\n\nTake a screenshot of your completed `Dockerfile` and the passing test output.\n\n✅ Save it as: `dockerfile-setup.jpg` or `dockerfile-setup.png`\n\n---\n\n## 🚀 Next Up\n\nReady for **Exercise 3: Create a Docker Image**?\nLet’s build and run your containerized microservice! 🏗️🔧\n\n---\n\n### 🔀 Exercise 4: Make a Pull Request\n\nYou're almost done containerizing your microservice! 🚀  \nNow let's integrate your work into the main branch by creating a pull request (PR) on GitHub.\n\n---\n\n## 📝 Step-by-Step Instructions\n\n### 🧩 1. Check Your Git Status\n```bash\ngit status\n````\n\nMake sure your changes are committed. If not, continue below. ✅\n\n---\n\n### ➕ 2. Add the `Dockerfile` to Staging\n\n```bash\ngit add Dockerfile\n```\n\n---\n\n### 💬 3. Commit Your Changes\n\n```bash\ngit commit -m \"Added docker support\"\n```\n\n---\n\n### ☁️ 4. Push Your Branch to GitHub\n\nFirst-time Git setup (if needed):\n\n```bash\ngit config --local user.email \"you@example.com\"\ngit config --local user.name \"Your Name\"\n```\n\nThen push:\n\n```bash\ngit push --set-upstream origin add-docker\n```\n\n\u003e 🛡️ Use your GitHub Personal Access Token when prompted for a password.\n\n---\n\n### 🔃 5. Create a Pull Request (PR)\n\n1. Go to your repository on GitHub.\n2. You should see a banner prompting you to **Compare \u0026 Pull Request**.\n3. Review the changes and submit the PR.\n4. GitHub Actions 🤖 will automatically run your tests.\n5. ✅ Once the tests pass, **Merge** the pull request into the main branch.\n\n---\n\n### 🗃️ 6. Update Your Kanban Board\n\nMove the story “Add Docker Support” to the ✅ **Done** column on your Kanban board.\n\n📸 **Evidence**: Take a screenshot of the Done column.\n💾 Save as: `kube-docker-done.jpg` or `kube-docker-done.png`\n\n---\n\n### 🧹 7. Clean Up Your Local Branch\n\n```bash\ngit checkout main\ngit pull\ngit branch -d add-docker\n```\n\n🧼 This deletes the old working branch after merging.\n\n---\n\n## ⏭️ What's Next?\n\n🎯 **Exercise 5: Pick Up the Next Story**\nHead to your Kanban board and take the next story from the Sprint Backlog:\n\n### ✨ \"Deploy your Docker image to Kubernetes\"\n\n📦 Move the story to **In Progress**, assign it to yourself, and read through it carefully.\nYou’ll create Kubernetes manifests, deploy to OpenShift, and expose your microservice! 🚀\n\n---\n\n# ✅ Final Evidence Collection 📸\n\nBefore wrapping up the lab, you’ll need to collect and submit the following proof of your work.\n\n---\n\n## 🔗 1. Save the Dockerfile URL\n\n📁 Navigate to your Dockerfile on GitHub.  \n🌐 Open the file in your browser.  \n🔗 Copy the full URL from the address bar.\n\n📝 Example:\n```\n\n[https://github.com/](https://github.com/)\u003cyour-username\u003e/devops-capstone-project/blob/main/Dockerfile\n\n````\n\n💡 You’ll submit this link as part of your final deliverables.\n\n---\n\n## 🐳 2. Capture Docker Image List\n\n📦 Run the following command in your terminal:\n```bash\ndocker image ls\n````\n\n📸 Take a screenshot of the output. Make sure your image (e.g., `accounts:1`) is clearly visible.\n\n💾 Save the screenshot as:\n\n```\nkube-images.jpg\n```\n\nor\n\n```\nkube-images.png\n```\n\n---\n\n## ☸️ 3. Capture Kubernetes Deployment\n\n📊 Check that your `accounts` app is deployed and running:\n\n```bash\noc get all -l app=accounts\n```\n\n📸 Take a screenshot of the full output.\nMake sure it shows:\n\n* 🧱 Deployment\n* 🔁 ReplicaSet\n* 🐳 Pod\n* 🌐 Service\n* 🛣️ Route (if applicable)\n\n💾 Save the screenshot as:\n\n```\nkube-deploy-accounts.jpg\n```\n\nor\n\n```\nkube-deploy-accounts.png\n```\n\n---\n\n## 🎓 Conclusion\n\n🎉 **Congratulations!** You’ve completed a major step in your DevOps journey.\n\n🚀 What you accomplished:\n\n* ✅ Built a Docker image from a secure, reusable Dockerfile.\n* ✅ Deployed your microservice to an OpenShift Kubernetes cluster.\n* ✅ Authored Kubernetes manifests for repeatable deployments.\n* ✅ Practiced modern CI/CD workflows using GitHub and pull requests.\n* ✅ Prepared for automation via Tekton pipelines and CD tooling.\n\n---\n\n## 🛡️ Security Reminder\n\n\u003e ⚠️ Your cloud environment is **ephemeral** and **shared**:\n\n* Always push your work to GitHub so you can restore it later.\n* **Never** store sensitive data like tokens or passwords in this environment.\n* Use a **GitHub Personal Access Token (PAT)** when prompted for a password in Git.\n\n---\n\n## 📝 Screenshot Tips\n\n📷 You’ll need screenshots for quizzes or peer review:\n\n### Mac\n\n* Capture full screen: `Shift + Command + 3`\n* Capture selection: `Shift + Command + 4`\n\n### Windows\n\n* Active window: `Alt + Print Screen`\n* Paste into an image editor and save as `.jpg` or `.png`\n\n🧾 Ensure your screenshots are clear, relevant, and saved with correct filenames.\n\n---\n\nLet me know if you need help with:\n\n* ✅ Tekton CD pipeline setup\n* ✅ YAML templates\n* ✅ Submitting your final lab work\n* ✅ Peer review tips\n\n🎯 You're on track to mastering modern DevOps workflows!\n\n---\n\n# 🚀 Tekton CD Pipeline Lab Progress Guide\n\nYou'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**.\n\n---\n\n## 🔐 Security \u0026 Environment Reminders\n\n⚠️ **Ephemeral Environment**  \nYour Cloud IDE and OpenShift environment can be reset at any time. Always:\n\n- ✅ Push code to **GitHub** frequently\n- ❌ Never store **credentials**, **tokens**, or **personal info** in the lab environment\n- 🔑 Use a **GitHub Personal Access Token (PAT)** when pushing code\n\n📌 PAT requirements:\n- Must have **repo** and **write** access\n- Should **expire in 60 days or less**\n\n---\n\n## 🖼️ Screenshot Instructions \u0026 Naming Convention\n\nYou'll be asked to provide screenshots as **evidence** for your work. Keep file names consistent:\n\n| Step                        | Filename Suggestion          |\n|----------------------------|------------------------------|\n| Tekton pipeline run        | `cd-pipeline-run.jpg`        |\n| OpenShift deployment view  | `tekton-deploy.jpg`          |\n| Kanban board story done    | `cd-pipeline-done.jpg`       |\n\n### 📷 Screenshot Shortcuts\n\n**Mac:**\n- 🖥️ Full screen: `Shift + Cmd + 3`\n- 🔲 Select area: `Shift + Cmd + 4`\n\n**Windows:**\n- 🪟 Active window: `Alt + Print Screen`\n- 🖌️ Paste in Paint or editor, then **save as `.jpg` or `.png`**\n\n---\n\n## 🧪 Development Environment Setup\n\nYour lab environment may be reset. Use this process every time to reinitialize:\n\n### 1. 🖥️ Open a New Terminal\n\n```bash\nexport GITHUB_ACCOUNT=your_github_username\ngit clone https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\ncd devops-capstone-project\nbash ./bin/setup.sh\n````\n\nYou should see:\n\n```text\ncapstone_setup_complete\n```\n\n### 2. ❌ Exit the Terminal\n\n```bash\nexit\n```\n\nThis step ensures your virtual environment will activate correctly.\n\n### 3. 🔄 Open a New Terminal\n\nThen validate your setup:\n\n```bash\nwhich python\npython --version\n```\n\nExpected Output:\n\n* `/home/theia/.venv/bin/python`\n* `Python 3.9.x`\n\nYou're now fully set up to continue!\n\n---\n\n## 📌 Your Current Story: Create a CD Pipeline\n\n📝 **Title**: *Create a CD pipeline to automate deployment to Kubernetes*\n\n**As a** developer\n**I need** to create a CD pipeline\n**So that** deployments aren’t manual anymore\n\n### 🧠 Assumptions:\n\n* Use **Tekton** to define the pipeline\n* Pipeline stages: **clone → lint → test → build → deploy**\n* Deployment should be to **OpenShift**\n* Can be triggered manually\n\n### ✅ Acceptance Criteria:\n\n1. CD pipeline has been created\n2. When triggered, it runs the full pipeline\n3. It deploys the `accounts` service to OpenShift\n\n---\n\n## 🛠️ Next Steps\n\n* 🔁 Finish creating Tekton `Task` and `Pipeline` YAMLs\n* 📂 Apply them using `oc apply -f`\n* ✅ Trigger a pipeline run and observe the deployment\n* 📸 Capture the required screenshots\n* ✅ Move your Kanban story to **Done**\n\n---\n\n\n# 🛠️ Tekton CD Pipeline Lab: What's Next?\n\nWelcome 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`.\n\n---\n\n## 📁 Create the CD Pipeline Directory Structure\n\nRun the following to organize your Tekton files:\n\n```bash\nmkdir -p tekton/cd-pipeline\ncd tekton/cd-pipeline\n````\n\nAll your Tasks, Pipeline, PipelineRun, and Secrets will live here.\n\n---\n\n## 🧪 Exercise 2: Overview \u0026 Setup\n\nYou're creating a CD pipeline that will:\n\n* ✅ Clone your repo\n* ✅ Lint your code\n* ✅ Run unit tests\n* ✅ Build a Docker image\n* ✅ Deploy to OpenShift\n\n---\n\n### 🔀 Step 1: Switch to a New Git Branch\n\n```bash\ncd devops-capstone-project\ngit checkout -b cd-pipeline\n```\n\n---\n\n### 🧪 Step 2: Run Unit Tests\n\n```bash\nnosetests\n```\n\nFix any failing tests before proceeding.\n\n---\n\n### 📦 Step 3: Apply Existing Pipeline Files\n\n```bash\noc create -f tekton/pvc.yaml          # Create workspace PVC\noc apply -f tekton/tasks.yaml         # Apply starter tasks\noc apply -f tekton/pipeline.yaml      # Apply initial pipeline\n```\n\n---\n\n### ⬇️ Step 4: Install Required Tekton Tasks\n\n```bash\ntkn hub install task git-clone\n```\n\n📌 If that fails due to a version mismatch:\n\n```bash\nkubectl apply -f https://raw.githubusercontent.com/tektoncd/catalog/main/task/git-clone/0.9/git-clone.yaml\n```\n\n---\n\n### ▶️ Step 5: Run the Initial Pipeline\n\n```bash\ntkn pipeline start cd-pipeline \\\n  -p repo-url=\"https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\" \\\n  -p branch=\"main\" \\\n  -w name=pipeline-workspace,claimName=pipelinerun-pvc \\\n  -s pipeline \\\n  --showlog\n```\n\n---\n\n### ✅ Step 6: Verify Pipeline Run\n\n```bash\ntkn pipelinerun ls                # Check STATUS column\ntkn pipelinerun logs --last       # View logs of the latest run\n```\n\n---\n\n## 🧹 Exercise 3: Create the Lint Task with flake8\n\nYou're going to use `flake8` to lint your code using an official task from Tekton Hub.\n\n---\n\n### 🧰 Step 1: Install the flake8 Task\n\n```bash\ntkn hub install task flake8\n```\n\n---\n\n### 🛠️ Step 2: Add Lint Task to Your Pipeline\n\nEdit your `tekton/pipeline.yaml` file and add the following **lint task** definition after the `clone` task:\n\n```yaml\n- name: lint\n  workspaces:\n    - name: source\n      workspace: pipeline-workspace\n  taskRef:\n    name: flake8\n  params:\n    - name: image\n      value: \"python:3.9-slim\"\n    - name: args\n      value: [\"--count\",\"--max-complexity=10\",\"--max-line-length=127\",\"--statistics\"]\n  runAfter:\n    - clone\n```\n\n---\n\n### 🚀 Step 3: Apply the Updated Pipeline\n\n```bash\noc apply -f tekton/pipeline.yaml\n```\n\n---\n\n### ▶️ Step 4: Re-run the Pipeline with Lint Task\n\n```bash\ntkn pipeline start cd-pipeline \\\n  -p repo-url=\"https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\" \\\n  -p branch=\"main\" \\\n  -w name=pipeline-workspace,claimName=pipelinerun-pvc \\\n  -s pipeline \\\n  --showlog\n```\n\n---\n\n### ✅ Step 5: Confirm It Worked\n\nUse:\n\n```bash\ntkn pipelinerun ls\n```\n\nCheck `STATUS` is `Succeeded`.\n\nTo see logs:\n\n```bash\ntkn pipelinerun logs --last\n```\n\n---\n\n## 💾 Step 6: Commit \u0026 Push Your Work\n\nBecause the Cloud IDE is **ephemeral**, **always** commit and push after any milestone:\n\n```bash\ngit add tekton/pipeline.yaml\ngit commit -m \"Added lint task to Tekton pipeline\"\ngit push --set-upstream origin cd-pipeline\n```\n\n---\n\n## 📌 Up Next\n\nYou’re ready to create the **test** task to run your Python unit tests using `nosetests`.\n\n---\n\n\n# 🧪 Tekton CD Pipeline: Lint \u0026 Test Tasks\n\nWelcome 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`.\n\n---\n\n## ✅ Exercise 3: Add the Lint Task\n\n### 🧰 Step 1: Install the `flake8` Task from Tekton Hub\n\n```bash\ntkn hub install task flake8\n````\n\n---\n\n### ✏️ Step 2: Edit `tekton/pipeline.yaml`\n\nAdd this `lint` task after the `clone` step:\n\n```yaml\n- name: lint\n  workspaces:\n    - name: source\n      workspace: pipeline-workspace\n  taskRef:\n    name: flake8\n  params:\n    - name: image\n      value: \"python:3.9-slim\"\n    - name: args\n      value: [\"--count\", \"--max-complexity=10\", \"--max-line-length=127\", \"--statistics\"]\n  runAfter:\n    - clone\n```\n\n📝 Key Points:\n\n* Use `source` as the workspace name (required by `flake8`).\n* Run after the `clone` task.\n* Pass `flake8` arguments via `args`.\n\n---\n\n### 🚀 Step 3: Apply the Updated Pipeline\n\n```bash\noc apply -f tekton/pipeline.yaml\n```\n\n---\n\n### ▶️ Step 4: Start the Pipeline and Watch Logs\n\n```bash\ntkn pipeline start cd-pipeline \\\n    -p repo-url=\"https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\" \\\n    -p branch=\"main\" \\\n    -w name=pipeline-workspace,claimName=pipelinerun-pvc \\\n    -s pipeline \\\n    --showlog\n```\n\n---\n\n### 🔎 Step 5: Check Pipeline Run Status\n\n```bash\ntkn pipelinerun ls\ntkn pipelinerun logs --last\n```\n\n---\n\n### 💾 Step 6: Commit Your Work\n\n```bash\ngit commit -am 'added lint task'\ngit push --set-upstream origin cd-pipeline\n```\n\n---\n\n## 🧪 Exercise 4: Create the Test Task (`nose`)\n\n### ✏️ Step 1: Add a `nose` Task to `tekton/tasks.yaml`\n\n```yaml\n---\napiVersion: tekton.dev/v1beta1\nkind: Task\nmetadata:\n  name: nose\nspec:\n  description: This task will run nosetests on the provided input.\n  workspaces:\n    - name: source\n  params:\n    - name: args\n      description: Arguments to pass to nose\n      type: string\n      default: \"-v\"\n    - name: database_uri\n      description: Database connection string\n      type: string\n      default: \"sqlite:///test.db\"\n  steps:\n    - name: nosetests\n      image: python:3.9-slim\n      workingDir: $(workspaces.source.path)\n      env:\n        - name: DATABASE_URI\n          value: $(params.database_uri)\n      script: |\n        #!/bin/bash\n        set -e\n        echo \"***** Installing dependencies *****\"\n        python -m pip install --upgrade pip wheel\n        pip install -qr requirements.txt\n        echo \"***** Running nosetests with: $(params.args)\"\n        nosetests $(params.args)\n```\n\n---\n\n### 🚀 Step 2: Apply the Task\n\n```bash\noc apply -f tekton/tasks.yaml\n```\n\n---\n\n### 💾 Step 3: Commit Your Changes\n\n```bash\ngit commit -am 'added nose task'\ngit push\n```\n\n---\n\n## 🧪 Exercise 5: Add the Test Task to the Pipeline\n\n### ✏️ Step 1: Update `tekton/pipeline.yaml`\n\nAdd the `tests` task using the `nose` task:\n\n```yaml\n- name: tests\n  workspaces:\n    - name: source\n      workspace: pipeline-workspace\n  taskRef:\n    name: nose\n  params:\n    - name: database_uri\n      value: \"sqlite:///test.db\"\n    - name: args\n      value: \"-v --with-spec --spec-color\"\n  runAfter:\n    - clone\n```\n\n🧠 Note: This runs in **parallel** with `lint` since both run after `clone`.\n\n---\n\n### 🚀 Step 2: Apply the Pipeline\n\n```bash\noc apply -f tekton/pipeline.yaml\n```\n\n---\n\n### ▶️ Step 3: Start and Watch the Pipeline\n\n```bash\ntkn pipeline start cd-pipeline \\\n    -p repo-url=\"https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\" \\\n    -p branch=\"main\" \\\n    -w name=pipeline-workspace,claimName=pipelinerun-pvc \\\n    -s pipeline \\\n    --showlog\n```\n\n---\n\n### ✅ Step 4: Confirm Execution\n\n```bash\ntkn pipelinerun ls\ntkn pipelinerun logs --last\n```\n\n---\n\n### 💾 Step 5: Commit the Pipeline Update\n\n```bash\ngit commit -am 'added test pipeline task'\ngit push\n```\n\n---\n\n### 🎯 You're All Set!\n\nYou've now added both **linting** and **testing** steps to your Tekton pipeline. Next up: **build** and **deploy**. \n\n---\n\n\n### 🚀 DevOps Capstone CI/CD Pipeline with Tekton\n\nThis guide walks you through adding essential tasks to your `pipeline.yaml` for a full CI/CD pipeline using **Tekton** on **OpenShift**.\n\n---\n\n### ✅ Task 1: Add the `tests` Task 🧪\n\nAdd this block under `spec.tasks:` in your `pipeline.yaml`:\n\n```yaml\n- name: tests\n  workspaces:\n    - name: source\n      workspace: pipeline-workspace\n  taskRef:\n    name: nose\n  params:\n    - name: database_uri\n      value: \"sqlite:///test.db\"\n    - name: args\n      value: \"-v --with-spec --spec-color\"\n  runAfter:\n    - clone\n````\n\n### 💾 Apply the changes:\n\n```bash\noc apply -f tekton/pipeline.yaml\n```\n\n### 📤 Commit and Push:\n\n```bash\ngit commit -am 'added test pipeline task'\ngit push\n```\n\n---\n\n## ✅ Task 2: Add the `build` Task 🏗️\n\nUpdate `spec.params` at the top of your pipeline:\n\n```yaml\nspec:\n  params:\n    - name: repo-url\n    - name: branch\n      default: main\n    - name: build-image\n```\n\nAdd the `build` task under `spec.tasks:`:\n\n```yaml\n- name: build\n  workspaces:\n    - name: source\n      workspace: pipeline-workspace\n  taskRef:\n    name: buildah\n    kind: ClusterTask\n  params:\n    - name: IMAGE\n      value: \"$(params.build-image)\"\n  runAfter:\n    - tests\n    - lint\n```\n\n### 💾 Apply the changes:\n\n```bash\noc apply -f tekton/pipeline.yaml\n```\n\n### 📤 Commit and Push:\n\n```bash\ngit commit -am 'added build task'\ngit push\n```\n\n---\n\n## ✅ Task 3: Add the `deploy` Task 🚢\n\nAdd the following task under `spec.tasks:`:\n\n```yaml\n- name: deploy\n  workspaces:\n    - name: manifest-dir\n      workspace: pipeline-workspace\n  taskRef:\n    name: openshift-client\n    kind: ClusterTask\n  params:\n    - name: SCRIPT\n      value: |\n        echo \"Updating manifest...\"\n        sed -i \"s|IMAGE_NAME_HERE|$(params.build-image)|g\" deploy/deployment.yaml\n        cat deploy/deployment.yaml\n        echo \"Deploying to OpenShift...\"\n        oc apply -f deploy/\n        oc get pods -l app=accounts\n  runAfter:\n    - build\n```\n\n### 🛠️ Modify `deploy/deployment.yaml`:\n\nMake sure the `image:` tag contains this placeholder:\n\n```yaml\nimage: IMAGE_NAME_HERE\n```\n\n### 💾 Apply the changes:\n\n```bash\noc apply -f tekton/pipeline.yaml\n```\n\n### 📤 Commit and Push:\n\n```bash\ngit commit -am 'added deploy task'\ngit push\n```\n\n---\n\n## ▶️ Run the Pipeline 🏁\n\nMake sure required environment variables are set:\n\n```bash\nexport GITHUB_ACCOUNT=your_github_username\nexport SN_ICR_NAMESPACE=your_project_namespace\n```\n\nStart the pipeline:\n\n```bash\ntkn pipeline start cd-pipeline \\\n  -p repo-url=\"https://github.com/$GITHUB_ACCOUNT/devops-capstone-project.git\" \\\n  -p branch=main \\\n  -p build-image=image-registry.openshift-image-registry.svc:5000/$SN_ICR_NAMESPACE/accounts:1 \\\n  -w name=pipeline-workspace,claimName=pipelinerun-pvc \\\n  -s pipeline \\\n  --showlog\n```\n\n📝 **Note**: `tests` and `lint` tasks run in parallel, so their logs may be intermixed.\n\n---\n\n## 🧠 Troubleshooting Tips\n\n* Make sure your PostgreSQL pod is running:\n\n  ```bash\n  oc get pods\n  ```\n* If `postgresql` service is missing:\n\n  ```bash\n  oc new-app postgresql-ephemeral\n  ```\n\n---\n\n## 🏁 Summary\n\n✅ Tests\n✅ Build\n✅ Deploy\n✅ All tasks added and integrated into Tekton pipeline!\n\n\n---\n# 🔐 Lab: Add Security to Your RESTful Service\n\nWelcome 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.\n\n---\n\n## 🎯 Objectives\n\n✅ Understand common REST API security patterns  \n✅ Add authentication (e.g., API Key, JWT, or Basic Auth)  \n✅ Restrict access using role-based authorization  \n✅ Test the API to ensure it's secure  \n\n---\n\n## 🔧 Prerequisites\n\nBefore starting, make sure you have:\n\n- A working RESTful API (e.g., Flask, FastAPI, Express.js)\n- Experience from the TDD and API development labs\n- A tool to test your API (like `curl`, `Postman`, or Swagger UI)\n\n---\n\n## 🛠️ Step-by-Step Instructions\n\n### 1️⃣ Choose an Authentication Strategy\n\nPick the security method that best fits your app:\n\n🔐 **Basic Authentication** – Quick for local testing  \n🔐 **API Keys** – Simple but less secure  \n🔐 **JWT (JSON Web Token)** – Recommended for stateless APIs  \n🔐 **OAuth 2.0** – For more advanced use cases with external identity providers  \n\n---\n\n### 2️⃣ Implement Authentication\n\nHere’s an example using **JWT in Flask**:\n\n```python\nfrom flask import request, jsonify\nimport jwt\n\nSECRET = \"supersecretkey\"\n\ndef token_required(f):\n    def wrapper(*args, **kwargs):\n        token = request.headers.get(\"Authorization\", None)\n        if not token:\n            return jsonify({\"message\": \"Missing token\"}), 401\n        try:\n            jwt.decode(token, SECRET, algorithms=[\"HS256\"])\n        except jwt.ExpiredSignatureError:\n            return jsonify({\"message\": \"Token expired\"}), 401\n        except jwt.InvalidTokenError:\n            return jsonify({\"message\": \"Invalid token\"}), 401\n        return f(*args, **kwargs)\n    return wrapper\n````\n\nApply it to your endpoints:\n\n```python\n@app.route(\"/secure-data\")\n@token_required\ndef secure_data():\n    return jsonify({\"message\": \"You are authorized!\"})\n```\n\n---\n\n### 3️⃣ Add Authorization (Role-Based Access Control)\n\nIf your API has multiple user roles (e.g., admin, user), check the user's role inside the JWT payload:\n\n```python\ndata = jwt.decode(token, SECRET, algorithms=[\"HS256\"])\nif data.get(\"role\") != \"admin\":\n    return jsonify({\"message\": \"Access denied\"}), 403\n```\n\n---\n\n### 4️⃣ Test Your Secured API\n\nUse Postman or `curl` to try different requests:\n\n❌ Without Token:\n\n```bash\ncurl http://localhost:5000/secure-data\n```\n\n✅ With Valid Token:\n\n```bash\ncurl -H \"Authorization: your_jwt_token_here\" http://localhost:5000/secure-data\n```\n\n🧪 Also test:\n\n* Expired or malformed tokens\n* Valid tokens with insufficient privileges\n\n---\n\n## 🧼 Step 5: Secure Your Secrets\n\n🔒 Use `.env` or environment variables to store secrets\n🚫 **Never** hardcode tokens or passwords in your source code\n✅ Use HTTPS for all production traffic\n\n---\n\n## 📑 Evidence to Submit\n\n* ✅ Screenshot of your API rejecting unauthorized requests\n* ✅ Screenshot of successful authorized requests\n* ✅ (Optional) Your API security code (e.g., `auth.py`)\n* ✅ `curl` or Postman output showing denied vs. allowed access\n\n---\n\n## 💡 Tips\n\n* Use `pyjwt`, `fastapi-jwt-auth`, or `flask-jwt-extended` for easier JWT handling\n* Regularly rotate your secrets or API keys\n* Always validate and sanitize user input — even authenticated ones!\n\n---\n\n## 🎉 Congratulations!\n\nYou've now secured your RESTful service with proper **authentication** and **authorization**! This is a huge step in building **safe**, **production-grade** APIs.\n\nFeel free to ask if you need help with JWT setup, token generation, or securing secrets! 🔐💬\n\n\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillie-conway%2Fdevops-capstone-project","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwillie-conway%2Fdevops-capstone-project","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwillie-conway%2Fdevops-capstone-project/lists"}