Compare commits

...

80 Commits

Author SHA1 Message Date
dfcea79f84 ci: add repo secret
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/manual/build Pipeline was successful
2025-08-23 10:27:36 +03:00
06fbf2e4de ci: try to fix registry
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-23 10:04:59 +03:00
b453dd3b9e ci: update image registry
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-23 09:58:49 +03:00
46dc855a06 ci: update the trivy db
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-23 09:38:26 +03:00
623a909e43 ci: fix trivy image typo
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-16 19:51:23 +03:00
9a390bb831 ci: update trivy image
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-16 19:49:22 +03:00
0e3ae13474 ci: update workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-16 19:45:51 +03:00
dba043a4af update woodpecker workflow
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2025-08-04 21:50:04 +03:00
8a2536dd5d update workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-04 21:23:23 +03:00
1d803dbe64 update workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-04 21:18:14 +03:00
99eb2193cd ci: add push to registry step to workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/manual/build Pipeline failed
2025-08-03 21:48:00 +03:00
ea5e6f2f06 use buildx for building
Some checks failed
ci/woodpecker/manual/build Pipeline failed
2025-08-03 21:20:25 +03:00
fc3bf98fbd troubleshoot woodpecker worflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-03 21:17:29 +03:00
1c04f377bb troubleshoot woodpecker workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-03 21:12:59 +03:00
d0a576e76f troubleshoot docker push
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-03 21:09:00 +03:00
345bc2c08a update woodpecker workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-08-03 21:01:55 +03:00
9d2c2b7bcd check push in workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-07-29 19:37:30 +03:00
2a25da1e77 fix workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-07-29 19:21:36 +03:00
2209f36943 fix registry address for CI
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-07-29 19:13:07 +03:00
4c6bb29f9c fix woodpecker workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-07-29 18:59:29 +03:00
173b2778ce updated woodpecker workflow
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-07-28 20:36:46 +03:00
f28607e405 fix trivyignore
Some checks failed
ci/woodpecker/push/build Pipeline failed
2025-07-28 19:42:13 +03:00
dc3b4628ec deprecate gitea workflow 2025-07-28 19:31:29 +03:00
12d7e880d9 added trivyignore file
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 6m41s
ci/woodpecker/push/build Pipeline failed
Build the portfolio website / build-and-release-image (push) Successful in 4m19s
2025-07-28 19:30:51 +03:00
c427234a45 add trivyignore file
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 2m18s
ci/woodpecker/push/build Pipeline failed
Build the portfolio website / build-and-release-image (push) Has been cancelled
2025-07-28 19:21:02 +03:00
b57b11c469 update npm modules and add entry to trivyignore
Some checks failed
ci/woodpecker/push/build Pipeline failed
Build the portfolio website / build-portfolio-website (push) Successful in 15m6s
Build the portfolio website / build-and-release-image (push) Successful in 1m48s
2025-07-28 08:54:29 +03:00
e40252de99 fix npm package issue for minimatch
Some checks failed
ci/woodpecker/push/build Pipeline failed
Build the portfolio website / build-portfolio-website (push) Successful in 22m59s
Build the portfolio website / build-and-release-image (push) Failing after 30m54s
2025-07-26 20:09:19 +03:00
8c39e38fb3 update node modules 2025-07-26 20:02:36 +03:00
a298941e0c update npm packages
Some checks failed
Build the portfolio website / build-portfolio-website (push) Failing after 1m48s
Build the portfolio website / build-and-release-image (push) Has been skipped
ci/woodpecker/push/build Pipeline failed
2025-07-26 19:51:58 +03:00
a516f020ef fix woodpecker workflow
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 6m4s
ci/woodpecker/push/build Pipeline failed
Build the portfolio website / build-and-release-image (push) Failing after 5m13s
2025-07-26 19:26:05 +03:00
7f27e0c673 add woodpecker CI workflow
Some checks failed
Build the portfolio website / build-and-release-image (push) Has been cancelled
Build the portfolio website / build-portfolio-website (push) Has been cancelled
2025-07-26 19:25:24 +03:00
4879071e08 workflows: fix workflow
All checks were successful
Build the portfolio website / build-portfolio-website (push) Successful in 1m7s
Build the portfolio website / build-and-release-image (push) Successful in 1m21s
2025-06-29 11:16:10 +03:00
45410eaf4f Update .gitea/workflows/build.yaml
All checks were successful
Build the portfolio website / build-portfolio-website (push) Successful in 1m1s
Build the portfolio website / build-and-release-image (push) Successful in 2m11s
2025-06-29 08:09:14 +00:00
280511118c fix workflow 2025-06-29 11:06:42 +03:00
ae8a3643a4 frontend: fix mouse event listener 2025-06-29 11:04:31 +03:00
5c9d8de03d updated dockerfile to use slip docker image
All checks were successful
Build the portfolio website / build-and-release-image (push) Has been skipped
Build the portfolio website / build-portfolio-website (push) Successful in 1m0s
- updated dockerfile
- updated readme regarding image upload
2025-06-22 09:58:59 +03:00
f980da7daf updated the kubeconfig generator script
All checks were successful
Build the portfolio website / build-portfolio-website (push) Successful in 54s
Build the portfolio website / build-and-release-image (push) Successful in 1m40s
2025-05-18 10:50:25 +03:00
e7c9e6d4e0 updated README regarding CI/CD
All checks were successful
Build the portfolio website / build-portfolio-website (push) Successful in 54s
Build the portfolio website / build-and-release-image (push) Successful in 1m47s
2025-05-17 22:46:07 +03:00
0cdb868e6e fix docker image tag
All checks were successful
Build the portfolio website / build-portfolio-website (push) Successful in 48s
Build the portfolio website / build-and-release-image (push) Successful in 1m41s
2025-05-17 22:36:43 +03:00
7d29c9abbc finalize workflow to build and push container image 2025-05-17 22:34:34 +03:00
c8ca07904b remove hard coded registry
All checks were successful
Build the portfolio website / build-portfolio-website (push) Successful in 49s
Build the portfolio website / build-and-release-image (push) Successful in 1m42s
2025-05-17 22:32:50 +03:00
649f30075d fix
All checks were successful
Build the portfolio website / build-portfolio-website (push) Successful in 47s
Build the portfolio website / build-and-release-image (push) Successful in 1m43s
2025-05-17 22:29:22 +03:00
0052dbe30f fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 57s
Build the portfolio website / build-and-release-image (push) Failing after 37s
2025-05-17 22:23:51 +03:00
897b04fa4f fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 48s
Build the portfolio website / build-and-release-image (push) Has been cancelled
2025-05-17 22:22:11 +03:00
4f2f98988f update readme
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 53s
Build the portfolio website / build-and-release-image (push) Failing after 22s
2025-05-17 22:19:34 +03:00
9dd04356fc fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 45s
Build the portfolio website / build-and-release-image (push) Failing after 2m15s
2025-05-17 22:14:22 +03:00
9654ea3050 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 46s
Build the portfolio website / build-and-release-image (push) Failing after 11s
2025-05-17 22:11:26 +03:00
82c8b1d451 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 53s
Build the portfolio website / build-and-release-image (push) Has been cancelled
2025-05-17 22:10:26 +03:00
b5877f36f5 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 51s
Build the portfolio website / build-and-release-image (push) Failing after 11s
2025-05-17 22:04:52 +03:00
cbca63c43f fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 46s
Build the portfolio website / build-and-release-image (push) Failing after 0s
2025-05-17 21:53:15 +03:00
9f95564022 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 45s
Build the portfolio website / build-and-release-image (push) Failing after 0s
2025-05-17 21:47:18 +03:00
c451ec5b92 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 46s
Build the portfolio website / build-and-release-image (push) Failing after 4s
2025-05-17 21:42:38 +03:00
4c0ad887b5 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 54s
Build the portfolio website / build-and-release-image (push) Failing after 4s
2025-05-17 21:34:16 +03:00
c7746ff91d fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 52s
Build the portfolio website / build-and-release-image (push) Failing after 23s
2025-05-17 21:28:42 +03:00
aad5dc83e1 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 55s
Build the portfolio website / build-and-release-image (push) Failing after 18s
2025-05-17 21:16:56 +03:00
c9898eb26c fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 54s
Build the portfolio website / build-and-release-image (push) Failing after 16s
2025-05-17 21:13:59 +03:00
2a5702db4b fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 46s
Build the portfolio website / build-and-release-image (push) Failing after 21s
2025-05-17 21:12:17 +03:00
2badabb167 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 45s
Build the portfolio website / build-and-release-image (push) Has been cancelled
2025-05-17 21:11:18 +03:00
3f5facc265 fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 53s
Build the portfolio website / build-and-release-image (push) Failing after 14s
2025-05-17 21:08:24 +03:00
9d15e4e50e fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 45s
Build the portfolio website / build-and-release-image (push) Failing after 17s
2025-05-17 21:02:45 +03:00
b45468fcbd continueing with troubleshooting
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 54s
Build the portfolio website / build-and-release-image (push) Failing after 0s
2025-05-17 20:56:31 +03:00
e5534d4f1d continueing with the fix
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 56s
Build the portfolio website / build-and-release-image (push) Failing after 1m2s
2025-05-17 20:54:11 +03:00
a3580be305 fix gitea runner
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 47s
Build the portfolio website / build-and-release-image (push) Failing after 2m16s
2025-05-17 20:46:37 +03:00
47660ff80c update the build and push workflow
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 46s
Build the portfolio website / build-and-push-docker-image (push) Failing after 1m34s
2025-05-17 15:35:21 +03:00
940435cc8a fix workflow following gitea workflow in github
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 47s
Build the portfolio website / build-and-push-docker-image (push) Has been cancelled
2025-05-17 15:26:24 +03:00
6d76e17613 third attempt to fix workflow
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 1m32s
Build the portfolio website / build-and-push-docker-image (push) Has been cancelled
2025-05-17 15:23:54 +03:00
1d37e5a39f second attempt to fix the docker build workflow
Some checks failed
Build the portfolio website / build-portfolio-website (push) Failing after 1m16s
2025-05-17 15:19:54 +03:00
9cdb13c753 fix workflow for docker build
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 53s
Build the portfolio website / build-and-push-docker-image (push) Failing after 1m25s
2025-05-17 15:15:09 +03:00
97d9372642 add workflow actions for building the app
Some checks failed
Build the portfolio website / build-portfolio-website (push) Successful in 54s
Build the portfolio website / build-and-push-docker-image (push) Failing after 20s
2025-05-17 15:11:18 +03:00
44d312ab4e added script to create kubeconfig for CI/CD
Some checks failed
Build the portfolio website / build-and-push-docker-image (push) Failing after 2m26s
Build the portfolio website / build-portfolio-website (push) Failing after 2m31s
2025-05-17 15:04:34 +03:00
8fcf4e8758 frontend: fix menubar overlapping with social tab 2025-04-30 19:05:53 +03:00
8ff3487198 Merge branch 'sidebar' 2025-04-21 19:39:50 +03:00
9cd3573a00 sidebar: align items to center in widescreen 2025-04-21 19:35:26 +03:00
a8824e3864 sidebar: adapt sidebar to mobile display 2025-04-21 19:00:27 +03:00
92913fb20f refactor sidebar component 2025-04-20 21:35:53 +03:00
672d3df1af add links to sidebar 2025-04-20 21:31:48 +03:00
6aa16bd0ef Revert "refactor frontend code"
This reverts commit 1d45fca783.
2025-04-20 13:28:17 +03:00
a50caacfce add sidebar to portfolio 2025-04-20 13:21:28 +03:00
71b095a0e4 frotnend: add more refactoring 2025-04-20 13:16:47 +03:00
c7e09cb8c4 sidebar: add sidebar to portfolio 2025-04-20 13:02:46 +03:00
20 changed files with 16715 additions and 1280 deletions

View File

@ -0,0 +1,73 @@
name: Build the portfolio website
on:
workflow_dispatch:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build-portfolio-website:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: "20"
- name: Install dependencies
run: cd frontend && npm install
- name: Build the project
run: cd frontend && npm run build
build-and-release-image:
runs-on: ubuntu-latest
container: catthehacker/ubuntu:act-latest
needs: build-portfolio-website
if: success()
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # all history for all branches and tags
- name: Create kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver: kubernetes
driver-opts: |
namespace=gitea
qemu.install=true
- name: Login to Docker registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Generate timestamp
id: timestamp
run: echo "timestamp=$(date +%Y%m%d%H%M%S)" >> $GITEA_OUTPUT
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile
push: true
tags: |
${{ secrets.DOCKER_REGISTRY }}/taqi/portfolio/my-portfolio-app:latest
${{ secrets.DOCKER_REGISTRY }}/taqi/portfolio/my-portfolio-app:1.0.0-${{ steps.timestamp.outputs.timestamp }}

9
.trivyignore Normal file
View File

@ -0,0 +1,9 @@
CVE-2024-4068
CVE-2024-21538
CVE-2024-21536
CVE-2025-7783
CVE-2024-29415
CVE-2022-23539
CVE-2022-24771
CVE-2022-24772
CVE-2024-29180

35
.woodpecker/build.yaml Normal file
View File

@ -0,0 +1,35 @@
when:
- event: [push, pull_request, manual]
branch: [master, feature/*]
steps:
- name: trivy-scan
image: aquasec/trivy:latest
commands:
- trivy fs --scanners vuln,config --exit-code 1 --ignorefile .trivyignore --severity HIGH,CRITICAL frontend/
- name: lint-frontend
image: node:24
commands:
- cd frontend
- npm install
- npm run lint
- name: build-and-publish
image: woodpeckerci/plugin-docker-buildx
settings:
registry:
from_secret: docker_registry
repo:
from_secret: portfolio_docker_repo
tags:
- latest
- 1.0.0-${CI_PIPELINE_NUMBER} # Ref: https://woodpecker-ci.org/docs/usage/environment
skip_tls_verify: false # set to true for testing registries ONLY with self-signed certs
build_args:
- COMMIT_SHA=${CI_COMMIT_SHA}
- COMMIT_AUTHOR_EMAIL=${CI_COMMIT_AUTHOR_EMAIL}
username:
from_secret: docker-username
password:
from_secret: docker-password

View File

@ -1,5 +1,5 @@
# Step 1: Build the React app # Step 1: Build the React app
FROM node:20 AS build FROM node:24-slim AS build
WORKDIR /app WORKDIR /app

View File

@ -1,5 +1,4 @@
My Portfolio Website # My Portfolio Website
=====================
# Overview # Overview
@ -31,4 +30,19 @@ docker push $DOCKER_REGISTRY/my-portfolio-app:latest
# Check the registry # Check the registry
curl -u user:pass https://$DOCKER_REGISTRY/v2/_catalog curl -u user:pass https://$DOCKER_REGISTRY/v2/_catalog
# Or if using gitea registry
curl --netrc -X GET https://gitea.yourdomain.com/v2/_catalog
``` ```
# CI/CD
Run in Gitea Actions within kubernetes cluster
Current, the project has workflow files for:
- Build and push the container to the registry
- Deploy the container to the Kubernetes cluster
Thus making a commit to the master branch will automatically build the
container and deploy it to the cluster.

17197
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,8 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-markdown": "^9.0.3", "react-markdown": "^9.0.3",
"react-router-dom": "^7.1.1" "react-router-dom": "^7.1.1",
"styled-component": "^2.8.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.0", "@eslint/js": "^9.9.0",
@ -35,6 +36,9 @@
"tailwindcss": "^3.4.11", "tailwindcss": "^3.4.11",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"typescript-eslint": "^8.0.1", "typescript-eslint": "^8.0.1",
"vite": "^5.4.1" "vite": "^7.0.6"
},
"overrides": {
"@types/minimatch": "5.1.2"
} }
} }

View File

@ -4,6 +4,7 @@ import { Routes, Route } from 'react-router-dom'
import './App.css' import './App.css'
import Home from './pages/Home' import Home from './pages/Home'
import Navbar from './components/Navbar' import Navbar from './components/Navbar'
import Sidebar from './components/Sidebar'
import Footer from './components/Footer' import Footer from './components/Footer'
import Experience from './pages/Experience' import Experience from './pages/Experience'
import Projects from './pages/Projects' import Projects from './pages/Projects'
@ -24,14 +25,22 @@ function App() {
<main className="bg-amber-50 px-10 dark:bg-gray-900"> <main className="bg-amber-50 px-10 dark:bg-gray-900">
<section className="min-h-screen"> <section className="min-h-screen">
<Navbar toggleDarkMode={toggleDarkMode} darkMode={darkMode} /> <Navbar toggleDarkMode={toggleDarkMode} darkMode={darkMode} />
<Router> <div className="md:flex md:flex-row md:h-full">
<Routes> <div className="mb-4 md:w-1/4 md:max-w-[260px] md:max-h-[900px] border-2 border-gray-300 dark:border-gray-700 rounded-lg shadow-sm">
<Route path="/" element={<Home />} /> <Sidebar />
<Route path="/experience" element={<Experience />} /> </div>
<Route path="/projects" element={<Projects />} /> <div className="md:flex-1">
<Route path="/Interests" element={<Interests />} /> <Router>
</Routes> <Routes>
</Router> <Route path="/" element={<Home />} />
<Route path="/experience" element={<Experience />} />
<Route path="/projects" element={<Projects />} />
<Route path="/Interests" element={<Interests />} />
</Routes>
</Router>
</div>
<div className="hidden md:block md:w-1/4 md:max-w-[260px] md:max-h-[900px]"></div>
</div>
<Footer /> <Footer />
</section> </section>
</main> </main>

View File

@ -29,7 +29,7 @@ const Footer = () => {
<div className="flex items-center gap-2 text-xs"> <div className="flex items-center gap-2 text-xs">
<Coffee size={14} className="inline-block" /> <Coffee size={14} className="inline-block" />
<span>Powered by coffee and countless hours of debugging</span> <span>Powered by coffee and Love</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,4 @@
import { COLORS, SOCIALLINKS } from '../constants' import { COLORS } from '../constants'
import { Tooltip } from './Tooltip'
const Introduction = () => { const Introduction = () => {
const BoldStyle = 'text-blue-900 dark:text-blue-300 font-semibold' const BoldStyle = 'text-blue-900 dark:text-blue-300 font-semibold'
@ -35,24 +34,6 @@ const Introduction = () => {
and infrastructure automation to implementing robust testing frameworks. and infrastructure automation to implementing robust testing frameworks.
</p> </p>
</div> </div>
<div className="flex justify-center gap-8 py-3">
{SOCIALLINKS.map((link, index) => (
<div key={index} className="group relative">
<Tooltip label={link.label} position="top">
<a
href={link.href}
target="_blank"
rel="noreferrer"
className={`${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY} hover:text-sky-600 dark:hover:text-sky-500 transition-colors duration-200`}
aria-label={link.label}
>
{link.icon}
</a>
</Tooltip>
</div>
))}
</div>
</div> </div>
) )
} }

View File

@ -35,11 +35,13 @@ const Navbar: React.FC<NavProps> = ({ toggleDarkMode, darkMode }) => {
} }
} }
document.addEventListener('mousedown', handleClickoutside) if (isMenuOpen) {
document.addEventListener('mousedown', handleClickoutside)
}
return () => { return () => {
document.removeEventListener('mousedown', handleClickoutside) document.removeEventListener('mousedown', handleClickoutside)
} }
}) }, [isMenuOpen])
const menuItem = [ const menuItem = [
{ title: 'Home', href: '/' }, { title: 'Home', href: '/' },
@ -102,7 +104,8 @@ const Navbar: React.FC<NavProps> = ({ toggleDarkMode, darkMode }) => {
{/* Dropdown Menu */} {/* Dropdown Menu */}
{isMenuOpen && ( {isMenuOpen && (
<div className="absolute right-0 mt-3 w-36 rounded-md shadow-lg bg-amber-50 dark:bg-gray-800 ring-1 ring-black ring-opacity-5"> // Dropdown Menu. The z value is set to 10 so that it appears above other elements.
<div className="absolute right-0 mt-3 w-36 rounded-md shadow-lg bg-amber-50 dark:bg-gray-800 ring-1 ring-black ring-opacity-5 z-10">
<div className="py-1" role="menu" aria-orientation="vertical"> <div className="py-1" role="menu" aria-orientation="vertical">
{menuItem.map((item) => ( {menuItem.map((item) => (
<a <a

View File

@ -0,0 +1,75 @@
import { COLORS, SOCIALLINKS } from '../constants'
import { Tooltip } from './Tooltip'
interface LinkProps {
href: string
children: React.ReactNode
icon: React.ReactNode
label: string
}
const Link: React.FC<LinkProps> = ({ icon, href, children, label }) => {
return (
<div className="transition-all duration-200 hover:translate-x-1">
<div className="m-2 border border-gray-200 dark:border-gray-700 p-2 md:p-3 rounded-lg hover:shadow-md">
<Tooltip label={label} position="top" additionalClass="md:hidden">
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center text-gray-700 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400"
>
{icon}
<span className="text-sm font-medium hidden md:inline">{children}</span>
</a>
</Tooltip>
</div>
</div>
)
}
const Sidebar = () => {
const SectionTitle = ({ children }: { children: React.ReactNode }) => (
<h2
className={`mt-4 mb-4 text-lg font-semibold ${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY} hidden md:block`}
>
{children}
</h2>
)
return (
<div
className={`grid grid-cols-4 md:grid-cols-1 p-2 md:p-6 max-w-xs mx-auto ${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY} rounded-xl shadow-sm`}
>
<SectionTitle>Contact</SectionTitle>
{SOCIALLINKS.contact.map((link, index) => (
<Link key={index} icon={link.icon} href={link.href} label={link.text}>
{link.text}
</Link>
))}
<SectionTitle>Connect</SectionTitle>
{SOCIALLINKS.connect.map((link, index) => (
<Link key={index} icon={link.icon} href={link.href} label={link.text}>
{link.text}
</Link>
))}
<SectionTitle>Follow</SectionTitle>
{SOCIALLINKS.follow.map((link, index) => (
<Link key={index} icon={link.icon} href={link.href} label={link.text}>
{link.text}
</Link>
))}
<SectionTitle>Achievements</SectionTitle>
{SOCIALLINKS.publications.map((link, index) => (
<Link key={index} icon={link.icon} href={link.href} label={link.text}>
{link.text}
</Link>
))}
</div>
)
}
export default Sidebar

View File

@ -1,6 +1,33 @@
import { COLORS, SKILLS } from '../constants' import pythonIcon from '../assets/python.svg'
import robotIcon from '../assets/robotframework-svgrepo-com.svg'
import goIcon from '../assets/go-original.svg'
import reactIcon from '../assets/react.svg'
import ansibleIcon from '../assets/ansible.svg'
import terraformIcon from '../assets/terraform-icon.svg'
import jenkinsIcon from '../assets/jenkins.svg'
import gitIcon from '../assets/git-icon.svg'
import dockerIcon from '../assets/docker-icon.svg'
import kubernetesIcon from '../assets/kubernetes.svg'
import prometheusIcon from '../assets/prometheus.svg'
import grafanaIcon from '../assets/grafana.svg'
import { COLORS } from '../constants'
const Skills = () => { const Skills = () => {
const skills = [
{ name: 'Python', icon: pythonIcon },
{ name: 'Golang', icon: goIcon },
{ name: 'React', icon: reactIcon },
{ name: 'Robot Framework', icon: robotIcon },
{ name: 'Ansible', icon: ansibleIcon },
{ name: 'Terraform', icon: terraformIcon },
{ name: 'Jenkins', icon: jenkinsIcon },
{ name: 'Git', icon: gitIcon },
{ name: 'Docker', icon: dockerIcon },
{ name: 'Kubernetes', icon: kubernetesIcon },
{ name: 'Prometheus', icon: prometheusIcon },
{ name: 'Grafana', icon: grafanaIcon },
]
return ( return (
<div> <div>
<h1 className={`text-2xl py-5 font-burtons ${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY}`}> <h1 className={`text-2xl py-5 font-burtons ${COLORS.PRIMARY} ${COLORS.DARK_PRIMARY}`}>
@ -9,7 +36,7 @@ const Skills = () => {
<div <div
className={`grid grid-cols-4 gap-6 text-md py-5 leading-8 ${COLORS.TEXT} ${COLORS.DARK_TEXT} mx-auto max-w-2xl md:text-xl`} className={`grid grid-cols-4 gap-6 text-md py-5 leading-8 ${COLORS.TEXT} ${COLORS.DARK_TEXT} mx-auto max-w-2xl md:text-xl`}
> >
{SKILLS.map((skill) => ( {skills.map((skill) => (
<div key={skill.name} className="flex flex-col items-center"> <div key={skill.name} className="flex flex-col items-center">
<img src={skill.icon} alt={skill.name} className="h-10 w-10" /> <img src={skill.icon} alt={skill.name} className="h-10 w-10" />
<p className="mt-2 text-center">{skill.name}</p> <p className="mt-2 text-center">{skill.name}</p>

View File

@ -2,9 +2,10 @@ export interface TooltipProps {
children: React.ReactNode children: React.ReactNode
label: string label: string
position: 'top' | 'bottom' | 'left' | 'right' position: 'top' | 'bottom' | 'left' | 'right'
additionalClass?: string
} }
export const Tooltip: React.FC<TooltipProps> = ({ children, label, position }) => { export const Tooltip: React.FC<TooltipProps> = ({ children, label, position, additionalClass }) => {
const tooltipStyles = { const tooltipStyles = {
top: 'bottom-full left-1/2 -translate-x-1/2', top: 'bottom-full left-1/2 -translate-x-1/2',
bottom: 'top-full left-1/2 -translate-x-1/2', bottom: 'top-full left-1/2 -translate-x-1/2',
@ -12,7 +13,7 @@ export const Tooltip: React.FC<TooltipProps> = ({ children, label, position }) =
right: 'left-full top-1/2 -translate-y-1/2', right: 'left-full top-1/2 -translate-y-1/2',
} }
const tooltipPosition: string = tooltipStyles[position] const tooltipPosition: string = tooltipStyles[position]
const tooltipClass = `pointer-events-none absolute mt-2 whitespace-nowrap rounded bg-slate-800 px-2 py-1 text-xs text-slate-100 opacity-0 transition before:absolute before:left-1/2 before:top-full before:-translate-x-1/2 before:border-4 before:border-transparent before:border-t-slate-800 before:content-[''] group-hover:opacity-100 ${tooltipPosition}` const tooltipClass = `pointer-events-none absolute mt-2 whitespace-nowrap rounded bg-slate-800 px-2 py-1 text-xs text-slate-100 opacity-0 transition before:absolute before:left-1/2 before:top-full before:-translate-x-1/2 before:border-4 before:border-transparent before:border-t-slate-800 before:content-[''] group-hover:opacity-100 ${tooltipPosition} ${additionalClass}`
return ( return (
<div className="group relative"> <div className="group relative">

View File

@ -1,4 +1,6 @@
import { Linkedin, Github, Award, Link } from 'lucide-react' import { Linkedin, Github, Award, Link } from 'lucide-react'
import { Aperture, Instagram, Mail, Globe, ScrollText } from 'lucide-react'
import { Camera, Plane, Film, Server, Cpu, Trophy, Car, Gamepad2 } from 'lucide-react'
import pythonIcon from './assets/python.svg' import pythonIcon from './assets/python.svg'
import robotIcon from './assets/robotframework-svgrepo-com.svg' import robotIcon from './assets/robotframework-svgrepo-com.svg'
import goIcon from './assets/go-original.svg' import goIcon from './assets/go-original.svg'
@ -26,7 +28,6 @@ export const EMAIL = 'taqitahmid@gmail.com'
export const RESUME = export const RESUME =
'https://www.linkedin.com/in/taqi-tahmid/overlay/1735981754176/single-media-viewer/?profileId=ACoAACDU_GsBCgKtvw2bmzbVwTy2WixBG6-e3JM' 'https://www.linkedin.com/in/taqi-tahmid/overlay/1735981754176/single-media-viewer/?profileId=ACoAACDU_GsBCgKtvw2bmzbVwTy2WixBG6-e3JM'
export const PROJECTS = [ export const PROJECTS = [
{ {
title: 'Self-Hosted Kubernetes Homelab Cluster', title: 'Self-Hosted Kubernetes Homelab Cluster',
@ -87,23 +88,58 @@ export const PROJECTS = [
}, },
] ]
export const SOCIALLINKS = [ export const iconClass = 'text-blue-600 dark:text-blue-400 mr-3'
{
icon: <Linkedin size={32} />, export const SOCIALLINKS = {
href: 'https://www.linkedin.com/in/taqi-tahmid/', contact: [
label: 'LinkedIn', {
}, icon: <Mail size={20} className={iconClass} />,
{ href: 'mailto:taqitahmid@gmail.com',
icon: <Github size={32} />, text: 'Email Me',
href: 'https://github.com/TheTaqiTahmid', },
label: 'GitHub', {
}, icon: <Globe size={20} className={iconClass} />,
{ href: 'https://portfolio.tahmidcloud.com/',
icon: <Award size={32} />, text: 'Website',
href: 'https://ti-user-certificates.s3.amazonaws.com/e0df7fbf-a057-42af-8a1f-590912be5460/3da54db2-f994-4148-a0ca-705ae1d748cd-mohammad-taqi-tahmid-094cf8b4-0db8-4a9f-b787-b4efbb2a90fe-certificate.pdf', },
label: 'CKA Certificate', ],
}, connect: [
] {
icon: <Linkedin size={20} className={iconClass} />,
href: 'https://www.linkedin.com/in/taqi-tahmid/',
text: 'LinkedIn',
},
{
icon: <Github size={20} className={iconClass} />,
href: 'https://github.com/theTaqiTahmid',
text: 'GitHub',
},
],
follow: [
{
icon: <Aperture size={20} className={iconClass} />,
href: 'https://500px.com/p/taqi1203050?view=photos',
text: 'Photography',
},
{
icon: <Instagram size={20} className={iconClass} />,
href: 'https://www.instagram.com/tahmidtaqi/',
text: 'Instagram',
},
],
publications: [
{
icon: <Award size={20} className={iconClass} />,
href: 'https://www.credly.com/badges/abb049aa-d811-4954-a460-8c7351ceba3e/public_url',
text: 'CKA Certification',
},
{
icon: <ScrollText size={20} className={iconClass} />,
href: 'https://scholar.google.fi/citations?user=w3BoP0AAAAAJ&hl=en',
text: 'Google Scholar',
},
],
}
export const SKILLS = [ export const SKILLS = [
{ name: 'Python', icon: pythonIcon }, { name: 'Python', icon: pythonIcon },
@ -119,3 +155,128 @@ export const SKILLS = [
{ name: 'Prometheus', icon: prometheusIcon }, { name: 'Prometheus', icon: prometheusIcon },
{ name: 'Grafana', icon: grafanaIcon }, { name: 'Grafana', icon: grafanaIcon },
] ]
export const INTERESTS = [
{
title: 'Travelling',
icon: <Plane size={32} />,
description:
'Exploring new places, experiencing different cultures, and creating lasting memories through adventures around the world. From scenic landscapes to bustling cities, every journey is an opportunity to learn and grow.',
},
{
title: 'Photography',
icon: <Camera size={32} />,
description:
'Capturing moments and perspectives through the lens. Particularly interested in landscape and street photography, always looking to improve composition skills and trying new techniques.',
},
{
title: 'Movies & Shows',
icon: <Film size={32} />,
description:
'Passionate about cinema across various genres and cultures. Enjoy analyzing cinematography, storytelling techniques, and discovering hidden gems from different parts of the world.',
},
{
title: 'Homelab',
icon: <Server size={32} />,
description:
'Managing a personal homelab setup for experimenting with self-hosted services, networking configurations, and learning about system administration in a hands-on environment.',
},
{
title: 'New Technologies',
icon: <Cpu size={32} />,
description:
'Keeping up with the latest technological advancements, particularly in cloud computing, automation, and emerging DevOps tools. Enjoy experimenting with new frameworks and platforms.',
},
{
title: 'Playing Video Games',
icon: <Gamepad2 size={32} />,
description:
'Enthusiastic gamer with a deep appreciation for interactive storytelling and virtual worlds. Enjoy exploring diverse genres from immersive RPGs to strategic multiplayer games.',
},
{
title: 'Sports',
icon: <Trophy size={32} />,
description: 'Avid sports enthusiast following multiple disciplines:',
subInterests: [
{
name: 'Football',
details:
'Following major leagues and international tournaments, appreciating the tactical aspects and team dynamics of the beautiful game.',
},
{
name: 'Cricket',
details:
'Enjoying both test matches and limited-overs formats, following international competitions and analyzing game strategies.',
},
{
name: 'Formula 1',
icon: <Car size={24} />,
details:
'Following the high-speed world of F1, keeping up with team developments, race strategies, and technical innovations in motorsport.',
},
],
},
]
export const EXPERIENCE = [
{
title: 'Experienced Developer (DevOps)',
company: 'Ericsson',
location: 'Jorvas, Finland',
period: 'November-2022 - Present',
responsibilities: [
'Managing and optimizing Kubernetes clusters in production environments',
'Designing and implementing CI/CD pipelines for end to end product development flow using Jenkins',
'Automating infrastructure deployment using Terraform and Ansible',
'Develop and maintain monitoring solutions of various resources for greater observability and troubleshooting',
'Actively support development teams regarding product development flow and infrastructure issues',
'Develop and perform automated end-to-end product testing with Python, Robot Framework, Jenkins, Bash, etc.',
],
tools: 'Kubernetes, Docker, KVM, Openstack, Ansible, Terraform, Prometheus, Grafana',
},
{
title: 'Test Engineer',
company: 'Nokia',
location: 'Espoo, Finland',
period: 'June-2021 - October-2022',
responsibilities: [
'Develop and maintain Cloud RAN E2E test setup for vCU and vDU application testing on top of RedHat Openshift',
'Develop automation and CI/CD flow for Cloud RAN testing using Python, Robot Framework, Bash, Jenkins etc.',
'Develop and perform automated testing to validate the functionality of Nokia Cloud RAN base stations',
'Integrate new hardware and software into the test setup',
'Perform hands on debugging and log analysis to nd root cause and solve any software or hardware issues',
],
tools: 'Keysight Nemo Outdoor, Nemo Analyze, Qualcomm PCAT, QCAT, QXDM',
},
{
title: 'Testing and Prototyping Intern',
company: 'GE Healthcare',
location: 'Helsinki, Finland',
period: 'Jan-2019 - May-2021',
responsibilities: [
'Planning, writing, and performing manual and automated tests of different prototype wireless medical devices',
'Designing driver and PCB circuits in Altium Designer to test the performance of the Digital Sensor Interface',
'Ensuring the PCB componets used in the devices are EU RoHS and REACH compliant',
],
tools:
'LTSpice, Altium Designer, HP-ALM, Vector Network Analyzer, Spectrum Analyzer, Climate Chamber',
},
]
export const EDUCATION = [
{
degree: "Master's in Wireless Communication & RF Systems",
institution: 'Tampere University',
location: 'Tampere, Finland',
period: '2018 - 2020',
thesis: '5G Reference Signals and their Possibility to be for 5G Based Positioning',
},
{
degree: "Bachelor's in Electrical & Electronic Engineering",
institution: 'Khulna University of Engineering & Technology',
location: 'Khulna, Bangladesh',
period: '2013 - 2017',
thesis:
'Density-based smart traffic control system using Canny edge detection technique using Digital Image Processing',
},
]

View File

@ -1,70 +1,7 @@
import { Building2, Calendar, GraduationCap, MapPin, Microscope, Wrench } from 'lucide-react' import { Building2, Calendar, GraduationCap, MapPin, Microscope, Wrench } from 'lucide-react'
import { COLORS } from '../constants' import { COLORS, EXPERIENCE, EDUCATION } from '../constants'
const Experience = () => { const Experience = () => {
const experiences = [
{
title: 'Experienced Developer (DevOps)',
company: 'Ericsson',
location: 'Jorvas, Finland',
period: 'November-2022 - Present',
responsibilities: [
'Managing and optimizing Kubernetes clusters in production environments',
'Designing and implementing CI/CD pipelines for end to end product development flow using Jenkins',
'Automating infrastructure deployment using Terraform and Ansible',
'Develop and maintain monitoring solutions of various resources for greater observability and troubleshooting',
'Actively support development teams regarding product development flow and infrastructure issues',
'Develop and perform automated end-to-end product testing with Python, Robot Framework, Jenkins, Bash, etc.',
],
tools: 'Kubernetes, Docker, KVM, Openstack, Ansible, Terraform, Prometheus, Grafana',
},
{
title: 'Test Engineer',
company: 'Nokia',
location: 'Espoo, Finland',
period: 'June-2021 - October-2022',
responsibilities: [
'Develop and maintain Cloud RAN E2E test setup for vCU and vDU application testing on top of RedHat Openshift',
'Develop automation and CI/CD flow for Cloud RAN testing using Python, Robot Framework, Bash, Jenkins etc.',
'Develop and perform automated testing to validate the functionality of Nokia Cloud RAN base stations',
'Integrate new hardware and software into the test setup',
'Perform hands on debugging and log analysis to nd root cause and solve any software or hardware issues',
],
tools: 'Keysight Nemo Outdoor, Nemo Analyze, Qualcomm PCAT, QCAT, QXDM',
},
{
title: 'Testing and Prototyping Intern',
company: 'GE Healthcare',
location: 'Helsinki, Finland',
period: 'Jan-2019 - May-2021',
responsibilities: [
'Planning, writing, and performing manual and automated tests of different prototype wireless medical devices',
'Designing driver and PCB circuits in Altium Designer to test the performance of the Digital Sensor Interface',
'Ensuring the PCB componets used in the devices are EU RoHS and REACH compliant',
],
tools:
'LTSpice, Altium Designer, HP-ALM, Vector Network Analyzer, Spectrum Analyzer, Climate Chamber',
},
]
const education = [
{
degree: "Master's in Wireless Communication & RF Systems",
institution: 'Tampere University',
location: 'Tampere, Finland',
period: '2018 - 2020',
thesis: '5G Reference Signals and their Possibility to be for 5G Based Positioning',
},
{
degree: "Bachelor's in Electrical & Electronic Engineering",
institution: 'Khulna University of Engineering & Technology',
location: 'Khulna, Bangladesh',
period: '2013 - 2017',
thesis:
'Density-based smart traffic control system using Canny edge detection technique using Digital Image Processing',
},
]
return ( return (
<div className="p-4 max-w-4xl mx-auto"> <div className="p-4 max-w-4xl mx-auto">
<h2 <h2
@ -74,7 +11,7 @@ const Experience = () => {
</h2> </h2>
<div className="space-y-8"> <div className="space-y-8">
{experiences.map((exp, index) => ( {EXPERIENCE.map((exp, index) => (
<div <div
key={index} key={index}
className={`border-l-4 ${COLORS.BORDER} ${COLORS.DARK_BORDER} pl-4 space-y-2`} className={`border-l-4 ${COLORS.BORDER} ${COLORS.DARK_BORDER} pl-4 space-y-2`}
@ -128,7 +65,7 @@ const Experience = () => {
</h2> </h2>
<div className="space-y-6"> <div className="space-y-6">
{education.map((edu, index) => ( {EDUCATION.map((edu, index) => (
<div <div
key={index} key={index}
className={`border-l-4 ${COLORS.BORDER} ${COLORS.DARK_BORDER} pl-4 space-y-2`} className={`border-l-4 ${COLORS.BORDER} ${COLORS.DARK_BORDER} pl-4 space-y-2`}

View File

@ -1,69 +1,6 @@
import { Camera, Plane, Film, Server, Cpu, Trophy, Car, Gamepad2 } from 'lucide-react' import { COLORS, INTERESTS } from '../constants'
import { COLORS } from '../constants'
const Interests = () => { const Interests = () => {
const interests = [
{
title: 'Travelling',
icon: <Plane size={32} />,
description:
'Exploring new places, experiencing different cultures, and creating lasting memories through adventures around the world. From scenic landscapes to bustling cities, every journey is an opportunity to learn and grow.',
},
{
title: 'Photography',
icon: <Camera size={32} />,
description:
'Capturing moments and perspectives through the lens. Particularly interested in landscape and street photography, always looking to improve composition skills and trying new techniques.',
},
{
title: 'Movies & Shows',
icon: <Film size={32} />,
description:
'Passionate about cinema across various genres and cultures. Enjoy analyzing cinematography, storytelling techniques, and discovering hidden gems from different parts of the world.',
},
{
title: 'Homelab',
icon: <Server size={32} />,
description:
'Managing a personal homelab setup for experimenting with self-hosted services, networking configurations, and learning about system administration in a hands-on environment.',
},
{
title: 'New Technologies',
icon: <Cpu size={32} />,
description:
'Keeping up with the latest technological advancements, particularly in cloud computing, automation, and emerging DevOps tools. Enjoy experimenting with new frameworks and platforms.',
},
{
title: 'Playing Video Games',
icon: <Gamepad2 size={32} />,
description:
'Enthusiastic gamer with a deep appreciation for interactive storytelling and virtual worlds. Enjoy exploring diverse genres from immersive RPGs to strategic multiplayer games.',
},
{
title: 'Sports',
icon: <Trophy size={32} />,
description: 'Avid sports enthusiast following multiple disciplines:',
subInterests: [
{
name: 'Football',
details:
'Following major leagues and international tournaments, appreciating the tactical aspects and team dynamics of the beautiful game.',
},
{
name: 'Cricket',
details:
'Enjoying both test matches and limited-overs formats, following international competitions and analyzing game strategies.',
},
{
name: 'Formula 1',
icon: <Car size={24} />,
details:
'Following the high-speed world of F1, keeping up with team developments, race strategies, and technical innovations in motorsport.',
},
],
},
]
return ( return (
<div className="p-4 max-w-4xl mx-auto"> <div className="p-4 max-w-4xl mx-auto">
<h1 <h1
@ -73,7 +10,7 @@ const Interests = () => {
</h1> </h1>
<div className="grid gap-6 md:grid-cols-2"> <div className="grid gap-6 md:grid-cols-2">
{interests.map((interest, index) => ( {INTERESTS.map((interest, index) => (
<div <div
key={index} key={index}
className={`group p-6 rounded-lg border-2 border-gray-300 dark:border-gray-700 hover:shadow-lg transition-all duration-300 ${ className={`group p-6 rounded-lg border-2 border-gray-300 dark:border-gray-700 hover:shadow-lg transition-all duration-300 ${

View File

@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/Introduction.tsx","./src/components/Navbar.tsx","./src/components/Photo.tsx","./src/components/Skills.tsx"],"version":"5.6.2"} {"root":["./src/App.tsx","./src/constants.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/Footer.tsx","./src/components/Introduction.tsx","./src/components/Navbar.tsx","./src/components/Photo.tsx","./src/components/Sidebar.tsx","./src/components/Skills.tsx","./src/components/Tooltip.tsx","./src/pages/Experience.tsx","./src/pages/Home.tsx","./src/pages/Interests.tsx","./src/pages/Projects.tsx"],"version":"5.8.3"}

View File

@ -1 +1 @@
{"root":["./vite.config.ts"],"version":"5.6.2"} {"root":["./vite.config.ts"],"version":"5.8.3"}

144
scripts/create_kubeconfig.sh Executable file
View File

@ -0,0 +1,144 @@
#!/usr/bin/env bash
function usage() {
echo "--namespace <namespace> --user <user> --kubeconfig <kubeconfig>"
}
function create_namespace() {
local namespace="$1"
kubectl create namespace "$namespace" \
--dry-run=client -o yaml | kubectl apply -f -
}
function create_service_account() {
local user="$1"
local namespace="$2"
kubectl create serviceaccount "$user" \
--namespace "$namespace" \
--dry-run=client -o yaml | kubectl apply -f -
# Create associated secret
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: ${user}-secret
namespace: ${namespace}
annotations:
kubernetes.io/service-account.name: ${user}
type: kubernetes.io/service-account-token
EOF
echo "Service account $user created in namespace $namespace"
}
function create_role() {
local user="$1"
local namespace="$2"
kubectl create role "$user" \
--verb=get,list,watch,create,update,delete,patch,exec \
--resource=pods,pods/exec,services,deployments,secrets,configmaps \
--namespace "$namespace" \
--dry-run=client -o yaml | kubectl apply -f -
echo "Role $user created in namespace $namespace"
}
function create_role_binding() {
local user="$1"
local namespace="$2"
kubectl create rolebinding "$user" \
--role="$user" \
--serviceaccount="$namespace:$user" \
--namespace "$namespace" \
--dry-run=client -o yaml | kubectl apply -f -
echo "Role binding $user created in namespace $namespace"
}
function create_kubeconfig() {
local user="$1"
local namespace="$2"
local kubeconfig="$3"
SECRET_NAME=${user}-secret
CLUSTER_NAME=$(kubectl config view --minify -o jsonpath='{.clusters[0].name}')
TOKEN=$(kubectl get secret "$SECRET_NAME" -n "$namespace" -o jsonpath='{.data.token}' | base64 --decode)
CA=$(kubectl get secret "$SECRET_NAME" -n "$namespace" -o jsonpath='{.data.ca\.crt}')
SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
# Create kubeconfig file with proper indentation
cat >"${kubeconfig}" <<EOF
apiVersion: v1
kind: Config
clusters:
- name: ${CLUSTER_NAME}
cluster:
server: ${SERVER}
certificate-authority-data: ${CA}
contexts:
- name: ${CLUSTER_NAME}-${namespace}-ci
context:
cluster: ${CLUSTER_NAME}
namespace: ${namespace}
user: ${user}
current-context: ${CLUSTER_NAME}-${namespace}-ci
users:
- name: ${user}
user:
token: ${TOKEN}
EOF
echo "Kubeconfig file created at ${kubeconfig}"
}
# Main script
function main() {
local namespace=""
local user=""
local kubeconfig=""
while [[ "$#" -gt 0 ]]; do
case $1 in
-n|--namespace)
namespace="$2"
shift
;;
-u|--user)
user="$2"
shift
;;
-k|--kubeconfig)
kubeconfig="$2"
shift
;;
-h|--help)
usage
exit 0
;;
*)
usage
exit 1
;;
esac
shift
done
if [[ -z "$namespace" || -z "$user" || -z "$kubeconfig" ]]; then
usage
exit 1
fi
create_namespace "$namespace"
create_service_account "$user" "$namespace"
create_role "$user" "$namespace"
create_role_binding "$user" "$namespace"
create_kubeconfig "$user" "$namespace" "$kubeconfig"
}
# Call the main function
main "$@"