diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000000000000000000000000000000000000..ea0d23a3dc28a6118bcf3ac9744ce64fc478d894 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# Copyright 2015, Google, Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START docker] + +# The Google App Engine python runtime is Debian Jessie with Python installed +# and various os-level packages to allow installation of popular Python +# libraries. The source is on github at: +# https://github.com/GoogleCloudPlatform/python-docker +FROM gcr.io/google_appengine/python + +# Create a virtualenv for the application dependencies. +# # If you want to use Python 2, use the -p python2.7 flag. +RUN virtualenv -p python3 /env +ENV PATH /env/bin:$PATH + +ADD requirements.txt /app/requirements.txt +RUN /env/bin/pip install --upgrade pip && /env/bin/pip install -r /app/requirements.txt +ADD . /app + +CMD gunicorn -b :$PORT mysite.wsgi +# [END docker] diff --git a/Makefile b/Makefile new file mode 100755 index 0000000000000000000000000000000000000000..a06da05cc66b2d22086a7ad526c6a3571fbf37d3 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +GCLOUD_PROJECT:=$(shell gcloud config list project --format="value(core.project)") + +.PHONY: all +all: deploy + +.PHONY: create-cluster +create-cluster: + gcloud container clusters create polls \ + --scopes "https://www.googleapis.com/auth/userinfo.email","cloud-platform" + +.PHONY: create-bucket +create-bucket: + gsutil mb gs://$(GCLOUD_PROJECT) + gsutil defacl set public-read gs://$(GCLOUD_PROJECT) + +.PHONY: build +build: + docker build -t gcr.io/$(GCLOUD_PROJECT)/polls . + +.PHONY: push +push: build + gcloud docker push -- gcr.io/$(GCLOUD_PROJECT)/polls + +.PHONY: template +template: + sed -i ".tmpl" "s/\$$GCLOUD_PROJECT/$(GCLOUD_PROJECT)/g" polls.yaml + +.PHONY: deploy +deploy: push template + kubectl create -f polls.yaml + +.PHONY: update +update: + kubectl patch deployment polls -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"`date +'%s'`\"}}}}}" + +.PHONY: delete +delete: + kubectl delete rc polls + kubectl delete service polls diff --git a/README.md b/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1d45703e2b8ddb3de756ff70d3e678dc31ba269a 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,26 @@ +# Getting started with Django on Google Container Engine + +[![Open in Cloud Shell][shell_img]][shell_link] + +[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png +[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=container_engine/django_tutorial/README.md + +This repository is an example of how to run a [Django](https://www.djangoproject.com/) +app on Google Container Engine. It uses the +[Writing your first Django app](https://docs.djangoproject.com/en/1.11/intro/tutorial01/) +Polls application (parts 1 and 2) as the example app to deploy. From here on +out, we refer to this app as the 'polls' application. + + +# Tutorial +See our [Django on Container Engine](https://cloud.google.com/python/django/container-engine) tutorial for instructions for setting up and deploying this sample application. + + +## Contributing changes + +* See [CONTRIBUTING.md](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/CONTRIBUTING.md) + + +## Licensing + +* See [LICENSE](https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/LICENSE) diff --git a/__init__.py b/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/manage.py b/manage.py new file mode 100755 index 0000000000000000000000000000000000000000..2062ecd2666d174a81e1e9c71126fed937f800eb --- /dev/null +++ b/manage.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/mysite/__init__.py b/mysite/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/mysite/settings.py b/mysite/settings.py new file mode 100755 index 0000000000000000000000000000000000000000..bdcb39913e9c86c531041f95eb48273af313e606 --- /dev/null +++ b/mysite/settings.py @@ -0,0 +1,115 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'pf-@jxtojga)z+4s*uwbgjrq$aep62-thd0q7f&o77xtpka!_m' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +# SECURITY WARNING: If you deploy a Django app to production, make sure to set +# an appropriate host here. +# See https://docs.djangoproject.com/en/1.10/ref/settings/ +ALLOWED_HOSTS = ['*'] + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'polls' +) + +MIDDLEWARE = ( + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +ROOT_URLCONF = 'mysite.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'mysite.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/1.8/ref/settings/#databases + +# [START dbconfig] +DATABASES = { + 'default': { + # If you are using Cloud SQL for MySQL rather than PostgreSQL, set + # 'ENGINE': 'django.db.backends.mysql' instead of the following. + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'polls', + 'USER': os.getenv('DATABASE_USER'), + 'PASSWORD': os.getenv('DATABASE_PASSWORD'), + 'HOST': '127.0.0.1', + 'PORT': '5432', + } +} +# [END dbconfig] + +# Internationalization +# https://docs.djangoproject.com/en/1.8/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.8/howto/static-files/ + +# [START staticurl] +STATIC_URL = '/static/' +# STATIC_URL = 'https://storage.googleapis.com/<your-gcs-bucket>/static/' +# [END staticurl] + +STATIC_ROOT = 'static/' diff --git a/mysite/urls.py b/mysite/urls.py new file mode 100755 index 0000000000000000000000000000000000000000..bbb417c431e5f6dbff3466c8911d344c68233762 --- /dev/null +++ b/mysite/urls.py @@ -0,0 +1,29 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.conf import settings +from django.contrib import admin +from django.contrib.staticfiles.urls import staticfiles_urlpatterns +from django.urls import include, path + + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('polls.urls')), +] + +# Only serve static files from Django during development +# Use Google Cloud Storage or an alternative CDN for production +if settings.DEBUG: + urlpatterns += staticfiles_urlpatterns() diff --git a/mysite/wsgi.py b/mysite/wsgi.py new file mode 100755 index 0000000000000000000000000000000000000000..bd3a0d0c2b71afda19f90c92c54c25c94c187e0e --- /dev/null +++ b/mysite/wsgi.py @@ -0,0 +1,22 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") + +application = get_wsgi_application() diff --git a/polls.yaml b/polls.yaml new file mode 100755 index 0000000000000000000000000000000000000000..6ccc2d337b07f0de7432e7b9a9c49c864160b3b4 --- /dev/null +++ b/polls.yaml @@ -0,0 +1,114 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License + +# This file configures the polls application . The frontend serves +# public web traffic. + +# The bookshelf frontend replication controller ensures that at least 3 +# instances of the bookshelf app are running on the cluster. +# For more info about Pods see: +# https://cloud.google.com/container-engine/docs/pods/ +# For more info about Deployments: +# https://kubernetes.io/docs/user-guide/deployments/ + +# [START kubernetes_deployment] +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: polls + labels: + app: polls +spec: + replicas: 3 + template: + metadata: + labels: + app: polls + spec: + containers: + - name: polls-app + # Replace with your project ID or use `make template` + image: gcr.io/<your-project-id>/polls + # This setting makes nodes pull the docker image every time before + # starting the pod. This is useful when debugging, but should be turned + # off in production. + imagePullPolicy: Always + env: + # [START cloudsql_secrets] + - name: DATABASE_USER + valueFrom: + secretKeyRef: + name: cloudsql + key: username + - name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: cloudsql + key: password + # [END cloudsql_secrets] + ports: + - containerPort: 8080 + + # [START proxy_container] + - image: gcr.io/cloudsql-docker/gce-proxy:1.05 + name: cloudsql-proxy + command: ["/cloud_sql_proxy", "--dir=/cloudsql", + "-instances=<your-cloudsql-connection-string>=tcp:5432", + "-credential_file=/secrets/cloudsql/credentials.json"] + volumeMounts: + - name: cloudsql-oauth-credentials + mountPath: /secrets/cloudsql + readOnly: true + - name: ssl-certs + mountPath: /etc/ssl/certs + - name: cloudsql + mountPath: /cloudsql + # [END proxy_container] + # [START volumes] + volumes: + - name: cloudsql-oauth-credentials + secret: + secretName: cloudsql-oauth-credentials + - name: ssl-certs + hostPath: + path: /etc/ssl/certs + - name: cloudsql + emptyDir: + # [END volumes] +# [END kubernetes_deployment] + +--- + +# [START service] +# The polls service provides a load-balancing proxy over the polls app +# pods. By specifying the type as a 'LoadBalancer', Container Engine will +# create an external HTTP load balancer. +# For more information about Services see: +# https://cloud.google.com/container-engine/docs/services/ +# For more information about external HTTP load balancing see: +# https://cloud.google.com/container-engine/docs/load-balancer +apiVersion: v1 +kind: Service +metadata: + name: polls + labels: + app: polls +spec: + type: LoadBalancer + ports: + - port: 80 + targetPort: 8080 + selector: + app: polls +# [END service] diff --git a/polls/__init__.py b/polls/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/polls/admin.py b/polls/admin.py new file mode 100755 index 0000000000000000000000000000000000000000..d0fde6f1f6e3be742f37bd80925240b53843262f --- /dev/null +++ b/polls/admin.py @@ -0,0 +1,19 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.contrib import admin + +from .models import Question + +admin.site.register(Question) diff --git a/polls/apps.py b/polls/apps.py new file mode 100755 index 0000000000000000000000000000000000000000..6f98d05c893fc2f4a3b7954340a6580132676a70 --- /dev/null +++ b/polls/apps.py @@ -0,0 +1,19 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.apps import AppConfig + + +class PollsConfig(AppConfig): + name = 'polls' diff --git a/polls/migrations/0001_initial.py b/polls/migrations/0001_initial.py new file mode 100755 index 0000000000000000000000000000000000000000..0609ba72f430670f080db8c73c0720ebddf894df --- /dev/null +++ b/polls/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9 on 2015-12-28 23:27 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Choice', + fields=[ + ('id', models.AutoField( + auto_created=True, primary_key=True, serialize=False, + verbose_name='ID')), + ('choice_text', models.CharField(max_length=200)), + ('votes', models.IntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='Question', + fields=[ + ('id', models.AutoField( + auto_created=True, primary_key=True, serialize=False, + verbose_name='ID')), + ('question_text', models.CharField(max_length=200)), + ('pub_date', models.DateTimeField( + verbose_name=b'date published')), + ], + ), + migrations.AddField( + model_name='choice', + name='question', + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='polls.Question'), + ), + ] diff --git a/polls/migrations/__init__.py b/polls/migrations/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/polls/models.py b/polls/models.py new file mode 100755 index 0000000000000000000000000000000000000000..c38f6e55c06c75fea0322597787d52988490cd61 --- /dev/null +++ b/polls/models.py @@ -0,0 +1,28 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.db import models + + +class Question(models.Model): + question_text = models.CharField(max_length=200) + pub_date = models.DateTimeField('date published') + + +class Choice(models.Model): + question = models.ForeignKey(Question, on_delete=models.CASCADE) + choice_text = models.CharField(max_length=200) + votes = models.IntegerField(default=0) + +# Create your models here. diff --git a/polls/tests.py b/polls/tests.py new file mode 100755 index 0000000000000000000000000000000000000000..d85408e51791f792f0270736a559fc31e2bb9f90 --- /dev/null +++ b/polls/tests.py @@ -0,0 +1,13 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/polls/urls.py b/polls/urls.py new file mode 100755 index 0000000000000000000000000000000000000000..672ee5c879a669b17fc66dacfa75cc802f1f24e1 --- /dev/null +++ b/polls/urls.py @@ -0,0 +1,21 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.urls import path + +from . import views + +urlpatterns = [ + path('', views.index, name='index') +] diff --git a/polls/views.py b/polls/views.py new file mode 100755 index 0000000000000000000000000000000000000000..acc5671ba625cfd57c8d1b084a80971cb5461489 --- /dev/null +++ b/polls/views.py @@ -0,0 +1,19 @@ +# Copyright 2015 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from django.http import HttpResponse + + +def index(request): + return HttpResponse("Hello, world. You're at the polls index.") diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000000000000000000000000000000000000..c19a8e2bed0a30e6f5f1e4436c64745cf0da5c6f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Django==2.1.5 +mysqlclient==1.4.1 +wheel==0.32.3 +gunicorn==19.9.0 +psycopg2==2.7.7