diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64c1784..a732b2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,9 +16,6 @@ jobs: strategy: matrix: ruby-version: - - '2.5' - - '2.6' - - '2.7' - '3.0' steps: - uses: actions/checkout@v2 diff --git a/.rubocop.yml b/.rubocop.yml index b0b0db0..70ce6e5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,7 @@ require: - rubocop-rspec AllCops: - TargetRubyVersion: '2.5' + TargetRubyVersion: '3.0' NewCops: enable Layout/EmptyLineAfterGuardClause: diff --git a/Rakefile b/Rakefile index c183ea3..1189a04 100644 --- a/Rakefile +++ b/Rakefile @@ -26,7 +26,19 @@ task :hadolint do sh 'docker run --rm -i hadolint/hadolint:v1.18.0 hadolint --ignore DL3018 - < docker/Dockerfile' end -task default: [:rubocop, :spec, 'bundle:audit', :solargraph] +desc 'Run experimental steep type checker' +task :steep do + sh 'steep check' +end + +namespace :steep do + desc 'Output coverage stats from steep' + task :stats do + sh 'steep stats --log-level=fatal | awk -F\',\' \'{ printf "%-50s %-9s %-12s %-14s %-10s\n", $2, $3, $4, $5, $7 }\'' + end +end + +task default: [:rubocop, :spec, 'bundle:audit', :solargraph, :steep] desc 'Run all tasks desired for CI' task ci: ['solargraph:init', :default, :hadolint] diff --git a/Steepfile b/Steepfile new file mode 100644 index 0000000..ab00050 --- /dev/null +++ b/Steepfile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +target :lib do + signature 'sig' + + check 'lib' + ignore 'lib/dyndnsd/updater/zone_transfer_server.rb' + + library 'date' + library 'forwardable' +end diff --git a/dyndnsd.gemspec b/dyndnsd.gemspec index 5d69098..3081165 100644 --- a/dyndnsd.gemspec +++ b/dyndnsd.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.executables = ['dyndnsd'] s.extra_rdoc_files = Dir['README.md', 'CHANGELOG.md', 'LICENSE'] - s.required_ruby_version = '>= 2.5' + s.required_ruby_version = '>= 3.0' s.add_runtime_dependency 'async-dns', '~> 1.2.0' s.add_runtime_dependency 'jaeger-client', '~> 1.1.0' @@ -40,8 +40,9 @@ Gem::Specification.new do |s| s.add_development_dependency 'rack-test' s.add_development_dependency 'rake' s.add_development_dependency 'rspec' - s.add_development_dependency 'rubocop', '~> 1.8.1' + s.add_development_dependency 'rubocop', '~> 1.7.0' s.add_development_dependency 'rubocop-rake', '~> 0.5.1' s.add_development_dependency 'rubocop-rspec', '~> 2.1.0' s.add_development_dependency 'solargraph', '~> 0.40.0' + s.add_development_dependency 'steep', '~> 0.39.0' end diff --git a/lib/dyndnsd.rb b/lib/dyndnsd.rb index 9d2c482..143b33d 100644 --- a/lib/dyndnsd.rb +++ b/lib/dyndnsd.rb @@ -276,7 +276,7 @@ module Dyndnsd reporter = Metriks::Reporter::Graphite.new(host, port, options) reporter.start elsif config['textfile'] - file = config['textfile']['file'] || '/tmp/dyndnsd-metrics.prom' + file = (config['textfile']['file'] || '/tmp/dyndnsd-metrics.prom').to_s options = {} options[:prefix] = config['textfile']['prefix'] if config['textfile']['prefix'] reporter = Dyndnsd::TextfileReporter.new(file, options) diff --git a/lib/dyndnsd/responder/dyndns_style.rb b/lib/dyndnsd/responder/dyndns_style.rb index 8a9276f..db8a5ff 100644 --- a/lib/dyndnsd/responder/dyndns_style.rb +++ b/lib/dyndnsd/responder/dyndns_style.rb @@ -33,6 +33,7 @@ module Dyndnsd when 422 error_response_map[headers['X-DynDNS-Response']] end + # TODO: possible nil response! end # @param status_code [Integer] @@ -46,6 +47,7 @@ module Dyndnsd when 401 [status_code, headers, ['badauth']] end + # TODO: possible nil response! end # @param changes [Array] diff --git a/lib/dyndnsd/responder/rest_style.rb b/lib/dyndnsd/responder/rest_style.rb index ef0c54a..f26251e 100644 --- a/lib/dyndnsd/responder/rest_style.rb +++ b/lib/dyndnsd/responder/rest_style.rb @@ -33,6 +33,7 @@ module Dyndnsd when 422 error_response_map[headers['X-DynDNS-Response']] end + # TODO: possible nil response! end # @param status_code [Integer] @@ -46,6 +47,7 @@ module Dyndnsd when 401 [status_code, headers, ['Unauthorized']] end + # TODO: possible nil response! end # @param changes [Array] diff --git a/lib/dyndnsd/textfile_reporter.rb b/lib/dyndnsd/textfile_reporter.rb index 91896cf..09bcdd3 100644 --- a/lib/dyndnsd/textfile_reporter.rb +++ b/lib/dyndnsd/textfile_reporter.rb @@ -91,7 +91,7 @@ module Dyndnsd end end - # @param file [String] + # @param file [File] # @param base_name [String] # @param metric [Object] # @param keys [Array] diff --git a/sig/dyndnsd.rbs b/sig/dyndnsd.rbs new file mode 100644 index 0000000..d705fd0 --- /dev/null +++ b/sig/dyndnsd.rbs @@ -0,0 +1,69 @@ +module Dyndnsd + type config = Hash[String, untyped] + type env = Hash[String, String] + type headers = Hash[String, String] + type response = Array[int | Hash[string, string] | Array[string]] + + interface _App + def call: (env env) -> response + end + + + # @return [Logger] + def self.logger: () -> untyped + + # @param logger [Logger] + # @return [Logger] + def self.logger=: (untyped logger) -> untyped + + class LogFormatter + # @param lvl [Object] + # @param _time [DateTime] + # @param _progname [String] + # @param msg [Object] + # @return [String] + def call: (untyped lvl, DateTime _time, String _progname, untyped msg) -> String + end + + class Daemon + # @param config [Hash{String => Object}] + # @param db [Dyndnsd::Database] + # @param updater [#update] + def initialize: (untyped config, untyped db, untyped updater) -> void + + # @param username [String] + # @param password [String] + # @return [Boolean] + def authorized?: (String username, String password) -> bool + + def call: (env env) -> response + + def self.run!: () -> void + + private + + # @param params [Hash{String => String}] + # @return [Array] + def extract_v4_and_v6_address: (untyped params) -> (::Array[untyped] | untyped) + + # @param env [Hash{String => String}] + # @param params [Hash{String => String}] + # @return [Array] + def extract_myips: (untyped env, untyped params) -> (untyped | ::Array[untyped]) + + # @param hostnames [String] + # @param myips [Array] + # @return [Array] + def process_changes: (untyped hostnames, untyped myips) -> untyped + + def update_db: () -> void + + def handle_dyndns_request: (env env) -> response + + def self.setup_logger: (config config) -> void + def self.setup_traps: () -> void + def self.setup_monitoring: (config config) -> void + def self.setup_tracing: (config config) -> void + def self.setup_rack: (config config) -> void + end +end diff --git a/sig/dyndnsd/database.rbs b/sig/dyndnsd/database.rbs new file mode 100644 index 0000000..562d4ee --- /dev/null +++ b/sig/dyndnsd/database.rbs @@ -0,0 +1,21 @@ +module Dyndnsd + class Database + extend Forwardable + + def initialize: (string db_file) -> void + + def load: () -> void + + def save: () -> void + + def changed?: () -> bool + + def []: (string key) -> untyped + + def []=: (string key, untyped value) -> void + + def each: () { (string key, untyped value) -> void } -> void + + def has_key?: (string key) -> bool + end +end diff --git a/sig/dyndnsd/generator/bind.rbs b/sig/dyndnsd/generator/bind.rbs new file mode 100644 index 0000000..e8f58db --- /dev/null +++ b/sig/dyndnsd/generator/bind.rbs @@ -0,0 +1,9 @@ +module Dyndnsd + module Generator + class Bind + def initialize: (String domain, Hash[String, untyped] updater_params) -> void + + def generate: (Dyndnsd::Database db) -> string + end + end +end diff --git a/sig/dyndnsd/helper.rbs b/sig/dyndnsd/helper.rbs new file mode 100644 index 0000000..6f2fc08 --- /dev/null +++ b/sig/dyndnsd/helper.rbs @@ -0,0 +1,32 @@ +module Dyndnsd + type users = Hash[String, Hash[String, String]] + type hosts = Hash[String, Array[String]] + + class Helper + # @param hostname [String] + # @param domain [String] + # @return [Boolean] + def self.fqdn_valid?: (String hostname, String domain) -> bool + + # @param ip [String] + # @return [Boolean] + def self.ip_valid?: (String ip) -> bool + + # @param username [String] + # @param password [String] + # @param users [Hash] + # @return [Boolean] + def self.user_allowed?: (String username, String password, users users) -> bool + + # @param hostname [String] + # @param myips [Array] + # @param hosts [Hash] + # @return [Boolean] + def self.changed?: (String hostname, Array[String] myips, hosts hosts) -> bool + + # @param operation [String] + # @param block [Proc] + # @return [void] + def self.span: (String operation) { (untyped) -> untyped } -> untyped + end +end diff --git a/sig/dyndnsd/responder/dyndns_style.rbs b/sig/dyndnsd/responder/dyndns_style.rbs new file mode 100644 index 0000000..13c56e2 --- /dev/null +++ b/sig/dyndnsd/responder/dyndns_style.rbs @@ -0,0 +1,19 @@ +module Dyndnsd + module Responder + class DynDNSStyle + def initialize: (_App app) -> void + + def call: (env env) -> response? + + private + + def decorate_dyndnsd_response: (int status_code, headers headers, Array[untyped] body) -> response? + + def decorate_other_response: (int status_code, headers headers, Array[untyped] _body) -> response? + + def get_success_body: (Array[Symbol] changes, Array[string] myips) -> string + + def error_response_map: () -> Hash[string, response] + end + end +end diff --git a/sig/dyndnsd/responder/rest_style.rbs b/sig/dyndnsd/responder/rest_style.rbs new file mode 100644 index 0000000..f94fb2a --- /dev/null +++ b/sig/dyndnsd/responder/rest_style.rbs @@ -0,0 +1,19 @@ +module Dyndnsd + module Responder + class RestStyle + def initialize: (_App app) -> void + + def call: (env env) -> response? + + private + + def decorate_dyndnsd_response: (int status_code, headers headers, Array[untyped] body) -> response? + + def decorate_other_response: (int status_code, headers headers, Array[untyped] _body) -> response? + + def get_success_body: (Array[Symbol] changes, Array[string] myips) -> string + + def error_response_map: () -> Hash[string, response] + end + end +end diff --git a/sig/dyndnsd/textfile_reporter.rbs b/sig/dyndnsd/textfile_reporter.rbs new file mode 100644 index 0000000..be5d1b5 --- /dev/null +++ b/sig/dyndnsd/textfile_reporter.rbs @@ -0,0 +1,17 @@ +module Dyndnsd + class TextfileReporter + attr_reader file: String + + def initialize: (String file, ?Hash[Symbol, untyped] options) -> void + + def start: () -> void + + def stop: () -> void + + def restart: () -> void + + def write: () -> void + + def write_metric: (File file, String base_name, untyped metric, Array[Symbol] keys, Array[Symbol] snapshot_keys) -> void + end +end diff --git a/sig/dyndnsd/updater/command_with_bind_zone.rbs b/sig/dyndnsd/updater/command_with_bind_zone.rbs new file mode 100644 index 0000000..9729447 --- /dev/null +++ b/sig/dyndnsd/updater/command_with_bind_zone.rbs @@ -0,0 +1,9 @@ +module Dyndnsd + module Updater + class CommandWithBindZone + def initialize: (String domain, Hash[String, untyped] updater_params) -> void + + def update: (Dyndnsd::Database db) -> void + end + end +end diff --git a/sig/dyndnsd/updater/zone_transfer_server.rbs b/sig/dyndnsd/updater/zone_transfer_server.rbs new file mode 100644 index 0000000..85216e7 --- /dev/null +++ b/sig/dyndnsd/updater/zone_transfer_server.rbs @@ -0,0 +1,39 @@ +module Dyndnsd + module Updater + class ZoneTransferServer + DEFAULT_SERVER_LISTENS: Array[String] + + def initialize: (String domain, Hash[String, untyped] updater_params) -> void + + def update: (Dyndnsd::Database db) -> void + + # converts into suitable parameter form for Async::DNS::Resolver or Async::DNS::Server + # + # @param endpoint_list [Array] + # @return [Array{Array{Object}}] + def self.parse_endpoints: (Array[String] endpoint_list) -> Array[Array[untyped]] + + private + + # creates correct Resolv::DNS::Resource object for IP address type + # + # @param ip_string [String] + # @return [Resolv::DNS::Resource::IN::A,Resolv::DNS::Resource::IN::AAAA] + def create_addr_rr_for_ip: (String ip_string) -> untyped + + def send_dns_notify: () -> void + end + + class ZoneTransferServerHelper #< Async::DNS::Server + attr_accessor axfr_rrs: untyped + + def initialize: (untyped endpoints, String domain) -> void + + # @param name [String] + # @param resource_class [Resolv::DNS::Resource] + # Since solargraph cannot parse this: param transaction [Async::DNS::Transaction] + # @return [void] + def process: (String name, untyped resource_class, untyped transaction) -> void + end + end +end diff --git a/sig/dyndnsd/version.rbs b/sig/dyndnsd/version.rbs new file mode 100644 index 0000000..5a979bb --- /dev/null +++ b/sig/dyndnsd/version.rbs @@ -0,0 +1,3 @@ +module Dyndnsd + VERSION: ::String +end