shark-on-lambda 0.0.0 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.gitlab-ci.yml +13 -0
  4. data/.rubocop.yml +13 -0
  5. data/.travis.yml +9 -2
  6. data/README.md +184 -18
  7. data/Rakefile +2 -0
  8. data/bin/console +4 -9
  9. data/changelog.md +17 -0
  10. data/gems.locked +92 -0
  11. data/{Gemfile → gems.rb} +2 -1
  12. data/lib/shark-on-lambda.rb +1 -5
  13. data/lib/shark_on_lambda.rb +104 -1
  14. data/lib/shark_on_lambda/api_gateway/base_controller.rb +76 -0
  15. data/lib/shark_on_lambda/api_gateway/base_handler.rb +82 -0
  16. data/lib/shark_on_lambda/api_gateway/concerns/http_response_validation.rb +61 -0
  17. data/lib/shark_on_lambda/api_gateway/errors.rb +49 -0
  18. data/lib/shark_on_lambda/api_gateway/headers.rb +37 -0
  19. data/lib/shark_on_lambda/api_gateway/jsonapi_controller.rb +77 -0
  20. data/lib/shark_on_lambda/api_gateway/jsonapi_parameters.rb +68 -0
  21. data/lib/shark_on_lambda/api_gateway/jsonapi_renderer.rb +105 -0
  22. data/lib/shark_on_lambda/api_gateway/parameters.rb +18 -0
  23. data/lib/shark_on_lambda/api_gateway/query.rb +69 -0
  24. data/lib/shark_on_lambda/api_gateway/request.rb +148 -0
  25. data/lib/shark_on_lambda/api_gateway/response.rb +82 -0
  26. data/lib/shark_on_lambda/api_gateway/serializers/base_error_serializer.rb +20 -0
  27. data/lib/shark_on_lambda/concerns/filter_actions.rb +82 -0
  28. data/lib/shark_on_lambda/concerns/resettable_singleton.rb +18 -0
  29. data/lib/shark_on_lambda/concerns/yaml_config_loader.rb +28 -0
  30. data/lib/shark_on_lambda/configuration.rb +71 -0
  31. data/lib/shark_on_lambda/inferrers/name_inferrer.rb +66 -0
  32. data/lib/shark_on_lambda/inferrers/serializer_inferrer.rb +45 -0
  33. data/lib/shark_on_lambda/secrets.rb +43 -0
  34. data/lib/shark_on_lambda/tasks.rb +3 -0
  35. data/lib/shark_on_lambda/tasks/build.rake +146 -0
  36. data/lib/{shark-on-lambda → shark_on_lambda}/version.rb +1 -1
  37. data/shark-on-lambda.gemspec +21 -6
  38. metadata +158 -20
  39. data/Gemfile.lock +0 -35
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ module ApiGateway
5
+ module Errors
6
+ class BaseSerializer < ::JSONAPI::Serializable::Error
7
+ id { @object.id }
8
+ status { @object.status }
9
+ code { @object.code }
10
+ title { @object.title }
11
+ detail { @object.detail }
12
+ meta { @object.meta }
13
+ source do
14
+ pointer @object.pointer if @object.pointer.present?
15
+ parameter @object.parameter if @object.parameter.present?
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ module Concerns
5
+ module FilterActions
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def after_action(symbol, only: [], except: [])
12
+ @after_actions ||= []
13
+ @after_actions << {
14
+ symbol: symbol,
15
+ only: Array(only),
16
+ except: Array(except)
17
+ }
18
+ end
19
+
20
+ def after_actions
21
+ @after_actions || []
22
+ end
23
+
24
+ def before_action(symbol, only: [], except: [])
25
+ @before_actions ||= []
26
+ @before_actions << {
27
+ symbol: symbol,
28
+ only: Array(only),
29
+ except: Array(except)
30
+ }
31
+ end
32
+
33
+ def before_actions
34
+ @before_actions || []
35
+ end
36
+ end
37
+
38
+ def call_with_filter_actions(method, *args)
39
+ run_before_actions(method)
40
+ result = send(method, *args)
41
+ run_after_actions(method)
42
+ result
43
+ end
44
+
45
+ protected
46
+
47
+ def after_actions
48
+ self.class.after_actions
49
+ end
50
+
51
+ def before_actions
52
+ self.class.before_actions
53
+ end
54
+
55
+ def run_actions(method, actions)
56
+ actions.each do |action|
57
+ next if skip_filter_action?(method, action)
58
+
59
+ send(action[:symbol])
60
+ end
61
+ end
62
+
63
+ def run_after_actions(method)
64
+ run_actions(method, after_actions)
65
+ end
66
+
67
+ def run_before_actions(method)
68
+ run_actions(method, before_actions)
69
+ end
70
+
71
+ def skip_filter_action?(method, filter_action)
72
+ only = filter_action[:only]
73
+ except = filter_action[:except]
74
+
75
+ return true if only.any? && !only.include?(method)
76
+ return true if except.any? && except.include?(method)
77
+
78
+ false
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ module Concerns
5
+ module ResettableSingleton
6
+ def self.included(base)
7
+ base.include(Singleton)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ def reset
13
+ @singleton__instance__ = nil
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ module Concerns
5
+ module YamlConfigLoader
6
+ def load_yaml_files(stage:, fallback: :default, paths:)
7
+ result = HashWithIndifferentAccess.new
8
+ paths.each do |path|
9
+ data = load_yaml_file(stage: stage, fallback: fallback, path: path)
10
+ result.deep_merge!(data)
11
+ end
12
+ result
13
+ end
14
+
15
+ protected
16
+
17
+ def load_yaml_file(stage:, fallback:, path:)
18
+ return {} unless File.exist?(path)
19
+
20
+ data = YAML.load_file(path)
21
+ return {} unless data.is_a?(Hash)
22
+
23
+ data = data.with_indifferent_access
24
+ data[stage] || data[fallback] || {}
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ class Configuration < OpenStruct
5
+ include Concerns::ResettableSingleton
6
+
7
+ attr_writer :stage
8
+
9
+ class << self
10
+ include Concerns::YamlConfigLoader
11
+
12
+ attr_writer :database_files, :settings_files
13
+
14
+ def database_files
15
+ return @database_files if defined?(@database_files)
16
+
17
+ files = %w[config/database.yml config/database.local.yml]
18
+ @database_files = paths(files)
19
+ end
20
+
21
+ def load(stage, fallback: :default)
22
+ load_settings(stage, fallback: fallback)
23
+ load_database_configuration(stage, fallback: fallback)
24
+
25
+ instance
26
+ end
27
+
28
+ def settings_files
29
+ return @settings_files if defined?(@settings_files)
30
+
31
+ files = %w[config/settings.yml config/settings.local.yml]
32
+ @settings_files = paths(files)
33
+ end
34
+
35
+ protected
36
+
37
+ def load_database_configuration(stage, fallback:)
38
+ instance.database = load_yaml_files(stage: stage,
39
+ fallback: fallback,
40
+ paths: paths(database_files))
41
+ end
42
+
43
+ def load_settings(stage, fallback:)
44
+ settings = load_yaml_files(stage: stage,
45
+ fallback: fallback,
46
+ paths: paths(settings_files))
47
+ settings.each_pair do |key, value|
48
+ next if key.to_s == 'serverless'
49
+
50
+ instance.send("#{key}=", value)
51
+ end
52
+ end
53
+
54
+ def paths(files)
55
+ files.map { |file| SharkOnLambda.config.root.join(file) }
56
+ end
57
+ end
58
+
59
+ def root
60
+ @root ||= Pathname.new('.')
61
+ end
62
+
63
+ def root=(new_root)
64
+ @root = Pathname.new(new_root)
65
+ end
66
+
67
+ def stage
68
+ @stage || 'development'
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ module Inferrers
5
+ class NameInferrer
6
+ class << self
7
+ def from_controller_name(class_name)
8
+ from_name(:controller, class_name)
9
+ end
10
+
11
+ def from_deserializer_name(class_name)
12
+ from_name(:deserializer, class_name)
13
+ end
14
+
15
+ def from_handler_name(class_name)
16
+ from_name(:handler, class_name)
17
+ end
18
+
19
+ def from_model_name(class_name)
20
+ from_name(:model, class_name)
21
+ end
22
+
23
+ def from_serializer_name(class_name)
24
+ from_name(:serializer, class_name)
25
+ end
26
+
27
+ protected
28
+
29
+ def from_name(type, class_name)
30
+ base = class_name.underscore
31
+ base = case type
32
+ when :controller, :deserializer, :handler, :serializer
33
+ base.sub(/_#{type}\z/, '')
34
+ when :model
35
+ base
36
+ end
37
+ new(base)
38
+ end
39
+ end
40
+
41
+ def initialize(base)
42
+ @base = base
43
+ end
44
+
45
+ def controller
46
+ "#{@base}_controller".camelize
47
+ end
48
+
49
+ def deserializer
50
+ "#{@base}_deserializer".camelize
51
+ end
52
+
53
+ def handler
54
+ "#{@base}_handler".camelize
55
+ end
56
+
57
+ def model
58
+ @base.camelize
59
+ end
60
+
61
+ def serializer
62
+ "#{@base}_serializer".camelize
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ module Inferrers
5
+ class SerializerInferrer
6
+ def initialize(object_class)
7
+ @object_class = object_class
8
+ end
9
+
10
+ def serializer_class
11
+ return @serializer_class if defined?(@serializer_class)
12
+
13
+ serializer_class_names.each do |serializer_class_name|
14
+ @serializer_class = serializer_class_name.safe_constantize
15
+ break if @serializer_class.present?
16
+ end
17
+
18
+ @serializer_class
19
+ end
20
+
21
+ protected
22
+
23
+ def object_class
24
+ unless @object_class.is_a?(String) || @object_class.is_a?(Symbol)
25
+ return @object_class
26
+ end
27
+
28
+ @object_class.to_s.camelize.constantize
29
+ end
30
+
31
+ def serializer_class_names
32
+ return @serializer_class_names if defined?(@serializer_class_names)
33
+
34
+ @serializer_class_names = object_class.ancestors.map do |ancestor|
35
+ ancestor_name = ancestor.name
36
+ next if ancestor_name.blank?
37
+
38
+ name_inferrer = NameInferrer.from_model_name(ancestor_name)
39
+ name_inferrer.serializer
40
+ end
41
+ @serializer_class_names.compact! || @serializer_class_names
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ class Secrets < OpenStruct
5
+ include Concerns::ResettableSingleton
6
+
7
+ class << self
8
+ include Concerns::YamlConfigLoader
9
+
10
+ attr_writer :files
11
+
12
+ def load(stage, fallback: :default)
13
+ load_secrets(stage, fallback: fallback)
14
+
15
+ instance
16
+ end
17
+
18
+ def files
19
+ return @files if defined?(@files)
20
+
21
+ @files = paths(%w[config/secrets.yml config/secrets.local.yml])
22
+ end
23
+
24
+ protected
25
+
26
+ def load_secrets(stage, fallback:)
27
+ secrets = load_yaml_files(stage: stage,
28
+ fallback: fallback,
29
+ paths: files)
30
+ secrets.each_pair { |key, value| instance.send("#{key}=", value) }
31
+ end
32
+
33
+ def paths(files)
34
+ files.map { |file| SharkOnLambda.config.root.join(file) }
35
+ end
36
+ end
37
+
38
+ def inspect
39
+ # Do not display all the internals of this object when #inspect is called.
40
+ "#<#{self.class.name}>"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ load 'shark_on_lambda/tasks/build.rake'
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shark-on-lambda'
4
+
5
+ class DockerContainer
6
+ attr_reader :project_dir, :tag
7
+ attr_accessor :params, :commands
8
+
9
+ def initialize(project_dir:, tag: 'latest')
10
+ @project_dir = project_dir
11
+ @tag = tag
12
+ @params = default_params
13
+ @commands = default_commands
14
+ end
15
+
16
+ def build
17
+ sh("cd #{project_dir} && docker build . -t #{name}")
18
+ end
19
+
20
+ def exist?
21
+ system("docker image inspect #{name} &> /dev/null")
22
+ end
23
+
24
+ def mount_dir
25
+ File.join('/src', project_name)
26
+ end
27
+
28
+ def name
29
+ "#{project_name}-builder:#{tag}"
30
+ end
31
+
32
+ def remove
33
+ sh("docker image rm #{name}")
34
+ end
35
+
36
+ def run
37
+ arguments = params.join(' ')
38
+ command = commands.join(' && ')
39
+
40
+ sh(%(docker run #{arguments} #{name} /bin/bash -c "#{command}"))
41
+ end
42
+
43
+ def working_dir
44
+ File.join('/tmp', project_name)
45
+ end
46
+
47
+ protected
48
+
49
+ def default_commands
50
+ [
51
+ "cp -a #{mount_dir} #{working_dir}",
52
+ "cd #{working_dir}"
53
+ ]
54
+ end
55
+
56
+ def default_params
57
+ [
58
+ '--rm',
59
+ "-v #{project_dir}:#{mount_dir}"
60
+ ]
61
+ end
62
+
63
+ def project_name
64
+ File.basename(project_dir)
65
+ end
66
+
67
+ def sh(command)
68
+ puts command
69
+ system(command)
70
+ end
71
+ end
72
+
73
+ def build_stage(stage)
74
+ container = DockerContainer.new(project_dir: SharkOnLambda.root)
75
+ Rake::Task['docker:build'].invoke unless container.exist?
76
+
77
+ container.commands += build_commands(container, stage: stage)
78
+ container.run
79
+ end
80
+
81
+ def deploy_stage(stage)
82
+ deploy_command = deploy_commands(stage: stage).join(' && ')
83
+ sh(deploy_command)
84
+ end
85
+
86
+ def remove_stage(stage)
87
+ sh("sls remove -v -s #{stage}")
88
+ end
89
+
90
+ namespace :docker do
91
+ container = DockerContainer.new(project_dir: SharkOnLambda.root)
92
+
93
+ desc 'Build the Docker container required for... building.'
94
+ task :build do
95
+ container.build
96
+ end
97
+
98
+ desc 'Remove the Docker container required for building.'
99
+ task :remove do
100
+ container.remove
101
+ end
102
+ end
103
+
104
+ %i[integration staging production].each do |stage|
105
+ namespace :build do
106
+ desc "Build this service for the '#{stage}' stage."
107
+ task stage => [:clean] do
108
+ build_stage(stage)
109
+ end
110
+ end
111
+
112
+ namespace :deploy do
113
+ desc "Deploy this service to the '#{stage}' stage."
114
+ task stage => ["build:#{stage}"] do
115
+ deploy_stage(stage)
116
+ end
117
+ end
118
+
119
+ namespace :remove do
120
+ desc "Remove this service from the '#{stage}' stage."
121
+ task stage do
122
+ remove_stage(stage)
123
+ end
124
+ end
125
+ end
126
+
127
+ desc 'Build this service for STAGE.'
128
+ task :build, [:stage] => [:clean] do |_, args|
129
+ build_stage(args.stage)
130
+ end
131
+
132
+ desc 'Remove package build directory.'
133
+ task :clean do
134
+ package_dir = SharkOnLambda.root.join('pkg')
135
+ FileUtils.rm_rf(package_dir)
136
+ end
137
+
138
+ desc 'Deploy this service to STAGE.'
139
+ task :deploy, [:stage] => [:build] do |_, args|
140
+ deploy_stage(args.stage)
141
+ end
142
+
143
+ desc 'Remove this service from STAGE.'
144
+ task :remove, [:stage] do |_, args|
145
+ remove_stage(args.stage)
146
+ end