├── README.md ├── assets └── img │ ├── git │ ├── actions_get_started.png │ ├── actions_run_workflow.png │ ├── actions_workfows.png │ ├── actions_workfows_2.png │ ├── as_actions.png │ ├── as_docker.png │ ├── btn_new.png │ ├── checkbox_template.png │ ├── environment_secrets.png │ ├── file_navigation_add_file.png │ ├── file_navigation_tags.png │ ├── fork.png │ ├── issue.png │ ├── issues_new_issue.png │ ├── new_branch.png │ ├── new_repo.png │ ├── new_repo_from_template.png │ ├── pr.png │ ├── pr_compare.png │ ├── pr_workflow.png │ ├── release.png │ ├── settings_pages.png │ ├── tabs_actions.png │ ├── tabs_issues.png │ ├── tabs_pr.png │ ├── tabs_settings.png │ └── tabs_wiki.png │ ├── slides │ ├── astrcat_transparent.png │ ├── bg1.png │ ├── bg2.png │ ├── bg3.png │ ├── fp2.png │ └── globe.png │ └── workshop-monitor-presenter.png ├── docs ├── links.md ├── notes.md ├── polls.md ├── proposal.md ├── requirements.md └── timing.md ├── slides └── github_fundamentals.pdf └── src ├── .github └── workflows │ └── cicd.yaml ├── Dockerfile ├── README.md └── app ├── db.json ├── rengine.py ├── requirements.txt ├── run.py └── test_app.py /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Fundamentals 2 | 3 | #### Objectives 4 | 5 | To create a movie recommendation engine as an Open Source Software implemented in a microservice architecture. 6 | 7 | By the end of this workshop, you’ll know: 8 | 9 | - [X] History of OpenSource 10 | - [X] How to create an Open Source Software 11 | - [X] [Markdown markup language](https://guides.github.com/features/mastering-markdown/) 12 | - [X] [Docker](#1-github-repository---github-templates) 13 | - [X] [Github Repository / Templates](#1-github-repository---github-templates) 14 | - [X] [Github Codespaces](https://github.com/features/codespaces) 15 | - [X] [Microservice](#2-microservice) 16 | - [X] [Github Issues](#3-github-issues) 17 | - [X] [Github Releases / Tag](#4-github-releases--tags) 18 | - [X] [Github Actions](#5-github-actions) 19 | - [X] [Pull Requests](#6-github-pull-request) 20 | - [X] [Github Advanced Search](#7-github-advanced-search) 21 | - [X] [Github Pages](#8-github-pages) 22 | - [X] [Github Wikis](#9-github-wikis) 23 | 24 | And you’ll be able to: 25 | 26 | - [X] Create an Open Source Software on GitHub 27 | - [X] Collaborate on Open Source Software by creating GitHub Issues and doing Pull Requests 28 | - [X] Build CI/CD pipelines with GitHub Actions 29 | - [X] Host website with GitHub Pages 30 | - [X] Use GitHub’s advanced search 31 | - [X] User GIT version control 32 | - [X] Build software with Docker 33 | - [X] Share your docker images on hub.docker.com 34 | 35 | #### Requirements 36 | 37 | - [GitHub](https://github.com/) account 38 | - [DockerHub](https://hub.docker.com/) account 39 | 40 | # Warning - No license, no Open Source !!! 41 | 42 | Choosing the right license when creating an open-source project is [crucial](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/licensing-a-repository#choosing-the-right-license). 43 | 44 | > "**without a license**, the default copyright laws apply, meaning that you retain all rights to your source code, and **no one may reproduce, distribute, or create derivative works from your work.**" 45 | 46 | There are many available [open-source licenses](https://opensource.org/licenses). If you are confused about which one to use, visit [choosealicense.com](https://choosealicense.com/licenses/) - a comprehensive guide about open source licenses. 47 | 48 | Additional information about **legal aspects of having no license** can be found [here](https://opensource.stackexchange.com/questions/1720/what-can-i-assume-if-a-publicly-published-project-has-no-license). 49 | 50 | ## 1. GitHub Repository / GitHub Templates 51 | 52 |
53 | Context 54 | 55 | #### What is a GitHub repository? 56 | 57 | A repository is a place where your git **project** and its **files** reside. A typical repository stores source code along with the `.git` folder - a directory that tracks snapshots of changes introduced to your files. 58 | 59 | #### Why use the GitHub repository? 60 | 61 | GitHub repository is: 62 | - A place for documenting your project - **GitHub Wiki** 63 | - A place for automatizing tasks within the software development life cycle - **GitHub Actions** 64 | - A place for organizing and tracking work items - **GitHub Projects** 65 | - A forum for sharing and raising questions - **GitHub Issues** 66 | - A place for security scanning - **GitHub Security** 67 | - And many more ... 68 | 69 | #### What is a GitHub template? 70 | 71 | A GitHub Template is a way of marking your repository as a reusable blueprint. GitHub Template allows to create new repositories that preserve the same structure, branches, and files as the blueprint repository. 72 | 73 |
74 | 75 | Let's create our first GitHub repository. This repository will hold `Hello world!` Flask application and will become our GitHub template for a microservice that we'll build in the next step. 76 | 77 | 1. On the [GitHub](https://github.com/) page click [New](https://github.com/new) button. 78 | 79 | ![New Repository](assets/img/git/btn_new.png) 80 | 81 | 1. Name the repository `flask-init-mini` and click the `Create repository` button. 82 | 83 | ![Repository](assets/img/git/new_repo.png) 84 | 85 | 1. What's left is to create a Flask application. If you are not familiar with Flask, read the [quick start](https://flask.palletsprojects.com/en/1.1.x/quickstart/) guide. In GitHub's interface, click the `Add file` button and select `Create new file`. 86 | 87 | [![File Navigation](assets/img/git/file_navigation_add_file.png)](https://github.com/ldynia/flask-init-mini) 88 | 89 | **app/requirements.txt** 90 | 91 | ``` 92 | coverage==7.3.2 93 | Flask==3.0.0 94 | pytest==7.4.2 95 | ``` 96 | **app/test_app.py** 97 | 98 | ```python 99 | import pytest 100 | 101 | from run import app as application 102 | 103 | 104 | @pytest.fixture() 105 | def app(): 106 | application.config.update({ 107 | "TESTING": True, 108 | }) 109 | yield application 110 | 111 | 112 | @pytest.fixture 113 | def client(app): 114 | return app.test_client() 115 | 116 | 117 | @pytest.fixture() 118 | def runner(app): 119 | return app.test_cli_runner() 120 | 121 | 122 | def test_api(client): 123 | response = client.get("/") 124 | assert response.status_code == 200 125 | assert b"Flask" in response.data 126 | ``` 127 | 128 | **app/run.py** 129 | 130 | ```python 131 | from flask import Flask 132 | 133 | app = Flask(__name__) 134 | 135 | 136 | @app.route("/") 137 | def hello_world(): 138 | return "Hello, Flask!" 139 | ``` 140 | 141 | **Dockerfile** 142 | 143 | ```Dockerfile 144 | FROM python:3.12-alpine 145 | 146 | # Build arguments 147 | ARG FLASK_DEBUG=False \ 148 | GROUP=nogroup \ 149 | USER=nobody \ 150 | WORKDIR=/usr/src 151 | 152 | # Environment variables 153 | ENV FLASK_APP=$WORKDIR/run.py \ 154 | FLASK_DEBUG=$FLASK_DEBUG \ 155 | HOST=0.0.0.0 \ 156 | PORT=8080 \ 157 | PYTHONUNBUFFERED=True 158 | 159 | # App's file system 160 | WORKDIR $WORKDIR 161 | RUN chown $USER:$GROUP $WORKDIR 162 | COPY --chown=$USER:$GROUP app/ $WORKDIR 163 | 164 | # Install OS packages 165 | RUN apk add --no-cache curl 166 | 167 | # Install python packages 168 | RUN pip install --upgrade pip --requirement requirements.txt 169 | 170 | # Expose app's port 171 | EXPOSE $PORT 172 | 173 | # Run rootless 174 | USER $USER:$GROUP 175 | 176 | # Start app 177 | CMD ["sh", "-c", "flask run --host=$HOST --port=$PORT"] 178 | ``` 179 | 180 | **.devcontainer/devcontainer.json** 181 | ```json 182 | { 183 | "name": "Python 3.12", 184 | "image": "mcr.microsoft.com/devcontainers/python:3.12", 185 | "hostRequirements": { 186 | "cpus": 2, 187 | "memory": "4gb", 188 | "storage": "16gb" 189 | }, 190 | "features": { 191 | "ghcr.io/devcontainers-contrib/features/black:1": {}, 192 | "ghcr.io/devcontainers-contrib/features/pylint:1": {}, 193 | "ghcr.io/devcontainers/features/docker-in-docker:2": {}, 194 | "ghcr.io/guiyomh/features/vim:0": {} 195 | }, 196 | "customizations": { 197 | "codespaces": { 198 | "openFiles": [ 199 | "README.md" 200 | ] 201 | }, 202 | "vscode": { 203 | "extensions": [ 204 | "cschleiden.vscode-github-actions", 205 | "DavidAnson.vscode-markdownlint", 206 | "GitHub.vscode-pull-request-github", 207 | "ms-python.python", 208 | "ms-python.vscode-pylance", 209 | "redhat.vscode-yaml" 210 | ] 211 | } 212 | } 213 | } 214 | ``` 215 | 216 | **README.md** 217 | ~~~ 218 | # flask-init-mini 219 | 220 | This project is a boilerplate for future Flask applications. 221 | 222 | The steps below can be executed on any Unix-like system. 223 | 224 | ## Setup SSH key 225 | 226 | **This step is an option and can be omitted.** 227 | 228 | Create ssh key and add it to GitHub's [SSH keys](https://github.com/settings/keys) settings. 229 | 230 | ```bash 231 | ssh-keygen 232 | cat ~/.ssh/id_rsa.pub 233 | ``` 234 | 235 | ## Installation 236 | 237 | ```bash 238 | # Cloning the source code 239 | git clone https://github.com/ldynia/flask-init-mini.git 240 | cd flask-init-mini 241 | 242 | # Building and running the docker container 243 | docker build --tag flask-mini --build-arg FLASK_DEBUG=True . 244 | docker run --detach --name flask-app --publish 80:8080 --rm flask-mini 245 | docker ps 246 | ``` 247 | 248 | ## API 249 | 250 | ```bash 251 | curl "http://localhost"; echo 252 | ``` 253 | 254 | ## Testing 255 | 256 | Unit test 257 | 258 | ```bash 259 | docker exec flask-app pytest 260 | ``` 261 | 262 | Code coverage 263 | 264 | ```bash 265 | docker exec flask-app coverage run -m pytest 266 | docker exec flask-app coverage report 267 | ``` 268 | 269 | Stop container 270 | 271 | ```bash 272 | docker stop flask-app 273 | ``` 274 | ~~~ 275 | 276 | 1. Let's mark the repository as a template by clicking the repository `Settings` tab and selecting the `Template repository` checkbox. 277 | 278 | [![Settings](assets/img/git/tabs_settings.png)](https://github.com/ldynia/flask-init-mini/settings) 279 | 280 | [![Template repository](assets/img/git/checkbox_template.png)](https://github.com/ldynia/flask-init-mini/settings) 281 | 282 | 1. Spin up Codespaces for this repository 283 | - Click the `Code` button 284 | - Select `Codespaces` tab 285 | - Click the `Create codespace on main` button 286 | 287 | 1. Follow `REDME.md` instructions in the `Installation`, `Testing`, and `API` sections. 288 | 289 | ## 2. Microservice 290 | 291 |
292 | Context 293 | 294 | #### What are microservices, and why to use them? 295 | 296 | Microservices/Microservice Architecture - is an **architectural style that structures an application as a collection of services** that are: 297 | - Loosely coupled. 298 | - Independently deployable. 299 | - Organized around business capabilities. 300 | - Highly maintainable and testable. 301 | - Owned by a small team. 302 | 303 | #### What are typical features of microservice? 304 | 305 | A typical microservice exposes the following features: 306 | - Is structured around business boundaries / [bounded context](https://www.infoq.com/news/2019/06/bounded-context-eric-evans/) 307 | - Has an independent database. 308 | - Communicates over the network. 309 | - Has well-defined API. 310 | 311 |
312 | 313 | 1. Create [New Repository](https://github.com/new) and fill it in according to the values listed on the image. Notice that this time we choose the `flask-init-mini` project from the `Repository template` as a base for our microservice. Name new repository `flask-sherlock` 314 | ![New Repository](assets/img/git/new_repo_from_template.png) 315 | 316 | 1. Click the [**Add File**](https://github.com/ldynia/flask-init-mini) button and add/update blow files. 317 | [![File Navigation](assets/img/git/file_navigation_add_file.png)](https://github.com/ldynia/flask-init-mini) 318 | 319 | **app/db.json** 320 | 321 | ```json 322 | [{ 323 | "title": "Groundhog Day", 324 | "genre": ["comedy", "fantasy", "romance"], 325 | "year": 1993, 326 | "rating": 8.0, 327 | "directors": ["Harold Ramis"], 328 | "stars": ["Bill Murray", "Andie MacDowell", "Chris Elliott", "Punxsutawney Phil"] 329 | }, { 330 | "title": "Kingpin", 331 | "genre": ["comedy", "sport"], 332 | "year": 1996, 333 | "rating": 6.9, 334 | "director": ["Bobby Farrelly", "Peter Farrelly"], 335 | "stars": ["Woody Harrelson", "Randy Quaid", "Bill Murray"] 336 | }, { 337 | "title": "The Bridges of Madison County", 338 | "genre": ["drama", "romance"], 339 | "year": 1995, 340 | "rating": 7.6, 341 | "director": ["Clint Eastwood"], 342 | "stars": ["Clint Eastwood", "Meryl Streep"] 343 | }, { 344 | "title": "Good Will Hunting", 345 | "genre": ["drama", "romance"], 346 | "year": 1997, 347 | "rating": 8.3, 348 | "director": ["Gus Van Sant"], 349 | "stars": ["Robin Williams", "Matt Damon", "Ben Affleck"] 350 | }, { 351 | "title": "The Rainmaker", 352 | "genre": ["crime", "drama", "thriller"], 353 | "year": 1997, 354 | "rating": 7.2, 355 | "director": ["Francis Ford Coppola"], 356 | "stars": ["Matt Damon", "Danny DeVito", "Claire Danes"] 357 | }, { 358 | "title": "Ghost in the Shell", 359 | "genre": ["animation", "action", "crime"], 360 | "year": 1995, 361 | "rating": 8.0, 362 | "director": ["Mamoru Oshii"], 363 | "stars": ["Atsuko Tanaka", "Iemasa Kayumi", "Akio Ôtsuka"] 364 | }, { 365 | "title": "Aliens", 366 | "genre": ["action", "adventure", "sci-fi"], 367 | "year": 1986, 368 | "rating": 8.3, 369 | "director": ["James Cameron"], 370 | "stars": ["Sigourney Weaver", "Michael Biehn", "Carrie Henn"] 371 | }, { 372 | "title": "Terminator 2", 373 | "genre": ["action", "sci-fi"], 374 | "year": 1986, 375 | "rating": 8.5, 376 | "director": ["James Cameron"], 377 | "stars": ["Arnold Schwarzenegger", "Linda Hamilton", "Edward Furlong"] 378 | }, { 379 | "title": "Lethal Weapon 2", 380 | "genre": ["action", "crime", "thriller"], 381 | "year": 1989, 382 | "rating": 7.2, 383 | "director": ["Richard Donner"], 384 | "stars": ["Mel Gibson", "Danny Glover", "Joe Pesci"] 385 | }, { 386 | "title": "Lost in Translation", 387 | "genre": ["comedy", "drama"], 388 | "year": 3003, 389 | "rating": 7.7, 390 | "director": ["Sofia Coppola"], 391 | "stars": ["Bill Murray", "Scarlett Johansson", "Giovanni Ribisi"] 392 | }] 393 | ``` 394 | 395 | **app/rengine.py** 396 | 397 | ```python 398 | import random 399 | 400 | 401 | class Sherlock(): 402 | """ 403 | Movies recommendation engine. 404 | """ 405 | 406 | def __init__(self, movies, features): 407 | self.movies = movies 408 | self.title = features.get("title") 409 | self.features = ["genre", "stars"] 410 | 411 | def recommend(self): 412 | """ 413 | Algorithm recommends movies based on default movie features. 414 | The algorithm uses partial match as search criteria and returns sorted list of movie(s). 415 | """ 416 | ref_movie = self.__get_movie(self.title) 417 | if not ref_movie: 418 | return self.__lucky_recommendation(self.movies) 419 | ref_movie = ref_movie[0] 420 | 421 | movies = [] 422 | for movie in self.movies: 423 | if movie["title"] != self.title: 424 | for feature in self.features: 425 | feature_match = [fm in movie[feature] for fm in ref_movie[feature]] 426 | if any(feature_match): 427 | movies.append(movie) 428 | break 429 | 430 | return sorted(movies, key=lambda movie: movie["rating"], reverse=True) 431 | 432 | def __lucky_recommendation(self, movies): 433 | """ 434 | I feel lucky - random choice. 435 | """ 436 | return [random.choice(movies)] 437 | 438 | def __get_movie(self, title): 439 | """ 440 | Find movie by title. 441 | """ 442 | movie = [movie for movie in self.movies if movie["title"] == title] 443 | return movie if movie else [] 444 | ``` 445 | 446 | **app/run.py** 447 | 448 | ```python 449 | import os 450 | import json 451 | from json.decoder import JSONDecodeError 452 | 453 | from flask import Flask 454 | from flask import jsonify 455 | from flask import request 456 | 457 | from rengine import Sherlock 458 | 459 | 460 | # Set up app 461 | app = Flask(__name__) 462 | app.json.ensure_ascii = False 463 | APP_DIR = os.path.dirname(os.path.realpath(__file__)) 464 | 465 | def read_data(source): 466 | """ 467 | Reads file that is expected to hold JSON encoded content. 468 | In case of errors return empty data and list holding error message. 469 | """ 470 | data = [] 471 | errors = [] 472 | try: 473 | with open(source) as db: 474 | content = db.read() 475 | data = json.loads(content) 476 | except FileNotFoundError as e: 477 | errors = [f"Reading {source}, {str(e)}"] 478 | except JSONDecodeError as e: 479 | errors = [f"Reading {source}, {str(e)}"] 480 | except Exception as e: 481 | errors = [f"Reading {source}, {str(e)}"] 482 | 483 | return data, errors 484 | 485 | 486 | @app.route("/api/v1/movies/recommend", methods=["GET"]) 487 | def recommend(): 488 | """ 489 | Function loads movies from db and returns recommendations. 490 | """ 491 | MOVIES, errors = read_data(f"{APP_DIR}/db.json") 492 | if errors: 493 | return jsonify({"errors": errors, "status_code": 500}), 500 494 | 495 | sherlock = Sherlock(MOVIES, request.args) 496 | recommendation = sherlock.recommend() 497 | 498 | return jsonify(recommendation) 499 | ``` 500 | 501 | **/app/test_app.py** 502 | 503 | ```python 504 | import pytest 505 | 506 | from run import app as application 507 | 508 | 509 | @pytest.fixture() 510 | def app(): 511 | application.config.update({ 512 | "TESTING": True, 513 | }) 514 | yield application 515 | 516 | 517 | @pytest.fixture 518 | def client(app): 519 | return app.test_client() 520 | 521 | 522 | @pytest.fixture() 523 | def runner(app): 524 | return app.test_cli_runner() 525 | 526 | 527 | def test_api(client): 528 | response = client.get("/api/v1/movies/recommend") 529 | assert response.status_code == 200 530 | assert response.is_json 531 | assert response.get_json()[0]["title"] != "" 532 | 533 | response = client.get("/api/v1/movies/recommend?title=Kingpin") 534 | assert response.status_code == 200 535 | assert response.is_json 536 | assert len(response.get_json()) >= 2 537 | 538 | response = client.get("/api/v1/movies/recommend?title=Lost%20in%20Translation") 539 | assert response.status_code == 200 540 | assert response.is_json 541 | assert len(response.get_json()) >= 5 542 | ``` 543 | 544 | **README.md** 545 | 546 | ~~~ 547 | # Sherlock 548 | 549 | Welcome to the Sherlock project. Sherlock is a movie recommendation microservice written in Flask. 550 | 551 | The steps below can be executed on any Unix-like system. I will use Ubuntu deployed on [O'Reilly's sandbox](https://learning.oreilly.com/scenarios/ubuntu-sandbox/9781492062837) (alternatively, you could use [Katacoda's playground](https://www.katacoda.com/courses/ubuntu/playground2004)). Once the sandbox/playground is ready, execute the instructions specified in the sections below. 552 | 553 | ## Setup SSH key 554 | 555 | **This step is an option and can be omitted.** 556 | 557 | Create ssh key and add it to GitHub's [SSH keys](https://github.com/settings/keys) settings. 558 | 559 | ```bash 560 | ssh-keygen 561 | cat ~/.ssh/id_rsa.pub 562 | ``` 563 | 564 | ## Installation 565 | 566 | ```bash 567 | # Cloning the source code 568 | git clone https://github.com/ldynia/flask-sherlock.git 569 | cd flask-sherlock 570 | 571 | # Building and running the docker container 572 | docker build --tag flask-sherlock --build-arg FLASK_DEBUG=True . 573 | docker run --detach --name sherlock --publish 80:8080 --rm flask-sherlock 574 | docker ps 575 | ``` 576 | 577 | ## API 578 | 579 | Filter up algorithm 580 | 581 | ```bash 582 | curl "http://localhost/api/v1/movies/recommend?title=Kingpin" 583 | curl "http://localhost/api/v1/movies/recommend?title=Lost%20in%20Translation" 584 | ``` 585 | 586 | ## Testing 587 | 588 | Unit test 589 | 590 | ```bash 591 | docker exec sherlock pytest 592 | ``` 593 | 594 | Code coverage 595 | 596 | ```bash 597 | docker exec sherlock coverage run -m pytest 598 | docker exec sherlock coverage report 599 | ``` 600 | 601 | Stop container 602 | 603 | ```bash 604 | docker stop sherlock 605 | ``` 606 | ~~~ 607 | 608 | 1. Spin up Codespaces for this repository 609 | - Click `Code` button 610 | - Select `Codespaces` tab 611 | - Click `Create codespace on main` button 612 | 613 | 1. Follow `REDME.md` instructions in the `Installation`, `Testing`, and `API` sections. 614 | 615 | ## 3. GitHub Issues 616 | 617 |
618 | Context 619 | 620 | #### What are GitHub Issues? 621 | 622 | GitHub Issues is a tool for keeping track of tasks, bugs, and feedback on your project. 623 | 624 | #### Why use GitHub Issues? 625 | 626 | It's just a convenient way to manage all affairs related to your project. 627 | 628 |
629 | 630 | 1. In repository tabs, click [Issues](https://github.com/ldynia/flask-sherlock/issues). Next, click the [New issue](https://github.com/ldynia/flask-sherlock2/issues/new) button. Fill out the form with the text specified in the image below, then click the `Submit new issue` button. 631 | 632 | [![Issues](assets/img/git/tabs_issues.png)](https://github.com/ldynia/flask-sherlock/issues) 633 | [![Issues](assets/img/git/issues_new_issue.png)](https://github.com/ldynia/flask-sherlock/issues/new) 634 | [![Issues](assets/img/git/issue.png)](https://github.com/ldynia/flask-sherlock/issues/new) 635 | 636 | 1. Fix the invalid date and close the issue 637 | 638 | ## 4. GitHub Releases / Tags 639 | 640 |
641 | Context 642 | 643 | It's very seldom that your software will be released only in one version, e.g. `v1.0.0`. As your project grows, you will have a bug to fix and feature to add. GitHub Releases allows you to create tagged artifacts of your software. 644 | 645 |
646 | 647 | 1. In your repository, click the [tags](https://github.com/ldynia/flask-sherlock/tags) icon. 648 | 649 | [![Tags](assets/img/git/file_navigation_tags.png)](https://github.com/ldynia/flask-sherlock/tags) 650 | 651 | 1. Click the `Releases` tab, then click the `Create a new release` button, and fill it out with the information specified in the picture below. Once done, click the `Publish release` button. 652 | 653 | ![New Release](assets/img/git/release.png) 654 | 655 | ## 5. GitHub Actions 656 | 657 |
658 | Context 659 | 660 | #### What are GitHub Actions? 661 | 662 | GitHub Actions is a tool that allows you to automate tasks within your software development life cycle. GitHub Actions are event-driven, which means that commands that you want to execute run after the occurrence of a specified event. 663 | 664 | #### Why use GitHub Actions? 665 | 666 | GitHub Actions allows you to adopt the backbone of DevOps methodology, such as CI/CD. 667 | 668 | #### Explanation 669 | 670 | - **Continuous Integration** goal is to enable an automated way to build, package, and test applications. 671 | - **Continuous Delivery** goal is to automate the delivery of applications to a given environment (test or production) via manual release. 672 | - **Continuous Deployment** The goal is to automate code release in a production environment. 673 | 674 | #### Books 675 | 676 | [The Toyota Way: 14 Management Principles](https://www.goodreads.com/book/show/161789.The_Toyota_Way) 677 | 678 |
679 | 680 | 1. In the repository, click the [Actions](https://github.com/ldynia/flask-sherlock/actions) tab. Then click [set up a workflow yourself](https://github.com/ldynia/flask-sherlock2/new/main?filename=.github%2Fworkflows%2Fmain.yml&workflow_template=blank) link and create below workflows. **Remember to change username !!!** 681 | 682 | [![Actions](assets/img/git/tabs_actions.png)](https://github.com/ldynia/flask-sherlock/actions) 683 | ![Actions Get Started](assets/img/git/actions_get_started.png) 684 | 685 | **.github/workflows/ci.yaml** 686 | 687 | ```yaml 688 | name: Continuous Integration 689 | on: [ pull_request, workflow_dispatch ] 690 | jobs: 691 | unit_test: 692 | runs-on: ubuntu-latest 693 | env: 694 | CODE_COVERAGE_THRESHOLD: 90 695 | strategy: 696 | matrix: 697 | python-version: ["3.11", "3.12"] 698 | steps: 699 | - uses: actions/checkout@v4 700 | - name: Set up Python ${{ matrix.python-version }} 701 | uses: actions/setup-python@v5 702 | with: 703 | python-version: ${{ matrix.python-version }} 704 | - name: Install python dependencies 705 | run: pip install -r app/requirements.txt 706 | - name: Run flask app 707 | run: | 708 | export FLASK_APP=$PWD/app/run.py 709 | flask run & 710 | - name: Run unit test 711 | run: coverage run -m pytest app/ 712 | - name: Print unit test report 713 | run: coverage report 714 | - name: Validate code coverage 715 | run: | 716 | COVERAGE=$(coverage report | tail -n 1 | awk '{print $4}' | head -c 2) 717 | if [ "$COVERAGE" -lt "$CODE_COVERAGE_THRESHOLD" ]; then 718 | echo "Error: Code coverage cannot be smaller than $CODE_COVERAGE_THRESHOLD%, got $COVERAGE%" 719 | exit 1 720 | fi 721 | publish: 722 | if: "github.event_name == 'workflow_dispatch'" 723 | runs-on: ubuntu-latest 724 | needs: 725 | - unit_test 726 | env: 727 | IMAGE_ARTIFACT: ${{ secrets.DOCKER_HUB_USERNAME }}/sherlock:latest 728 | environment: production 729 | steps: 730 | - uses: actions/checkout@v4 731 | - name: Login to DockerHub 732 | run: docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} -p ${{ secrets.DOCKER_HUB_PASSWORD }} 733 | - name: Build docker image 734 | run: docker build --tag flask-sherlock $GITHUB_WORKSPACE 735 | - name: Tag docker image 736 | run: docker tag flask-sherlock $IMAGE_ARTIFACT 737 | - name: Push image to DockerHub 738 | run: docker push $IMAGE_ARTIFACT 739 | ``` 740 | 741 | 1. What's missing are the `DOCKER_HUB_USERNAME` and `DOCKER_HUB_PASSWORD` environment variables which are our secrets. Go to repository [Settings](https://github.com/ldynia/flask-sherlock/settings), click [Environments](https://github.com/ldynia/flask-sherlock/settings/environments) blade, then click [New Environment](https://github.com/ldynia/flask-sherlock/settings/environments/new) button, name it **production**. Next, click the `Configure environment` button. Finally, click the `Add Secret` button and add [DockerHub](https://hub.docker.com/settings/security) secrets. 742 | 743 | ![Settings](assets/img/git/tabs_settings.png) 744 | ![Settings](assets/img/git/environment_secrets.png) 745 | 746 | 747 | 1. Now in [Actions](https://github.com/ldynia/flask-sherlock/actions) you will see below workflows. Select the `Continuous Integration` blade, click the `Run Workflow` button, and run workflow against the **main** branch. 748 | 749 | ![Workflows](assets/img/git/actions_workfows_2.png) 750 | ![Run Workflow](assets/img/git/actions_run_workflow.png) 751 | 752 | 1. Check that our image appears in [DockerHub Repositories](https://hub.docker.com/repositories) 753 | 754 | ## 6. GitHub Pull Request 755 | 756 |
757 | Context 758 | 759 | #### What is a Pull Request? 760 | 761 | A pull request (PR) is a feature of a git hosting service that allows to create a contribution to the repository. PRs allow the maintainer of a repository to review, ask for comments, edit, or even discard submitted work. I like to think of a PR as a tangible unit of work in a collaborative world of code. 762 | 763 |
764 | 765 | 1. Fork repository by going to [https://github.com/ldynia/flask-sherlock](https://github.com/ldynia/flask-sherlock) and click `Fork` button. Next, in your fork create a new branch called **db/update** 766 | 767 | ![Fork](./assets/img/git/fork.png) 768 | ![Fork](./assets/img/git/new_branch.png) 769 | 770 | 1. Update `/app/db.json` with your favorite movie and set commit message to **Adding my favorite movie**. **Remember to stick to JSON encoding !!!** 771 | 772 | ```json 773 | { 774 | "title": "The Ghost Writer", 775 | "genre": ["crime", "drama", "mystery"], 776 | "year": 2010, 777 | "rating": 7.2, 778 | "director": ["Roman Polański"], 779 | "stars": ["Ewan McGregor", "Pierce Brosnan", "Olivia Williams"] 780 | } 781 | ``` 782 | 783 | 1. Go to your repository [https://github.com/kigetj/flask-sherlock](https://github.com/kigetj/flask-sherlock) and click the [Pull Request](https://github.com/kigetj/flask-sherlock/pulls) tab, then click the `Compare & pull request` button. Finally, write a comment and click the `Create pull request` button. 784 | 785 | [![Pull Request](assets/img/git/tabs_pr.png)](https://github.com/kigetj/flask-sherlock/pulls) 786 | [![x](./assets/img/git/pr_compare.png)](https://github.com/ldynia/flask-sherlock/compare/main...kigetj:db/update?expand=1) 787 | ![PR](assets/img/git/pr.png) 788 | ![PR Workflow](assets/img/git/pr_workflow.png) 789 | 790 | ## 7. GitHub Advanced Search 791 | 792 | - [https://github.com/search](https://github.com/search) 793 | - [GitHub search docs](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github) 794 | - [GitHub Code Search (beta)](https://docs.github.com/en/search-github/github-code-search/understanding-github-code-search-syntax) 795 | 796 |
797 | Copy 798 | 799 | - `flask in:name,description` 800 | - `HEALTHCHECK (path:**/Dockerfile OR path:**/*.Dockerfile) language:Dockerfile` 801 | - `if ((path:*.yaml OR path:*.yml) AND path:.github/workflows) language:YAML` 802 | 803 |
804 | 805 | ![Advanced Search GitHub Actions](assets/img/git/as_actions.png) 806 | ![Advanced Search GitHub Actions](assets/img/git/as_docker.png) 807 | 808 | 809 | ## 8. GitHub Pages 810 | 811 |
812 | Context 813 | 814 | #### What are GitHub Pages? 815 | 816 | GitHub Pages is a hosting service for static sites. GitHub Pages serve any static files (HTML, CSS, JavaScript) you push to the repository. You can create your static files or use a static site generator such [Jekyll](https://jekyllrb.com/docs/) to build your site. 817 | 818 | #### Why to use GitHub Pages? 819 | 820 | The short answer is for **branding** and **promotion**. You can use it for blogging or as a journal of your work. You can promote yourself with `my_username.github.io` or your project `my_username.github.io/my_project`. Moreover, you have the option to brand your work with a custom domain. 821 | 822 |
823 | 824 | 1. In repository settings [Settings](https://github.com/ldynia/flask-sherlock/settings) locate [Pages](https://github.com/ldynia/flask-sherlock/settings/pages) tab. Set `Source` to the **main** branch and `directory` to **/docs** and click the ' Save` button. 825 | 826 | [![GitHub Pages](assets/img/git/tabs_settings.png)](https://github.com/ldynia/flask-sherlock/settings) 827 | [![Github Pages](assets/img/git/settings_pages.png)](https://github.com/ldynia/flask-sherlock/settings/pages) 828 | 829 | 1. Configure [themen]([url](https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/adding-a-theme-to-your-github-pages-site-using-jekyll)) `docs/_config.yml` 830 | 831 | ``` 832 | markdown: kramdown 833 | theme: minima 834 | ``` 835 | 836 | 1. Click the `Choose a theme` button and select the `Cayman` theme (I like it the most), then click the `Select theme` button. Finally, copy and paste the below content into the interface that you see or into `docs/index.md` 837 | 838 | ~~~ 839 | ## Sherlock 840 | 841 | [webpage](https://ldynia.github.io/flask-sherlock/) 842 | 843 | Sherlock is the best movie recommendation engine ever created. Isn't it Dr. [Jekyll](https://jekyllrb.com/)? More advanced references please look up how [Jekyll docs](https://github.com/jekyll/jekyll/tree/master/docs) are structured. 844 | 845 | ![sherlock](https://c8.alamy.com/comp/HHCX7G/sherlock-holmes-with-computer-laptop-silhouette-sitting-in-rocking-HHCX7G.jpg) 846 | 847 | ## Multiverse 848 | 849 | ```bash 850 | $ echo Hello, bash! 851 | ``` 852 | 853 | ```python 854 | >>> print('Hello, python!') 855 | ``` 856 | 857 | #### Markdown 101 858 | 859 | Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for 860 | 861 | ```markdown 862 | Syntax highlighted code block 863 | 864 | > Quotes "Life is like box of chocolates" 865 | 866 | # Header 1 867 | ## Header 2 868 | #### Header 3 869 | 870 | - Bulleted 871 | - List 872 | 873 | 1. Numbered 874 | 2. List 875 | 876 | **Bold** and _Italic_ and `Code` text 877 | 878 | [Link](url) and ![Image](src) 879 | ``` 880 | ~~~ 881 | 882 | 1. Once ready, you will be able to promote your project at this URL [https://ldynia.github.io/flask-sherlock/](https://ldynia.github.io/flask-sherlock/) **Remember to change username !!!** 883 | 884 | ## 9. GitHub Wikis 885 | 886 |
887 | Context 888 | 889 | Wiki is an important part of an open-source project. `READEME.md` is intended to be used as a brief documentation on how to get started with a project. `Wiki`, on the other hand, are intended to provide information about the project that can't be expressed by code. 890 | 891 |
892 | 893 | 1. On the main page of your repository, click [Wiki](https://github.com/ldynia/flask-sherlock/wiki) tab. 894 | 895 | [![Wiki](assets/img/git/tabs_wiki.png)](https://github.com/ldynia/flask-sherlock/wiki) 896 | 897 | 1. Next, we will create below pages by clicking the [`Create Page`](https://github.com/ldynia/flask-sherlock/wiki/_new) button. 898 | 899 | **Home** 900 | 901 | ~~~ 902 | # Welcome to the flask-sherlock wiki! 903 | 904 | Wiki is an important part of an open-source project. `READEME.md` is intended to be used as a brief documentation on how to get started with a project. `Wiki`, on the other hand, is intended to provide documentation of the project that can't be expressed by code. 905 | ~~~ 906 | 907 | **Agile Manifesto** 908 | 909 | ~~~ 910 | # [Manifesto for Agile Software Development](https://agilemanifesto.org/) 911 | 912 | While there is value in the items on the right, we value the items on the left more: 913 | 914 | * Individuals and interactions over processes and tools 915 | * Working software over comprehensive documentation 916 | * Customer collaboration over contract negotiation 917 | * Responding to change over following a plan 918 | ~~~ 919 | 920 | **Code of Conduct** 921 | 922 | ~~~ 923 | # [Kubernetes Code of Conduct](https://kubernetes.io/community/code-of-conduct/) 924 | 925 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 926 | 927 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 928 | 929 | Examples of unacceptable behavior by participants include: 930 | * The use of sexualized language or imagery 931 | * Personal attacks 932 | * Trolling or insulting/derogatory comments 933 | * Public or private harassment 934 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 935 | * Other unethical or unprofessional conduct. 936 | ~~~ 937 | 938 | **_Sidebar** 939 | 940 | **Remember to change username !!!** 941 | 942 | ~~~ 943 | # Shortcuts 944 | 945 | * [Home](https://github.com/ldynia/flask-sherlock/wiki/Home) 946 | * [Agile Manifesto](https://github.com/ldynia/flask-sherlock/wiki/Agile-Manifesto) 947 | * [Code of Conduct](https://github.com/ldynia/flask-sherlock/wiki/Code-of-Conduct) 948 | ~~~ 949 | 950 | **_Footer** 951 | 952 | ~~~ 953 | Sherlock project is awesome! 954 | ~~~ 955 | -------------------------------------------------------------------------------- /assets/img/git/actions_get_started.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/actions_get_started.png -------------------------------------------------------------------------------- /assets/img/git/actions_run_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/actions_run_workflow.png -------------------------------------------------------------------------------- /assets/img/git/actions_workfows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/actions_workfows.png -------------------------------------------------------------------------------- /assets/img/git/actions_workfows_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/actions_workfows_2.png -------------------------------------------------------------------------------- /assets/img/git/as_actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/as_actions.png -------------------------------------------------------------------------------- /assets/img/git/as_docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/as_docker.png -------------------------------------------------------------------------------- /assets/img/git/btn_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/btn_new.png -------------------------------------------------------------------------------- /assets/img/git/checkbox_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/checkbox_template.png -------------------------------------------------------------------------------- /assets/img/git/environment_secrets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/environment_secrets.png -------------------------------------------------------------------------------- /assets/img/git/file_navigation_add_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/file_navigation_add_file.png -------------------------------------------------------------------------------- /assets/img/git/file_navigation_tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/file_navigation_tags.png -------------------------------------------------------------------------------- /assets/img/git/fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/fork.png -------------------------------------------------------------------------------- /assets/img/git/issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/issue.png -------------------------------------------------------------------------------- /assets/img/git/issues_new_issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/issues_new_issue.png -------------------------------------------------------------------------------- /assets/img/git/new_branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/new_branch.png -------------------------------------------------------------------------------- /assets/img/git/new_repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/new_repo.png -------------------------------------------------------------------------------- /assets/img/git/new_repo_from_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/new_repo_from_template.png -------------------------------------------------------------------------------- /assets/img/git/pr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/pr.png -------------------------------------------------------------------------------- /assets/img/git/pr_compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/pr_compare.png -------------------------------------------------------------------------------- /assets/img/git/pr_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/pr_workflow.png -------------------------------------------------------------------------------- /assets/img/git/release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/release.png -------------------------------------------------------------------------------- /assets/img/git/settings_pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/settings_pages.png -------------------------------------------------------------------------------- /assets/img/git/tabs_actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/tabs_actions.png -------------------------------------------------------------------------------- /assets/img/git/tabs_issues.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/tabs_issues.png -------------------------------------------------------------------------------- /assets/img/git/tabs_pr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/tabs_pr.png -------------------------------------------------------------------------------- /assets/img/git/tabs_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/tabs_settings.png -------------------------------------------------------------------------------- /assets/img/git/tabs_wiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/git/tabs_wiki.png -------------------------------------------------------------------------------- /assets/img/slides/astrcat_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/slides/astrcat_transparent.png -------------------------------------------------------------------------------- /assets/img/slides/bg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/slides/bg1.png -------------------------------------------------------------------------------- /assets/img/slides/bg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/slides/bg2.png -------------------------------------------------------------------------------- /assets/img/slides/bg3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/slides/bg3.png -------------------------------------------------------------------------------- /assets/img/slides/fp2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/slides/fp2.png -------------------------------------------------------------------------------- /assets/img/slides/globe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/slides/globe.png -------------------------------------------------------------------------------- /assets/img/workshop-monitor-presenter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/assets/img/workshop-monitor-presenter.png -------------------------------------------------------------------------------- /docs/links.md: -------------------------------------------------------------------------------- 1 | 2 | # Links 3 | 4 | - [choosealicense.com](https://choosealicense.com/) 5 | - [Dockerfile](https://docs.docker.com/engine/reference/builder/) 6 | - [Markdown](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) 7 | - [Jekyll](https://jekyllrb.com/) 8 | - [GitHub Actions Docs](https://docs.github.com/en/actions) 9 | - [GitHub Search Docs](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github) 10 | -------------------------------------------------------------------------------- /docs/notes.md: -------------------------------------------------------------------------------- 1 | # Presentation 2 | 3 | ## Introduction 4 | 5 | ### Who am I? 6 | 7 | Good morning, good afternoon and good evening everyone. My name is Lukasz Dynowski and I'm an independent consultant located in Copenhagen, Denmark. Having over a decade of experience in IT I was involved in over hundreds of projects. I managed to work for a start-up, in academia and for a big organization working on premise and in the cloud. I managed to do work as a FullStack Developer, DevOps Engineer, Software Architect and more. The closest to my heart though is Backend in its full spectrum that is development and operations. 8 | 9 | ### About the workshop 10 | 11 | So here I'm with around 10 years of experience with GitHub. Although, the title GitHub Fundamentals might sound trivial and well explored, you will be surprise that this workshop encapsulates several decades of software engineering. My goal is to show you how to create an open source project, how to contribute to an open source project, demonstrate you DevOps practices by utilizing GitHub actions, teach you how to create a microservice, and finally show you how you can use GitHub as a branding platform, so you can stand out from the crowd. 12 | 13 | ### Important 14 | 15 | If you have any questions that cannot be answered during this workshop then don't be shy to drop me a message on the Linked in or just simply connect with me. Furthermore, it's important for us to deliver the best quality workshop, so please leave your feedback, so we can improve on it. 16 | 17 | ### Personal Note 18 | 19 | On a personal note, I'd like to congratulate you for choosing O'Reilly as a learning platform, I believe it’s the shortest way to get access to various experts. 20 | 21 | ### Structured 22 | 23 | Structure of this workshop consist of a short presentation and hands on exercises. I'm sure that this workshop will be blast for you and for me. Without further a do lets start the presentation. 24 | 25 | ## Open Source 26 | 27 | ``` 28 | AT&T Bell Labs 1970 Unix 29 | GNU Project 1983 GNU 30 | GCC + Libs 1987 GCC 31 | 4 Freedoms 1989 GPL 32 | Linux Kernel 1991 Linux Kernel 33 | Netscape 1998 Open Source 34 | GIT 2005 GIT 35 | GitHub 2008 GitHub 36 | GitHub 2016 GitHub Universe 37 | GitHub 2018 Acquisition $7.5 billion 38 | ``` 39 | 40 | 1970s Unix - AT&T Bell Labs research center [Ken Thompson](https://en.wikipedia.org/wiki/Ken_Thompson), [Dennis Ritchie](https://en.wikipedia.org/wiki/Dennis_Ritchie), started development of Unix - [Multics](https://en.wikipedia.org/wiki/Multics)-like operating system. Unix is licensed as proprietary software which means the creator or publisher of software reserves some rights to use, modify, share modifications, or share the software. 41 | 42 | 1983 GNU ("GNU's Not Unix!") Project was created by [Richard Stallman](https://en.wikipedia.org/wiki/Richard_Stallman) to create free a Unix-like operating system with source code that could be copied, modified, and redistributed (free/libre software). The work was funded by donations from individuals and companies to the [Free Software Foundation](https://www.fsf.org/about). 43 | 44 | 1987 GCC (GNU C Compiler/Compiler Collection ) 1.0 Released. - C compiler and collection of libraries. 45 | 46 | 1989 [GPL](https://www.gnu.org/licenses/old-licenses/gpl-1.0.html) (GNU GPL or GNU General Public License) is released for use with programs released as part of the GNU project. License gives you four Freedom to: run a program for any purpose, study the mechanics of the program and modify it, redistribute copies, and improve and change modified versions for public use. 47 | 48 | 1991 LINUX Kernel - [GPL2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) licenses (28.8 million lines of code) 49 | 50 | 1998 Open Source - [Open Source Initiative](https://opensource.org/) is founded by [Bruce Perens](https://en.wikipedia.org/wiki/Bruce_Perens) and [Eric Raymond](https://en.wikipedia.org/wiki/Eric_S._Raymond) to promote [open source movement](https://en.wikipedia.org/wiki/Open-source-software_movement). The term edged out "sourceware" and was coined by [Christine Peters](https://en.wikipedia.org/wiki/Christine_Peterson) to describe free software. 51 | 52 | 2005 GIT - Being dissatisfied with the Source Control Management (SCM) system of that time and inspired by the workflow of [BitKeeper](https://en.wikipedia.org/wiki/BitKeeper) Linus Torvalds created GIT for the purpose of developing the Linux kernel. 53 | 54 | 2008 GitHub - Known as Logical Awesome LLC GitHub is founded. 55 | 56 | [“Free software”](https://www.gnu.org/philosophy/free-sw.html) means software that respects users' freedom and community. Roughly, it means that the users have the freedom to run, copy, distribute, study, change, and improve the software. Thus, “free software” is a matter of liberty, not price. To understand the concept, you should think of “free” as in “free speech,” not as in “free beer”. We sometimes call it “libre software,” borrowing the French or Spanish word for “free” as in freedom, to show we do not mean the software is gratis. 57 | 58 | ## Licenses - Permissive vs Copyleft 59 | 60 | Permissive licenses allow you to copy, modify, recombine, and redistribute the work with minimal restrictions. Usually, only attribution is required. Copyleft provides the same permission as a permissive license but requires you to release any derivative works you make under the same copyleft license. 61 | 62 | | | Copyleft | Permissive | 63 | | - | --- | ----------- | 64 | | Linux | GPLv2 | - | 65 | | Kubernetes | - | Apache 2.0 | 66 | | GitLab | - | MIT | 67 | | Mongo | SSPL* | - | 68 | 69 | --- 70 | 71 | 72 | # Technologies 73 | 74 | ## Git 75 | 76 | Git is a source version control developed by Linus Torvalds. 77 | 78 | ## Markdown 79 | 80 | Markdown is a markup language. The goal was to create an easy to read and write annotated text that can be optionally converted into HTML. Created by John Gruber and Aaron Swartz in 2004 (17 years old at the time). 81 | 82 | ## Docker 83 | 84 | Docker is a containerization platform created by Solomon Hykes in 2013 (10 years ago). Containers are executable artifacts containing OS, OS's libraries, application source code, and dependencies described in a Dockerfile and movable as a docker image. 85 | 86 | ## Jekyll 87 | 88 | Static site generator written by GitHub's co-founder Tom Preston-Werner in 2008. It takes Markdown as an input and produces a static web page ready to be served by any HTTP server. It can be used with any front-end framework. 89 | 90 | --- 91 | 92 | # GitHub 93 | 94 | ## 1. GitHub Repository 95 | 96 | ### What is a GitHub repository? 97 | 98 | A repository is a place where your git project and its files reside. A typical repository stores source code along with .git folder - a directory which tracks snapshot of changes introduced to your files. 99 | 100 | ### Why to use GitHub repository? 101 | 102 | GitHub repository is: 103 | 104 | - A place for documenting your project - GitHub Wiki 105 | - A place for automatizing tasks within software development life cycle - GitHub Actions 106 | - A place for organizing and tracking work items - GitHub Projects 107 | - A forum for sharing and raising questions - GitHub Issues 108 | - A place for security scanning - GitHub Security 109 | - And many more ... 110 | 111 | ## 2. GitHub Template 112 | 113 | ### What is a GitHub template? 114 | 115 | A GitHub Template is a way of marking your repository as a reusable blueprint. GitHub Template allows to create a new repositories from the blueprint repository that will preserve the same structure, branches, and files. 116 | 117 | ## 3. GitHub Codespaces 118 | 119 | ### What is a GitHub codespaces? 120 | 121 | A codespace is a Ubuntu Linux development environment that's hosted in the cloud. Codespace is a Docker container that runs in a VirtualMachine. Codespaces are customizable by `devcontainer.json` file -file that describes your development environment. 122 | 123 | ### Why to use GitHub codespaces? 124 | 125 | To get freedom from your laptop. With codespace, you can literally develop on a tablet. Any device that has an internet browser. 126 | 127 | ## 4. Microservice 128 | 129 | ### What is microservice and why to use them? 130 | 131 | Microservices/Microservice Architecture - is an architectural style that structures an application as a collection of services that are: 132 | 133 | - Loosely coupled. 134 | - Independently deployable. 135 | - Highly maintainable and testable. 136 | - Organized around business capabilities. 137 | - Owned by a small team. 138 | 139 | A typical microservice exposes the following features: 140 | 141 | - Is structured around business boundaries / bounded context 142 | - Has an independent database. 143 | - Communicates over the network. 144 | - Has well defined API. 145 | 146 | ## 3. GitHub Issues 147 | 148 | ### What are GitHub Issues? 149 | 150 | GitHub Issues is a tool for keeping track of tasks, bugs and feedback for your project. 151 | 152 | ### Why to use GitHub Issues? 153 | 154 | It's just a convenient way to manage all affairs related to your project. 155 | 156 | ## 4. GitHub Releases / Tags 157 | 158 | It's very seldom that your software will be released only in one version, e.g. v1.0.0. As your project grows, you will have a bug to fix and feature to add. GitHub Releases allows you to create tagged artifacts of your software. 159 | 160 | ## 5. GitHub Actions 161 | 162 | ### What is GitHub Actions? 163 | 164 | GitHub Actions is a tool that allows you to automate tasks within your software development life cycle. GitHub Actions are event-driven, which means that commands that you want to execute run after occurrence of a specified event. 165 | 166 | ### Why to use GitHub Actions? 167 | 168 | GitHub Actions allows you to adopt backbone of DevOps methodology such CI/CD. 169 | 170 | Explenation: 171 | 172 | - **Continuous Integration** goal is to automatize tasks related to building, packagin, and testing application. 173 | - **Continuous Delivery** goal is to automatize the delivery of applications to given environment (test or production) via manual release. 174 | - **Continuous Deployment** goal is to automatize software release to a production environment - no manual. 175 | 176 | ## 6. GitHub Pull Request 177 | 178 | ### What is a Pull Request? 179 | 180 | A pull request (PR) is a feature of a git hosting service that allows to create a contribution to the repository. PRs allow the maintainer of a repository to review, ask for comments, edit or even discard submitted work. I like to think of a PR as a **tangible unit of work in a collaborative world of code**. 181 | 182 | ## 8. GitHub Pages 183 | 184 | ### What is GitHub Pages? 185 | 186 | GitHub Pages is a hosting service for static sites, it will serve any static files (HTML, CSS, JavaScript) that you push to repository. You can create your own website from static files, or use a static website generator such [Jekyll](https://jekyllrb.com/docs/). 187 | 188 | ### Why to use GitHub Pages? 189 | 190 | The short answer is for **branding** and **promotion**. You can use it for blogging, or as a journal of your work. You can promote yourself with my_username.github.io or your project my_username.github.io/my_project. Moreover, you have the option to brand your work with a custom domain. 191 | 192 | ## 9. GitHub Wikis 193 | 194 | Wiki is an important part of an open-source project. READEME.md is intended to be used as a brief documentation on how to get started with a project. Wiki on the other hand are intended to provide information about the project that can't be expressed by code. 195 | 196 | --- 197 | 198 | # Links 199 | 200 | - https://opensource.org/osd 201 | - https://www.gnu.org/philosophy/free-sw.en.html 202 | - https://www.youtube.com/channel/UCQvR8lgE9rishcKT_hZT6eQ 203 | - https://en.wikipedia.org/wiki/License_compatibility#:~:text=The%20need%20for%20such%20a,and%20publish%20a%20new%20program. 204 | - https://www.thehyve.nl/articles/open-source-software-licenses-part-3 205 | -------------------------------------------------------------------------------- /docs/polls.md: -------------------------------------------------------------------------------- 1 | # Polls 2 | 3 | 1. How do you use GitHub ? 4 | - I've just started using it (no. repositories 0-5). 5 | - I use it only for my own projects (no. repositories >= 10). 6 | - I use it mainly in DevOps context GitHub Actions. 7 | - I regularly contribute to an open source projects. 8 | 9 | 1. How fluent are you with docker ? 10 | - I preliminarily consume docker images. 11 | - I preliminarily build docker images. 12 | - Docker captain level! Ahoy! 13 | 14 | 1. How good is your python ? 15 | - Beginner 16 | - Confident (Scripting) 17 | - Master (Object-Oriented) 18 | - Expert 19 | -------------------------------------------------------------------------------- /docs/proposal.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | github.com is a central hub for most of the open source software, counting over 94 million users, it is the biggest hosting platform for storing a source code versioned with GIT. Yet, it still manages to be highly underutilized. Correctly managed, GitHub can be a proven record of your work and interests -a resume that can be used to get your dream job; a hosting platform to promote yourself or your software; a CI/CD platform that delivers your software to millions; a fantastic collaborative tool for your team that facilitates communication among developers. Finally, GitHub is my go-to place for solving daily programming challenges and fixations - say “Hasta la vista” stackoverflow.com. In this course, you’ll build a production ready microservice (with Flask) and deploy it to dockerhub.com (the central image registry) by utilizing GitHub Actions as CI/CD pipeline. Once the software is implemented, you’ll create a web page and a wiki to promote, maintain and collaborate on an open source software. 4 | 5 | ### By the end of this live, hands-on, online course, you’ll understand: 6 | 7 | - [ ] What is github.com 8 | - [ ] How to create an Open Source project. 9 | - [ ] Markdown markup language. 10 | - [ ] Docker 11 | - [ ] Github Actions 12 | - [ ] Github Pages 13 | - [ ] Github Wikis 14 | - [ ] Github Templates 15 | - [ ] Pull Requests 16 | - [ ] Github Issues 17 | - [ ] Github Advanced Search 18 | 19 | 20 | ### And you’ll be able to: 21 | 22 | - [ ] Create an open source project. 23 | - [ ] Build a team to collaborate on the project. 24 | - [ ] Collaborate on an open source project by creating issues and doing Pull Requests. 25 | - [ ] Utilize CI/CD pipelines to build your software with github actions. 26 | - [ ] Host your portfolio page. 27 | - [ ] Use github’s advanced search. 28 | - [ ] User GIT version control. 29 | - [ ] Share your software on dockerhub.com 30 | - [ ] Build software with Docker. 31 | 32 | ### This training is for you because... 33 | 34 | - [ ] You’re a software developer or want to become one. 35 | - [ ] You want to know how to work on an open source project like a pro. 36 | - [ ] You want to learn DevOps and build production ready software. 37 | - [ ] You’d like to discover the life cycle of a modern software. 38 | - [ ] You’d like to learn how to do advanced search with github 39 | 40 | 41 | # Course overview (75 minutes) 42 | 43 | - [ ] Introduction: 3 min 44 | - [ ] Objectives: 2 min 45 | - [ ] Poll: 1st poll 2 min 46 | - [ ] Poll: 2nd poll 2 min 47 | - [ ] Exercise: Creating an open source repository 5 min 48 | - [ ] Exercise: Cloning a repository 2 min 49 | - [ ] Exercise: Creating an Issue and first pull request 5 min 50 | - [ ] Exercise: Creating a template repository 3 min 51 | - [ ] Exercise: Dockerizing Flask microservice 10 min 52 | - [ ] Exercise: Building docker image with Github Actions - CI/CD pipeline 10 min 53 | - [ ] Exercise: Documenting open source project with Wiki 5 min 54 | - [ ] Exercise: Promoting Project with github pages 5 min 55 | - [ ] Use Case: Using github’s Advanced Search 10 min 56 | - [ ] Q&A: 5-10min 57 | 58 | # Biography 59 | 60 | Lukasz Dynowski is an independent consultant who in his career was involved in over 150+ projects. Counting over 10 years of experience as a software engineer Lukasz was doing Full-stack development, DevOps, Software Architecture, as well as building distributed systems for High Performance Computing. Lukasz has a scientific background; he spent a number of years in academia where he was co-author and contributor to several scientific papers in high impact journals. Besides that, Lukasz is a firm believer of open source projects, and values behind Agile Manifesto. He had a chance to spin start several open source projects and contribute to few including Kubernetes. Lukasz is finalist of the first ever O’Reilly’s Software Architectural Katas, and is ranked in the first 0.5% of developers on stackoverflow.com. Finally, he calls himself “the biggest geek that he knows” and he’s a husband to a lovely wife and a father to two lovely kids. 61 | 62 | # Links 63 | 64 | * [O'Reilly Proposal](https://docs.google.com/document/d/1B55XFChBUx2S3xRnaYFTf-un73ixX7rvOVw1TBHaXuk/edit#) 65 | -------------------------------------------------------------------------------- /docs/requirements.md: -------------------------------------------------------------------------------- 1 | Welcome to GitHub Fundamentals workshop. Prerequisites to this workshop is to have access (user account) to https://github.com/ and https://hub.docker.com/. All materials are availiable here https://github.com/ldynia/workshop-github-fundamentals. Please, give us big shout about your location. Thanks! 2 | -------------------------------------------------------------------------------- /docs/timing.md: -------------------------------------------------------------------------------- 1 | # 3 Hours Workshop 2 | 3 | | Topic | Time [min] | Time Total | 4 | | --- | --- | --- | 5 | | Introduction | 5 | 5 | 6 | | Poll | 3 | 8 | 7 | | Presentation | 17 | 25 | 8 | | Q&A | 3 | 28 | 9 | | GitHub Repo | 35 | 1:03 | 10 | | BREAK | 10 | 1:13 | 11 | | --- | --- | --- | 12 | | Q&A | 3 | 1:16 | 13 | | Microservice | 18 | 1:34 | 14 | | Q&A | 3 | 1:37 | 15 | | GitHub Issues | 4 | 1:41 | 16 | | GitHub Release | 4 | 1:45 | 17 | | Q&A | 2 | 1:47 | 18 | | GitHub Actions | 14 | 2:01 | 19 | | BREAK | 10 | 2:11 | 20 | | --- | --- | --- | 21 | | Q&A | 3 | 2:14 | 22 | | GitHub PR | 9 | 2:23 | 23 | | GitHub Pages | 8 | 2:31 | 24 | | GitHub Wikis | 8 | 2:40 | 25 | | GitHub Search | 10 | 2:50 | 26 | | GitHub Copilot | 5 | 2:55 | 27 | -------------------------------------------------------------------------------- /slides/github_fundamentals.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ldynia/workshop-github-fundamentals/d9df154e1c3f678a1df5b6fe1183dd20cd03bf61/slides/github_fundamentals.pdf -------------------------------------------------------------------------------- /src/.github/workflows/cicd.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration & Delivery 2 | on: [ pull_request, workflow_dispatch ] 3 | jobs: 4 | unit_test: 5 | runs-on: ubuntu-latest 6 | env: 7 | PORT: 8080 8 | HOST: 0.0.0.0 9 | CODE_COVERAGE_THRESHOLD: 95 10 | strategy: 11 | matrix: 12 | python-version: [3.7, 3.8, 3.9] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install python dependencies 20 | run: pip install -r app/requirements.txt 21 | - name: Run flask app 22 | run: | 23 | export FLASK_APP=$PWD/app/run.py 24 | flask run --host=$HOST --port=$PORT & 25 | - name: Run unit test 26 | run: coverage run -m pytest app/ 27 | - name: Print unit test report 28 | run: coverage report 29 | - name: Validate code coverage 30 | run: | 31 | COVERAGE=$(coverage report | tail -n 1 | awk '{print $4}' | head -c 2) 32 | if [ "$COVERAGE" -lt "$CODE_COVERAGE_THRESHOLD" ]; then 33 | echo "Error: Code coverage cannot be smaller than $CODE_COVERAGE_THRESHOLD%, got $COVERAGE%" 34 | exit 1 35 | fi 36 | deploy: 37 | runs-on: ubuntu-latest 38 | needs: 39 | - unit_test 40 | env: 41 | DOCKER_HUB_USERNAME: ldynia 42 | IMAGE_ARTIFACT: ldynia/sherlock:latest 43 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 44 | environment: production 45 | steps: 46 | - uses: actions/checkout@v2 47 | - name: Login to DockerHub 48 | run: docker login -u $DOCKER_HUB_USERNAME -p $ACCESS_TOKEN 49 | - name: Build docker image 50 | run: docker build --tag flask-sherlock $GITHUB_WORKSPACE 51 | - name: Tag docker image 52 | run: docker tag flask-sherlock $IMAGE_ARTIFACT 53 | - name: Push image to DockerHub 54 | run: docker push $IMAGE_ARTIFACT 55 | -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7.9-alpine 2 | 3 | # Setup environment variables 4 | ENV PORT=8080 \ 5 | HOST=0.0.0.0 \ 6 | FLASK_APP=/app/run.py \ 7 | PYTHONUNBUFFERED=True 8 | ARG FLASK_DEBUG=False 9 | ENV FLASK_DEBUG=$FLASK_DEBUG 10 | 11 | # Setup file system 12 | WORKDIR /app 13 | COPY app/ /app 14 | 15 | # Upgrade pip & install python packages 16 | RUN pip install --upgrade pip --requirement /app/requirements.txt 17 | 18 | # Indicate which port to expose 19 | EXPOSE $PORT 20 | 21 | # Start app server 22 | CMD flask run --host=$HOST --port=$PORT -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Sherlock 2 | 3 | Welcome to Sherlock project. Sherlock is a movie recommendation microservice written in Flask. 4 | 5 | Below steps can be executed on any unix like system. I will use ubuntu deployed on [O'Reilly's sandbox](https://learning.oreilly.com/scenarios/ubuntu-sandbox/9781492062837) (alternatively you could use [Katacoda's playground](https://www.katacoda.com/courses/ubuntu/playground2004)). Once the sandbox/playground is ready, execute instructions specified in below sections. 6 | 7 | ## Setup SSH key 8 | 9 | Create ssh key and add it to GitHub's [SSH keys](https://github.com/settings/keys) settings. 10 | 11 | ```bash 12 | ssh-keygen 13 | cat ~/.ssh/id_rsa.pub 14 | ``` 15 | 16 | ## Installation 17 | 18 | ```bash 19 | # Cloning the source code 20 | git clone https://github.com/ldynia/flask-sherlock.git 21 | cd flask-sherlock 22 | 23 | # Building and running docker container 24 | docker build --tag flask-sherlock --build-arg FLASK_DEBUG=True . 25 | docker run --detach --name sherlock --publish 80:8080 --rm flask-sherlock 26 | docker ps 27 | ``` 28 | 29 | ## API 30 | 31 | ```bash 32 | curl "http://localhost/api/v1/movies/recommend?title=Kingpin" 33 | curl "http://localhost/api/v1/movies/recommend?title=Lost%20in%20Translation" 34 | ``` 35 | 36 | ## Testing 37 | 38 | Unit test 39 | 40 | ```bash 41 | docker exec sherlock pytest 42 | ``` 43 | 44 | Code coverage 45 | 46 | ```bash 47 | docker exec sherlock coverage run -m pytest 48 | docker exec sherlock coverage report 49 | ``` 50 | 51 | ## Debug 52 | 53 | ```bash 54 | { 55 | docker stop sherlock; 56 | docker build --no-cache --tag flask-sherlock $PWD; 57 | docker run --rm --detach --name sherlock --publish 80:8080 --volume $PWD/app:/app flask-sherlock; 58 | } 59 | ``` -------------------------------------------------------------------------------- /src/app/db.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "title": "Groundhog Day", 3 | "genre": ["comedy", "fantasy", "romance"], 4 | "year": 1993, 5 | "rating": 8.0, 6 | "directors": ["Harold Ramis"], 7 | "stars": ["Bill Murray", "Andie MacDowell", "Chris Elliott", "Punxsutawney Phil"] 8 | }, { 9 | "title": "Kingpin", 10 | "genre": ["comedy", "sport"], 11 | "year": 1996, 12 | "rating": 6.9, 13 | "director": ["Bobby Farrelly", "Peter Farrelly"], 14 | "stars": ["Woody Harrelson", "Randy Quaid", "Bill Murray"] 15 | }, { 16 | "title": "The Bridges of Madison County", 17 | "genre": ["drama", "romance"], 18 | "year": 1995, 19 | "rating": 7.6, 20 | "director": ["Clint Eastwood"], 21 | "stars": ["Clint Eastwood", "Meryl Streep"] 22 | }, { 23 | "title": "Good Will Hunting", 24 | "genre": ["drama", "romance"], 25 | "year": 1997, 26 | "rating": 8.3, 27 | "director": ["Gus Van Sant"], 28 | "stars": ["Robin Williams", "Matt Damon", "Ben Affleck"] 29 | }, { 30 | "title": "The Rainmaker", 31 | "genre": ["crime", "drama", "thriller"], 32 | "year": 1997, 33 | "rating": 7.2, 34 | "director": ["Francis Ford Coppola"], 35 | "stars": ["Matt Damon", "Danny DeVito", "Claire Danes"] 36 | }, { 37 | "title": "Ghost in the Shell", 38 | "genre": ["animation", "action", "crime"], 39 | "year": 1995, 40 | "rating": 8.0, 41 | "director": ["Mamoru Oshii"], 42 | "stars": ["Atsuko Tanaka", "Iemasa Kayumi", "Akio Ôtsuka"] 43 | }, { 44 | "title": "Aliens", 45 | "genre": ["action", "adventure", "sci-fi"], 46 | "year": 1986, 47 | "rating": 8.3, 48 | "director": ["James Cameron"], 49 | "stars": ["Sigourney Weaver", "Michael Biehn", "Carrie Henn"] 50 | }, { 51 | "title": "Terminator 2", 52 | "genre": ["action", "sci-fi"], 53 | "year": 1986, 54 | "rating": 8.5, 55 | "director": ["James Cameron"], 56 | "stars": ["Arnold Schwarzenegger", "Linda Hamilton", "Edward Furlong"] 57 | }, { 58 | "title": "Lethal Weapon 2", 59 | "genre": ["action", "crime", "thriller"], 60 | "year": 1989, 61 | "rating": 7.2, 62 | "director": ["Richard Donner"], 63 | "stars": ["Mel Gibson", "Danny Glover", "Joe Pesci"] 64 | }, { 65 | "title": "Lost in Translation", 66 | "genre": ["comedy", "drama"], 67 | "year": 3003, 68 | "rating": 7.7, 69 | "director": ["Sofia Coppola"], 70 | "stars": ["Bill Murray", "Scarlett Johansson", "Giovanni Ribisi"] 71 | }] 72 | -------------------------------------------------------------------------------- /src/app/rengine.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | 4 | class Sherlock(): 5 | """ 6 | Movies recommendation engine. 7 | """ 8 | 9 | def __init__(self, movies, features): 10 | self.movies = movies 11 | self.title = features.get("title") 12 | self.features = ["genre", "stars"] 13 | 14 | def recommend(self): 15 | """ 16 | Algorithm recommends movies based on default movie features. 17 | The algorithm uses partial match as search criteria and returns sorted list of movie(s). 18 | """ 19 | ref_movie = self.__get_movie(self.title) 20 | if not ref_movie: 21 | return self.__lucky_recommendation(self.movies) 22 | ref_movie = ref_movie[0] 23 | 24 | movies = [] 25 | for movie in self.movies: 26 | if movie["title"] != self.title: 27 | for feature in self.features: 28 | feature_match = [fm in movie[feature] for fm in ref_movie[feature]] 29 | if any(feature_match): 30 | movies.append(movie) 31 | break 32 | 33 | return sorted(movies, key=lambda movie: movie["rating"], reverse=True) 34 | 35 | 36 | def __lucky_recommendation(self, movies): 37 | """ 38 | I feel lucky - random choice. 39 | """ 40 | return [random.choice(movies)] 41 | 42 | def __get_movie(self, title): 43 | """ 44 | Find movie by title. 45 | """ 46 | movie = [movie for movie in self.movies if movie["title"] == title] 47 | return movie if movie else [] 48 | -------------------------------------------------------------------------------- /src/app/requirements.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | Flask 3 | pytest -------------------------------------------------------------------------------- /src/app/run.py: -------------------------------------------------------------------------------- 1 | import json 2 | from json.decoder import JSONDecodeError 3 | 4 | from flask import Flask 5 | from flask import jsonify 6 | from flask import request 7 | 8 | from rengine import Sherlock 9 | 10 | 11 | # Set up app 12 | app = Flask(__name__) 13 | app.config["JSON_AS_ASCII"] = False 14 | 15 | 16 | def read_data(source): 17 | """ 18 | Reads file that is expected to hold JSON encoded content. 19 | In case of errors return empty data and list holding error message. 20 | """ 21 | data = [] 22 | errors = [] 23 | try: 24 | with open(source) as db: 25 | content = db.read() 26 | data = json.loads(content) 27 | except FileNotFoundError as e: 28 | errors = [f"Reading {source}, {str(e)}"] 29 | except JSONDecodeError as e: 30 | errors = [f"Reading {source}, {str(e)}"] 31 | except Exception as e: 32 | errors = [f"Reading {source}, {str(e)}"] 33 | 34 | return data, errors 35 | 36 | 37 | @app.route("/api/v1/movies/recommend", methods=["GET"]) 38 | def recommend(): 39 | """ 40 | Function loads movies from db and returns recommendations. 41 | """ 42 | MOVIES, errors = read_data("/app/db.json") 43 | if errors: 44 | return jsonify({"errors": errors, "status_code": 500}), 500 45 | 46 | sherlock = Sherlock(MOVIES, request.args) 47 | recommendation = sherlock.recommend() 48 | 49 | return jsonify(recommendation) 50 | -------------------------------------------------------------------------------- /src/app/test_app.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from run import app 4 | 5 | 6 | @pytest.fixture 7 | def client(): 8 | app.config["TESTING"] = True 9 | with app.test_client() as client: 10 | yield client 11 | 12 | 13 | def test_api(client): 14 | response = client.get("/api/v1/movies/recommend") 15 | assert response.status_code == 200 16 | assert response.is_json 17 | assert response.get_json()[0]["title"] != "" 18 | 19 | response = client.get("/api/v1/movies/recommend?title=Kingpin") 20 | assert response.status_code == 200 21 | assert response.is_json 22 | assert len(response.get_json()) >= 2 23 | 24 | response = client.get("/api/v1/movies/recommend?title=Lost%20in%20Translation") 25 | assert response.status_code == 200 26 | assert response.is_json 27 | assert len(response.get_json()) >= 5 28 | --------------------------------------------------------------------------------