trigger: branches: include: - main - develop paths: include: - src/* - templates/* - requirements.txt - Dockerfile - config.json - main.py pr: branches: include: - main paths: include: - src/* - templates/* - requirements.txt - Dockerfile - config.json - main.py variables: # Container registry service connection dockerRegistryServiceConnection: 'dock-ptslondon-connection' imageRepository: 'price-tracker' containerRegistry: 'dock.ptslondon.co.uk' dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile' # Agent VM image name vmImageName: 'ubuntu-latest' stages: - stage: Build displayName: 'Build and Test' jobs: - job: Build displayName: 'Build Docker Image' pool: vmImage: $(vmImageName) variables: tag: '$(Build.BuildId)' steps: - checkout: self displayName: 'Checkout source code' - task: Docker@2 displayName: 'Build Docker image' inputs: command: 'build' repository: $(imageRepository) dockerfile: $(dockerfilePath) tags: | $(tag) latest - script: | echo "Running container tests..." # Start container for testing docker run -d --name price-tracker-test -p 5000:5000 $(imageRepository):$(tag) # Wait for container to be ready echo "Waiting for container to start..." for i in {1..30}; do if curl -f http://localhost:5000/ > /dev/null 2>&1; then echo "Container is ready!" break fi echo "Waiting... ($i/30)" sleep 2 done # Run basic health checks echo "Running health checks..." curl -f http://localhost:5000/ || (echo "Health check failed" && exit 1) # Cleanup docker stop price-tracker-test docker rm price-tracker-test echo "All tests passed!" displayName: 'Test Docker container' - task: Docker@2 displayName: 'Push to registry' inputs: command: 'push' repository: $(imageRepository) containerRegistry: $(dockerRegistryServiceConnection) tags: | $(tag) latest condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) - stage: SecurityScan displayName: 'Security Scanning' dependsOn: Build condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) jobs: - job: VulnerabilityScan displayName: 'Vulnerability Scan' pool: vmImage: $(vmImageName) variables: tag: '$(Build.BuildId)' steps: - task: Docker@2 displayName: 'Pull image for scanning' inputs: command: 'pull' arguments: '$(containerRegistry)/$(imageRepository):$(tag)' containerRegistry: $(dockerRegistryServiceConnection) - script: | # Install Trivy sudo apt-get update sudo apt-get install wget apt-transport-https gnupg lsb-release -y wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list sudo apt-get update sudo apt-get install trivy -y # Run vulnerability scan trivy image --exit-code 0 --severity LOW,MEDIUM --format table $(containerRegistry)/$(imageRepository):$(tag) trivy image --exit-code 1 --severity HIGH,CRITICAL --format table $(containerRegistry)/$(imageRepository):$(tag) displayName: 'Run Trivy vulnerability scan' continueOnError: true - stage: DeployDev displayName: 'Deploy to Development' dependsOn: - Build - SecurityScan condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) jobs: - deployment: DeployToDev displayName: 'Deploy to Development Environment' environment: 'price-tracker-dev' pool: vmImage: $(vmImageName) variables: tag: '$(Build.BuildId)' strategy: runOnce: deploy: steps: - checkout: self - task: Docker@2 displayName: 'Pull latest image' inputs: command: 'pull' arguments: '$(containerRegistry)/$(imageRepository):$(tag)' containerRegistry: $(dockerRegistryServiceConnection) - script: | # Create deployment directory mkdir -p ~/price-tracker-deployment cd ~/price-tracker-deployment # Copy deployment files cp $(Pipeline.Workspace)/s/docker-compose.yml . cp $(Pipeline.Workspace)/s/config.json . # Update image tag in docker-compose sed -i "s/price-tracker:latest/$(containerRegistry)\/$(imageRepository):$(tag)/g" docker-compose.yml # Deploy using docker-compose docker-compose down || true docker-compose up -d # Wait for deployment sleep 10 # Verify deployment curl -f http://localhost:5000/ || (echo "Deployment verification failed" && exit 1) echo "Deployment to development completed successfully!" displayName: 'Deploy to development' - stage: DeployProd displayName: 'Deploy to Production' dependsOn: - Build - SecurityScan condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs: - deployment: DeployToProd displayName: 'Deploy to Production Environment' environment: 'price-tracker-prod' pool: vmImage: $(vmImageName) variables: tag: '$(Build.BuildId)' strategy: runOnce: deploy: steps: - checkout: self - task: Docker@2 displayName: 'Pull latest image' inputs: command: 'pull' arguments: '$(containerRegistry)/$(imageRepository):$(tag)' containerRegistry: $(dockerRegistryServiceConnection) - script: | # Production deployment script echo "Deploying to production..." # Create deployment directory mkdir -p ~/price-tracker-production cd ~/price-tracker-production # Copy deployment files cp $(Pipeline.Workspace)/s/docker-compose.yml . cp $(Pipeline.Workspace)/s/config.json . # Update image tag in docker-compose for production sed -i "s/price-tracker:latest/$(containerRegistry)\/$(imageRepository):$(tag)/g" docker-compose.yml # Production-specific environment settings export FLASK_ENV=production # Deploy with zero-downtime strategy docker-compose pull docker-compose up -d # Health check sleep 15 for i in {1..10}; do if curl -f http://localhost:5000/; then echo "Production deployment successful!" break fi echo "Waiting for production deployment... ($i/10)" sleep 5 done displayName: 'Deploy to production' - script: | # Post-deployment notifications (optional) echo "Production deployment completed at $(date)" # You can add notification webhooks here displayName: 'Post-deployment tasks'