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.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.gitlab-ci.yml +13 -0
- data/.rubocop.yml +13 -0
- data/.travis.yml +9 -2
- data/README.md +184 -18
- data/Rakefile +2 -0
- data/bin/console +4 -9
- data/changelog.md +17 -0
- data/gems.locked +92 -0
- data/{Gemfile → gems.rb} +2 -1
- data/lib/shark-on-lambda.rb +1 -5
- data/lib/shark_on_lambda.rb +104 -1
- data/lib/shark_on_lambda/api_gateway/base_controller.rb +76 -0
- data/lib/shark_on_lambda/api_gateway/base_handler.rb +82 -0
- data/lib/shark_on_lambda/api_gateway/concerns/http_response_validation.rb +61 -0
- data/lib/shark_on_lambda/api_gateway/errors.rb +49 -0
- data/lib/shark_on_lambda/api_gateway/headers.rb +37 -0
- data/lib/shark_on_lambda/api_gateway/jsonapi_controller.rb +77 -0
- data/lib/shark_on_lambda/api_gateway/jsonapi_parameters.rb +68 -0
- data/lib/shark_on_lambda/api_gateway/jsonapi_renderer.rb +105 -0
- data/lib/shark_on_lambda/api_gateway/parameters.rb +18 -0
- data/lib/shark_on_lambda/api_gateway/query.rb +69 -0
- data/lib/shark_on_lambda/api_gateway/request.rb +148 -0
- data/lib/shark_on_lambda/api_gateway/response.rb +82 -0
- data/lib/shark_on_lambda/api_gateway/serializers/base_error_serializer.rb +20 -0
- data/lib/shark_on_lambda/concerns/filter_actions.rb +82 -0
- data/lib/shark_on_lambda/concerns/resettable_singleton.rb +18 -0
- data/lib/shark_on_lambda/concerns/yaml_config_loader.rb +28 -0
- data/lib/shark_on_lambda/configuration.rb +71 -0
- data/lib/shark_on_lambda/inferrers/name_inferrer.rb +66 -0
- data/lib/shark_on_lambda/inferrers/serializer_inferrer.rb +45 -0
- data/lib/shark_on_lambda/secrets.rb +43 -0
- data/lib/shark_on_lambda/tasks.rb +3 -0
- data/lib/shark_on_lambda/tasks/build.rake +146 -0
- data/lib/{shark-on-lambda → shark_on_lambda}/version.rb +1 -1
- data/shark-on-lambda.gemspec +21 -6
- metadata +158 -20
- 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,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
|