diff --git a/CHANGELOG.md b/CHANGELOG.md index 29efdf69..330d6241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,18 @@ Features: - Use Puma instead of unicorn - Gets ruby version from http://ruby.platan.us/latest - Adds the install command - - Adds heroku buildpack support using the [multi buildpack](http://github.com/ddollar/heroku-buildpack-multi) + - Adds heroku buildpack support using the [multi buildpack](multi-buildpack) - Adds `rack-timeout` to prevent long running requests - - Adds [deploy-tasks buildpack](http://github.com/gunpowderlabs/buildpack-ruby-rake-deploy-tasks) mainly to perform migrations, [#39](http://github.com/platanus/potassium/pull/39) - - Add Delayed Jobs to handle background processes [#41] + - Adds [deploy-tasks buildpack](deploy-tasks) mainly to perform migrations, [#39] + - Add Delayed Jobs to handle background processes, [#41] + - Adds continuous integration using [CircleCI](https://circleci.com) + and docker, [#51] + +[multi-buildpack]: http://github.com/ddollar/heroku-buildpack-multi +[deploy-tasts]: http://github.com/gunpowderlabs/buildpack-ruby-rake-deploy-tasks +[#39]: http://github.com/platanus/potassium/pull/39 +[#41]: http://github.com/platanus/potassium/pull/41 +[#51]: http://github.com/platanus/potassium/pull/51 ## 1.3 diff --git a/README.md b/README.md index ab6bd0e1..10afd007 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ When you choose to deploy to heroku a few extra things are added for the project to configure the app to log to standard out, which is how [Heroku's logging][heroku-logging] works. - Adds a [Procfile][procfile] to define the processes to run in heroku +- Setup continuous integration in [CircleCI](circle-ci) to run tests. It use + docker to maintain better parity between testing and production environments - Adds a `.buildpacks` file with the default buildpacks to use. It use the following buildpacks: @@ -97,6 +99,7 @@ When you choose to deploy to heroku a few extra things are added for the project [heroku-buildpack-ruby]: http://github.com/heroku/heroku-buildpack-ruby [heroku-buildpack-multi]: http://github.com/ddollar/heroku-buildpack-multi [buildpack-deploy-tasks]: http://github.com/gunpowderlabs/buildpack-ruby-rake-deploy-tasks +[circle-ci]: https://circleci.com ## Contributing diff --git a/circle.yml b/circle.yml index d0be8866..a1e96cd9 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,8 @@ machine: ruby: version: 2.3.0 + services: + - docker dependencies: override: diff --git a/lib/potassium/templates/application/assets/Dockerfile.ci b/lib/potassium/templates/application/assets/Dockerfile.ci new file mode 100644 index 00000000..a3fc8c97 --- /dev/null +++ b/lib/potassium/templates/application/assets/Dockerfile.ci @@ -0,0 +1,4 @@ +FROM platanus/buildstep +EXPOSE 3000 + +RUN /exec bundle install --with test diff --git a/lib/potassium/templates/application/assets/README.md.erb b/lib/potassium/templates/application/assets/README.md.erb index 22d25934..425c46ee 100644 --- a/lib/potassium/templates/application/assets/README.md.erb +++ b/lib/potassium/templates/application/assets/README.md.erb @@ -1,4 +1,4 @@ -# <%= get(:titleized_app_name) %> +# <%= get(:titleized_app_name) %> <% if selected?(:heroku) %>[![Circle CI](https://circleci.com/gh/platanus/<%= get(:dasherized_app_name)%>.svg?style=svg)](https://circleci.com/gh/platanus/<%= get(:dasherized_app_name)%>)<% end-%> This is a Rails application, initially generated using [Potassium](https://github.com/platanus/potassium) by Platanus. ## Local installation @@ -13,8 +13,8 @@ Assuming you've just cloned the repo: ## External services TODO: add external services here -## Deployment <% if selected?(:heroku) %> +## Deployment This project is pre-configured to be (easily) deployed to Heroku servers, but needs you to have the Potassium binary installed. If you don't, then run: ```shell $ gem install potassium @@ -43,6 +43,16 @@ Remember to connect each stage to the corresponding branch: 2. Production -> Production That's it. You should already have a running app and each time you push to the corresponding branch, the system will (hopefully) update accordingly. + +## Continuous Integrations + +The project is setup to run tests +in [CircleCI](https://circleci.com/gh/platanus/<%= get(:dasherized_app_name)%>/tree/master) + +You can also run the test locally simulating the production environment using docker. +Just make sure you have docker installed and run: + + bin/cibuild <% end-%> ## Internal dependencies diff --git a/lib/potassium/templates/application/assets/bin/cibuild.erb b/lib/potassium/templates/application/assets/bin/cibuild.erb new file mode 100644 index 00000000..8fcc3f87 --- /dev/null +++ b/lib/potassium/templates/application/assets/bin/cibuild.erb @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +DOCKE_COMPOSE_ARGS="-f docker-compose.ci.yml run" + +<% if(selected?(:database, :mysql) || selected?(:database, :postgresql))-%> +function test_<%=get(:database).to_s%> { + docker-compose $DOCKE_COMPOSE_ARGS test sh -c 'nc -z $<%=get(:database).to_s.upcase%>_HOST $<%=get(:database).to_s.upcase%>_PORT' +} + +count=0 +# Chain tests together by using && +until ( test_<%=get(:database).to_s%> && echo "Services ready" ) +do + ((count++)) + if [ ${count} -gt 50 ] + then + echo "Services didn't become ready in time" + exit 1 + else + echo "Waiting for services to become ready..." + fi + sleep 0.2 +done +<% end-%> + +docker-compose $DOCKE_COMPOSE_ARGS test /exec bundle exec rake db:setup +docker-compose $DOCKE_COMPOSE_ARGS test /exec bundle exec rspec spec diff --git a/lib/potassium/templates/application/assets/circle.yml b/lib/potassium/templates/application/assets/circle.yml new file mode 100644 index 00000000..ec6603ee --- /dev/null +++ b/lib/potassium/templates/application/assets/circle.yml @@ -0,0 +1,16 @@ +machine: + services: + - docker + +dependencies: + override: + - docker-compose -f docker-compose.ci.yml pull + - docker-compose -f docker-compose.ci.yml build test + +database: + override: + - echo "Skipping database" + +test: + override: + - bin/cibuild diff --git a/lib/potassium/templates/application/assets/config/database_mysql.yml.erb b/lib/potassium/templates/application/assets/config/database_mysql.yml.erb index f8efe980..1816ea45 100644 --- a/lib/potassium/templates/application/assets/config/database_mysql.yml.erb +++ b/lib/potassium/templates/application/assets/config/database_mysql.yml.erb @@ -3,8 +3,8 @@ development: &default database: <%= get(:underscorized_app_name) %>_development encoding: utf8 username: root - host: <%%= ENV["BOXEN_MYSQL_HOST"] || "127.0.0.1" %> - port: <%%= ENV["BOXEN_MYSQL_PORT"] || 3306 %> + host: <%%= ENV["BOXEN_MYSQL_HOST"] || ENV["MYSQL_HOST"] || "127.0.0.1" %> + port: <%%= ENV["BOXEN_MYSQL_PORT"] || ENV["MYSQL_PORT"] || 3306 %> min_messages: warning pool: <%%= Integer(ENV.fetch("DB_POOL", 5)) %> reaping_frequency: <%%= Integer(ENV.fetch("DB_REAPING_FREQUENCY", 10)) %> diff --git a/lib/potassium/templates/application/assets/config/database_postgresql.yml.erb b/lib/potassium/templates/application/assets/config/database_postgresql.yml.erb index 87342a09..a94f0ee5 100644 --- a/lib/potassium/templates/application/assets/config/database_postgresql.yml.erb +++ b/lib/potassium/templates/application/assets/config/database_postgresql.yml.erb @@ -2,8 +2,9 @@ development: &default adapter: postgresql database: <%= get(:underscorized_app_name) %>_development encoding: utf8 - host: <%%= ENV["BOXEN_POSTGRESQL_HOST"] || "127.0.0.1" %> - port: <%%= ENV["BOXEN_POSTGRESQL_PORT"] || 5432 %> + host: <%%= ENV["BOXEN_POSTGRESQL_HOST"] || ENV["POSTGRESQL_HOST"] || "127.0.0.1" %> + port: <%%= ENV["BOXEN_POSTGRESQL_PORT"] || ENV["POSTGRESQL_PORT"] || 5432 %> + username: <%%= ENV["POSTGRESQL_USER"] %> min_messages: warning pool: <%%= Integer(ENV.fetch("DB_POOL", 5)) %> reaping_frequency: <%%= Integer(ENV.fetch("DB_REAPING_FREQUENCY", 10)) %> diff --git a/lib/potassium/templates/application/assets/docker-compose.ci.yml b/lib/potassium/templates/application/assets/docker-compose.ci.yml new file mode 100644 index 00000000..3da1f9c2 --- /dev/null +++ b/lib/potassium/templates/application/assets/docker-compose.ci.yml @@ -0,0 +1,6 @@ +test: + build: . + dockerfile: Dockerfile.ci + working_dir: '/app' + environment: + RAILS_ENV: test diff --git a/lib/potassium/templates/application/helpers/docker-helpers.rb b/lib/potassium/templates/application/helpers/docker-helpers.rb new file mode 100644 index 00000000..064ddfa8 --- /dev/null +++ b/lib/potassium/templates/application/helpers/docker-helpers.rb @@ -0,0 +1,37 @@ +class DockerHelpers + def initialize(compose_path) + @compose_path = compose_path + @compose = YAML.load(File.read(compose_path)) + end + + def add_link(target_service, linked_service) + service = @compose[target_service] + unless service['links'].is_a? Array + service['links'] = [] + end + service['links'].push(linked_service) + save + end + + def add_env(target_service, variable_key, variable_value) + service = @compose[target_service] + unless service['environment'].is_a? Hash + service['environment'] = {} + end + service['environment'][variable_key] = variable_value + save + end + + def add_service(name, definition) + service = {} + service[name] = YAML.load(definition) + @compose.merge!(service) + save + end + + private + + def save + File.open(@compose_path, 'w') { |f| f.write @compose.to_yaml } + end +end diff --git a/lib/potassium/templates/application/helpers/template-dsl.rb b/lib/potassium/templates/application/helpers/template-dsl.rb index a261a4b4..20b60ce0 100644 --- a/lib/potassium/templates/application/helpers/template-dsl.rb +++ b/lib/potassium/templates/application/helpers/template-dsl.rb @@ -4,6 +4,7 @@ def self.extend_dsl(object, source_path: __FILE__) require_relative './variable-helpers' require_relative './environment-helpers' require_relative './gem-helpers' + require_relative './docker-helpers' require_relative './callback-helpers' require_relative './answer-helpers' diff --git a/lib/potassium/templates/application/recipes/ci.rb b/lib/potassium/templates/application/recipes/ci.rb new file mode 100644 index 00000000..b7a1d1b0 --- /dev/null +++ b/lib/potassium/templates/application/recipes/ci.rb @@ -0,0 +1,36 @@ +if get(:heroku) + copy_file 'assets/Dockerfile.ci', 'Dockerfile.ci' + copy_file 'assets/circle.yml', 'circle.yml' + + template 'assets/bin/cibuild.erb', 'bin/cibuild' + run "chmod a+x bin/cibuild" + + copy_file 'assets/docker-compose.ci.yml', 'docker-compose.ci.yml' + + compose = DockerHelpers.new('docker-compose.ci.yml') + + if selected?(:database, :mysql) + service = <<-YAML + image: "mysql:5.6.23" + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: 'true' + YAML + compose.add_service("mysql", service) + compose.add_link('test', 'mysql') + compose.add_env('test', 'MYSQL_HOST', 'mysql') + compose.add_env('test', 'MYSQL_PORT', '3306') + + elsif selected?(:database, :postgresql) + service = <<-YAML + image: "postgres:9.4.5" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: '' + YAML + compose.add_service("postgresql", service) + compose.add_link('test', 'postgresql') + compose.add_env('test', 'POSTGRESQL_USER', 'postgres') + compose.add_env('test', 'POSTGRESQL_HOST', 'postgresql') + compose.add_env('test', 'POSTGRESQL_PORT', '5432') + end +end diff --git a/lib/potassium/templates/application/template.rb b/lib/potassium/templates/application/template.rb index 9e3e1afb..a2827be7 100644 --- a/lib/potassium/templates/application/template.rb +++ b/lib/potassium/templates/application/template.rb @@ -1,6 +1,7 @@ set :app_name, @app_name set :titleized_app_name, get(:app_name).titleize set :underscorized_app_name, get(:app_name).underscore +set :dasherized_app_name, get(:app_name).dasherize run_action(:cleaning) do clean_gemfile @@ -44,6 +45,7 @@ eval_file "recipes/api.rb" eval_file "recipes/rack-cors.rb" eval_file "recipes/paperclip.rb" + eval_file "recipes/ci.rb" eval_file "recipes/cleanup.rb" end diff --git a/spec/features/ci_spec.rb b/spec/features/ci_spec.rb new file mode 100644 index 00000000..c0448cc6 --- /dev/null +++ b/spec/features/ci_spec.rb @@ -0,0 +1,14 @@ +require "spec_helper" +require "rubocop" + +RSpec.describe "A new project" do + before(:all) do + drop_dummy_database + remove_project_directory + create_dummy_project("heroku" => true) + end + + it "correctly runs continous integration" do + expect { on_project { `bin/cibuild` } }.to_not output.to_stderr + end +end diff --git a/spec/support/potassium_test_helpers.rb b/spec/support/potassium_test_helpers.rb index 3fe9b3e1..92bf9a2a 100644 --- a/spec/support/potassium_test_helpers.rb +++ b/spec/support/potassium_test_helpers.rb @@ -9,10 +9,11 @@ def create_tmp_directory FileUtils.mkdir_p(tmp_path) end - def create_dummy_project + def create_dummy_project(arguments = {}) Dir.chdir(tmp_path) do Bundler.with_clean_env do - run_command("#{potassium_bin} create #{APP_NAME} #{bin_arguments}") + full_arguments = hash_to_arguments(default_arguments.merge(arguments)) + run_command("#{potassium_bin} create #{APP_NAME} #{full_arguments}") end end end @@ -44,18 +45,30 @@ def potassium_bin File.join(root_path, "bin", "potassium") end - def bin_arguments - [ - "--db=mysql", - "--lang=es", - "--no-devise", - "--no-admin", - "--no-pundit", - "--no-paperclip", - "--no-api", - "--no-heroku", - "--no-delayed-job" - ].join(" ") + def default_arguments + { + "db" => "mysql", + "lang" => "es", + "heroku" => false, + "admin" => false, + "pundit" => false, + "paperclip" => false, + "devise" => false, + "api" => false, + "delayed-job" => false + } + end + + def hash_to_arguments(hash) + hash.map do |key, value| + if value == true + "--#{key}" + elsif value == false + "--no-#{key}" + elsif value + "--#{key}=#{value}" + end + end.join(" ") end def root_path