diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..3b3098110 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,33 @@ +# EditorConfig is awesome: https://editorconfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py}] +charset = utf-8 + +# 4 space indentation +[*.py] +indent_style = space +indent_size = 4 + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +# Indentation override for all JS under lib directory +[lib/**.js] +indent_style = space +indent_size = 2 + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/github-action-terraform.yml b/.github/workflows/github-action-terraform.yml new file mode 100644 index 000000000..ea5ce01c9 --- /dev/null +++ b/.github/workflows/github-action-terraform.yml @@ -0,0 +1,74 @@ +name: GitHub Actions Terraform +run-name: ${{ github.actor }} Terraform GitHub Actions πŸš€ +on: + schedule: + # Runs every Hour from 8AM to 5PM on weekdays + #- cron: '0 8-17 * * 1-5' + push: + paths: + - 'terraform/**' + - 'github/workflows/github-action-terraform.yml' + branches: + - main + - infra-* +jobs: + Terraform-action: + runs-on: self-hosted + steps: + - name: Check out repo + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Terraform + uses: hashicorp/setup-terraform@v1 + with: + terraform_version: 1.10.5 + - name: Determine changed directories + id: changed_dirs + run: | + echo "Finding changed directories..." + dirs=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} -- terraform/ | grep -o 'terraform/[^/]*' | sort -u) + changed_dirs=$(echo "$dirs" | tr '\n' ',' | sed 's/,$//') + echo "Changed directories: $dirs" + echo "changed_dirs=$changed_dirs" >> $GITHUB_ENV + - name: Check for changes in Terraform directories + id: check_changes + run: | + if [ -z "$changed_dirs" ]; then + echo "No changes in Terraform directories." + echo "should_run=false" >> $GITHUB_ENV + else + echo "Changes detected in: $changed_dirs" + echo "should_run=true" >> $GITHUB_ENV + fi + echo $should_run + - name: Terraform Init + if: env.should_run == 'true' + run: | + for dir in ${changed_dirs//,/ }; do + echo "Initializing Terraform in directory: $dir" + cd /opt/actions-runner/_work/devops-programme/devops-programme/$dir + terraform init + done + - name: Kubernetes access preparation + if: env.should_run == 'true' + run: | + export KUBECONFIG=~/.kube/sof-lab03 + kubectl config set-context sof-lab03 + kubectl port-forward svc/argo-cd-7-1734333419-argocd-server -n argocd 8080:443 & + - name: Terraform Plan + if: env.should_run == 'true' + run: | + for dir in ${changed_dirs//,/ }; do + echo "Running Terraform plan in directory: $dir" + cd /opt/actions-runner/_work/devops-programme/devops-programme/$dir + terraform plan + done + - name: Terraform Apply + if: env.should_run == 'true' + run: | + for dir in ${changed_dirs//,/ }; do + echo "Applying Terraform changes in directory: $dir" + cd /opt/actions-runner/_work/devops-programme/devops-programme/$dir + terraform apply -auto-approve + done diff --git a/.github/workflows/github-actions-demo.yml b/.github/workflows/github-actions-demo.yml new file mode 100644 index 000000000..ba869edc8 --- /dev/null +++ b/.github/workflows/github-actions-demo.yml @@ -0,0 +1,174 @@ +name: GitHub Actions Demo +run-name: ${{ github.actor }} is deploying with GitHub ActionsπŸš€ +on: + push: + paths-ignore: + - '_homework/*' + - 'deployment/*' + - 'terraform/*' + - 'rollout/*' + - '.pre-commit-config.yaml' + - '.gitignore' + - '.editorconfig' + - '*.md' + - 'LICENSE' + - 'github/workflows/github-action-terraform.yml' + - 'github/workflows/github-actions-demo.yml' + branches: + - main + - feature-* +env: + IMAGE_TAG: dimitardd/dimitar-app02 +jobs: + Pylint: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set Node version + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.10' + cache: 'pip' + - run: | + pip install pylint black flake8 flask prometheus_flask_exporter + npm install -g markdownlint-cli editorconfig-checker + - name: Check Editorconfig + run: editorconfig-checker + - name: Analysing the code with pylint + run: pylint --disable=C0111,C0114,C0115,C0116 $(git ls-files '*.py') + - name: Markdownlint config file + run: | + echo '{ + "MD012": false, + "MD013": false, + "line-lenght": false + }' > .markdownlint.json + - name: Markdown lint check + run: markdownlint -i '{**/*.md}' -i '{terraform/*.md}' -i '{*.md}' -i node_modules + UnitTest: + needs: Pylint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + cache: 'pip' + - run: | + pip install flask prometheus_flask_exporter + - name: Run app test + run: | + cd app + python -m unittest app_test.py + CheckforSecrets: + needs: UnitTest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Gitleaks scan + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TrivySecurity: + runs-on: ubuntu-latest + needs: UnitTest + steps: + - uses: actions/checkout@v4 + - name: Trivy Vulnerability Scan + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + ignore-unfixed: true + format: 'sarif' + output: 'trivy-output.sarif' + severity: 'CRITICAL' + - name: Trivy Scan Output + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-output.sarif' + SonarcloudSecurity: + runs-on: ubuntu-latest + needs: UnitTest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: SonarCloud Scan + uses: sonarsource/sonarcloud-github-action@master + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + with: + projectBaseDir: app + args: > + -Dsonar.projectKey=dimitardd_devops-programme + -Dsonar.organization=dimitardd + Build-Test: + name: Build Image and Test + runs-on: ubuntu-latest + if: ${{ !cancelled() && !failure() }} + needs: [ SonarcloudSecurity, TrivySecurity, CheckforSecrets ] + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Build an image from Dockerfile + run: | + docker build -t ${{ env.IMAGE_TAG }}:${{ github.sha }} . + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.28.0 + with: + image-ref: '${{ env.IMAGE_TAG }}:${{ github.sha }}' + format: 'sarif' + output: 'trivy-results-container.sarif' + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: 'trivy-results-container.sarif' + UploadtoDockerHub: + name: Push container to docker hub + runs-on: ubuntu-latest + if: ${{ !cancelled() && !failure() }} + needs: [ Build-Test ] + steps: + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Push + uses: docker/build-push-action@v6 + with: + push: true + tags: '${{ env.IMAGE_TAG }}:${{ github.sha }}' + if: success() # Only push if get login is ok + PushforArgoCD: + name: Update deplayment for ArgoCD + runs-on: ubuntu-latest + if: ${{ !cancelled() && !failure() }} + needs: [ UploadtoDockerHub ] + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Update sof-app01 deployment image + run: | + sudo snap install yq + sudo apt-get install git -y + IMAGE_NEWTAG=$(echo ${{ github.sha }}) + /usr/bin/yq eval ".spec.template.spec.containers[0].image = \"dimitardd/dimitar-app02:${IMAGE_NEWTAG}\"" -i deployment/sof-app01.yaml + git config --local user.email "dhd.dimitrov@gmail.com" + git config --local user.name "Dimitar Dimitrov" + git add $GITHUB_WORKSPACE/deployment/sof-app01.yaml + git commit -m "Update image to ${{ github.sha }}" + git push origin main + env: + github_token: ${{ secrets.GIT_TOKEN }} + GITHUB_SHA: ${{ github.sha }} diff --git a/.gitignore b/.gitignore index 68bc17f9f..55e99037b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,16 @@ __pycache__/ # C extensions *.so +# terrafomr +*.terraform.lock.hcl +terraform/.terraform +**/.terraform/* +terraform/.terraform.lock.hcl +terrafrom/*/.terraform.lock.hcl +terraform/**/*/.terraform.lock.hcl +terrafrom/**/.terraform.lock.hcl +terrafrom/kubernetes/~/ + # Distribution / packaging .Python build/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..1b599c588 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: check-yaml + - id: trailing-whitespace + - id: check-added-large-files + - id: check-merge-conflict +- repo: https://github.com/gitleaks/gitleaks + rev: v8.18.0 + hooks: + - id: gitleaks +- repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.97.0 + hooks: + - id: terraform_fmt + args: + - --args=--recursive +- repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..1286d0abe --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM ubuntu:22.04 + +LABEL maintainer="dhd.dimitrov@gmail.com" +LABEL version="5.0" + +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + python3 \ + python3-pip && \ + apt-get clean && \ + groupadd -r appgroup && \ + useradd -r -g appgroup appuser + +WORKDIR /app + +COPY --chown=appuser:appgroup --chmod=755 ./app . + +RUN pip3 install flask==3.0.0 prometheus_flask_exporter + +USER appuser + +CMD ["python3", "app.py"] diff --git a/M1-3-Ansible/README.md b/M1-3-Ansible/README.md deleted file mode 100644 index e44faf9c3..000000000 --- a/M1-3-Ansible/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# M1-3-1 Configuration Management - -## Ansible Task - -Create an Ansible playbook that build, push and then run the Docker image for the Python -application. Let your playbook has the following variables: - -* `image_name` - contains the name of your image without the tag, i.e. `vutoff/python-app` -* `image_tag` - contains the tag you tagged your image with, i.e. `v0.2` -* `listen_port` - contains the listening port you're binding your app to. - -Make sure that you set environment variable `PORT` when you define your container -in the Ansible playbook that takes its value from `listen_port` variable. - -Use Ansible modules. Do not shell out. - -### Requirements - -* Make sure you have Python installed. Any version above 3.8 would suffice. -* The `requirements.txt` file in this directory contains the required Ansible version. Run - -```sh -pip install -r requirements.txt -``` - -* Make sure that Docker is running on your local machine. - -### Mind the following - -* If you're running Docker Desktop or Rancher Desktop, mind the location of the `docker.sock` file. The location of the socket file is - * Docker Desktop - `${HOME}/.docker/run/docker.sock` - * Rancher DEsktop - ${HOME}/.rd/run/docker.sock - -* If you're using one of the above, when you write your Ansible playbook you -must specify the path to the docker socket with the parameter `docker_host`, -i.e. `docker_host: "unix://{{ ansible_env.HOME }}/.rd/docker.sock"`. diff --git a/M1-4-2-CI-Practice/README.md b/M1-4-2-CI-Practice/README.md deleted file mode 100644 index 761192ab9..000000000 --- a/M1-4-2-CI-Practice/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# GitHub Actions Practice - -## Prerequisites - -- Organize your git repo to follow the guidelines provides in the presentation - -```sh -β”œβ”€β”€ .editorconfig -β”œβ”€β”€ .github -β”‚Β Β  └── workflows -β”‚Β Β  └── ci-pipeline.yml -β”œβ”€β”€ .gitignore -β”œβ”€β”€ .markdownlint.json -β”œβ”€β”€ .python-version -β”œβ”€β”€ Dockerfile -β”œβ”€β”€ LICENSE -β”œβ”€β”€ README.md -β”œβ”€β”€ ansible -β”‚Β Β  β”œβ”€β”€ README.md -β”‚Β Β  └── playbook.yml -β”œβ”€β”€ app -β”‚Β Β  β”œβ”€β”€ README.md -β”‚Β Β  β”œβ”€β”€ app.py -β”‚Β Β  β”œβ”€β”€ app_test.py -β”‚Β Β  └── requirements.txt -└── requirements.txt -``` - -## Task description - -Create a GitHub Actions pipeline that runs on commit to a feature branch (i.e. not `main`) and performs the following checks on our simple Flask app repository. - -- Check `.editorconfig` -- Code Lint and style - use `pylint` and `black` to check for style/formatting/syntax errors -- Check makrdown files [markdownlint-cli](https://www.npmjs.com/package/cli-markdown) -- Code Unittest - there's a simple unit test next to our app called `app_test.py`. Make sure our unittest passes (`python -m unittest` executed in the app directory) -- Check for hardcoded secrets (`gitleaks`) - not just our app but the whole repository. -- SAST - SonarCloud; Review code smells and security issues -- SCA - Snyk; review security issues -- Build a Docker image. Use Git commit SHA as an Image tag. -- Scan the built image with `Trivy` -- Push the built image to your Docker HUB account -- (optional) Add CONTRIBUTORS guide. Follow [this](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/setting-guidelines-for-repository-contributors) document from GitHUb. - -:warning: Make sure that you run as many tests in parallel as you see fit - -:warning: Make sure you don't push your image to Docker HUB if Critical vulnerabilities are found - -:warning: Try and use ready-made GH Actions. Avoid shell-out if possible - -:exclamation: At the end open a PR with your solution. - -## Extra effort - -- Create a pre-commit hook that safeguards for the following - - hardcoded secrets (`gitleaks`) - - yamllint - - check-merge-conflict - - check-added-large-files -- Setup docker-compose with build and run a container -- Try out GitHub Actions schedule trigger event - - diff --git a/README.md b/README.md index d19dfd95a..d815edc2c 100644 --- a/README.md +++ b/README.md @@ -1 +1,24 @@ -# devops-programme \ No newline at end of file +# GitHub Actions Demo + +This repository demonstrates a CI/CD pipeline using GitHub Actions. The workflow includes code quality checks, unit testing, security scans, and deployment processes to Docker Hub and ArgoCD. + +## Overview + +The GitHub Actions workflow is triggered on pushes to the `main` and `dimitardd-281024` branches, excluding changes in the `deployment/`, `terraform/`, and `rollout/` directories. It performs the following tasks: + +1. **Code Analysis**: Checks code quality using tools like Pylint, Black, Flake8, and Markdownlint. +2. **Unit Testing**: Runs unit tests for the application. +3. **Secret Scanning**: Scans the codebase for sensitive information. +4. **Security Scanning**: Uses Trivy to scan for vulnerabilities in the application and Docker images. +5. **Deployment**: Builds Docker images, pushes them to Docker Hub, and updates the ArgoCD deployment. + +## Prerequisites + +- A GitHub repository where you can add this workflow. +- Docker Hub account for pushing container images. +- Secrets set up in GitHub repository settings: + - `GITHUB_TOKEN` + - `SONAR_TOKEN` + - `DOCKERHUB_USERNAME` + - `DOCKERHUB_TOKEN` + - `GIT_TOKEN` diff --git a/_homework/m1-3-1-docker.yml b/_homework/m1-3-1-docker.yml new file mode 100644 index 000000000..790d6e20c --- /dev/null +++ b/_homework/m1-3-1-docker.yml @@ -0,0 +1,59 @@ +- name: Build, push and run Docker image M1-3-1 Ansible + hosts: localhost + gather_facts: no + + vars: + image_name: "dimitar-app02" + image_tag: "v4.19" + listen_port: "3000" + access_port: "5000" + dockerpath: "/opt/telerik/build" + dockerhubuser: "dimitardd" + gitbanch: "dimitardd-281024" + + vars_files: + - /opt/telerik/secrets/secret.yaml + + + tasks: + - name: Get Dockerfile lates version! + git: + repo: git@github.com:dimitardd/devops-programme.git + dest: "{{ dockerpath }}" + version: "{{ gitbanch }}" + force: yes + register: git_status + + - name: Build Container Image! + when: git_status.changed == true + docker_image: + name: "{{ image_name }}" + tag: "{{ image_tag }}" + build: + path: "{{ dockerpath }}" + dockerfile: Dockerfile + source: build + state: present + + - name: Tag image with lates! + when: git_status.changed == true + command: docker image tag "{{ image_name }}:{{ image_tag }}" "{{ image_name }}:latest" "dimitardd/{{ image_name }}" +# command: docker image tag "{{ image_name }}:{{ image_tag }}" "dimitardd/{{ image_name }}" + + - name: Log into DockerHub + community.docker.docker_login: + username: "{{ dockerhubuser }}" + password: "{{ dockerhub }}" + + - name: Pus docker image to Docker HUB! + when: git_status.changed == true + command: docker image push "dimitardd/{{ image_name }}" + + - name: Run Docker container! + community.docker.docker_container: + name: "{{ image_name }}" + image: "{{ image_name }}:{{ image_tag }}" + state: started + restart_policy: always + published_ports: + - "{{ access_port }}:{{ listen_port }}" diff --git a/app/app.py b/app/app.py index 67e0180c0..af668ea1d 100644 --- a/app/app.py +++ b/app/app.py @@ -1,14 +1,14 @@ import os -from flask import Flask +from flask import Flask # type: ignore +from prometheus_flask_exporter import PrometheusMetrics # type: ignore app = Flask(__name__) - +metrics = PrometheusMetrics(app) @app.route("/") def hello_world(): - return "Hello, World!" - + return "Hello, DevOps World!" if __name__ == "__main__": app.run(port=os.environ.get("PORT", 3000), host="0.0.0.0") diff --git a/app/app_test.py b/app/app_test.py index a1b1bacb2..382bc27f4 100644 --- a/app/app_test.py +++ b/app/app_test.py @@ -10,7 +10,7 @@ def setUp(self): def test_hello_world(self): response = self.client.get("/") self.assertEqual(response.status_code, 200) - self.assertEqual(response.data, b"Hello, World!") + self.assertEqual(response.data, b"Hello, DevOps World!") if __name__ == "__main__": diff --git a/deployment/sof-app01-hpa.yaml b/deployment/sof-app01-hpa.yaml new file mode 100644 index 000000000..2e3ec9dc3 --- /dev/null +++ b/deployment/sof-app01-hpa.yaml @@ -0,0 +1,37 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: sof-app01-hpa + namespace: sof-app01 +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: sof-app01 + minReplicas: 3 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 50 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 50 + behavior: + scaleDown: + stabilizationWindowSeconds: 60 + policies: + - type: Pods + value: 2 + periodSeconds: 60 +# - type: Percent +# value: 50 +# periodSeconds: 60 + selectPolicy: Max + diff --git a/deployment/sof-app01-ingress.yaml b/deployment/sof-app01-ingress.yaml new file mode 100644 index 000000000..5f42387bc --- /dev/null +++ b/deployment/sof-app01-ingress.yaml @@ -0,0 +1,24 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: sof-app01-ingress + namespace: sof-app01 + annotations: + kubernetes.io/spec.ingressClassName: "nginx" +spec: + rules: + - host: sof-app01.pt.playtech.corp + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: sof-app01-service + port: + number: 3000 + tls: + - hosts: + - sof-app01.pt.playtech.corp + secretName: sof-app01-tls # pragma: allowlist secret + diff --git a/deployment/sof-app01-secret.yaml b/deployment/sof-app01-secret.yaml new file mode 100644 index 000000000..9bc6066a6 --- /dev/null +++ b/deployment/sof-app01-secret.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Secret +metadata: + name: sof-app01-tls + namespace: sof-app01 + annotations: + vault.hashicorp.com/agent-inject: "true" + vault.hashicorp.com/role: "sof-app01" + vault.hashicorp.com/agent-inject-secret-tls.crt: "kv/sof-app01" + vault.hashicorp.com/agent-inject-secret-tls.key: "kv/sof-app01" + vault.hashicorp.com/agent-inject-template-tls.crt: | + {{- with secret "kv/sof-app01" -}} + {{ .Data.data.cert}} + {{- end -}} + vault.hashicorp.com/agent-inject-template-tls.key: | + {{- with secret "kv/sof-app01" -}} + {{ .Data.data.key}} + {{- end -}} +type: kubernetes.io/tls +data: + tls.crt: "" + tls.key: "" + diff --git a/deployment/sof-app01-svc.yaml b/deployment/sof-app01-svc.yaml new file mode 100644 index 000000000..283bb03fb --- /dev/null +++ b/deployment/sof-app01-svc.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: sof-app01-service + namespace: sof-app01 + labels: + app: sof-app01 +spec: + selector: + app: sof-app01 + ports: + - name: 3000tcp + protocol: TCP + port: 3000 + targetPort: 3000 + type: ClusterIP + diff --git a/deployment/sof-app01.yaml b/deployment/sof-app01.yaml new file mode 100644 index 000000000..817fdb57f --- /dev/null +++ b/deployment/sof-app01.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sof-app01 + namespace: sof-app01 + labels: + app: sof-app01 +spec: + # replicas: 9 + selector: + matchLabels: + app: sof-app01 + template: + metadata: + labels: + app: sof-app01 + spec: + serviceAccountName: sof-app01-sa + containers: + - name: sof-app01 + image: dimitardd/dimitar-app02:459a77b624815c64d21a78a8eb4b014fba74ceac #Auto deplayed from build + # image: dimitardd/dimitar-app02:b07c7bf5c0df29652dd1c86ec06463770388fb98 #Green + # image: dimitardd/dimitar-app02:207e179a46ce047db5ac0d73ec2a1b0fb9352514 #Blue + # image: dimitardd/dimitar-app01:e0b1dc1bb0edf9e161d17d7ba1565844b64e8e37 #Yellow + ports: + - containerPort: 3000 + resources: + requests: + memory: "256Mi" + cpu: "500m" + limits: + memory: "512Mi" + cpu: "1" diff --git a/rollout/sof-app02-ingress.yaml b/rollout/sof-app02-ingress.yaml new file mode 100644 index 000000000..a0f914786 --- /dev/null +++ b/rollout/sof-app02-ingress.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: sof-app02 + namespace: sof-app02 + annotations: + kubernetes.io/ingress.class: "nginx" +spec: + rules: + - host: sof-app02.pt.playtech.corp + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: sof-app02 + port: + number: 3000 diff --git a/rollout/sof-app02-service.yaml b/rollout/sof-app02-service.yaml new file mode 100644 index 000000000..494d56f91 --- /dev/null +++ b/rollout/sof-app02-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: sof-app02 + namespace: sof-app02 +spec: + ports: + - port: 3000 + targetPort: http + protocol: TCP + name: http + selector: + app: sof-app02 diff --git a/rollout/sof-app02.yaml b/rollout/sof-app02.yaml new file mode 100644 index 000000000..4f66cb681 --- /dev/null +++ b/rollout/sof-app02.yaml @@ -0,0 +1,49 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: sof-app02 + namespace: sof-app02 +spec: + replicas: 5 + strategy: + canary: + steps: + - setWeight: 20 + - pause: {} + # - analysis: + # template: sof-app02-analysis + # arguments: {} + - setWeight: 40 + - pause: {duration: 10} + - setWeight: 60 + - pause: {duration: 10} + - setWeight: 80 + - pause: {duration: 10} + revisionHistoryLimit: 2 + selector: + matchLabels: + app: sof-app02 + template: + metadata: + labels: + app: sof-app02 + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/path: /metrics + prometheus.io/port: "3000" + spec: + containers: + - name: sof-app02 + image: dimitardd/dimitar-app02:2b80cf31bff5f03d0e97357ffca9a5c1c08bdfb2 #DevOps +# image: dimitardd/dimitar-app02:57bb7e9bcd3546159be0917df7e87526d8170140 #Blue +# image: dimitardd/dimitar-app02:e0b1dc1bb0edf9e161d17d7ba1565844b64e8e37 #Fresh + ports: + - name: http + containerPort: 3000 + protocol: TCP + resources: + requests: + memory: 32Mi + cpu: 5m + diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 000000000..b10508e28 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,92 @@ +# Terraform Helm Chart Deployment + +This repository contains Terraform code for deploying ArgoCD and Argo Rollouts using Helm charts within a Kubernetes cluster. The Helm releases are configured to be deployed in the `argocd` namespace. + +## Prerequisites + +- Terraform installed on your machine. +- Access to a Kubernetes cluster. +- `kubectl` configured to interact with your Kubernetes cluster. +- Helm installed on your machine. + +## Getting Started + +1. **Clone the Repository**: + Clone this repository to your local machine. + + + git clone https://github.com/dimitardd/devops-programme.git + cd devops-programme + + +2. **Configure Kubernetes Context**: + Ensure your Kubernetes configuration is set up correctly. The configuration file is expected to be located at `~/.kube/sof-lab03`. + +3. **Set Up Terraform Backend**: + The Terraform state file will be stored locally at `/Users/dimitar.dimitrov/devops/telerik/terraform.tfstate`. Ensure that this path is accessible. + +4. **Required Values Files**: + Make sure that the following values files are present in the `values` directory: + - `argocd.yaml` + - `argo-rollouts.yaml` + +5. **Initialize Terraform**: + Run the following command to initialize Terraform, which will download the necessary providers: + + terraform init + +6. **Plan the Deployment**: + Generate an execution plan to review the resources that will be created: + + + terraform plan + + +7. **Apply the Configuration**: + Apply the Terraform configuration to deploy the Helm charts: + + + terraform apply + + + Confirm the action when prompted. + +## Resources + +### Helm Releases + +- **ArgoCD**: + - **Name**: `argo-cd-7-1734333419` + - **Chart**: `argo-cd` + - **Version**: `7.7.15` + - **Repository**: [Helm Repository](http://slo-it-nexus01.pt.playtech.corp/repository/helm-argocd) + +- **Argo Rollouts**: + - **Name**: `argo-rollouts` + - **Chart**: `argo-rollouts` + - **Version**: `2.38.2` + - **Repository**: [Helm Repository](http://slo-it-nexus01.pt.playtech.corp/repository/helm-argocd) + +### Kubernetes Namespace + +- **Namespace**: `sof-app01` +- This namespace will be created for your application resources. + +## Providers + +- **Helm**: + - Configured to use the Kubernetes config located at `~/.kube/sof-lab03`. + +- **Kubernetes**: + - Configured to use the Kubernetes config located at `~/.kube/sof-lab03` with the context `sof-lab03`. + +## Cleanup + +To remove the deployed resources, you can run: + + +terraform destroy + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/terraform/argo-rollouts/argo-rollouts.tf b/terraform/argo-rollouts/argo-rollouts.tf new file mode 100644 index 000000000..f16e75151 --- /dev/null +++ b/terraform/argo-rollouts/argo-rollouts.tf @@ -0,0 +1,11 @@ +resource "helm_release" "argo-rollouts" { + name = "argo-rollouts" + + repository = "http://slo-it-nexus01.pt.playtech.corp/repository/helm-argocd" + chart = "argo-rollouts" + namespace = "argo-rollouts" + create_namespace = false + version = "2.38.2" + + values = [file("../values/argo-rollouts.yaml")] +} diff --git a/terraform/argo-rollouts/provider.tf b/terraform/argo-rollouts/provider.tf new file mode 100644 index 000000000..7ad472ba8 --- /dev/null +++ b/terraform/argo-rollouts/provider.tf @@ -0,0 +1,16 @@ +provider "helm" { + kubernetes { + config_path = "~/.kube/sof-lab03" + } +} + +provider "kubernetes" { + config_path = "~/.kube/sof-lab03" + config_context = "sof-lab03" +} + +terraform { + backend "local" { + path = "/Users/dimitar.dimitrov/devops/telerik/terraform-argo-rollouts.tfstate" + } +} diff --git a/terraform/argocd/argo-install.tf b/terraform/argocd/argo-install.tf new file mode 100644 index 000000000..d844ae121 --- /dev/null +++ b/terraform/argocd/argo-install.tf @@ -0,0 +1,11 @@ +resource "helm_release" "argocd" { + name = "argo-cd-7-1734333419" + + repository = "http://slo-it-nexus01.pt.playtech.corp/repository/helm-argocd" + chart = "argo-cd" + namespace = "argocd" + create_namespace = false + version = "7.7.15" + + values = [file("../values/argocd.yaml")] +} diff --git a/terraform/argocd/provider.tf b/terraform/argocd/provider.tf new file mode 100644 index 000000000..bb50b2388 --- /dev/null +++ b/terraform/argocd/provider.tf @@ -0,0 +1,16 @@ +provider "helm" { + kubernetes { + config_path = "~/.kube/sof-lab03" + } +} + +provider "kubernetes" { + config_path = "~/.kube/sof-lab03" + config_context = "sof-lab03" +} + +terraform { + backend "local" { + path = "/Users/dimitar.dimitrov/devops/telerik/terraform-argocd.tfstate" + } +} diff --git a/terraform/kubernetes/namespace.tf b/terraform/kubernetes/namespace.tf new file mode 100644 index 000000000..6f740bc05 --- /dev/null +++ b/terraform/kubernetes/namespace.tf @@ -0,0 +1,23 @@ +resource "kubernetes_namespace" "sof-app01" { + metadata { + name = "sof-app01" + } +} + +resource "kubernetes_namespace" "sof-app02" { + metadata { + name = "sof-app02" + } +} + +resource "kubernetes_namespace" "argocd" { + metadata { + name = "argocd" + } +} + +resource "kubernetes_namespace" "argo-rollouts" { + metadata { + name = "argo-rollouts" + } +} diff --git a/terraform/kubernetes/provider.tf b/terraform/kubernetes/provider.tf new file mode 100644 index 000000000..198ec822c --- /dev/null +++ b/terraform/kubernetes/provider.tf @@ -0,0 +1,10 @@ +provider "kubernetes" { + config_path = "~/.kube/sof-lab03" + config_context = "sof-lab03" +} + +terraform { + backend "local" { + path = "/Users/dimitar.dimitrov/devops/telerik/terraform-kubernetes.tfstate" + } +} diff --git a/terraform/sof-app01/argo-app01.tf b/terraform/sof-app01/argo-app01.tf new file mode 100644 index 000000000..f2554d626 --- /dev/null +++ b/terraform/sof-app01/argo-app01.tf @@ -0,0 +1,38 @@ +resource "argocd_application" "sof-app01" { + metadata { + name = "sof-app01" + } + + spec { + project = "default" + + sync_policy { + sync_options = ["Validate=true"] + + retry { + limit = "2" + + backoff { + duration = "5s" + factor = "2" + max_duration = "3m0s" + } + } + automated { + allow_empty = false + prune = false + self_heal = false + } + } + destination { + server = "https://kubernetes.default.svc" + namespace = "sof-app01" + } + + source { + repo_url = "https://github.com/dimitardd/devops-programme" + path = "deployment" + target_revision = "main" + } + } +} diff --git a/terraform/sof-app01/provider.tf b/terraform/sof-app01/provider.tf new file mode 100644 index 000000000..8fcb394c6 --- /dev/null +++ b/terraform/sof-app01/provider.tf @@ -0,0 +1,34 @@ +terraform { + required_version = ">= 1.10" + required_providers { + argocd = { + source = "argoproj-labs/argocd" + version = "7.3.0" + } + } +} + +provider "helm" { + kubernetes { + config_path = "~/.kube/sof-lab03" + } +} + +provider "kubernetes" { + config_path = "~/.kube/sof-lab03" + config_context = "sof-lab03" +} + +provider "argocd" { + server_addr = "localhost:8080" + plain_text = true + insecure = true + username = "admin" + password = var.password +} + +terraform { + backend "local" { + path = "/opt/actions-runner/terraform-argocd-sof-app01.tfstate" + } +} diff --git a/terraform/sof-app01/variables.tf b/terraform/sof-app01/variables.tf new file mode 100644 index 000000000..fd17de099 --- /dev/null +++ b/terraform/sof-app01/variables.tf @@ -0,0 +1,4 @@ +variable "password" { + description = "The password for ArgoCD" + type = string +} diff --git a/terraform/sof-app02/argo-app02.tf b/terraform/sof-app02/argo-app02.tf new file mode 100644 index 000000000..c9d5a19f7 --- /dev/null +++ b/terraform/sof-app02/argo-app02.tf @@ -0,0 +1,38 @@ +resource "argocd_application" "sof-app02" { + metadata { + name = "sof-app02" + } + + spec { + project = "default" + + sync_policy { + sync_options = ["Validate=true"] + + retry { + limit = "1" + + backoff { + duration = "5s" + factor = "2" + max_duration = "3m0s" + } + } + automated { + allow_empty = false + prune = false + self_heal = false + } + } + destination { + server = "https://kubernetes.default.svc" + namespace = "sof-app02" + } + + source { + repo_url = "https://github.com/dimitardd/devops-programme" + path = "rollout" + target_revision = "main" + } + } +} diff --git a/terraform/sof-app02/provider.tf b/terraform/sof-app02/provider.tf new file mode 100644 index 000000000..f8bf15454 --- /dev/null +++ b/terraform/sof-app02/provider.tf @@ -0,0 +1,34 @@ +terraform { + required_version = ">= 1.10" + required_providers { + argocd = { + source = "argoproj-labs/argocd" + version = "7.3.0" + } + } +} + +provider "helm" { + kubernetes { + config_path = "~/.kube/sof-lab03" + } +} + +provider "kubernetes" { + config_path = "~/.kube/sof-lab03" + config_context = "sof-lab03" +} + +provider "argocd" { + server_addr = "localhost:8080" + plain_text = true + insecure = true + username = "admin" + password = var.password +} + +terraform { + backend "local" { + path = "/opt/actions-runner/terraform-argocd-sof-app02.tfstate" + } +} diff --git a/terraform/sof-app02/variables.tf b/terraform/sof-app02/variables.tf new file mode 100644 index 000000000..fd17de099 --- /dev/null +++ b/terraform/sof-app02/variables.tf @@ -0,0 +1,4 @@ +variable "password" { + description = "The password for ArgoCD" + type = string +} diff --git a/terraform/values/argo-rollouts.yaml b/terraform/values/argo-rollouts.yaml new file mode 100644 index 000000000..4b2299781 --- /dev/null +++ b/terraform/values/argo-rollouts.yaml @@ -0,0 +1,9 @@ +--- +global: + image: + tag: "v1.7.2" + +server: + extraArgs: + - --insecure + diff --git a/terraform/values/argocd.yaml b/terraform/values/argocd.yaml new file mode 100644 index 000000000..953cca4f3 --- /dev/null +++ b/terraform/values/argocd.yaml @@ -0,0 +1,9 @@ +--- +global: + image: + tag: "v2.13.3" + +server: + extraArgs: + - --insecure +