{"action":"created","comment":{"url":"https://api.github.com/repos/osuosl/wiki/pulls/comments/28537565","id":28537565,"diff_hunk":"@@ -0,0 +1,958 @@\n+\n+.. _osl-development-process:\n+\n+The OSL Development Workflow Guide\n+==================================\n+\n+The OSL builds websites for a variety of clients including the Oregon Virtual\n+School Districts, the Oregon Sea Grant Foundation, the OSU Biology department\n+and Google. When the initial development is finished, the OSL will host and\n+maintain the websites. This document will guide developers through the steps of\n+creating a new website for a client. New developers are encouraged to read this\n+document to understand how the developer team works, and experienced developers\n+are encouraged to review this document to brush up their knowledge of parts of\n+the process they may not be intimately familiar with. This workflow description\n+will also be available to clients so they can understand how the OSL developer\n+team works.\n+\n+How the Developer Team Interacts With Clients\n+---------------------------------------------\n+The needs of clients vary a surprising amount. The developer team is expected\n+to modify the development and maintenance schedule to suit the specific needs\n+of each customer. In the past, the OSL has adopted an agile methodology for\n+working with the BSG and the Sea Grant foundation. This contrasts with the more\n+conservative development and release cycle of the Protein Geometry Database,\n+where every major change and new release must be certified by scientists.\n+\n+For the What's Fresh and Working Waterfronts website the developer team chose\n+to follow the agile methodology. The agile method promotes quick responses to\n+change and customer collaboration, which made it an ideal fit for working with\n+both the Sea Grant Foundation and the BSG on an integrated website and mobile\n+application. The agile development methodology is defined in the `Agile\n+Manifesto`_, which is required reading for\n+all developers should the team choose to follow this methodology. When\n+following the agile methodology the developer team meets every month to\n+discuss the state of the project and show their progress to the client. The Sea\n+Grant projects also followed the test driven development model, which dictates\n+that tests should be written before code. This ensures that all of the code is\n+tested and works properly.\n+\n+The PGD project has a very different set of constraints. It is a large project\n+which is being maintained, but little active development is being done. The PGD\n+is a tool used by hundreds of scientists throughout the world, so it is very\n+important that changes do not introduce bugs which may affect ongoing research.\n+Every change to the PGD project must be approved by the scientists in charge of\n+the project. It is difficult to write tests to ensure that new changes don't\n+impact the science. Historically the PGD has had issues with upstream protein\n+repositories and antiquated closed-source dependencies. Even importing new data\n+can sometimes be a fraught process, but the team is developing a detailed\n+process to minimize problems when upstream software or data changes.\n+\n+.. _Agile Manifesto: http://www.agilemanifesto.org/\n+\n+Tools We Use\n+------------\n+At the OSL developers use a variety of open source tools. Developers are\n+expected to have at least passing familiarity with all of these tools. New\n+developers are encouraged to research any of these tools they have not heard\n+of.\n+\n+Most OSL websites are written in Python using the Django web application\n+framework. Websites are deployed using Chef configuration management on CentOS\n+6.6. Most of our sites use the MySQL database, although some use PostGres.\n+Most OSL websites have a Docker development environment set up to make\n+development fast and easy. To learn how to use the Docker development\n+environment please consult the project documentation and the OSL's `internal\n+documentation`_.\n+The OSL uses ``git`` for version control and keeps projects on\n+GitHub_. The OSL uses the Github issue tracker.\n+\n+.. _internal documentation: https://docs.osuosl.org/development/docker-dev-environments.html\n+.. _GitHub: https://github.com/osuosl\n+.. _RedMine: https://code.osuosl.org\n+\n+How a Typical Project is Written\n+================================\n+This is a step by step guide for writing a Django website. It will not cover\n+how to write a website using Flask because the process is nearly identical and\n+the team has indicated that Django is to be preferred for new projects.\n+\n+Determine Requirements\n+----------------------\n+- Meet with client\n+- determine requirements\n+- determine milestones\n+- internally determine code sprints\n+\n+Setting Up the Project\n+----------------------\n+\n+Create the git repo:\n+~~~~~~~~~~~~~~~~~~~~\n+\n+Unless instructed to do otherwise, or given\n+special permission in the `OSL organization github\n+<https://github.com/osuosl>`_, ask a full-time employee to make the repository.\n+All the repositories for the OSL should be owned by the OSL, so don't\n+make a repo under your own account! Once the repo is created use ``$ git clone\n+<SSH URI>`` to start working on the project.\n+\n+Create the project skeleton with Django\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+`Django <https://www.djangoproject.com/>`_ is a powerful python web\n+application framework that makes building web applications much\n+easier.  Framework is just a fancy word for \"lots of helpful tools\n+that make your life easier\".  Django has `excellent documentation\n+<https://docs.djangoproject.com/en/1.7/>`_ and `tutorials\n+<https://docs.djangoproject.com/en/1.7/intro/tutorial01/#creating-a-project>`_\n+that are well worth looking at. To begin a project, get to the directory where\n+the site will live and run ``$ django-admin.py startproject mysite``.  Now the\n+skeleton of the project is ready to go!  Add, commit, and push it to github as\n+an initial commit if appropriate.\n+\n+Create the Dockerfile\n+~~~~~~~~~~~~~~~~~~~~~\n+\n+Docker is a tool for putting discrete parts of an application on their own\n+pretend operating systems. This is called containerization. The upshot is that\n+a web application and a database don't need to be configured together. One of\n+Dockers biggest advantages for developers is its speed -- rebuilding a\n+development version of a website with Docker takes seconds as opposed to the 20\n+minutes Chef may need to converge. Writing a Dockerfile is easy. Here is a\n+simplified example from the PGD project:\n+\n+.. code:: text\n+\n+\tFROM centos:7\n+\n+\tMAINTAINER OSU OSL support@osuosl.org\n+\n+\tEXPOSE 8000\n+\n+\tRUN yum update && yum install -y \\\n+\t  cairo \\\n+\t  gcc \\\n+\t  gcc-c++ \\\n+\t  git \\\n+\t  libcairo-devel \\\n+\t  libffi \\\n+\t  libffi-devel \\\n+\t  mysql \\\n+\t  mysql-devel \\\n+\t  osuosl-dssp \\\n+\t  pycairo \\\n+\t  python-setuptools \\\n+\t  python-devel\n+\n+\tRUN easy_install pip\n+\n+\t# Copy and configure pgd\n+\tWORKDIR /opt/pgd\n+\t# Copy requirements.txt separately for better caching\n+\tCOPY ./requirements.txt /opt/pgd/requirements.txt\n+\tRUN pip install -r requirements.txt\n+\t# NB: copying the settings file is not a good idea when using volumes!\n+\tCOPY . /opt/pgd/\n+\tRUN cp /opt/pgd/settings.py.dist /opt/pgd/settings.py\n+\n+\tCMD [\"python\", \"manage.py\", \"runserver\", \"0.0.0.0:8000\"]\n+\n+More about writing Dockerfiles can be found in the `Docker documentation`_.\n+Docker also has a `document`_ specifying best practices. The Lab also has\n+`extensive internal documentation`_ specifying how Docker is used as part of\n+the developer team's workflow.\n+\n+.. _Docker documentation: http://docs.docker.com/reference/builder/\n+.. _document: https://docs.docker.com/articles/dockerfile_best-practices/\n+.. _extensive internal documentation: ://docs.osuosl.org/development/docker-dev-environments.html\n+\n+Create a Virtualenv:\n+~~~~~~~~~~~~~~~~~~~~\n+\n+Virtual environments are a pythonic way of containing dependencies within a\n+project. Virtualenvs are directories with their own version of Python, Pip, and\n+all of the packages a python application needs. Virtual environments should\n+probably have a descriptive name and be project specific. Many developers\n+prefer to keep them in one place, such as ``~/.my_venvs/``. To create a virtual\n+environment for a project such as What's Fresh, run ``virtualenv\n+~/my_venvs/whats_fresh``. A new directory will be created at that location.\n+Virtual environments must be activated to be used, by running ``$ source\n+~/.my_venvs/whats_fresh/bin/activate``.  Once a virtual environment has been\n+activated, dependencies can be installed as usual. To install all of the\n+dependencies listed in a project's ``requirements.txt`` file, run ``$ pip\n+install -r requirements.txt``. Once finished with the virtual environment, run\n+``$ deactivate`` to exit.  More detailed documentation can be found in the\n+`python guide <http://docs.python-guide.org/en/latest/dev/virtualenvs/>`_.\n+\n+\n+Create the projects default branch (if not master):\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Use the repositories settings (the little screwdriver in the sidebar) to\n+`change the default branch\n+<https://help.github.com/articles/setting-the-default-branch/>`_.  Remember\n+that the branch needs to exist first before it's made the default!\n+\n+The Development Process\n+-----------------------\n+- Pick an issue\n+- Checkout a new branch (with a branch naming scheme)\n+- Write tests (with a sample Django test)\n+- Write the code\n+- Make a PR\n+- Merge it\n+- Close the issue\n+\n+How to Write Python for Humans\n+------------------------------\n+\n+A lot of the work here at the Open Source Lab involves reading other people's\n+code. To make this process less painful for everyone involved, it's best to\n+standardize how code is written. Python also has interesting idioms, like\n+decorators, which are not common in other languages, which the Django framework\n+makes extensive use of.\n+\n+\n+PEP8, and how to check for it\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+PEP8 is the \"Style Guide for Python Code\". Essentially, it's a document\n+detailing how to write Python that's homogeneous and easy to read. It's a long\n+document -- here are some of the more important, more common bits.\n+\n+1. Limit lines to 79 characters\n+```````````````````````````````\n+\n+Lines that are too long get hard to read. If a line is more than 79\n+characters long, break it to the next line:\n+\n+.. code-block:: python\n+\n+  # Don't do this\n+  data = json.loads(serializer.serialize([preparation], use_natural_foreign_keys=True))\n+\n+  # Do this\n+\n+   data = json.loads(\n+      serializer.serialize(\n+          [preparation],\n+          use_natural_foreign_keys=True\n+    ))\n+  # Or this\n+  data = json.loads(\n+    serializer.serialize([preparation],\n+                         use_natural_foreign_keys=True\n+                         )\n+  )\n+\n+\n+When breaking just after an open-parenthesis, go in by four spaces as shown\n+in the first example. When breaking after a parameter, put the second parameter\n+indented to the same level as the first. The most important part to remember,\n+though, is that the code should be *readable*. Don't confuse future readers as\n+to what gets passed to what.\n+\n+2. Indentation\n+``````````````\n+\n+Use four spaces per indentation level. Don't use tabs. Any text editor\n+can be configured to insert spaces when tab is pressed. Do not leave trailing\n+whitespace at the end of lines or on otherwise blank lines.\n+\n+3. Blank lines\n+``````````````\n+\n+Put two blank lines in between classes, and in between functions. Put one blank\n+line in between methods. This helps the reader understand how the code is\n+logically organized.\n+\n+Docstrings\n+~~~~~~~~~~\n+\n+Docstrings are comments that appear at the top of a function, class, or other\n+definition. Says `PEP 257`_, the Python Docstring Conventions document:\n+\n+  All modules should normally have docstrings, and all functions and classes\n+  exported by a module should also have docstrings. Public methods (including\n+  the __init__ constructor) should also have docstrings.\n+\n+Docstrings appear as triple-quoted strings at the top of a definition. If\n+they're one line long, the quotes can be on the same line as the comment; if\n+they're multiple lines, the first should be a quick summary, followed by a\n+blank line and the rest of the docstring.\n+\n+.. _PEP 257: https://www.python.org/dev/peps/pep-0257/\n+\n+Example docstrings from the PEP 257:\n+\n+.. code-block:: python\n+\n+  def complex(real=0.0, imag=0.0):\n+      \"\"\"Form a complex number.\n+\n+      Keyword arguments:\n+      real -- the real part (default 0.0)\n+      imag -- the imaginary part (default 0.0)\n+      \"\"\"\n+      if imag == 0.0 and real == 0.0:\n+          return complex_zero\n+      ...\n+\n+A Brief Introduction to Django at the OSL\n+-----------------------------------------\n+\n+The Django project has a `great beginner's tutorial`_ that requires very little\n+knowledge of Python.\n+\n+.. _great beginner's tutorial: https://docs.djangoproject.com/en/dev/intro/tutorial01/\n+\n+Lots of OSL projects are written in Django, including Ganeti Web Manager,\n+What's Fresh, Working Waterfronts, and PGD. Here are some notes about Django\n+at the OSL:\n+\n+Configuration\n+~~~~~~~~~~~~~\n+\n+The lab uses yaml-based configuration for many of its Django projects. This\n+makes the configuration easier to read, and in general allows a slightly greater\n+degree of freedom in setting up. To see how this is set up, take a look at\n+`What's Fresh's settings.py`_.\n+\n+.. _What's Fresh's settings.py: https://github.com/osu-cass/whats-fresh-api/blob/master/whats_fresh/settings.py\n+\n+In general, apps will attempt to load settings from environmental variables.\n+This is most commonly used with our Docker-based developer environments. If\n+the environment variables are not set, it'll load settings from a yaml config\n+file, generally stored at ``/opt/app_name/config/config.yml`` by default. This\n+can also be overridden with an environment variable.\n+\n+Application location\n+~~~~~~~~~~~~~~~~~~~~\n+\n+Applications live in a subdirectory of the project. That is to say, if the project\n+is named ``project``, and the app is named ``app``, the directory structure will\n+look something like this::\n+\n+  manage.py\n+  project/\n+    /project/settings.py\n+    /app/models.py\n+\n+Example Model\n+~~~~~~~~~~~~~\n+\n+A model consists of everything one might need to store about an object in a\n+database. Imagine a blogging platform that allows users to share public posts,\n+and write private posts. The ``models.py`` for such a blog might include\n+something like this:\n+\n+.. code-block:: python\n+\n+  from django.db import models\n+\n+  class Entry(models.Model):\n+      \"\"\"This docstring contains information about the model.\"\"\"\n+      name = models.CharField(max_length=100)\n+      text = models.TextField()\n+      created = models.DateTimeField(auto_now_add=True)\n+      public = models.BooleanField(default=False)\n+\n+      def __unicode__(self):\n+          \"\"\"Returns Entry's name\n+\n+          The __unicode__ function allows Django to print which object\n+          is being dealt with. It uses this when it prints the object,\n+          or just put the object in the template.\n+          \"\"\"\n+          return self.name\n+\n+\n+Example View\n+~~~~~~~~~~~~\n+\n+Imagine the same blog platform from before. The following view might be used\n+to view the details of an entry:\n+\n+.. code-block:: python\n+\n+  def entry(request, id=None):\n+      \"\"\" /entry/<id> method. Handles private/public entry page requests\n+\n+      If the user is authenticated, this returns the details page for the\n+      requested entry. If the user is not authenticated, and it is a private\n+      post, the user is redirected to the login page.\n+      \"\"\"\n+      entry = get_object_or_404(Entry, pk=id)\n+      if not entry.public and not request.user.is_authenticated():\n+          return HttpResponseRedirect(reverse('login'))\n+      return render(request, 'entry.html', {'entry': entry})\n+\n+Here's another example view, this one used to create new Entries. Note that\n+this view can handle both GET requests, which are for the form before it's\n+been filled out, and POST requests, which save the form.\n+\n+.. code-block:: python\n+\n+  @login_required\n+  def new(request):\n+      \"\"\"/entry/new. Handles new entry creation for auth'd users\n+\n+      The form for creating a new entry. On a GET request, this returns the\n+      form that can be used to save the entry. On a POST request, it checks\n+      the validity of the form, and if it's valid, saves and redirects the\n+      user to its details page.\n+      \"\"\"\n+      form = EntryForm(request.POST or None)\n+      if form.is_valid():\n+          entry = form.save()\n+          return HttpResponseRedirect(\n+              reverse('entry-details',\n+                      kwargs={'id': entry.id}))\n+      return render(request, 'new.html', {'form': EntryForm})\n+\n+Notice the ``@login_required`` above the view function. This is a decorator,\n+a special Python function that \"wraps\" the function it's above. In this case,\n+Django's login_required decorator is being used. This decorator will make sure\n+the user is authenticated before running the view, and if they are not, will\n+redirect them to the login page.\n+\n+\n+Automated testing with Travis\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Automated testing with Travis CI is an incredibly powerful tool. Travis is a\n+continuous integration tool, meaning it's designed to run every time someone\n+pushes commits to a repository. In our case, it integrates directly into GitHub\n+and runs all of our tests, allowing code reviewers to see if a pull request\n+breaks something, or if a merge went horribly, horribly wrong.\n+\n+To set up Travis CI, make a ``.travis.yml`` file in the root of the repository.\n+It should look something like this::\n+\n+  language: python\n+  python:\n+    - \"2.7\"\n+  # command to install dependencies\n+  install:\n+    - \"python setup.py develop\"\n+    - \"pip install flake8\"\n+  # command to run tests\n+  script:\n+    - flake8 working_waterfronts/\n+    - django-admin test working_waterfronts --settings=\"working_waterfronts.settings\"\n+  addons:\n+    postgresql: \"9.3\"\n+  before_script:\n+    - psql -c 'create database working_waterfronts;' -U postgres\n+    - psql -U postgres -c \"create extension postgis;\" working_waterfronts\n+\n+Any command put in the ``before_script`` will be run. This allows us to create\n+database and extension needed for the app. The ``script`` is the actual test\n+run itself. If either command fails, the \"build\" will fail, and Travis will\n+report that it is not safe to merge.\n+\n+Using the flake8 Python Linter in CI forces the team to keep code clean and\n+easy to read.\n+\n+How to Git Out of a Tight Corner\n+--------------------------------\n+Git is a powerful tool which is generally easy to use, but sometimes it is\n+possible to wind up in a weird state. This section of the document describes\n+some intermediate level features of git which may be useful to the developer\n+team. Developers may also want to read `tricks for configuring git\n+<configuring_git.html>`_.\n+\n+\n+When to Use the Powers of the Force\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+Sometimes changes are made which affect the past history of the local branch,\n+and when these changes are pushed to GitHub or another git server they will be\n+rejected because the history on the server does not match the local history. A\n+common example is rebasing a branch to pick up new changes, which is detailed\n+further in the next section. The server can be made to accept these changes\n+with the ``--force`` flag, but be careful! Force pushing is a dangerous\n+operation which will permanently rewrite history on the server. Junior\n+developers should consult more experienced colleagues before force pushing.\n+\n+Developers must always use a fully qualified push command when force pushing,\n+naming both the git remote and branch. To force push to a branch named\n+``branch`` run\n+\n+.. code:: bash\n+\n+\tgit push --force origin branch\n+\n+Do NOT run\n+\n+.. code:: bash\n+\n+\tgit push --force\n+\n+Resolving Merge Conflicts\n+~~~~~~~~~~~~~~~~~~~~~~~~~\n+Merge conflicts are an unfortunate reality when working with peers on a large\n+code base. They are easy to fix, but they must be fixed with care as the\n+functionality of the code may be unintentionally changed. Always run tests\n+before pushing a branch which had merge conflicts. Merge conflicts can happen\n+when running ``git merge source_branch`` and when pulling or rebasing.\n+\n+Git denotes merge conflicts with a series of angle brackets. After each series\n+of angle brackets is the name of the commit where the changes came from. In\n+this case, the code in the HEAD revision is older than the other revision, so\n+the code between ``<< HEAD`` and the equal signs should be removed. The line\n+with the angle brackets and the newer commit hash should also be removed. This\n+cannot be done automatically because git doesn't know which lines to include,\n+or whether some combination of the lines should be included.\n+\n+.. code:: python\n+\n+\t<<<<<<< HEAD\n+\t\t\tsortx_sql = sortx.aggregate.as_sql(qn, cn)\n+\t\t\tsorty_sql = sorty.aggregate.as_sql(qn, cn)\n+\t=======\n+\t\t\tsortx_sql = sortx.aggregate.as_sql(qn, cn)[0]\n+\t\t\tsorty_sql = sorty.aggregate.as_sql(qn, cn)[0]\n+\t>>>>>>> 692b8936b466d8c651bb1ab39e96ca98c7c4714b\n+\n+To resolve merge conflicts when running ``git merge``, add the files which were\n+corrected, and then commit. That commit will have its title automatically\n+generated -- do not change the title, but feel free to add more details to the\n+body of the commit about why the merge is happening.\n+\n+Always run ``git grep '<<<'`` and ``git grep '>>>'`` before pushing code after\n+a merg conflict and run tests. It is possible that some merge related code\n+escaped notice, and this should be fixed as soon as possible.\n+\n+Rebasing and Squashing Commits\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+Often a developer will check out a new branch and while they are working on the\n+branch different changes will be merged into develop. To pick up changes on\n+develop, check out the working branch and run the following:\n+\n+.. code:: bash\n+\n+\t$ git checkout fancy-changes\n+\t$ git rebase develop\n+\n+This will add all of the changes merged into develop since the branches\n+diverged onto ``fancy-new-changes``.\n+\n+Sometimes a series of commits should be combined into one large commit. This\n+can be useful when there were many \"work in progress\" commits which do not need\n+to clutter the git history. This is called squashing commits. First, find the\n+oldest commit hash which should be squashed using ``git log``, in this example\n+``abcde12345``. Then run:\n+\n+.. code:: bash\n+\n+\t$ git rebase -i abcde12345 # i stands for interactive\n+\n+Git will open up the editor and provide detailed instructions on how to choose\n+which commits to keep or combine.\n+\n+Cherry Picking\n+~~~~~~~~~~~~~~\n+Sometimes it will be necessary to move several commits from one branch to\n+another. This can be achieved simply by using ``git cherry-pick``. First, check\n+out the branch which the commits will be moved to. Then, find the hash\n+representing the commit using ``git log --all``, for example ``abcde12345``,\n+and run:\n+\n+.. code:: shell\n+\n+\t$ git cherry-pick abcde2345\n+\n+This will add commit ``abcde12345`` to the current branch.\n+\n+\n+When Disaster Strikes\n+~~~~~~~~~~~~~~~~~~~~~\n+On occasion disaster will strike, and it will appear that all has been lost. It\n+is important not to panic, such mistakes can often be resolved. As long as the\n+``.git`` folder is intact, git keeps a log of the changes made to the\n+repository. If a change is made which affects git history, the hash of the\n+commit previous to the change will be stored in the special file\n+``.git/ORIG_HEAD``, for instance ``absde12345``. To go back to that commit, run\n+``git reset abcde12345``.\n+\n+Miscellaneous Git Tips\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+* To undo the last commit, run ``git reset HEAD~1``.\n+* To amend the last commit *which has not been pushed* and fix anything which\n+  was forgotten, first add any files which were changed or forgotten, then run\n+  ``git commit --amend``.\n+* To add part of a file, use ``git add -p`` and follow the interactive\n+  instructions.\n+* To delete a remote branch named ``branch``, run ``git push origin :branch``\n+* To get a pretty view of git history, run ``git log --graph --all``.\n+  Some developers alias this to ``git-net``.\n+\n+\n+Writing the Chef Cookbook\n+-------------------------\n+\n+Writing a chef cookbook is very similar to writing the rest of the project. The\n+Lab has internal resources explaining how to write a cookbook, so this is just\n+a summary. A cookbook describes the desired state of a server. A cookbook may\n+have several recipes for different operating systems or for different parts of\n+the servers configuration. Cookbooks can use pieces of code from other\n+cookbooks called resources. Here is an abridged version of the What's Fresh\n+coobook's default recipe:\n+\n+.. code:: ruby\n+\n+\tdirectory node['whats_fresh']['config_dir'] do\n+\t  owner node['whats_fresh']['venv_owner']\n+\t  group node['whats_fresh']['venv_group']\n+\t  recursive true\n+\tend\n+\n+\tpython_webapp 'whats_fresh' do\n+\t  create_user true\n+\t  path node['whats_fresh']['application_dir']\n+\t  owner node['whats_fresh']['venv_owner']\n+\t  group node['whats_fresh']['venv_group']\n+\n+\t  repository node['whats_fresh']['repository']\n+\t  revision node['whats_fresh']['git_branch']\n+\n+\t  config_template 'config.yml.erb'\n+\t  config_destination \"#{node['whats_fresh']['config_dir']}/config.yml\"\n+\t  django_migrate true\n+\t  django_collectstatic true\n+\t  interpreter 'python2.7'\n+\n+\t  gunicorn_port node['whats_fresh']['gunicorn_port']\n+\tend\n+\n+\tnginx_app 'whats_fresh' do\n+\t  template 'whats_fresh.conf.erb'\n+\t  cookbook 'whats-fresh'\n+\tend\n+\n+This recipe uses several resources, including ``python_webapp``, ``nginx_app``,\n+and ``directory``. The section ``node['whats_fresh']['application_dir']`` is\n+just like a python dictionary with a specific configuration value, often a\n+string.\n+\n+\n+The Architecture of Chef, Abridged\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+Chef has several parts, a central server which holds all of the cookbooks, and\n+a collection of nodes, which are managed by the chef server. Each node has\n+attributes which are essentially a hash or a dictionary of configuration values\n+and keys. Each node has a list of roles it serves, like web server or database\n+server, and a run list of recipes it will run. Every half hour or so each node\n+checks with the chef server and converges to the state which the chef cookbook\n+specifies. Sysadmins can edit the attributes of a node on the chef server using\n+the ``knife`` tool.\n+\n+Anatomy of a Coobook\n+~~~~~~~~~~~~~~~~~~~~\n+Most cookbooks begin with a table of contents, but Chef cookbooks are full of\n+files. Here is a summary of important files in a cookbook.\n+\n+.. code:: text\n+\n+\t.\n+\t├── attributes: A directory","path":"source/development_process/index.rst","position":661,"original_position":661,"commit_id":"7cd24841ccd35d8c3dfa2c492ee1aa4ec41f235e","original_commit_id":"7cd24841ccd35d8c3dfa2c492ee1aa4ec41f235e","user":{"login":"iankronquist","id":5192353,"avatar_url":"https://avatars.githubusercontent.com/u/5192353?v=3","gravatar_id":"","url":"https://api.github.com/users/iankronquist","html_url":"https://github.com/iankronquist","followers_url":"https://api.github.com/users/iankronquist/followers","following_url":"https://api.github.com/users/iankronquist/following{/other_user}","gists_url":"https://api.github.com/users/iankronquist/gists{/gist_id}","starred_url":"https://api.github.com/users/iankronquist/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iankronquist/subscriptions","organizations_url":"https://api.github.com/users/iankronquist/orgs","repos_url":"https://api.github.com/users/iankronquist/repos","events_url":"https://api.github.com/users/iankronquist/events{/privacy}","received_events_url":"https://api.github.com/users/iankronquist/received_events","type":"User","site_admin":false},"body":"Oops. What would you put?","created_at":"2015-04-16T18:40:25Z","updated_at":"2015-04-16T18:40:25Z","html_url":"https://github.com/osuosl/wiki/pull/12#discussion_r28537565","pull_request_url":"https://api.github.com/repos/osuosl/wiki/pulls/12","_links":{"self":{"href":"https://api.github.com/repos/osuosl/wiki/pulls/comments/28537565"},"html":{"href":"https://github.com/osuosl/wiki/pull/12#discussion_r28537565"},"pull_request":{"href":"https://api.github.com/repos/osuosl/wiki/pulls/12"}}},"pull_request":{"url":"https://api.github.com/repos/osuosl/wiki/pulls/12","id":31079246,"html_url":"https://github.com/osuosl/wiki/pull/12","diff_url":"https://github.com/osuosl/wiki/pull/12.diff","patch_url":"https://github.com/osuosl/wiki/pull/12.patch","issue_url":"https://api.github.com/repos/osuosl/wiki/issues/12","number":12,"state":"open","locked":false,"title":"Dev workflow docs","user":{"login":"tschuy","id":6532435,"avatar_url":"https://avatars.githubusercontent.com/u/6532435?v=3","gravatar_id":"","url":"https://api.github.com/users/tschuy","html_url":"https://github.com/tschuy","followers_url":"https://api.github.com/users/tschuy/followers","following_url":"https://api.github.com/users/tschuy/following{/other_user}","gists_url":"https://api.github.com/users/tschuy/gists{/gist_id}","starred_url":"https://api.github.com/users/tschuy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tschuy/subscriptions","organizations_url":"https://api.github.com/users/tschuy/orgs","repos_url":"https://api.github.com/users/tschuy/repos","events_url":"https://api.github.com/users/tschuy/events{/privacy}","received_events_url":"https://api.github.com/users/tschuy/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-03-12T21:23:39Z","updated_at":"2015-04-16T18:40:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"6b094eb8dfc94376bea42fde363a0bd7052fc0da","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/osuosl/wiki/pulls/12/commits","review_comments_url":"https://api.github.com/repos/osuosl/wiki/pulls/12/comments","review_comment_url":"https://api.github.com/repos/osuosl/wiki/pulls/comments{/number}","comments_url":"https://api.github.com/repos/osuosl/wiki/issues/12/comments","statuses_url":"https://api.github.com/repos/osuosl/wiki/statuses/7cd24841ccd35d8c3dfa2c492ee1aa4ec41f235e","head":{"label":"osuosl:dev-workflow","ref":"dev-workflow","sha":"7cd24841ccd35d8c3dfa2c492ee1aa4ec41f235e","user":{"login":"osuosl","id":1380625,"avatar_url":"https://avatars.githubusercontent.com/u/1380625?v=3","gravatar_id":"","url":"https://api.github.com/users/osuosl","html_url":"https://github.com/osuosl","followers_url":"https://api.github.com/users/osuosl/followers","following_url":"https://api.github.com/users/osuosl/following{/other_user}","gists_url":"https://api.github.com/users/osuosl/gists{/gist_id}","starred_url":"https://api.github.com/users/osuosl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/osuosl/subscriptions","organizations_url":"https://api.github.com/users/osuosl/orgs","repos_url":"https://api.github.com/users/osuosl/repos","events_url":"https://api.github.com/users/osuosl/events{/privacy}","received_events_url":"https://api.github.com/users/osuosl/received_events","type":"Organization","site_admin":false},"repo":{"id":16817132,"name":"wiki","full_name":"osuosl/wiki","owner":{"login":"osuosl","id":1380625,"avatar_url":"https://avatars.githubusercontent.com/u/1380625?v=3","gravatar_id":"","url":"https://api.github.com/users/osuosl","html_url":"https://github.com/osuosl","followers_url":"https://api.github.com/users/osuosl/followers","following_url":"https://api.github.com/users/osuosl/following{/other_user}","gists_url":"https://api.github.com/users/osuosl/gists{/gist_id}","starred_url":"https://api.github.com/users/osuosl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/osuosl/subscriptions","organizations_url":"https://api.github.com/users/osuosl/orgs","repos_url":"https://api.github.com/users/osuosl/repos","events_url":"https://api.github.com/users/osuosl/events{/privacy}","received_events_url":"https://api.github.com/users/osuosl/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/osuosl/wiki","description":"Public documentation for OSUOSL","fork":false,"url":"https://api.github.com/repos/osuosl/wiki","forks_url":"https://api.github.com/repos/osuosl/wiki/forks","keys_url":"https://api.github.com/repos/osuosl/wiki/keys{/key_id}","collaborators_url":"https://api.github.com/repos/osuosl/wiki/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/osuosl/wiki/teams","hooks_url":"https://api.github.com/repos/osuosl/wiki/hooks","issue_events_url":"https://api.github.com/repos/osuosl/wiki/issues/events{/number}","events_url":"https://api.github.com/repos/osuosl/wiki/events","assignees_url":"https://api.github.com/repos/osuosl/wiki/assignees{/user}","branches_url":"https://api.github.com/repos/osuosl/wiki/branches{/branch}","tags_url":"https://api.github.com/repos/osuosl/wiki/tags","blobs_url":"https://api.github.com/repos/osuosl/wiki/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/osuosl/wiki/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/osuosl/wiki/git/refs{/sha}","trees_url":"https://api.github.com/repos/osuosl/wiki/git/trees{/sha}","statuses_url":"https://api.github.com/repos/osuosl/wiki/statuses/{sha}","languages_url":"https://api.github.com/repos/osuosl/wiki/languages","stargazers_url":"https://api.github.com/repos/osuosl/wiki/stargazers","contributors_url":"https://api.github.com/repos/osuosl/wiki/contributors","subscribers_url":"https://api.github.com/repos/osuosl/wiki/subscribers","subscription_url":"https://api.github.com/repos/osuosl/wiki/subscription","commits_url":"https://api.github.com/repos/osuosl/wiki/commits{/sha}","git_commits_url":"https://api.github.com/repos/osuosl/wiki/git/commits{/sha}","comments_url":"https://api.github.com/repos/osuosl/wiki/comments{/number}","issue_comment_url":"https://api.github.com/repos/osuosl/wiki/issues/comments{/number}","contents_url":"https://api.github.com/repos/osuosl/wiki/contents/{+path}","compare_url":"https://api.github.com/repos/osuosl/wiki/compare/{base}...{head}","merges_url":"https://api.github.com/repos/osuosl/wiki/merges","archive_url":"https://api.github.com/repos/osuosl/wiki/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/osuosl/wiki/downloads","issues_url":"https://api.github.com/repos/osuosl/wiki/issues{/number}","pulls_url":"https://api.github.com/repos/osuosl/wiki/pulls{/number}","milestones_url":"https://api.github.com/repos/osuosl/wiki/milestones{/number}","notifications_url":"https://api.github.com/repos/osuosl/wiki/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/osuosl/wiki/labels{/name}","releases_url":"https://api.github.com/repos/osuosl/wiki/releases{/id}","created_at":"2014-02-13T21:02:16Z","updated_at":"2015-04-03T23:16:07Z","pushed_at":"2015-04-09T16:32:15Z","git_url":"git://github.com/osuosl/wiki.git","ssh_url":"git@github.com:osuosl/wiki.git","clone_url":"https://github.com/osuosl/wiki.git","svn_url":"https://github.com/osuosl/wiki","homepage":"","size":1261,"stargazers_count":2,"watchers_count":2,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":3,"forks":1,"open_issues":3,"watchers":2,"default_branch":"master"}},"base":{"label":"osuosl:master","ref":"master","sha":"f6c055b7148dbc37eb87ce6a42dd08bb2bce0746","user":{"login":"osuosl","id":1380625,"avatar_url":"https://avatars.githubusercontent.com/u/1380625?v=3","gravatar_id":"","url":"https://api.github.com/users/osuosl","html_url":"https://github.com/osuosl","followers_url":"https://api.github.com/users/osuosl/followers","following_url":"https://api.github.com/users/osuosl/following{/other_user}","gists_url":"https://api.github.com/users/osuosl/gists{/gist_id}","starred_url":"https://api.github.com/users/osuosl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/osuosl/subscriptions","organizations_url":"https://api.github.com/users/osuosl/orgs","repos_url":"https://api.github.com/users/osuosl/repos","events_url":"https://api.github.com/users/osuosl/events{/privacy}","received_events_url":"https://api.github.com/users/osuosl/received_events","type":"Organization","site_admin":false},"repo":{"id":16817132,"name":"wiki","full_name":"osuosl/wiki","owner":{"login":"osuosl","id":1380625,"avatar_url":"https://avatars.githubusercontent.com/u/1380625?v=3","gravatar_id":"","url":"https://api.github.com/users/osuosl","html_url":"https://github.com/osuosl","followers_url":"https://api.github.com/users/osuosl/followers","following_url":"https://api.github.com/users/osuosl/following{/other_user}","gists_url":"https://api.github.com/users/osuosl/gists{/gist_id}","starred_url":"https://api.github.com/users/osuosl/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/osuosl/subscriptions","organizations_url":"https://api.github.com/users/osuosl/orgs","repos_url":"https://api.github.com/users/osuosl/repos","events_url":"https://api.github.com/users/osuosl/events{/privacy}","received_events_url":"https://api.github.com/users/osuosl/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/osuosl/wiki","description":"Public documentation for OSUOSL","fork":false,"url":"https://api.github.com/repos/osuosl/wiki","forks_url":"https://api.github.com/repos/osuosl/wiki/forks","keys_url":"https://api.github.com/repos/osuosl/wiki/keys{/key_id}","collaborators_url":"https://api.github.com/repos/osuosl/wiki/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/osuosl/wiki/teams","hooks_url":"https://api.github.com/repos/osuosl/wiki/hooks","issue_events_url":"https://api.github.com/repos/osuosl/wiki/issues/events{/number}","events_url":"https://api.github.com/repos/osuosl/wiki/events","assignees_url":"https://api.github.com/repos/osuosl/wiki/assignees{/user}","branches_url":"https://api.github.com/repos/osuosl/wiki/branches{/branch}","tags_url":"https://api.github.com/repos/osuosl/wiki/tags","blobs_url":"https://api.github.com/repos/osuosl/wiki/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/osuosl/wiki/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/osuosl/wiki/git/refs{/sha}","trees_url":"https://api.github.com/repos/osuosl/wiki/git/trees{/sha}","statuses_url":"https://api.github.com/repos/osuosl/wiki/statuses/{sha}","languages_url":"https://api.github.com/repos/osuosl/wiki/languages","stargazers_url":"https://api.github.com/repos/osuosl/wiki/stargazers","contributors_url":"https://api.github.com/repos/osuosl/wiki/contributors","subscribers_url":"https://api.github.com/repos/osuosl/wiki/subscribers","subscription_url":"https://api.github.com/repos/osuosl/wiki/subscription","commits_url":"https://api.github.com/repos/osuosl/wiki/commits{/sha}","git_commits_url":"https://api.github.com/repos/osuosl/wiki/git/commits{/sha}","comments_url":"https://api.github.com/repos/osuosl/wiki/comments{/number}","issue_comment_url":"https://api.github.com/repos/osuosl/wiki/issues/comments{/number}","contents_url":"https://api.github.com/repos/osuosl/wiki/contents/{+path}","compare_url":"https://api.github.com/repos/osuosl/wiki/compare/{base}...{head}","merges_url":"https://api.github.com/repos/osuosl/wiki/merges","archive_url":"https://api.github.com/repos/osuosl/wiki/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/osuosl/wiki/downloads","issues_url":"https://api.github.com/repos/osuosl/wiki/issues{/number}","pulls_url":"https://api.github.com/repos/osuosl/wiki/pulls{/number}","milestones_url":"https://api.github.com/repos/osuosl/wiki/milestones{/number}","notifications_url":"https://api.github.com/repos/osuosl/wiki/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/osuosl/wiki/labels{/name}","releases_url":"https://api.github.com/repos/osuosl/wiki/releases{/id}","created_at":"2014-02-13T21:02:16Z","updated_at":"2015-04-03T23:16:07Z","pushed_at":"2015-04-09T16:32:15Z","git_url":"git://github.com/osuosl/wiki.git","ssh_url":"git@github.com:osuosl/wiki.git","clone_url":"https://github.com/osuosl/wiki.git","svn_url":"https://github.com/osuosl/wiki","homepage":"","size":1261,"stargazers_count":2,"watchers_count":2,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":3,"forks":1,"open_issues":3,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/osuosl/wiki/pulls/12"},"html":{"href":"https://github.com/osuosl/wiki/pull/12"},"issue":{"href":"https://api.github.com/repos/osuosl/wiki/issues/12"},"comments":{"href":"https://api.github.com/repos/osuosl/wiki/issues/12/comments"},"review_comments":{"href":"https://api.github.com/repos/osuosl/wiki/pulls/12/comments"},"review_comment":{"href":"https://api.github.com/repos/osuosl/wiki/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/osuosl/wiki/pulls/12/commits"},"statuses":{"href":"https://api.github.com/repos/osuosl/wiki/statuses/7cd24841ccd35d8c3dfa2c492ee1aa4ec41f235e"}}}}