require 'date' require 'drb/drb' require 'colorize' require 'fileutils' require 'json' require 'ostruct' require 'rake' require 'yaml' require_relative 'config' require_relative 'email_client' require_relative 'task' require_relative 'execution' require_relative 'execution_service' require_relative 'input' require_relative 'logger' require_relative 'logger_service' require_relative 'container/version' module Henry # Henry Container class Container # Initialize the Container with the given options # # @param [Hash] the execution given options. def initialize(opts) @opts = opts end # Executes the loaded tasks and stores their results. def execute self.before_execution enabled_tasks = self.tasks.select {|task| task.enabled?} self.alert(:init, :tasks => enabled_tasks, :params => self.params) self.params.each_with_index do |params, index| tasks_results_set = [] enabled_tasks.each_with_index do |task, task_index| self.alert(:task, :index => task_index+enabled_tasks.count*index+1, :total => enabled_tasks.count*self.params.count) task_params = (params['all'] || {}).merge(params[task.name] || {}) task.configure(task_params,self.task_extended_context(task_params)) task.export_params(task_params) task.export_config({output_directory:task.data.system[:output_directory]}) task.execution.params = task_params task.before_execute self.execute_task(task) task.after_execute tasks_results_set << task.report end self.tasks_results << tasks_results_set end self.update_results self.alert(:result) self.dump_results end protected # Initial state the conteiner execution results. # # @return [Hash] the results template. RESULTS_TEMPLATE = { summary: { total: 0, breakdownByStatusCode: { 'OK' => 0, 'ERROR' => 0, 'WARNING' => 0 } }, tasks: [] } # Paths collection of the out.henry templates # # @return [Hash] the paths of the out.henry templates TEMPLATE_PATHS = { json_parse_error: 'templates/errors/json_parse_error.json' } def alert(type, data={}) case type when :init then puts <___| /__| / ____| \\/ \\/ \\/ \\/ henry-ruby-container #{Henry::Container::VERSION} (http://henry.despegar.com/doc) ------------------------------------------------------------------------ --- [ Starting ] --- [ Tasks: #{data[:tasks].collect(&:name).join(', ')} ] --- [ Parameter set count: #{data[:params].count} ] LOGO when :result then puts < String}>] collection of execution params. def params @params ||= self.load_params end # Loads and parses the @opts[:in] params, if they exists. # # @return [<{String => String}>] collection of execution params. def load_params self.reload_params! return [{}] unless File.exists?(self.input_file_path) params = JSON.parse(File.read(self.input_file_path)) return [{}] if params.empty? params end # Creates a dynamic in.henry based on the in-rules and # in-files. def reload_params! return false unless self.reload_params? in_files_params = self.in_files_params in_rules = self.in_rules params = in_rules.collect do |rule| rule.inject({}) do |param, in_file| in_files_params[in_file].each do |key, value| param[key] ||= {} param[key].merge!(value) end param end end File.open(self.input_file_path, 'w') do |input_file| JSON.dump(params, input_file) end end # Loads and returns all the in-files as a hash # # @return [Hash] the in-files with its values. def in_files_params return @in_files_params if @in_files_params @in_file_params = {} Dir.new('./in-files').each do |file_name| next if file_name.match(/^\.+$/) @in_file_params[file_name] = JSON.parse(File.read("./in-files/#{file_name}")) end return @in_file_params end # Loads and returns the in-rules as hash. # # @return [Hash] the in-file values. def in_rules @in_rules ||= JSON.parse(File.read('in-rules.henry')) end # Returns the default params. # @note Custom task_params will overwrite the defaults. # # @return [Hash] the default params. def default_params @default_params ||= (self.params['all'] || {}) end # Returns the custom params for the given task. # @note default_params will be used for undefined keys. # # @param [String] task_name the target Task name. # @return [Array] the Task custom params. def task_params(task_name) self.params.collect do |params| (params['all'] || {}).merge(params[task_name] || {}) end end # Return the default extended execution. # @note Custom extended_executions will overwrite the defaults. # # @return [Hash] the Task default extended context def default_extended_context {} end # Retrun the custom extended_context fo the given task. # @note default_extended_context will be used for undefined keys. # # @param [Hash] task_params the params of the target Task. # @return [Hash] the Task custom extended context. def extended_context(task_params) task_params['extended_context'] end # Returns the custom execution_context for the given task. # @note default_execution_context will be used for undefined keys. # # @param [Hash] task_params the params of the target Task. # @return [Hash] the Task custom execution_context. def task_extended_context(task_params) self.default_extended_context.merge(self.extended_context(task_params) || {}) end # Returns de task hints (tasks to be executed) or an empty array if they are not defined. # # @return [] the task hints. def task_hints self.hints["tasks"] || [] end # Returns de hints or an empty Hash if they are not defined. # # @return [Hash] the hints. def hints @hints ||= self.load_hints end # Loads and parses the @opts[:hints] params, if they exists. # # @return [Hash] the hints:w def load_hints # To be removed once that the Worker starts using the --hints-path if File.exists?(self.hints_file_patched_path) return JSON.parse(File.read(self.hints_file_patched_path)) end return {} unless File.exists?(self.hints_file_path) JSON.parse(File.read(self.hints_file_path)) end # Load and parses the henry-context.yml params. # # @return [{String => String}] the execution context. def context @context ||= OpenStruct.new(YAML.load_file('henry-context.yml')) end # Returns the tasks to be executed based on the context. # # @return [] tasks to be executed. def tasks @tasks ||= self.build_tasks end # Builds and disables the Tasks to be executed based on the context and hints. # # @return [] tasks to be executed. def build_tasks tasks = self.context.tasks.collect do |task_data| Task.create(task_data["name"], task_data.merge({system: self.default_task_system_context})) end return tasks if self.task_hints.empty? tasks.each do |task| task.disable! unless self.task_hints.include? task.name end end # Returns the execution's current results. # # @return [Hash] the execution's current resutls. def results @results ||= RESULTS_TEMPLATE end # Returns the tasks_results node from the results hash. # # @return [Array] def tasks_results self.results[:tasks] end # Sets the tasks_results node of the results hash. # # @param [Array] results the tasks_results collection. def tasks_results=(results) self.results[:tasks] = results end # Updates the results attributes based on the task_results. def update_results self.tasks_results.each do |task_results_set| task_results_set.each do |task_results| self.results[:summary][:total] += 1 self.results[:summary][:breakdownByStatusCode][task_results[:code]] += 1 end end end # Writes into @opts[:out] the execution results. def dump_results File.open(self.output_file_path, 'w') { |f| f.write(results.to_json) } begin JSON.parse(File.read(self.output_file_path)) rescue JSON::ParserError FileUtils.copy(TEMPLATE_PATHS[:json_parse_error], self.output_file_path) end end end end