I'd advise for starting out that you begin with a role_base cookbook
with multiple recipes in it. The structure then looks like:
metadata.rb
name "role_base"
depends "openssh"
depends "resolver"
attributes/default.rb
ssh settings
default["openssh"]["server"]["port"] = 22
resolver settings
default[:resolver][:nameservers] = [ "8.8.8.8", "8.8.4.4" ]
recipes/default.rb
include_recipe "openssh"
include_recipe "resolver"
include_recipe "#{cookbook_name}::packages"
recipes/packages.rb
%w{lsof tcpdump zsh dmidecode}.each do |pkg|
package pkg
end
I would still use an actual base role:
roles/base.json
{
"name": "base",
"description": "It's all about the base",
"run_list": [
"recipe[role_base]"
]
}
This keeps things fairly tidy. You still have a role in your run_list
but its pretty much a no-op pointer to the role cookbook. The role
cookbook can serve multiple purposes and you should break it up into
sub-recipes that each do one thing with a default recipe that runs them
all. You can start to break out other cookbooks from the base cookbook
and make it all smaller and more composable, but I think you should do
that as you decide that you start wanting to do more
test-driven-development or one of the recipes starts to give you obvious
pain. One thing that you cannot do with this approach is have multiple
base roles that share code between them. You cant have a single
cookbook with multiple roles in it (the attributes and libraries files
don't really allow it -- whenever you include a cookbook you include all
its attribute files so there no way to switch behavior there between
different kinds of hosts).
For your apps, I'd have simpler roles, they should have one default
recipe and should depend on library cookbooks that export LWRPs to set
up applications. You can use cookbooks like application_ruby to provide
those, or you can roll your own LWRPs for "what it means to be an app at
MYORG"
So:
cookbooks/role_foo/metadata.rb
name "role_foo"
depends "application_ruby"
depends "build-essentials"
cookbooks/role_foo/attributes/default.rb
default['build-essential']['compile_time'] = true
cookbooks/role_foo/recipes/default.rb
include_recipe "build-essentials"
package "libv8-dev"
... more stuff ...
application "foo" do
path "/srv/foo"
owner "root"
group "root"
rails do
# ... stuff ...
end
end
You'd probably call this an "application" cookbook, but its also a
"wrapper" cookbook around the build-essentials cookbook, which I think
is where the terminology can wind up tying your brain into knots. The
important point is that your application needs:
- the compiler toolchain installed (at compile_time for the sake of
argument [*])
- the libv8-dev package installed
- a bunch of typical stuff you do to install and deploy a rails app in
you organization
If you start duplicating a lot of code in your application cookbooks you
can start to extract those out to either other cookbooks which may be
recipes or may be LWRPs so that you might wind up with a 'base'
application cookbook that all your application cookbooks depend upon and
use include recipe:
cookbooks/app_base/metadata.rb
name "app_base"
depends "application_ruby"
depends "build-essentials"
cookbooks/app_base/attributes/default.rb
default['build-essential']['compile_time'] = true
cookbooks/app_base/recipes/default.rb
include_recipe "build-essentials"
package "libv8-dev"
cookbooks/role_foo/metadata.rb
name "role_foo"
depends "app_base"
cookbooks/role_foo/recipes/default.rb
include_recipe "role_foo::default.rb"
application "foo" do
[....stuff...]
end
As "app_base" grows larger you can keep your role_foo (and role_bar and
role_baz) cookbooks smaller now.
And there's no pattern which you should definitely adopt. For small
needs what I've outlined here will be more than enough. As you grow you
probably need to refactor and wrap more. At the same time, for my own
use I wound up overengineering my small personal needs and had a few
dozen cookbooks composing my base role and recently refactored them into
this one-base-cookbook-many-recipes pattern which greatly simplified
things for me.
I do suggest that your run_list still looks like 'role[base], role[foo]'
and that those are independent. That way you can build your app in a
test harness and not have to install every user account and all their
dotfiles that your base role should be responsible for -- the
application role should have all of its dependencies and only its
dependencies. If you slurp your whole base role into your app cookbooks
then you'll miss out on being able to quickly build apps in test harnesses.
HTH
[*] also I don't think you should ever need to force build-essentials to
compile-time for apps that you install so maybe thats a bad example to
pick, but I couldn't find a more relevant one offhand and it nearly
makes sense that you might want to do that for native gem installs.
On 2/12/15 6:01 PM, Greg Barker wrote:
Thanks for the feedback!
Regarding setting the attributes in recipe code like that, I got it
from this blog post
http://blog.vialstudios.com/the-environment-cookbook-pattern/ that
cheeseplus linked me to in IRC. Today I've been trying to experiment
with using Environment Cookbooks and Role Cookbooks but I'm having a
hard time wrapping my head around it. Regular Environments & Roles
make a lot of sense to me, and I understand some of the downsides to
them. I like that I get a version number associated with the
environment and role if they are moved to cookbooks. I'm just not
clear to me if what I'm gonna end up with after this refactoring is
ideal, or if it's just gonna be a mess of different types of cookbooks
and simple roles that only reference the role cookbook they are
associated with. Other questions arise along the way like, if I have a
mycorp-base cookbook, do I need a mycorp_base role, or can I just have
the mycorp-myapp-jenkins cookbook do /include_recipe "mycorp-base"/ ?
Do I keep the myapp_jenkins role and have the run_list set to
role["mycorp-base"], recipe["mycorp-myapp-jenkins"] ?
I'm also a little unsure if things have changed enough since posts I'm
basing this off like this one
https://www.chef.io/blog/2013/11/19/chef-roles-arent-evil/ and this
one
http://realityforge.org/code/2012/11/19/role-cookbooks-and-wrapper-cookbooks.html
were written that maybe there's other some best practice to follow now?
On Thu, Feb 12, 2015 at 5:37 PM, Lamont Granquist <lamont@chef.io
mailto:lamont@chef.io> wrote:
Also you don't want to use node.set, you want to use node.default
or maybe node.override if you have to. When you use node.set that
is an alias for node.normal which has the side effect of
persisting data permanently in the node object. If you delete
those lines, you'll find the attributes are still set, which can
be highly confusing. The normal and override attribute precedence
levels are actually wiped at the start of the chef run and
re-built completely by your code which is what you actually want.
You should only use 'node.set' or 'node.normal' if you're doing
something like generating a password for a database on the first
run and you need to remember that (and even there you should
probably use a data bag).
And one last thing is that its better to move those kinds of
settings into an attributes file if at all possible and not set
attributes in recipe code like that.
On 2/12/15 3:53 PM, Sean Clemmer wrote:
Yes.
The first version will replace the entire node.openssh.server
attribute with the Hash you've provided. The second will only
update node.openssh.server.port and
node.openssh.server.permit_root_login. So if there were other
attributes like node.openssh.server.exampledeclared elsewhere,
the first version would blow away this value, while the second
would not.
On Thu, Feb 12, 2015 at 3:41 PM, Greg Barker
<fletch@fletchowns.net <mailto:fletch@fletchowns.net>> wrote:
This is for my "base cookbook", wasn't sure if there was a
difference between setting attributes like this:
node.set["openssh"]["server"] = {
"port" => 22,
"permit_root_login" => "no"
}
Versus setting them like this:
node.set["openssh"]["server"]["port"] = 22
node.set["openssh"]["server"]["permit_root_login"] = "no"
Thanks!