{"action":"created","comment":{"url":"https://api.github.com/repos/crowbar/crowbar/pulls/comments/97779741","pull_request_review_id":18390958,"id":97779741,"diff_hunk":"@@ -0,0 +1,1321 @@\n+# Barclamp development guide\n+\n+This document is an introduction to Barclamp development. It will guide you\n+through all the steps neccessary to create a minimal (without UI elements)\n+Barclamp for Crowbar. It will then guide you through all the testing, CI and\n+review steps until the point where it gets merged into Crowbar.\n+\n+## What is a Barclamp?\n+\n+A barclamp is a plugin for Crowbar that configures a particular service or\n+group of services. It contains a Chef cookbook with one or more roles to\n+perform the actual configuration work and Crowbar UI code to parametrize this\n+chef cookbook and orchestrate applying its roles to individual machines.\n+\n+## Terms and definitions\n+\n+We will assume throughout this text that your barclamp is will be named\n+`mybarclamp`. Please substitute your actual barclamp's name for this\n+placeholder when you create a barclamp. The convention for OpenStack barclamps\n+is to use the OpenStack component's name, e.g. `nova`.\n+\n+## Prerequisites\n+\n+We will assume some things to already be present. Namely, the following:\n+\n+* A Github fork of the appropriate Barclamp collection. For instance, you will\n+  find OpenStack related barclamps in the\n+  [crowbar-openstack](https://www.github.com/crowbar/crowbar-openstack)\n+  repository and high availability related Barclamps in the\n+  [crowbar-ha](https://github.com/crowbar/crowbar-ha) repository. For the sake\n+  of readability we will assume you are developing an OpenStack related and\n+  forked [crowbar-openstack](https://www.github.com/crowbar/crowbar-openstack)\n+  throughout the rest of this document.\n+\n+* A Crowbar admin node with the Crowbar version you are developing against\n+  (for new barclamps this will be `master`) already installed and running. This\n+  should be as up-to-date as possible to reduce the chance of merge conflicts\n+  happening.\n+\n+* A set of machines for your Crowbar admin node to orchestrate. In the case of\n+  SUSE OpenStack Cloud this would be a controller and one compute node at a\n+  minimum.\n+\n+* You will need to install the following packages:\n+\n+  * `patch`\n+  * `git-core`\n+\n+Providing these is out of scope for this document. We assume you are able to\n+fork the appropriate Barclamp collection. The machines can be provided through\n+manual installation or by using an automated setup tool such as\n+[mkcloud](https://github.com/SUSE-Cloud/automation/blob/master/scripts/mkcloud).\n+Depending on the individual situation you can use either the `plain` or the\n+`installonly` `mkcloud` step. The former will install the nodes and deploy the\n+standard set of barclamps in their default configuration, while the latter will\n+only install the nodes. Use `plain` if your barclamp does not need to be\n+deployed before other barclamps and if you do not modify other barclamps.\n+Otherwise use `installonly` and deploy barclamps bit by bit through the Crowbar\n+web UI or the Crowbar CLI client. This will give you the opportunity to add\n+package repositories or tweak barclamps' proposal settings where needed along\n+the way.\n+\n+Optionally (but strongly recommended) you can also\n+\n+* Create a backup of your Crowbar admin node: You are probably going to break\n+  it a few times in the course of testing your new barclamp so it is a good\n+  idea to have a backup you can quickly restore rather than rebuild your entire\n+  test environment. If you are using `mkcloud` you can create and restore a LVM\n+  snapshot of your admin node using its `createadminsnapshot` and\n+  `restoreadminfromsnapshot` steps, respectively.\n+\n+* Create an additional OpenStack controller or compute node as a scratch node:\n+  this will allow you to deploy only your own barclamp's controller side\n+  components to this node while all other OpenStack services reside on the main\n+  controller. This greatly shortens the time a chef run for your barclamp's\n+  controller side components takes and thus speeds up development. Make a\n+  backup of this node as well to guard against breakage and allow for\n+  clean-slate deployment of your barclamp's controller side components.\n+\n+The best way to set up your scratch node is to make it a lonely node. To this\n+end, you will need to re-run mkcloud after you have created your cloud:\n+\n+```\n+nodenumberlonelynode=1 mkcloud setuplonelynodes crowbar_register\n+\n+```\n+\n+## Development Environment\n+\n+Most of your development will take place on your Crowbar node (log in as root).\n+You will find the currently running Crowbar code (both Rails application\n+components and chef recipes) in the `/opt/dell` directory. We will provide\n+detailed locations for all important components of a Barclamp in the _Basic\n+Implementation_ section below.\n+\n+In order to track changes you make we recommend you keep a checkout of your\n+`crowbar-openstack` fork on the Crowbar node and apply the changes you make in\n+there\n+\n+### Preparations for Crowbar Node\n+\n+These are recommendations, but we'll assume you followed them for the remainder\n+of this document since this shortens the commands in our examples. First of\n+all, clone your `crowbar-openstack` fork to `/root/crowbar-openstack` on your\n+Crowbar node and switch to your development branch.\n+\n+Next, create the following /root/.bashrc with a few useful aliases and\n+environment variables (substitute with appropriate values where so indicated by\n+comments):\n+\n+```\n+# Replace \"Crowbar Committer\" by your name as it should appear in commit\n+# messages.\n+C_NAME=\"Crowbar Committer\"\n+\n+# Replace \"email@address.invalid\" by your email address as it should appear in\n+# commit messages.\n+C_EMAIL=\"email@address.invalid\"\n+\n+# Replace \"6cefbd80254141a63f4c86df195cd930ac7eb10b\" by the ID of the oldest\n+# commit on your branch. Running\n+#\n+#   git log --format='%H %s' $branch..HEAD\n+#\n+# where $branch is the upstream branch you are developing against such as\n+# `master` or `stable/3.0`, should give you a list of original commits. This\n+# assumes $branch has not been improperly cherry-picked into or rebased in your\n+# fork, though. It also assumes that you are not working on your fork's\n+# `master` branch (you shouldn't, but people have been known to do this\n+# anyway). Since all of these may occasionally happen, this guide leaves it up\n+# to the user to set $COMMIT as they see fit rather than trying to determine\n+# its value automatically through the command mentioned above.\n+\n+export COMMIT=\"6cefbd80254141a63f4c86df195cd930ac7eb10b\"\n+\n+# Insert your own barclamp's name here:\n+export BARCLAMP_NAME=mybarclamp\n+\n+# Insert a space delimited list of the Barclamps you are working on here (only\n+# required if your barclamp requires changes to other barclamps as well).\n+\n+export BARCLAMPS=\"$BARCLAMP_NAME\"\n+\n+# Insert the path to your barclamp collection checkout here (in case it's\n+# somewhere other than /root/crowbar-openstack). This directory must be a git\n+# working copy of the crowbar-openstack fork and branch you are working on.\n+\n+export BARCLAMP_FORK_PATH=/root/crowbar-openstack\n+\n+export GIT_AUTHOR_NAME=\"$C_NAME\"\n+export GIT_AUTHOR_EMAIL=\"$C_EMAIL\"\n+export GIT_COMMITTER_NAME=\"$C_NAME\"\n+export GIT_COMMITTER_EMAIL=\"$C_EMAIL\"\n+\n+# Applies all the committed changes in your crowbar-openstack working copy to\n+# /opt/dell and updates Crowbar runtime state. Note: this will break if you\n+# rebase your working copy, so do not rebase your working copy. If you rebase\n+# it (e.g. for final testing before a merge, please build a fresh Crowbar node\n+# or restore it from a snapshot).\n+function reposync_crowbar()\n+  {\n+  if [ -e /opt/dell/patchlevel ]; then\n+    . /opt/dell/patchlevel\n+    head=$(cd $BARCLAMP_FORK_PATH; git log --format='%H'| head -n 1)\n+    if [ \"$head\" = \"$commit\" ]; then\n+      echo \"/opt/dell is already up to date. If this is not what you \" 1>&2\n+      echo \"expected to see, you may have uncommitted changes\" 1>&2\n+      echo \"in ${BARCLAMP_FORK_PATH}.\" 1>&2\n+      return\n+    fi\n+  else\n+    commit=\"${COMMIT}^\"\n+  fi\n+\n+  pushd $BARCLAMP_FORK_PATH > /dev/null\n+  cp -a /opt/dell /opt/dell.orig\n+  git diff $commit HEAD | patch -N --merge -p1 -d /opt/dell || return\n+  rm -rf /opt/dell.orig\n+  # Record current HEAD as patchlevel\n+  echo \"commit=$(git log --format='%H'| head -n 1)\" > /opt/dell/patchlevel\n+  popd > /dev/null\n+\n+  for barclamp in $BARCLAMPS\n+    do\n+    if [ ! -h /opt/dell/crowbar_framework/barclamps/${barclamp}.yml ]; then\n+      ln -s /opt/dell/${barclamp}.yml /opt/dell/crowbar_framework/barclamps 2>&1 > /dev/null\n+    fi\n+    done\n+  chmod 755 /opt/dell/bin/*\n+\n+  sync_crowbar\n+  }\n+\n+# Helper function: updates Crowbar's runtime state after /opt/dell\n+# has been modified.\n+sync_crowbar()\n+  {\n+  barclamp_install.rb /opt/dell/crowbar_framework/barclamps &&\n+  for barclamp in $BARCLAMPS\n+    do\n+    knife cookbook upload -o /opt/dell/chef/cookbooks/ $barclamp\n+    done\n+  systemctl restart crowbar\n+  }\n+\n+```\n+\n+We will make heavy use of the `reposync_crowbar` command thus defined in the\n+following sections. A short explanation of the three:\n+\n+`reposync_crowbar` will apply your whole fork against `/opt/dell` using\n+`patch(1)` and sync Crowbar's runtime state with what is now in /opt/dell. You\n+use this command to apply the current state of your fork to a freshly installed\n+Crowbar node. This command may cause merge conflicts which you will have to\n+resolve before proceeding. They usually happen due to a very out of date topic\n+branch. While you can resolve them in /opt/dell you are better off rebasing\n+your `crowbar-openstack` fork against upstream and applying the result to a\n+fresh Crowbar node. Also, you will find a backup copy of /opt/dell in\n+/opt/dell.orig if a merge conflict does occur.\n+\n+Using patches against `/opt/dell` may seem a bit over-the-top at first since\n+one could simply copy files back and forth or use `barclamp_install` on your\n+checkout, but it prevents a lot of breakage in cases where `/opt/dell` is out\n+of sync with the `crowbar-openstack` repository's master branch or your own\n+fork of `crowbar-openstack` is out of sync with upstream.\n+\n+In the former case copying files from your fork to `/opt/dell` or using\n+`barclamp_install` may cause an inconsistent state and errors entirely\n+unrelated to your change.\n+\n+In the latter case copying files from `/opt/dell` to your fork may re-introduce\n+old code that is no longer present upstream. Both should not be an issue for\n+new barclamps, but it is better to get used to the `patch(1)` based workflow\n+right away since these problems do become a problem when changing an existing\n+barclamp later and thus the `patch(1)` based workflow becomes neccessary.\n+\n+## Packages\n+\n+If you create a new barclamp this is typically to deploy an OpenStack service\n+that does not exist on SUSE OpenStack Cloud, yet. While important, your\n+barclamp is only half of what is needed. The other half is creating the\n+packages your Barclamp will install the new OpenStack service from.\n+\n+Ideally, you have already created these packages in [Open Build\n+Service](https://build.opensuse.org) and have taken the appropriate steps to\n+get them into the repository `mkcloud` uses for deployment (since `mkcloud`\n+uses SUSE internal repositories or a local copy of these internal repositories,\n+there is no point in detailing these steps in this public guide). In this case\n+you are all set, and your chef recipes will merrily install the packages they\n+need from official sources. If this isn't the case for you there are still some\n+strategies for getting your packages installed:\n+\n+First of all, you will need a package repository to store your non-official\n+packages in. The easiest way to accomplish this is to register an account on\n+[Open Build Service](https://build.opensuse.org) and create a project with all\n+the packages you need under your home project. We'll assume you have this a\n+non-official package repository (or multiple repositories) with all the\n+packages you need in place for the remainder of this section.\n+\n+If all you do is adding new packages, you should be fine with simply adding\n+your repository using `zypper ar` on every node you are installing your\n+packages to:\n+\n+```\n+zypper ar -f http://download.opensuse.org/repositories/home:/<your OBS user>:/<your project name>/SLE_12_SP2 myrepo\n+```\n+\n+If you need to override existing packages with newer versions (for instance, if\n+the service your barclamp deploys requires a newer python-openstackclient\n+version you ship in your own repository), you may also need to disable vendor\n+locking for Zypper. To do this, set the following in `/etc/zypper/zypp.conf`:\n+\n+```\n+solver.allowVendorChange = true\n+\n+```\n+\n+You need to do this because normally Zypper would prefer packages from official\n+repositories, even if your own repository contains newer version. If you set\n+this option the newer package from your repository will trump the one from\n+the official one.\n+\n+Both steps must be performed on all relevant nodes _before_ you apply your\n+barclamp, otherwise applying it will fail due to Crowbar being unable to locate\n+your packages.\n+\n+## Barclamp Component Overview\n+\n+A barclamp consists of two main components:\n+\n+* A [chef cookbook](https://docs.chef.io/cookbooks.html) which takes care of\n+  actually deploying your service.\n+* Some glue code to tie your chef cookbook into the Crowbar Web UI. This glue\n+  code will parametrize your chef cookbook and apply its component roles to the\n+  nodes it orchestrates.\n+\n+The subsequent sections will describe these two components and the ingredients\n+that go into them into detail. They start of with an overview of a barclamp's\n+development phases as we know them from experience and take you through all\n+aspects of barclamp implementation in the order you are likely to encounter\n+them. They are meant to be read in parallel to developing your own barclamp.\n+\n+## Development Phases\n+\n+In this section we will show a development workflow we consider useful. This is\n+not the end-all-be-all of Barclamp development workflows, but it should be\n+useful for new developers.\n+\n+The Development workflow essentially consists of 3 phases: \n+\n+1. Basic Implementation: In this phase you will write a scaffold for the\n+   Barclamp's chef code, define roles and parameters and create UI code for\n+   parametrizing your Barclamp. Note that no testing needs to take place during\n+   this phase (and doesn't make much sense either). This phase produces a rough\n+   draft that may have any number of errors but will already define your\n+   barclamp's basic architecture.\n+\n+2. Local Testing: in this phase you will debug and fix the rough draft from the\n+   first phase until the UI side of the barclamp works well and parametrizes\n+   the barclamp as expected. As a second step you will fill your chef recipe\n+   scaffold with life and develop your chef recipes to the point where they\n+   correctly deploy what they are supposed to deploy.\n+\n+3. Pull Request Testing: once you are finished with phase 2 to the point where\n+   the barclamp performs to your own satisfaction you submit a pull request for\n+   your barclamp against Crowbar's `master` branch. Now Crowbar's CI tests will\n+   run using the code from your pull request and Crowbar maintainers will\n+   review it. During this phase you may again have to fix a number of things\n+   and may well need to return to phase 2 for local testing of the changes you\n+   make (it is not recommended to rely on CI tests by themselves for extensive\n+   changes).\n+\n+Below we will describe these phases in detail.\n+\n+_Note: unless otherwise indicated, all paths from here on out are relative to\n+your_ `crowbar-openstack` _fork's root directory._\n+\n+### Basic implementation\n+\n+In this phase you will do a scaffold implementation of your barclamp. This\n+means that you will mostly focus on the Crowbar UI side of things and how it\n+ties into your barclamp's chef cookbook. This means that on the chef side\n+you'll only implement the structure of your chef cookbook, i.e. define the\n+parameters it takes and the roles it defines for the Crowbar UI to use.\n+\n+You should also create recipes and configuration templates in your cookbook\n+already, but you only need to flesh these out as far as you need them to be\n+clear about the parameters you'll need Crowbar to supply.\n+\n+Throughout this phase you can work on your local machine or on your development\n+Crowbar admin node, as you prefer, since the testing you can do in this phase is\n+limited to basic ruby syntax checks using `ruby -c`. Once you are finished with\n+this phase development will have to move to your Crowbar admin node, though.\n+\n+#### Chef Cookbook\n+\n+Location: `chef/cookbooks/`\n+\n+The best point to start barclamp development is the\n+chef cookbook. Each barclamp contains\n+one of these. The chef cookbook is your barclamp's core component. It contains\n+the code that will do the grunt work of deploying the services, configuration\n+files and runtime state handled by your barclamp. As a first step, create\n+`chef/cookbooks/` now.\n+\n+#### Cookbook metadata\n+\n+Locations:\n+\n+* `chef/cookbooks/mybarclamp/metadata.rb`\n+* `chef/cookbooks/mybarclamp/README.md`\n+\n+Before we begin with the cookbook proper we will need to take care of some\n+boilerplate files. The first of these is `metadata.rb`. It mostly contains\n+various descriptive information about your barclamps. Here's an example from\n+the Barbican barclamp (`chef/cookbooks/barbican/metadata.rb`):\n+\n+```\n+name \"barbican\"\n+maintainer \"Crowbar project\"\n+maintainer_email \"crowbar@googlegroups.com\"\n+license \"Apache 2.0\"\n+description \"Installs/Configures Barbican\"\n+long_description IO.read(File.join(File.dirname(__FILE__), \"README.md\"))\n+version \"0.1\"\n+\n+depends \"apache2\"\n+depends \"database\"\n+depends \"keystone\"\n+depends \"crowbar-openstack\"\n+depends \"crowbar-pacemaker\"\n+depends \"utils\"\n+```\n+\n+The contents of these fields are mostly free-form, hence you can copy the file\n+from another barclamp and simply adapt the `name` and `description` fields. The\n+one exception to that rule are the `depends` statements: these define which\n+other barclamps your own barclamp depends on because it uses their cookbooks'\n+contents. If, for instance, you use the Keystone barclamp's\n+`KeystoneHelper.keystone_settings()` method (which you should for OpenStack\n+barclamps), you'd add a `depends \"keystone\"` statement here.\n+\n+You may already have noticed the second piece of boilerplate: the README.md\n+file referenced by the `long_description` statement. This goes one goes into\n+`chef/cookbooks/` and describes your Chef cookbook as\n+verbosely or tersely as you like (please put at least one complete sentence in\n+there).\n+\n+#### Recipes\n+\n+Location: `chef/cookbooks/mybarclamp/recipes/*.rb`\n+\n+[Recipes](https://docs.chef.io/recipes.html) make up the bulk of your chef\n+cookbook's payload. These recipes are written in Ruby (hence they must have a\n+`.rb` extension) and are the smallest organization unit of a chef cookbook.\n+They consist of [resource](https://docs.chef.io/resource.html) definitions that\n+define various aspects of system state, such as a configuration file's content,\n+packages to install, or a service to enable and start. In this step the recipes\n+do not need to contain any code, yet. You primarily need to know which recipes\n+to define and what each recipe's purpose is at this stage.\n+\n+You are free to organize your recipes as you see fit. There are no\n+hard-and-fast rules for this since they mostly depend on the application your\n+barclamp deploys:\n+\n+For instance, if your application (like most OpenStack components) consists of\n+an API service and a RabbitMQ driven backend service, you might want to split\n+these into a `api.rb` and `backend.rb` service. Likewise, if you need an\n+agent to run on compute nodes, this might warrant a dedicated `agent.rb`\n+recipe. If you want to use Crowbar's high availability features, it might also\n+make sense to have a dedicated `ha.rb` recipe for users deploying it in a\n+highly available manner.\n+\n+On a more general note, feel free to take a look at the other cookbooks'\n+recipes in `chef/cookbooks/` for inspiration. We have barclamps for a range of\n+OpenStack services already and one of these might just match your own\n+application's architecture perfectly.\n+\n+#### Roles\n+\n+Locations:\n+\n+* Role recipes: `chef/cookbooks/mybarclamp/recipes`\n+* Role definitions: `chef/roles/role_mybarclamp_*.rb`\n+\n+Once you've got a set of chef recipes, you'll need to organize them into roles.\n+A role aggregates one or more chef recipes and deploys all of its component\n+chef recipes. Crowbar can assign roles to one or more nodes (subject to\n+constraints defined in the Crowbar application). In the HA case it can assign\n+roles to one or more clusters of roles. It pays to spend some time on getting\n+the roles you need right from the start because you will need to modify a lot\n+of code to add new roles or remove existing ones later. Roles vary in scope\n+from deploying just a single service (such as a logging agent) to deploying\n+multiple related services (such as all of an OpenStack service's controller\n+side daemons). All roles' unifying feature is that they are a container for\n+something that gets deployed to a certain type of machine which may among other\n+things be:\n+\n+* \"Any machine orchestrated by Crowbar\"\n+\n+* \"Any OpenStack controller node\"\n+\n+* \"Any OpenStack compute node\"\n+\n+* \"Any Node in the Ceph Cluster\"\n+\n+* \"Any Ceph OSD node\"\n+\n+We have a limited range of node types in Crowbar to define types of roles,\n+namely, these are the following:\n+\n+* `compute`\n+\n+* `controller`\n+\n+* `network`\n+\n+* `storage`\n+\n+If you need a dedicated node type for your barclamp you will have to add it to\n+[crowbar-core](https://github.com/crowbar/crowbar-core) in the following\n+places:\n+\n+* [`node_wall_list()` in `crowbar_framework/app/helpers/nodes_helper.rb`](https://github.com/crowbar/crowbar-core/blob/8a6f1c2c44717e3ceb066695312fef494e410f94/crowbar_framework/app/helpers/nodes_helper.rb#L361)\n+* [`role()` in `bin/crowbar_machines`](https://github.com/crowbar/crowbar-core/blob/8a6f1c2c44717e3ceb066695312fef494e410f94/bin/crowbar_machines#L358)\n+* [The list entry mappings in `crowbar_framework/config/locales/crowbar/en.yml`](https://github.com/crowbar/crowbar-core/blob/8a6f1c2c44717e3ceb066695312fef494e410f94/bin/crowbar_machines#L358)\n+\n+Roles are defined in two places in the chef cookbook. First, you create a _role\n+recipe_ in the `chef/cookbooks/<your_barclamp_name>/recipes` directory. Our\n+naming convention for role recipes is as follows:\n+\n+```\n+role_<barclamp name>_<role description>.rb\n+```\n+\n+`<role description>` may contain letters, numbers and underscore (`_`)\n+characters. Assuming you've got two separate role recipes, one for the compute\n+nodes and one for OpenStack controller nodes, you might end up with something\n+like this:\n+\n+* `chef/cookbooks/mybarclamp/role_mybarclamp_compute.rb`\n+* `chef/cookbooks/mybarclamp/role_mybarclamp_controller.rb`\n+\n+This role recipe contains one or more `include_recipe` statements to pull in\n+component recipes and a validity check that asks the Crowbar application\n+whether the role is valid for the current node. Here's an example from the\n+Barbican barclamp (`cookbooks/barbican/recipes/role_barbican_controller.rb`):\n+\n+```\n+if CrowbarRoleRecipe.node_state_valid_for_role?(node, \"barbican\", \"barbican-controller\")\n+  include_recipe \"barbican::api\"\n+  include_recipe \"barbican::common\"\n+  include_recipe \"barbican::worker\"\n+  include_recipe \"barbican::keystone-listener\"\n+  include_recipe \"barbican::ha\"\n+end\n+```\n+\n+Note how the recipes are qualified with the cookbook name (\"barbican\") in this\n+case and omit the `.rb` extension from their actual file name.\n+\n+Once all role recipes are created, you can create the corresponding role\n+definitions in the `chef/roles` directory. Role definitions are snippets of\n+ruby code and must have a `.rb` definitions. Our naming convention for role\n+definitions is as follows:\n+\n+```\n+<barclamp name>-<role description>\n+```\n+\n+where `<role description>` is the role description from the role recipe with\n+underscores (`_`) substituted by dashes (`-`). This will be the role's\n+canonical name from this point onward.\n+\n+A role definition looks as follows (example from `roles/barbican-controller.rb`):\n+\n+```\n+name \"barbican-controller\"\n+description \"Barbican Controller Role\"\n+run_list(\"recipe[barbican::role_barbican_controller]\")\n+default_attributes\n+override_attributes\n+```\n+\n+Most of this is boilerplate, with two notable exceptions:\n+\n+1. `name` defines the role's canonical name as described above\n+2. `run_list` contains a reference to the role's underlying role recipe.\n+\n+Create role definitions for all of your roles now and proceed to the next\n+section.\n+\n+#### Data Bag\n+\n+Locations:\n+\n+* Data bag schema: `chef/data_bags/crowbar/template-mybarclamp.schema`\n+* Default data bag: `chef/data_bags/crowbar/template-mybarclamp.json`\n+* Migrations: `chef/data_bags/crowbar/migrate/mybarclamp/*.rb`\n+\n+Now that you have your recipes and roles defined you should also know what\n+configuration settings are static (e.g. best practice settings you want to\n+hardwire as enabled or disabled) and what needs to go into your chef cookbooks\n+as parameters that can vary for each local setup (e.g. user names and passwords\n+or the OpenStack identity URL). Everything you want to be configurable will\n+need to go into the `data bag`.\n+\n+For a newly created Barclamp the data bag consists of a _data bag schema_\n+(`chef/data_bags/crowbar/template-mybarclamp.schema`) and a\n+_default data bag_ (`chef/data_bags/crowbar/template-mybarclamp.json`). Both\n+are in JSON format. The _data bag schema_ describes how your data bag is\n+structured and imposes constraints such as data type and permissible values on\n+individual fields (much like an XML schema). It is used to validate your\n+barclamp's parameters, especially when the user edits its parameters directly\n+as JSON, either in the Web UI's raw mode or through the Crowbar command line\n+client.\n+\n+The _default data bag_ contains default settings for these parameters where\n+defaults can reasonably be provided. There are some special cases such as user\n+names and passwords that are initially left blank and will be filled in by the\n+Crowbar web UI when the barclamp is activated, so feel free to leave fields\n+blank if you plan on adding code to the Crowbar web UI to populate them or make\n+use of existing web UI.\n+\n+When fields are added to or removed from an existing barclamp's data bag, you\n+will need to add another component to the data bag: a _migration_. This is\n+essentially a database migration for the Crowbar web UI since data bags map\n+directly to a table in the Crowbar web UI's database. To keep that database\n+in sync with the data bag you will need to provide migrations to update that\n+database table if you make changes to an existing barclamp's data bag.\n+\n+In the following sections we will explain the data bag's components in detail.\n+We will start with the default data bag rather than the data bag schema because\n+it is easier to fill that with content first, and then produce a schema to match.\n+\n+##### Default Data Bag\n+\n+* Location: `chef/data_bags/crowbar/template-mybarclamp.json`\n+\n+First of all, copy an existing default data bag to\n+`chef/data_bags/crowbar/template-mybarclamp.json`. You will modify\n+this data bag to contain your own barclamp's parameters. This is easier and far\n+less error prone than writing a default data bag from scratch. Throughout this\n+section I will assume you used the\n+[Barbican barclamp's default data bag](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.json)\n+and link to it when we discuss the changes you need to make.\n+\n+First of all, replace all occurrences of `barbican` by `` and adjust\n+the top-level `description field`. Your default data bag's first few lines\n+should now look something like this:\n+\n+```\n+{\n+  \"id\": \"template-\",\n+  \"description\": \"The Foobar as a Service Component of OpenStack\",\n+  \"attributes\": {\n+      \"\" : {\n+```\n+\n+The corresponding section of the Barbican default data bag begins\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.json#L1) and ends\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.json#L29).\n+\n+With these preliminaries out of the way you can now add all the parameters you\n+need under `attributes[\"\"]` and remove existing parameters left over\n+from the default data bag you copied. You should leave the following fields in\n+place, since the Crowbar UI will automatically populate them for barclamps in\n+`crowbar-openstack` and/or because some chef side helper functions will use\n+them:\n+\n+* `db`\n+* `database_instance`\n+* `keystone_instance`\n+* `rabbitmq_instance`\n+* `user`\n+* `group`\n+* `service_password`\n+* `service_user`\n+\n+When you are done `attributes[\"\"]` should look something like this:\n+\n+```\n+     \"\" : {\n+        \"api\" : {\n+           \"bind_host\" : \"*\",\n+           \"bind_port\" : 12345,\n+           \"logfile\" : \"/var/log/myservice/myservice-api.log\"\n+        },\n+        \"backend\" : {\n+            db_plugin = \"postgres\",\n+            enable_foo = true,\n+            foobar_threshhold = 255\n+        },\n+        \"db\" : {\n+           \"database\" : \"myservice\",\n+           \"password\" : \"\",\n+           \"user\" : \"myservice\"\n+        },\n+        \"debug\" : false,\n+        \"group\" : \"\",\n+        \"user\" : \"\",\n+        \"database_instance\": \"none\",\n+        \"keystone_instance\": \"none\",\n+        \"rabbitmq_instance\": \"none\",\n+        \"service_password\": \"none\",\n+        \"service_user\": \"myservice\"\n+     }\n+```\n+\n+The corresponding section of the Barbican default data bag begins\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.json#L5) and ends\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.json#L29).\n+\n+Once you have added your parameters you will need to adjust\n+`deployment[\"\"]`. This section provides meta data about your chef\n+cookbook to the Crowbar web UI. The following fields need to be changed in\n+there:\n+\n+* `schema-revision`: This field contains the data bag's schema revision. For a\n+   new barclamp it starts with the `crowbar-core` `master` branch's current\n+   base revision (at the time of this writing this was `100` but it increases\n+   with every new stable Crowbar release. Please ask a Crowbar developer about\n+   the current value). Whenever the data bag is changed, this revision is\n+   incremented by `1` and a corresponding migration is added to the\n+   `chef/data_bags/crowbar/migrate/mybarclamp/` directory. Since you are\n+   writing a new barclamp, set this to  the current `master` branch's base\n+   revision.\n+\n+* `element_states`: this field contains a mapping from your chef roles to a\n+  list of valid states for each role. Typically these states are `readying`,\n+  `ready` and `applying` for every role, so just use these values for every\n+  role's list.\n+\n+* `element_order`: this field is a list of single-element lists and defines the\n+  order in which your barclamp's roles should be applied. Each single-element\n+  list contains a role name. Whichever role occurs first in this list must have\n+  been applied successfully to all nodes it is assigned to before the roles\n+  further down are applied to their respective nodes. In other words, this\n+  controls global execution order of your roles across all nodes orchestrated\n+  by Crowbar. All of your roles should be mentioned here.\n+\n+* `element_run_list_order`: this field governs the priority of your roles in\n+  relation to the other roles assigned to any given node your own role is\n+  assigned to. In other words, this governs local execution order of all roles\n+  (i.e. not just yours) assigned to a given node. All of your roles should be\n+  assigned a priority here.\n+\n+When you are done `deployment[\"\"]` should look something like this\n+(this example assumes you have defined a `mybarclamp-compute` and a\n+`mybarclamp-controller` role):\n+\n+```\n+  \"deployment\": {\n+    \"\" : {\n+      \"crowbar-revision\": 0,\n+      \"crowbar-applied\": false,\n+      \"schema-revision\": 100,\n+      \"element_states\": {\n+        \"mybarclamp-controller\": [ \"readying\", \"ready\", \"applying\" ],\n+        \"mybarclamp-compute\": [ \"readying\", \"ready\", \"applying\" ]\n+      },\n+      \"elements\": {},\n+      \"element_order\": [\n+        [ \"mybarclamp-controller\" ],\n+        [ \"mybarclamp-compute\" ]\n+      ],\n+      \"element_run_list_order\": {\n+        \"mybarclamp-controller\": 120,\n+        mybarclamp\"-compute\": 120\n+      },\n+      \"config\": {\n+        \"environment\": \"mybarclamp-base-config\",\n+        \"mode\": \"full\",\n+        \"transitions\": false,\n+        \"transition_list\": []\n+      }\n+    }\n+  }\n+```\n+\n+The corresponding section of the Barbican default data bag begins\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.json#L31) and ends\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.json#L53).\n+\n+##### Data bag schema\n+\n+* Location: `chef/data_bags/crowbar/template-mybarclamp.schema`\n+\n+With your default data bag finished you can now use the attributes and their\n+data types from the default data bag as a guide for definining its schema. The\n+schema holds constraints for every attribute in the default data bag, plus\n+for optional attributes that may not be set in the default data bag but can\n+nonetheless be added by the user.\n+\n+The schema contains a hierarchy of dictionaries that mirror the data structure\n+in `template-mybarclamp.json`. The keys in this hierarchy are the attribute\n+names from `template-mybarclamp.json` and the values are dictionaries of\n+constraints for the attribute in question. Such a dictionary of constraints can\n+have the following fields (these are only the most common ones):\n+\n+* `required`: a boolean value indicating whether this attribute must be present\n+  (`true`) or not (`false`).\n+\n+* `type`: the attribute's data type. Valid values are `str`, `bool`, `int`,\n+  `map` (key/value pairs) and `sequence` (lists of values).\n+\n+* `pattern` [only for attributes of type `str`]: a regular expression the attribute\n+  must match.\n+\n+* `mapping` and `sequence` [only for attributes of type `map` and `sequence`,\n+  respectively]: these contain lists or dictionaries that can be used to impose\n+  constraints on a list or dictionary value's contents. This is mainly used to\n+  cover the whole tree of nested data dictionaries in the data bag, but it can\n+  also be used to impose restrictions on an individual list or dictionary\n+  attribute at the lowest level.\n+\n+Armed with this knowledge we can now create a schema that matches the default\n+data bag from the previous section. Similar to the previous section we will\n+start out with a copy of the Barbican barclamp's schema\n+(`template-barbican.schema`), which we'll copy to `template-mybarclamp.json`.\n+\n+Again we will first globally search and replace `barbican` by `` and\n+then go through it section by section, starting with the top-level fields. The\n+only thing that needs adjusting at the top level is the pattern constraint for\n+the `id` field, which our global search and replace will already have taken\n+care of, leaving us with a top-level section that should look something like this:\n+\n+```\n+{\n+  \"required\": true,\n+  \"type\": \"map\",\n+  \"mapping\": {\n+    \"id\": { \"type\": \"str\", \"required\": true, \"pattern\": \"/^-|^template-mybarclamp$/\" },\n+    \"description\": { \"required\": true, \"type\": \"str\" },\n+    \"attributes\": {\n+      \"required\": true,\n+      \"type\": \"map\",\n+      \"mapping\": {\n+        \"\": {\n+```\n+\n+The corresponding section from the Barbican data bag schema begins\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.schema#L1) and ends\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/chef/data_bags/crowbar/template-barbican.schema#L11).\n+\n+Next, we will need to adjust the constraints for `attributes[\"\"]`. The Crowbar\n+web UI is a Ruby on Rails application with a MVC (Model-View-Controller)\n+architecture so you will need to create the MVC architecture's three\n+components for your barclamp plus some glue and metadata. This section will\n+take you through all of these steps.\n+\n+##### Barclamp Metadata\n+\n+Location: `mybarclamp.yml`\n+\n+Much like the chef cookbook, the Crowbar application needs some metadata about\n+your barclamp. This is a YAML formatted file residing in the top level\n+directory. It's filename consists of your barclamp's name plus the extension\n+`.yml`. Below you will find a commented example:\n+\n+```\n+barclamp:\n+  # Your barclamp's name as it appears in file names.\n+  name: 'mybarclamp'\n+  # Your barclamp's name as it will be displayed in the Crowbar web UI.\n+  display: 'Mybarclamp'\n+  # Here we usually have the service name and a short description of the\n+  # Service the barclamp deploys for OpenStack barclamps.\n+  description: 'OpenStack Myservice: Provides Foo and Bar'\n+  # Just leave this as it is.\n+  version: 0\n+  # This is a repeat of the requirements list from the chef cookbook.\n+  requires:\n+    - '@crowbar'\n+    - 'database'\n+    - 'rabbitmq'\n+    - 'keystone'\n+  # This denotes the Barclamp collection this barclamp belongs to.\n+  member:\n+    - 'openstack'\n+\n+crowbar:\n+  # The barclamp's priority in the crowbar UI (lower number means higher priority)\n+  run_order: 98\n+  # The barclamp's priority in the ordering of chef cookbooks (lower number\n+  # means higher priority)\n+  chef_order: 98\n+\n+  # FIXME: The following appear to be unused. They are neither loaded in\n+  # catalog() in barclamp_mgmt_lib.rb, nor do they have a corresponding method\n+  # in the BarclampCatalog class). You might be able to omit them.\n+  order: 98\n+  proposal_schema_version: 3\n+  layout: 1\n+```\n+\n+##### Command Line Executable\n+\n+Location: `bin/crowbar_mybarclamp`\n+\n+This executable is used by the crowbar command line client and is mostly a\n+stub. Just copy this from another barclamp and change the `@barclamp` variable\n+to your barclamp's name, which will leave you with something like this for our\n+example barclamp:\n+\n+```\n+require File.join(File.expand_path(File.dirname(__FILE__)), \"barclamp_lib\")\n+@barclamp = \"mybarclamp\"\n+@timeout = 3600\n+\n+main\n+```\n+\n+##### Minimal Edit View\n+\n+Locations:\n+\n+* `crowbar_framework/app/views/barclamp/mybarclamp/_edit_attributes.html.haml`\n+* `crowbar_framework/config/locales/mybarclamp/en.yml`\n+\n+Since we are building a minimal barclamp without much in the way of UI\n+components, we can stick to the bare minimum for the \"View\" part of MVC. We\n+only need its components to be present without much content.\n+\n+First of all we create the directories\n+\n+* `crowbar_framework/app/views/barclamp/mybarclamp`\n+* `crowbar_framework/config/locales/mybarclamp`\n+\n+Next, we create the basic view in\n+`crowbar_framework/app/views/barclamp/mybarclamp/_edit_attributes.html.haml`.\n+Since all we need is an edit field for raw parameters in JSON format, we can\n+get away with only having the following snippet of code in there:\n+\n+```\n+= attributes_for @proposal do\n+  .panel-sub\n+      = header show_raw_deployment?, true\n+```\n+\n+The corresponding file from the Barbican barclamp is\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/views/barclamp/barbican/_edit_attributes.html.haml).\n+\n+\n+What remains for the view is a minimum English language locale in\n+`crowbar_framework/config/locales/mybarclamp/en.yml`. Since we do not display\n+any form fields this can be fairly short. To give you an idea of how an actual\n+form field's description would look we included one in our example below (note\n+that this would require an entry in\n+`crowbar_framework/app/views/barclamp/mybarclamp/_edit_attributes.html.haml` to\n+actually show up):\n+\n+```\n+en:\n+  barclamp:\n+    mybarclamp:\n+      edit_attributes:\n+        api_header: 'API Settings'\n+        api:\n+          bind_host: 'Address to listen on'\n+\n+```\n+\n+The corresponding file from the Barbican barclamp is\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/config/locales/barbican/en.yml).\n+It contains field descriptions for a bunch of fields already, since this will\n+make it easier to add UI elements later. Feel free to prepare your own barclamp\n+in a similar manner.\n+\n+##### Minimal Model Class\n+\n+Location: `crowbar_framework/app/models/mybarclamp_service.rb`\n+\n+With the view in place we need a model class to pre-process the proposal\n+information and enforce constraints that cannot be expressed using the\n+declarative means available in `template-mybarclamp.schema`, i.e. constraints\n+that need program logic to check. Just like in the data bag sections we will\n+again use the Barbican barclamp's proposal as an example and modify it bit by\n+bit, so as a first step, copy `crowbar_framework/app/models/barbican_service.rb`\n+to `crowbar_framework/app/models/mybarclamp_service.rb` and globally replace\n+`barbican` by `mybarclamp` and `Barbican` by `Mybarclamp`, respectively.\n+\n+This will leave us with with something like this in the first few lines:\n+\n+```\n+class MybarclampService < PacemakerServiceObject\n+  def initialize(thelogger)\n+    @bc_name = \"mybarclamp\"\n+    @logger = thelogger\n+  end\n+```\n+\n+The corresponding lines from the Barbican barclamp's controller begin\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/models/barbican_service.rb#L17) and end\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/models/barbican_service.rb#L21).\n+\n+The next thing we need to change is the `role_constraints` method. This method\n+tells Crowbar on how many nodes the service can be deployed, what types of node\n+are valid and on which platform it may run. All the roles you defined in the\n+chef sections need to be accounted for by this method. This means that we will\n+need to add an entry for the `mybarclamp-compute` role. Below you will see a\n+commented example:\n+\n+```\n+    def role_constraints\n+      {\n+        \"mybarclamp-controller\" => {\n+          \"unique\" => false,   # Set to `true` if this can only be deployed to one node.\n+          \"count\" => 3,        # Number of allowed instances ('-1' means infinite)\n+          \"cluster\" => true,   # Whether this role can be clustered (important for HA later on).\n+          \"admin\" => false,    # Whether this role can be assigned to the Crowbar admin node.\n+          \"exclude_platform\" => {\n+            \"suse\" => \"< 12.1\",  # Do not deploy on SUSE version 12.1 and lower.\n+            \"windows\" => \"/.*/\"  # Do not deploy on any Windows platforms.\n+          }\n+        },\n+         \"mybarclamp-compute\" => {\n+          \"unique\" => false,\n+          \"count\" => -1,   # Can be deployed to an arbitrary number of Compute nodes\n+          \"cluster\" => true,\n+          \"admin\" => false,\n+          \"exclude_platform\" => {\n+            \"suse\" => \"< 12.1\",\n+            \"windows\" => \"/.*/\"\n+          }\n+        }\n+      end\n+    end\n+```\n+The corresponding lines from the Barbican barclamp's controller begin\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/models/barbican_service.rb#L29) and end\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/models/barbican_service.rb#L43).\n+\n+Depending on your barclamp's dependencies you may need to adjust the list of\n+dependencies returned by the `proposal_dependencies()` method. Since `mybarclamp`\n+has the same dependencies as the Barbican barclamp, that is not neccessary in\n+our case. You will find the Barbican barclamp's relevant line\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/models/barbican_service.rb#L47).\n+\n+Now we come to the method where we'll usually have most of our changes:\n+`create_proposal()`. This method creates the run-time proposal in Crowbar's\n+data base that contains the parameters as they will be passed to your chef\n+recipe. It has two main purposes:\n+\n+1. Allocate roles to nodes automatically as they make sense (i.e. assign\n+   `mybarclamp-controller` to a one or more designated controller nodes and assign\n+   `mybarclamp-compute` to all compute nodes).\n+\n+2. Populate fields that you left blank in your default data bag (e.g. generate\n+   random passwords).\n+\n+After adaption our method might look something like this:\n+\n+```\n+  def create_proposal\n+    @logger.debug(\"Mybarclamp create_proposal: entering\")\n+    base = super\n+\n+    nodes = NodeObject.all\n+    server_nodes = nodes.select { |n| n.intended_role == \"controller\" }\n+    server_nodes = [nodes.first] if server_nodes.empty?\n+\n+    compute_nodes = nodes.select { |n| n.intended_role == \"compute\" }\n+    compute_nodes = [nodes.first] if compute_nodes.empty?\n+\n+    base[\"deployment\"][@bc_name][\"elements\"] = {\n+      \"mybarclamp-controller\" => [server_nodes.first.name]\n+    } unless server_nodes.nil?\n+\n+    base[\"deployment\"][@bc_name][\"elements\"] = {\n+      \"mybarclamp-compute\" => compute_nodes\n+    } unless compute_nodes.nil?\n+\n+\n+    base[\"attributes\"][@bc_name][\"database_instance\"] =\n+      find_dep_proposal(\"database\")\n+    base[\"attributes\"][@bc_name][\"rabbitmq_instance\"] =\n+      find_dep_proposal(\"rabbitmq\")\n+    base[\"attributes\"][@bc_name][\"keystone_instance\"] =\n+      find_dep_proposal(\"keystone\")\n+    base[\"attributes\"][@bc_name][\"service_password\"] = random_password\n+    base[\"attributes\"][@bc_name][:db][:password] = random_password\n+\n+    @logger.debug(\"Mybarclamp create_proposal: exiting\")\n+    base\n+  end\n+```\n+\n+The Barbican barclamp's `create_proposal` method starts\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/models/barbican_service.rb#L57) \n+and ends\n+[here](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/models/barbican_service.rb#L57).\n+\n+##### Minimal Controller Class\n+\n+Location: `crowbar_framework/app/controllers/mybarclamp_controller.rb`\n+\n+The final thing we will need to the Crowbar application is a controller. This\n+goes into `crowbar_framework/app/controllers/mybarclamp_controller.rb` and is\n+very straightforward since we only need stub code in there (we normally don't\n+do much in Barclamp controllers). This will suffice:\n+\n+```\n+class MybarclampController < BarclampController\n+  # Controller for Mybarclamp barclamp\n+\n+  protected\n+\n+  def initialize_service\n+    @service_object = MybarclampService.new logger\n+  end\n+end\n+```\n+\n+As you can see, Barbican has the same [piece of boilerplate\n+code](https://github.com/crowbar/crowbar-openstack/blob/8bebf8a379ebea8ef462ad49746dda6d36a3c46d/crowbar_framework/app/controllers/barbican_controller.rb).\n+\n+### Local Testing\n+\n+*Note: Whenever you make changes during this phase, make them to the\n+`crowbar-openstack` git checkout in `/root/crowbar-openstack`, commit them, and\n+then run `reposync_crowbar` to make them known to Crowbar.\n+\n+In the previous sections you created a scaffold for your barclamp, possibly\n+with some pieces of implementation as well. Before you create a pull request\n+for that code you should test it locally now. While there is a school of\n+thought that advocates creating pull requests as soon as the first commit is\n+in, this approach is problematic for Crowbar development: one of our CI tests\n+is the [Hound](https://houndci.com/) code style checker, which will generate a\n+notification for every single violation it detects. Since each commit is likely\n+to contain multiple such violations, it is better to get your code working\n+first and then present it to Hound at that point. If you test and debug your\n+barclamp iteratively while the pull request is already open you will likely\n+generate a lot of needless spam (e.g. for code that may end up eventually\n+getting removed entirely in the course of development)\n+\n+For testing your code locally you will need the development environment\n+described in the [Prerequisites](#prerequisites) section. So build such an\n+environment now (if you haven't already done so) and make sure you've got an\n+up-to-date checkout of your `crowbar-openstack` fork in\n+`/root/openstack-crowbar` on the development environment's Crowbar admin node.\n+This is also a good time to create a LVM snapshot or other backup of your\n+Crowbar admin node in case you break it in the course of debugging.\n+\n+With your Crowbar node backed up and a checkout of your `crowbar-openstack`\n+fork in place, apply your code to the running Crowbar instance using the\n+commands from the [Preparations for Crowbar Node](#preparations-for-crowbar-node)\n+section:\n+\n+```\n+reposync_crowbar   # Fix any merge conflicts reported by this command before proceeding\n+```\n+\n+If you navigate to your Crowbar Admin Node's web interface now and select\n+*OpenStack* from the barclamp menu, you should see your new barclamp listed. If\n+it doesn't appear or you got an error message from the `sync_crowbar` command\n+you will now have to start debugging. Common errors at this stage are mostly\n+bad file names, or problems in `mybarclamp.yml`.\n+","path":"doc/barclamp.md","position":1119,"original_position":1119,"commit_id":"3585329c3f28990b0003fb1a8c4381aa40513239","original_commit_id":"3585329c3f28990b0003fb1a8c4381aa40513239","user":{"login":"aspiers","id":100738,"avatar_url":"https://avatars.githubusercontent.com/u/100738?v=3","gravatar_id":"","url":"https://api.github.com/users/aspiers","html_url":"https://github.com/aspiers","followers_url":"https://api.github.com/users/aspiers/followers","following_url":"https://api.github.com/users/aspiers/following{/other_user}","gists_url":"https://api.github.com/users/aspiers/gists{/gist_id}","starred_url":"https://api.github.com/users/aspiers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aspiers/subscriptions","organizations_url":"https://api.github.com/users/aspiers/orgs","repos_url":"https://api.github.com/users/aspiers/repos","events_url":"https://api.github.com/users/aspiers/events{/privacy}","received_events_url":"https://api.github.com/users/aspiers/received_events","type":"User","site_admin":false},"body":"I think there's a missing step here, or somewhere around here.  You cover local testing via some mechanism for quickly transferring changes from a git repo into `/opt/dell` (and you suggest `reposync_crowbar` which is one possibility but I have also mentioned others).  This speed of transferral is of course important to keep the hack/test cycle efficient.  So far so good.  But when the changes are stabilising, it then makes sense to sacrifice some development speed for higher quality testing, and in general it's more reliable to test by deploying rpms, which get the test deployment closer to a real world deployment.\r\n\r\nThe downside of testing via rpm deployments is that it takes a lot longer to build an rpm from a git topic branch and then deploy it to a local test cloud.  But that's exactly why [I automated this process down to a one-line command!](https://github.com/openSUSE/pack-tools/tree/master/contrib/BS-pkg-testing)  So I would like to see that also covered in this guide.","created_at":"2017-01-25T13:44:38Z","updated_at":"2017-01-25T13:45:10Z","html_url":"https://github.com/crowbar/crowbar/pull/2294#discussion_r97779741","pull_request_url":"https://api.github.com/repos/crowbar/crowbar/pulls/2294","_links":{"self":{"href":"https://api.github.com/repos/crowbar/crowbar/pulls/comments/97779741"},"html":{"href":"https://github.com/crowbar/crowbar/pull/2294#discussion_r97779741"},"pull_request":{"href":"https://api.github.com/repos/crowbar/crowbar/pulls/2294"}}},"pull_request":{"url":"https://api.github.com/repos/crowbar/crowbar/pulls/2294","id":98704643,"html_url":"https://github.com/crowbar/crowbar/pull/2294","diff_url":"https://github.com/crowbar/crowbar/pull/2294.diff","patch_url":"https://github.com/crowbar/crowbar/pull/2294.patch","issue_url":"https://api.github.com/repos/crowbar/crowbar/issues/2294","number":2294,"state":"open","locked":false,"title":"Barclamp guide","user":{"login":"jgrassler","id":5631641,"avatar_url":"https://avatars.githubusercontent.com/u/5631641?v=3","gravatar_id":"","url":"https://api.github.com/users/jgrassler","html_url":"https://github.com/jgrassler","followers_url":"https://api.github.com/users/jgrassler/followers","following_url":"https://api.github.com/users/jgrassler/following{/other_user}","gists_url":"https://api.github.com/users/jgrassler/gists{/gist_id}","starred_url":"https://api.github.com/users/jgrassler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgrassler/subscriptions","organizations_url":"https://api.github.com/users/jgrassler/orgs","repos_url":"https://api.github.com/users/jgrassler/repos","events_url":"https://api.github.com/users/jgrassler/events{/privacy}","received_events_url":"https://api.github.com/users/jgrassler/received_events","type":"User","site_admin":false},"body":"This pull requests adds a step-to-step guide for writing a new barclamp.","created_at":"2016-12-20T08:23:31Z","updated_at":"2017-01-25T13:45:10Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1b7138b3cf90bbd4772c3b191cec0b5fb77c8862","assignee":null,"assignees":[],"milestone":{"url":"https://api.github.com/repos/crowbar/crowbar/milestones/9","html_url":"https://github.com/crowbar/crowbar/milestone/9","labels_url":"https://api.github.com/repos/crowbar/crowbar/milestones/9/labels","id":2225215,"number":9,"title":"Cloud 7 Update1","description":null,"creator":{"login":"dirkmueller","id":1029152,"avatar_url":"https://avatars.githubusercontent.com/u/1029152?v=3","gravatar_id":"","url":"https://api.github.com/users/dirkmueller","html_url":"https://github.com/dirkmueller","followers_url":"https://api.github.com/users/dirkmueller/followers","following_url":"https://api.github.com/users/dirkmueller/following{/other_user}","gists_url":"https://api.github.com/users/dirkmueller/gists{/gist_id}","starred_url":"https://api.github.com/users/dirkmueller/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dirkmueller/subscriptions","organizations_url":"https://api.github.com/users/dirkmueller/orgs","repos_url":"https://api.github.com/users/dirkmueller/repos","events_url":"https://api.github.com/users/dirkmueller/events{/privacy}","received_events_url":"https://api.github.com/users/dirkmueller/received_events","type":"User","site_admin":false},"open_issues":1,"closed_issues":2,"state":"open","created_at":"2017-01-03T13:52:14Z","updated_at":"2017-01-04T13:44:59Z","due_on":"2017-03-12T08:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/crowbar/crowbar/pulls/2294/commits","review_comments_url":"https://api.github.com/repos/crowbar/crowbar/pulls/2294/comments","review_comment_url":"https://api.github.com/repos/crowbar/crowbar/pulls/comments{/number}","comments_url":"https://api.github.com/repos/crowbar/crowbar/issues/2294/comments","statuses_url":"https://api.github.com/repos/crowbar/crowbar/statuses/3585329c3f28990b0003fb1a8c4381aa40513239","head":{"label":"jgrassler:barclamp-guide","ref":"barclamp-guide","sha":"3585329c3f28990b0003fb1a8c4381aa40513239","user":{"login":"jgrassler","id":5631641,"avatar_url":"https://avatars.githubusercontent.com/u/5631641?v=3","gravatar_id":"","url":"https://api.github.com/users/jgrassler","html_url":"https://github.com/jgrassler","followers_url":"https://api.github.com/users/jgrassler/followers","following_url":"https://api.github.com/users/jgrassler/following{/other_user}","gists_url":"https://api.github.com/users/jgrassler/gists{/gist_id}","starred_url":"https://api.github.com/users/jgrassler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgrassler/subscriptions","organizations_url":"https://api.github.com/users/jgrassler/orgs","repos_url":"https://api.github.com/users/jgrassler/repos","events_url":"https://api.github.com/users/jgrassler/events{/privacy}","received_events_url":"https://api.github.com/users/jgrassler/received_events","type":"User","site_admin":false},"repo":{"id":63335092,"name":"crowbar","full_name":"jgrassler/crowbar","owner":{"login":"jgrassler","id":5631641,"avatar_url":"https://avatars.githubusercontent.com/u/5631641?v=3","gravatar_id":"","url":"https://api.github.com/users/jgrassler","html_url":"https://github.com/jgrassler","followers_url":"https://api.github.com/users/jgrassler/followers","following_url":"https://api.github.com/users/jgrassler/following{/other_user}","gists_url":"https://api.github.com/users/jgrassler/gists{/gist_id}","starred_url":"https://api.github.com/users/jgrassler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jgrassler/subscriptions","organizations_url":"https://api.github.com/users/jgrassler/orgs","repos_url":"https://api.github.com/users/jgrassler/repos","events_url":"https://api.github.com/users/jgrassler/events{/privacy}","received_events_url":"https://api.github.com/users/jgrassler/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jgrassler/crowbar","description":"Cloud Operations Platform","fork":true,"url":"https://api.github.com/repos/jgrassler/crowbar","forks_url":"https://api.github.com/repos/jgrassler/crowbar/forks","keys_url":"https://api.github.com/repos/jgrassler/crowbar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jgrassler/crowbar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jgrassler/crowbar/teams","hooks_url":"https://api.github.com/repos/jgrassler/crowbar/hooks","issue_events_url":"https://api.github.com/repos/jgrassler/crowbar/issues/events{/number}","events_url":"https://api.github.com/repos/jgrassler/crowbar/events","assignees_url":"https://api.github.com/repos/jgrassler/crowbar/assignees{/user}","branches_url":"https://api.github.com/repos/jgrassler/crowbar/branches{/branch}","tags_url":"https://api.github.com/repos/jgrassler/crowbar/tags","blobs_url":"https://api.github.com/repos/jgrassler/crowbar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jgrassler/crowbar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jgrassler/crowbar/git/refs{/sha}","trees_url":"https://api.github.com/repos/jgrassler/crowbar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jgrassler/crowbar/statuses/{sha}","languages_url":"https://api.github.com/repos/jgrassler/crowbar/languages","stargazers_url":"https://api.github.com/repos/jgrassler/crowbar/stargazers","contributors_url":"https://api.github.com/repos/jgrassler/crowbar/contributors","subscribers_url":"https://api.github.com/repos/jgrassler/crowbar/subscribers","subscription_url":"https://api.github.com/repos/jgrassler/crowbar/subscription","commits_url":"https://api.github.com/repos/jgrassler/crowbar/commits{/sha}","git_commits_url":"https://api.github.com/repos/jgrassler/crowbar/git/commits{/sha}","comments_url":"https://api.github.com/repos/jgrassler/crowbar/comments{/number}","issue_comment_url":"https://api.github.com/repos/jgrassler/crowbar/issues/comments{/number}","contents_url":"https://api.github.com/repos/jgrassler/crowbar/contents/{+path}","compare_url":"https://api.github.com/repos/jgrassler/crowbar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jgrassler/crowbar/merges","archive_url":"https://api.github.com/repos/jgrassler/crowbar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jgrassler/crowbar/downloads","issues_url":"https://api.github.com/repos/jgrassler/crowbar/issues{/number}","pulls_url":"https://api.github.com/repos/jgrassler/crowbar/pulls{/number}","milestones_url":"https://api.github.com/repos/jgrassler/crowbar/milestones{/number}","notifications_url":"https://api.github.com/repos/jgrassler/crowbar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jgrassler/crowbar/labels{/name}","releases_url":"https://api.github.com/repos/jgrassler/crowbar/releases{/id}","deployments_url":"https://api.github.com/repos/jgrassler/crowbar/deployments","created_at":"2016-07-14T12:34:18Z","updated_at":"2016-07-14T12:34:20Z","pushed_at":"2017-01-16T13:18:00Z","git_url":"git://github.com/jgrassler/crowbar.git","ssh_url":"git@github.com:jgrassler/crowbar.git","clone_url":"https://github.com/jgrassler/crowbar.git","svn_url":"https://github.com/jgrassler/crowbar","homepage":"https://github.com/crowbar/crowbar","size":30928,"stargazers_count":0,"watchers_count":0,"language":"Shell","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":0,"forks":1,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"crowbar:master","ref":"master","sha":"0d6ab6b510ff9ca96f162d00c50e7cf84ca4d3bc","user":{"login":"crowbar","id":2910205,"avatar_url":"https://avatars.githubusercontent.com/u/2910205?v=3","gravatar_id":"","url":"https://api.github.com/users/crowbar","html_url":"https://github.com/crowbar","followers_url":"https://api.github.com/users/crowbar/followers","following_url":"https://api.github.com/users/crowbar/following{/other_user}","gists_url":"https://api.github.com/users/crowbar/gists{/gist_id}","starred_url":"https://api.github.com/users/crowbar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crowbar/subscriptions","organizations_url":"https://api.github.com/users/crowbar/orgs","repos_url":"https://api.github.com/users/crowbar/repos","events_url":"https://api.github.com/users/crowbar/events{/privacy}","received_events_url":"https://api.github.com/users/crowbar/received_events","type":"Organization","site_admin":false},"repo":{"id":2040151,"name":"crowbar","full_name":"crowbar/crowbar","owner":{"login":"crowbar","id":2910205,"avatar_url":"https://avatars.githubusercontent.com/u/2910205?v=3","gravatar_id":"","url":"https://api.github.com/users/crowbar","html_url":"https://github.com/crowbar","followers_url":"https://api.github.com/users/crowbar/followers","following_url":"https://api.github.com/users/crowbar/following{/other_user}","gists_url":"https://api.github.com/users/crowbar/gists{/gist_id}","starred_url":"https://api.github.com/users/crowbar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crowbar/subscriptions","organizations_url":"https://api.github.com/users/crowbar/orgs","repos_url":"https://api.github.com/users/crowbar/repos","events_url":"https://api.github.com/users/crowbar/events{/privacy}","received_events_url":"https://api.github.com/users/crowbar/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/crowbar/crowbar","description":"Cloud Operations Platform","fork":false,"url":"https://api.github.com/repos/crowbar/crowbar","forks_url":"https://api.github.com/repos/crowbar/crowbar/forks","keys_url":"https://api.github.com/repos/crowbar/crowbar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/crowbar/crowbar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/crowbar/crowbar/teams","hooks_url":"https://api.github.com/repos/crowbar/crowbar/hooks","issue_events_url":"https://api.github.com/repos/crowbar/crowbar/issues/events{/number}","events_url":"https://api.github.com/repos/crowbar/crowbar/events","assignees_url":"https://api.github.com/repos/crowbar/crowbar/assignees{/user}","branches_url":"https://api.github.com/repos/crowbar/crowbar/branches{/branch}","tags_url":"https://api.github.com/repos/crowbar/crowbar/tags","blobs_url":"https://api.github.com/repos/crowbar/crowbar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/crowbar/crowbar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/crowbar/crowbar/git/refs{/sha}","trees_url":"https://api.github.com/repos/crowbar/crowbar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/crowbar/crowbar/statuses/{sha}","languages_url":"https://api.github.com/repos/crowbar/crowbar/languages","stargazers_url":"https://api.github.com/repos/crowbar/crowbar/stargazers","contributors_url":"https://api.github.com/repos/crowbar/crowbar/contributors","subscribers_url":"https://api.github.com/repos/crowbar/crowbar/subscribers","subscription_url":"https://api.github.com/repos/crowbar/crowbar/subscription","commits_url":"https://api.github.com/repos/crowbar/crowbar/commits{/sha}","git_commits_url":"https://api.github.com/repos/crowbar/crowbar/git/commits{/sha}","comments_url":"https://api.github.com/repos/crowbar/crowbar/comments{/number}","issue_comment_url":"https://api.github.com/repos/crowbar/crowbar/issues/comments{/number}","contents_url":"https://api.github.com/repos/crowbar/crowbar/contents/{+path}","compare_url":"https://api.github.com/repos/crowbar/crowbar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/crowbar/crowbar/merges","archive_url":"https://api.github.com/repos/crowbar/crowbar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/crowbar/crowbar/downloads","issues_url":"https://api.github.com/repos/crowbar/crowbar/issues{/number}","pulls_url":"https://api.github.com/repos/crowbar/crowbar/pulls{/number}","milestones_url":"https://api.github.com/repos/crowbar/crowbar/milestones{/number}","notifications_url":"https://api.github.com/repos/crowbar/crowbar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/crowbar/crowbar/labels{/name}","releases_url":"https://api.github.com/repos/crowbar/crowbar/releases{/id}","deployments_url":"https://api.github.com/repos/crowbar/crowbar/deployments","created_at":"2011-07-13T05:02:51Z","updated_at":"2017-01-18T11:48:09Z","pushed_at":"2017-01-17T09:54:52Z","git_url":"git://github.com/crowbar/crowbar.git","ssh_url":"git@github.com:crowbar/crowbar.git","clone_url":"https://github.com/crowbar/crowbar.git","svn_url":"https://github.com/crowbar/crowbar","homepage":"https://github.com/crowbar/crowbar","size":30874,"stargazers_count":715,"watchers_count":715,"language":"Shell","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":277,"mirror_url":null,"open_issues_count":51,"forks":277,"open_issues":51,"watchers":715,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/crowbar/crowbar/pulls/2294"},"html":{"href":"https://github.com/crowbar/crowbar/pull/2294"},"issue":{"href":"https://api.github.com/repos/crowbar/crowbar/issues/2294"},"comments":{"href":"https://api.github.com/repos/crowbar/crowbar/issues/2294/comments"},"review_comments":{"href":"https://api.github.com/repos/crowbar/crowbar/pulls/2294/comments"},"review_comment":{"href":"https://api.github.com/repos/crowbar/crowbar/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/crowbar/crowbar/pulls/2294/commits"},"statuses":{"href":"https://api.github.com/repos/crowbar/crowbar/statuses/3585329c3f28990b0003fb1a8c4381aa40513239"}}}}
{"actor":{"display_login":"aspiers"}}