As briefly discussed in Chapter 2, both the Chef Development Kit and Chef Client are written and implemented in Ruby. However, prior experience with Ruby is not a requirement for writing Chef code. Most people who use Chef have no prior experience with the Ruby programming language. So let’s spend some time going over the basics of Ruby and how it relates to Chef syntax.
Overview of Ruby
Ruby is an object-oriented programming language that was originally designed in 1993 as a replacement for Perl.Yukihiro Matsumoto (or “Matz” for short) designed and created Ruby in Japan. Ruby became very popular in the United States after two things occurred: 1) Dave Thomas wrote the book “Programming Ruby” in English in 2000, as until then most of the documentation on Ruby was in Japanese, and 2) David Heinemeier Hansson created the Ruby on Rails framework in 2003, which came to be viewed as an incredibly productive way to build web applications. The rapid adoption of Ruby on Rails, along with great documentation written in English, led people outside Japan to appreciate the Ruby language for other purposes besides web development. As illustrated in the next section, Ruby boasts a very English-like syntax.
Although Ruby is object oriented, it also supports functional and imperative programming paradigms. Unlike C or Java, which implement static typing, Ruby is a dynamically typed language. In this way, Ruby is similar toPython and Lisp. Ruby is designed for programmer productivity and fun. Usability and interface design are often given preference over speed or concurrency:
Often people, especially computer engineers, focus on the machines. They think, “By doing this, the machine will run faster. By doing this, the machine will run more effectively. By doing this, the machine will something something something.” They are focusing on machines. But in fact we need to focus on humans, on how humans care about doing programming or operating the application of the machines. We are the masters. They are the slaves.— Yukihiro Matsumoto
Similarly, Ruby follows the Principle of Least Surprise (also called the Principle of Least Astonishment), which aims to minimize confusion for both new and experienced users. The principle encourages consistency, common design patterns, and reusable code. The Ruby principles of simplicity and ease of use are echoed throughout Chef as well.
There is much more to the Ruby programming language, but that knowledge is not required to use Chef. For a more detailed explanation of Ruby and the Ruby programming language, check out Programming Ruby 1.9 & 2.0 (4th edition): The Pragmatic Programmers’ Guide by Dave Thomas with Chard Fowler and Andy Hunt, orLearning Ruby by Michael Fitzgerald (O’Reilly).
Ruby Syntax and Examples
Let’s cover the basics of Ruby syntax through the use of real-world examples.
CHECKING SYNTAX
Ruby provides a built-in mechanism to verify that a file contains valid Ruby syntax. You can check a file’s syntax by passing the
-c
flag and the path to the file to the Ruby interpreter.
This will return
Syntax OK
if the syntax is correct. Otherwise, it will print a stack trace pointing to the line where the error occurred.COMMENTS
In Ruby, the hash character (
#
) is used to represent a comment. Comments contain documentation for your code. Everything after the \#
on the same line is treated as a comment intended to be read by humans and ignored by the Ruby interpreter:
It is important to document why you are writing code. Use comments to explain why you chose to implement your code in the way you did and describe the alternative approaches you considered. Jeff Atwood, of Stack Exchange and Discourse fame, brilliantly explains the purpose of comments this way: “Code tells you how, comments tell you why.”
For example, consider the following Ruby code snippet, which buys bacon if there are currently fewer than five strips. By reading the code you can understand what the code does, but it is not clear why the code was written in the first place:
The following code adds a simple comment explaining why we should buy more bacon when there are fewer than five strips. It is helpful to understand the context around this bacon purchase: Jake likes to eat five pieces of bacon in the morning, so we want to have enough bacon on hand at all times:
VARIABLES
Because Ruby is not a statically typed language, you do not need to declare the type of variable when assigning it. The following examples assign very different kinds of values to variables: a number, the string hello, and even an object. There is no need to tell Ruby the kind of content that will be stored in a variable before assigning values:
In Ruby, a variable is accessible within the outermost scope of its declaration. Consider the following example that better demonstrates variable scope. First, a top-level variable named
bacon_type
is declared and assigned the value crispy
. Next, the bacon is cooked twice and an additional variable named temperature
is assigned the value 300
. Finally, the script attempts to access the temperature
variable, which is now out of scope:- The
bacon_type
variable is declared at the top-level scope, so it will be accessible everywhere within this context. - We can access the
bacon_type
variable inside a more specific scope, such as a loop. - A variable declared inside a scope is accessible only from inside that scope.
- Outside of the scope of declaration, attempting to access a variable will result in an exception (
undefined local variable or method ‘temperature’
).
WAIT… WHAT ARE YOU SAYING?
It’s worth noting that if you’re feeling a bit lost at this point, this book presumes a basic level of experience with object-oriented programming concepts. If you’re not clear about variables, scope, and loops, you’ll probably want to brush up on some of the basics of object-oriented programming and then head back here to get up to speed on Ruby and Chef. A great book on object-oriented programming with Ruby is Practical Object-Oriented Design in Ruby, by Sandi Metz.
MATHEMATICAL OPERATIONS
Ruby exposes basic arithmetic operations such as addition, multiplication, and division as a core feature of the language:
Let’s say that to ensure a productive and collaborative work environment, your company policy requires that your team take a bacon break every three hours, on the hour. Even though the policy is strict, the team often becomes engrossed in its work and forgets to check the clock. You could write a short Ruby script that alerts the team when it should stop and enjoy some freshly-cooked bacon:
More complex operations, such as rounding or polynomial distributions, are exposed in Ruby’s
Math
module. You can use the Math.hypot
method in a Ruby script to calculate the length of a diagonal for a right triangle by returning the sum of the squares of its two sides:
Additional functions and constants such as
log
, sin
, sqrt
, e
, and Ļ
are also contained in the Math
module.STRINGS
There are two common ways to create strings in Ruby: with single quotes and with double quotes. Single-quoted strings have the advantage of one fewer keystroke and do not apply interpolation.
Double-quoted strings are useful when variables need to be evaluated as part of the string content—this evaluation process is known as string interpolation. A hash symbol is used as a placeholder within a double-quoted string to indicate to Ruby that the placeholder should be replaced with the evaluated content of a variable. In the following example,
#{x}
is used to insert the value of x
within a string.
You might need to escape special characters. For example, when storing the player name Jim O’Rourke as a string, you need to escape the single quote in his last name:
Alternatively, you can use double quotes and avoided escaping the character altogether:
HEREDOC NOTATION
In Chef, you might see the “heredoc” notation for strings. Heredoc is especially useful when having a multiline string. Heredoc notation starts with two “less than” symbols (
<<
) and then some identifier. The identifier can be any string, but should not be a string that is likely to appear in the body of text. In the following example, we chose METHOD_DESCRIPTION
as the heredoc identifier:TRUE AND FALSE
In addition to the literal
true
and false
, Ruby supports “truthy” and “falsey” values. Expressions evaluate as true
or false
in a situation where a boolean result is expected (such as when they are used in conditionals like the if
statement). Table 3-1 demonstrates the common truthy and falsey values and their actual evaluations.ARRAYS
Ruby’s native array support allows you to create lists of items. Ruby’s arrays use the bracket notation as shown in the following example. Arrays are zero-indexed and the Ruby interpreter automatically allocates memory as new items are added; there is no need to worry about dynamically resizing arrays:
- The
length
method tells us how many items are in the array. - You’ll sometimes see
size
used as a synonym forlength
; Ruby offers the choice to use either. - Add one or more items to the end of the array with
push
. <<
is a helpful alias forpush
when you want to add just one item to the end.- Arrays are zero-indexed, so we can access the first element using
0
as the index. - The first element is also accessible via the convenient
first
method. - To complement the
first
method, Ruby also exposes alast
method. - Specifying a range inside the brackets will slice the array from the first index up to and including the second index.
For example, you could use Ruby arrays to store an ordered list of all employees in the order in which they were hired. Each time a new employee joins the team, he is added to the list. The first employee is at index
0
, the second employee is at index 1
, and so on:
When a new employee joins the team, his name is pushed onto the end of the array:
HASHES
Ruby also supports hashes (sometimes called dictionaries or maps in other languages). Hashes are key-value pairs that behave similarly to arrays. Hashes are created using literal curly braces (
{}
):
For example, you can use Ruby hashes to store information about popular baseball players and their current statistics. The
key
in this hash was the name of the baseball player. The value
was another hash whose key is the name of the statistic and whose value is the number of that statistic. This is better illustrated by the following sample data:
An individual player is accessed using his name as the key in the
players
hash. Because players
is a hash of hashes, the returned value is also a hash:REGULAR EXPRESSIONS
REMEMBERING THE ORDER
It’s easy to forget the order of the equal-tilde matcher. Is it
=~
or ~=
? The easiest way to always get the order correct is to think in alphabetical order. “Equals” comes before “tilde” in the dictionary, so the equal sign comes before the tilde in the expression.
For example, you could try using regular expressions to find names in a list of players on a baseball team beginning with a certain letter of the alphabet. Regular expressions can be anchored using the caret (
^
), which starts a match at the beginning of the string. The following snippet searches for all players that have a last name beginning with the letter F
:CONDITIONALS AND FLOW
Like most programming languages, Ruby supports conditionals to manage the flow and control of application logic. The most common control gate is the
if
keyword. The if
keyword is complemented by the unless
keyword, which is the equivalent of if not
:
Less common in Ruby (but common in Chef) is the
case
statement. Very similar to the if
statement, the case
statement applies more syntactic sugar and logic for the developer:- If a literal object is supplied, the
case
statement will perform a pure equality match. - If a regular expression is supplied, Ruby will attempt to call
match
on the receiving object. - If multiple items are given, they are interpreted as “or” (if the item matches “list”, “of”, or “items”).
case
statements also support a default case, in the event that nothing else matches.
For example, you could use Ruby’s
case
statement to classify players on a baseball team based on their age. Players younger than 12 are considered to be in the minor league. Players between 13 and 18 are developing. Players between 19 and 30 are in their prime. Players still in the league between 31 and 40 are on their decline, and anyone over 40 is in retirement. This logic is captured by the following Ruby case
statement:METHODS, CLASSES, AND MODULES
Although not necessary until more advanced interactions with Chef, Ruby also supports methods, classes, modules, and an object-oriented hierarchy. Ruby methods are defined using the
def
keyword. Ruby classes are defined using the class
keyword. Ruby modules are defined using the module
keyword:
Methods may be invoked by name. Although optional, parentheses are highly recommended for readability and code portability:
Chef Syntax and Examples
The Domain Specific Language (DSL) used by Chef is actually just a subset of Ruby. The full power of the Ruby programming language is accessible in Chef code. This allows developers to conditionally perform actions, perform mathematical operations, and communicate with other services easily from within Chef code. Before diving into the more advanced features of Chef’s DSL, we will explore the basic syntax first.
Here is an example of the Chef DSL in action, demonstrating how a user account can be created with a Chef resource. In Chef, resources are the building blocks used to define specific parts of your infrastructure. For example, the following statement manages a user account named
alice
with a user identifier (UID) of 503:
The following code sample demonstrates the more abstract syntax for invoking a DSL method in Chef code, on which the previous example is based:
The first part is the name of the resource (such as
template
, package
, or service
). The next part is the name_attribute
for that resource. The interpretation of this value changes from resource to resource. For example, in the package
resource, the name_attribute
is the name of the package you wish to install. In the template
resource, the name_attribute
is the path on the target node where the compiled file should reside. Next comes the Ruby keyword do
. In Ruby, the do
must always be accompanied by a closing end
. Everything that resides between the do
and end
is called the block
. Inside the block, resource parameters and their values are declared. This varies from resource to resource. A valid parameter for the package
provider is version
, whereas a valid parameter for the template
provider is source
.
It might help to think about the code in a more object-oriented approach as follows. The Chef DSL creates a new resource object for you, sets the correct attributes and parameters, and then executes that resource when Chef evaluates the code:
WARNING
Please note that the preceding code is not valid syntax and is only used for instructional purposes.
template
, package
, and service
are just three of the many types of resources built into the Chef DSL. The following code demonstrates using the template
, package
, and service
resources in Chef code:- This declares a template resource in Chef’s DSL. The template will be compiled from the local file my_resolv.conf.erb, be owned by
root:root
, have0644
permissions, and be placed at /etc/resolv.conf on the target machine (where Chef evaluates the code). - This declares a package resource in Chef’s DSL. The “ntp” package will be upgraded.
- This declares a service resource in Chef’s DSL. The “apache2” service will be accessible and manageable by Chef.
If you specify an invalid parameter (either one that does not exist or is misspelled), Chef will raise an exception:
Chef uses a multiphase execution model, which lets you include logic switches or loops inside Chef code. For example, if you wanted to execute a resource on an array of objects, you could do so using the following code. The
file
resource is used to manage a file. content
is a Chef DSL expression used to specify a string that is written to the file:
In the first phase of evaluation, Chef will dynamically expand that Ruby loop. After the first phase, internally the code becomes equivalent to:
Even though the resources were dynamically created using Ruby interpolation and looping, they are still available as individual items in the resource list, because of Chef’s multiphase execution.
Similarly, top-level Ruby code is computed during the first phase of execution. When dynamically calculating a value (such as the total free memory on a target node), those values are cached and stored during the first phase of execution:
In the second phase of evaluation, the resource contained in the resource list will be:
So far, you have seen the
file
resource, template
resource, service
resource, and package
resource. These are all resources packaged into the core of Chef. You can find a complete listing of all resources on the resources page. Here are some of the most commonly used Chef resources, followed by examples of their basic usage:bash
chef_gem
cron
deploy_revision
directory
execute
file
gem_package
group
link
mount
package
remote_file
service
template
user
These examples illustrate Chef’s DSL, as well as showcase some of the common resources used when working with Chef. Although our list is not comprehensive, it does include some of the most common Chef resources you will encounter. The full list of Chef’s built-in resources can be found in the online resource documentation.
The material presented in this chapter is all you need to know about Ruby to write Chef code. Only when you need to extend Chef beyond what is provided out of the box will you have to worry about delving more deeply into Ruby coding. When you need to level up your Ruby knowledge for this task, we highly recommendCustomizing Chef by Jon Cowie (O’Reilly).
Now that we have covered all the necessary fundamentals of Ruby, let’s get back to more Chef coding! In the next chapter, we will put your new Ruby knowledge to use by writing some Chef code.
Nice post ! Thanks for sharing valuable information with us. Keep sharing..Ruby on Rails Online Training Hyderabad
ReplyDelete
ReplyDeleteThanks for sharing such a Excellent Blog! We are linking too this particularly great post on our site. Keep up the great writing.
ruby assignment help