Don't symlink roles
authorLorin Hochstein <lhochstein@netflix.com>
Sun, 15 Jan 2017 22:46:09 +0000 (14:46 -0800)
committerLorin Hochstein <lhochstein@netflix.com>
Sun, 15 Jan 2017 22:46:09 +0000 (14:46 -0800)
We can't symlink roles because they need to be mounted into a container.

13 files changed:
ch13/ansible/roles/database [deleted symlink]
ch13/ansible/roles/mezzanine [deleted symlink]
ch13/ansible/roles/mezzanine/defaults/main.yml [new file with mode: 0644]
ch13/ansible/roles/mezzanine/handlers/main.yml [new file with mode: 0644]
ch13/ansible/roles/mezzanine/tasks/django.yml [new file with mode: 0644]
ch13/ansible/roles/mezzanine/tasks/main.yml [new file with mode: 0644]
ch13/ansible/roles/mezzanine/tasks/nginx.yml [new file with mode: 0644]
ch13/ansible/roles/mezzanine/templates/gunicorn.conf.py.j2 [new file with mode: 0644]
ch13/ansible/roles/mezzanine/templates/local_settings.py.filters.j2 [new file with mode: 0644]
ch13/ansible/roles/mezzanine/templates/local_settings.py.j2 [new file with mode: 0644]
ch13/ansible/roles/mezzanine/templates/nginx.conf.j2 [new file with mode: 0644]
ch13/ansible/roles/mezzanine/templates/supervisor.conf.j2 [new file with mode: 0644]
ch13/ansible/roles/mezzanine/vars/main.yml [new file with mode: 0644]

diff --git a/ch13/ansible/roles/database b/ch13/ansible/roles/database
deleted file mode 120000 (symlink)
index bfd2c38..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../../../ch08/playbooks/roles/database
\ No newline at end of file
diff --git a/ch13/ansible/roles/mezzanine b/ch13/ansible/roles/mezzanine
deleted file mode 120000 (symlink)
index 0fb1595..0000000
+++ /dev/null
@@ -1 +0,0 @@
-../../../ch08/playbooks/roles/mezzanine
\ No newline at end of file
diff --git a/ch13/ansible/roles/mezzanine/defaults/main.yml b/ch13/ansible/roles/mezzanine/defaults/main.yml
new file mode 100644 (file)
index 0000000..1de786e
--- /dev/null
@@ -0,0 +1,2 @@
+---
+tls_enabled: True
diff --git a/ch13/ansible/roles/mezzanine/handlers/main.yml b/ch13/ansible/roles/mezzanine/handlers/main.yml
new file mode 100644 (file)
index 0000000..0913020
--- /dev/null
@@ -0,0 +1,7 @@
+---
+- name: restart supervisor
+  supervisorctl: name=gunicorn_mezzanine state=restarted
+  become: True
+- name: restart nginx
+  service: name=nginx state=restarted
+  become: True
diff --git a/ch13/ansible/roles/mezzanine/tasks/django.yml b/ch13/ansible/roles/mezzanine/tasks/django.yml
new file mode 100644 (file)
index 0000000..8a0c685
--- /dev/null
@@ -0,0 +1,61 @@
+- name: create a logs directory
+  file: path="{{ ansible_env.HOME }}/logs" state=directory
+- name: check out the repository on the host
+  git: repo={{ mezzanine_repo_url }} dest={{ mezzanine_proj_path }} accept_hostkey=yes
+- name: install Python requirements globally via pip
+  pip: name={{ item }} state=latest
+  with_items:
+    - pip
+    - virtualenv
+    - virtualenvwrapper
+  become: True
+- name: install required python packages
+  pip: name={{ item }} virtualenv={{ mezzanine_venv_path }}
+  with_items:
+    - gunicorn
+    - setproctitle
+    - psycopg2
+    - django-compressor
+    - python-memcached
+- name: install requirements.txt
+  pip: >
+    requirements={{ mezzanine_proj_path }}/{{ mezzanine_reqs_path }}
+    virtualenv={{ mezzanine_venv_path }}
+- name: generate the settings file
+  template: src=local_settings.py.j2 dest={{ mezzanine_settings_path }}/local_settings.py
+- name: apply migrations to create the database, collect static content
+  django_manage:
+    command: "{{ item }}"
+    app_path: "{{ mezzanine_proj_path }}"
+    virtualenv: "{{ mezzanine_venv_path }}"
+  with_items:
+    - migrate
+    - collectstatic
+- name: set the site id
+  script: scripts/setsite.py
+  environment:
+    PATH: "{{ mezzanine_venv_path }}/bin"
+    PROJECT_DIR: "{{ mezzanine_proj_path }}"
+    PROJECT_APP: "{{ mezzanine_proj_app }}"
+    WEBSITE_DOMAIN: "{{ live_hostname }}"
+- name: set the admin password
+  script: scripts/setadmin.py
+  environment:
+    PATH: "{{ mezzanine_venv_path }}/bin"
+    PROJECT_DIR: "{{ mezzanine_proj_path }}"
+    PROJECT_APP: "{{ mezzanine_proj_app }}"
+    ADMIN_PASSWORD: "{{ admin_pass }}"
+- name: set the gunicorn config file
+  template: src=gunicorn.conf.py.j2 dest={{ mezzanine_proj_path }}/gunicorn.conf.py
+- name: set the supervisor config file
+  template: src=supervisor.conf.j2 dest=/etc/supervisor/conf.d/mezzanine.conf
+  become: True
+  notify: restart supervisor
+- name: ensure config path exists
+  file: path={{ mezzanine_conf_path }} state=directory
+  become: True
+  when: tls_enabled
+- name: install poll twitter cron job
+  cron: >
+    name="poll twitter" minute="*/5" user={{ mezzanine_user }}
+    job="{{ mezzanine_manage }} poll_twitter"
diff --git a/ch13/ansible/roles/mezzanine/tasks/main.yml b/ch13/ansible/roles/mezzanine/tasks/main.yml
new file mode 100644 (file)
index 0000000..975a15e
--- /dev/null
@@ -0,0 +1,18 @@
+---
+- name: install apt packages
+  apt: pkg={{ item }} update_cache=yes cache_valid_time=3600
+  become: True
+  with_items:
+    - git
+    - libjpeg-dev
+    - libpq-dev
+    - memcached
+    - nginx
+    - python-dev
+    - python-pip
+    - python-psycopg2
+    - python-setuptools
+    - python-virtualenv
+    - supervisor
+- include: django.yml
+- include: nginx.yml
diff --git a/ch13/ansible/roles/mezzanine/tasks/nginx.yml b/ch13/ansible/roles/mezzanine/tasks/nginx.yml
new file mode 100644 (file)
index 0000000..1852181
--- /dev/null
@@ -0,0 +1,24 @@
+- name: set the nginx config file
+  template: src=nginx.conf.j2 dest=/etc/nginx/sites-available/mezzanine.conf
+  notify: restart nginx
+  become: True
+- name: enable the nginx config file
+  file:
+    src: /etc/nginx/sites-available/mezzanine.conf
+    dest: /etc/nginx/sites-enabled/mezzanine.conf
+    state: link
+  notify: restart nginx
+  become: True
+- name: remove the default nginx config file
+  file: path=/etc/nginx/sites-enabled/default state=absent
+  notify: restart nginx
+  become: True
+- name: create ssl certificates
+  command: >
+    openssl req -new -x509 -nodes -out {{ mezzanine_proj_name }}.crt
+    -keyout {{ mezzanine_proj_name }}.key -subj '/CN={{ domains[0] }}' -days 3650
+    chdir={{ mezzanine_conf_path }}
+    creates={{ mezzanine_conf_path }}/{{ mezzanine_proj_name }}.crt
+  become: True
+  when: tls_enabled
+  notify: restart nginx
diff --git a/ch13/ansible/roles/mezzanine/templates/gunicorn.conf.py.j2 b/ch13/ansible/roles/mezzanine/templates/gunicorn.conf.py.j2
new file mode 100644 (file)
index 0000000..c12307c
--- /dev/null
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import multiprocessing
+
+bind = "unix:{{ mezzanine_proj_path }}/gunicorn.sock"
+workers = {{ mezzanine_num_workers }}
+errorlog = "/home/{{ mezzanine_user }}/logs/{{ mezzanine_proj_name }}_error.log"
+loglevel = "error"
+proc_name = "{{ mezzanine_proj_name }}"
diff --git a/ch13/ansible/roles/mezzanine/templates/local_settings.py.filters.j2 b/ch13/ansible/roles/mezzanine/templates/local_settings.py.filters.j2
new file mode 100644 (file)
index 0000000..69a0697
--- /dev/null
@@ -0,0 +1,43 @@
+from __future__ import unicode_literals
+
+SECRET_KEY = "{{ secret_key }}"
+NEVERCACHE_KEY = "{{ nevercache_key }}"
+ALLOWED_HOSTS = [{{ domains|surround_by_quote|join(", ") }}]
+
+DATABASES = {
+    "default": {
+        # Ends with "postgresql_psycopg2", "mysql", "sqlite3" or "oracle".
+        "ENGINE": "django.db.backends.postgresql_psycopg2",
+        # DB name or path to database file if using sqlite3.
+        "NAME": "{{ proj_name }}",
+        # Not used with sqlite3.
+        "USER": "{{ proj_name }}",
+        # Not used with sqlite3.
+        "PASSWORD": "{{ db_pass }}",
+        # Set to empty string for localhost. Not used with sqlite3.
+        "HOST": "127.0.0.1",
+        # Set to empty string for default. Not used with sqlite3.
+        "PORT": "",
+    }
+}
+
+SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTOCOL", "https")
+
+CACHE_MIDDLEWARE_SECONDS = 60
+
+CACHE_MIDDLEWARE_KEY_PREFIX = "{{ proj_name }}"
+
+CACHES = {
+    "default": {
+        "BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
+        "LOCATION": "127.0.0.1:11211",
+    }
+}
+
+SESSION_ENGINE = "django.contrib.sessions.backends.cache"
+
+TWITTER_ACCESS_TOKEN_KEY = "{{ twitter_access_token_key }}"
+TWITTER_ACCESS_TOKEN_SECRET = "{{ twitter_access_token_secret }}"
+TWITTER_CONSUMER_KEY = "{{ twitter_consumer_key }}"
+TWITTER_CONSUMER_SECRET = "{{ twitter_consumer_secret }}"
+TWITTER_DEFAULT_QUERY = "from:ansiblebook"
diff --git a/ch13/ansible/roles/mezzanine/templates/local_settings.py.j2 b/ch13/ansible/roles/mezzanine/templates/local_settings.py.j2
new file mode 100644 (file)
index 0000000..47cb458
--- /dev/null
@@ -0,0 +1,43 @@
+from __future__ import unicode_literals
+
+SECRET_KEY = "{{ secret_key }}"
+NEVERCACHE_KEY = "{{ nevercache_key }}"
+ALLOWED_HOSTS = [{% for domain in domains %}"{{ domain }}",{% endfor %}]
+
+DATABASES = {
+    "default": {
+        # Ends with "postgresql_psycopg2", "mysql", "sqlite3" or "oracle".
+        "ENGINE": "django.db.backends.postgresql_psycopg2",
+        # DB name or path to database file if using sqlite3.
+        "NAME": "{{ mezzanine_proj_name }}",
+        # Not used with sqlite3.
+        "USER": "{{ mezzanine_proj_name }}",
+        # Not used with sqlite3.
+        "PASSWORD": "{{ db_pass }}",
+        # Set to empty string for localhost. Not used with sqlite3.
+        "HOST": "{{ database_host | default("localhost") }}",
+        # Set to empty string for default. Not used with sqlite3.
+        "PORT": "{{ database_port }}",
+    }
+}
+
+SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTOCOL", "https")
+
+CACHE_MIDDLEWARE_SECONDS = 60
+
+CACHE_MIDDLEWARE_KEY_PREFIX = "{{ mezzanine_proj_name }}"
+
+CACHES = {
+    "default": {
+        "BACKEND": "django.core.cache.backends.memcached.MemcachedCache",
+        "LOCATION": "127.0.0.1:11211",
+    }
+}
+
+SESSION_ENGINE = "django.contrib.sessions.backends.cache"
+
+TWITTER_ACCESS_TOKEN_KEY = "{{ twitter_access_token_key }}"
+TWITTER_ACCESS_TOKEN_SECRET = "{{ twitter_access_token_secret }}"
+TWITTER_CONSUMER_KEY = "{{ twitter_consumer_key }}"
+TWITTER_CONSUMER_SECRET = "{{ twitter_consumer_secret }}"
+TWITTER_DEFAULT_QUERY = "from:ansiblebook"
diff --git a/ch13/ansible/roles/mezzanine/templates/nginx.conf.j2 b/ch13/ansible/roles/mezzanine/templates/nginx.conf.j2
new file mode 100644 (file)
index 0000000..ed85645
--- /dev/null
@@ -0,0 +1,51 @@
+upstream {{ mezzanine_proj_name }} {
+    server unix:{{ mezzanine_proj_path }}/gunicorn.sock fail_timeout=0;
+}
+
+server {
+
+    listen 80;
+
+    {% if tls_enabled %}
+    listen 443 ssl;
+    {% endif %}
+    server_name {{ domains|join(", ") }};
+    client_max_body_size 10M;
+    keepalive_timeout    15;
+
+    {% if tls_enabled %}
+    ssl_certificate      conf/{{ mezzanine_proj_name }}.crt;
+    ssl_certificate_key  conf/{{ mezzanine_proj_name }}.key;
+    ssl_session_cache    shared:SSL:10m;
+    ssl_session_timeout  10m;
+    ssl_ciphers CDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;
+    ssl_prefer_server_ciphers on;
+    {% endif %}
+
+    location / {
+        proxy_redirect      off;
+        proxy_set_header    Host                    $host;
+        proxy_set_header    X-Real-IP               $remote_addr;
+        proxy_set_header    X-Forwarded-For         $proxy_add_x_forwarded_for;
+        proxy_set_header    X-Forwarded-Protocol    $scheme;
+        proxy_pass          http://{{ mezzanine_proj_name }};
+    }
+
+    location /static/ {
+        root            {{ mezzanine_proj_path }};
+        access_log      off;
+        log_not_found   off;
+    }
+
+    location /robots.txt {
+        root            {{ mezzanine_proj_path }}/static;
+        access_log      off;
+        log_not_found   off;
+    }
+
+    location /favicon.ico {
+        root            {{ mezzanine_proj_path }}/static/img;
+        access_log      off;
+        log_not_found   off;
+    }
+}
diff --git a/ch13/ansible/roles/mezzanine/templates/supervisor.conf.j2 b/ch13/ansible/roles/mezzanine/templates/supervisor.conf.j2
new file mode 100644 (file)
index 0000000..a5f766f
--- /dev/null
@@ -0,0 +1,9 @@
+[program:{{ mezzanine_gunicorn_procname }}]
+command={{ mezzanine_venv_path }}/bin/gunicorn -c gunicorn.conf.py -p gunicorn.pid {{ mezzanine_proj_app }}.wsgi:application
+directory={{ mezzanine_proj_path }}
+user={{ mezzanine_user }}
+autostart=true
+stdout_logfile=/home/{{ mezzanine_user }}/logs/{{ mezzanine_proj_name }}_supervisor
+autorestart=true
+redirect_stderr=true
+environment=LANG="{{ locale }}",LC_ALL="{{ locale }}",LC_LANG="{{ locale }}"
diff --git a/ch13/ansible/roles/mezzanine/vars/main.yml b/ch13/ansible/roles/mezzanine/vars/main.yml
new file mode 100644 (file)
index 0000000..24c0bc6
--- /dev/null
@@ -0,0 +1,14 @@
+---
+# vars file for mezzanine
+mezzanine_user: "{{ ansible_user }}"
+mezzanine_venv_home: "{{ ansible_env.HOME }}/.virtualenvs"
+mezzanine_venv_path: "{{ mezzanine_venv_home }}/{{ mezzanine_proj_name }}"
+mezzanine_repo_url: git@github.com:ansiblebook/mezzanine_example.git
+mezzanine_proj_path: "{{ ansible_env.HOME }}/mezzanine/{{ mezzanine_proj_name }}"
+mezzanine_settings_path: "{{ mezzanine_proj_path }}/{{ mezzanine_proj_name }}"
+mezzanine_reqs_path: requirements.txt
+mezzanine_conf_path: /etc/nginx/conf
+mezzanine_python: "{{ mezzanine_venv_path }}/bin/python"
+mezzanine_manage: "{{ mezzanine_python }} {{ mezzanine_proj_path }}/manage.py"
+mezzanine_num_workers: "multiprocessing.cpu_count() * 2 + 1"
+mezzanine_gunicorn_procname: gunicorn_mezzanine