rails-service 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.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +99 -0
  5. data/.travis.yml +4 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE +21 -0
  8. data/README.md +71 -0
  9. data/Rakefile +16 -0
  10. data/app/controller/rails/service/admin_controller.rb +32 -0
  11. data/app/controller/rails/service/base_controller.rb +3 -0
  12. data/app/controller/rails/service/status_controller.rb +44 -0
  13. data/app/helpers/rails/service/admin_helper.rb +27 -0
  14. data/app/views/layouts/admin.html +100 -0
  15. data/app/views/rails/service/admin/config.html.erb +3 -0
  16. data/app/views/rails/service/admin/environment.html.erb +16 -0
  17. data/app/views/rails/service/admin/manifest.html.erb +1 -0
  18. data/app/views/rails/service/admin/rails_properties.html.erb +1 -0
  19. data/app/views/rails/service/admin/routing.html.erb +1 -0
  20. data/bin/console +8 -0
  21. data/bin/setup +7 -0
  22. data/config/routes.rb +9 -0
  23. data/lib/rails/service.rb +29 -0
  24. data/lib/rails/service/admin_view_resolver.rb +18 -0
  25. data/lib/rails/service/app_config.rb +28 -0
  26. data/lib/rails/service/boot.rb +23 -0
  27. data/lib/rails/service/config.rb +128 -0
  28. data/lib/rails/service/context.rb +13 -0
  29. data/lib/rails/service/core_ext/deep_struct.rb +23 -0
  30. data/lib/rails/service/engine.rb +20 -0
  31. data/lib/rails/service/manifest.rb +27 -0
  32. data/lib/rails/service/version.rb +5 -0
  33. data/rails-service.gemspec +22 -0
  34. data/spec/controllers/admin_controller_spec.rb +51 -0
  35. data/spec/controllers/status_controller_spec.rb +21 -0
  36. data/spec/fixtures/app-manifest.yaml +7 -0
  37. data/spec/lib/app_config_spec.rb +35 -0
  38. data/spec/lib/context_spec.rb +22 -0
  39. data/spec/lib/manifest_spec.rb +62 -0
  40. data/spec/rails_app/README.rdoc +28 -0
  41. data/spec/rails_app/Rakefile +6 -0
  42. data/spec/rails_app/app-manifest.yaml +6 -0
  43. data/spec/rails_app/app/assets/images/.keep +0 -0
  44. data/spec/rails_app/app/assets/javascripts/application.js +13 -0
  45. data/spec/rails_app/app/assets/stylesheets/application.css +15 -0
  46. data/spec/rails_app/app/controllers/application_controller.rb +5 -0
  47. data/spec/rails_app/app/controllers/concerns/.keep +0 -0
  48. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  49. data/spec/rails_app/app/mailers/.keep +0 -0
  50. data/spec/rails_app/app/models/.keep +0 -0
  51. data/spec/rails_app/app/models/concerns/.keep +0 -0
  52. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  53. data/spec/rails_app/bin/bundle +3 -0
  54. data/spec/rails_app/bin/rails +4 -0
  55. data/spec/rails_app/bin/rake +4 -0
  56. data/spec/rails_app/bin/setup +29 -0
  57. data/spec/rails_app/config.ru +4 -0
  58. data/spec/rails_app/config/app-config.yaml +8 -0
  59. data/spec/rails_app/config/application.rb +17 -0
  60. data/spec/rails_app/config/boot.rb +5 -0
  61. data/spec/rails_app/config/database.yml +25 -0
  62. data/spec/rails_app/config/environment.rb +5 -0
  63. data/spec/rails_app/config/environments/development.rb +41 -0
  64. data/spec/rails_app/config/environments/production.rb +79 -0
  65. data/spec/rails_app/config/environments/test.rb +42 -0
  66. data/spec/rails_app/config/initializers/assets.rb +11 -0
  67. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/rails_app/config/initializers/cookies_serializer.rb +3 -0
  69. data/spec/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
  70. data/spec/rails_app/config/initializers/inflections.rb +16 -0
  71. data/spec/rails_app/config/initializers/mime_types.rb +4 -0
  72. data/spec/rails_app/config/initializers/session_store.rb +3 -0
  73. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  74. data/spec/rails_app/config/locales/en.yml +23 -0
  75. data/spec/rails_app/config/routes.rb +4 -0
  76. data/spec/rails_app/config/secrets.yml +22 -0
  77. data/spec/rails_app/db/test.sqlite3 +0 -0
  78. data/spec/rails_app/lib/assets/.keep +0 -0
  79. data/spec/rails_app/lib/custom_admin_actions.rb +7 -0
  80. data/spec/rails_app/lib/service/admin/actions.rb +11 -0
  81. data/spec/rails_app/lib/service/admin/views/foobar.html.erb +1 -0
  82. data/spec/rails_app/lib/service/status/actions.rb +11 -0
  83. data/spec/rails_app/log/.keep +0 -0
  84. data/spec/rails_app/public/404.html +67 -0
  85. data/spec/rails_app/public/422.html +67 -0
  86. data/spec/rails_app/public/500.html +66 -0
  87. data/spec/rails_app/public/favicon.ico +0 -0
  88. data/spec/routes_spec.rb +32 -0
  89. data/spec/spec_helper.rb +16 -0
  90. metadata +208 -0
@@ -0,0 +1,3 @@
1
+ <h2>App Config</h2>
2
+
3
+ <pre><%= JSON.pretty_generate(@config.to_h) %></pre>
@@ -0,0 +1,16 @@
1
+ <table class="table table-bordered">
2
+ <thead>
3
+ <tr>
4
+ <th>Key</th>
5
+ <th>Value</th>
6
+ </tr>
7
+ </thead>
8
+ <tbody>
9
+ <% @env.each do |key, value| %>
10
+ <tr>
11
+ <td class="key"><%= key %></td>
12
+ <td class="value"><%= value %></td>
13
+ </tr>
14
+ <% end %>
15
+ </tbody>
16
+ </table>
@@ -0,0 +1 @@
1
+ <pre><%= JSON.pretty_generate(@manifest.to_h) %></pre>
@@ -0,0 +1 @@
1
+ <%= render_rails_iframe(name: "rails-properties", src: "/rails/info/properties") %>
@@ -0,0 +1 @@
1
+ <%= render_rails_iframe(name: "rails-routes", src: "/rails/info/routes") %>
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rails/all"
5
+ require "rails/service"
6
+
7
+ require "pry"
8
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ Rails::Service::Engine.routes.draw do
2
+ constraints Rails::Service.config.status_routes_constraint do
3
+ get '/_status(/:action)', to: 'status#index', as: :status
4
+ end
5
+
6
+ constraints Rails::Service.config.admin_routes_constraint do
7
+ match '/_admin(/:action)', to: 'admin#environment', via: [:get, :post], as: :admin
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ require 'rails/service/version'
2
+ require 'rails/service/config'
3
+ require 'rails/service/context'
4
+ require 'rails/service/manifest'
5
+ require 'rails/service/app_config'
6
+
7
+ module Rails
8
+ module Service
9
+ module_function
10
+
11
+ def config
12
+ @config ||= Config.new
13
+ end
14
+
15
+ def context
16
+ @context ||= Context.new(config._for_context)
17
+ end
18
+
19
+ def manifest
20
+ @manifest ||= Manifest.new(config._for_manifest)
21
+ end
22
+
23
+ def app_config
24
+ @app_config ||= AppConfig.new(config._for_app_config)
25
+ end
26
+ end
27
+ end
28
+
29
+ require 'rails/service/engine'
@@ -0,0 +1,18 @@
1
+ require 'action_view/template/resolver'
2
+
3
+ module Rails
4
+ module Service
5
+ # AdminViewResolver is a custom ActionView resolver which
6
+ # removes the prefix normally added to path when resolvin templates.
7
+ # Without it, we can set arbitraty view path for Rails::Service Admin controller
8
+ class AdminViewResolver < ::ActionView::FileSystemResolver
9
+ def initialize(path)
10
+ super(Rails.root.join(path))
11
+ end
12
+
13
+ def find_templates(name, _prefix, partial, details)
14
+ super(name, '', partial, details)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ require 'rails/service/core_ext/deep_struct'
5
+
6
+ module Rails
7
+ module Service
8
+ module AppConfig
9
+ module_function
10
+
11
+ def new(options = {})
12
+ path = options.fetch(:path)
13
+ logger = options.fetch(:logger)
14
+ env = options.fetch(:env)
15
+
16
+ path = Rails.root.join(path)
17
+ if File.exist?(path)
18
+ file = File.read(path)
19
+ logger.info("loading app config file: #{path}")
20
+ DeepStruct.new(YAML.load(ERB.new(file).result)[env.to_s])
21
+ else
22
+ logger.warn("app config file not found: #{path}")
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,23 @@
1
+ module Rails
2
+ module Service
3
+ module Boot
4
+ module_function
5
+
6
+ STATUS_CONTROLLER = 'Rails::Service::StatusController'.freeze
7
+
8
+ def lograge(app)
9
+ return unless defined?(Lograge) && !app.config.lograge.enable
10
+
11
+ app.config.lograge.ignore_custom = lambda { |event|
12
+ !app.config.service.status_logs_enabled && event.payload[:controller] == STATUS_CONTROLLER
13
+ }
14
+
15
+ app.config.lograge.custom_options = lambda { |_event|
16
+ { dc: app.config.service.dc, host: app.config.service.host, app: app.config.service.app }
17
+ }
18
+
19
+ Lograge.setup(app)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,128 @@
1
+ require 'socket'
2
+
3
+ module Rails
4
+ module Service
5
+ class Config
6
+ attr_writer :app, :dc, :host
7
+
8
+ attr_accessor :process_id
9
+
10
+ attr_writer :status_routes_contraint, :admin_routes_contraint,
11
+ :admin_view_paths, :status_action_modules, :admin_action_modules,
12
+ :status_logs_enabled, :manifest_path, :logger, :env, :app_config_path
13
+
14
+ def app
15
+ @app ||= begin
16
+ if Rails::Service.manifest.respond_to?(:app)
17
+ Rails::Service.manifest.app
18
+ else
19
+ Rails.application.class.parent_name.downcase
20
+ end
21
+ end
22
+ end
23
+
24
+ def dc
25
+ @dc ||= (ENV.key?('DATACENTER') ? ENV.fetch('DATACENTER') : 'local').downcase
26
+ end
27
+
28
+ def host
29
+ @host ||= Socket.gethostname
30
+ end
31
+
32
+ def process_id
33
+ @process_id ||= -> (app, dc, host, pid, _rev) { "#{app}:#{dc}:#{host}:#{pid}:#{SecureRandom.uuid}".freeze }
34
+ end
35
+
36
+ def pid
37
+ @pid ||= Process.pid
38
+ end
39
+
40
+ def rev
41
+ @rev ||= File.exist?('REVISION') ? File.read('REVISION') : '(none)'
42
+ end
43
+
44
+ def env
45
+ @env ||= Rails.env
46
+ end
47
+
48
+ def logger
49
+ @logger ||= Rails.logger
50
+ end
51
+
52
+ def manifest_path
53
+ @manifest_path ||= Rails.root.join('app-manifest.yaml')
54
+ end
55
+
56
+ def app_config_path
57
+ @app_conifg_path ||= Rails.root.join('config/app-config.yaml')
58
+ end
59
+
60
+ def status_logs_enabled
61
+ @status_logs_enabled ||= false
62
+ end
63
+
64
+ def status_routes_constraint
65
+ @status_routes_constraint ||= -> (_request) { true }
66
+ end
67
+
68
+ def admin_routes_constraint
69
+ @admin_routes_constraint ||= -> (request) { request.local? }
70
+ end
71
+
72
+ def admin_action_modules
73
+ @admin_action_modules ||= [_default_action_module(:admin)]
74
+ end
75
+
76
+ def status_action_modules
77
+ @status_action_modules ||= [_default_action_module(:status)]
78
+ end
79
+
80
+ def admin_view_paths
81
+ @admin_view_paths ||= ['lib/service/admin/views']
82
+ end
83
+
84
+ def admin_view_resolvers
85
+ admin_view_paths.map { |admin_view_path| Rails::Service::AdminViewResolver.new(admin_view_path) }
86
+ end
87
+
88
+ def _for_context
89
+ {
90
+ app: app,
91
+ dc: dc,
92
+ host: host,
93
+ env: env,
94
+ pid: pid,
95
+ rev: rev,
96
+ process_id: process_id.call(app, dc, host, pid, rev),
97
+ logger: logger,
98
+ }
99
+ end
100
+
101
+ def _for_manifest
102
+ {
103
+ path: manifest_path,
104
+ logger: logger,
105
+ }
106
+ end
107
+
108
+ def _for_app_config
109
+ {
110
+ path: app_config_path,
111
+ logger: logger,
112
+ env: env,
113
+ }
114
+ end
115
+
116
+ private
117
+
118
+ def _default_action_module(resource)
119
+ filename = "service/#{resource}/actions"
120
+ return unless File.exist?(Rails.root.join("lib/#{filename}.rb"))
121
+
122
+ require filename
123
+ mod = "#{Rails.application.class.parent_name}::Service::#{resource.to_s.capitalize}::Actions"
124
+ mod.constantize if Kernel.const_defined?(mod)
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,13 @@
1
+ require 'ostruct'
2
+
3
+ module Rails
4
+ module Service
5
+ # Context is an object which holds common configuration and informations
6
+ # about your service/applicaion that your service uses.
7
+ #
8
+ # It is very helpful when developing libraries that will be shared between
9
+ # your services.
10
+ class Context < OpenStruct
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ # Cribbed from http://andreapavoni.com/blog/2013/4/create-recursive-openstruct-from-a-ruby-hash/
2
+ require 'ostruct'
3
+
4
+ class DeepStruct < OpenStruct
5
+ def initialize(hash = nil)
6
+ @table = {}
7
+ @hash_table = {}
8
+
9
+ return unless hash
10
+
11
+ hash.each do |k, v|
12
+ k = k.to_s
13
+ @table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
14
+ @hash_table[k] = v
15
+
16
+ new_ostruct_member(k)
17
+ end
18
+ end
19
+
20
+ def to_h
21
+ @hash_table
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ require 'rails/engine'
2
+ require 'rails/service'
3
+ require 'rails/service/context'
4
+ require 'rails/service/boot'
5
+
6
+ module Rails
7
+ module Service
8
+ class Engine < Rails::Engine
9
+ engine_name 'rails-service'
10
+ isolate_namespace Rails::Service
11
+
12
+ config.autoload_paths << File.expand_path('../../../', __FILE__)
13
+ config.service = Rails::Service.config
14
+
15
+ initializer 'rails.service.lograge' do |app|
16
+ Rails::Service::Boot.lograge(app)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+
4
+ require 'rails/service/core_ext/deep_struct'
5
+
6
+ module Rails
7
+ module Service
8
+ module Manifest
9
+ module_function
10
+
11
+ def new(options = {})
12
+ path = options.fetch(:path)
13
+ logger = options.fetch(:logger)
14
+
15
+ path = Rails.root.join(path)
16
+ if File.exist?(path)
17
+ file = File.read(path)
18
+ logger.info("loading manifest file: #{path}")
19
+ DeepStruct.new(YAML.load(ERB.new(file).result))
20
+ else
21
+ logger.warn("manifest file not found: #{path}")
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module Rails
2
+ module Service
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rails/service/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rails-service"
8
+ spec.version = Rails::Service::VERSION
9
+ spec.authors = ["Łukasz Strzałkowski"]
10
+ spec.email = ["lukasz.strzalkowski@gmail.com"]
11
+
12
+ spec.summary = %q{Microservices on Rails}
13
+
14
+ spec.files = `git ls-files`.split("\n")
15
+ spec.test_files = `git ls-files -- spec/*`.split("\n")
16
+
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.required_ruby_version = '>= 2.0.0'
20
+
21
+ spec.add_dependency "rails", ">= 3.2.6", "< 5"
22
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Rails::Service::AdminController, type: :controller do
4
+ routes { Rails::Service::Engine.routes }
5
+ render_views
6
+
7
+ it 'renders environment action' do
8
+ get :environment
9
+ expect(response).to have_http_status(:ok)
10
+ expect(response).to render_template(:environment)
11
+ end
12
+
13
+ it 'renders app manifest' do
14
+ get :manifest
15
+ expect(response).to have_http_status :ok
16
+ expect(response).to render_template 'manifest'
17
+ expect(assigns(:manifest)).to eq Rails::Service.manifest
18
+ expect(response.body).to include 'fooxample'
19
+ end
20
+
21
+ it 'renders app config' do
22
+ get :config
23
+ expect(response).to have_http_status :ok
24
+ expect(response).to render_template 'config'
25
+ expect(assigns(:config)).to eq Rails::Service.app_config
26
+ expect(response.body).to include 'foobar'
27
+ expect(response.body).to_not include 'foobar2'
28
+ expect(response.body).to include 'foobar-overwritten'
29
+ expect(response.body).to include 'barfoo'
30
+ end
31
+
32
+ describe 'custom resolvers' do
33
+ describe 'default' do
34
+ it 'renders custom action included from lib/service/admin/actions' do
35
+ get :foobar
36
+ expect(response).to have_http_status :ok
37
+ expect(response).to render_template 'foobar'
38
+ expect(assigns(:foo)).to eq 'test'
39
+ expect(response.body).to include 'OH HAI'
40
+ end
41
+ end
42
+
43
+ describe 'custom module at custom path (configured in application.rb)' do
44
+ it 'renders action from custom module' do
45
+ get :custom
46
+ expect(response).to have_http_status :ok
47
+ expect(response.body).to include 'custom action'
48
+ end
49
+ end
50
+ end
51
+ end