herdst_worker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c5b5decf991f20026eb7fe48327f09a7c385c6fad0fdff66e683a4d4d142c8d8
4
+ data.tar.gz: f3e8a159f0ceb54681f120a69c688ed95a86e78a1f7c008b960f2bd41c3558eb
5
+ SHA512:
6
+ metadata.gz: 768569b090a702a755a589d353af9c9f67d478c7a8e81bc7a6a92f04d5b3c826a11e546fa237347ed5e8c83284b630803673b749c830a32a2cfb87ff3e4fe5a3
7
+ data.tar.gz: 84044ceab2c26c95c5338b16d3ef4c97596781b04a5d4a341550aef02b7cd166d2b4eed1013986868ceaeacfe6066719b01e9c9f3089e856ac01aaee4d35fb08
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ ENV["TZ"] = "UTC"
2
+ ENV["ENV"] ||= (ARGV[0] == "test" ? "test" : "development")
3
+
4
+ require_relative 'lib/herdst_worker'
5
+
6
+ Rake.add_rakelib 'tasks'
7
+ task default: %w[test]
data/bin/herdst_worker ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ args = Hash[ ARGV.flat_map { |s| s.scan(/--?([^=\s]+)(?:=(\S+))?/) } ]
4
+
5
+ application_file = args["application"] || "application.rb"
6
+ application_path = "#{Dir.pwd}/#{application_file}"
7
+ application_env = args["env"] || "production"
8
+ queue_enabled = args["queue_enabled"] == "false" ? false : true
9
+
10
+ require_relative "../lib/herdst_worker"
11
+
12
+ application_instance = HerdstWorker::Application.new(application_env, queue_enabled)
13
+
14
+ require_relative application_path
15
+
16
+ application_instance.run
@@ -0,0 +1,8 @@
1
+ # Require Lib files
2
+ require 'active_support/all'
3
+ require 'active_record'
4
+ require 'mysql2'
5
+ require "aws-sdk-core"
6
+
7
+ # Require application
8
+ require_relative 'herdst_worker/application/facade'
@@ -0,0 +1,31 @@
1
+ module HerdstWorker
2
+ module Adapters
3
+ class Database
4
+
5
+
6
+ def self.setup(app)
7
+ db_config = app.config_for(:database)
8
+
9
+ if app.config.is_dev?
10
+ ActiveRecord::Base.logger = app.logger.activerecord
11
+ end
12
+
13
+ ActiveRecord::Base.default_timezone = :utc
14
+ ActiveRecord::Base.establish_connection(
15
+ adapter: db_config[:adapter],
16
+ encoding: db_config[:encoding],
17
+ charset: db_config[:charset],
18
+ collation: db_config[:collation],
19
+ pool: db_config[:pool],
20
+ host: db_config[:host],
21
+ username: db_config[:username],
22
+ password: db_config[:password],
23
+ database: db_config[:database]
24
+ )
25
+ ActiveRecord::Base.connection.enable_query_cache!
26
+ end
27
+
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ require "sentry-raven"
2
+
3
+ require_relative "database"
4
+ require_relative "sentry"
5
+
6
+
7
+ module HerdstWorker
8
+ module Adapters
9
+ class Facade
10
+
11
+
12
+ def self.bootstrap(app)
13
+ HerdstWorker::Adapters::Database.setup(app)
14
+ HerdstWorker::Adapters::Sentry.setup(app)
15
+ end
16
+
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ require "sentry-raven"
2
+
3
+
4
+ module HerdstWorker
5
+ module Adapters
6
+ class Sentry
7
+
8
+
9
+ def self.setup(app)
10
+ environment = app.config.metadata[:DEPLOYMENT_ENV]
11
+ release = app.config.metadata[:RELEASE_VERSION]
12
+ sentry_key = app.config.metadata[:SENTRY_KEY]
13
+
14
+ Raven.configure do |config|
15
+ config.current_environment = environment
16
+ config.release = release
17
+ config.dsn = sentry_key if sentry_key
18
+ config.environments = ["preview", "production"]
19
+ config.async = lambda { |event| Thread.new { Raven.send_event(event) } }
20
+ end
21
+ end
22
+
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,88 @@
1
+ require_relative '../configuration/facade'
2
+ require_relative '../log/facade'
3
+ require_relative '../autoload/facade'
4
+ require_relative '../signals/facade'
5
+ require_relative '../queue/facade'
6
+ require_relative '../adapters/facade'
7
+
8
+
9
+ module HerdstWorker
10
+ class Application
11
+
12
+
13
+ attr_accessor :name
14
+ attr_accessor :logger, :config, :autoload
15
+ attr_accessor :poller_enabled, :queues, :poller_url, :queue
16
+
17
+
18
+ def initialize(env, poller_enabled)
19
+ self.poller_enabled = poller_enabled
20
+ self.queues = ActiveSupport::HashWithIndifferentAccess.new
21
+
22
+ HerdstWorker.set_application(self)
23
+
24
+ self.logger = HerdstWorker::Log::Facade.new(get_logger_level(env))
25
+ self.config = HerdstWorker::Configuration::Facade.new(env)
26
+ self.autoload = HerdstWorker::Autoload::Facade.new(self)
27
+ self.set_inflections
28
+
29
+ HerdstWorker::Adapters::Facade.bootstrap(self)
30
+ end
31
+
32
+
33
+ def configure(&block)
34
+ yield self
35
+
36
+ self.autoload.reload
37
+ end
38
+
39
+
40
+ def add_queue(name, url, poller = false)
41
+ self.queues[name] = url
42
+ self.poller_url = url if poller
43
+ end
44
+
45
+
46
+ def run
47
+ if self.queue == nil
48
+ self.logger.info "Starting Application (#{$$})"
49
+
50
+ HerdstWorker::Signals::Facade.listen(self.config.paths.temp)
51
+
52
+ self.queue = HerdstWorker::Queue::Facade.new(self, self.poller_enabled, self.poller_url, 20)
53
+ self.queue.start
54
+ end
55
+ end
56
+
57
+
58
+ def config_for(name)
59
+ self.config.config_for(name)
60
+ end
61
+
62
+
63
+ private
64
+ def set_inflections
65
+ ActiveSupport::Inflector.inflections do |inflect|
66
+ inflect.irregular "meta", "meta"
67
+ end
68
+ end
69
+
70
+ def get_logger_level(env)
71
+ return "WARN" if env == "production"
72
+ return "FATAL" if env == "test"
73
+ return "DEBUG"
74
+ end
75
+
76
+
77
+ end
78
+
79
+
80
+ def self.set_application(application)
81
+ @@application = application
82
+ end
83
+
84
+
85
+ def self.application
86
+ @@application
87
+ end
88
+ end
@@ -0,0 +1,67 @@
1
+ module HerdstWorker
2
+ module Autoload
3
+ class Facade
4
+
5
+
6
+ attr_accessor :root_path, :loaded, :files, :paths
7
+
8
+
9
+ def initialize(app)
10
+ self.root_path = app.config.paths.root
11
+
12
+ self.loaded = []
13
+ self.files = []
14
+ self.paths = [ "lib" ]
15
+
16
+ self.reload
17
+ end
18
+
19
+
20
+ def <<(path)
21
+ full_path = self.root_path + "/" + path
22
+
23
+ if File.directory?(full_path)
24
+ self.paths << path
25
+ else
26
+ self.files << path
27
+ end
28
+ end
29
+
30
+
31
+ def reload
32
+ self.files.each do |file|
33
+ self.load_file(self.root_path + "/" + file)
34
+ end
35
+
36
+ self.paths.each do |folder|
37
+ concern_files = Dir.glob("#{root_path}/#{folder}/concerns/*.rb").sort
38
+ folder_files = Dir.glob("#{root_path}/#{folder}/*.rb").sort
39
+
40
+ concern_sub_files = Dir.glob("#{self.root_path}/#{folder}/**/concerns/*.rb").sort
41
+ folder_sub_files = Dir.glob("#{self.root_path}/#{folder}/**/*.rb").sort
42
+
43
+ files = (concern_files + folder_files + concern_sub_files + folder_sub_files).uniq
44
+
45
+ files.each do |file|
46
+ self.load_file(file)
47
+ end
48
+ end
49
+ end
50
+
51
+
52
+ def has_loaded?(file)
53
+ self.loaded.include?(file)
54
+ end
55
+
56
+
57
+ def load_file(file)
58
+ unless has_loaded?(file)
59
+ load file
60
+ self.loaded << file
61
+ end
62
+ end
63
+
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,51 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ require_relative 'metadata'
5
+ require_relative 'paths'
6
+
7
+
8
+ module HerdstWorker
9
+ module Configuration
10
+ class Facade
11
+
12
+
13
+ attr_accessor :env, :metadata, :paths, :actions
14
+
15
+
16
+ def initialize(env)
17
+ self.env = env.downcase
18
+
19
+ self.paths = Paths.new
20
+ self.metadata = Metadata.new(env, self)
21
+ self.actions = self.config_for(:actions)
22
+ end
23
+
24
+
25
+ def config_for(name)
26
+ file = "#{self.paths.config}/#{name}.yml"
27
+ file_contents = ERB.new(File.new(file).read)
28
+ yaml = YAML.load(file_contents.result(binding)).with_indifferent_access
29
+
30
+ yaml.include?(self.env) ? yaml[self.env] : yaml
31
+ end
32
+
33
+
34
+ def is_prod?
35
+ self.env == "production"
36
+ end
37
+
38
+
39
+ def is_test?
40
+ self.env == "test"
41
+ end
42
+
43
+
44
+ def is_dev?
45
+ self.env == "development"
46
+ end
47
+
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,183 @@
1
+ require "aws-sdk-core"
2
+ require "aws-sdk-secretsmanager"
3
+ require "aws-sdk-ecs"
4
+ require "net/http"
5
+
6
+
7
+ module HerdstWorker
8
+ module Configuration
9
+ class Metadata
10
+
11
+
12
+ attr_accessor :config, :aws_credentials, :secrets
13
+ attr_accessor :config_suffix, :secrets_suffix
14
+
15
+
16
+ def initialize(env, config)
17
+ self.config = config
18
+ self.config_suffix = "-service-config"
19
+ self.secrets_suffix = "-service-secrets"
20
+
21
+ self.secrets = {}.with_indifferent_access
22
+ self.secrets["ENV"] = env
23
+
24
+ begin
25
+ self.reload!
26
+ rescue Exception => ex
27
+ raise ex unless self.is_prod?
28
+ end
29
+ end
30
+
31
+
32
+ def reload!
33
+ self.get_aws_credentials!
34
+ self.set_task_info!
35
+ self.get_config_and_secrets!
36
+
37
+ self
38
+ end
39
+
40
+
41
+ def get_secrets
42
+ self.secrets
43
+ end
44
+
45
+
46
+ def [](key)
47
+ self.secrets[key.to_s.upcase]
48
+ end
49
+
50
+
51
+ def []=(key, value)
52
+ self.secrets[key.to_s.upcase] = value
53
+ end
54
+
55
+
56
+ def get_aws_credentials
57
+ self.get_aws_credentials! rescue nil
58
+ end
59
+
60
+
61
+ def get_aws_credentials!
62
+ credentials = self.is_prod? ?
63
+ make_request!(get_credentials_uri) :
64
+ self.config.config_for(:aws)
65
+
66
+ self.aws_credentials = Aws::Credentials.new(
67
+ credentials["AccessKeyId"],
68
+ credentials["SecretAccessKey"],
69
+ credentials["Token"]
70
+ )
71
+
72
+ Aws.config.update(
73
+ region: self.get_aws_region,
74
+ credentials: self.aws_credentials
75
+ )
76
+
77
+ self.aws_credentials
78
+ end
79
+
80
+
81
+ def get_aws_region
82
+ # "ap-southeast-2"
83
+
84
+ self["AWS_REGION"] = (ENV["AWS_REGION"] || "us-east-1")
85
+ end
86
+
87
+
88
+ protected
89
+ def is_prod?
90
+ self.secrets["ENV"] == "production"
91
+ end
92
+
93
+
94
+ def set_task_info!
95
+ return self.secrets["DEPLOYMENT_ENV"] if self.secrets["DEPLOYMENT_ENV"]
96
+
97
+ if self.is_prod?
98
+ metadata = make_request!(get_metadata_uri)
99
+
100
+ ecs_client = Aws::ECS::Client.new(
101
+ :region => self.secrets["AWS_REGION"],
102
+ :credentials => self.aws_credentials
103
+ )
104
+
105
+ ecs_response = ecs_client.describe_tasks(
106
+ :cluster => metadata["Cluster"],
107
+ :tasks => [
108
+ metadata["TaskARN"]
109
+ ],
110
+ :include => ["TAGS"]
111
+ )
112
+
113
+ ecs_response["tasks"][0]["tags"].each do |i|
114
+ self[i["key"]] = i["value"]
115
+ end
116
+
117
+ report_error("Deployment env not found") unless self.secrets["DEPLOYMENT_ENV"]
118
+
119
+ self["RELEASE_VERSION"] = ecs_response["tasks"][0]["containers"][0]["containerArn"] rescue nil
120
+ else
121
+ self["DEPLOYMENT_ENV"] = "development"
122
+ end
123
+ end
124
+
125
+
126
+ def get_config_and_secrets!
127
+ values = nil
128
+
129
+ if self.is_prod?
130
+ secrets_client = Aws::SecretsManager::Client.new(
131
+ :region => self.secrets["AWS_REGION"],
132
+ :credentials => self.aws_credentials
133
+ )
134
+
135
+ config = secrets_client.get_secret_value({
136
+ :secret_id => (self.secrets["DEPLOYMENT_ENV"].to_s + self.config_suffix)
137
+ })
138
+
139
+ secrets = secrets_client.get_secret_value({
140
+ :secret_id => (self.secrets["DEPLOYMENT_ENV"].to_s + self.secrets_suffix)
141
+ })
142
+
143
+ values = Hash.new
144
+ values.merge!(JSON.parse(config[:secret_string]))
145
+ values.merge!(JSON.parse(secrets[:secret_string]))
146
+ else
147
+ values = self.config.config_for(:metadata)
148
+ end
149
+
150
+ values.each { |k, v| self[k] = v } if values
151
+ end
152
+
153
+
154
+ private
155
+ def get_metadata_uri
156
+ ENV["ECS_CONTAINER_METADATA_URI"].to_s + "/task"
157
+ end
158
+
159
+ def get_credentials_uri
160
+ "http://169.254.170.2" + ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"].to_s
161
+ end
162
+
163
+ def make_request!(request_url)
164
+ uri = URI.parse(request_url)
165
+ http = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == "https")
166
+ request = Net::HTTP::Get.new(uri.to_s)
167
+ response = http.request(request) rescue nil
168
+
169
+ if response && response.code.to_i === 200
170
+ return JSON.parse(response.body).with_indifferent_access
171
+ else
172
+ report_error("Failed to ger request: #{request_url}")
173
+ end
174
+ end
175
+
176
+ def report_error(message)
177
+ raise message
178
+ end
179
+
180
+
181
+ end
182
+ end
183
+ end