#!/usr/bin/env ruby # # etl [run] [options] ('run' is optional) # # executable of the gem datapipeline; in this repository bin/etl loads the bundle first # # --from=DATE --till=DATE in DataPipeline.params, also passed on to the pipeline script # --param key=value in DataPipeline.params[:key], repeatable # --dry-run run everything, write nothing, print the first rows # --limit=N only the first N source rows # --verbose log level debug, --quiet only warnings (default info) # --lock=FILE exclusive lock for the whole script, exit 3 when another run holds it # --name=value any other option: DataPipeline.params[:name] and passed on to the script # # exit code: 0 ok, 1 aborted, 2 finished with rejected rows, 3 locked by another run # require 'optparse' require 'logger' require_relative '../lib/datapipeline' options = { settings: { log_level: Logger::INFO }, passthrough: [] } DataPipeline.params = {} parser = OptionParser.new do |opts| opts.banner = "Usage: etl [run] PIPELINE.rb [options]\n\n etl examples/kunden_import.rb --dry-run\n" opts.on('--from=DATE', String) { |v| DataPipeline.params[:from] = v; options[:passthrough] << "--from=#{v}" } opts.on('--till=DATE', String) { |v| DataPipeline.params[:till] = v; options[:passthrough] << "--till=#{v}" } opts.on('--param KEY=VALUE', String) do |v| key, value = v.split('=', 2) DataPipeline.params[key.to_sym] = value end opts.on('--dry-run') { options[:settings][:dry_run] = true } opts.on('--limit=N', Integer) { |v| options[:settings][:limit] = v } opts.on('--verbose') { options[:settings][:log_level] = Logger::DEBUG } opts.on('--quiet') { options[:settings][:log_level] = Logger::WARN } opts.on('--lock=FILE', String) { |v| options[:lock] = v } end # own options for the parser, everything else (also --name=value of the script) goes to the script own = [] args = [] argv = ARGV.dup while (arg = argv.shift) if arg.match?(/\A--(from|till|param|dry-run|limit|verbose|quiet|lock)(=|\z)/) own << arg own << argv.shift if %w[--param --limit --from --till].include?(arg) else args << arg end end parser.parse(own) args.shift if args.first == 'run' script = args.shift DataPipeline.params.merge!(DataPipeline.parse_params(args)) { |_key, own_value, _| own_value } abort parser.help if script.nil? abort "etl: #{script} not found\n\n#{parser.help}" unless File.exist?(script) DataPipeline.settings(options[:settings]) begin script_lock = DataPipeline::Lock.new(options[:lock]).acquire if options[:lock] # held until the process ends rescue DataPipeline::LockError => e warn "#{File.basename(script)}: #{e.message}" exit 3 end # the pipeline script sees only its own arguments ARGV.replace(options[:passthrough] + args) begin load File.expand_path(script) rescue StandardError => e warn "#{File.basename(script)}: #{e.class}: #{e.message}" exit(e.is_a?(DataPipeline::LockError) ? 3 : 1) end runs = DataPipeline.runs exit 3 if runs.any? { |stats| stats.error.is_a?(DataPipeline::LockError) } exit 1 if runs.any?(&:error) exit 2 if runs.any? { |stats| stats.rejected.positive? } exit 0