├── requirements.txt ├── app.py ├── Dockerfile ├── scripts └── git_update.sh ├── .github └── workflows │ └── gcp.yml └── .gitignore /requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.0.3 2 | Flask==2.0.2 3 | itsdangerous==2.0.1 4 | Jinja2==3.0.2 5 | MarkupSafe==2.0.1 6 | Werkzeug==2.0.2 7 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | app = Flask(__name__) 4 | 5 | @app.route("/hello") 6 | def hello_world(): 7 | return "Hello, World!" 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim-buster 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt requirements.txt 6 | 7 | RUN pip3 install -r requirements.txt 8 | 9 | COPY . . 10 | 11 | CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"] 12 | -------------------------------------------------------------------------------- /scripts/git_update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION="" 4 | 5 | # get parameters 6 | while getopts v: flag 7 | do 8 | case "${flag}" in 9 | v) VERSION=${OPTARG};; 10 | esac 11 | done 12 | 13 | # get highest tag number, and add v0.1.0 if doesn't exist 14 | git fetch --prune --unshallow 2>/dev/null 15 | CURRENT_VERSION=`git describe --abbrev=0 --tags 2>/dev/null` 16 | 17 | if [[ $CURRENT_VERSION == '' ]] 18 | then 19 | CURRENT_VERSION='v0.1.0' 20 | fi 21 | echo "Current Version: $CURRENT_VERSION" 22 | 23 | # replace . with space so can split into an array 24 | CURRENT_VERSION_PARTS=(${CURRENT_VERSION//./ }) 25 | 26 | # get number parts 27 | VNUM1=${CURRENT_VERSION_PARTS[0]} 28 | VNUM2=${CURRENT_VERSION_PARTS[1]} 29 | VNUM3=${CURRENT_VERSION_PARTS[2]} 30 | 31 | if [[ $VERSION == 'major' ]] 32 | then 33 | VNUM1=v$((VNUM1+1)) 34 | elif [[ $VERSION == 'minor' ]] 35 | then 36 | VNUM2=$((VNUM2+1)) 37 | elif [[ $VERSION == 'patch' ]] 38 | then 39 | VNUM3=$((VNUM3+1)) 40 | else 41 | echo "No version type (https://semver.org/) or incorrect type specified, try: -v [major, minor, patch]" 42 | exit 1 43 | fi 44 | 45 | # create new tag 46 | NEW_TAG="$VNUM1.$VNUM2.$VNUM3" 47 | echo "($VERSION) updating $CURRENT_VERSION to $NEW_TAG" 48 | 49 | # get current hash and see if it already has a tag 50 | GIT_COMMIT=`git rev-parse HEAD` 51 | NEEDS_TAG=`git describe --contains $GIT_COMMIT 2>/dev/null` 52 | 53 | # only tag if no tag already 54 | if [ -z "$NEEDS_TAG" ]; then 55 | echo "Tagged with $NEW_TAG" 56 | git tag $NEW_TAG 57 | git push --tags 58 | git push 59 | else 60 | echo "Already a tag on this commit" 61 | fi 62 | 63 | echo ::set-output name=git-tag::$NEW_TAG 64 | 65 | exit 0 66 | -------------------------------------------------------------------------------- /.github/workflows/gcp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build and Push Python Image to Google Cloud Platform 3 | on: 4 | push: 5 | branches: [ main ] 6 | jobs: 7 | build-push-gcr: 8 | name: Build and Push to GCP 9 | runs-on: ubuntu-latest 10 | env: 11 | IMAGE_NAME: lesson-087 12 | PROJECT_ID: devopsbyexample-325402 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - uses: google-github-actions/setup-gcloud@master 18 | with: 19 | service_account_key: ${{ secrets.SERVICE_ACCOUNT_KEY }} 20 | project_id: ${{ env.PROJECT_ID }} 21 | export_default_credentials: true 22 | 23 | - name: Build Docker Image 24 | run: docker build -t $IMAGE_NAME:latest . 25 | 26 | - name: Automatic Tagging of Releases 27 | id: increment-git-tag 28 | run: | 29 | bash ./scripts/git_update.sh -v major 30 | 31 | - name: Configure Docker Client 32 | run: |- 33 | gcloud auth configure-docker --quiet 34 | gcloud auth configure-docker us-west2-docker.pkg.dev --quiet 35 | 36 | - name: Push Docker Image to Container Registry (GCR) 37 | env: 38 | GIT_TAG: ${{ steps.increment-git-tag.outputs.git-tag }} 39 | run: |- 40 | docker tag $IMAGE_NAME:latest gcr.io/$PROJECT_ID/$IMAGE_NAME:latest 41 | docker tag $IMAGE_NAME:latest gcr.io/$PROJECT_ID/$IMAGE_NAME:$GIT_TAG 42 | docker push gcr.io/$PROJECT_ID/$IMAGE_NAME:latest 43 | docker push gcr.io/$PROJECT_ID/$IMAGE_NAME:$GIT_TAG 44 | 45 | - name: Push Docker Image to Artifact Registry 46 | env: 47 | GIT_TAG: ${{ steps.increment-git-tag.outputs.git-tag }} 48 | run: |- 49 | docker tag $IMAGE_NAME:latest us-west2-docker.pkg.dev/$PROJECT_ID/images/$IMAGE_NAME:latest 50 | docker tag $IMAGE_NAME:latest us-west2-docker.pkg.dev/$PROJECT_ID/images/$IMAGE_NAME:$GIT_TAG 51 | docker push us-west2-docker.pkg.dev/$PROJECT_ID/images/$IMAGE_NAME:latest 52 | docker push us-west2-docker.pkg.dev/$PROJECT_ID/images/$IMAGE_NAME:$GIT_TAG 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | --------------------------------------------------------------------------------