From d08407fcf3c55a0b24c9217b4c2f156fa7bf3c57 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Donoso Date: Thu, 3 Mar 2016 11:41:47 -0500 Subject: [PATCH] feat(): adds continuous integration support when deploying to heroku --- circle.yml | 2 + .../application/assets/Dockerfile.ci | 4 ++ .../application/assets/bin/cibuild.erb | 27 ++++++++++++++ .../templates/application/assets/circle.yml | 16 ++++++++ .../application/assets/docker-compose.ci.yml | 6 +++ .../application/helpers/docker-helpers.rb | 37 +++++++++++++++++++ .../application/helpers/template-dsl.rb | 1 + .../templates/application/recipes/ci.rb | 36 ++++++++++++++++++ .../templates/application/template.rb | 1 + spec/features/ci_spec.rb | 14 +++++++ 10 files changed, 144 insertions(+) create mode 100644 lib/potassium/templates/application/assets/Dockerfile.ci create mode 100644 lib/potassium/templates/application/assets/bin/cibuild.erb create mode 100644 lib/potassium/templates/application/assets/circle.yml create mode 100644 lib/potassium/templates/application/assets/docker-compose.ci.yml create mode 100644 lib/potassium/templates/application/helpers/docker-helpers.rb create mode 100644 lib/potassium/templates/application/recipes/ci.rb create mode 100644 spec/features/ci_spec.rb 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/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/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..e09bd5c9 100644 --- a/lib/potassium/templates/application/template.rb +++ b/lib/potassium/templates/application/template.rb @@ -44,6 +44,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