diff --git a/.gitignore b/.gitignore index 3e7b48d..f5587e6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,6 @@ /spec/reports/ /tmp/ -/Gemfile.lock - # ---> Vim # Swap [._]*.s[a-v][a-z] diff --git a/Gemfile b/Gemfile index 844a1ab..548b363 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,4 @@ source "https://rubygems.org" -ruby '>=2.7' # Specify your gem's dependencies in knot.gemspec gemspec diff --git a/knot-ruby.gemspec b/knot-ruby.gemspec index d8dded8..9076bd2 100644 --- a/knot-ruby.gemspec +++ b/knot-ruby.gemspec @@ -1,7 +1,7 @@ require_relative 'lib/knot/version' Gem::Specification.new do |spec| - spec.name = "knot-ruby" + spec.name = "knot" spec.version = Knot::VERSION spec.authors = ['Denis Knauf'] spec.email = ['gems+knot@denkn.at'] @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| spec.summary = %q{Provides interface to knot-server.} spec.description = %q{Implements knot-protocol to provide an interface to knot-DNS-server} spec.homepage = 'https://git.denkn.at/deac/knot' - spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0") + spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") #spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" diff --git a/lib/knot.rb b/lib/knot.rb index 32f0701..4231108 100644 --- a/lib/knot.rb +++ b/lib/knot.rb @@ -2,13 +2,3 @@ require 'knot/version' require 'knot/errors' require 'knot/protocol' require 'knot/interface' - -module Knot - class < [a-z0-9_-]+ ) - (?: \[ (? [a-z0-9_.-]+) \] )? - (?: \. (?[a-z0-9_-]+) )? + (?
[a-z0-9_-]+ ) + (?: \[ (? [a-z0-9_.-]+) \] )? + (?: \. (?[a-z0-9_-]+) )? \z/xi - - $~.named_captures.delete_if {|_,v| v.nil? } - - when nil - {} - - else - raise ArgumentError, "Invalid Item-format: #{kv}" + $~.named_captures.delete_if {|_,v| v.nil? } + else raise ArgumentError, "Invalid Item-format" end end - def get item = nil - @protocol.call **parse_item( item).update( command: 'conf-get') - end - - # Sets or adds a new value to item. - # knot knows single-value items like `server.rundir` and multi-value items like `server.listen`. - # If you set a single-value, it will replace the old value. On a multi-value, it will add it. def set item, value - @protocol.call **parse_item( item).update( command: 'conf-set', data: value) + @protocol.call parse_item( item).update( command: 'conf-set', data: value) end + alias [] set - # Removes value from item. If you provide a value, this value will be removed. def unset item, value = nil - @protocol.call **parse_item( item).update( command: 'conf-unset', data: value) + @protocol.call parse_item( item).update( command: 'conf-unset', data: value) end alias delete unset def list item = nil - @protocol.call **parse_item( item).update( command: 'conf-list') + @protocol.call (item ? parse_item( item) : {}).update( command: 'conf-list') end def read item = nil - @protocol.call **parse_item( item).update( command: 'conf-read') + @protocol.call (item ? parse_item( item) : {}).update( command: 'conf-read') end end diff --git a/lib/knot/protocol.rb b/lib/knot/protocol.rb index e05a112..903697b 100644 --- a/lib/knot/protocol.rb +++ b/lib/knot/protocol.rb @@ -1,7 +1,4 @@ require 'iounpack' -require 'pathname' -require 'socket' -require 'logger' require_relative 'errors' module Knot @@ -54,120 +51,44 @@ module Knot::Protocol::Type end end -#class Knot::KnotC -# attr_accessor :binary -# -# def initialize path = nil, binary: nil -# @path = path -# @binary = binary || 'knotc' -# @conf = Knot::Conf.new self -# @zones = Hash.new {|h, zone| h[zone] = Knot::Zone.new zone, self } -# end -# -# def call command:, flags: nil, section: nil, item: nil, id: nil, zone: nil, owner: nil, ttl: nil, type: nil, data: nil, filter: nil -# cs = -# case command.to_s -# when 'conf-begin', 'conf-commit', 'conf-abort', 'status', 'stop', 'reload' -# [@binary, command, ] -# else raise ArgumentError, "Unknown Command: #{command}" -# end -# end -#end - -class Knot::Protocol::Code - include Comparable - attr_reader :name, :code, :cname, :description - - def initialize name, code, cname, description - raise ArgumentError, "Expecting Symbol for #{self.class.name} instead of: #{name.inspect}" unless Symbol === name - raise ArgumentError, "Expecting Integer for #{self.class.name} instead of: #{code.inspect}" unless Integer === code - @name, @code, @cname, @description = name, code, cname, description - freeze +module Knot::Protocol::Idx + Idx = [ + :command, # 10, :CMD, # Control command name. + :flags, # 11, :FLAGS, # Control command flags. + :error, # 12, :ERROR, # Error message. + :section, # 13, :SECTION, # Configuration section name. + :item, # 14, :ITEM, # Configuration item name. + :id, # 15, :ID, # Congiguration item identifier. + :zone, # 16, :ZONE, # Zone name. + :owner, # 17, :OWNER, # Zone record owner + :ttl, # 18, :TTL, # Zone record TTL. + :type, # 19, :TYPE, # Zone record type name. + :data, # 1a, :DATA, # Configuration item/zone record data. + :filter, # 1b, :FILTER, # An option or a filter for output data processing. + ] + Name = {} + Code = {} + Idx.each_with_index do |v, i| + Code[0x10+i] = v + Name[v] = i end - - def === x - case x - when self.class then @id == x.id - when Symbol then @name == x - when String then @name == x.to_sym - when Integer then @code == x - else nil - end - end - - def <=>( x) @id <=> x.id end - def to_s() @name.to_s end - def to_sym() @name end - def to_i() @code end -end - -module Knot::Protocol::Codes - include Enumerable - def [] k + def self.[] k case k when Symbol - self::Name[k] or raise Knot::Errors::EINVAL, "Unknown Codes: #{k}" + Name[k] or raise Knot::Errors::EINVAL, "Unknown Idx: #{k}" when Integer - self::Code[k] or raise Knot::Errors::EINVAL, "Unknown Codes: #{k}" + Idx[k] or raise Knot::Errors::EINVAL, "Unknown Idx: #{k}" else - raise ArgumentError, "Unknown Codes-Type: #{k}" + raise ArgumentError, "Unknown Idx-Type: #{k}" end end - - def each &exe - block_given? ? self::Codes.each( &exe) : self::Codes.to_enum( :each) - end -end - -module Knot::Protocol::Idx - extend Knot::Protocol::Codes - - Codes = [ - Knot::Protocol::Code.new( :command, 0x10, :CMD, 'Control command name.'), - Knot::Protocol::Code.new( :flags, 0x11, :FLAGS, 'Control command flags.'), - Knot::Protocol::Code.new( :error, 0x12, :ERROR, 'Error message.'), - Knot::Protocol::Code.new( :section, 0x13, :SECTION, 'Configuration section name.'), - Knot::Protocol::Code.new( :item, 0x14, :ITEM, 'Configuration item name.'), - Knot::Protocol::Code.new( :id, 0x15, :ID, 'Congiguration item identifier.'), - Knot::Protocol::Code.new( :zone, 0x16, :ZONE, 'Zone name.'), - Knot::Protocol::Code.new( :owner, 0x17, :OWNER, 'Zone record owner'), - Knot::Protocol::Code.new( :ttl, 0x18, :TTL, 'Zone record TTL.'), - Knot::Protocol::Code.new( :type, 0x19, :TYPE, 'Zone record type name.'), - Knot::Protocol::Code.new( :data, 0x1a, :DATA, 'Configuration item/zone record data.'), - Knot::Protocol::Code.new( :filter, 0x1b, :FILTER, 'An option or a filter for output data processing.'), - ] - Name = {} - Code = {} - - Codes.each do |id| - Code[id.to_i] = id - Name[id.to_sym] = id - end -end - -module Knot::Protocol::Types - extend Knot::Protocol::Codes - - Codes = [ - Knot::Protocol::Code.new( :end, 0x00, :END, 'Type END.'), - Knot::Protocol::Code.new( :data, 0x01, :DATA, 'Type DATA.'), - Knot::Protocol::Code.new( :extra, 0x02, :EXTRA, 'Type EXTRA.'), - Knot::Protocol::Code.new( :block, 0x03, :BLOCK, 'Type BLOCK.'), - ] - - Name = {} - Code = {} - - Codes.each do |id| - Code[id.to_i] = id - Name[id.to_sym] = id - end end class Knot::Protocol - attr_reader :sock, :conf, :zones, :logger + attr_reader :sock, :conf, :zones + attr_accessor :debug - def initialize path_or_sock = nil, logger: nil + def initialize path_or_sock = nil case path_or_sock when String, Pathname @sock = UNIXSocket.new path_or_sock.to_s @@ -176,80 +97,77 @@ class Knot::Protocol when nil @sock = UNIXSocket.new '/run/knot/knot.sock' end - @logger = logger || Logger.new(STDERR) + @debug = false @conf = Knot::Conf.new self @zones = Hash.new {|h, zone| h[zone] = Knot::Zone.new zone, self } end def snd sock: nil, **data rsock = sock || @sock - data = Hash[ *data.map {|k,v| [k.to_sym, v]}.flatten] + s = '' + sock = StringIO.new s + sock.write [1].pack( 'c') data[:flags] ||= '' - ds = - Idx. - select {|n| data[n.to_sym] }. - map {|n| v = data[n.to_sym].to_s.b; [n.to_i, v.size, v] } - s = [Types[:data].to_i, ds, Types[:block].to_i].flatten.pack( "c #{'c na*'*ds.length} c").b - if 0 >= @logger.sev_threshold - @logger.debug "send data #{data.inspect}" - @logger.debug "send raw #{s.inspect}" + Idx::Idx.each_with_index do |n, i| + v = data[n]&.to_s + sock.write [0x10+i, v.size, v].pack( 'c na*') if v end + sock.write [3].pack( 'c') + sock.flush + STDERR.puts( {data: data, _: s}.inspect) if @debug rsock.write s rsock.flush - end + end class RecordIO attr_reader :str def initialize sock, str = nil - @str, @sock = str || ''.b, sock + @str, @sock = str || '', sock end def unpack pattern - IOUnpack.new( pattern).unpack self + IOUnpack.new(pattern).unpack self end def unpack1 pattern - IOUnpack.new( pattern).unpack1 self + IOUnpack.new(pattern).unpack1 self end def read n s = @sock.read n - @str.insert -1, s unless s.nil? - s || '' + @str.insert -1, s + s end end def rcv sock: nil ret, r = [], nil sock = sock || @sock - sock = RecordIO.new sock if 0 >= @logger.sev_threshold + sock = RecordIO.new sock if @debug loop do t = sock.unpack1 'c' case t - when Knot::Protocol::Types[:end], Knot::Protocol::Types[:block] + when 0, 3 return ret - when Knot::Protocol::Types[:data], Knot::Protocol::Types[:extra] + when 1, 2 type = t ret.push( r = {}) - else + else raise Knot::Errors::EINVAL, "Missing Type before: #{t}" if ret.empty? - i = Idx[t] or raise Knot::Errors::EINVAL, "Unknown index: #{t}" + i = Idx::Idx[t - 0x10] or raise Knot::Errors::EINVAL, "Unknown index: #{t-0x10}" l = sock.unpack1 'n' - r[i.to_sym] = sock.read( l) + r[i] = sock.read( l) end end ensure - if RecordIO === sock - @logger.debug "rcvd raw #{sock.str.inspect}" - @logger.debug "rcvd data #{ret.inspect}" - end + STDERR.puts( {rcvd: ret, read: sock.str}.inspect) if @debug ret end def call sock: nil, **data snd sock: sock, **data - rcv( sock: sock).each do |r| + rcv( sock: sock).each do |r| if r[:error] if e = Knot::Errors.err2exc[r[:error]] raise e, r[:error] @@ -261,10 +179,10 @@ class Knot::Protocol def zone( zone) @zones[zone.to_s.to_sym] end - def conf_set( **opts) call **opts.update( command: 'conf-set') end - def conf_unset( **opts) call **opts.update( command: 'conf-unset') end + def conf_set( **opts) call opts.update( command: 'conf-set') end + def conf_unset( **opts) call opts.update( command: 'conf-set') end - def zone_set( **opts) call **opts.update( command: 'zone-set') end - def zone_unset( **opts) call **opts.update( command: 'zone-unset') end - def zone_get( **opts) call **opts.update( command: 'zone-get') end + def zone_set( **opts) call opts.update( command: 'zone-set') end + def zone_unset( **opts) call opts.update( command: 'zone-unset') end + def zone_get( **opts) call opts.update( command: 'zone-get') end end diff --git a/lib/knot/version.rb b/lib/knot/version.rb index 46de30f..4db3f7f 100644 --- a/lib/knot/version.rb +++ b/lib/knot/version.rb @@ -1,3 +1,3 @@ module Knot - VERSION = "0.3.2" + VERSION = "0.1.0" end