1module MCollective 2 module Agent 3 class Puppet<RPC::Agent 4 activate_when do 5 require 'mcollective/util/puppet_agent_mgr' 6 true 7 end 8 9 def startup_hook 10 configfile = @config.pluginconf.fetch("puppet.config", nil) 11 12 @puppet_command = @config.pluginconf.fetch("puppet.command", default_agent_command) 13 @puppet_service = @config.pluginconf.fetch("puppet.windows_service", "puppet") 14 @puppet_splaylimit = Integer(@config.pluginconf.fetch("puppet.splaylimit", 30)) 15 @puppet_splay = Util.str_to_bool(@config.pluginconf.fetch("puppet.splay", "true")) 16 @puppet_agent = Util::PuppetAgentMgr.manager(configfile, @puppet_service) 17 end 18 19 # Determines the default command to run for puppet agent 20 # 21 # Return the puppet script to execute on the local platform. 22 def default_agent_command 23 if Util.windows? 24 "puppet.bat" 25 else 26 "puppet" 27 end + " agent" 28 end 29 30 def run(command, options) 31 if MCollective::Util.windows? 32 require 'win32/process' 33 # If creating the process doesn't outright fail, assume everything 34 # was okay. The caller wants to know our exit code, so we'll just use 35 # 0 or 1. 36 begin 37 ::Process.create(:command_line => command, 38 :creation_flags => ::Process::CREATE_NEW_CONSOLE) 39 0 40 rescue Exception => e 41 Log.warn("Failed to execute #{command} - #{e}") 42 1 43 end 44 else 45 # On Unices double fork and exec to run puppet in a disowned child 46 child = fork { 47 # If relying on Puppet on PATH, ensure the default AIO path is included. 48 # On Windows, the MSI adds Puppet to the PATH. 49 if command.start_with?("puppet ") 50 ENV["PATH"] += ":/opt/puppetlabs/bin" 51 end 52 53 grandchild = fork { 54 exec command 55 } 56 if grandchild != nil 57 ::Process.detach(grandchild) 58 end 59 } 60 return 1 if child.nil? 61 ::Process.detach(child) 62 return 0 63 end 64 end 65 66 action "disable" do 67 begin 68 request_msg = request.fetch(:message, "Disabled via MCollective by " \ 69 "%s at %s" % [request.caller, 70 Time.now.strftime("%F %R")]) 71 agent_msg = @puppet_agent.disable!(request_msg) 72 reply[:status] = "Succesfully locked the Puppet agent: %s" % agent_msg 73 rescue => e 74 reply.fail(reply[:status] = "Could not disable Puppet: %s" % e.to_s) 75 end 76 77 reply[:enabled] = @puppet_agent.status[:enabled] 78 end 79 80 action "enable" do 81 begin 82 @puppet_agent.enable! 83 reply[:status] = "Succesfully enabled the Puppet agent" 84 rescue => e 85 reply.fail(reply[:status] = "Could not enable Puppet: %s" % e.to_s) 86 end 87 88 reply[:enabled] = @puppet_agent.status[:enabled] 89 end 90 91 action "last_run_summary" do 92 summary = @puppet_agent.load_summary 93 94 if request[:logs] 95 reply[:logs] = @puppet_agent.last_run_logs 96 else 97 reply[:logs] = {} 98 end 99 100 reply[:type_distribution] = @puppet_agent.managed_resource_type_distribution 101 reply[:out_of_sync_resources] = summary["resources"].fetch("out_of_sync", 0) 102 reply[:failed_resources] = summary["resources"].fetch("failed", 0) 103 reply[:corrected_resources] = summary["resources"].fetch("corrective_change", 0) 104 reply[:changed_resources] = summary["resources"].fetch("changed", 0) 105 reply[:total_resources] = summary["resources"].fetch("total", 0) 106 reply[:total_time] = summary["time"].fetch("total", 0) 107 reply[:config_retrieval_time] = summary["time"].fetch("config_retrieval", 0) 108 reply[:lastrun] = Integer(summary["time"].fetch("last_run", 0)) 109 reply[:since_lastrun] = Integer(Time.now.to_i - reply[:lastrun]) 110 reply[:config_version] = summary["version"].fetch("config", "unknown") 111 reply[:summary] = summary 112 end 113 114 action "status" do 115 status = @puppet_agent.status 116 117 @reply.data.merge!(status) 118 end 119 120 action "resource" do 121 allow_managed_resources_management = Util.str_to_bool( 122 @config.pluginconf.fetch("puppet.resource_allow_managed_resources", "false")) 123 resource_types_whitelist = \ 124 @config.pluginconf.fetch("puppet.resource_type_whitelist", nil) 125 resource_types_blacklist = \ 126 @config.pluginconf.fetch("puppet.resource_type_blacklist", nil) 127 128 if resource_types_whitelist && resource_types_blacklist 129 reply.fail!("You cannot specify both puppet.resource_type_whitelist " \ 130 "and puppet.resource_type_blacklist in the config file") 131 end 132 133 # if 'none' is specified whitelist nothing 134 resource_types_whitelist = "" if resource_types_whitelist == "none" 135 136 # if neither is specified default to whitelisting 137 # nothing thus denying everything 138 if resource_types_blacklist.nil? && resource_types_whitelist.nil? 139 resource_types_whitelist = "" 140 end 141 142 143 params = request.data.clone 144 params.delete(:process_results) 145 type = params.delete(:type).downcase 146 resource_name = "%s[%s]" % [type.to_s.capitalize, params[:name]] 147 148 resource_names_whitelist = \ 149 @config.pluginconf.fetch("puppet.resource_name_whitelist.%s" % type, nil) 150 resource_names_blacklist = \ 151 @config.pluginconf.fetch("puppet.resource_name_blacklist.%s" % type, nil) 152 153 if resource_names_whitelist && resource_names_blacklist 154 reply.fail!("You cannot specify both puppet.resource_name_whitelist.%s " \ 155 "and puppet.resource_name_blacklist.%s in the config file" % [type, type]) 156 end 157 158 if resource_types_blacklist 159 if resource_types_blacklist.split(",").include?(type) 160 reply.fail!("The %s type is listed in the type blacklist" % type) 161 end 162 elsif resource_types_whitelist 163 unless resource_types_whitelist.split(",").include?(type) 164 reply.fail!("The %s type is not listed in the type whitelist" % type) 165 end 166 end 167 168 if resource_names_blacklist 169 if resource_names_blacklist.split(",").include?(params[:name]) 170 reply.fail!("The %s name is listed in the name blacklist" % params[:name]) 171 end 172 elsif resource_names_whitelist 173 unless resource_names_whitelist.split(",").include?(params[:name]) 174 reply.fail!("The %s name is not listed in the name whitelist" % params[:name]) 175 end 176 end 177 178 if allow_managed_resources_management \ 179 || !@puppet_agent.managing_resource?(resource_name) 180 resource = ::Puppet::Type.type(type).new(params) 181 report = ::Puppet::Transaction::Report.new(:mcollective) 182 ::Puppet::Util::Log.newdestination(report) 183 catalog = ::Puppet::Resource::Catalog.new 184 catalog.add_resource(resource) 185 catalog.apply(:report => report) 186 187 if report.logs.empty? 188 reply[:result] = "no output produced" 189 else 190 reply[:result] = report.logs.join("\n") 191 end 192 193 reply[:changed] = report.resource_statuses[resource_name].changed 194 195 if report.resource_statuses[resource_name].failed 196 reply.fail!("Failed to apply %s: %s" % [resource_name, reply[:result]]) 197 end 198 else 199 reply.fail!("Puppet is managing the resource '%s', " \ 200 "refusing to create conflicting states" % resource_name) 201 end 202 end 203 204 action "runonce" do 205 args = {} 206 207 if @puppet_agent.disabled? 208 message = @puppet_agent.lock_message 209 210 if message == "" 211 reply.fail!(reply[:summary] = "Puppet is disabled") 212 else 213 reply.fail!(reply[:summary] = "Puppet is disabled: '%s'" % message) 214 end 215 end 216 217 args[:options_only] = true 218 args[:noop] = request[:noop] if request.include?(:noop) 219 args[:environment] = request[:environment] if request[:environment] 220 if request[:server] 221 if Util.str_to_bool(@config.pluginconf.fetch("puppet.allow_server_override","false")) 222 args[:server] = request[:server] 223 else 224 reply.fail!(reply[:summary] = "Passing 'server' option is not allowed in module configuration") 225 end 226 end 227 args[:tags] = request[:tags].split(",").map{|t| t.strip} if request[:tags] 228 args[:ignoreschedules] = request[:ignoreschedules] if request[:ignoreschedules] 229 args[:signal_daemon] = false if MCollective::Util.windows? 230 args[:use_cached_catalog] = request[:use_cached_catalog] if request.include?(:use_cached_catalog) 231 232 # we can only pass splay arguments if the daemon isn't in signal mode :( 233 signal_daemon = Util.str_to_bool(@config.pluginconf.fetch("puppet.signal_daemon","true")) 234 unless @puppet_agent.status[:daemon_present] && signal_daemon 235 if request[:force] == true 236 # forcing implies --no-splay 237 args[:splay] = false 238 else 239 # respect splay options 240 args[:splay] = request[:splay] if request.include?(:splay) 241 args[:splaylimit] = request[:splaylimit] if request.include?(:splaylimit) 242 243 unless args.include?(:splay) 244 args[:splay] = @puppet_splay 245 end 246 247 if !args.include?(:splaylimit) && args[:splay] 248 args[:splaylimit] = @puppet_splaylimit 249 end 250 end 251 end 252 253 begin 254 run_method, options = @puppet_agent.runonce!(args) 255 rescue => e 256 reply.fail!(reply[:summary] = e.to_s) 257 end 258 259 command = [@puppet_command].concat(options).join(" ") 260 261 case run_method 262 when :run_in_foreground 263 Log.debug("Initiating a puppet agent run using the command: %s" % command) 264 265 exitcode = run(command, { 266 :stdout => :summary, 267 :stderr => :summary, 268 :chomp => true, 269 }) 270 271 unless exitcode == 0 272 reply.fail!(reply[:summary] = "Puppet command '%s' had exit " \ 273 "code %d, expected 0" \ 274 % [command, exitcode]) 275 else 276 reply[:summary] = "Started a Puppet run using the " \ 277 "'%s' command" % command 278 end 279 280 when :signal_running_daemon 281 Log.debug("Signaling the running Puppet agent " \ 282 "to start an immediate run") 283 @puppet_agent.signal_running_daemon 284 reply[:summary] = "Signalled the running Puppet Daemon" 285 286 else 287 reply.fail!(reply[:summary] = "Do not know how to do puppet runs " \ 288 "using method %s" % run_method) 289 end 290 reply[:initiated_at] = Time.now.to_i 291 end 292 end 293 end 294end 295