diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..121a2e5 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,181 @@ +name: Build and Push Docker image to AWS ECR + +on: + push: + branches: + - development + paths-ignore: + - 'README.md' +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + with: + fetch-depth: 0 # Necessary to fetch all tags and history + +################################################################ +### SONAR CLOUD SCAN ### +### Drops the build if any bugs or vulnerabilities are found.### +### Using the default quality gate. ### +### Connected to my personal Sonar Cloud account ### +################################################################ + + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: Setup Git + run: | + git config --global user.name 'github-actions' + git config --global user.email 'github-actions@github.com' + +################################################################ +### DETERMINE NEXT VERSION ### +### Used for creating new releases and image tags ### +################################################################ + + - name: Determine Next Version + id: next_version + run: | + # Fetch all tags + git fetch --tags + + # Get the latest tag, assume semver, and sort. + LATEST_TAG=$(git tag -l | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n1) + + # If there's no tag yet, start with v0.0.0. Used for new repos + if [ -z "$LATEST_TAG" ]; then + LATEST_TAG="v0.0.0" + fi + + # Increment the patch version + NEXT_TAG=$(echo $LATEST_TAG | awk -F. '{print $1"."$2"."$3+1}') + + # Output the next version + echo "::set-output name=tag::$NEXT_TAG" + echo "Next version: $NEXT_TAG" + +################################################################ +### CREATE RELEASE ### +### Creating release with the tag from the previous step ### +################################################################ + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN_2 }} + with: + tag_name: ${{ steps.next_version.outputs.tag }} + release_name: Release ${{ steps.next_version.outputs.tag }} + draft: false + prerelease: false + +################################################################ +### BUILD DOCKER IMAGE ### +### Build Docker image from the Dockefile ### +################################################################ + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Extract repository name + id: repo-name + run: | + REPO_NAME="${GITHUB_REPOSITORY##*/}" + echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV + echo "::set-output name=repo_name::$REPO_NAME" + + - name: Build Docker image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: ${{ env.REPO_NAME }} + IMAGE_TAG: ${{ steps.next_version.outputs.tag }} + run: | + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + echo "IMAGE_NAME=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_ENV + +########################################################### +### Docker image Snyk scan | If fails, drop the action ### +### Connected to my personal Snyk account ### +### The code owner receives an email notification ### +### Possible to configure Slack notification if needed ### +########################################################### + + - name: Run Snyk to check Docker image for vulnerabilities + uses: snyk/actions/docker@master + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + image: ${{ env.IMAGE_NAME }} + args: --severity-threshold=high --policy-path=.snyk + continue-on-error: false + +########################################################### +### PUSH IMAGE TO ECR ### +### Tag Docker image as "latest" and push to ECR ### +########################################################### + + - name: Push Docker image to Amazon ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: py-aws-cicd + IMAGE_TAG: ${{ steps.next_version.outputs.tag }} + run: | + # Tag the image as latest + docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest + # Push the specific version tag + docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG + # Push the latest tag + docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest + + - name: Deploy to EC2 + env: + EC2_PEM_KEY: ${{ secrets.EC2_PEM_KEY }} + EC2_HOST: ${{ secrets.EC2_HOST }} + EC2_USER: ${{ secrets.EC2_USER }} + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: py-aws-cicd + IMAGE_TAG: ${{ steps.next_version.outputs.tag }} + run: | + # Save PEM key to file and set permissions + echo "$EC2_PEM_KEY" > ec2.pem + chmod 400 ec2.pem + + # SSH, SCP commands + SSH_COMMAND="ssh -i ec2.pem -o StrictHostKeyChecking=no $EC2_USER@$EC2_HOST" + SCP_COMMAND="scp -i ec2.pem -o StrictHostKeyChecking=no" + + #Login to Docker registry + $SSH_COMMAND "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY aws ecr get-login-password --region us-east-1 | sudo docker login --username AWS --password-stdin $ECR_REGISTRY" + + # Create app dir if missing + $SSH_COMMAND "mkdir -p /home/$EC2_USER/docker/data" + + # Copy docker-compose.yml to EC2 server + $SCP_COMMAND docker-compose.yml $EC2_USER@$EC2_HOST:/home/$EC2_USER/docker/ + + # Run compose with env vars injected into the remote shell + $SSH_COMMAND "cd /home/$EC2_USER/docker && \ + sudo --preserve-env=ECR_REGISTRY,ECR_REPOSITORY,IMAGE_TAG \ + env ECR_REGISTRY='$ECR_REGISTRY' \ + ECR_REPOSITORY='$ECR_REPOSITORY' \ + IMAGE_TAG='$IMAGE_TAG' \ + docker compose up -d --pull always --force-recreate" + + # Cleanup PEM key + rm -f ec2.pem + diff --git a/Dockerfile b/Dockerfile index 4afdfea..0b0c4bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ ARG PYTHON_VERSION=3.12.8 -FROM python:${PYTHON_VERSION}-alpine3.19 AS base +FROM python:${PYTHON_VERSION}-alpine3.21 AS base + +# Make sure system packages are always up-to-date +RUN apk update && apk upgrade --no-cache # Prevents Python from writing pyc files. ENV PYTHONDONTWRITEBYTECODE=1 diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..154062f --- /dev/null +++ b/Readme.md @@ -0,0 +1 @@ +This is a demo file diff --git a/counter-app.py b/counter-app.py index de8cf74..058da58 100644 --- a/counter-app.py +++ b/counter-app.py @@ -4,7 +4,7 @@ app = Flask(__name__) # Define the path for the counter file to store the data in Docker Volume -COUNTER_FILE = "data/counter.txt" +COUNTER_FILE = "/data/counter.txt" def read_counter(): """ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f911061 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: '2.4' # The last version of Docker Compose file format that directly supports mem_limit and cpus +services: + counter-service: + container_name: py-aws-cicd + image: "${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}" + volumes: + - ./data:/data + ports: + - "80:8080" + restart: always + mem_limit: 256M + cpus: 0.6 diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..89bdefa --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,18 @@ +sonar.projectKey=Ayaan49_py-aws-cicd +sonar.organization=ayaan49 + +# Display name and version in SonarCloud UI +sonar.projectName=py-aws-cicd +sonar.projectVersion=1.0 + +# Where your source code is (adjust if not in root) +sonar.sources=. + +# Disable C/C++/Obj-C analysis (since this is Python) +sonar.c.file.suffixes=- +sonar.cpp.file.suffixes=- +sonar.objc.file.suffixes=- + +# Encoding +# sonar.sourceEncoding=UTF-8 +