├── .gitignore ├── .travis.yml ├── README.md ├── project ├── __init__.py ├── __init__.pyc ├── authentication │ ├── __init__.py │ ├── __init__.pyc │ ├── admin.py │ ├── admin.pyc │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0001_initial.pyc │ │ ├── __init__.py │ │ └── __init__.pyc │ ├── models.py │ ├── models.pyc │ ├── permissions.py │ ├── permissions.pyc │ ├── serializers.py │ ├── serializers.pyc │ ├── tests.py │ ├── views.py │ └── views.pyc ├── posts │ ├── __init__.py │ ├── __init__.pyc │ ├── admin.py │ ├── admin.pyc │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0001_initial.pyc │ │ ├── __init__.py │ │ └── __init__.pyc │ ├── models.py │ ├── models.pyc │ ├── permissions.py │ ├── permissions.pyc │ ├── serializers.py │ ├── serializers.pyc │ ├── tests.py │ ├── views.py │ └── views.pyc ├── settings.py ├── settings.pyc ├── urls.py ├── urls.pyc ├── views.py ├── views.pyc ├── wsgi.py └── wsgi.pyc ├── src ├── html │ └── index.html ├── js │ ├── authentication │ │ ├── authentication.module.js │ │ ├── controllers │ │ │ ├── login.controller.js │ │ │ └── register.controller.js │ │ └── services │ │ │ └── authentication.service.js │ ├── layout │ │ ├── controllers │ │ │ ├── index.controller.js │ │ │ └── navbar.controller.js │ │ └── layout.module.js │ ├── main.js │ ├── posts │ │ ├── controllers │ │ │ ├── new-post.controller.js │ │ │ └── posts.controller.js │ │ ├── directives │ │ │ ├── post.directive.js │ │ │ └── posts.directive.js │ │ ├── posts.module.js │ │ └── services │ │ │ └── posts.service.js │ ├── profiles │ │ ├── controllers │ │ │ ├── profile-settings.controller.js │ │ │ └── profile.controller.js │ │ ├── profiles.module.js │ │ └── services │ │ │ └── profile.service.js │ └── utils │ │ ├── services │ │ └── snackbar.service.js │ │ └── utils.module.js ├── lib │ ├── snackbar.min.css │ └── snackbar.min.js ├── sass │ ├── _base.scss │ ├── _index.scss │ ├── _mixins.scss │ ├── _reset.scss │ ├── _vars.scss │ ├── _z-index.scss │ └── style.scss └── templates │ ├── layout │ └── index.html │ ├── login.html │ ├── navbar.html │ ├── posts │ ├── new-post.html │ ├── post.html │ └── posts.html │ ├── profiles │ ├── profile.html │ └── settings.html │ └── register.html └── test ├── inventory └── test.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: trusty 3 | sudo: required 4 | group: edge 5 | 6 | services: 7 | - docker 8 | before_install: 9 | - sudo apt-add-repository 'deb http://archive.ubuntu.com/ubuntu trusty-backports universe' 10 | - sudo apt-get update -qq 11 | install: 12 | - pip install https://github.com/ansible/ansible/archive/devel.tar.gz 13 | - pip install -e git+https://github.com/ansible/ansible-container.git@develop#egg=ansible_container[docker] 14 | script: 15 | - docker version 16 | - docker-compose version 17 | - docker info 18 | - cd test 19 | - mkdir demo 20 | - cd demo 21 | - ansible-container init ansible.django-gulp-nginx 22 | - cp -R ../../src/* src 23 | - cp -R ../../project/* project 24 | - ansible-container build 25 | - ansible-container run 26 | - docker ps -a 27 | - docker images 28 | - cd .. 29 | - ansible-playbook -i inventory test.yml 30 | - cd demo 31 | - ansible-container stop -f 32 | notifications: 33 | email: false 34 | slack: 35 | rooms: 36 | secure: IvdJAMzZUQgyzUt1a2VaQDPAisAX04O9tc+lbdrHx9OI92XiLtQgPrtPjytsHULKUGKDtaQ7pqorUVJviz7biO5crzLo1mct6WuWvJgU7V0BEvHXH2Ip+KVCPDhvjrVcu7NMY9JxZ+HZSszHQgGaLttg+GzxhoBqiHA/G6Ien+MEYgU8cu8EMxsSxNhJ5CGfwnGKA2yVM9T1E/2ZaE+zAUMHSnuG86jCW3m7gbufgxaDDGs2rdWLSQHCE6BwmRdgg3/SWpX1F7dMNcGT9Elru06QHdxRI316WRrm6q0mGQgIr0+orDHM9lEbzC2R32TK5i7BMSnM1ngDTAw8utt03iw+LbtDRIbJDg9oxBPdfg4DBoTuNJe3pjCta9btGUH5WEY0hVbMGjVQudF/Rct0vxgijRt0yKFE9ej87gTVQ2TyMrygXjJVSZYuDIgEM3sqtMJvBSglwzFBrntm+RkrR/PzqW6ZMpnqlIC+ejPdScEUw6RidyJcOWOWp02AuNA/nAIeYJ0+s6AnlP3DT9btpjsui5LP8N21QQiYh3utOoPIGmNxH9QJZkYZ1qYu4P2GjkZj8Ig8y/cJ9YXyKEKVkYzY+XQrGDpxWhYmm05wfKWEg4pISTl69OwdC8FfyoQNdKL9arkOwWEzCYWkcRfdhiezQP6QGlQuuH5YNsOW54g= 37 | on_success: change 38 | on_failure: always 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://travis-ci.com/ansible/ansible-container-demo) 2 | 3 | [Ansible Container](https://github.com/ansible/ansible-container) can manage the lifecycle of an application from development through cloud deployment. To demonstrate, we'll create, test and deploy a new social media application called *Not Google Plus*. 4 | 5 | The application comes from a [tutorial](https://thinkster.io/django-angularjs-tutorial), but not to worry, this isn't a programming exercise. Instead, we'll focus on how to use Ansible Container at each phase. 6 | 7 | ## Requirements 8 | 9 | Before continuing, you'll need a couple of things: 10 | 11 | - A Linux or OSX environment 12 | - Ansible Container installed from source. See our *[Running from source guide](http://docs.ansible.com/ansible-container/installation.html#running-from-source)* for assistance. Be sure to install *docker* engine support, and *openshift* engine support. 13 | - Docker Engine or Docker for Mac. See [Docker Installation](https://docs.docker.com/engine/installation/) for assistance. 14 | - [Ansible 2.3+](http://docs.ansible.com/ansible/intro_installation.html) 15 | 16 | ## Getting Started 17 | 18 | Ansible Container uses Ansible roles to build images, initialize projects, and add services to existing projects. You can find roles contributed and maintained by the community at [Ansible Galaxy](https://galaxy.ansible.com). 19 | 20 | Keep in mind, there are three different types of roles. There are standard roles that simply execute tasks, which we can use with Ansible Container to build images. Container App roles can be used to initialize an Ansible Container project, and Container Enabled roles define a service that can be added to an existing project. 21 | 22 | To initialize our project we'll use the Container App role, *[ansible.django-gulp-nginx](https://galaxy.ansible.com/ansible/django-gulp-nginx)*. We'll start by creating an empty project directory, setting the new directory as the working directory, and then running the `init` command to copy the full contents of the role into the directory. This particular role will give us a fully functioning Django framework. 23 | 24 | Go ahead and initialize a new project called *demo* by opening a terminal session, and executing the following commands: 25 | 26 | ``` 27 | # Create an empty directory called 'demo' 28 | $ mkdir demo 29 | 30 | # Set the working directory to demo 31 | $ cd demo 32 | 33 | # Initialize the project 34 | $ ansible-container init ansible.django-gulp-nginx 35 | ``` 36 | 37 | The following video shows the project init steps: 38 | 39 | [](https://youtu.be/UTUBO7JHxWM) 40 | 41 | You now have a copy of the *ansible.django-gulp-nginx* framework project in your *demo* directory. Among the files added to the directory, you'll find a `container.yml` file describing the services that make up the application, a `roles` directory containing custom roles used to services, and all the supporting source files that make up the framework project. 42 | 43 | ### Build the Images 44 | 45 | The `container.yml` file defines four services: *django, gulp, nginx, and postgresql*. Before we can use the services, and begin application development, we need to build the images. Start the build process by running the following command: 46 | 47 | ``` 48 | # Set the working directory to demo 49 | $ cd demo 50 | 51 | # Start the image build process 52 | $ ansible-container build 53 | ``` 54 | 55 | [](https://youtu.be/J6SMRomTCxY) 56 | 57 | For each service that has a set of roles in `container.yml`, the process executes the roles to build the service. It does this by starting the Conductor container, and starting a container for the service. It then executes the Ansible roles one at time. For each role, it generates a playbook to execute the role. It runs the playbook on the Conductor container, and executes role tasks against the service container. 58 | 59 | After each role finishes, a snapshot is taken of the service container, and committed as a new image layer. Together, the complete set of layers forms the image for the service. 60 | 61 | When the build completes, run the `docker images` command to view a list of local images. The output will include the following images: 62 | 63 | ```bash 64 | # View the images 65 | $ docker images 66 | 67 | demo-django 20170613005625 32819eb0ae21 2 hours ago 428 MB 68 | demo-django latest 32819eb0ae21 2 hours ago 428 MB 69 | demo-nginx 20170613001807 de4e2b36cc13 2 hours ago 272 MB 70 | demo-nginx latest de4e2b36cc13 2 hours ago 272 MB 71 | demo-gulp 20170613001455 9dca2dd36ab0 2 hours ago 670 MB 72 | demo-gulp latest 9dca2dd36ab0 2 hours ago 670 MB 73 | demo-conductor latest 6583f1d349aa 2 hours ago 550 MB 74 | centos 7 3bee3060bfc8 7 days ago 193 MB 75 | ansible/postgresql latest d1c4b61b9fde 5 months ago 396 MB 76 | ``` 77 | 78 | ### Run the Application 79 | 80 | Now that you have the application images built in your environment, start the application by running the following: 81 | 82 | ```bash 83 | # Start the application 84 | $ ansible-container run 85 | ``` 86 | 87 | The containers are now running in the background. The *gulp* service proxies requests to the *django* service. But before it can be accessed, it first has to install *node* and *bower* packages required by the frontend, and then start the web server. 88 | 89 | Check its progress by running the following to watch the service's log output: 90 | 91 | ```bash 92 | # Tail the log for the gulp service 93 | $ docker logs -f demo_gulp_1 94 | ``` 95 | 96 | Once you see the following in the log output, the web service is running and accessible: 97 | 98 | ``` 99 | [17:05:29] Starting 'js'... 100 | [BS] Access URLs: 101 | ----------------------------------- 102 | Local: http://localhost:8080 103 | External: http://172.21.0.4:8080 104 | ----------------------------------- 105 | UI: http://localhost:3001 106 | UI External: http://172.21.0.4:3001 107 | ----------------------------------- 108 | [BS] Serving files from: dist 109 | [17:05:30] Finished 'lib' after 601 ms 110 | [17:05:30] Finished 'html' after 574 ms 111 | [17:05:30] Finished 'sass' after 644 ms 112 | [17:05:30] Finished 'templates' after 685 ms 113 | [17:05:30] Finished 'js' after 631 ms 114 | ``` 115 | 116 | To access the server, open a browser, and go to [http://localhost:8080](http://localhost:8080), where you'll see the default "Hello World!" page. 117 | 118 | ### Development vs Production 119 | 120 | The containers are currently running in *development* mode, which means that for each service the *dev_overrides* directive takes precedence. For example, take a look at the *gulp* service definition found in `container.yml`: 121 | 122 | ``` 123 | gulp: 124 | from: 'centos:7' 125 | roles: 126 | - role: gulp-static 127 | working_dir: '{{ NODE_HOME }}' 128 | command: ['/bin/false'] 129 | environment: 130 | NODE_HOME: '{{ NODE_HOME }}' 131 | dev_overrides: 132 | entrypoint: [/entrypoint.sh] 133 | command: [/usr/bin/dumb-init, /usr/local/bin/gulp] 134 | ports: 135 | - '8080:{{ GULP_DEV_PORT }}' 136 | - 3001:3001 137 | links: 138 | - django 139 | volumes: 140 | - '$PWD:{{ NODE_HOME }}' 141 | openshift: 142 | state: absent 143 | ``` 144 | 145 | In development, *dev_overrides* takes precedence, so the command `/usr/bin/dumb-init /usr/local/bin/gulp` will be executed, ports `8080` and `3001` will be exposed, and the container will be linked to the *django* service container. 146 | 147 | The application can be started in production mode, by running `ansible-container run --production`. In production the *dev_overrides* directive is completely ignored, which means the `/bin/false` command is executed, causing the container to immediately stop. No ports are exposed, and the container is not linked to the *django* service. 148 | 149 | Since the frontend tools provided by the *gulp* service are only needed during development and not during production, we use *dev_overrides* to manage when the container runs. 150 | 151 | The same is true for the *nginx* service. Take a look at the service definition in `container.yml`, and you'll notice it's configured opposite of the *gulp* service: 152 | 153 | ``` 154 | nginx: 155 | from: 'centos:7' 156 | roles: 157 | - role: ansible.nginx-container 158 | ASSET_PATHS: 159 | - /tmp/dist 160 | PROXY: yes 161 | PROXY_PASS: 'http://django:8080' 162 | PROXY_LOCATION: "~* /(admin|api)" 163 | ports: 164 | - '{{ DJANGO_PORT }}:8000' 165 | links: 166 | - django 167 | dev_overrides: 168 | ports: [] 169 | command: /bin/false 170 | ``` 171 | 172 | In development the *nginx* service runs the `/bin/false` command, and immediately exits. But in production, it starts the *nginx* process, and takes the place of the *gulp* service as the application's web server. 173 | 174 | ### Developing the Application 175 | 176 | Let's make some updates, and create the *Not Google Plus* app, and then we'll see how to test and deploy it. To make the changes, run the following commands to download the source code, and update the project: 177 | 178 | ``` 179 | # Set the working directory to your *demo* folder 180 | $ cd demo 181 | 182 | # Stop the application 183 | $ ansible-container stop 184 | 185 | # Download and expand the source archive 186 | $ curl -L https://github.com/ansible/ansible-container-demo/archive/0.2.0.tar.gz | tar -xzv 187 | 188 | # Copy the frontend files 189 | $ cp -R ansible-container-demo-0.2.0/src/* src 190 | 191 | # Copy the Django files 192 | $ cp -R ansible-container-demo-0.2.0/project/* project 193 | 194 | # Restart the application 195 | $ ansible-container run 196 | ``` 197 | 198 | Check the *gulp* service log using the `docker logs -f demo_gulp_1` command, and once the web server is running, the 199 | *Not Google Plus* application will be available. If you open your browser, and go to [http://localhost:8080](http://localhost:8080), you'll see that the "Hello World!" page has been replaced by our social media site. 200 | 201 | ### Tour the Site 202 | 203 | Let's check out *Not Google Plus*. Watch the video below, and follow along on your local site to register, log in, and create posts. Your site will be reachable at [http://localhost:8080](http://localhost:8080), and you can browse the API directly at [http://localhost:8080/api/v1/](http://localhost:8080/api/v1/). 204 | 205 | Click the image below to watch a video tour of the site: 206 | 207 | [](https://youtu.be/6ZIkuOEHitQ) 208 | 209 | ## Testing 210 | 211 | Now that you made changes to the application by adding the code for *Not Google Plus*, you'll need to build a new set of images that contain the updated source code, before moving on to testing and deployment. 212 | 213 | Run the following to stop the containers, and then start the build process. This time, use the `--no-container-cache` option on the `build` command to force the rebuild of each image, and insure that the new source code gets picked up. 214 | 215 | ``` 216 | # Stop the running containers 217 | $ ansible-container stop 218 | 219 | # Start the build process 220 | $ ansible-container build --no-container-cache 221 | ``` 222 | 223 | Once the build process completes, take a look at the local images using `docker images`: 224 | 225 | ``` 226 | # View the images once again 227 | $ docker images 228 | 229 | REPOSITORY TAG IMAGE ID CREATED SIZE 230 | demo-nginx 20170613012958 b92a8065656e About an hour ago 272 MB 231 | demo-nginx latest b92a8065656e About an hour ago 272 MB 232 | demo-gulp 20170613012504 65da0dbb9941 About an hour ago 670 MB 233 | demo-gulp latest 65da0dbb9941 About an hour ago 670 MB 234 | demo-django 20170613011928 70c3cce3a2b4 About an hour ago 428 MB 235 | demo-django latest 70c3cce3a2b4 About an hour ago 428 MB 236 | demo-gulp 20170613010219 4da2ebf7007a About an hour ago 668 MB 237 | demo-django 20170613005625 32819eb0ae21 2 hours ago 428 MB 238 | demo-nginx 20170613001807 de4e2b36cc13 2 hours ago 272 MB 239 | demo-gulp 20170613001455 9dca2dd36ab0 2 hours ago 670 MB 240 | demo-conductor latest 6583f1d349aa 2 hours ago 550 MB 241 | centos 7 3bee3060bfc8 7 days ago 193 MB 242 | ansible/postgresql latest d1c4b61b9fde 5 months ago 396 MB 243 | ``` 244 | 245 | There's a new set of images containing the updated code. Now when you deploy the application to production, you'll be deploying the *Not Google Plus* app. 246 | 247 | For testing, we want the application in *production mode*, so that it runs exactly the same as it will when deployed to the cloud. As we pointed out earlier, when run in production the *dev_overrides* settings are ignored, which means we'll see the *gulp* service stop and the *nginx* service start. 248 | 249 | To start the application in production mode, run the following command: 250 | 251 | ``` 252 | # Start the application with the production option 253 | $ ansible-container run --production 254 | ``` 255 | 256 | If we were running a CI/CD process, this is the point where we would run our automated testing scripts. In lieu of that, open a browser, and once again go to [http://localhost:8080](http://localhost:8080) to confirm the site is working as expected. 257 | 258 | Click the image below to watch a video of the application starting with the `--production` option: 259 | 260 | [](https://youtu.be/rXJW5JoWTl4) 261 | 262 | ## Deploy the application 263 | 264 | Once the application passes testing, it's time to deploy it. To demonstrate, we'll create a local instance of OpenShift, and run the `deploy` command to push images and generate a deployment playbook. 265 | 266 | ### Create a local OpenShift instance 267 | 268 | To run the deployment, you'll need access to an OpenShift instance. The [Install and Configure Openshift](http://docs.ansible.com/ansible-container/configure_openshift.html) guide at our doc site provides a how-to that will help you create a containerized instance. 269 | 270 | [Minishift](https://github.com/minishift/minishift) is a virtual machine that hosts a Docker daemon, and a containerized OpenShift cluster. The following demonstrates creating a [minishift](https://github.com/minishift/minishift) instance by running the Ansible role, [chouseknecht.minishift-up-role](https://galaxy.ansible.com/chouseknecht/minishift-up-role): 271 | 272 | [](https://youtu.be/UqGdqJf3iFc) 273 | 274 | To use the role, you'll need Ansible installed. Also, note in the video that the playbook is copied from the installed role's file structure. You'll find the playbook, *minishift-up.yml*, in the *files* directory. 275 | 276 | ### Create an OpenShift project 277 | 278 | Now that you have an OpenShift instance, run the following to make sure you're logged into the cluster as the *developer* user, and create a *demo* project: 279 | 280 | ```bash 281 | # Verify that we're logged in as the *developer* user 282 | $ oc whoami 283 | developer 284 | 285 | # Create a demo project 286 | $ oc new-project demo 287 | ``` 288 | 289 | **Note** 290 | > When you run the `deploy` command, it will attempt to authenticate to the registry using `docker login`, which will check for an existing credential in `${HOME}/.docker/config.json`. If there is an existing entry in this file for the local OpenShift cluster, you'll need to remove it, if the token has expired. Also, you'll need to remove it if the entry points to a key store. Key stores cannot be accessed from within the Conductor container, where the authentication with the registry will actually take place. 291 | 292 | The project name is defined in `container.yml`. Within the *settings* section, you will find a *k8s_namespace* directive that sets the name. The project name is arbitrary. However, before running the `deploy` command, the project must already exist, and the user you're logged in as must have access to it. 293 | 294 | The reason for creating the project first is because the `deploy` process will attempt to push images to the local registry using the project name as the namespace. If the project does not already exist, then the push will fail. 295 | 296 | ### Run the deployment 297 | 298 | Next, use the `deploy` command to push the project images to the local registry, and create the deployment playbook. For demonstration purposes, we're referencing the *local_openshift* registry defined in `container.yml`. Depending on how you created the local OpenShift cluster, you may need to adjust the registry attributes. 299 | 300 | One of the registry attributes is *namespace*. For OpenShift and K8s, the registry *namespace* should match the *name* value set in *k8s_namespace* within the *settings* section. In the case of OpenShift, the *name* in *k8s_namespace* will be the *project* name, and for K8s, it's the *Namespace*. 301 | 302 | Once you're ready to push the images, run the following from the root of the *demo* project directory: 303 | 304 | ```bash 305 | # Push the built images and generate the deployment playbook 306 | $ ansible-container --engine openshift deploy --push-to local_openshift --username developer --password $(oc whoami -t) 307 | ``` 308 | 309 | The above will authenticate to the registry using the `developer` username, and a token generated by the `oc whoami -t` command. This presumes that your cluster has a `developer` account, and that you previously authenticated to the cluster with this account. 310 | 311 | After pushing the images, a playbook is generated and written to the `ansible-deployment` directory. The name of the playbook will match the project name, and have a `.yml` extension. In this case, the name of the playbook will be `demo.yml`. 312 | 313 | You will also find a `roles` directory containing the `ansible.kubernetes-modules` role. The deployment playbook relies on this role for access to the Ansible Kubernetes modules. 314 | 315 | To deploy the application, execute the playbook, making sure to include the appropriate tag. Possible tags include: `start`, `stop`, `restart`, and `destroy`. To start the application, run the following: 316 | 317 | ```bash 318 | # Run the deployment playbook 319 | $ ansible-playbook ./ansible-deployment/demo.yml --tags start 320 | ``` 321 | Once the playbook completes, log into the OpenShift console to check the status of the deployment. From the *Applications* menu, choose *Routes*, and find the URL that points to the *nginx* service. Using this URL, you can access the appication running on the cluster. 322 | 323 | Watch the following video to see the full deployment: 324 | 325 | [](https://youtu.be/40qbISem8Tc) 326 | -------------------------------------------------------------------------------- /project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/__init__.py -------------------------------------------------------------------------------- /project/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/__init__.pyc -------------------------------------------------------------------------------- /project/authentication/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/__init__.py -------------------------------------------------------------------------------- /project/authentication/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/__init__.pyc -------------------------------------------------------------------------------- /project/authentication/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /project/authentication/admin.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/admin.pyc -------------------------------------------------------------------------------- /project/authentication/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-01-23 02:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Account', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('password', models.CharField(max_length=128, verbose_name='password')), 19 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 20 | ('email', models.EmailField(max_length=254, unique=True)), 21 | ('username', models.CharField(max_length=40, unique=True)), 22 | ('first_name', models.CharField(blank=True, max_length=40)), 23 | ('last_name', models.CharField(blank=True, max_length=40)), 24 | ('tagline', models.CharField(blank=True, max_length=140)), 25 | ('is_admin', models.BooleanField(default=False)), 26 | ('created_at', models.DateTimeField(auto_now_add=True)), 27 | ('updated_at', models.DateTimeField(auto_now=True)), 28 | ], 29 | options={ 30 | 'abstract': False, 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /project/authentication/migrations/0001_initial.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/migrations/0001_initial.pyc -------------------------------------------------------------------------------- /project/authentication/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/migrations/__init__.py -------------------------------------------------------------------------------- /project/authentication/migrations/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/migrations/__init__.pyc -------------------------------------------------------------------------------- /project/authentication/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractBaseUser, BaseUserManager 2 | from django.db import models 3 | 4 | 5 | class AccountManager(BaseUserManager): 6 | def create_user(self, email, password=None, **kwargs): 7 | if not email: 8 | raise ValueError('Users must have a valid email address.') 9 | 10 | if not kwargs.get('username'): 11 | raise ValueError('Users must have a valid username.') 12 | 13 | account = self.model( 14 | email=self.normalize_email(email), username=kwargs.get('username') 15 | ) 16 | 17 | account.set_password(password) 18 | account.save() 19 | 20 | return account 21 | 22 | def create_superuser(self, email, password, **kwargs): 23 | account = self.create_user(email, password, **kwargs) 24 | 25 | account.is_admin = True 26 | account.save() 27 | 28 | return account 29 | 30 | 31 | class Account(AbstractBaseUser): 32 | email = models.EmailField(unique=True) 33 | username = models.CharField(max_length=40, unique=True) 34 | 35 | first_name = models.CharField(max_length=40, blank=True) 36 | last_name = models.CharField(max_length=40, blank=True) 37 | tagline = models.CharField(max_length=140, blank=True) 38 | 39 | is_admin = models.BooleanField(default=False) 40 | 41 | created_at = models.DateTimeField(auto_now_add=True) 42 | updated_at = models.DateTimeField(auto_now=True) 43 | 44 | objects = AccountManager() 45 | 46 | USERNAME_FIELD = 'email' 47 | REQUIRED_FIELDS = ['username'] 48 | 49 | def __unicode__(self): 50 | return self.email 51 | 52 | def get_full_name(self): 53 | return ' '.join([self.first_name, self.last_name]) 54 | 55 | def get_short_name(self): 56 | return self.first_name 57 | 58 | @property 59 | def is_superuser(self): 60 | return self.is_admin 61 | 62 | @property 63 | def is_staff(self): 64 | return self.is_admin 65 | 66 | def get_full_name(self): 67 | pass 68 | 69 | def get_short_name(self): 70 | pass 71 | 72 | def has_perm(self, perm, obj=None): 73 | return self.is_admin 74 | 75 | def has_module_perms(self, app_label): 76 | return self.is_admin 77 | 78 | @is_staff.setter 79 | def is_staff(self, value): 80 | self._is_staff = value 81 | -------------------------------------------------------------------------------- /project/authentication/models.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/models.pyc -------------------------------------------------------------------------------- /project/authentication/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class IsAccountOwner(permissions.BasePermission): 5 | def has_object_permission(self, request, view, account): 6 | if request.user: 7 | return account == request.user 8 | return False 9 | -------------------------------------------------------------------------------- /project/authentication/permissions.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/permissions.pyc -------------------------------------------------------------------------------- /project/authentication/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import update_session_auth_hash 2 | from rest_framework import serializers 3 | from project.authentication.models import Account 4 | 5 | 6 | class AccountSerializer(serializers.ModelSerializer): 7 | password = serializers.CharField(write_only=True, required=False) 8 | confirm_password = serializers.CharField(write_only=True, required=False) 9 | 10 | class Meta: 11 | model = Account 12 | fields = ('id', 'email', 'username', 'created_at', 'updated_at', 13 | 'first_name', 'last_name', 'tagline', 'password', 14 | 'confirm_password',) 15 | read_only_fields = ('created_at', 'updated_at',) 16 | 17 | def create(self, validated_data): 18 | return Account.objects.create(**validated_data) 19 | 20 | def update(self, instance, validated_data): 21 | instance.username = validated_data.get('username', instance.username) 22 | instance.tagline = validated_data.get('tagline', instance.tagline) 23 | 24 | instance.save() 25 | 26 | password = validated_data.get('password', None) 27 | confirm_password = validated_data.get('confirm_password', None) 28 | 29 | if password and confirm_password and password == confirm_password: 30 | instance.set_password(password) 31 | instance.save() 32 | 33 | update_session_auth_hash(self.context.get('request'), instance) 34 | 35 | return instance 36 | -------------------------------------------------------------------------------- /project/authentication/serializers.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/serializers.pyc -------------------------------------------------------------------------------- /project/authentication/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /project/authentication/views.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | 4 | from rest_framework import permissions, viewsets, status 5 | from rest_framework.response import Response 6 | from rest_framework import status, views 7 | 8 | from django.contrib.auth import authenticate, login, logout 9 | 10 | from project.authentication.models import Account 11 | from project.authentication.serializers import AccountSerializer 12 | from project.authentication.permissions import IsAccountOwner 13 | 14 | 15 | class AccountViewSet(viewsets.ModelViewSet): 16 | lookup_field = 'username' 17 | queryset = Account.objects.all() 18 | serializer_class = AccountSerializer 19 | 20 | def get_permissions(self): 21 | if self.request.method in permissions.SAFE_METHODS: 22 | return (permissions.AllowAny(),) 23 | 24 | if self.request.method == 'POST': 25 | return (permissions.AllowAny(),) 26 | 27 | return (permissions.IsAuthenticated(), IsAccountOwner(),) 28 | 29 | def create(self, request): 30 | serializer = self.serializer_class(data=request.data) 31 | 32 | if serializer.is_valid(): 33 | Account.objects.create_user(**serializer.validated_data) 34 | 35 | return Response(serializer.validated_data, status=status.HTTP_201_CREATED) 36 | 37 | return Response({ 38 | 'status': 'Bad request', 39 | 'message': 'Account could not be created with received data.' 40 | }, status=status.HTTP_400_BAD_REQUEST) 41 | 42 | 43 | class LoginView(views.APIView): 44 | 45 | permission_classes = (permissions.AllowAny,) 46 | 47 | def post(self, request, format=None): 48 | data = json.loads(request.body) 49 | 50 | email = data.get('email', None) 51 | password = data.get('password', None) 52 | 53 | account = authenticate(email=email, password=password) 54 | 55 | if account is not None: 56 | if account.is_active: 57 | login(request, account) 58 | 59 | serialized = AccountSerializer(account) 60 | 61 | return Response(serialized.data) 62 | else: 63 | return Response({ 64 | 'status': 'Unauthorized', 65 | 'message': 'This account has been disabled.' 66 | }, status=status.HTTP_401_UNAUTHORIZED) 67 | else: 68 | return Response({ 69 | 'status': 'Unauthorized', 70 | 'message': 'Username/password combination invalid.' 71 | }, status=status.HTTP_401_UNAUTHORIZED) 72 | 73 | 74 | class LogoutView(views.APIView): 75 | permission_classes = (permissions.IsAuthenticated,) 76 | 77 | def post(self, request, format=None): 78 | logout(request) 79 | 80 | return Response({}, status=status.HTTP_204_NO_CONTENT) -------------------------------------------------------------------------------- /project/authentication/views.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/authentication/views.pyc -------------------------------------------------------------------------------- /project/posts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/__init__.py -------------------------------------------------------------------------------- /project/posts/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/__init__.pyc -------------------------------------------------------------------------------- /project/posts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /project/posts/admin.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/admin.pyc -------------------------------------------------------------------------------- /project/posts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-01-23 01:59 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Post', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('content', models.TextField()), 22 | ('created_at', models.DateTimeField(auto_now_add=True)), 23 | ('updated_at', models.DateTimeField(auto_now=True)), 24 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /project/posts/migrations/0001_initial.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/migrations/0001_initial.pyc -------------------------------------------------------------------------------- /project/posts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/migrations/__init__.py -------------------------------------------------------------------------------- /project/posts/migrations/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/migrations/__init__.pyc -------------------------------------------------------------------------------- /project/posts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from project.authentication.models import Account 3 | 4 | class Post(models.Model): 5 | author = models.ForeignKey(Account, on_delete=models.CASCADE) 6 | content = models.TextField() 7 | 8 | created_at = models.DateTimeField(auto_now_add=True) 9 | updated_at = models.DateTimeField(auto_now=True) 10 | 11 | def __unicode__(self): 12 | return '{0}'.format(self.content) 13 | -------------------------------------------------------------------------------- /project/posts/models.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/models.pyc -------------------------------------------------------------------------------- /project/posts/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class IsAuthorOfPost(permissions.BasePermission): 5 | def has_object_permission(self, request, view, post): 6 | if request.user: 7 | return post.author == request.user 8 | return False -------------------------------------------------------------------------------- /project/posts/permissions.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/permissions.pyc -------------------------------------------------------------------------------- /project/posts/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from project.authentication.serializers import AccountSerializer 4 | from project.posts.models import Post 5 | 6 | 7 | class PostSerializer(serializers.ModelSerializer): 8 | author = AccountSerializer(read_only=True, required=False) 9 | 10 | class Meta: 11 | model = Post 12 | 13 | fields = ('id', 'author', 'content', 'created_at', 'updated_at') 14 | read_only_fields = ('id', 'created_at', 'updated_at') 15 | 16 | def get_validation_exclusions(self, *args, **kwargs): 17 | exclusions = super(PostSerializer, self).get_validation_exclusions() 18 | 19 | return exclusions + ['author'] -------------------------------------------------------------------------------- /project/posts/serializers.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/serializers.pyc -------------------------------------------------------------------------------- /project/posts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /project/posts/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions, viewsets 2 | from rest_framework.response import Response 3 | 4 | from project.posts.models import Post 5 | from project.posts.permissions import IsAuthorOfPost 6 | from project.posts.serializers import PostSerializer 7 | 8 | 9 | class PostViewSet(viewsets.ModelViewSet): 10 | queryset = Post.objects.order_by('-created_at') 11 | serializer_class = PostSerializer 12 | 13 | def get_permissions(self): 14 | if self.request.method in permissions.SAFE_METHODS: 15 | return (permissions.AllowAny(),) 16 | return (permissions.IsAuthenticated(), IsAuthorOfPost(),) 17 | 18 | def perform_create(self, serializer): 19 | instance = serializer.save(author=self.request.user) 20 | return super(PostViewSet, self).perform_create(serializer) 21 | 22 | 23 | 24 | class AccountPostsViewSet(viewsets.ViewSet): 25 | queryset = Post.objects.select_related('author').all() 26 | serializer_class = PostSerializer 27 | 28 | def list(self, request, account_username=None): 29 | queryset = self.queryset.filter(author__username=account_username) 30 | serializer = self.serializer_class(queryset, many=True) 31 | 32 | return Response(serializer.data) -------------------------------------------------------------------------------- /project/posts/views.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/posts/views.pyc -------------------------------------------------------------------------------- /project/settings.py: -------------------------------------------------------------------------------- 1 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 2 | import os 3 | import warnings 4 | 5 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 6 | 7 | 8 | # Quick-start development settings - unsuitable for production 9 | # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ 10 | 11 | # SECURITY WARNING: keep the secret key used in production secret! 12 | SECRET_KEY = 'wj9rt420!n0(np*gi6#rpb7)y*b2qzp#@3l8p66na#q(4svs!e' 13 | 14 | # SECURITY WARNING: don't run with debug turned on in production! 15 | DEBUG = True 16 | 17 | ALLOWED_HOSTS = ['*'] 18 | 19 | AUTH_USER_MODEL = 'authentication.Account' 20 | 21 | USE_X_FORWARDED_HOST = True 22 | 23 | APPEND_SLASH = True 24 | 25 | # Application definition 26 | 27 | INSTALLED_APPS = ( 28 | 'django.contrib.admin', 29 | 'django.contrib.auth', 30 | 'django.contrib.contenttypes', 31 | 'django.contrib.sessions', 32 | 'django.contrib.messages', 33 | 'django.contrib.staticfiles', 34 | 'rest_framework', 35 | 'gunicorn', 36 | 'compressor', 37 | 'project.authentication', 38 | 'project.posts', 39 | ) 40 | 41 | MIDDLEWARE = [ 42 | 'django.middleware.security.SecurityMiddleware', 43 | 'django.contrib.sessions.middleware.SessionMiddleware', 44 | 'django.middleware.common.CommonMiddleware', 45 | 'django.middleware.csrf.CsrfViewMiddleware', 46 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 47 | 'django.contrib.messages.middleware.MessageMiddleware', 48 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 49 | ] 50 | 51 | ROOT_URLCONF = 'project.urls' 52 | 53 | TEMPLATES = [ 54 | { 55 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 56 | 'DIRS': [os.path.join(BASE_DIR, 'dist', 'templates')], 57 | 'APP_DIRS': True, 58 | 'OPTIONS': { 59 | 'context_processors': [ 60 | 'django.template.context_processors.debug', 61 | 'django.template.context_processors.request', 62 | 'django.contrib.auth.context_processors.auth', 63 | 'django.contrib.messages.context_processors.messages', 64 | ], 65 | }, 66 | }, 67 | ] 68 | 69 | WSGI_APPLICATION = 'project.wsgi.application' 70 | 71 | 72 | # Database 73 | # https://docs.djangoproject.com/en/1.8/ref/settings/#databases 74 | 75 | import dj_database_url 76 | DATABASES = { 77 | 'default': dj_database_url.config() 78 | } 79 | 80 | 81 | # Internationalization 82 | # https://docs.djangoproject.com/en/1.8/topics/i18n/ 83 | 84 | LANGUAGE_CODE = 'en-us' 85 | 86 | TIME_ZONE = 'UTC' 87 | 88 | USE_I18N = True 89 | 90 | USE_L10N = True 91 | 92 | USE_TZ = True 93 | 94 | 95 | # Static files (CSS, JavaScript, Images) 96 | # https://docs.djangoproject.com/en/1.8/howto/static-files/ 97 | 98 | STATIC_URL = '/static/' 99 | STATIC_ROOT = '/django/dist/' 100 | 101 | 102 | REST_FRAMEWORK = { 103 | # Use Django's standard `django.contrib.auth` permissions, 104 | # or allow read-only access for unauthenticated users. 105 | # 'DEFAULT_PERMISSION_CLASSES': [ 106 | # 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' 107 | # ], 108 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 109 | 'rest_framework.authentication.SessionAuthentication', 110 | ) 111 | } 112 | 113 | -------------------------------------------------------------------------------- /project/settings.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/settings.pyc -------------------------------------------------------------------------------- /project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.conf import settings 5 | from django.conf.urls import url 6 | from rest_framework_nested import routers 7 | from project.views import ApiV1RootView, IndexView 8 | from project.authentication.views import AccountViewSet, LoginView, LogoutView 9 | from project.posts.views import AccountPostsViewSet, PostViewSet 10 | 11 | 12 | router = routers.SimpleRouter() 13 | router.register(r'accounts', AccountViewSet) 14 | router.register(r'posts', PostViewSet) 15 | 16 | accounts_router = routers.NestedSimpleRouter( 17 | router, r'accounts', lookup='account' 18 | ) 19 | accounts_router.register(r'posts', AccountPostsViewSet) 20 | 21 | urlpatterns = [ 22 | url(r'^api/v1/login/$', LoginView.as_view(), name='login'), 23 | url(r'^api/v1/logout/$', LogoutView.as_view(), name='logout'), 24 | url(r'^api/v1/$', ApiV1RootView.as_view()), 25 | url(r'^api/v1/', include(router.urls)), 26 | url(r'^api/v1/', include(accounts_router.urls)), 27 | url(r'^admin/', admin.site.urls), 28 | url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), 29 | ] 30 | 31 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 32 | 33 | -------------------------------------------------------------------------------- /project/urls.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/urls.pyc -------------------------------------------------------------------------------- /project/views.py: -------------------------------------------------------------------------------- 1 | from django.views.decorators.csrf import ensure_csrf_cookie 2 | from django.views.generic.base import TemplateView 3 | from django.utils.decorators import method_decorator 4 | 5 | from rest_framework import permissions, viewsets 6 | from rest_framework.views import APIView 7 | from rest_framework.reverse import reverse 8 | from rest_framework.response import Response 9 | 10 | class ApiV1RootView(APIView): 11 | permission_classes = (permissions.AllowAny,) 12 | view_name = 'Version 1' 13 | 14 | def get(self, request, format=None): 15 | # list top level resources 16 | data = dict() 17 | data['accounts'] = reverse('account-list', request=request) 18 | data['posts'] = reverse('post-list', request=request) 19 | return Response(data) 20 | 21 | 22 | class IndexView(TemplateView): 23 | template_name = 'index.html' 24 | 25 | @method_decorator(ensure_csrf_cookie) 26 | def dispatch(self, *args, **kwargs): 27 | return super(IndexView, self).dispatch(*args, **kwargs) 28 | -------------------------------------------------------------------------------- /project/views.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/views.pyc -------------------------------------------------------------------------------- /project/wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import socket 4 | import time 5 | 6 | postgres_is_alive = False 7 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 8 | 9 | while not postgres_is_alive: 10 | try: 11 | s.connect(('postgresql', 5432)) 12 | except socket.error: 13 | time.sleep(1) 14 | else: 15 | postgres_is_alive = True 16 | 17 | 18 | from django.core.wsgi import get_wsgi_application 19 | 20 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 21 | 22 | application = get_wsgi_application() 23 | -------------------------------------------------------------------------------- /project/wsgi.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansible/ansible-container-demo/9440ec2a4a39888e84d09fd2e5f591cb3031cde7/project/wsgi.pyc -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |{{ vm.profile.tagline }}
5 |