diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..d70cebd23 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,48 @@ +name: Python CI + +on: + push: + branches: [lab3] + paths: + - "app_python/**" + - ".github/workflows/main.yaml" + +jobs: + build_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.10.7' + - name: Install dependencies + run: pip install -r app_python/requirements.txt + - name: Upgrade Flask and Werkzeug + run: pip install --upgrade Flask + - name: Run unit tests + run: python3 app_python/test_main.py + + deploy: + needs: build_test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Log in to Docker registry + uses: docker/login-action@v1 + with: + registry: docker.io + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: . + file: app_python/Dockerfile + push: true + tags: docker.io/haraphat01/moscowtime:tagname + \ No newline at end of file diff --git a/README.md b/README.md index 119379536..b5094c4a8 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,92 @@ -# Labs +# Current Time in Moscow -## Introduction +![Python CI](https://github.com/haraphat01/moscow_time/workflows/Python%20CI/badge.svg) -Welcome to DevOps course labs. All labs are practical and will be built on each other. You will implement simple application, containerize it, implement simple tests, prepare an infrastructure and CI/CD processes, collect metrics, logs, etc. -## Architecture +## Overview +This is a simple Python web application that displays the current time in Moscow. It uses the Flask web framework and the datetime module to get the current time in the Europe/Moscow timezone. The time is displayed in a user-friendly format on a web page. -This repository contains a master branch with introduction and one branch with instructions for each lab. +## Build +To build this application, you need to create a Python file with a main.py extension, for example, app.py. Then, copy the code in main.py to your file. Save the file and run it using the command python app.py in a terminal or command prompt. +The application will start running on your local machine. -## Rules +## Usage +To use the application, open a web browser and navigate to http://localhost:5000/. The current time in Moscow will be displayed on the web page. The time will be updated every time you refresh the page. -Each labs requires the participant to finish all previous labs, therefore **participants are required to submit each lab and get at least 6/10 points for each lab to pass the course**. +## To run the Dockerfile in your project, you can follow these steps: -Grading is based on PRs with your solution to the corresponding branch of this repository. This repository is read-only for all participants, therefore to be able to create a pull request, a participant should fork this repository to his own workspace and solve the lab there. It is recommended to build a solution of a lab N upon a solution of lab N-1, so choose workflow in your fork of this repository wisely. Structure of your repository will not affect your grade, only state of your repository from which the PR is created will be checked and graded (state after last commit in your PR on corresponding lab). +1. Make sure that you have Docker installed on your local machine. You can download Docker from the official website: https://www.docker.com/products/docker-desktop -### Recommended workflow +2. Open a terminal or command prompt and navigate to the directory that contains your Dockerfile. -#### For the first lab +3. Build the Docker image using the following command: + docker build -t . + Make sure to replace with the name that you want to give to your Docker image. The . at the end of the command specifies that the build context is the current directory. -1. Fork this repository. -2. Checkout to lab1 branch. -3. Complete lab1 tasks. -4. Push the code to your repository. -5. Create a PR to the lab1 branch on this repository from your fork's lab1 branch. -6. Create an archive with the current version of your code and submit a zip file to Moodle. -7. Create a team with with your classmates, 6 people max. -8. Each student must review PRs of all teammates. -9. Wait for your grade. +4. Once the build is complete, you can run the Docker container using the following command: + docker run -p : -## Grading +Make sure to replace with the port number on your local machine that you want to use to access the container, with the port number that your application is listening on inside the container, and with the name of the Docker image that you built in step 3. -### Points distribution for the course +For example, if your application is listening on port 80 inside the container and you want to access it on port 8080 on your local machine, you can use the following command: + docker run -p 8080:80 -``` -70 - labs -20 - final exam -10 - attendance on lectures -``` +5. Once the container is running, you can access your application by opening a web browser and navigating to http://localhost:. In the example above, you would navigate to http://localhost:8080. -### Grade ranges +## Unit tests -``` -[90;100] - A -[75;90) - B -[60;75) - C -[0;60) - D -``` +I have written unit tests for the Python web application that displays the current time in Moscow. These tests ensure that the application is working as expected and help us catch any bugs early in the development process. -### Labs grading +To run the unit tests, navigate to the directory where the `test_main.py` file is located and run the following command: +python test_main.py -Each lab is marked out of 10. All labs have a set of main tasks and a set of extra tasks. -Completing main tasks correctly will give you 10 points out of 10. Completing extra tasks correctly will give you some additional points, depends on the bonus task difficulty. Your points for main and extra tasks will be summed up and will help you to get a better grade. +Make sure that you have all the necessary dependencies installed, including Flask and any other modules that your application depends on. -If you finish all bonus tasks correctly the **permission to skip the exam will be granted to you + 10 extra points**. If you finish not all of them you will must pass the exam, but it can save you from the exam's failure. +If the tests pass successfully, you should see output similar to the following: -## Deadlines and labs distribution +Ran 1 test in 0.001s -Participants have 2 new labs every week simultaneously and 1 week to submit solutions. Moodle will contain presentations and deadlines. +OK -You are required to submit a zip file with your source code to corresponding assignment in moodle. This is required for the university as a proof of work. -## Submission policy +## Git Actions CI -**Submitting results after the deadline will result in maximum of 6 points for the corresponding lab. As stated before, all labs must be submitted to pass the course.** +I have set up a continuous integration (CI) workflow using Git Actions to build and test our Python web application. The workflow has three steps: installing dependencies, running linters, and running unit tests. + +To run the workflow, push changes to the `main` branch of the repository. The workflow will automatically be triggered and the steps will be run in order. Make sure that you have all the necessary dependencies installed, including Flask and any other modules that your application depends on. + +I have also added Docker-related steps to our workflow to log in to a Docker registry, build a Docker image, and push the image to the registry. To use these steps, you will need to set up the `DOCKER_USERNAME` and `DOCKER_PASSWORD` secrets in your repository settings. + +By following these best practices and including a Git Actions CI workflow in your project, I can ensure that my application is thoroughly tested and working as expected. + +# Terraform AWS Example + +This Terraform configuration sets up an AWS infrastructure with a single EC2 instance running in the `us-west-2` region. The instance is tagged with the name "ExampleAppServerInstance". + +## Prerequisites + +- Terraform >= 1.2.0 +- AWS CLI +- An AWS account with the necessary permissions + +## Configuration + +The `main.tf` file contains the following resources: + +1. **Terraform block**: Specifies the required providers and their versions. +2. **AWS provider block**: Configures the AWS provider with the `us-west-2` region. +3. **AWS instance resource**: Creates an EC2 instance with the specified AMI and instance type. + + +## Usage + +1. Install Terraform and the AWS CLI. +2. Configure your AWS credentials using `aws configure` or by setting the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables. +3. Run `terraform init` to initialize the Terraform working directory. +4. Run `terraform apply` to create the infrastructure. Confirm the changes when prompted. +5. To destroy the infrastructure when you're done, run `terraform destroy`. + +## Customization + +You can customize the configuration by modifying the `main.tf` file. For example, you can change the instance type, region, or add additional resources as needed. \ No newline at end of file diff --git a/app_python/Dockerfile b/app_python/Dockerfile new file mode 100644 index 000000000..eb3113a8d --- /dev/null +++ b/app_python/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3-alpine +USER root +WORKDIR /main + +COPY app_python/requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8081 + +CMD ["python3", "main.py"] + + diff --git a/app_python/main.py b/app_python/main.py new file mode 100644 index 000000000..00c8b1f82 --- /dev/null +++ b/app_python/main.py @@ -0,0 +1,17 @@ +from datetime import datetime + +from flask import Flask + +app = Flask(__name__) + +def get_current_time(): + now = datetime.now() + current_time = now.strftime("Current Time in Moscow: %Y-%m-%d %H:%M:%S") + return current_time + +@app.route('/') +def home(): + return get_current_time() + +if __name__ == '__main__': + app.run(host="0.0.0.0", debug=True) \ No newline at end of file diff --git a/app_python/requirements.txt b/app_python/requirements.txt new file mode 100644 index 000000000..496a52930 --- /dev/null +++ b/app_python/requirements.txt @@ -0,0 +1,2 @@ +pytz +Flask==2.0.1 diff --git a/app_python/test_main.py b/app_python/test_main.py new file mode 100644 index 000000000..676849e5e --- /dev/null +++ b/app_python/test_main.py @@ -0,0 +1,14 @@ +import unittest +from main import app + +class TestApp(unittest.TestCase): + def setUp(self): + self.app = app.test_client() + + def test_homepage(self): + response = self.app.get('/') + self.assertEqual(response.status_code, 200) + self.assertIn(b'Current Time in Moscow', response.data) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/terraform/TF.md b/terraform/TF.md new file mode 100644 index 000000000..c959d4200 --- /dev/null +++ b/terraform/TF.md @@ -0,0 +1,73 @@ +# Output of Terraform show +docker_container.nginx: +resource “docker_container” “nginx” { attach = false command = [ “nginx”, “-g”, “daemon off;”, ] container_read_refresh_timeout_milliseconds = +15000 cpu_shares = 0 entrypoint = [ “/docker-entrypoint.sh”, ] env = [] hostname = “21dca2656095” id = +“21dca2656095460e1391309cc5904b687acdaee6500a047df9493911addb239c” image = +“sha256:f9c14fe76d502861ba0939bc3189e642c02e257f06f4c0214b1f8ca329326cda” init = false ipc_mode = “private” log_driver = “json-file” logs = +false max_retry_count = 0 memory = 0 memory_swap = 0 must_run = true name = “tutorial” network_data = [ { gateway = “172.17.0.1” +global_ipv6_address = “” global_ipv6_prefix_length = 0 ip_address = “172.17.0.2” ip_prefix_length = 16 ipv6_gateway = “” mac_address = “02:42:ac +11:00:02” network_name = “bridge” }, ] network_mode = “default” privileged = false publish_all_ports = false read_only = false remove_volumes = +true restart = “no” rm = false runtime = “runc” security_opts = [] shm_size = 64 start = true stdin_open = false stop_signal = “SIGQUIT” stop_timeout += 0 tty = false wait = false wait_timeout = 60 +ports { +external = 8000 +internal = 80 +ip = "0.0.0.0" +protocol = "tcp" +} + +} +docker_image.nginx: +resource “docker_image” “nginx” { id = “sha256:f9c14fe76d502861ba0939bc3189e642c02e257f06f4c0214b1f8ca329326cdanginx:latest” image_id = +“sha256:f9c14fe76d502861ba0939bc3189e642c02e257f06f4c0214b1f8ca329326cda” keep_locally = false name = “nginx:latest” repo_digest = +“nginx@sha256:af296b188c7b7df99ba960ca614439c99cb7cf252ed7bbc23e90cfda59092305” } + +# Output of terraform state list +docker_container.nginx docker_image.nginx +aws_instance.app_server: +resource “aws_instance” “app_server” { ami = “ami-08d70e59c07c61a3a” arn = “arn:aws:ec2:us-west-2:354156216263:instance +i-0b39be7dc8d0a893a” associate_public_ip_address = true availability_zone = “us-west-2a” cpu_core_count = 1 cpu_threads_per_core = 1 +disable_api_stop = false disable_api_termination = false ebs_optimized = false get_password_data = false hibernation = false id = +“i-0b39be7dc8d0a893a” instance_initiated_shutdown_behavior = “stop” instance_state = “running” instance_type = “t2.micro” ipv6_address_count = +0 ipv6_addresses = [] monitoring = false placement_partition_number = 0 primary_network_interface_id = “eni-0cc62b162b9c9df4c” private_dns = +“ip-172-31-16-27.us-west-2.compute.internal” private_ip = “172.31.16.27” public_dns = “ec2-35-93-162-232.us-west-2.compute.amazonaws.com” +public_ip = “35.93.162.232” secondary_private_ips = [] security_groups = [ “default”, ] source_dest_check = true subnet_id = +“subnet-039b0778d5eaf2eb4” tags = { “Name” = “ExampleAppServerInstance” } tags_all = { “Name” = “ExampleAppServerInstance” } tenancy = +“default” user_data_replace_on_change = false vpc_security_group_ids = [ “sg-0849ce7988dae5104”, ] +capacity_reservation_specification { +capacity_reservation_preference = "open" +} +cpu_options { +core_count = 1 +threads_per_core = 1 +} +credit_specification { +cpu_credits = "standard" +} +enclave_options { +enabled = false +} +maintenance_options { +auto_recovery = "default" +}metadata_options { +http_endpoint = "enabled" +http_put_response_hop_limit = 1 +http_tokens = "optional" +instance_metadata_tags = "disabled" +} +private_dns_name_options { +enable_resource_name_dns_a_record = false +enable_resource_name_dns_aaaa_record = false +hostname_type = "ip-name" +} +root_block_device { +delete_on_termination = true +device_name = "/dev/sda1" +encrypted = false +iops = 100 +tags = {} +throughput = 0 +volume_id = "vol-0403cb7cdf11b3fb7" +volume_size = 8 +volume_type = "gp2" +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 000000000..68d9bef50 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,24 @@ +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0.1" + } + } +} + +provider "docker" {} + +resource "docker_image" "nginx" { + name = "nginx:latest" + keep_locally = false +} + +resource "docker_container" "nginx" { + image = docker_image.nginx.image_id + name = "tutorial" + ports { + internal = 80 + external = 8000 + } +}