Is there a difference between setting attributes like this?


#1

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!


#2

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.example declared
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 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!


#3

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!

#4

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 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.example declared
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
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!


#5

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!

#6

Thank you for the very detailed feedback Lamont, this is extremely helpful
and very much appreciated. Many thanks!

On Thu, Feb 12, 2015 at 7:04 PM, Lamont Granquist lamont@chef.io wrote:

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 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.example declared
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
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!