diff --git a/.env b/.env new file mode 100644 index 000000000..8de65f3b5 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +USER=KALISA +PASS=1234 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..7bdcfdff8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,154 @@ +name: CI + +on: + pull_request: + branches: + - innox-edits + - main + - master + + push: + branches: + - innox-edits + - main + - master + +jobs: + + # ========================================================== + # 1. BUILD + # ========================================================== + build: + name: Build application + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Check application syntax + run: node --check index.js + + - name: Build application + run: npm run build --if-present + + + # ========================================================== + # 2. TESTS + # ========================================================== + test: + name: Run tests + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + + # ========================================================== + # 3. VULNERABILITY SCAN + # ========================================================== + vulnerability-scan: + name: Vulnerability scan + needs: test + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run npm vulnerability scan + run: npm audit --audit-level=high + + + # ========================================================== + # 4. DOCKER BUILD + PUSH + # Only after code is pushed/merged to the target branch + # ========================================================== + docker: + name: Build and push Docker image + needs: + - build + - test + - vulnerability-scan + + # Don't push Docker images from pull requests + if: github.event_name == 'push' + + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build Docker image + run: | + docker build \ + -t ${{ secrets.DOCKER_USERNAME }}/my-demo-repo:${{ github.sha }} . + + - name: Push Docker image + run: | + docker push \ + ${{ secrets.DOCKER_USERNAME }}/my-demo-repo:${{ github.sha }} + + + # ========================================================== + # 5. HELM + # ========================================================== + helm: + name: Validate Helm chart + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Helm + uses: azure/setup-helm@v4 + + - name: Check Helm + run: helm version + + - name: Lint Helm chart + run: helm lint ./web-app + + - name: Render Helm templates + run: helm template web-app ./web-app \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..40b21be83 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + docker: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get version + run: echo "VERSION=${GITHUB_REF_NAME}" >> $GITHUB_ENV + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build Docker image + run: | + docker build \ + -t ${{ secrets.DOCKER_USERNAME }}/my-demo-repo:${VERSION} . + + - name: Push Docker image + run: | + docker push \ + ${{ secrets.DOCKER_USERNAME }}/my-demo-repo:${VERSION} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 33717dc11..ba6dfc10e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,5 +20,7 @@ "titleBar.inactiveBackground": "#cf6d2899", "titleBar.inactiveForeground": "#15202b99" }, - "peacock.color": "#cf6d28" + "peacock.color": "#cf6d28", + "workbench.colorTheme": "Dark 2026", + "window.autoDetectColorScheme": false } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..ed5f87e67 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Use an official lightweight Node.js image +FROM node:20-alpine + +# Set the working directory inside the container +WORKDIR /app + +# Copy package files first to leverage Docker's cache layer +COPY package*.json ./ + +# Install only production dependencies +RUN npm ci --only=production + +# Copy the remaining application source code +COPY . . + +# Expose the port your app listens on (e.g., 3000) +EXPOSE 3000 + +# Start the application +CMD ["node", "index.js"] diff --git a/README.md b/README.md index b84b3924e..494a2df04 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ Simple node.js app that servers "hello world" + +# INNOX Edits + +I just editted README.md in my forked repo. + Great for testing simple deployments to the cloud ## Run It diff --git a/argocd/application.yaml b/argocd/application.yaml new file mode 100644 index 000000000..a147d6466 --- /dev/null +++ b/argocd/application.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application + +metadata: + name: web-app + namespace: argocd + +spec: + project: default + + source: + repoURL: https://github.com/innox123/node-hello.git + targetRevision: innox-edits + path: web-app + + destination: + server: https://kubernetes.default.svc + namespace: default + + syncPolicy: + automated: + enabled: true + prune: true + selfHeal: true \ No newline at end of file diff --git a/index.js b/index.js index 54e5fef1f..111e5ff42 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ const port = process.env.PORT || 3000; const server = http.createServer((req, res) => { res.statusCode = 200; - const msg = 'Hello Node!\n' + const msg = 'Hello Node!\n This is another version 2' res.end(msg); }); diff --git a/package.json b/package.json index b0d12dfc6..51a1387a0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "start": "node index.js" + "start": "node index.js", + "test": "echo \"Error: Test passed\"" }, "repository": { "type": "git", diff --git a/web-app/Chart.yaml b/web-app/Chart.yaml new file mode 100644 index 000000000..ae3a4cf86 --- /dev/null +++ b/web-app/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: web-app +description: My first Helm chart for a Kubernetes web application +type: application +version: 0.1.0 +appVersion: "1.0" \ No newline at end of file diff --git a/web-app/templates/configmap.yaml b/web-app/templates/configmap.yaml new file mode 100644 index 000000000..688e731be --- /dev/null +++ b/web-app/templates/configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap + +metadata: + name: {{ .Release.Name }}-config + +data: + APP_NAME: {{ .Values.config.APP_NAME | quote }} + ENVIRONMENT: {{ .Values.config.ENVIRONMENT | quote }} + LOG_LEVEL: {{ .Values.config.LOG_LEVEL | quote }} \ No newline at end of file diff --git a/web-app/templates/deployment.yaml b/web-app/templates/deployment.yaml new file mode 100644 index 000000000..2ec020d04 --- /dev/null +++ b/web-app/templates/deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment + +metadata: + name: {{ .Release.Name }}-web-app + + labels: + app: web-app + +spec: + replicas: {{ .Values.replicaCount }} + + selector: + matchLabels: + app: web-app + + template: + metadata: + labels: + app: web-app + + spec: + containers: + - name: web-app + + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + + imagePullPolicy: {{ .Values.image.pullPolicy }} + + ports: + - containerPort: 3000 + + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config + + - secretRef: + name: {{ .Release.Name }}-secret + + resources: + requests: + cpu: {{ .Values.resources.requests.cpu | quote }} + memory: {{ .Values.resources.requests.memory | quote }} + + limits: + cpu: {{ .Values.resources.limits.cpu | quote }} + memory: {{ .Values.resources.limits.memory | quote }} + + + # ------------------------- + # Liveness probe + # ------------------------- + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.livenessProbe.httpGet.path }} + port: {{ .Values.livenessProbe.httpGet.port }} + + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + {{- end }} + + # ------------------------- + # Readiness probe + # ------------------------- + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.httpGet.path }} + port: {{ .Values.readinessProbe.httpGet.port }} + + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + {{- end }} \ No newline at end of file diff --git a/web-app/templates/hpa.yaml b/web-app/templates/hpa.yaml new file mode 100644 index 000000000..c00f090e7 --- /dev/null +++ b/web-app/templates/hpa.yaml @@ -0,0 +1,26 @@ +{{- if .Values.autoscaling.enabled }} + +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler + +metadata: + name: {{ .Release.Name }}-hpa + +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ .Release.Name }}-web-app + + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + +{{- end }} \ No newline at end of file diff --git a/web-app/templates/ingress.yaml b/web-app/templates/ingress.yaml new file mode 100644 index 000000000..8c5ee0391 --- /dev/null +++ b/web-app/templates/ingress.yaml @@ -0,0 +1,23 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-ingress + +spec: + ingressClassName: {{ .Values.ingress.className }} + + rules: + - host: {{ .Values.ingress.host }} + + http: + paths: + - path: {{ .Values.ingress.path }} + pathType: {{ .Values.ingress.pathType }} + + backend: + service: + name: {{ .Release.Name }}-service + port: + number: {{ .Values.service.port }} +{{- end }} \ No newline at end of file diff --git a/web-app/templates/secret.yaml b/web-app/templates/secret.yaml new file mode 100644 index 000000000..5175e7261 --- /dev/null +++ b/web-app/templates/secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret + +metadata: + name: {{ .Release.Name }}-secret + +type: Opaque + +stringData: + username: {{ .Values.secret.username | quote }} + password: {{ .Values.secret.password | quote }} \ No newline at end of file diff --git a/web-app/templates/service.yaml b/web-app/templates/service.yaml new file mode 100644 index 000000000..70698315f --- /dev/null +++ b/web-app/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-service + +spec: + type: {{ .Values.service.type }} + + selector: + app: web-app + + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP \ No newline at end of file diff --git a/web-app/values.yaml b/web-app/values.yaml new file mode 100644 index 000000000..ff969a65f --- /dev/null +++ b/web-app/values.yaml @@ -0,0 +1,78 @@ +replicaCount: 2 + +image: + repository: innox123/my-demo-repo + tag: v1.0 + pullPolicy: IfNotPresent + +service: + type: ClusterIP + port: 80 + targetPort: 3000 + +ingress: + enabled: true + className: nginx + host: webapp.local + path: / + pathType: Prefix + +config: + APP_NAME: "My Web App" + ENVIRONMENT: "development" + LOG_LEVEL: "info" + +secret: + username: "admin" + password: "my-secret-password" + +autoscaling: + enabled: true + minReplicas: 1 + maxReplicas: 5 + + targetCPUUtilizationPercentage: 50 + + +resources: + requests: + cpu: "100m" + memory: "128Mi" + + limits: + cpu: "500m" + memory: "256Mi" + + + +# ------------------------- +# Liveness probe +# ------------------------- +livenessProbe: + enabled: true + + httpGet: + path: / + port: 3000 + + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 2 + failureThreshold: 3 + successThreshold: 1 + +# ------------------------- +# Readiness probe +# ------------------------- +readinessProbe: + enabled: true + + httpGet: + path: / + port: 3000 + + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 2 + failureThreshold: 3 + successThreshold: 1 \ No newline at end of file