file_sv 0.1.0 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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.