Skip to content
Open

Lab09 #3249

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/workflows/ansible-deploy-bonus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Ansible Deployment (Bonus App)

on:
push:
branches: [ main, master ]
paths:
- 'ansible/vars/app_bonus.yml'
- 'ansible/playbooks/deploy_bonus.yml'
- 'ansible/playbooks/deploy_all.yml'
- 'ansible/roles/web_app/**'
- '.github/workflows/ansible-deploy-bonus.yml'
pull_request:
branches: [ main, master ]
paths:
- 'ansible/vars/app_bonus.yml'
- 'ansible/playbooks/deploy_bonus.yml'
- 'ansible/roles/web_app/**'
- '.github/workflows/ansible-deploy-bonus.yml'

jobs:
lint:
name: Ansible Lint (Bonus)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ansible ansible-lint
ansible-galaxy collection install -r ansible/requirements.yml

- name: Run ansible-lint
run: |
cd ansible
ansible-lint playbooks/deploy_bonus.yml playbooks/deploy_all.yml

deploy:
name: Deploy Bonus Application
needs: lint
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Ansible and collection
run: |
python -m pip install --upgrade pip
pip install ansible
ansible-galaxy collection install -r ansible/requirements.yml

- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "${{ secrets.VM_HOST }}" >> ~/.ssh/known_hosts

- name: Build runtime inventory
run: |
cat <<EOF > ansible/inventory/ci_hosts.ini
[all]
target ansible_host=${{ secrets.VM_HOST }} ansible_user=${{ secrets.VM_USER }} ansible_port=22
EOF

- name: Deploy bonus app with Ansible
env:
ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
APP_SECRET_KEY: ${{ secrets.APP_SECRET_KEY }}
run: |
cd ansible
printf '%s' "$ANSIBLE_VAULT_PASSWORD" > /tmp/vault_pass
ansible-playbook -i inventory/ci_hosts.ini playbooks/deploy_bonus.yml \
--vault-password-file /tmp/vault_pass
rm -f /tmp/vault_pass

- name: Verify Bonus App Deployment
run: |
sleep 10
curl -fsS "http://${{ secrets.VM_HOST }}:8001" > /dev/null
curl -fsS "http://${{ secrets.VM_HOST }}:8001/health" > /dev/null
98 changes: 98 additions & 0 deletions .github/workflows/ansible-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Ansible Deployment (Python App)

on:
push:
branches: [ main, master ]
paths:
- 'ansible/vars/app_python.yml'
- 'ansible/playbooks/deploy_python.yml'
- 'ansible/playbooks/deploy.yml'
- 'ansible/roles/web_app/**'
- 'ansible/roles/docker/**'
- 'ansible/roles/common/**'
- 'ansible/group_vars/**'
- 'ansible/inventory/**'
- 'ansible/requirements.yml'
- '.github/workflows/ansible-deploy.yml'
- '!ansible/docs/**'
pull_request:
branches: [ main, master ]
paths:
- 'ansible/**'
- '.github/workflows/ansible-deploy.yml'

jobs:
lint:
name: Ansible Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install ansible ansible-lint
ansible-galaxy collection install -r ansible/requirements.yml

- name: Run ansible-lint
run: |
cd ansible
ansible-lint playbooks/*.yml

deploy:
name: Deploy Python Application
needs: lint
runs-on: ubuntu-latest
if: github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Install Ansible and collection
run: |
python -m pip install --upgrade pip
pip install ansible
ansible-galaxy collection install -r ansible/requirements.yml

- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H "${{ secrets.VM_HOST }}" >> ~/.ssh/known_hosts

- name: Build runtime inventory
run: |
cat <<EOF > ansible/inventory/ci_hosts.ini
[all]
target ansible_host=${{ secrets.VM_HOST }} ansible_user=${{ secrets.VM_USER }} ansible_port=22
EOF

- name: Deploy with Ansible
env:
ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
APP_SECRET_KEY: ${{ secrets.APP_SECRET_KEY }}
run: |
cd ansible
printf '%s' "$ANSIBLE_VAULT_PASSWORD" > /tmp/vault_pass
ansible-playbook -i inventory/ci_hosts.ini playbooks/deploy_python.yml \
--vault-password-file /tmp/vault_pass
rm -f /tmp/vault_pass

- name: Verify Deployment
run: |
sleep 10
curl -fsS "http://${{ secrets.VM_HOST }}:8000" > /dev/null
curl -fsS "http://${{ secrets.VM_HOST }}:8000/health" > /dev/null
198 changes: 198 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
name: Python CI/CD Pipeline

on:
push:
branches: [ main, master, develop, lab* ]
paths:
- 'app_python/**'
- '.github/workflows/python-ci.yml'
pull_request:
branches: [ main, master ]
paths:
- 'app_python/**'
- '.github/workflows/python-ci.yml'

env:
REGISTRY: docker.io
IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/devops-info-service
PYTHON_VERSION: '3.13'
DOCKERFILE_PATH: './app_python/Dockerfile'
CONTEXT_PATH: './app_python'

jobs:
test:
name: Test & Lint
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./app_python

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: 'app_python/requirements*.txt'

- name: Cache Python dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('app_python/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
pip install pylint pytest pytest-cov

- name: Run linting with pylint
run: |
pylint --fail-under=8.0 *.py tests/*.py || echo "Linting warnings found, but continuing..."

- name: Run tests with pytest and coverage
run: |
pytest tests/ -v --cov=. --cov-report=xml --cov-report=term-missing --cov-fail-under=70

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
file: ./coverage.xml
flags: python
name: python-coverage
fail_ci_if_error: false

security-scan:
name: Security Scan with Snyk
runs-on: ubuntu-latest
needs: test
defaults:
run:
working-directory: ./app_python

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install dependencies
run: |
pip install -r requirements.txt
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi

- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/python@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high --file=requirements.txt

docker:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: [test, security-scan]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Generate version tag (CalVer)
id: version
run: |
# Calendar Versioning: YYYY.MM.DD
DATE_TAG=$(date +'%Y.%m.%d')
# Add build number for uniqueness
FULL_TAG="${DATE_TAG}-${{ github.run_number }}"
SHA_TAG="${FULL_TAG}-${{ github.sha }}"
echo "version=${FULL_TAG}" >> $GITHUB_OUTPUT
echo "date=${DATE_TAG}" >> $GITHUB_OUTPUT
echo "sha=${SHA_TAG}" >> $GITHUB_OUTPUT
echo "latest=latest" >> $GITHUB_OUTPUT

- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ steps.version.outputs.version }}
type=raw,value=${{ steps.version.outputs.sha }}
type=raw,value=${{ steps.version.outputs.date }}
type=raw,value=latest
type=sha,format=short
labels: |
org.opencontainers.image.title=DevOps Info Service
org.opencontainers.image.description=FastAPI service for DevOps course
org.opencontainers.image.version=${{ steps.version.outputs.version }}
org.opencontainers.image.created=${{ steps.version.outputs.date }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ${{ env.CONTEXT_PATH }}
file: ${{ env.DOCKERFILE_PATH }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_DATE=${{ steps.version.outputs.date }}
VERSION=${{ steps.version.outputs.version }}
VCS_REF=${{ github.sha }}

docker-build-test:
name: Test Docker Build
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'pull_request' || (github.event_name == 'push' && startsWith(github.ref_name, 'lab'))

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker image (test only, no push)
uses: docker/build-push-action@v5
with:
context: ${{ env.CONTEXT_PATH }}
file: ${{ env.DOCKERFILE_PATH }}
push: false
tags: ${{ env.IMAGE_NAME }}:test-${{ github.sha }}
cache-from: type=gha
load: true

- name: Test Docker image
run: |
docker run --rm -d -p 8000:8000 --name test-app ${{ env.IMAGE_NAME }}:test-${{ github.sha }}
sleep 5
curl -f http://localhost:8000/health || exit 1
docker stop test-app
Loading