berkshelf provides the same functionality as
librarian-chef in that it follows
the information provided in a berksfile
(almost equivalent of librarian-chef's
cheffile
) and loads all the required cookbooks. the main difference between
the two, aside from berkshelf's superior
integration with vagrant, is
that berkshelf has more awareness of its surrounding environment and is
integrated more smoothly within a cookbook.
berks install
follows the directives in your berksfile
to load cookbooks
(from community api, local system, or git repo). you can also group cookbooks
together (using group
blocks) and use this grouping at installation time (when
you perform berks install
), to exclude or include certain cookbooks using
options like --without
and --only
.
you can use the site
directive in your berksfile
to indicate a community
site api to be used by berkshelf. for using the opscode's newest community api
you can simply use :opscode
(instead of
http://cookbooks.opscode.com/api/v1/cookbooks
). cookbooks can also be loaded
from other sources using :path
(local) and :git
(and potentially :rel
)
options.
the convention has become to leave a berksfile
in the root of the cookbook,
even when there is no provisioning setup. the immediate use case for this is to
provide alternative (non-community) sources for specific dependencies. but also
to use the metadata
keyword to tell berkshelf that it should also load the
dependencies mentioned in the metadata.rb
file of the cookbook (this only
works if berksfile
is in the cookbook root).
berks, as opposed to librarian-chef, maintains some sort of state of its own by
installing cookbooks to its directory (stored in berkshelf_path
, by
default ~/.berkshelf/
). all cookbooks installed in this way can be
catalogued using berks shelf list
. although apparently you can get
berks install
to put the cookbooks in a
custom folder (relative to the directory where install is invoked berks install -p /path/to/cookbooks
). in the
latter case, berkshelf will leave a copy of all cookbooks it installs in the
path you specify, in addition to installing them, for further reuse, in its
directory.
furthermore, the customary way of using berkshelf is to allow it to install dependencies on the node as needed. so you provide the node with all your cookbooks and use berkshelf on the node to load all external dependencies before provisioning with chef.
berkshelf works easily with vagrant through a plugin (vagrant plugin install vagrant-berkshelf
). note that this does install a gem named
vagrant-berkshelf
, but installing the gem directly (without vagrant plugin install
would not let vagrant know about the plugin.
once the plugin is installed, vagrant by default calls berkshelf before
provisioning. if you want vagrant to not use the plugin you should
indicate so in the vagrantfile
by adding config.berkshelf.enabled = false
to
your vagrant.configure("2")
block. once you have done that, the plugin would
allow vagrant to access berkshelf's cookbook directory without the vagrantfile
having to contain a chef.cookbooks_path
directive (this attribute is, in fact,
hijacked by vagrant-berkshelf in
solo provisioning). all cookbooks that berkshelf has installed (in
~/.berkshelf/
) can be used, and any non-installed cookbooks indicated in the
berksfile
will be downloaded and available to the vm, as usual, at
/tmp/vagrant-chef-1/chef-solo-1/cookbooks/
.
Using Vagrant, Berkshelf, and Chef you can create a configured
virtual machine using only two configuration
files; all you need is a Berksfile
and a
Vagrantfile
.
Vagrant v1 refers to v1.0.x
and Vagrant v2 refers to
anything late, i.e v1.1+
. Furthermore, Vagrant v1 is provided as a Rubygem (soon to be
discontinuted) but as of v2, Vagrant is only provided as a system package. You
can install two Vagrants (using gem install
and apt-get install
), both of
which registering a linux command, and
confuse
yourself!
The Vagrantfile
here is written for Vagrant v2. To
rollback to Vagrant v1 apply the following to it:
- use
Vagrant.configure("1")
orVagrant::Config.run
- for port forwarding use
config.vm.forwarded\_port, guest: 80, host: 8080
config.vm.customize ["modifyvm", :id, "--memory", "512"]
instead of the provider specific block (config.vm.provider :virtualbox do
)
Chef 11 introduced many backward incompatible features. But Ubuntu 12.04 (precise) comes with an older version of Chef that cannot make sense of many mainstream cookbooks. Therefore, for now, we are installing a modern version of Chef using an inline shell provision command.
In a port forwarded setup, Drupal would not realize its own true host port,
since it the global variable $base_url
is read off of http_host
which
contains the information before port forwarding. Due to this problem the
variable http_request_status_fails
should be set to false
to suppress errors
in status reports.
Notice that in no other setting but this, the error can be avoided and Drupal
should be able to resolve its own FQDN properly. For example,
if you are using a proxy, you can use the
UseCanonicalName
and UseCanonicalPhysicalPort
directives in Apache. Even in
the port forwarded setup, with this directive, Apache sets the right
HTTP_PORT
, but Drupal only looks at HTTP_HOST
that contains the wrong port.
Upon fresh installation, and only sometimes, Drupal shows an error: "There was a problem checking available updates for Drupal", in the status report and when trying to access the Modules page. But once you manually check for updates, the issue is resolved.
The only way that I have been able to reproduce the error consistently has been
chown -R www-data:<whatever> modules/update/
. As far as I have checked, checking
for updates manually does not change any of the permissions, but for some
reason, this command (which is run as part of the drupal-perm.sh
script)
causes this error.
notes:
- I tried removing setgid (
chmod -R g-s
), did not help. - I tried
chown -R www-data:www-data
, did not help. - I tried
chown www-data:www-data
, the problem goes away, but: - The only directory inside
modules/update/
istest/
. By runningchown www-data:www-data modules/update/.
and consequentlychown -R www-data:www-data modules/update/test
, the Drupal status error goes away, although technically it should have had the exact same effect as owning the entire directory at once.
Permission rules take precedence in order of specifity. For example, assume user
bob
is in group smith
, and file foo
is owned by bob:smith
with
permissoins r--,rw-,---
. User bob
cannot write on the file, despite
the fact that all other members of smith
have write access to it.
Since Vagrant is the primary testing and debugging tool, there exists a
Vagrantfile
in the repository that provisions a virtual machine serving Drupal
and runs minitests after provisioning. You should be careful with running
multiple Vagrant machines while testing different things. Vagrant is generally
not
good
with concurrency; at the very least, you should change your port forwarding
configuration. The better way to run multiple tests is to use Test-Kitchen in
parallel mode (kitchen test --parallel
).
Tests are written using the
minitest-handler cookbook.
Look at
this
for an example of how it works. Once test recipes are written, all that needs to
be done is first, add minitest-handler
to Berkshelf's dependencies (in the
Berksfile
), and second, add recipe[minitest-handler]
to Chef's run list.
Also, keep in mind that the path where test
recipes are looked up has changed in recent versions (refer to their
README).
Currently, they are expected to be found at files/default/test/*_test.rb
Automated testing of different combinations of provisioning and minitest recipes on multiple platforms is done by Test-Kitchen. Currently, this cookbook is using Test-Kitchen's Vagrant driver. The only other official Opscode alternative is EC2, but portertech has written drivers for LXC and Docker.
For a good introduction to Test-Kitchen, look at jtimberman's two part blog post.
To test/debug the cookbook with Test-Kitchen, which simply runs the
minitest test cases defined at files/default/test/*_test.rb
, you first need to
install Vagrant (look at note on Vagrant
versions above). The rest is straightforward:
git clone https://github.com/amirkdv/chef-deploy-drupal.git
cd chef-deploy-drupal
# install vagrant-berkshelf, kitchen-vagrant:
bundle install
kitchen test
Right now, Travis-CI is being used only minimally;
only foodcritic
and knife cookbook test
are run against the cookbook. I
tried to setup a simple convergence test using minitest.
Here is the Rakefile
and
here is the Gemfile
I used.
The first thing to remember is that Travis workers have (an old version of) Chef
running,
which is used by Travis itself to provision them. So, the Gemfile
should
specifically ask for a modern version of Chef to be installed (say 11.2.0
)
alongside the original one.
Fixing that, Chef would get stuck while trying to perform action :restart
on
mysql
. Since Travis workers have MySQL running on
boot, I tried
running the following as a before_script
in .travis.yml
:
sudo apt-get purge mysql-client mysql-server mysql-common mysql-server-core
This resulted in Chef throwing an error at the same spot. Here is the last recorded log of the failed attempt to configure a Travis worker using only the mysql cookbook.
Ideally, at least the "fresh Drupal install" use case (see Scope below) should be tested on Travis.
Take a look at mlafeldt's skeleton cookbook for ChefSpec Unit testing on Travis.
Also, look at these two about different strategies for testing Chef cookbooks.
It seems that Jenkins is a popular platform for continuous integration testing of Chef cookbooks, look at jtimberman's blogpost here, and his cookbook for setting up a Jenkins build environment.
The use cases for the cookbook should be defined in a broader sense than the example Vagrant setup. The following are all potential use cases for the cookbook:
- The curious: play with Drupal with minimal effort and cruft
(
Vagrantfile
+Berksfile
orgnized in a usable way; Vagrant-Drupal). - The developer/designer: develop a Drupal project, continuously, in an environment that is consistent over time. This use case has requirements for being able to perform version control in the VM.
- The dev-team: collaborate on a Drupal project in an environment that is consistent for all members of the team. This use case has requirements for remote version control.
- The sysadmin: configure a development/production environment to serve a Drupal site. This requires a bootstrap script to configure a server from scratch as such:
apt-get install ruby1.9.3 libxml2-dev libxslt-dev git
gem install chef berkshelf
# load Berksfile, dna.json, and solo.rb somehow ...
berks install -p /tmp/cookbooks
cd tmp
chef-solo -c solo.rb -j dna.json
The differences between the requirements of the use cases above should be understood, and the use cases to be supported should be identified before reasonable test cases can be defined.
In any case, one major question must be resolved:
Is the cookbook expected to deduce on its own whether it should load an existing
site or create a new one? And if yes (the alternative being that this switch is
controlled via an attribute) what should happen if there is any discrepancy
between the code base (specifically settings.php
), the sql dump, the
attributes provided to the cookbook, and the potentially non-trivial state of
the Chef node (after the first round of provisioning).
One obvious solution to the complication described above is to use Chef
attributes to decide between the different use cases (fresh install, load
existing site, bootstrap existing codebase). These attributes can be set in
dna.json
, and in the virtualization case can be passed to Chef using
environment variables (see [Reset-Functionality][] below):
case=[fresh,reset,load] vagrant [up,provision]
This might be a better recipe decomposition of the existing workflow:
deploy-drupal::lamp_stack
deploy-drupal::pear_dependencies
deploy-drupal::load_existing_site
deploy-drupal::create_new_site
deploy-drupal::default
(minimal)
One good solution for implementing the ability to fully understand the state of
a Drupal site (sys-admin-vise) and spot (and deal with) discrepancies mentioned
above is native PHP code as a Drush command.
Here is a Ruby script
that parses the output of drush status
, which in the latest version (6.x)
accepts a significantly wider array of options.
The current release of Drush (test on earlier stable versions?) has the
following issue in drush status
. If all the following conditions hold:
- credentials exist in
settings.php
, - database with specified name exists,
- specified user has access to the database,
- specified database is empty
then drush throws an exception (not if any of the conditions above does not hold).
For now, user management is considered outside the scope of this cookbook. The
only user/group management that happens in the cookbook is the ownership of the
deployed project root by a group defined in the attribute dev_group_name
which
defaults to root
(and in the Vagrant use case can easily be replaced with
vagrant
). If the provided group name does not exist, it will not be created,
nor will the cookbook add any users to this group.
If such measures are to be implemented in the cookbook, a good place to start would be the sudo cookbook for configuring and managing sudoers.
Also, none of the passwordless MySQL user accounts will be secured by the cookbook. If this were to be included at some point in the cookbook, the easiest way would be to run the following:
UPDATE mysql.user
SET password = PASSWORD ('newpwd')
WHERE password='';
Note that this would disallow an empty root password which might be desirable.
An alternative would be to remove all MySQL users that are defined using
wildcards (''
usernames and/or %
hosts).
Currently, reset functionality is provided through setting an environment
varible in the Vagrant run like this reset=true vagrant [up,provision]
If the solo-provisioner script is to be used, Right now the Vagrantfile
does two things regarding chef attributes:
reset = ENV["reset"].nil? ? "" : ENV["reset"]
chef.json.merge!({
. . .
"deploy-drupal" => {
"reset" => reset
. . .
}
. . .
})
The issue is that if the second part is run as above, the configuration in
dna.json
will be ignored since the following block would be added to the end
of dna.json
:
"deploy-drupal" : {
"destroy_existing" : "true"
}
and Chef will ignore the initial "deploy-drupal"
block. If it is run using
merge
(instead of merge!
) it will not merge at all (no destroy_existing
inside dna.json
in VM).
- MySQL password (for the Drupal MySQL user):
Apparently as far as MySQL
is concerned, all we need to do is to get read of our CREATE USER
statements
and only use this:
GRANT ALL ON <drupal_db_name>.* TO '<user>'@'<host>' IDENTIFIED BY '<password>'
and this will ensure the following:
When the IDENTIFIED BY clause is present and you have global grant privileges, the password becomes the new password for the account, even if the account exists and already has a password. With no IDENTIFIED BY clause, the account password remains unchanged.
and:
If the
NO_AUTO_CREATE_USER
SQL mode is not enabled and the account named in aGRANT
statement does not exist in themysql.user
table,GRANT
creates it.
But the trouble is that once the Drupal site is bootstrapped, if the provisioner
changes the database credentials, drush status
would show the connection
failure and
the cookbook would fire up drush site-install
and that would drop the entire
database! A more crafty workaround has to be developed for password resetting.