herdst_worker 0.1.0

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 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