jtimberman (owner)

Fork Of

Forks

Revisions

gist: 104080 Download_button fork
public
Public Clone URL: git://gist.github.com/104080.git
Embed All Files: show embed
Ruby #
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#!/usr/bin/env ruby
 
require 'rubygems'
require 'thor'
require 'chef'
require 'chef/node'
require 'chef/rest'
 
# Please see the readme for overview documentation.
#
class JsonPrinter
  attr_reader :buf, :indent
  
  # ==== Arguments
  # obj<Object>::
  # The object to be rendered into JSON. This object and all of its
  # associated objects must be either nil, true, false, a String, a Symbol,
  # a Numeric, an Array, or a Hash.
  #
  # ==== Returns
  # <String>::
  # The pretty-printed JSON ecoding of the given <i>obj</i>. This string
  # can be parsed by any compliant JSON parser without modification.
  #
  # ==== Examples
  # See <tt>JsonPrinter</tt> docs.
  #
  def self.render(obj)
    new(obj).buf
  end
  
  
  private
  
  # Execute the JSON rendering of <i>obj</i>, storing the result in the
  # <tt>buf</tt>.
  #
  def initialize(obj)
    @buf = ""
    @indent = ""
    render(obj)
  end
  
  # Increase the indentation level.
  #
  def indent_out
    @indent << " "
  end
  
  # Decrease the indendation level.
  #
  def indent_in
    @indent.slice!(-1, 1)
  end
  
  # Append the given <i>str</i> to the <tt>buf</tt>.
  #
  def print(str)
    @buf << str
  end
  
  # Recursive rendering method. Primitive values, like nil, true, false,
  # numbers, symbols, and strings are converted to JSON and appended to the
  # buffer. Enumerables are treated specially to generate pretty whitespace.
  #
  def render(obj)
    # We can't use a case statement here becuase "when Hash" doesn't work for
    # ActiveSupport::OrderedHash - respond_to?(:values) is a more reliable
    # indicator of hash-like behavior.
    if NilClass === obj
      print("null")
      
    elsif TrueClass === obj
      print("true")
    
    elsif FalseClass === obj
      print("false")
    
    elsif String === obj
      print(escape_json_string(obj))
      
    elsif Symbol === obj
      print("\"#{obj}\"")
      
    elsif Numeric === obj
      print(obj.to_s)
    
    elsif Time === obj
      print(obj.to_s)
    
    elsif obj.respond_to?(:keys)
      print("{")
      indent_out
      last_key = obj.keys.last
      obj.each do |(key, val)|
        render(key)
        case val
        when Hash, Array
          indent_out
          print(":\n#{indent}")
          render(val)
          indent_in
        else
          print(": ")
          render(val)
        end
        print(",\n#{indent}") unless key == last_key
      end
      indent_in
      print("}")
      
    elsif Array === obj
      print("[")
      indent_out
      last_index = obj.size - 1
      obj.each_with_index do |elem, index|
        render(elem)
        print(",\n#{indent}") unless index == last_index
      end
      indent_in
      print("]")
      
    else
      raise "unrenderable object: #{obj.inspect}"
    end
  end
  
  # Special JSON character escape cases.
  ESCAPED_CHARS = {
    "\010" => '\b',
    "\f" => '\f',
    "\n" => '\n',
    "\r" => '\r',
    "\t" => '\t',
    '"' => '\"',
    '\\' => '\\\\',
    '>' => '\u003E',
    '<' => '\u003C',
    '&' => '\u0026'}
  
  # String#to_json extracted from ActiveSupport, using interpolation for speed.
  #
  def escape_json_string(str)
    "\"#{
    str.gsub(/[\010\f\n\r\t"\\><&]/) { |s| ESCAPED_CHARS[s] }.
gsub(/([\xC0-\xDF][\x80-\xBF]|
[\xE0-\xEF][\x80-\xBF]{2}|
[\xF0-\xF7][\x80-\xBF]{3})+/nx) do |s|
s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/, '\\\\u\&')
end
}\""
  end
end
 
Chef::Config.from_file("/etc/chef/server.rb")
 
API_USERNAME=ENV['CHEF_USERNAME']
API_PASSWORD=ENV['CHEF_PASSWORD']
 
raise StandardError, "Please set CHEF_USERNAME and CHEF_PASSWORD" unless ENV['CHEF_USERNAME'] && ENV['CHEF_PASSWORD']
 
class Knife < Thor
 
  desc "register", "Register an openid for an API user"
  method_options :username => :required, :password => :required
  def register
    @rest = Chef::REST.new(Chef::Config[:registration_url])
    @rest.register(options[:username], options[:password])
  end
  
  
  desc "add_recipe", "Add a recipe to a node"
  method_options :recipe => :required, :after => :optional, :node => :required
  def add_recipe
    authenticate
    node = @rest.get_rest("nodes/#{expand_node(options[:node])}")
    node.recipes << options[:recipe] if !node.recipes.include?(options[:recipe])
    @rest.put_rest("nodes/#{expand_node(options[:node])}", node)
    list_recipes
  end
 
  desc "remove_recipe", "Remove a recipe from a node"
  method_options :recipe => :required, :node => :required
  def remove_recipe
    authenticate
    node = @rest.get_rest("nodes/#{expand_node(options[:node])}")
    node.recipes.delete(options[:recipe]) if node.recipes.include?(options[:recipe])
    @rest.put_rest("nodes/#{expand_node(options[:node])}", node)
    list_recipes
  end
 
  desc "show_attr", "Display a node attribute"
  method_options :node => :required, :attr => :required
  def show_attr
    authenticate
    node = @rest.get_rest("nodes/#{expand_node(options[:node])}")
    puts JsonPrinter.render(node[options[:attr]])
  end
 
  desc "edit_attr", "Display a node attribute"
  method_options :node => :required, :attr => :required
  def edit_attr
    editor = ENV['EDITOR'] || "vi"
    puts "Authenticating..."
    authenticate
    puts "Fetching node data for #{expand_node(options[:node])}..."
    node = @rest.get_rest("nodes/#{expand_node(options[:node])}")
    filename = "/tmp/.chef-#{node[:hostname]}"
    File.open(filename, "w") {|f| f.write(JsonPrinter.render(node[options[:attr]])) }
    system("#{editor} #{filename}") or raise StandardError, "Error communicating with #{editor}"
    node[options[:attr]] = JSON.parse(File.read(filename))
    puts "Storing node data for #{expand_node(options[:node])}..."
    begin
      retries = 5
      @rest.put_rest("nodes/#{expand_node(options[:node])}", node)
    rescue Net::HTTPFatalError
      retry if (retries -= 1) > 0
    end
    puts "Done."
  end
  
  desc "list_recipes", "List a node's recipes"
  method_options :node => :required
  def list_recipes
    authenticate
    node = @rest.get_rest("nodes/#{expand_node(options[:node])}")
    puts node.recipes.inspect
  end
 
  def authenticate
    @rest = Chef::REST.new(Chef::Config[:registration_url])
    @rest.authenticate(API_USERNAME, API_PASSWORD)
  end
end
 
  def expand_node(name)
    name + "_" + (ENV['CHEF_DOMAIN'] || `hostname -d`.chomp.gsub(".", "_"))
  end
  
Knife.start