diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..6ab2494 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,76 @@ +pipeline { + agent any + + environment { + DOCKER_CREDENTIALS = credentials('docker-hub-credentials') + DOCKER_IMAGE = "your-dockerhub-username/your-app-name" + DOCKER_TAG = "${env.GIT_COMMIT.take(7)}" + AWS_CREDENTIALS = credentials('aws-credentials') + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Build Docker Image') { + steps { + sh """ + docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} . + docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest + """ + } + } + + stage('Push Docker Image') { + steps { + sh """ + echo ${DOCKER_CREDENTIALS_PSW} | docker login -u ${DOCKER_CREDENTIALS_USR} --password-stdin + docker push ${DOCKER_IMAGE}:${DOCKER_TAG} + docker push ${DOCKER_IMAGE}:latest + """ + } + } + + stage('Update Application Version') { + steps { + sh """ + sed -i 's|docker_image:.*|docker_image: ${DOCKER_IMAGE}:${DOCKER_TAG}|' main-server/ansible/vars.yml + """ + } + } + + stage('Deploy Infrastructure') { + environment { + AWS_ACCESS_KEY_ID = "${AWS_CREDENTIALS_USR}" + AWS_SECRET_ACCESS_KEY = "${AWS_CREDENTIALS_PSW}" + } + steps { + dir('main-server/terraform') { + sh """ + terraform init + terraform apply -auto-approve + """ + } + } + } + + stage('Deploy Application') { + steps { + dir('main-server/ansible') { + sh """ + ansible-playbook -i inventory.ini playbook.yml + """ + } + } + } + } + + post { + always { + sh 'docker logout' + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index e69de29..1644d29 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,147 @@ +# Complete CI/CD Pipeline for a Flask Application + +## Overview + +This project is a web application that allows users to view stock price graphs and historical data for a given ticker symbol. It is built using Flask for the backend and uses yfinance to fetch stock data. The application is containerized using Docker and can be deployed on AWS using Terraform and Ansible. + +This project was created for the purpose of learning and practicing CI/CD pipelines, infrastructure as code, and containerization. +So, it is not optimized for production use and is not recommended to be used in a production environment. + +## Features + +- Real-time stock price graphs using FinViz charts +- Historical price data in a tabular format +- Responsive web design with mobile support +- Automated CI/CD pipeline with Jenkins +- Infrastructure as Code using Terraform +- Automated deployment using Ansible +- Containerized application using Docker + +## Prerequisites + +- Docker +- Terraform +- Ansible +- Jenkins +- AWS account with appropriate permissions +- Python +- pip package manager + +## Setup Instructions + +### Local Development + +1. **Clone the repository:** + ```bash + git clone https://github.com/jenkins_pipeline.git + cd jenkins_pipeline + ``` + +2. **Create and activate a virtual environment (recommended):** + ```bash + python -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +3. **Install dependencies:** + ```bash + pip install -r requirements.txt + ``` + +4. **Run the application:** + ```bash + flask run + ``` + The application will be available at `http://localhost:5000` + +### Docker Deployment + +1. **Build the Docker image:** + ```bash + docker build -t flask-stock-app . + ``` + +2. **Run the Docker container:** + ```bash + docker run -p 5000:5000 flask-stock-app + ``` + Access the application at `http://localhost:5000` + +### Production Deployment + +#### 1. Infrastructure Setup + +1. **Configure AWS credentials:** + ```bash + export AWS_ACCESS_KEY_ID="your_access_key" + export AWS_SECRET_ACCESS_KEY="your_secret_key" + export AWS_DEFAULT_REGION="your_preferred_region" + ``` + +#### 2. Jenkins CI/CD Setup + +1. **Deploy Jenkins server:** + ```bash + cd jenkins/terraform + terraform init + terraform apply + ``` + +2. **Configure Jenkins:** + ```bash + cd ../ansible + ansible-playbook playbook.yml + ``` + +3. **Access Jenkins UI:** + - Navigate to `http://:8080` + - Use the initial admin password displayed in the Ansible output + - Install suggested plugins + - Create admin user + - Configure the following credentials: + - Docker Hub credentials + - AWS credentials + - GitHub credentials + +4. **Configure Jenkins Pipeline:** + - Create a new pipeline job + - Point it to your repository + - Use the provided Jenkinsfile + +#### 3. Application Deployment + +1. **Update variables:** + - Edit `main-server/ansible/vars.yml` with your Docker image details + - Edit `jenkins/ansible/vars.yml` with your GitHub repository + +2. **Deploy application:** + ```bash + cd main-server/ansible + ansible-playbook playbook.yml + ``` + +## Project Structure + +``` +. +├── app.py # Flask application +├── Dockerfile # Docker configuration +├── requirements.txt # Python dependencies +├── static/ # Static assets +├── templates/ # HTML templates +├── main-server/ # Main application deployment +│ ├── ansible/ # Ansible playbooks +│ └── terraform/ # Infrastructure as code +└── jenkins/ # Jenkins CI/CD setup + ├── ansible/ # Jenkins configuration + └── terraform/ # Jenkins infrastructure +``` + +## Acknowledgments + +- yfinance for stock data API +- FinViz for stock charts +- Flask framework +- Jenkins community +- Terraform and Ansible communities +``` \ No newline at end of file diff --git a/jenkins/ansible/inventory.ini b/jenkins/ansible/inventory.ini new file mode 100644 index 0000000..6719df3 --- /dev/null +++ b/jenkins/ansible/inventory.ini @@ -0,0 +1,2 @@ +[jenkins] +jenkins_server ansible_host=${jenkins_ip} ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/your-key-pair.pem \ No newline at end of file diff --git a/jenkins/ansible/playbook.yml b/jenkins/ansible/playbook.yml new file mode 100644 index 0000000..6e6f590 --- /dev/null +++ b/jenkins/ansible/playbook.yml @@ -0,0 +1,136 @@ +--- +- hosts: jenkins + become: yes + vars_files: + - vars.yml + + tasks: + - name: Update apt cache + apt: + update_cache: yes + + - name: Install Java + apt: + name: openjdk-11-jdk + state: present + + - name: Add Jenkins repository key + apt_key: + url: https://pkg.jenkins.io/debian-stable/jenkins.io.key + state: present + + - name: Add Jenkins repository + apt_repository: + repo: deb https://pkg.jenkins.io/debian-stable binary/ + state: present + + - name: Install Jenkins + apt: + name: jenkins + state: present + + - name: Install Docker + apt: + name: docker.io + state: present + + - name: Add jenkins user to docker group + user: + name: jenkins + groups: docker + append: yes + + - name: Start Jenkins service + service: + name: jenkins + state: started + enabled: yes + + - name: Get initial admin password + command: cat /var/lib/jenkins/secrets/initialAdminPassword + register: jenkins_password + changed_when: false + + - name: Display Jenkins initial admin password + debug: + var: jenkins_password.stdout + + - name: Install Jenkins plugins + jenkins_plugin: + name: "{{ item }}" + jenkins_home: "{{ jenkins_home }}" + with_items: + - git + - pipeline + - docker-pipeline + - ansible + - terraform + + - name: Install required system packages + apt: + name: + - python3-pip + - git + state: present + + - name: Install AWS CLI + pip: + name: awscli + state: present + + - name: Install Terraform + unarchive: + src: https://releases.hashicorp.com/terraform/1.5.7/terraform_1.5.7_linux_amd64.zip + dest: /usr/local/bin + remote_src: yes + mode: 0755 + + - name: Create Jenkins job directory + file: + path: "{{ jenkins_home }}/jobs/main-pipeline" + state: directory + owner: jenkins + group: jenkins + mode: '0755' + + - name: Configure Jenkins job + copy: + content: | + + + + Main application pipeline + + + + + + true + -1 + -1 + + + + + + + {{ github_repo }} + github-credentials + + + + + + + + + + dest: "{{ jenkins_home }}/jobs/main-pipeline/config.xml" + owner: jenkins + group: jenkins + mode: '0644' + + - name: Restart Jenkins to apply changes + service: + name: jenkins + state: restarted \ No newline at end of file diff --git a/jenkins/ansible/vars.yml b/jenkins/ansible/vars.yml new file mode 100644 index 0000000..f679a49 --- /dev/null +++ b/jenkins/ansible/vars.yml @@ -0,0 +1,6 @@ +--- +jenkins_home: /var/lib/jenkins +jenkins_user: jenkins +jenkins_group: jenkins +github_repo: "your-repo-url" +github_branch: "main" \ No newline at end of file diff --git a/jenkins/jenkins-init.sh b/jenkins/jenkins-init.sh new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/jenkins/jenkins-init.sh @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jenkins/main.tf b/jenkins/main.tf new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/jenkins/main.tf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jenkins/terraform.tfvars.example b/jenkins/terraform.tfvars.example new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/jenkins/terraform.tfvars.example @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/jenkins/terraform/main.tf b/jenkins/terraform/main.tf new file mode 100644 index 0000000..8539da6 --- /dev/null +++ b/jenkins/terraform/main.tf @@ -0,0 +1,69 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = "us-west-2" # Change to your preferred region +} + +resource "aws_instance" "jenkins_server" { + ami = "ami-0735c191cf914754d" # Ubuntu 20.04 LTS + instance_type = "t2.medium" + key_name = "your-key-pair" + + vpc_security_group_ids = [aws_security_group.jenkins_sg.id] + + tags = { + Name = "jenkins-server" + } + + root_block_device { + volume_size = 30 + } +} + +resource "aws_security_group" "jenkins_sg" { + name = "jenkins-security-group" + description = "Security group for Jenkins server" + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 8080 + to_port = 8080 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +# Output the Jenkins server IP to be used in Ansible +output "jenkins_ip" { + value = aws_instance.jenkins_server.public_ip +} + +# Create a local file with Jenkins IP for Ansible +resource "local_file" "ansible_inventory" { + content = templatefile("${path.module}/inventory.tpl", + { + jenkins_ip = aws_instance.jenkins_server.public_ip + } + ) + filename = "../ansible/inventory.ini" +} \ No newline at end of file diff --git a/jenkins/variables.tf b/jenkins/variables.tf new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/jenkins/variables.tf @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/main-server/ansible/inventory.ini b/main-server/ansible/inventory.ini new file mode 100644 index 0000000..8189785 --- /dev/null +++ b/main-server/ansible/inventory.ini @@ -0,0 +1,2 @@ +[app] +app_server ansible_host=${app_ip} ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/your-key-pair.pem \ No newline at end of file diff --git a/main-server/ansible/playbook.yml b/main-server/ansible/playbook.yml new file mode 100644 index 0000000..db72a2e --- /dev/null +++ b/main-server/ansible/playbook.yml @@ -0,0 +1,36 @@ +--- +- hosts: app + become: yes + vars_files: + - vars.yml + + tasks: + - name: Update apt cache + apt: + update_cache: yes + + - name: Install Docker + apt: + name: docker.io + state: present + + - name: Start Docker service + service: + name: docker + state: started + enabled: yes + + - name: Pull Docker image + docker_image: + name: "{{ docker_image }}" + source: pull + force_source: yes + + - name: Run Docker container + docker_container: + name: "{{ docker_container_name }}" + image: "{{ docker_image }}" + state: started + restart_policy: always + ports: + - "{{ app_port }}:80" \ No newline at end of file diff --git a/main-server/ansible/vars.yml b/main-server/ansible/vars.yml new file mode 100644 index 0000000..84c8138 --- /dev/null +++ b/main-server/ansible/vars.yml @@ -0,0 +1,4 @@ +--- +docker_image: your-docker-image +docker_container_name: your-app +app_port: 80 \ No newline at end of file diff --git a/main-server/terraform/main.tf b/main-server/terraform/main.tf new file mode 100644 index 0000000..23cba15 --- /dev/null +++ b/main-server/terraform/main.tf @@ -0,0 +1,70 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + +provider "aws" { + region = "us-west-2" # Change to your preferred region +} + +resource "aws_instance" "app_server" { + ami = "ami-0735c191cf914754d" # Ubuntu 20.04 LTS + instance_type = "t2.micro" + key_name = "your-key-pair" + + vpc_security_group_ids = [aws_security_group.app_sg.id] + + tags = { + Name = "app-server" + } +} + +resource "aws_security_group" "app_sg" { + name = "app-security-group" + description = "Security group for main application server" + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } +} + +output "app_server_ip" { + value = aws_instance.app_server.public_ip +} + +resource "local_file" "app_inventory" { + content = templatefile("${path.module}/inventory.tpl", + { + app_ip = aws_instance.app_server.public_ip + } + ) + filename = "../ansible/inventory.ini" +} \ No newline at end of file