Website Logo. Upload to /source/logo.png ; disable in /source/_includes/logo.html

Zuzur’s wobleg

technology, Internet, and a bit of this and that (with some dyslexia inside)

Scripting With Chef

| Comments

Chef is a very cool platform management solution. Once it is setup, you have a very clean solution to distribute server configurations across as many servers as you need, very cleanly (Ruby DSL) and quickly. A must on AWS.

One thing that you can’t find easily in Chef’s documentation is how to use the chef API to write scripts that would use the information chef is storing.

Imagine having a script that would run regularly (cron) and update your internal DNS zone with A records for each server it can find in your chef database.

I’m not sure I used the “right” approach. I just pasted some code i borrowed from knife (chef’s command-line tool) and added it to my own script. I intend to find a cleaner approach later (mixins, inheritance, etc …)

chef_api.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
    #!/usr/bin/env ruby
    require 'rubygems'
    require 'chef/application'
    require 'chef/client'

    require 'mixlib/cli'

    class Client < Chef::Application
      include Mixlib::CLI

      banner "Usage: #{$0} (options)"

      option :config_file,
        :short => "-c CONFIG",
        :long  => "--config CONFIG",
        :description => "The configuration file to use"

      option :log_level,
        :short        => "-l LEVEL",
        :long         => "--log_level LEVEL",
        :description  => "Set the log level (debug, info, warn, error, fatal)",
        :proc         => lambda { |l| l.to_sym }

      option :log_location,
        :short        => "-L LOGLOCATION",
        :long         => "--logfile LOGLOCATION",
        :description  => "Set the log file location, defaults to STDOUT",
        :proc         => nil

      option :editor,
        :short        => "-e EDITOR",
        :long         => "--editor EDITOR",
        :description  => "Set the editor to use for interactive commands",
        :default      => ENV['EDITOR']

      option :no_editor,
        :short        => "-n",
        :long         => "--no-editor",
        :description  => "Do not open EDITOR, just accept the data as is",
        :boolean      => true

      option :help,
        :short        => "-h",
        :long         => "--help",
        :description  => "Show this message",
        :on           => :tail,
        :boolean      => true

      option :node_name,
        :short => "-u USER",
        :long => "--user USER",
        :description => "API Client Username"

      option :client_key,
        :short => "-k KEY",
        :long => "--key KEY",
        :description => "API Client Key"

      option :chef_server_url,
        :short => "-s URL",
        :long => "--server-url URL",
        :description => "Chef Server URL"

      option :yes,
        :short => "-y",
        :long => "--yes",
        :description => "Say yes to all prompts for confirmation"

      option :defaults,
        :long => "--defaults",
        :description => "Accept default values for all questions"

      option :print_after,
        :short => "-p",
        :long => "--print-after",
        :description => "Show the data after a destructive operation"

      option :format,
        :short => "-F FORMAT",
        :long => "--format FORMAT",
        :description => "Which format to use for output",
        :default => "json"

      option :version,
        :short        => "-v",
        :long         => "--version",
        :description  => "Show chef version",
        :boolean      => true,
        :proc         => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
        :exit         => 0

      def configure_chef
        unless config[:config_file]
          full_path = Dir.pwd.split(File::SEPARATOR)
          (full_path.length - 1).downto(0) do |i|
            config_file_to_check = File.join([ full_path[0..i], ".chef", "client.rb" ].flatten)
            if File.exists?(config_file_to_check)
              config[:config_file] = config_file_to_check
              break
            end
          end
          # If we haven't set a config yet and $HOME is set, and the home
          # knife.rb exists, use it:
          if (!config[:config_file]) && ENV['HOME'] && File.exist?(File.join(ENV['HOME'], '.chef', 'client.rb'))
            config[:config_file] = File.join(ENV['HOME'], '.chef', 'client.rb')
          end
        end

        # Don't try to load a knife.rb if it doesn't exist.
        if config[:config_file]
          Chef::Config.from_file(config[:config_file])
        else
          # ...but do log a message if no config was found.
          self.msg("No knife configuration file found")
        end

        Chef::Config[:log_level] = config[:log_level] if config[:log_level]
        Chef::Config[:log_location] = config[:log_location] if config[:log_location]
        Chef::Config[:node_name] = config[:node_name] if config[:node_name]
        Chef::Config[:client_key] = config[:client_key] if config[:client_key]
        Chef::Config[:chef_server_url] = config[:chef_server_url] if config[:chef_server_url]
        Mixlib::Log::Formatter.show_time = false
        Chef::Log.init(Chef::Config[:log_location])
        Chef::Log.level(Chef::Config[:log_level])

        Chef::Log.debug("Using configuration from #{config[:config_file]}")

        if Chef::Config[:node_name].nil?
          raise ArgumentError, "No user specified, pass via -u or specifiy 'node_name' in #{config[:config_file] ? config[:config_file] : "~/.chef/knife.rb"}"
        end
      end


      def parse_options(args=[])
        super
        config
      end

      def run(args)
        parse_options(args)
        configure_chef
        Chef::Search::Query.new.search(:node,"*:*") do |n|
           ## iterate on every node. You have access to every information
           ## chef and ohai could collect from the nodes
        end
      end
    end

    Client.new.run

The command line options for your script are the same as knife’s. And it works flawlessly.

Comments