From 7e71d62bce2ddf1166c89514629ca755281bd497 Mon Sep 17 00:00:00 2001 From: Tristan Ancelet Date: Sun, 9 Jun 2024 21:16:20 -0500 Subject: [PATCH] Made a lot of progress --- lib/host.rb | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/manager.rb | 43 +++++++++++++++++++++++++ lib/resource.rb | 65 ++++++++++++++++++++++++++++++++++++++ lib/rubyqa.rb | 7 +++++ lib/test | 29 +++++++++++++++++ lib/test.rb | 71 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 298 insertions(+) create mode 100644 lib/host.rb create mode 100644 lib/manager.rb create mode 100644 lib/resource.rb create mode 100644 lib/rubyqa.rb create mode 100644 lib/test create mode 100644 lib/test.rb diff --git a/lib/host.rb b/lib/host.rb new file mode 100644 index 0000000..9ac0ef5 --- /dev/null +++ b/lib/host.rb @@ -0,0 +1,83 @@ +module RubyQA + require 'net/ssh' + class Host + DEFAULT_HOSTDATA={ + :hostname => "", + :ip => "", + :port => 22, + :user => "", + :password => "", + :site => "", + :cluster => false, + :sudo_password => "", + } + + attr_accessor *DEFAULT_HOSTDATA.keys + attr_reader :client, :data, :resources + + def initialize( data = {} ) + @resources = Hash.new + + @data = DEFAULT_HOSTDATA.merge data + + ## Verifying the (Needed) variables are set to valid values (and/or can be resolved to valid values) + verify_data + + ## initialize client for usage tests + init_client + end + + def init_client + @client = Net::SSH.start(@data[:ip], @data[:user]) + end + + def exec (command) + @client.exec!(command) + end + + def [](key) + @data[key] + end + + def name + if @data[:hostname] != "" + return @data[:hostname] + elsif @resources['facts'].data['networking']['hostname'] != "" + return @resources['facts'].data['networking']['hostname'] + else + return @data[:ip] + end + end + + def add_resource (object) + ## If the requirements are met by the host, add the resource + if object::REQUIREMENTS.all?{|key,val| @data[key] == val} + resource = object.new(self) + @resources[resource.name]=resource + end + end + + def update_resources + @resources.each_value(&:gather) + end + + def verify_data + if @data[:ip].empty? + if not @data[:hostname].empty? + begin + @data[:ip] = Resolv.getaddress(@data[:hostname]) + rescue Resolv::ResolvError => error + raise "Host: Hostname provided, but did not resolve to IP" + end + else + raise "Host: No hostname or IP provided" + end + end + + if @data[:user].empty? + raise ArgumentError, "Host: User not provided" + end + + end + end +end diff --git a/lib/manager.rb b/lib/manager.rb new file mode 100644 index 0000000..95a8849 --- /dev/null +++ b/lib/manager.rb @@ -0,0 +1,43 @@ +module RubyQA + class Manager + attr_reader :resources + @@tests = Array.new + + def initialize + @hosts = Array.new + end + + def add_host(host) + @hosts << host + register_resources(host) + update_resources(host) + end + + def register_resources (host) + Resource.all_resources.each do |resource| + host.add_resource(resource) + end + end + + def update_resources (host) + host.update_resources + end + + def self.new_test(name, options={}, &test_proc) + @@tests << Test.new(name, **options, &test_proc) + end + + def run_tests + @hosts.each do |host| + tests = @@tests.select{|test| test.valid_host(host)} + tests.each do |test| + test.run(host) + end + end + end + + def report + puts @@tests.map(&:report).join("\n") + end + end +end diff --git a/lib/resource.rb b/lib/resource.rb new file mode 100644 index 0000000..7555bf4 --- /dev/null +++ b/lib/resource.rb @@ -0,0 +1,65 @@ +module RubyQA + class Resource + attr_reader :name, :data + REQUIREMENTS = {} + + def initialize (host) + @host = host + @data = Hash.new + @gather_command = "" + end + + def gather + if not @gather_command.empty? + output = @host.exec(@gather_command) + parse(output) + else + raise "@gather_command was not defined on Resource[#{@name}]" + end + end + + def parse(output) + raise "parse not yet implemented on Resource[#{@name}]" + end + + ## This will allow me to iterate through all subclasses without + ## having to manually define them elsewhere + def self.all_resources + ObjectSpace.each_object(Class).select{|klass| klass < self} + end + end + + + ## Gathers the facts from the remote machine via the facter utility + require 'json' + class Facts < Resource + REQUIREMENTS = {} + def initialize (host) + super host + @name = 'facts' + @gather_command = "facter -j" + end + + def parse (output) + @data = JSON.load(output) + end + end + + class DRDB < Resource + REQUIREMENTS = { + :cluster => true + } + + def initialize (host) + super host + @name = 'facts' + @gather_command = "sudo drbdadm" + end + + def parse (output) + @data = output + end + end + + +end diff --git a/lib/rubyqa.rb b/lib/rubyqa.rb new file mode 100644 index 0000000..c34143b --- /dev/null +++ b/lib/rubyqa.rb @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +$LOAD_PATH << __dir__ +require 'manager.rb' +require 'resource.rb' +require 'host.rb' +require 'test.rb' diff --git a/lib/test b/lib/test new file mode 100644 index 0000000..223ad70 --- /dev/null +++ b/lib/test @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby + +require './rubyqa' + +include RubyQA +$Manager = Manager.new + +host = Host.new( + :ip => '127.0.0.1', + :user => 'tristan', + :cluster => true +) + +Manager.new_test("hostname in /etc/hostname") {|host| + hostname = host.exec('cat /etc/hostname').strip + facts = host.resources['facts'].data + facts['networking']['hostname'] == hostname +} + +Manager.new_test("hostname in /etc/hosts", :cluster => true ) {|host| + facts = host.resources['facts'].data + hostname = facts['networking']['hostname'] + hosts = host.exec('cat /etc/hosts') + match_regex = /127\.0\.0\.1.+#{hostname.downcase}/ + hosts.match? match_regex +} +$Manager.add_host(host) +$Manager.run_tests +$Manager.report diff --git a/lib/test.rb b/lib/test.rb new file mode 100644 index 0000000..28ea61a --- /dev/null +++ b/lib/test.rb @@ -0,0 +1,71 @@ +module RubyQA + require 'erb' + class Test + DEFAULT_OPTIONS={ + :site => "", + :cluster => false, + :has_key => "", + }.freeze + + attr_reader :name, :options, :proc + + def initialize (name, options ={}, &test_block) + @tests = Hash.new + @name = name + @options = DEFAULT_OPTIONS.merge(options) + @test = test_block + @description = "" + if options[:description] + @description=options[:description] + end + end + + def valid_host(host) + ## By default if options weren't changed, then + ## we just assume it is to be tested against all + ## provided hosts + is_valid = 0 + if @options == DEFAULT_OPTIONS + return true + end + + DEFAULT_OPTIONS.each do |key,value| + if @options[key] != value and host[key] != @options[key] + return false + end + end + + return true + end + + def run (host) + test_status = "N/A" + if @test.call(host) + test_status = "PASSED" + else + test_status = "FAILED" + end + @tests[host.name]=test_status + end + + def report + if @tests.count == 0 + return + end + template = ERB.new < '-' +============================================== +Test : "<%= @name %>" +<% if not @description.empty? -%> +Description : "<%= @description %> +<% end -%> +============================================== +<% @tests.each do |key,val| -%> +<%= key %> : <%= val %> +<% end -%> + +EOF + template.result(binding) + end + end +end +