groundskeeper-bitcore 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 22443d1b2eb3e74cb732993ae2162a17d28899f8
4
- data.tar.gz: f64b1e9362ba9a7025afc5ed6b522f247bd2470b
3
+ metadata.gz: 2a59a195b265d2acb9f80f92047a932b9d383774
4
+ data.tar.gz: cc30dafde4d041ded80022417a6aab76f91505c2
5
5
  SHA512:
6
- metadata.gz: 8833b8710aad1de7efe9b704a042ea49ae90d541933c302b72e5e1ecab89ed6a8bb2efc6171ab53a2e81502d4bcc87de988076bcb9f0bb4ff4c222ebd0a479b3
7
- data.tar.gz: 194d6b7558ad5158d94adeaf816c405ba4142ce2fbe8c8bf8ddc27dc65b5a6a21419cda8bc44ce7edd111bf1bf927459e3b33997107bd21b35148cb36c93b831
6
+ metadata.gz: 4a6e194b5cb47ff597fe0088a77b2d9767f949bc748c51813a65a01e4e6460c54b6c62526e02eac519ff2f8d9693a390fe5620d2e80272366169cfcbd4ff35d3
7
+ data.tar.gz: 0be07658c32a457a974e6b7809b5e8c021e00acf632d3b33f032513d0c3d1a69e1e3a04c0ec9865a73a6b5ca4adf1202dcb08e8b0b34948b78086f1604e81cf0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 - 2017-11-29
4
+
5
+ * IN-8 Fixes #47 transition Jira issues upon deployment (#48)
6
+ * update script and instructions for predeployment (#46)
7
+ * Fixes #27 warn user if Jira env variables aren't set (#45)
8
+ * ensure RAILS_ENV gets set remotely during deployment (#44)
9
+ * enable production deployment (#43)
10
+ * IN-7 Merge rails_deployment functionality into groundskeeper (#40)
11
+
3
12
  ## 0.1.1 - 2017-09-14
4
13
 
5
14
  * sign gem and update installation instructions (#37)
data/README.md CHANGED
@@ -5,30 +5,17 @@ deploying Rails applications.
5
5
 
6
6
  ## Installation
7
7
 
8
- Groundskeeper is cryptographically signed. To be sure this gem hasn't been
9
- tampered with, add the following public key if you have not already:
10
-
11
- ```bash
12
- gem cert --add <(curl -Ls https://raw.github.com/NU-CBITS/groundskeeper/master/certs/ericcf.pem)
13
- ```
14
-
15
8
  Install this gem globally via:
16
9
 
17
10
  ```bash
18
- gem install groundskeeper -P HighSecurity
11
+ gem install groundskeeper-bitcore -P HighSecurity
19
12
  ```
20
13
 
21
14
  The HighSecurity trust profile will verify signed gems and require that all
22
15
  dependencies are signed.
23
16
 
24
17
  Note: if you have multiple versions of Ruby installed (via RVM e.g.), you will
25
- need to install this gem for each of them. In this example, your Rails
26
- application directory and the groundskeeper source are in the same parent
27
- directory:
28
-
29
- ```bash
30
- gem install ../groundskeeper/groundskeeper-<version>.gem
31
- ```
18
+ need to install this gem for each of them.
32
19
 
33
20
  Additionally, to integrate with Jira you will need to set some environment
34
21
  variables. One standard place to do this is in the `~/.bash_profile`:
@@ -62,6 +49,19 @@ application directory:
62
49
  ground release
63
50
  ```
64
51
 
52
+ To prepare the server (only required the first time) for deployment:
53
+
54
+ ```bash
55
+ tag=0.0.0 stage=production ground predeploy
56
+ ```
57
+
58
+ To deploy the release (note that `stage` is optional and will be set to staging
59
+ by default):
60
+
61
+ ```bash
62
+ tag=0.0.0 stage=production ground deploy
63
+ ```
64
+
65
65
  ## Development
66
66
 
67
67
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
@@ -0,0 +1,10 @@
1
+ <% project = fetch :project %>
2
+ <% db_env_variables = project.db_env_variables(fetch :stage) %>
3
+ <%= (fetch :stage).to_s %>:
4
+ adapter: postgresql
5
+ encoding: unicode
6
+ pool: 5
7
+ host: <%= db_env_variables["host"] %>
8
+ database: <%= db_env_variables["database"] %>
9
+ username: <%%= ENV['<%= db_env_variables["username"] %>'] %>
10
+ password: <%%= ENV['<%= db_env_variables["password"] %>'] %>
data/config/deploy.rb ADDED
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mina/rails"
4
+ require "mina/git"
5
+ require "mina/rvm" # for rvm support. (https://rvm.io)
6
+
7
+ require_relative "./git_config.rb"
8
+ require_relative "./predeploy.rb"
9
+ require_relative "./tasks.rb"
10
+
11
+ def load_project
12
+ invoke :before_run unless fetch(:project)
13
+ end
14
+
15
+ # Basic settings:
16
+ # domain - The hostname to SSH to.
17
+ # deploy_to - Path to deploy into.
18
+ # repository - Git repo to clone from. (needed by mina/git)
19
+ # branch - Branch name to deploy. (needed by mina/git)
20
+
21
+ # set :application_name, 'foobar'
22
+ # set :domain, 'foobar.com'
23
+ # set :deploy_to, '/var/www/foobar.com'
24
+ # set :repository, 'git://...'
25
+ # set :branch, 'master'
26
+
27
+ # Optional settings:
28
+ # set :user, 'foobar' # Username in the server to SSH to.
29
+ # set :port, '30000' # SSH port number.
30
+ # set :forward_agent, true # SSH forward_agent.
31
+
32
+ # Shared dirs and files will be symlinked into the app-folder by the
33
+ # 'deploy:link_shared_paths' step. Some plugins already add folders to
34
+ # shared_dirs like `mina/rails` add `public/assets`, `vendor/bundle` and many
35
+ # more run `mina -d` to see all folders and files already included in
36
+ # `shared_dirs` and `shared_files`
37
+ set :shared_dirs, fetch(:shared_dirs, []).push(
38
+ "log",
39
+ "tmp/pids",
40
+ "tmp/cache",
41
+ "tmp/sockets",
42
+ "bundle",
43
+ "public/system"
44
+ )
45
+
46
+ set :shared_files, fetch(:shared_files, []).push(
47
+ "config/database.yml",
48
+ "config/secrets.yml"
49
+ )
50
+
51
+ # This task is the environment that is loaded for all remote run commands, such
52
+ # as `mina deploy` or `mina rake`.
53
+ task :environment do
54
+ # Be sure to commit your .ruby-version to your repository.
55
+ # For those using RVM, use this to load an RVM version@gemset.
56
+ # invoke :'rvm:use', 'ruby-1.9.3-p125@default'
57
+ end
58
+
59
+ # Put any custom commands you need to run at setup
60
+ # All paths in `shared_dirs` and `shared_paths` will be created on their own.
61
+ task :setup do
62
+ end
63
+
64
+ task :staging do
65
+ set :stage, :staging
66
+ set :rails_env, "staging"
67
+ end
68
+
69
+ task :production do
70
+ set :stage, :production
71
+ set :rails_env, "production"
72
+ end
73
+
74
+ if ENV["stage"] == "production"
75
+ invoke :production
76
+ else
77
+ invoke :staging
78
+ end
79
+
80
+ desc "Run predeployment tasks."
81
+ task predeploy: :remote_environment do
82
+ load_project
83
+ run :remote do
84
+ invoke :setup
85
+ end
86
+ invoke :'deploy_configure:create_configs'
87
+ invoke :'deploy_prepare:create_vhost'
88
+ invoke :'deploy_prepare:configure_pg'
89
+ invoke :'deploy_prepare:set_owner'
90
+ end
91
+
92
+ desc "Deploys the current version to the server."
93
+ task deploy: :remote_environment do
94
+ load_project
95
+ deploy do
96
+ # Put things that will set up an empty directory into a fully set-up
97
+ # instance of your project.
98
+ invoke :rvm_use
99
+ invoke :checkout_release
100
+ invoke :link_shared_dirs
101
+ invoke :'bundle:install'
102
+ invoke :'rails:db_migrate'
103
+ invoke :'rails:assets_precompile'
104
+ invoke :'deploy:cleanup'
105
+
106
+ on :launch do
107
+ invoke :restart
108
+ end
109
+ end
110
+
111
+ invoke :open_browser
112
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "json"
5
+ require "groundskeeper"
6
+
7
+ desc "Sets up project details."
8
+ # rubocop:disable Metrics/BlockLength
9
+ task :before_run do
10
+ application = %x(basename `git rev-parse --show-toplevel`).strip!
11
+ unless Groundskeeper::Project.available_applications.include? application
12
+ raise "must be inside application repo (currently in #{application}). " \
13
+ "available applications: " +
14
+ Groundskeeper::Project.available_applications
15
+ end
16
+
17
+ project = Groundskeeper::Project.build(application)
18
+ set :project, project
19
+
20
+ set :application_name, application
21
+ set :domain, project.full_dns(fetch(:stage))
22
+ set :user, "deploy"
23
+ set :repository, project.repo_url
24
+ set :deploy_to, project.deploy_to
25
+ set :forward_agent, true
26
+ set :bundle_path, "bundle"
27
+
28
+ set :rvm_ruby_version, project.rvm_ruby_version
29
+ set :rvm_type, :system
30
+
31
+ # On each deploy, prompt for the tag
32
+ puts "Looking up tags for project #{application}... (#{project.repo_url})"
33
+ tags = project.tags
34
+ raise "There must be a tagged release prior to deploying." if tags.count.zero?
35
+ tag = ENV["tag"]
36
+ unless tags.include? tag
37
+ raise "Tag \"#{tag}\" not found. Available tags:\n #{tags.join("\n")}"
38
+ end
39
+ set :branch, "master"
40
+ set :tag, "tags/#{tag}"
41
+ end
42
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ PG_CONFIG = "/usr/pgsql-9.3/bin/pg_config"
4
+
5
+ def load_template(name)
6
+ template_str = File.binread(File.join(File.dirname(__FILE__), name))
7
+ template = ERB.new(template_str, nil, "-")
8
+ template.result
9
+ end
10
+
11
+ def environments_dir
12
+ "#{fetch :deploy_to}/shared/config/environments"
13
+ end
14
+
15
+ def secrets_config_file
16
+ "#{fetch :deploy_to}/shared/config/secrets.yml"
17
+ end
18
+
19
+ def database_config_file
20
+ "#{fetch :deploy_to}/shared/config/database.yml"
21
+ end
22
+
23
+ def rails_config_file
24
+ "#{environments_dir}/#{fetch :stage}.rb"
25
+ end
26
+
27
+ def vhost_config_file
28
+ "/etc/httpd/conf.d/#{fetch :application_name}.conf"
29
+ end
30
+
31
+ def remote_shared_path(path)
32
+ "#{fetch(:user)}@#{fetch(:domain)}:#{fetch(:shared_path)}/#{path}"
33
+ end
34
+
35
+ # rubocop:disable Metrics/BlockLength
36
+ namespace :deploy_configure do
37
+ desc "Create database and secrets"
38
+ task create_configs: :remote_environment do
39
+ load_project
40
+
41
+ secrets_config = load_template("secrets_config.yml.erb")
42
+ db_config = load_template("db_config.yml.erb")
43
+ # TODO: Move environment variables to env.yml
44
+ rails_config = load_template("rails_config.rb.erb")
45
+
46
+ run :remote do
47
+ command "mkdir -p #{environments_dir}"
48
+ end
49
+
50
+ if File.exist? "./config/secrets.yml"
51
+ invoke :'deploy_configure:copy_secrets'
52
+ end
53
+
54
+ comment "Writing staging secrets to #{secrets_config_file}"
55
+ comment "touch #{secrets_config_file}"
56
+ run :remote do
57
+ command "echo \"#{secrets_config}\" >> #{secrets_config_file}"
58
+ end
59
+
60
+ comment "Writing database config to #{database_config_file}"
61
+ run :remote do
62
+ command "echo \"#{db_config}\" > #{database_config_file}"
63
+ end
64
+
65
+ comment "Writing Rails config to #{rails_config_file}"
66
+ run :remote do
67
+ command "echo \"#{rails_config}\" > #{rails_config_file}"
68
+ end
69
+ end
70
+
71
+ desc "Copy secrets,"
72
+ task :copy_secrets do
73
+ path = remote_shared_path("config/secrets.yml")
74
+ comment "Copying secrets.yml to #{path}."
75
+ run :local do
76
+ command = "scp ./config/secrets.yml #{path}"
77
+ puts `#{command}`
78
+ end
79
+ end
80
+ end
81
+ # rubocop:enable Metrics/BlockLength
82
+
83
+ namespace :deploy_prepare do
84
+ desc "Write the virtual host config file. May require an Apache restart."
85
+ task :create_vhost do
86
+ load_project
87
+ vhost_config = load_template("vhost_config.conf.erb")
88
+
89
+ comment "Writing virtual host config to #{vhost_config_file}"
90
+ command "echo \"#{vhost_config}\" > #{vhost_config_file}"
91
+ end
92
+
93
+ desc "Configure Postgres."
94
+ task :configure_pg do
95
+ comment "Configuring Postgres with config #{PG_CONFIG}"
96
+ command "bundle config build.pg -- --with-pg-config=#{PG_CONFIG}"
97
+ end
98
+
99
+ desc "Set owner."
100
+ task :set_owner do
101
+ comment "Setting owner of #{fetch :deploy_to} apache"
102
+ command "sudo chgrp -R apache #{fetch :deploy_to}"
103
+ end
104
+ end
@@ -0,0 +1,88 @@
1
+ <% project = fetch :project %>
2
+ Rails.application.configure do
3
+ # For catching exceptions
4
+ config.sentry_dsn = '<%= project.sentry_dsn %>'
5
+ config.sentry_public_dsn = '<%= project.sentry_public_dsn %>'
6
+
7
+ # Settings specified here will take precedence over those in config/application.rb.
8
+
9
+ # Code is not reloaded between requests.
10
+ config.cache_classes = true
11
+
12
+ # Eager load code on boot. This eager loads most of Rails and
13
+ # your application in memory, allowing both thread web servers
14
+ # and those relying on copy on write to perform better.
15
+ # Rake tasks automatically ignore this option for performance.
16
+ config.eager_load = true
17
+
18
+ # Full error reports are disabled and caching is turned on.
19
+ config.consider_all_requests_local = false
20
+ config.action_controller.perform_caching = true
21
+
22
+ # Disable serving static files from the `/public` folder by default since
23
+ # Apache or NGINX already handles this.
24
+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
25
+
26
+ # Compress JavaScripts and CSS.
27
+ config.assets.js_compressor = :uglifier
28
+ # config.assets.css_compressor = :sass
29
+
30
+ # Do not fallback to assets pipeline if a precompiled asset is missed.
31
+ config.assets.compile = false
32
+
33
+ # Specifies the header that your server uses for sending files.
34
+ config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for apache
35
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
36
+
37
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
38
+ config.force_ssl = true
39
+
40
+ # Set to :debug to see everything in the log.
41
+ config.log_level = :info
42
+
43
+ # Prepend all log lines with the following tags.
44
+ config.log_tags = [ :request_id ]
45
+
46
+ # Use a different logger for distributed setups.
47
+ # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
48
+
49
+ # Use a different cache store in production.
50
+ # config.cache_store = :mem_cache_store
51
+
52
+ config.action_mailer.perform_caching = false
53
+
54
+ # Precompile additional assets.
55
+ # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
56
+ # config.assets.precompile += %w( search.js )
57
+
58
+ # Ignore bad email addresses and do not raise email delivery errors.
59
+ # Set this to true and configure the email server for immediate delivery to raise delivery errors.
60
+ config.action_mailer.default_url_options = { host: '<%= fetch :domain %>' }
61
+ config.action_mailer.delivery_method = :smtp
62
+ config.action_mailer.raise_delivery_errors = false
63
+ config.action_mailer.perform_deliveries = true
64
+ config.action_mailer.smtp_settings = {
65
+ authentication: :plain,
66
+ address: 'smtp.mailgun.org',
67
+ port: 587,
68
+ domain: 'cbits.northwestern.edu',
69
+ user_name: ENV['mailgun_db_username'],
70
+ password: ENV['mailgun_db_password']
71
+ }
72
+
73
+ Rails.application.routes.default_url_options[:host] = '<%= fetch :domain %>'
74
+ Rails.application.routes.default_url_options[:protocol] = 'https'
75
+
76
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
77
+ # the I18n.default_locale when a translation can not be found).
78
+ config.i18n.fallbacks = true
79
+
80
+ # Send deprecation notices to registered listeners.
81
+ config.active_support.deprecation = :notify
82
+
83
+ # Disable automatic flushing of the log to improve performance.
84
+ # config.autoflush_log = false
85
+
86
+ # Use default logging formatter so that PID and timestamp are not suppressed.
87
+ config.log_formatter = ::Logger::Formatter.new
88
+ end
@@ -0,0 +1,3 @@
1
+ <% require "securerandom" %>
2
+ <%= fetch :stage %>:
3
+ secret_key_base: <%= SecureRandom.hex(64) %>
data/config/tasks.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Restart application"
4
+ task :restart do
5
+ in_path(fetch(:current_path)) do
6
+ command %(mkdir -p tmp/)
7
+ command %(touch tmp/restart.txt)
8
+ end
9
+ end
10
+
11
+ desc "Checks out the selected release tag"
12
+ task :checkout_release do
13
+ comment "Checking out release \"#{fetch :tag}\""
14
+ command %(git clone -b "#{fetch :branch}" "#{fetch :repository}" .)
15
+ command "git fetch"
16
+ command %(git checkout "#{fetch :tag}")
17
+ end
18
+
19
+ desc "Uses an RVM envionment"
20
+ task :rvm_use do
21
+ command "rvm use \"ruby-#{fetch :rvm_ruby_version}\""
22
+ end
23
+
24
+ desc "Link shared directories."
25
+ task :link_shared_dirs do
26
+ # Must be set after :stage is set
27
+ set :shared_files, fetch(:shared_files, []).push(
28
+ "config/environments/#{fetch :stage}.rb"
29
+ )
30
+
31
+ comment "Shared directories: #{(fetch :shared_dirs).join("\n")}"
32
+ invoke :'deploy:link_shared_paths'
33
+ end
34
+
35
+ desc "Open the newly deployed server in the browser (for macOS)."
36
+ task :open_browser do
37
+ run(:local) do
38
+ command "open https://#{fetch :domain}"
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ <% project = fetch :project -%>
2
+ <VirtualHost *:80>
3
+ ServerName <%= fetch :domain %>
4
+ Redirect permanent / https://<%= fetch :domain -%>/
5
+ </VirtualHost>
6
+
7
+ <VirtualHost *:443>
8
+ PassengerFriendlyErrorPages off
9
+ PassengerAppEnv <%= fetch :stage %>
10
+ PassengerRuby /usr/local/rvm/wrappers/ruby-<%= fetch :rvm_ruby_version -%>/ruby
11
+ # Always have at least 1 process in existence for the application
12
+ PassengerMinInstances 1
13
+
14
+ ServerName <%= fetch :domain %>
15
+
16
+ SSLEngine On
17
+ SSLCertificateFile <%= project.ssl_certificate_file(fetch(:stage).to_sym) %>
18
+ SSLCertificateChainFile <%= project.ssl_certificate_chain_file(fetch(:stage).to_sym) %>
19
+ SSLCertificateKeyFile <%= project.ssl_certificate_key_file(fetch(:stage).to_sym) %>
20
+
21
+ DocumentRoot <%= fetch :deploy_to -%>/current/public
22
+ RailsBaseURI /
23
+ PassengerDebugLogFile /var/log/httpd/passenger.log
24
+
25
+ <Directory <%= fetch :deploy_to -%>/current/public >
26
+ Allow from all
27
+ Options -MultiViews
28
+ </Directory>
29
+
30
+ AddOutputFilterByType DEFLATE text/html text/css application/javascript
31
+
32
+ RewriteEngine On
33
+
34
+ # Show maintenance page if it exists
35
+ ErrorDocument 503 /system/maintenance.html
36
+ RewriteCond %{REQUEST_URI} !\.(css|gif|jpg|png)$
37
+ RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
38
+ RewriteCond %{SCRIPT_FILENAME} !maintenance.html
39
+ RewriteRule ^.*$ - [redirect=503,last]
40
+
41
+ <Location /assets/>
42
+ # RFC says only cache for 1 year
43
+ ExpiresActive On
44
+ ExpiresDefault \"access plus 1 year\"
45
+ </Location>
46
+ </VirtualHost>
47
+
48
+ # Start the application before the first access
49
+ PassengerPreStart https://<%= fetch :domain -%>:443/
@@ -15,10 +15,6 @@ Gem::Specification.new do |spec|
15
15
  spec.homepage = "https://github.com/NU-CBITS/groundskeeper.git"
16
16
  spec.license = "MIT"
17
17
  spec.cert_chain = ["certs/ericcf.pem"]
18
- if $PROGRAM_NAME.end_with?("gem")
19
- spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem")
20
- end
21
-
22
18
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the
23
19
  # 'allowed_push_host' to allow pushing to a single host or delete this
24
20
  # section to allow pushing to any host.
@@ -34,10 +30,11 @@ Gem::Specification.new do |spec|
34
30
  end
35
31
  spec.bindir = "bin"
36
32
  spec.executables = "ground"
37
- spec.require_paths = ["lib"]
33
+ spec.require_paths = %w[lib config]
38
34
 
39
35
  spec.add_dependency "jira-ruby", "~> 1.3"
40
36
  spec.add_dependency "thor", "~> 0.19"
37
+ spec.add_dependency "mina", "~> 1.2"
41
38
 
42
39
  spec.add_development_dependency "bundler", "~> 1.15"
43
40
  spec.add_development_dependency "minitest", "~> 5.0"
data/lib/groundskeeper.rb CHANGED
@@ -11,6 +11,7 @@ require "groundskeeper/project"
11
11
  require "groundskeeper/rails_version"
12
12
  require "groundskeeper/repository"
13
13
  require "groundskeeper/version"
14
+ require "groundskeeper/website"
14
15
 
15
16
  # :reek:IrresponsibleModule
16
17
  module Groundskeeper
@@ -7,6 +7,9 @@ module Groundskeeper
7
7
  class Application < Thor
8
8
  attr_reader :commands
9
9
 
10
+ class_option :simulate, desc: "Simulate mina"
11
+ class_option :verbose, desc: "Run mina verbosely"
12
+
10
13
  def initialize(args = [], local_options = {}, config = {})
11
14
  @commands = Commands.build(self)
12
15
  super
@@ -22,5 +25,17 @@ module Groundskeeper
22
25
  commands.info
23
26
  commands.release
24
27
  end
28
+
29
+ desc "predeploy", "create configuration files for the project"
30
+ def predeploy
31
+ commands.info
32
+ commands.predeploy options
33
+ end
34
+
35
+ desc "deploy", "deploy the latest release"
36
+ def deploy
37
+ commands.info
38
+ commands.deploy options
39
+ end
25
40
  end
26
41
  end
@@ -1,9 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rake"
4
+ require "mina"
5
+ require "pp"
6
+ require "open3"
7
+
3
8
  module Groundskeeper
4
- # Formulas for managing releases.
9
+ # Formulas for managing releases and deployments.
5
10
  # rubocop:disable Metrics/ClassLength
6
11
  class Commands
12
+ RAKEFILE = File.join(
13
+ File.dirname(__FILE__), "..", "..", "config", "deploy.rb"
14
+ )
15
+ STAGE = "stage"
16
+ STAGING = "staging"
17
+ PRODUCTION = "production"
18
+ DEFAULT_STAGE = STAGING
19
+ TAG = "tag"
20
+
7
21
  # rubocop:disable Metrics/MethodLength
8
22
  def self.build(console)
9
23
  repository = Repository.new
@@ -67,8 +81,10 @@ module Groundskeeper
67
81
  )
68
82
  end
69
83
 
84
+ # rubocop:disable Metrics/AbcSize
70
85
  def release
71
86
  return unrecognized_version unless version_file.exists?
87
+ return missing_jira_credentials unless jira.credentials?
72
88
 
73
89
  summarize_recent_commits
74
90
  ask_next_version
@@ -81,8 +97,21 @@ module Groundskeeper
81
97
  ask_add_version_to_jira_issues
82
98
  open_pull_request_page
83
99
  end
100
+ # rubocop:enable Metrics/AbcSize
84
101
  # rubocop:enable Metrics/MethodLength
85
102
 
103
+ def predeploy(options)
104
+ return unrecognized_version unless version_file.exists?
105
+
106
+ mina "predeploy", options
107
+ end
108
+
109
+ def deploy(options)
110
+ return unrecognized_version unless version_file.exists?
111
+
112
+ mina "deploy", options
113
+ end
114
+
86
115
  private
87
116
 
88
117
  # collaborators
@@ -92,6 +121,15 @@ module Groundskeeper
92
121
  attr_reader :next_version, :recent_commits, :did_checkout_branch,
93
122
  :did_push_to_remote
94
123
 
124
+ def mina(command, options)
125
+ cmd = String.new command
126
+ cmd << " -s" if options[:simulate]
127
+ cmd << " -v" if options[:verbose]
128
+ run_mina cmd
129
+ console.say("waiting for deployed application to restart...", :yellow)
130
+ update_deployed_issues
131
+ end
132
+
95
133
  def announce_version
96
134
  console.say("Groundskeeper version #{Groundskeeper::VERSION}\n\n", :bold)
97
135
  end
@@ -185,6 +223,13 @@ module Groundskeeper
185
223
  )
186
224
  end
187
225
 
226
+ def missing_jira_credentials
227
+ console.say(
228
+ "Please configure your Jira environment variables.",
229
+ :red
230
+ )
231
+ end
232
+
188
233
  def open_pull_request_page
189
234
  return unless did_checkout_branch && did_push_to_remote
190
235
 
@@ -194,6 +239,45 @@ module Groundskeeper
194
239
  def new_branch_name
195
240
  "v#{next_version}"
196
241
  end
242
+
243
+ def run_mina(arguments)
244
+ command = "mina #{arguments} -f #{RAKEFILE}"
245
+ Open3.popen3(command) do |_stdout, stderr, _status, _thread|
246
+ # rubocop:disable Lint/AssignmentInCondition
247
+ while line = stderr.gets
248
+ puts line
249
+ end
250
+ # rubocop:enable Lint/AssignmentInCondition
251
+ end
252
+ end
253
+
254
+ def update_deployed_issues
255
+ deployed_url = "https://#{project.full_dns(stage)}"
256
+ deployed_version = Website.new(deployed_url).version
257
+
258
+ if deployed_version == ENV[TAG]
259
+ console.say("# deployment successful", :green)
260
+ transition_remote_issues deployed_version
261
+ else
262
+ console.say("something went wrong", :red)
263
+ end
264
+ end
265
+
266
+ def stage
267
+ ENV[STAGE] == PRODUCTION ? PRODUCTION : DEFAULT_STAGE
268
+ end
269
+
270
+ def transition_remote_issues(version)
271
+ version_name = "#{repository.name} #{version}"
272
+ deployed_issues = jira.fetch_issues_by_fix_version(version_name)
273
+ action = stage == PRODUCTION ? Jira::DELIVER : Jira::DEPLOY_TO_STAGING
274
+ console.say(
275
+ "transitioning deployed issues in Jira: " \
276
+ "#{action} #{deployed_issues.join(', ')}",
277
+ :yellow
278
+ )
279
+ jira.transition_remote_issues(action, deployed_issues)
280
+ end
197
281
  end
198
282
  # rubocop:enable Metrics/ClassLength
199
283
  end
@@ -6,6 +6,16 @@ require "jira-ruby"
6
6
  module Groundskeeper
7
7
  # Wraps an interface to Jira.
8
8
  class Jira
9
+ JIRA_USERNAME_KEY = "JIRA_USERNAME"
10
+ JIRA_PASSWORD_KEY = "JIRA_PASSWORD"
11
+ JIRA_SITE_KEY = "JIRA_SITE"
12
+ DEPLOY_TO_STAGING = "Deploy to Staging"
13
+ DELIVER = "Deliver"
14
+ TRANSITION_IDS = {
15
+ DEPLOY_TO_STAGING => 231,
16
+ DELIVER => 201
17
+ }.freeze
18
+
9
19
  attr_reader :client, :prefix
10
20
 
11
21
  ISSUE_PATTERN = "%s-\\d+"
@@ -16,9 +26,9 @@ module Groundskeeper
16
26
 
17
27
  def initialize
18
28
  @client = JIRA::Client.new(
19
- username: ENV["JIRA_USERNAME"],
20
- password: ENV["JIRA_PASSWORD"],
21
- site: ENV["JIRA_SITE"],
29
+ username: ENV[JIRA_USERNAME_KEY],
30
+ password: ENV[JIRA_PASSWORD_KEY],
31
+ site: ENV[JIRA_SITE_KEY],
22
32
  context_path: "",
23
33
  auth_type: :basic,
24
34
  read_timeout: 120
@@ -36,6 +46,23 @@ module Groundskeeper
36
46
  .find(issue_id)
37
47
  .save(update: { fixVersions: [{ add: { name: version_name } }] })
38
48
  end
49
+
50
+ def transition_issue(issue_id:, transition_id:)
51
+ client.Issue
52
+ .find(issue_id)
53
+ .transitions
54
+ .build
55
+ .save(transition: { id: transition_id })
56
+ end
57
+
58
+ def fetch_issues_by_fix_version(version:)
59
+ search_path = "/rest/api/2/search"
60
+ query = "fixVersion = \"#{version}\""
61
+ response =
62
+ client.get("#{search_path}?fields=key&jql=#{URI.escape(query)}").body
63
+
64
+ JSON.parse(response)["issues"].map { |issue| issue["key"] }
65
+ end
39
66
  end
40
67
 
41
68
  def self.build(prefix)
@@ -50,6 +77,12 @@ module Groundskeeper
50
77
  @client = client
51
78
  end
52
79
 
80
+ def credentials?
81
+ ENV[JIRA_USERNAME_KEY].present? &&
82
+ ENV[JIRA_PASSWORD_KEY].present? &&
83
+ ENV[JIRA_SITE_KEY].present?
84
+ end
85
+
53
86
  # Returns the list of Jira issues found in a set of commit messages.
54
87
  def included_issues(changes)
55
88
  issue_expression = /#{format(ISSUE_PATTERN, prefix)}/
@@ -66,5 +99,18 @@ module Groundskeeper
66
99
  client.add_version_to_issue(issue_id: issue_id, version_name: name)
67
100
  end
68
101
  end
102
+
103
+ def transition_remote_issues(transition_type, issue_ids)
104
+ issue_ids.each do |issue_id|
105
+ client.transition_issue(
106
+ issue_id: issue_id,
107
+ transition_id: TRANSITION_IDS[transition_type]
108
+ )
109
+ end
110
+ end
111
+
112
+ def fetch_issues_by_fix_version(version)
113
+ client.fetch_issues_by_fix_version(version: version)
114
+ end
69
115
  end
70
116
  end
@@ -1,27 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "yaml"
4
+ require "json"
4
5
 
5
6
  module Groundskeeper
6
7
  # Accesses project details stored in the home directory.
7
8
  class Project
8
- attr_reader :details
9
+ attr_reader :details, :servers, :repo_name
9
10
 
10
11
  DETAILS_PATH = "~/.project_details/projects.yml"
12
+ SERVERS_PATH = "../../servers.yml"
11
13
  JIRA_PREFIX_KEY = "jira_prefix"
12
14
  PROJECT_NAME_KEY = "name"
13
15
  SOURCE_CONTROL_USERNAME_KEY = "scm_username"
16
+ FULL_DNS_KEY = "full_dns"
17
+ RVM_RUBY_VERSION_KEY = "ruby_version"
18
+ DB_ENV_VARIABLES_KEY = "db_env_variables"
19
+ SENTRY_DSN_KEY = "sentry_dsn"
20
+ SENTRY_PUBLIC_DSN_KEY = "sentry_public_dsn"
21
+ SSL_CERTIFICATE_FILE_KEY = "SSLCertificateFile"
22
+ SSL_CERTIFICATE_CHAIN_FILE_KEY = "SSLCertificateChainFile"
23
+ SSL_CERTIFICATE_KEY_FILE_KEY = "SSLCertificateKeyFile"
14
24
 
15
25
  def self.build(repository_name)
16
26
  new(
17
- yaml: Document.new(DETAILS_PATH).read,
27
+ projects_yaml: Document.new(DETAILS_PATH).read,
28
+ servers_yaml: Document.new(SERVERS_PATH).read,
18
29
  repository_name: repository_name
19
30
  )
20
31
  end
21
32
 
22
- def initialize(yaml:, repository_name:)
33
+ def self.available_applications
34
+ yaml = Document.new(DETAILS_PATH).read
23
35
  projects = YAML.safe_load(yaml) || {}
36
+ projects.keys.join(", ")
37
+ end
38
+
39
+ def initialize(projects_yaml:, servers_yaml:, repository_name:)
40
+ projects = YAML.safe_load(projects_yaml) || {}
24
41
  @details = projects[repository_name] || {}
42
+ @servers = YAML.safe_load(servers_yaml) || {}
43
+ @repo_name = repository_name
25
44
  end
26
45
 
27
46
  def project_name
@@ -35,5 +54,62 @@ module Groundskeeper
35
54
  def source_control_username
36
55
  details[SOURCE_CONTROL_USERNAME_KEY] || ""
37
56
  end
57
+
58
+ def full_dns(stage)
59
+ details.dig(FULL_DNS_KEY, stage.to_s) || ""
60
+ end
61
+
62
+ def db_env_variables(stage)
63
+ details.dig(DB_ENV_VARIABLES_KEY, stage.to_s) || ""
64
+ end
65
+
66
+ def rvm_ruby_version
67
+ details[RVM_RUBY_VERSION_KEY] || ""
68
+ end
69
+
70
+ def sentry_dsn
71
+ details[SENTRY_DSN_KEY] || ""
72
+ end
73
+
74
+ def sentry_public_dsn
75
+ details[SENTRY_PUBLIC_DSN_KEY] || ""
76
+ end
77
+
78
+ def ssl_certificate_file(stage)
79
+ details.dig(SSL_CERTIFICATE_FILE_KEY, stage.to_s) || ""
80
+ end
81
+
82
+ def ssl_certificate_chain_file(stage)
83
+ details.dig(SSL_CERTIFICATE_CHAIN_FILE_KEY, stage.to_s) || ""
84
+ end
85
+
86
+ def ssl_certificate_key_file(stage)
87
+ details.dig(SSL_CERTIFICATE_KEY_FILE_KEY, stage.to_s) || ""
88
+ end
89
+
90
+ def server_config(stage)
91
+ servers[full_dns(stage)]
92
+ end
93
+
94
+ def repo_url
95
+ "git@github.com:#{source_control_username}/#{repo_name}.git"
96
+ end
97
+
98
+ def deploy_to
99
+ "/var/www/apps/#{repo_name}"
100
+ end
101
+
102
+ def tags
103
+ api_url = "https://api.github.com/repos/" \
104
+ "#{source_control_username}/#{repo_name}/tags"
105
+ authorization_header = "Authorization: token #{ENV['GITHUB_API_TOKEN']}"
106
+ response = JSON.parse(`curl -s -H "#{authorization_header}" #{api_url}`)
107
+ unless response.is_a?(Array)
108
+ message = response["message"]
109
+ puts "Failed to obtain response: #{message}"
110
+ return []
111
+ end
112
+ response.map { |h| h["name"] }
113
+ end
38
114
  end
39
115
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Groundskeeper
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open-uri"
4
+
5
+ module Groundskeeper
6
+ # A deployed website/application.
7
+ class Website
8
+ VERSION_PATH = "version.txt"
9
+
10
+ attr_reader :uri
11
+
12
+ def initialize(uri)
13
+ @uri = uri
14
+ end
15
+
16
+ def version
17
+ URI.parse("#{uri}/#{VERSION_PATH}").read
18
+ end
19
+ end
20
+ end
data/servers.yml ADDED
@@ -0,0 +1,35 @@
1
+ cbits-railsapps-staging:
2
+ domain: cbits-railsapps-staging.nubic.northwestern.edu
3
+ SSLCertificateFile: /etc/pki/tls/certs/cbits.northwestern.edu.crt
4
+ SSLCertificateChainFile: /etc/pki/tls/certs/cbits.northwestern.edu_intermediate.crt
5
+ SSLCertificateKeyFile: /etc/pki/tls/private/cbits.northwestern.edu.key
6
+ cbits-railsapps:
7
+ domain: cbits-railsapps.nubic.northwestern.edu
8
+ SSLCertificateFile: /etc/pki/tls/certs/cbits-railsapps.nubic.northwestern.edu.crt
9
+ SSLCertificateChainFile: /etc/pki/tls/certs/komodo_intermediate_ca.crt
10
+ SSLCertificateKeyFile: /etc/pki/tls/private/cbits-railsapps.nubic.northwestern.edu.key
11
+ vtfsmcbitsapps01:
12
+ domain: vtfsmcbitsapps01.fsm.northwestern.edu
13
+ SSLCertificateFile: /etc/pki/tls/certs/cbits.northwestern.edu.crt
14
+ SSLCertificateChainFile: /etc/pki/tls/certs/cbits.northwestern.edu_intermediate.crt
15
+ SSLCertificateKeyFile: /etc/pki/tls/private/cbits.northwestern.edu.key
16
+ vfsmcbitsapps10:
17
+ domain: vfsmcbitsapps10.fsm.northwestern.edu
18
+ SSLCertificateFile: /etc/pki/tls/certs/vfsmcbitsapps10_nubic_northwestern_edu_cert.cer
19
+ SSLCertificateChainFile: /etc/pki/tls/certs/vfsmcbitsapps10_nubic_northwestern_edu_interm.cer
20
+ SSLCertificateKeyFile: /etc/pki/tls/private/cbits-railsapps.nubic.northwestern.edu.key
21
+ vfsmcbitsapps11:
22
+ domain: vfsmcbitsapps11.fsm.northwestern.edu
23
+ SSLCertificateFile: /etc/pki/tls/certs/vfsmcbitsapps11_nubic_northwestern_edu_cert.cer
24
+ SSLCertificateChainFile: /etc/pki/tls/certs/vfsmcbitsapps11_nubic_northwestern_edu_interm.cer
25
+ SSLCertificateKeyFile: /etc/pki/tls/private/cbits-railsapps.nubic.northwestern.edu.key
26
+ vfsmcbitsapps12:
27
+ domain: vfsmcbitsapps12.fsm.northwestern.edu
28
+ SSLCertificateFile: /etc/pki/tls/certs/vfsmcbitsapps12.fsm.northwestern.edu.crt
29
+ SSLCertificateChainFile: /etc/pki/tls/certs/vfsmcbitsapps12.fsm.northwestern.edu_intermediate.crt
30
+ SSLCertificateKeyFile: /etc/pki/tls/private/cbits-railsapps.nubic.northwestern.edu.key
31
+ vfsmcbitsapps13:
32
+ domain: vfsmcbitsapps13.fsm.northwestern.edu
33
+ SSLCertificateFile: /etc/pki/tls/certs/vfsmcbitsapps13.fsm.northwestern.edu.crt
34
+ SSLCertificateChainFile: /etc/pki/tls/certs/vfsmcbitsapps13.fsm.northwestern.edu_intermediate.crt
35
+ SSLCertificateKeyFile: /etc/pki/tls/private/cbits-railsapps.nubic.northwestern.edu.key
metadata CHANGED
@@ -1,36 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: groundskeeper-bitcore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BIT Core
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain:
11
- - |
12
- -----BEGIN CERTIFICATE-----
13
- MIIDhTCCAm2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQ8wDQYDVQQDDAZlcmlj
14
- Y2YxHDAaBgoJkiaJk/IsZAEZFgxub3J0aHdlc3Rlcm4xEzARBgoJkiaJk/IsZAEZ
15
- FgNlZHUwHhcNMTcwOTEyMjAwODQzWhcNMTgwOTEyMjAwODQzWjBEMQ8wDQYDVQQD
16
- DAZlcmljY2YxHDAaBgoJkiaJk/IsZAEZFgxub3J0aHdlc3Rlcm4xEzARBgoJkiaJ
17
- k/IsZAEZFgNlZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDk8FgP
18
- SIrleTjIXJT/yu+W9nujkvcIMRPbLqKq60KQy/2LANMZ7DiLZRBkg2oSK0D9p8GR
19
- be30F2DGAOSBZiBTLPDNAQZBpRVGeFpAAOEpREZ8ahaZVTfc8+IYSfxpnW2gaUte
20
- SfbG5xNcpHkRpwkbZGoq9yFOoaucxfwoz+9QunEFDiCAumn7uRMKHJdqSEpQnFkQ
21
- 56GKuUvUuq7J+J4M+kwOK6a/4/Y8Bs/qUYH3GHa/0WJwfFLfp3gqhagLcZu40JFe
22
- mQpX3bAkmojuxyAi7JjXKqFv1LZgxAGca5SC6sECXiTkw9eXInBKzvBN33gOv+Vj
23
- 9bot70L0EihaqJrnAgMBAAGjgYEwfzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAd
24
- BgNVHQ4EFgQUsMK40utpv6yRQ3i0R7avJn3LQ/YwIgYDVR0RBBswGYEXZXJpY2Nm
25
- QG5vcnRod2VzdGVybi5lZHUwIgYDVR0SBBswGYEXZXJpY2NmQG5vcnRod2VzdGVy
26
- bi5lZHUwDQYJKoZIhvcNAQEFBQADggEBACvUVV3V8EYqV9DanJS3ywMVnQB5ByP1
27
- 7TV70+Amnnu196Jp2JGJ27sjeE79VJeBbGyE0rCRUQXlaD2Zh8X6Yy04oWR0YFa8
28
- DQ3rqVmKTodTJ0K/rmpIMCbwrsyg7QRxfB6cUE+sgPX5ygDmj2gYY+fcw3MoVOif
29
- QY1i1gFSjcXvx0qnvQLnbD+gbr7D3wpfgFHRbBEDOuNdpFthggUO6eFraVYcsz9u
30
- 1v/mC2s7Cj+Jv053bCKPjSk2wLLZpHRs40jXgy/NudtouDy2yYlJHxBFfdeRNEk2
31
- fo8I5WeWdUoEzUI6WlkTjMw3nWezlF0LCGz781P4qvtz0BICgaS41qw=
32
- -----END CERTIFICATE-----
33
- date: 2017-09-15 00:00:00.000000000 Z
11
+ - certs/ericcf.pem
12
+ date: 2017-11-29 00:00:00.000000000 Z
34
13
  dependencies:
35
14
  - !ruby/object:Gem::Dependency
36
15
  name: jira-ruby
@@ -60,6 +39,20 @@ dependencies:
60
39
  - - "~>"
61
40
  - !ruby/object:Gem::Version
62
41
  version: '0.19'
42
+ - !ruby/object:Gem::Dependency
43
+ name: mina
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '1.2'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '1.2'
63
56
  - !ruby/object:Gem::Dependency
64
57
  name: bundler
65
58
  requirement: !ruby/object:Gem::Requirement
@@ -138,6 +131,14 @@ files:
138
131
  - bin/ground
139
132
  - bin/setup
140
133
  - certs/ericcf.pem
134
+ - config/db_config.yml.erb
135
+ - config/deploy.rb
136
+ - config/git_config.rb
137
+ - config/predeploy.rb
138
+ - config/rails_config.rb.erb
139
+ - config/secrets_config.yml.erb
140
+ - config/tasks.rb
141
+ - config/vhost_config.conf.erb
141
142
  - groundskeeper.gemspec
142
143
  - lib/groundskeeper.rb
143
144
  - lib/groundskeeper/application.rb
@@ -151,6 +152,8 @@ files:
151
152
  - lib/groundskeeper/rails_version.rb
152
153
  - lib/groundskeeper/repository.rb
153
154
  - lib/groundskeeper/version.rb
155
+ - lib/groundskeeper/website.rb
156
+ - servers.yml
154
157
  homepage: https://github.com/NU-CBITS/groundskeeper.git
155
158
  licenses:
156
159
  - MIT
@@ -160,6 +163,7 @@ post_install_message:
160
163
  rdoc_options: []
161
164
  require_paths:
162
165
  - lib
166
+ - config
163
167
  required_ruby_version: !ruby/object:Gem::Requirement
164
168
  requirements:
165
169
  - - ">="
checksums.yaml.gz.sig DELETED
Binary file
data.tar.gz.sig DELETED
@@ -1,2 +0,0 @@
1
- YN����� �@D����8��vr�F�Ϸ�ܓ�O�7��> ���P}j��
2
- iZDh4>�3��%ɻ�mr����v3��b��0_Fk��B�u&�y��;�G��3C-ɣ��g�ݕîw捀IR~�w��gm���Q��_=~V5�� �2SU^���%�O��|~ր;�]�f(���
metadata.gz.sig DELETED
@@ -1,3 +0,0 @@
1
- z#Vg!Kj�*�-�@�/.�u���M]�3�Coe�*�n�WZq^�RY!,���Ӫ.���3�,��)�k�0:��%(B�����H?�@ $�'�K�Mra�*�`DO��)
2
- �M/��Y,�����~kh�������Q��GD"�����Qၔn�l�������tlq���D,�P�TSH�'_� �MrO�r� 9�I$Ra)_]���MC-��F1�6j����36G�̶ѥU�r—��� �F�
3
- %