#!/usr/bin/env ruby require 'net/http' require 'yajl/json_gem' require 'open-uri' require 'pp' require 'cgi' require 'optparse' require 'zip' require 'zip/filesystem' require 'securerandom' class CoreDeveloper attr_accessor :name attr_accessor :factory attr_accessor :output attr_accessor :input attr_accessor :cert def login(host, port, user, pass) @host = host || '127.0.0.1' @port = port || 443 @http = Net::HTTP.new(@host, @port) @http.use_ssl = true @http.verify_mode = OpenSSL::SSL::VERIFY_NONE @http.read_timeout = 500 puts "Performing login to #{@host}:#{@port}" account = { user: user, pass: pass } resp = @http.request_post('/auth/login', account.to_json, nil) resp.kind_of? Net::HTTPSuccess or raise(resp.body) @cookie = resp['Set-Cookie'] unless resp['Set-Cookie'].nil? re = '.*?(session=)([A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12})' m = Regexp.new(re, Regexp::IGNORECASE).match(@cookie) @session = m[2] unless m.nil? puts end def logout begin @http.request_post('/auth/logout', nil, {'Cookie' => @cookie}) rescue # do nothing end puts puts "Done." end def list puts "List of cores:" puts "#{"name".ljust(15)} #{"version".ljust(10)} #{"size".rjust(15)}" resp = @http.request_get('/core', {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) list = JSON.parse(resp.body) list.each do |core| puts "- #{core['name'].ljust(15)} #{core['version'].to_s.ljust(10)} #{core['_grid_size'].to_s.rjust(15)} bytes" end end def get(output) raise "Must specify a core name" if @name.nil? puts "Retrieving [#{@name}] core..." resp = @http.request_get("/core/#{@name}", {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) File.open(output, 'wb') {|f| f.write(resp.body)} puts " --> #{output} saved (#{resp.body.bytesize} bytes)" end def content raise "Must specify a core name" if @name.nil? resp = @http.request_get("/core/#{@name}?content=true", {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) puts "Content of core #{@name}" list = JSON.parse(resp.body) list.each do |file| puts "-> #{file['name'].ljust(35)} #{file['size'].to_s.rjust(15)} bytes #{file['date'].ljust(15)}" end end def replace(file) raise "Must specify a core name" if @name.nil? content = File.open(file, 'rb') {|f| f.read} puts "Replacing [#{@name}] core with new file (#{content.bytesize} bytes)..." resp = @http.request_post("/core/#{@name}", content, {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) puts "Replaced." end def add(file, filename) raise "Must specify a core name" if @name.nil? filename = file if filename.nil? content = File.open(file, 'rb') {|f| f.read} puts "Adding [#{file}] to the [#{@name}] core (#{content.bytesize} bytes)" resp = @http.request_put("/core/#{@name}?name=#{filename}", content, {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) end def remove(file) raise "Must specify a core name" if @name.nil? puts "Removing [#{file}] from the [#{@name}] core" resp = @http.delete("/core/#{@name}?name=#{file}", {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) end def delete raise "Must specify a core name" if @name.nil? puts "Deleting [#{@name}] core" resp = @http.delete("/core/#{@name}", {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) end def retrieve_factory(ident, show, jsonfile) raise("you must specify a factory") if ident.nil? resp = @http.request_get('/factory', {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) factories = JSON.parse(resp.body) factories.keep_if {|f| f['ident'] == ident} raise('factory not found') if factories.empty? @factory = factories.first puts "Using factory: #{@factory['ident']} #{@factory['name']}" if show resp = @http.request_get("/factory/#{@factory['_id']}", {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) factory = JSON.parse(resp.body) logkey = Digest::MD5.digest(factory['logkey']) + SecureRandom.random_bytes(16) confkey = Digest::MD5.digest(factory['confkey']) + SecureRandom.random_bytes(16) puts "\t-> LOGKEY : " + logkey.unpack('H*').first puts "\t-> CONFKEY : " + confkey.unpack('H*').first resp = @http.request_get("/signature/agent", {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) signature = JSON.parse(resp.body) sig = Digest::MD5.digest(signature['value']) + SecureRandom.random_bytes(16) puts "\t-> SIGNATURE: " + sig.unpack('H*').first puts puts "CONFIG JSON:" configJson=JSON.parse(factory['configs'].first['config']) #puts JSON.pretty_generate(configJson) puts configJson end if jsonfile puts "Saving config to: " + jsonfile resp = @http.request_get("/factory/#{@factory['_id']}", {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) factory = JSON.parse(resp.body) configJson=JSON.parse(factory['configs'].first['config']) File.open(jsonfile, 'w') { |f| f.write(JSON.pretty_generate(configJson)) } end end def config(param_file) jcontent = File.open(param_file, 'r') {|f| f.read} resp = @http.request_post("/agent/add_config", {_id: @factory['_id'], config: jcontent}.to_json, {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) puts "Configuration saved" end def upload(param_file) content = File.open(param_file, 'rb') {|f| f.read} puts "Uploading file [#{param_file}]..." #RestClient.post('https://#{@host}}:#{@port}/upload', {:Filename => 'upfile', :content => File.new(param_file, 'rb')}, {:cookies => {:session => @session}}) resp = @http.request_post("/upload", content, {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) return resp.body end def build(param_file) jcontent = File.open(param_file, 'r') {|f| f.read} params = JSON.parse(jcontent) raise("factory not found") if factory.nil? raise("you must specify an output file") if output.nil? params[:factory] = {_id: @factory['_id']} # set the input file for the melting process params['melt'] ||= {} params['melt'][:input] = @input unless @input.nil? # set the cert file for the signing process params['sign'] ||= {} params['sign'][:cert] = @cert unless @cert.nil? puts "Building the agent with the following parameters:" puts params.inspect resp = @http.request_post("/build", params.to_json, {'Cookie' => @cookie}) resp.kind_of? Net::HTTPSuccess or raise(resp.body) File.open(@output, 'wb') {|f| f.write resp.body} puts puts "#{resp.body.bytesize} bytes saved to #{@output}" puts #Zip::File.open(@output) do |z| # z.each do |f| # puts "#{f.name.ljust(40)} #{f.size.to_s.rjust(10)} #{f.time}" # end #end end def self.run(options) begin c = CoreDeveloper.new c.name = options[:name] c.login(options[:db_address], options[:db_port], options[:user], options[:pass]) c.delete if options[:delete] c.replace(options[:replace]) if options[:replace] c.add(options[:add],options[:addwithname]) if options[:add] c.remove(options[:remove]) if options[:remove] c.content if options[:content] c.get(options[:get]) if options[:get] # list at the end to reflect changes made by the above operations c.list if options[:list] # building options c.retrieve_factory(options[:factory], options[:show_conf], options[:json_conf]) if options[:factory] c.output = options[:output] c.config(options[:config]) if options[:config] c.cert = c.upload(options[:cert]) if options[:cert] c.input = c.upload(options[:input]) if options[:input] c.build(options[:build]) if options[:build] rescue Exception => e puts "FATAL: #{e.message}" puts "EXCEPTION: [#{e.class}] " << e.backtrace.join("\n") return 1 ensure # clean the session c.logout end return 0 end end # This hash will hold all of the options parsed from the command-line by OptionParser. options = {} optparse = OptionParser.new do |opts| # Set a banner, displayed at the top of the help screen. opts.banner = "Usage: rcs-core [options]" opts.separator "" opts.separator "Core listing:" opts.on( '-l', '--list', 'get the list of cores' ) do options[:list] = true end opts.separator "" opts.separator "Core selection:" opts.on( '-n', '--name NAME', 'identify the core by it\'s name' ) do |name| options[:name] = name end opts.separator "" opts.separator "Core operations:" opts.on( '-g', '--get FILE', 'get the core from the db and store it in FILE' ) do |file| options[:get] = file end opts.on( '-R', '--replace CORE', 'replace the core in the db (CORE must be a zip file)' ) do |file| options[:replace] = file end opts.on( '-a', '--add FILE', 'add or replace FILE to the core on the db' ) do |file| options[:add] = file end opts.on( '-A', '--addwithname FILE', 'specify the core\'s name on the db' ) do |file| options[:addwithname] = file end opts.on( '-r', '--remove FILE', 'remove FILE from the core on the db' ) do |file| options[:remove] = file end opts.on( '-s', '--show', 'show the content of a core' ) do options[:content] = true end opts.on( '-D', '--delete', 'delete the core from the db' ) do options[:delete] = true end opts.separator "" opts.separator "Core building:" opts.on( '-f', '--factory IDENT', String, 'factory to be used' ) do |ident| factory="RCS_0000000000" options[:factory] = factory[0..-ident.size-1] + ident end opts.on( '-S', '--show-conf', 'show the config of the factory' ) do options[:show_conf] = true end opts.on( '-j', '--json JSON_FILE', String, 'save the config json in a file' ) do |file| options[:json_conf] = file end opts.on( '-b', '--build PARAMS_FILE', String, 'build the factory. PARAMS_FILE is a json file with the parameters' ) do |params| options[:build] = params end opts.on( '-c', '--config CONFIG_FILE', String, 'save the config to the specified factory' ) do |config| options[:config] = config end opts.on( '-C', '--cert FILE', String, 'certificate for the signing phase' ) do |file| options[:cert] = file end opts.on( '-i', '--input FILE', String, 'the input file for the melting phase of the build' ) do |file| options[:input] = file end opts.on( '-o', '--output FILE', String, 'the output of the build' ) do |file| options[:output] = file end opts.separator "" opts.separator "Account:" opts.on( '-u', '--user USERNAME', String, 'rcs-db username (SYS priv required)' ) do |user| options[:user] = user end opts.on( '-p', '--password PASSWORD', String, 'rcs-db password' ) do |password| options[:pass] = password end opts.on( '-d', '--db-address HOSTNAME', String, 'Use the rcs-db at HOSTNAME' ) do |host| options[:db_address] = host end opts.on( '-P', '--db-port PORT', Integer, 'Connect to tcp/PORT on rcs-db' ) do |port| options[:db_port] = port end opts.separator "" opts.separator "General:" opts.on( '-h', '--help', 'Display this screen' ) do puts opts exit! end end optparse.parse(ARGV) pp options # execute the configurator r = CoreDeveloper.run(options) puts "return #{r}" exit r .