napa 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 (44) hide show
  1. data/.gitignore +21 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.md +172 -0
  5. data/Rakefile +3 -0
  6. data/bin/napa +17 -0
  7. data/lib/generators/scaffold.rb +67 -0
  8. data/lib/generators/templates/scaffold/.env +6 -0
  9. data/lib/generators/templates/scaffold/.env.test +7 -0
  10. data/lib/generators/templates/scaffold/.gitignore.tpl +10 -0
  11. data/lib/generators/templates/scaffold/Gemfile +28 -0
  12. data/lib/generators/templates/scaffold/README.md +3 -0
  13. data/lib/generators/templates/scaffold/Rakefile +8 -0
  14. data/lib/generators/templates/scaffold/app/apis/hello_api.rb +13 -0
  15. data/lib/generators/templates/scaffold/app.rb +21 -0
  16. data/lib/generators/templates/scaffold/config/database.yml +18 -0
  17. data/lib/generators/templates/scaffold/config/initializers/active_record.rb +5 -0
  18. data/lib/generators/templates/scaffold/config/middleware/honeybadger.rb +7 -0
  19. data/lib/generators/templates/scaffold/config.ru +17 -0
  20. data/lib/generators/templates/scaffold/console +5 -0
  21. data/lib/generators/templates/scaffold/lib/password_protected_helpers.rb +17 -0
  22. data/lib/generators/templates/scaffold/spec/apis/hello_api_spec.rb +17 -0
  23. data/lib/generators/templates/scaffold/spec/factories/.gitkeep +0 -0
  24. data/lib/generators/templates/scaffold/spec/spec_helper.rb +35 -0
  25. data/lib/napa/activerecord.rb +21 -0
  26. data/lib/napa/grape_api.rb +15 -0
  27. data/lib/napa/identity.rb +38 -0
  28. data/lib/napa/logger/log_transaction.rb +17 -0
  29. data/lib/napa/logger/logger.rb +34 -0
  30. data/lib/napa/middleware/app_monitor.rb +17 -0
  31. data/lib/napa/middleware/logger.rb +69 -0
  32. data/lib/napa/version.rb +45 -0
  33. data/lib/napa.rb +37 -0
  34. data/lib/tasks/db.rake +87 -0
  35. data/lib/tasks/deploy.rake +13 -0
  36. data/lib/tasks/git.rake +50 -0
  37. data/lib/tasks/routes.rake +9 -0
  38. data/napa.gemspec +31 -0
  39. data/spec/identity_spec.rb +50 -0
  40. data/spec/logger/log_transaction_spec.rb +34 -0
  41. data/spec/spec_helper.rb +1 -0
  42. data/spec/version_spec.rb +40 -0
  43. data/tasks/version.rake +51 -0
  44. metadata +275 -0
@@ -0,0 +1,34 @@
1
+ module Napa
2
+ class Logger
3
+ class << self
4
+ def name
5
+ [Napa::Identity.name, Napa::LogTransaction.id].join('-')
6
+ end
7
+
8
+ def logger=(logger)
9
+ @logger = logger
10
+ end
11
+
12
+ def logger
13
+ unless @logger
14
+ Logging.appenders.stdout(
15
+ 'stdout',
16
+ :layout => Logging.layouts.json
17
+ )
18
+ Logging.appenders.file(
19
+ "log/#{Napa.env}.log",
20
+ :layout => Logging.layouts.json
21
+ )
22
+
23
+ @logger = Logging.logger["[#{name}]"]
24
+ unless Napa.env.test?
25
+ @logger.add_appenders 'stdout'
26
+ end
27
+ @logger.add_appenders "log/#{Napa.env}.log"
28
+ end
29
+
30
+ @logger
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ module Napa
2
+ class Middleware
3
+ class AppMonitor
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ if env['REQUEST_PATH'] == '/health'
10
+ [200, {'Content-type' => 'application/json'}, [Napa::Identity.health.to_json]]
11
+ else
12
+ @app.call(env)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,69 @@
1
+ module Napa
2
+ class Middleware
3
+ class Logger
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ # set transaction_id
10
+ transaction_id=SecureRandom.uuid
11
+
12
+ # log the request
13
+ Napa::Logger.logger.debug format_request(env)
14
+
15
+ # process the request
16
+ status, headers, body = @app.call(env)
17
+
18
+ # log the response
19
+ Napa::Logger.logger.debug format_response(status, headers, body)
20
+
21
+ # return the results
22
+ [status, headers, body]
23
+ ensure
24
+ # Clear the transaction id after each request
25
+ Napa::LogTransaction.clear
26
+ end
27
+
28
+ private
29
+ def format_request(env)
30
+ request = Rack::Request.new(env)
31
+ params = request.params
32
+ begin
33
+ params = JSON.parse(request.body.read) if env['CONTENT_TYPE'] == 'application/json'
34
+ rescue
35
+ # do nothing, params is already set
36
+ end
37
+
38
+ request_data = {
39
+ method: env['REQUEST_METHOD'],
40
+ path: env['PATH_INFO'],
41
+ query: env['QUERY_STRING'],
42
+ host: Napa::Identity.hostname,
43
+ pid: Napa::Identity.pid,
44
+ revision: Napa::Identity.revision,
45
+ params: params
46
+ }
47
+ request_data[:user_id] = current_user.try(:id) if defined?(current_user)
48
+ {request: request_data}
49
+ end
50
+
51
+ def format_response(status, headers, body)
52
+ response_body = nil
53
+ begin
54
+ response_body = body.respond_to?(:body) ? body.body.map{|r| r} : nil
55
+ rescue
56
+ response_body = body.inspect
57
+ end
58
+
59
+ {response:
60
+ {
61
+ status: status,
62
+ headers: headers,
63
+ response: response_body
64
+ }
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,45 @@
1
+ module Napa
2
+ VERSION = "0.1.0"
3
+
4
+ class Version
5
+ class << self
6
+ def next_major
7
+ self.next_level(:major)
8
+ end
9
+
10
+ def next_minor
11
+ self.next_level(:minor)
12
+ end
13
+
14
+ def next_patch
15
+ self.next_level(:patch)
16
+ end
17
+
18
+ def next_level(level)
19
+ raise "Unidentified Level" unless [:major,:minor,:patch].include?(level)
20
+
21
+ parts = Napa::VERSION.split('.').map{|p| p.to_i}
22
+
23
+ if level == :major
24
+ parts[0] += 1
25
+ significant_index = 1
26
+ end
27
+
28
+ if level == :minor
29
+ parts[1] += 1
30
+ significant_index = 2
31
+ end
32
+
33
+ if level == :patch
34
+ parts[2] += 1
35
+ significant_index = 3
36
+ end
37
+
38
+ parts.map.with_index{|p,i| parts[i] = 0 if i >= significant_index}
39
+
40
+ parts.join('.')
41
+ end
42
+
43
+ end
44
+ end
45
+ end
data/lib/napa.rb ADDED
@@ -0,0 +1,37 @@
1
+ # require external libraries
2
+ require 'rake'
3
+ require 'dotenv'
4
+ require 'logging'
5
+ require 'octokit'
6
+ require 'grape'
7
+
8
+ # require internal files
9
+ require "napa/version"
10
+ require "napa/logger/logger"
11
+ require "napa/logger/log_transaction"
12
+ require "napa/identity"
13
+ require "napa/middleware/logger"
14
+ require "napa/middleware/app_monitor"
15
+ require "napa/activerecord"
16
+ require "napa/grape_api"
17
+ require "generators/scaffold"
18
+
19
+ # load rake tasks if Rake installed
20
+ if defined?(Rake)
21
+ load 'tasks/git.rake'
22
+ load 'tasks/deploy.rake'
23
+ load 'tasks/routes.rake'
24
+ load 'tasks/db.rake'
25
+ end
26
+
27
+ module Napa
28
+ class << self
29
+ def env
30
+ @_env ||= ActiveSupport::StringInquirer.new(ENV["RACK_ENV"] || "development")
31
+ end
32
+
33
+ def env=(environment)
34
+ @_env = ActiveSupport::StringInquirer.new(environment)
35
+ end
36
+ end
37
+ end
data/lib/tasks/db.rake ADDED
@@ -0,0 +1,87 @@
1
+ task :environment do
2
+ require 'erb'
3
+ require './app'
4
+
5
+ raise "ActiveRecord Not Found" unless Module.const_defined?(:ActiveRecord)
6
+ end
7
+
8
+ namespace :db do
9
+ desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
10
+ task :migrate => :environment do
11
+ ActiveRecord::Migrator.migrate('db/migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil )
12
+ Rake::Task["db:schema:dump"].invoke
13
+ end
14
+
15
+ desc "Create the database"
16
+ task :create => :environment do
17
+ db = YAML.load(ERB.new(File.read('./config/database.yml')).result)[Napa.env]
18
+ adapter = db.merge({'database'=> 'mysql'})
19
+ ActiveRecord::Base.establish_connection(adapter)
20
+ ActiveRecord::Base.connection.create_database(db.fetch('database'))
21
+ end
22
+
23
+ desc "Delete the database"
24
+ task :drop => :environment do
25
+ db = YAML.load(ERB.new(File.read('./config/database.yml')).result)[Napa.env]
26
+ adapter = db.merge({'database'=> 'mysql'})
27
+ ActiveRecord::Base.establish_connection(adapter)
28
+ ActiveRecord::Base.connection.drop_database(db.fetch('database'))
29
+ end
30
+
31
+ desc "Create the test database"
32
+ task :reset => :environment do
33
+ Rake::Task["db:drop"].invoke
34
+ Rake::Task["db:create"].invoke
35
+ Rake::Task["db:schema:load"].invoke
36
+ end
37
+
38
+ namespace :generate do
39
+ desc "Generate a migration with given name. Specify migration name with NAME=my_migration_name"
40
+ task :migration => :environment do
41
+ raise "Please specify desired migration name with NAME=my_migration_name" unless ENV['NAME']
42
+
43
+ # Find migration name from env
44
+ migration_name = ENV['NAME'].strip.chomp
45
+
46
+ # Define migrations path (needed later)
47
+ migrations_path = './db/migrate'
48
+
49
+ # Find the highest existing migration version or set to 1
50
+ if (existing_migrations = Dir[File.join(migrations_path, '*.rb')]).length > 0
51
+ version = File.basename(existing_migrations.sort.reverse.first)[/^(\d+)_/,1].to_i + 1
52
+ else
53
+ version = 1
54
+ end
55
+
56
+ # Use the migration template to fill the body of the migration
57
+ migration_content = Napa::ActiveRecord.migration_template(migration_name.camelize)
58
+
59
+ # Generate migration filename
60
+ migration_filename = "#{"%03d" % version}_#{migration_name}.rb"
61
+
62
+ # Write the migration
63
+ File.open(File.join(migrations_path, migration_filename), "w+") do |migration|
64
+ migration.puts migration_content
65
+ end
66
+
67
+ # Done!
68
+ puts "Successfully created migration #{migration_filename}"
69
+ end
70
+ end
71
+
72
+ namespace :schema do
73
+ desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
74
+ task :dump => :environment do
75
+ require 'active_record/schema_dumper'
76
+ File.open(ENV['SCHEMA'] || "db/schema.rb", "w") do |file|
77
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
78
+ end
79
+ end
80
+
81
+ desc "Load a schema.rb file into the database"
82
+ task :load => :environment do
83
+ file = ENV['SCHEMA'] || "db/schema.rb"
84
+ load(file)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,13 @@
1
+ namespace :deploy do
2
+ desc "Deploy to production"
3
+ task :production do
4
+ Rake::Task["git:verify"].invoke
5
+ Rake::Task["git:set_tag"].invoke
6
+ end
7
+
8
+ desc "Deploy to staging"
9
+ task :staging do
10
+ Rake::Task["git:verify"].invoke
11
+ Rake::Task["git:set_tag"].invoke("staging")
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ namespace :git do
2
+ client = Octokit::Client.new(:oauth_token => ENV['GITHUB_OAUTH_TOKEN'])
3
+ logger = Logger.new(STDOUT)
4
+
5
+ github_repo = ENV['GITHUB_REPO']
6
+ local_sha = `git rev-parse HEAD`.strip
7
+
8
+ desc "Verify git repository is in a good state for deployment"
9
+ task :verify do
10
+ logger.info "Verifying git repository is in a good state"
11
+
12
+ # Be sure local HEAD exists on remote
13
+ begin
14
+ remote_commit = client.commit(github_repo, local_sha)
15
+ rescue Octokit::NotFound
16
+ raise RuntimeError, "Local commit #{local_sha} does not exist on remote. Be sure to push your changes."
17
+ end
18
+
19
+ # Check for uncommited changes
20
+ unless(system("git diff --quiet HEAD"))
21
+ raise RuntimeError, "You have uncommited changes. Either commit or stash them before continuing."
22
+ end
23
+
24
+ # Check for untracked files
25
+ unless(`git status --porcelain | grep '^??' | wc -l`.strip == "0")
26
+ raise RuntimeError, "You have untracked files. Either commit or remove them before continuing."
27
+ end
28
+ end
29
+
30
+ desc "Set tag, which triggers deploy"
31
+ task :set_tag, :tag do |t, args|
32
+ tag = args[:tag] || "production"
33
+ logger.info "Setting #{tag} tag on github"
34
+
35
+ # Update ref, create ref if it doesn't exist
36
+ begin
37
+ client.update_ref(
38
+ github_repo,
39
+ "tags/#{tag}",
40
+ local_sha
41
+ )
42
+ rescue Octokit::UnprocessableEntity
43
+ client.create_ref(
44
+ github_repo,
45
+ "tags/#{tag}",
46
+ local_sha
47
+ )
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ desc "display all routes"
2
+ task :routes do
3
+ grape_apis = ObjectSpace.each_object(Class).select { |klass| klass < Grape::API }
4
+ grape_apis.each do |api|
5
+ api.routes.each do |r|
6
+ puts "#{r}"
7
+ end
8
+ end
9
+ end
data/napa.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/napa/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Darby Frey"]
6
+ gem.email = ["darby@bellycard.com"]
7
+ gem.description = %q{A simple framework for building APIs with Grape}
8
+ gem.summary = %q{A simple framework for building APIs with Grape}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables << 'napa'
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "napa"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Napa::VERSION
17
+
18
+
19
+ gem.add_dependency 'rake'
20
+ gem.add_dependency 'logging'
21
+ gem.add_dependency 'dotenv'
22
+ gem.add_dependency 'octokit', '~> 1.25'
23
+ gem.add_dependency 'virtus', '0.5.5'
24
+ gem.add_dependency 'grape'
25
+ gem.add_dependency 'grape-swagger'
26
+ gem.add_dependency 'unicorn'
27
+
28
+ gem.add_development_dependency "rspec"
29
+ gem.add_development_dependency "pry"
30
+ gem.add_development_dependency "git"
31
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'napa/identity'
3
+
4
+ describe Napa::Identity do
5
+ context "#name" do
6
+ it "returns 'api-service' if no ENV['SERVICE_NAME'] is set" do
7
+ ENV['SERVICE_NAME'] = nil
8
+ Napa::Identity.name.should == 'api-service'
9
+ end
10
+
11
+ it "returns the ENV['SERVICE_NAME'] when specified" do
12
+ ENV['SERVICE_NAME'] = nil
13
+ ENV['SERVICE_NAME'] = 'my-service'
14
+ Napa::Identity.name.should == 'my-service'
15
+ end
16
+ end
17
+
18
+ context "#hostname" do
19
+ it "returns the value of the hostname system call and doesn't make a second system call" do
20
+ Napa::Identity.should_receive(:`).with("hostname").and_return("system-hostname")
21
+ Napa::Identity.hostname.should == 'system-hostname'
22
+
23
+ Napa::Identity.should_not_receive(:`).with("hostname")
24
+ Napa::Identity.hostname.should == 'system-hostname'
25
+ end
26
+ end
27
+
28
+ context "#revision" do
29
+ it "returns the value of the 'git rev-parse HEAD' system call and doesn't make a second system call" do
30
+ Napa::Identity.should_receive(:`).with("git rev-parse HEAD").and_return("12345")
31
+ Napa::Identity.revision.should == '12345'
32
+
33
+ Napa::Identity.should_not_receive(:`).with("git rev-parse HEAD")
34
+ Napa::Identity.revision.should == '12345'
35
+ end
36
+ end
37
+
38
+ context "#pid" do
39
+ it "returns the process ID value" do
40
+ Process.stub(:pid).and_return(112233)
41
+ Napa::Identity.pid.should == 112233
42
+ end
43
+ end
44
+
45
+ context "#platform_revision" do
46
+ it "returns the current version of the platform gem" do
47
+ Napa::Identity.platform_revision.should == Napa::VERSION
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'napa/logger/log_transaction'
3
+
4
+ describe Napa::LogTransaction do
5
+ before(:each) do
6
+ Napa::LogTransaction.clear
7
+ end
8
+
9
+ context "#id" do
10
+ it "returns the current transaction id if it has been set" do
11
+ id = SecureRandom.hex(10)
12
+ Thread.current[:napa_tid] = id
13
+ Napa::LogTransaction.id.should == id
14
+ end
15
+
16
+ it "sets and returns a new id if the transaction id hasn't been set" do
17
+ Napa::LogTransaction.id.should_not be_nil
18
+ end
19
+
20
+ it "allows the id to be overridden by a setter" do
21
+ Napa::LogTransaction.id.should_not be_nil
22
+ Napa::LogTransaction.id = 'foo'
23
+ Napa::LogTransaction.id.should == 'foo'
24
+ end
25
+ end
26
+
27
+ context "#clear" do
28
+ it "sets the id to nil" do
29
+ Napa::LogTransaction.id.should_not be_nil
30
+ Napa::LogTransaction.clear
31
+ Thread.current[:napa_tid].should be_nil
32
+ end
33
+ end
34
+ end
@@ -0,0 +1 @@
1
+ require 'napa'
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'napa/version'
3
+
4
+ describe Napa::Version do
5
+ context "#major_bump" do
6
+ it "should set the major revision value, and the rest should be 0" do
7
+ stub_const("Napa::VERSION", "1.2.3")
8
+ Napa::Version.next_major.should == "2.0.0"
9
+ end
10
+
11
+ it "should set the major revision value, and the rest should be 0" do
12
+ stub_const("Napa::VERSION", "5.0.0")
13
+ Napa::Version.next_major.should == "6.0.0"
14
+ end
15
+ end
16
+
17
+ context "#minor_bump" do
18
+ it "should set the minor revision value, leaving the major value unchanged and the patch value to 0" do
19
+ stub_const("Napa::VERSION", "1.2.3")
20
+ Napa::Version.next_minor.should == "1.3.0"
21
+ end
22
+
23
+ it "should set the minor revision value, leaving the major value unchanged and the patch value to 0" do
24
+ stub_const("Napa::VERSION", "0.5.0")
25
+ Napa::Version.next_minor.should == "0.6.0"
26
+ end
27
+ end
28
+
29
+ context "patch_bump" do
30
+ it "should set the patch revision value, leaving the major and minor values unchanged" do
31
+ stub_const("Napa::VERSION", "1.2.3")
32
+ Napa::Version.next_patch.should == "1.2.4"
33
+ end
34
+
35
+ it "should set the patch revision value, leaving the major and minor values unchanged" do
36
+ stub_const("Napa::VERSION", "5.5.5")
37
+ Napa::Version.next_patch.should == "5.5.6"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,51 @@
1
+ desc "bump the gem version"
2
+ namespace :version do
3
+ namespace :bump do
4
+
5
+ task :major do
6
+ @new_version = Napa::Version.next_major
7
+ execute_version_bump
8
+ end
9
+
10
+ task :minor do
11
+ @new_version = Napa::Version.next_minor
12
+ execute_version_bump
13
+ end
14
+
15
+ task :patch do
16
+ @new_version = Napa::Version.next_patch
17
+ execute_version_bump
18
+ end
19
+
20
+ def execute_version_bump
21
+ if !clean_staging_area?
22
+ system "git status"
23
+ raise "Unclean staging area! Be sure to commit or .gitignore everything first. See `git status` above."
24
+ else
25
+ require 'git'
26
+ git = Git.open('.')
27
+
28
+ write_update
29
+ git.add('lib/napa/version.rb')
30
+ git.commit("Version bump: #{release_tag}")
31
+ git.add_tag(release_tag)
32
+ git.push(git.remote('origin'), git.branch, release_tag) if git.remote('origin')
33
+ puts "Version bumped: #{release_tag}"
34
+ end
35
+ end
36
+
37
+ def write_update
38
+ filedata = File.read('lib/napa/version.rb')
39
+ changed_filedata = filedata.gsub("VERSION = \"#{Napa::VERSION}\"\n", "VERSION = \"#{@new_version}\"\n")
40
+ File.open('lib/napa/version.rb',"w"){|file| file.puts changed_filedata}
41
+ end
42
+
43
+ def clean_staging_area?
44
+ `git ls-files --deleted --modified --others --exclude-standard` == ""
45
+ end
46
+
47
+ def release_tag
48
+ "v#{@new_version}"
49
+ end
50
+ end
51
+ end