file_sv 0.1.0 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 373ecafe042decab366929da1430ddb873aca264006aaa1d59c4eaa5d67b68d0
4
- data.tar.gz: 730539bcd6461534c4ff9899abaf989518d002ffe0ab2865f87b30d08bd0b463
3
+ metadata.gz: bb4214e64bca77866967b848fb88c40eabe0a999d03e1ba88e47500662314b44
4
+ data.tar.gz: de2aa37269d3fc2d22904a1535a2d8e6308f011baae82fd352ee853def74f4d4
5
5
  SHA512:
6
- metadata.gz: f68f20ec798767c092128c50dfc6bd131d77350566b3dc37c72b2497728126bdfdb674cce6c99edf6882285761447b106895be6418cd1bf989e0692868ab4500
7
- data.tar.gz: c3f182e5d686d715b28513dde7a72231a181312e7e9be729eedc34ff464b153f93908d84417ff340f2ff9b3a9ee4cfb8af6f867d0e5eaf58963d5cbf23e5641e
6
+ metadata.gz: 72685470cd73d2f13d6e0d5894980d7603787204bcd5d454f339f81d105f96f0276b9d32026e3485e02abeb9001fcfe25648d64686fa804c0f57fc5a9df3ec0d
7
+ data.tar.gz: 786f78f51f7c02605b235c4306b972f67c33cd31c537a1af0ec41b363f5b8c340baee5648f40a3ae9006dc3561783d8dab6086d4412e4844da0515ff5d6175d8
data/exe/file_sv CHANGED
@@ -12,16 +12,24 @@ class Exe < Thor
12
12
  ServiceLoader.create_plan_for folder
13
13
  end
14
14
 
15
+ option :crt, default: nil, banner: "HTTPS CRT"
16
+ option :key, default: nil, banner: "HTTPS key"
15
17
  desc "serve folder", "Serve virtual service based on folder"
16
18
  def serve(folder)
17
19
  plan folder
18
- ServiceLoader.serve_plan
20
+ ServiceLoader.serve_plan options
19
21
  end
20
22
 
21
- desc "version", "Version of FileTest"
22
- def version
23
+ desc "inspect folder", "Inspect details of what's served at folder"
24
+ def inspect(folder)
23
25
  require "file_sv"
24
- puts "GenericTest version #{FileTest::VERSION}"
26
+ ServiceLoader.inspect folder
27
+ end
28
+
29
+ desc "version", "Version of FileSv"
30
+ def version
31
+ require "file_sv/version"
32
+ puts "FileSv version #{FileSv::VERSION}"
25
33
  end
26
34
  end
27
35
 
data/lib/file_sv.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "yaml"
3
4
  require_relative "file_sv/version"
4
5
  require_relative "file_sv/global_settings"
5
6
  require_relative "file_sv/sv_plan"
@@ -15,29 +16,37 @@ module FileSv
15
16
  class FileNameError < Error; end
16
17
 
17
18
  class << self
19
+ # @return [Hash] Mapping of REST method names to setting classes
18
20
  def rest_methods
19
21
  {
20
22
  get: GetSettings, post: PostSettings, patch: PatchSettings, options: OptionsSettings,
21
- delete: DeleteSettings
23
+ delete: DeleteSettings, put: PutSettings
22
24
  }
23
25
  end
24
26
  end
25
27
  end
26
28
 
27
- require "yaml"
28
29
  CONFIG_FILE = "file_sv.yaml"
29
- if File.exist? CONFIG_FILE
30
- # Set values in global settings based on config
31
- class GlobalSettings
32
- config = YAML.load_file CONFIG_FILE
33
- config["global"]&.each do |key, value|
34
- send("#{key}=", value)
35
- end
36
30
 
37
- FileSv.rest_methods.each do |method, setting_class|
38
- config[method.to_s]&.each do |key, value|
39
- setting_class.send("#{key}=", value)
40
- end
41
- end
31
+ # Set values in global settings based on config
32
+ def load_default_config(file_path)
33
+ return unless File.exist? file_path
34
+
35
+ config = YAML.load_file file_path
36
+ return unless config # Handle empty YAML file
37
+
38
+ config["global"]&.each do |key, value|
39
+ GlobalSettings.send("#{key}=", value)
42
40
  end
41
+
42
+ load_rest_method_config config
43
43
  end
44
+
45
+ # Load details of each REST method
46
+ def load_rest_method_config(config)
47
+ FileSv.rest_methods.each do |method, setting_class|
48
+ config[method.to_s]&.each { |key, value| setting_class.send("#{key}=", value) }
49
+ end
50
+ end
51
+
52
+ load_default_config CONFIG_FILE
Binary file
@@ -5,11 +5,23 @@ class GlobalSettings
5
5
  @default_method = "get"
6
6
 
7
7
  @empty_body_status = 204
8
+
9
+ @ignore_files = "{*.md,Dockerfile,.*}"
10
+
11
+ @https = false
8
12
  class << self
9
13
  # @return [String] Default REST method when none specified by filename
10
14
  attr_accessor :default_method
11
15
  # @return [Integer] Default status of response when file is empty
12
16
  attr_accessor :empty_body_status
17
+ # @return [Array] Expression representing files to ignore
18
+ attr_accessor :ignore_files
19
+ # @return [Boolean] Whether to serve https using self signed certificate
20
+ attr_accessor :https
21
+ # @return [String] Path to HTTPS cert
22
+ attr_accessor :cert
23
+ # @return [String] Path to HTTPS key
24
+ attr_accessor :key
13
25
  end
14
26
  end
15
27
 
@@ -19,25 +31,36 @@ end
19
31
 
20
32
  # Settings specific to GET
21
33
  class GetSettings
34
+ @default_status = 200
22
35
  extend CommonHttpSettings
23
36
  end
24
37
 
25
38
  # Settings specific to POST
26
39
  class PostSettings
40
+ @default_status = 201
27
41
  extend CommonHttpSettings
28
42
  end
29
43
 
30
44
  # Settings specific to PATCH
31
45
  class PatchSettings
46
+ @default_status = 200
47
+ extend CommonHttpSettings
48
+ end
49
+
50
+ # Settings specific to PATCH
51
+ class PutSettings
52
+ @default_status = 200
32
53
  extend CommonHttpSettings
33
54
  end
34
55
 
35
56
  # Settings specific to OPTIONS
36
57
  class OptionsSettings
58
+ @default_status = 200
37
59
  extend CommonHttpSettings
38
60
  end
39
61
 
40
62
  # Settings specific to DELETE
41
63
  class DeleteSettings
64
+ @default_status = 200
42
65
  extend CommonHttpSettings
43
66
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "erb"
4
4
  require "securerandom"
5
+ require "faker"
6
+ require "pathname"
5
7
 
6
8
  # Endpoint planned to be served
7
9
  class PlannedEndpoint
@@ -15,12 +17,12 @@ class PlannedEndpoint
15
17
  attr_accessor :status_code
16
18
 
17
19
  # @return [Array]
18
- HTTP_METHODS = %w[get post patch delete options].freeze
20
+ HTTP_METHODS = %w[get put post patch delete options].freeze
19
21
 
20
22
  # Represent a new endpoint
21
23
  def initialize(path)
22
24
  self.file_path = path
23
- self.path = File.split(path).first
25
+ self.path = serving_loc_for path
24
26
  @file = true if not_text?
25
27
  assign_params_from_name
26
28
  self.method ||= GlobalSettings.default_method
@@ -29,21 +31,28 @@ class PlannedEndpoint
29
31
  set_default_status_code
30
32
  end
31
33
 
34
+ def serving_loc_for(path)
35
+ loc = File.split(path).first # TODO: Handle if path has a % at beginning of a folder
36
+ loc.gsub("#{File::SEPARATOR}%", "#{File::SEPARATOR}:")
37
+ end
38
+
32
39
  # @return [Boolean] Whether endpoint serves a file not a string
33
40
  def file?
34
41
  @file
35
42
  end
36
43
 
44
+ # Return default status code for an empty body
37
45
  def default_empty_code
38
46
  return false if not_text?
39
47
 
40
- if content.strip.empty?
48
+ if content(binding).strip.empty?
41
49
  self.status_code = GlobalSettings.empty_body_status
42
50
  return true
43
51
  end
44
52
  false
45
53
  end
46
54
 
55
+ # Set default status code based on empty body or type of METHOD
47
56
  def set_default_status_code
48
57
  return if default_empty_code
49
58
 
@@ -90,8 +99,8 @@ class PlannedEndpoint
90
99
  end
91
100
 
92
101
  # @return [Object] Content of file
93
- def content
94
- render_text
102
+ def content(binding)
103
+ render_text(binding)
95
104
  end
96
105
 
97
106
  def not_text?
@@ -100,7 +109,7 @@ class PlannedEndpoint
100
109
  end
101
110
 
102
111
  # @return [String] Render text
103
- def render_text
112
+ def render_text(binding)
104
113
  ERB.new(File.read(serving_file_name)).result(binding)
105
114
  end
106
115
  end
@@ -9,13 +9,21 @@ module ServiceLoader
9
9
  # Create virtual service plan based on folder
10
10
  def create_plan_for(folder)
11
11
  SvPlan.create folder
12
+ puts SvPlan.show
13
+ end
14
+
15
+ # Inspect plan
16
+ def inspect(folder)
17
+ create_plan_for folder
12
18
  puts SvPlan.inspect
13
19
  end
14
20
 
15
21
  # Serve plan
16
- def serve_plan
22
+ def serve_plan(thor_options)
17
23
  require "sinatra"
18
24
  require_relative "virtual_server"
25
+ GlobalSettings.key = thor_options[:key] if thor_options[:key]
26
+ GlobalSettings.cert = thor_options[:crt] if thor_options[:crt]
19
27
  VirtualServer.run!
20
28
  end
21
29
  end
@@ -6,7 +6,7 @@ require_relative "file_processor"
6
6
  class SvPlan
7
7
  @endpoints = {}
8
8
  class << self
9
- # @return [Array]
9
+ # @return [Hash] Endpoints included in plan. Key - endpoint, value - methods served under it
10
10
  attr_reader :endpoints
11
11
  # @return [String] Folder plan is served from
12
12
  attr_accessor :serving_folder
@@ -15,33 +15,55 @@ class SvPlan
15
15
  def create(folder)
16
16
  self.serving_folder = folder
17
17
  puts "Creating service based on files in #{folder}"
18
- file_list = Dir.glob("#{folder}/**/*.*")
19
- file_list.each do |file|
20
- process_file file
21
- end
18
+ file_list = Dir.glob("#{folder}/**/*.*") - Dir.glob("#{folder}/#{GlobalSettings.ignore_files}")
19
+ file_list.each { |file| process_file file }
22
20
  end
23
21
 
24
- def process_file(file)
25
- file.slice! serving_folder
26
- extension = File.extname(file)
22
+ # Process file, for the most part creating endpoint.method from it
23
+ # @param [String] filename Path to file to process
24
+ def process_file(filename)
25
+ filename.slice! serving_folder
26
+ extension = File.extname(filename)
27
27
  case extension
28
- when "yaml" then YamlProcessor.process(file)
28
+ when ".yaml" then YamlProcessor.process(filename)
29
29
  else
30
- FileProcessor.process(file)
30
+ FileProcessor.process(filename)
31
31
  end
32
32
  end
33
33
 
34
- def inspect
34
+ # Show plan
35
+ def show
36
+ endpoint_desc = ""
37
+ endpoints.sort { |a, b| a[0].length - b[0].length }.each do |endpoint, methods|
38
+ endpoint_desc += "#{endpoint} \n"
39
+ methods.each do |method_name, endpoints|
40
+ endpoint_desc += description_message method_name, endpoints
41
+ end
42
+ end
43
+
35
44
  "** VIRTUAL SERVICE PLAN **
36
- Serving based on folder: #{serving_folder}
37
- Endpoints: #{endpoints.inspect}
38
- "
45
+ Serving based on folder: #{Dir.pwd}. Related to folder executed: #{serving_folder}
46
+ #{endpoint_desc}"
47
+ end
48
+
49
+ # Inspect details
50
+ def inspect
51
+ "Endpoints: #{endpoints.inspect}"
39
52
  end
40
53
 
54
+ # Add endpoint to plan
55
+ # @param [PlannedEndpoint] other Endpoint to add to plan
41
56
  def +(other)
42
57
  @endpoints[other.path] ||= {}
43
58
  @endpoints[other.path][other.method] ||= []
44
59
  @endpoints[other.path][other.method] << other
45
60
  end
61
+
62
+ private
63
+
64
+ # @return [String]
65
+ def description_message(method_name, endpoints)
66
+ " #{method_name.upcase} (#{endpoints.size} responses) #{endpoints.collect(&:status_code)}\n"
67
+ end
46
68
  end
47
69
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FileSv
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.5"
5
5
  end
@@ -2,27 +2,97 @@
2
2
 
3
3
  require "webrick"
4
4
  require "sinatra"
5
+ require "docdsl"
6
+ require "webrick/https"
7
+ require "openssl"
5
8
 
6
9
  # Virtual server hosting virtual service defined through files
7
10
  class VirtualServer < Sinatra::Base
8
11
  set :server, "webrick"
9
12
  set :bind, "0.0.0.0"
10
13
 
14
+ register Sinatra::DocDsl
15
+
16
+ if GlobalSettings.https
17
+ def self.own_certs(webrick_options)
18
+ puts "Using cert from #{GlobalSettings.cert}"
19
+ cert = OpenSSL::X509::Certificate.new File.read GlobalSettings.cert
20
+ pkey = OpenSSL::PKey::RSA.new File.read GlobalSettings.key
21
+ webrick_options[:SSLCertificate] = cert
22
+ webrick_options[:SSLPrivateKey] = pkey
23
+ webrick_options
24
+ end
25
+
26
+ def self.determine_certs(webrick_options)
27
+ if GlobalSettings.key && GlobalSettings.cert
28
+ webrick_options = own_certs webrick_options
29
+ else
30
+ puts "Using self signed cert"
31
+ webrick_options[:ServerName] = "localhost"
32
+ webrick_options[:SSLCertName] = "/CN=localhost"
33
+ end
34
+ webrick_options
35
+ end
36
+
37
+ # Run as https with self signed cert
38
+ def self.run!
39
+ logger = WEBrick::Log.new(nil, WEBrick::BasicLog::WARN)
40
+ webrick_options = { Port: port, SSLEnable: true, Logger: logger }
41
+ webrick_options = determine_certs webrick_options
42
+ # TODO: Following run does not work on Ruby 3
43
+ Rack::Handler::WEBrick.run(self, webrick_options) do |server|
44
+ %i[INT TERM].each { |sig| trap(sig) { server.stop } }
45
+ server.threaded = settings.threaded if server.respond_to? :threaded=
46
+ set :running, true
47
+ end
48
+ end
49
+ end
50
+
51
+ page do
52
+ title "File SV"
53
+ header "Service virtualization created from #{Dir.pwd}"
54
+ introduction 'Created using file_sv. See <a href="https://gitlab.com/samuel-garratt/file_sv">File SV</a>
55
+ for more details'
56
+ end
57
+
58
+ get "/favicon.ico" do
59
+ send_file File.join(__dir__, "file_sv.ico")
60
+ end
61
+
62
+ doc_endpoint "/docs"
63
+
64
+ # Output for endpoint, either a file or text content
65
+ # @param [PlannedEndpoint] endpoint Planned endpoint to serve
66
+ def output_for(endpoint, binding)
67
+ endpoint.file? ? send_file(endpoint.serving_file_name) : endpoint.content(binding)
68
+ end
69
+
70
+ # Log endpoint. Return content and status code defined by endpoint
71
+ # @param [PlannedEndpoint] endpoint Planned endpoint to serve
72
+ def serve(endpoint, id = nil)
73
+ message = "Using endpoint based on file #{endpoint.serving_file_name}."
74
+ @id = id
75
+ message += " Using param '#{@id}'" if id
76
+ puts message
77
+ [endpoint.status_code, output_for(endpoint, binding)]
78
+ end
79
+
11
80
  SvPlan.endpoints.each do |_endpoint_path, endpoint_value|
12
81
  endpoint_value.each do |_method_name, endpoints|
13
- if endpoints.size < 2
14
- endpoint = endpoints[0]
15
- send(endpoint.method, endpoint.path) do
16
- [endpoint.status_code, endpoint.file? ? send_file(endpoint.serving_file_name) : endpoint.content]
82
+ endpoint_base = endpoints[0]
83
+ documentation "Endpoint #{endpoint_base.path}" do
84
+ response "#{endpoints.size} kinds of response"
85
+ end
86
+ if endpoint_base.path.include? "#{File::Separator}:"
87
+ send(endpoint_base.method, endpoint_base.path) do |id|
88
+ endpoint = endpoints.sample
89
+ serve endpoint, id
17
90
  end
18
91
  else
19
- endpoint_base = endpoints[0]
20
92
  send(endpoint_base.method, endpoint_base.path) do
21
- my_points = endpoints
22
- endpoint = my_points.sample
23
- [endpoint.status_code, endpoint.file? ? send_file(endpoint.serving_file_name) : endpoint.content]
93
+ endpoint = endpoints.sample
94
+ serve endpoint
24
95
  end
25
- # Average same methods at same endpoint
26
96
  end
27
97
  end
28
98
  end
@@ -3,8 +3,14 @@
3
3
  # Process YAML files
4
4
  class YamlProcessor
5
5
  class << self
6
+ # Process YAML file
6
7
  def process(filename)
7
- puts "Overriding default config based on #{filename}" if filename == "file_sv"
8
+ if filename == "/file_sv.yaml"
9
+ puts "Overriding default config based on #{File.join(Dir.pwd, filename[1..-1])}"
10
+ load_default_config filename[1..-1]
11
+ else
12
+ puts "Skipping #{filename}"
13
+ end
8
14
  end
9
15
  end
10
16
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: file_sv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Garratt
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-03 00:00:00.000000000 Z
11
+ date: 2021-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faker
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: sinatra
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - ">="
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra-docdsl
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: thor
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +93,7 @@ files:
65
93
  - exe/file_sv
66
94
  - lib/file_sv.rb
67
95
  - lib/file_sv/file_processor.rb
96
+ - lib/file_sv/file_sv.ico
68
97
  - lib/file_sv/global_settings.rb
69
98
  - lib/file_sv/planned_endpoint.rb
70
99
  - lib/file_sv/render_file.rb
@@ -95,7 +124,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
124
  - !ruby/object:Gem::Version
96
125
  version: '0'
97
126
  requirements: []
98
- rubygems_version: 3.2.3
127
+ rubygems_version: 3.1.4
99
128
  signing_key:
100
129
  specification_version: 4
101
130
  summary: REST service virtualisation through file structure.