Namespace
A namespace is a declaration that provides various variables and keywords with similar names. Every knife plugin that you create should have its namespace.
We use the module keyword to declare a namespace.
Syntax: module <your namespace name>. This statement tells the compiler about the name of your namespace.
Declaring a namespace is essential because it helps in the logical organization of code.
Declaring of namespace is followed by mentioning the class name.
Class Name
We declare the class name of our plugin as a subclass of both knife and chef. In terms of object-oriented programming, this declaration is called inheritance. Our plugin will now inherit all the functionalities of knife and Chef.
Syntax: class <your subclass name> < Knife:: Chef.
This statement creates a children class from Knife and Chef class. Note that it is standard to use CamelCasing to declare subclass names. This style is traditional to all subclass.
Note: If you want to override an existing knife or chef subcommand, you can use the same name as the subcommand’s class name while declaring.
If you are willing to share your custom plugin with the community, you must create it in a user-friendly way. The Banner section helps you in this case.
Banner
Suppose a user uses your plugin for the first time. He will indeed require some documentation to use it. The banner method is used to provide that documentation. Whatever you put inside the banner will appear on the screen when the user types the -- help command in the command line.
Example:
module Delete
class delete < Chef::Knife
banner "this commands allows you to delete an object"
.
.
.
end
Now, you may create a plugin that offers the same functionalities as some other plugins. Writing the code for enabling these features is meaningless. It will make your code repetitive and extensive. Instead, you can declare these plugins as dependencies and access their features.
Dependencies and Requirements
We use the deps method and the require keyword to load these plugins in the knife. The good part about using deps is that it loads only those plugins that our plugin requires. This strategy reduces the overhead and saves time.
Syntax:
class subclassName < Chef::Knife
deps do
require <plugin path>
end
Generally, all the path plugins are in the knife directory inside the chef folder.
We suggest you type the location of your required plugin very carefully.
Here the deps keyword declares the starting of dependencies. The require keyword finds the plugins at the given location and loads them.
Options
Using the options keyword, we can also add various command line options, such as show, status, tag, etc. An option can have three types of values.
- Boolean that is true or false.
- String value.
- Piece of executable code.
After typing the plugin keyword, you can use these options by writing in the - <options> format. We can view the list of options for a particular command by typing - - help after the command.
For example
option :URL,
:short => "-s URL_",
:long => "--url-infra-server",
:description => "The URL of the Chef’s infra server."
We may also use options to update a particular class's run method and execute different conditions based on the option's value.
Syntax:
def run
if config[:<option>]
# condition statement 1
<code>
else
# condition statement 2
<code>
end
end
Arguments
Command line arguments are the inputs to commands.
For example, knife list roles/ has knife list as the command and roles/ as the argument.
We use the name_args method to add command line arguments to our plugin.
We suggest you check the size of the argument list before executing. If the number of arguments present is insufficient, you must exit the execution.
For example,
def run
#ensures that there is at least one argument present.
unless name_args.size == 1
<code>
#displays the right usage before exiting, in case of command failure.
show_usage
exit 1
end
first_arg = name_args.first
if config[:option]
<condtional code>
else
<conditional code>
end
end
Here, first_arg gets the first argument from the list of ideas.
Configuration Settings
A configuration file specifies the configuration settings for a knife. In Ruby, this file is named config.rb. This file loads every time we execute a knife. We can set the knife plugin settings in the configuration file in the following two ways:
-
We can use the “: proc” attribute of the option method.
Syntax: :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key
Chef::Config[:knife][:<setting_name>]
-
We can specify the configurations directly inside the def method. We can use either Chef::Config[:knife][:setting_name] or use config[:setting_name].
This method is quite useful when we use a particular setting frequently. You can easily configure that setting according to your requirements.
Now, we may often want to receive data from the Chef’s infra server. This data may have cookbook records or information about nodes. So, to search and receive data from the servers, we need to implement the search functionality in our plugin.
Search
We first import the search functionality using the require method.
require 'chef/search/query’
We then create a search query. This query is created as a ruby object using the Query subclass. Chef::Search::Query.new
We then assign a variable name to it for further use.
<variable_name> = Chef::Search::Query.new .
You can now use this search object to find your relevant data in the Chef servers.
For example
query = "role:webserver"
# searches the object query_nodes and stores the result in the node_item variable.
query_nodes.search('node', query) do |node_item|
# returns the name of the nodes found.
puts "Node Name: #{node_item.name}"
end
The node_item variable will store the query result. We can use this result later for other operations.
It is always good to have a proper user interface for your application. A beautiful, interactive layout is always easy to use and understand. Therefore, we advise you to create a knife plugin with a good UI.
User Interaction
The “ui” object provides several methods for user interactions in our plugin. We will look at a few of these methods below:
-
ui.color => It takes two arguments, the string and the color. The color is applied to that string. It provides font styling to commands such as bold, italic, etc. Example: ui.color('Class', :italics).
-
ui.edit_data => This interface property edits the data provided as input. It takes data as an argument and an optional argument, parse_output. Syntax: ui.edit_data(data, parse_output=true).
-
ui.error => Presents text as an error message to the user.
-
ui.fatal => Presents text as a fatal error to the user.
-
ui.warn => gives a warning to the user.
-
ui.msg(message) => presents the user with a message.
An example to show user interaction in a plugin:
unless name_args.size == 1
ui.warn("This is a warning statement.")
show_usage
exit 1
end
Executing a Plugin
Now, let us learn how to run a plugin. Once we have coded a plugin, we can execute it using the knife command.
The knife command triggers the run method, and the code inside the run gets executed.
module PluginExample
class DemoPlugin < Chef::Knife
def run
puts "This is a demo plugin!"
end
end
end
Now, in the command line, if you type, knife demo plugin,
Then, you will get This is a demo plugin! as the output.
Suppose someone already creates the plugin that you need. So, instead of coding the plugin again, you can simply install the plugin file. All you have to do is copy the file to the directory inside the plugin. Ensure the file is coded in rugby and has .rb as an extension. You may also install the file from RubyGems.
Congratulations Ninja! You have learned all the concepts and syntax to create your custom knife plugin or install it from scratch.
One last thing, ninja. Whenever we code a program, there is always a chance of getting errors or unexpected results. Troubleshooting these exceptions is essential but easy. Since we code plugins in Ruby, we handle the exceptions as we do for other codes written in Ruby. The troubleshooting available for a knife is mostly enough for managing the plugin exceptions.
Frequently Asked Questions
What is a recipe in Chef?
A recipe is a group of attributes that are used to manage the Chef’s infrastructure. Recipes load whenever we run the Chef client. Recipes modify or set the state of the infrastructure.
What do you mean by a Cookbook in chef?
As the name suggests, a Cookbook is a directory of recipes. It makes sure to provide the required infrastructure for the recipes present inside it.
How was Chef started?
Adam Jacob created Chef. He made it a tool for his consulting-based company. Initially, it started with the name “marionette.” Considering the long term, which was difficult to remember, Adam changed its name to Chef.
Conclusion
Hey Ninja! We hope you enjoyed reading this article and believe you can now create your own custom knife plugins per your requirements. Before ending this discussion, let's summarize everything we learned in this blog.
We started by understanding what a knife is and what its plugins are. We then looked at the syntax of a knife and understood each component one by one. We considered several examples while discussing. We also learned to install a plugin file in your knife. At last, we addressed the nature of exceptions and how to deal with them.
We ended our discussion with some frequently asked questions and their brief answers.
We sincerely hope the above discussion helped you to understand building custom knife plugins in Chef.
You can refer to our blogs on chef basics, integration, installation, and settings if you wish to learn more about Chef and its various components.
Visit our website to read more such blogs. Make sure you enroll in our courses. You can take mock tests, solve problems, and interview puzzles. Also, you can check out some exciting interview stuff- interview experiences and an interview bundle for placement preparations. Do upvote our blog to help fellow ninjas grow.
Keep Grinding! 🦾
Happy Coding! 💻