summaryrefslogtreecommitdiff
path: root/chef-server-api/app/controllers/application.rb
blob: b461b2098b254fd4ca9bf83f8ee985b0e252386c (plain)
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Brown (<cb@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
# Copyright:: Copyright (c) 2008 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'rubygems'
require "chef" / "mixin" / "checksum"
require "chef" / "cookbook_loader"

class ChefServerApi::Application < Merb::Controller
  include AuthenticateEvery
  include Chef::Mixin::Checksum

  include Mixlib::Auth::AuthHelper
  include Merb::ChefServerApi::ApplicationHelper::AuthenticateEvery

  controller_for_slice
  
  # Generate the absolute url for a slice - takes the slice's :path_prefix into account.
  #
  # @param slice_name<Symbol> 
  #   The name of the slice - in identifier_sym format (underscored).
  # @param *args<Array[Symbol,Hash]> 
  #   There are several possibilities regarding arguments:
  #   - when passing a Hash only, the :default route of the current 
  #     slice will be used
  #   - when a Symbol is passed, it's used as the route name
  #   - a Hash with additional params can optionally be passed
  # 
  # @return <String> A uri based on the requested slice.
  #
  # @example absolute_slice_url(:awesome, :format => 'html')
  # @example absolute_slice_url(:forum, :posts, :format => 'xml')          
  def absolute_slice_url(slice_name, *args)
    options = {}
    if args.length == 1 && args[0].respond_to?(:keys)
      options = args[0]
    else
      options  = extract_options_from_args!(args) || {}
    end
    protocol = options.delete(:protocol) || request.protocol
    host     = options.delete(:host) || request.host

    protocol + "://" + host + slice_url(slice_name, *args)
  end
  
  def fix_up_node_id
    if params.has_key?(:id)
      params[:id].gsub!(/_/, '.')
    end
  end
  
  def escape_node_id(arg=nil)
    unless arg
      arg = params[:id] if params.has_key?(:id)
    end
    arg.gsub(/\./, '_')
  end
  
  def login_required
    if session[:openid]
      return session[:openid]
    else  
      self.store_location
      throw(:halt, :access_denied)
    end
  end
  
  def authorized_node
    if session[:level] == :admin
      Chef::Log.debug("Authorized as Administrator")
      true
    elsif session[:level] == :node
      Chef::Log.debug("Authorized as node")
      if session[:node_name] == params[:id].gsub(/\./, '_')
        true
      else
        raise(
          Unauthorized,
          "You are not the correct node for this action: #{session[:node_name]} instead of #{params[:id]}"
        )
      end
    else
      Chef::Log.debug("Unauthorized")
      raise Unauthorized, "You are not allowed to take this action."
    end
  end
  
  # Store the URI of the current request in the session.
  #
  # We can return to this location by calling #redirect_back_or_default.
  def store_location
    session[:return_to] = request.uri
  end

  # Redirect to the URI stored by the most recent store_location call or
  # to the passed default.
  def redirect_back_or_default(default)
    loc = session[:return_to] || default
    session[:return_to] = nil
    redirect loc
  end
  
  def access_denied
    case content_type
    when :html
      store_location
      redirect slice_url(:openid_consumer), :message => { :error => "You don't have access to that, please login."}
    else
      raise Unauthorized, "You must authenticate first!"
    end
  end
  
  # Load a cookbook and return a hash with a list of all the files of a 
  # given segment (attributes, recipes, definitions, libraries)
  #
  # === Parameters
  # cookbook_id<String>:: The cookbook to load
  # segment<Symbol>:: :attributes, :recipes, :definitions, :libraries
  #
  # === Returns
  # <Hash>:: A hash consisting of the short name of the file in :name, and the full path
  #   to the file in :file.
  def load_cookbook_segment(cookbook, segment)
    files_list = segment_files(segment, cookbook)
    
    files = Hash.new
    files_list.each do |f|
      full = File.expand_path(f)
      name = File.basename(full)
      files[name] = {
        :name => name,
        :file => full,
      }
    end
    files
  end
  
  def segment_files(segment, cookbook)
    files_list = nil
    case segment
    when :attributes
      files_list = cookbook.attribute_files
    when :recipes
      files_list = cookbook.recipe_files
    when :definitions
      files_list = cookbook.definition_files
    when :libraries
      files_list = cookbook.lib_files
    when :files
      files_list = cookbook.remote_files
    when :templates
      files_list = cookbook.template_files
    else
      raise ArgumentError, "segment must be one of :attributes, :recipes, :definitions, :remote_files, :template_files or :libraries"
    end
    files_list
  end

  def specific_cookbooks(node_name, cl)
    valid_cookbooks = Hash.new
    begin
      node = Chef::Node.load(node_name, @couchdb)
      recipes, default_attrs, override_attrs = node.run_list.expand('couchdb', @couchdb)
    rescue Net::HTTPServerException
      recipes = []
    end
    recipes.each do |recipe|
      valid_cookbooks = expand_cookbook_deps(valid_cookbooks, cl, recipe)
    end
    valid_cookbooks
  end

  def expand_cookbook_deps(valid_cookbooks, cl, recipe)
    cookbook = recipe
    if recipe =~ /^(.+)::/
      cookbook = $1
    end
    Chef::Log.debug("Node requires #{cookbook}")
    valid_cookbooks[cookbook] = true 
    cl.metadata[cookbook.to_sym].dependencies.each do |dep, versions|
      expand_cookbook_deps(valid_cookbooks, cl, dep) unless valid_cookbooks[dep]
    end
    valid_cookbooks
  end

  def load_cookbook_files(cookbook)
    response = {
      :recipes => Array.new,
      :definitions => Array.new,
      :libraries => Array.new,
      :attributes => Array.new,
      :files => Array.new,
      :templates => Array.new
    }
    [ :recipes, :definitions, :libraries, :attributes, :files, :templates ].each do |segment|
      segment_files(segment, cookbook).each do |sf|
        next if File.directory?(sf)
        file_name = nil
        file_url = nil
        file_specificity = nil
        
        if segment == :templates || segment == :files
          mo = sf.match("#{Merb::Config.cookbook_cache_path}/[^/]+/#{cookbook.name}/#{segment}/(.+?)/(.+)")
          specificity = mo[1]
          file_name = mo[2]
          url_options = { :cookbook_id => cookbook.name.to_s, :segment => segment, :id => file_name, :organization_id => @organization_id }
          
          case specificity
          when "default"
          when /^host-(.+)$/
            url_options[:fqdn] = $1
          when /^(.+)-(.+)$/
            url_options[:platform] = $1
            url_options[:version] = $2
          when /^(.+)$/
            url_options[:platform] = $1
          end
          
          file_specificity = specificity
          file_url = absolute_slice_url(:organization_cookbook_segment, url_options)
        else
          mo = sf.match("#{Merb::Config.cookbook_cache_path}/[^/]+/#{cookbook.name}/#{segment}/(.+)")
          file_name = mo[1]
          url_options = { :cookbook_id => cookbook.name.to_s, :segment => segment, :id => file_name, :organization_id => @organization_id }
          file_url = absolute_slice_url(:organization_cookbook_segment, url_options)
        end
        rs = {
          :name => file_name, 
          :uri => file_url, 
          :checksum => checksum(sf)
        }
        rs[:specificity] = file_specificity if file_specificity
        response[segment] << rs 
      end
    end
    response
  end
  
  def load_all_files(node_name=nil)
    latest_cookbooks = get_all_latest_cookbooks
    Merb.logger.debug "Retrieved latest cookbooks: #{latest_cookbooks.inspect}"
    policies = latest_cookbooks.map{|doc| Chef::CookbookPolicy.new(doc['display_name'], doc['_id']) }
    cl = Chef::CachedCookbookLoader.new(policies)
    
    valid_cookbooks = node_name ? specific_cookbooks(node_name, cl) : {} 
    cookbook_list = Hash.new
    cl.each do |cookbook|
      if node_name
        next unless valid_cookbooks[cookbook.name.to_s]
      end
      cookbook_list[cookbook.name.to_s] = load_cookbook_files(cookbook) 
    end
    cookbook_list
  end

  def get_available_recipes
    cl = Chef::CookbookLoader.new
    available_recipes = cl.sort{ |a,b| a.name.to_s <=> b.name.to_s }.inject([]) do |result, element|
      element.recipes.sort.each do |r| 
        if r =~ /^(.+)::default$/
          result << $1
        else
          result << r
        end
      end
      result
    end
    available_recipes
  end
  
end