heroku_rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 877dd9dceebb80e6c15c8e5fd3f631569152f8eea1b7951c3cce6863224f73fc
4
+ data.tar.gz: 89b27c00e706dd8ed87c31cae2c75f20f7d8dab9182388445e8961a1f00ddb7c
5
+ SHA512:
6
+ metadata.gz: d85490b81a5b2177b64bc8280c5a2df3527b5f80316cc68797c09614d92701ad90bb4971b18e6fb85c8919e15c17f9bd0c36d9e9bd4fb9befaa1087fee38e852
7
+ data.tar.gz: e0d8a4042afa600672f227cef5bbc94254ace3f678c4500fcbe7dd25cd055d70c96b53c94ea7779b8b4c26021ec3ebd481e95b59c77e6dce379916a4a9419dc8
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.7
7
+ before_install: gem install bundler -v 1.17.3
@@ -0,0 +1,42 @@
1
+ # Code of Conduct
2
+
3
+ ## 1. Purpose
4
+
5
+ One of the primary goals of Siapbantu is to be inclusive to all contributors, with the most varied and diverse backgrounds possible. Everyone is welcome to contribute! Therefore, we are committed to providing a friendly and safe environment for everyone.
6
+
7
+ This code of conduct outlines our overall expectations for anyone who participates in our community, as well as the consequences for unacceptable behavior.
8
+
9
+ We invite all those who participate in Siapbantu to help us foster a safe and positive experience for everyone.
10
+
11
+ ## 2. Expected Behavior
12
+
13
+ - Embody our Culture & Values.
14
+ - Be friendly, kind and empathic towards each other.
15
+ - Use welcoming and inclusive language.
16
+ - Be respectful of differing viewpoints and experiences.
17
+ - Gracefully accept constructive criticism.
18
+ - Focus on what is best for the community.
19
+ - Participate in an authentic and active way. By doing so, you contribute to the success, health and future of this initiative.
20
+ - Please alert community leaders if you notice violations of this Code of Conduct, even if they seem minor.
21
+
22
+ ## 3. Unacceptable Behavior
23
+
24
+ - Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes or insults.
25
+ - Trolling, insulting/derogatory comments, and personal or political attacks.
26
+ - Inciting or threatening violence, including encouraging others to hurt themselves
27
+ - The use of sexualized language or imagery and unwelcome sexual attention or advances.
28
+ - False accusations or accusations without evidence.
29
+ - Spam (unsolicited or frequently off-topic messages)
30
+ - Advocating for, or encouraging, any of the above behavior.
31
+
32
+ ## 4. Consequences of Unacceptable Behavior
33
+
34
+ Unacceptable behavior from any volunteer or community member will not be tolerated. If you are subject to or witness unacceptable behavior, or have any other concerns, please notify your team leads and @shitiomatic via slack or email (human@shitiomatic.tech). We have the the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
35
+
36
+ ## 5. Addressing Grievances
37
+
38
+ If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify @shitiomatic via slack or (human@shitiomatic.tech) and/or the Siapbantu team leads with a concise description of your grievance.
39
+
40
+ ## 6. Scope
41
+
42
+ We expect all volunteers and community participants to abide by this Code of Conduct in all community channels, calls, events and in-person–as well as in all one-on-one communications pertaining to Siapbantu..
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in heroku_rails.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Siapbantu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,109 @@
1
+ # Heroku ❤️ Rails
2
+
3
+ Useless tool for easy deploy Ruby on Rails to Heroku
4
+
5
+ If you're using continuous deployment with pipelines in Heroku, you won't need this. However if that style doesn't work, then this may allow you to manage databases and deployment with more control but less hassle.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'heroku_rails'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install heroku_rails
22
+
23
+ ## Configuration
24
+
25
+ > TODO: Some of these manual configuration steps, would be nicer to make a bit more automatic perhaps by making this a Rails engine.
26
+ 1) Your database configuration (config/database.yml) needs to have a username if you want to use the db configurations
27
+
28
+ 2) Append `load "heroku_rails/tasks/db_drop_all_tables.rake"` to the end of Rakefile.
29
+
30
+ 3) Copy templates into codebase:
31
+
32
+ cp $(bundle show heroku_rails)/templates/heroku.thor ./lib/tasks
33
+ cp $(bundle show heroku_rails)/templates/heroku_targets.yml ./config
34
+
35
+ 4) update heroku_targets.yml with your staging and production targets.
36
+ My set up for this is to have staging deploy the local version, but production
37
+ deploy the origin/master.
38
+
39
+ > TODO: more detail
40
+
41
+ 5) You may want to set up a smoke test that your heroku targets are valid
42
+
43
+ require "heroku_rails/heroku_targets"
44
+
45
+ RSpec.describe "heroku_targets.yml" do
46
+ it "is valid (smoke test)" do
47
+ HerokuRails::HerokuTargets.from_file(Rails.root.join("config/heroku_targets.yml"))
48
+ end
49
+ end
50
+
51
+ ## Usage
52
+
53
+ ### Deploy
54
+
55
+ Deploy the latest with db migrate during maintenance
56
+
57
+ thor heroku:deploy staging
58
+
59
+ or without maintenance
60
+
61
+ thor heroku:deploy staging --no-maintenance
62
+
63
+ or without migrating
64
+
65
+ thor heroku:deploy staging --no-migrate
66
+
67
+ or a specific tag/branch
68
+
69
+ thor heroku:deploy staging hotfix-branch
70
+
71
+
72
+ ### Sync
73
+
74
+ Sync a database down from remote to local
75
+
76
+ thor heroku:sync:grab -f staging
77
+ # when it finishes
78
+ rake db:drop_all_tables && thor heroku:sync:from_dump -f staging
79
+
80
+ _FYI rake db:drop_all_tables is handy as it doesn't require you to disconnect any running processes from the database._
81
+
82
+ Copy production db to staging
83
+
84
+ thor heroku:sync:to staging -f production
85
+
86
+ NB: this won't work from a staging to a production environment (failsafe)
87
+
88
+
89
+ ## Development
90
+
91
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
92
+
93
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
94
+
95
+ Ensure standards before PR:
96
+
97
+ bundle exec standardrb --fix
98
+
99
+ ## Contributing
100
+
101
+ Bug reports and pull requests are welcome on GitHub at https://github.com/siapbantu/heroku_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
102
+
103
+ ## License
104
+
105
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
106
+
107
+ ## Code of Conduct
108
+
109
+ Everyone interacting in the HerokuRails project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/siapbantu/heroku_rails/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "heroku_rails"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,31 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "heroku_rails/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "heroku_rails"
7
+ spec.version = HerokuRails::VERSION
8
+ spec.authors = ["Ahmad Ainul Rizki"]
9
+ spec.email = ["human@shitiomatic.email"]
10
+
11
+ spec.summary = "Useless tool for easy deploy Ruby on Rails to Heroku"
12
+ spec.homepage = "https://github.com/siapbantu/heroku_rails"
13
+ spec.license = "MIT"
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "thor", "~> 0.18"
25
+ spec.add_dependency "activesupport"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.17"
28
+ spec.add_development_dependency "rake", ">= 12.3.3"
29
+ spec.add_development_dependency "rspec", "~> 3.0"
30
+ spec.add_development_dependency "standard", "~> 0.4.1"
31
+ end
@@ -0,0 +1,7 @@
1
+ require "heroku_rails/db_configuration"
2
+ require "heroku_rails/heroku_targets"
3
+ require "heroku_rails/version"
4
+
5
+ module HerokuRails
6
+ class Error < StandardError; end
7
+ end
@@ -0,0 +1,34 @@
1
+ # NB need to specify a username in the database.yml if you want to use any of these commands
2
+ module HerokuRails
3
+ class DbConfiguration
4
+ def config
5
+ @config ||= YAML.load(
6
+ ERB.new(File.read("config/database.yml")).result
7
+ )
8
+ end
9
+
10
+ def generate_drop_tables_sql
11
+ sql = %(select 'DROP TABLE IF EXISTS \\"' || tablename || '\\" CASCADE;' from pg_tables where schemaname = 'public')
12
+ %(psql #{user_arg} #{database} -t -c "#{sql}")
13
+ end
14
+
15
+ def user_arg
16
+ username = db_config["username"]
17
+ username.present? && "-U #{username}" || ""
18
+ end
19
+
20
+ def database
21
+ db_config["database"]
22
+ end
23
+
24
+ private
25
+
26
+ def db_config
27
+ config[env]
28
+ end
29
+
30
+ def env
31
+ ENV["RAILS_ENV"] || "development"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/all"
4
+ require "yaml"
5
+
6
+ module HerokuRails
7
+ class HerokuTargets
8
+ class << self
9
+ def from_string(heroku_targets_yml)
10
+ new(YAML.safe_load(heroku_targets_yml))
11
+ end
12
+
13
+ def from_file(yaml_file)
14
+ new(YAML.safe_load(File.read(yaml_file)))
15
+ end
16
+ end
17
+
18
+ attr_reader :targets, :staging_targets
19
+
20
+ DEFAULTS_KEY = "_defaults"
21
+
22
+ def initialize(targets_hash)
23
+ defaults = if targets_hash.keys.first == DEFAULTS_KEY
24
+ targets_hash.delete(DEFAULTS_KEY)
25
+ else
26
+ {}
27
+ end
28
+ specified = targets_hash.collect { |name, values|
29
+ heroku_target = HerokuTarget.new(defaults.merge(values), name)
30
+ [heroku_target.heroku_app, heroku_target]
31
+ }
32
+ built_in = [["local", LocalProxy.new(defaults)]]
33
+ @targets = TargetsContainer[(specified + built_in)].freeze
34
+ @staging_targets = TargetsContainer[@targets.select { |_name, target| target.staging? }]
35
+ end
36
+
37
+ class TargetsContainer < HashWithIndifferentAccess
38
+ def [](key)
39
+ return super if key?(key)
40
+ values.each do |value|
41
+ return value if value.name.to_s == key.to_s
42
+ end
43
+ nil
44
+ end
45
+ end
46
+
47
+ class HerokuTarget
48
+ attr_reader :name
49
+
50
+ def initialize(values_hash, name = nil)
51
+ @values = values_hash.symbolize_keys.freeze
52
+ @name = name.to_sym if name
53
+ %i[heroku_app git_remote deploy_ref].each do |required_name|
54
+ raise required_value(required_name) unless @values[required_name] || local?
55
+ end
56
+ end
57
+
58
+ def required_value(required_name)
59
+ ArgumentError.new("please specify '#{required_name}:' ")
60
+ end
61
+
62
+ def staging?
63
+ @values[:staging]
64
+ end
65
+
66
+ def local?
67
+ false
68
+ end
69
+
70
+ def display_name
71
+ @values[:display_name] || @values[:heroku_app]
72
+ end
73
+
74
+ def heroku_app
75
+ @values[:heroku_app]
76
+ end
77
+
78
+ def database_url
79
+ @values[:database_url]
80
+ end
81
+
82
+ def git_remote
83
+ @values[:git_remote]
84
+ end
85
+
86
+ def deploy_ref
87
+ @values[:deploy_ref]
88
+ end
89
+
90
+ def db_color
91
+ @values[:db_color] || "DATABASE"
92
+ end
93
+
94
+ def repository
95
+ @values[:repository] || raise(required_value(:repository))
96
+ end
97
+
98
+ def to_s
99
+ display_name
100
+ end
101
+
102
+ def dump_filename
103
+ File.expand_path("tmp/latest_#{heroku_app}_backup.dump")
104
+ end
105
+
106
+ def trackable_release_stage
107
+ @values[:trackable_release_stage].presence || (staging? ? "staging" : "production")
108
+ end
109
+ end
110
+
111
+ class LocalProxy < HerokuTarget
112
+ def initialize(defaults)
113
+ super(defaults.merge(staging: true, heroku_app: "local"), "local")
114
+ end
115
+
116
+ def local?
117
+ true
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :db do
4
+ desc "drop all tables without worrying about concurrent accesses"
5
+ task drop_all_tables: :environment do
6
+ require File.expand_path("../../heroku_rails/db_configuration.rb", __FILE__)
7
+ abort("Don't run this on production") if Rails.env.production?
8
+
9
+ db_config = HerokuRails::DbConfiguration.new
10
+ generate_drop_tables_sql = db_config.generate_drop_tables_sql
11
+ cmd_string = %(#{generate_drop_tables_sql} | psql #{db_config.user_arg} #{db_config.database})
12
+ puts cmd_string
13
+ system(cmd_string)
14
+ end
15
+ end
@@ -0,0 +1,435 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "../db_configuration"
5
+ require_relative "../heroku_targets"
6
+ require_relative "../thor_utils"
7
+
8
+ class Heroku < Thor
9
+ module Configuration
10
+ class << self
11
+ def base_asset_url
12
+ asset_host = get_config_env(target, "ASSET_HOST")
13
+ "https://#{asset_host}"
14
+ end
15
+
16
+ def maintenance_mode_env_var
17
+ "X_HEROKU_RAILS_MAINTENANCE_MODE"
18
+ end
19
+
20
+ def app_revision_env_var
21
+ raise "choose between APP_REVISION and COMMIT_HASH"
22
+ end
23
+
24
+ def before_deploying(_instance, target, version, description: nil)
25
+ puts "about to deploy #{version} to #{target.name}"
26
+ puts " #{description}" if description
27
+ end
28
+
29
+ def after_deploying(_instance, target, version, description: nil)
30
+ puts "deployed #{version} to #{target.name}"
31
+ puts " #{description}" if description
32
+ end
33
+
34
+ def notify_of_deploy_tracking(running_thor_task, release_stage:, revision:, revision_describe:, repository:, target:, target_name:, deploy_ref:)
35
+ if ENV["BUGSNAG_API_KEY"].present?
36
+ running_thor_task.notify_bugsnag_of_deploy_tracking(deploy_ref, release_stage, repository, revision, revision_describe, target_name)
37
+ else
38
+ puts "can't notify of deploy tracking: env var not present: BUGSNAG_API_KEY"
39
+ end
40
+ end
41
+
42
+ def after_sync_down(instance)
43
+ instance.puts_and_system "rake db:migrate"
44
+ instance.puts_and_system "rake db:test:prepare"
45
+ end
46
+
47
+ def after_sync_to(instance, target)
48
+ instance.puts_and_system %(heroku run rake db:migrate -a #{target.heroku_app})
49
+ end
50
+ end
51
+ end
52
+
53
+ module Shared
54
+ def self.exit_on_failure?
55
+ true
56
+ end
57
+
58
+ def lookup_heroku_staging(staging_target_name)
59
+ heroku_targets.staging_targets[staging_target_name] || raise_missing_target(staging_target_name, true)
60
+ end
61
+
62
+ def lookup_heroku(target_name)
63
+ heroku_targets.targets[target_name] || raise_missing_target(target_name, false)
64
+ end
65
+
66
+ def check_deploy_ref(deploy_ref, target)
67
+ if deploy_ref && deploy_ref[0] == "-"
68
+ raise Thor::Error, "Invalid deploy ref '#{deploy_ref}'"
69
+ end
70
+
71
+ deploy_ref || target.deploy_ref
72
+ end
73
+
74
+ def raise_missing_target(target_name, staging)
75
+ if staging
76
+ description = "Staging target_name '#{target_name}'"
77
+ targets = heroku_targets.staging_targets.keys
78
+ else
79
+ description = "Target '#{target_name}'"
80
+ targets = heroku_targets.targets.keys
81
+ end
82
+ msg = "#{description} was not found. Valid targets are #{targets.collect { |t| "'#{t}'" }.join(",")}"
83
+ raise Thor::Error, msg
84
+ end
85
+
86
+ def heroku_targets
87
+ @heroku_targets ||= HerokuRails::HerokuTargets.from_file(File.expand_path("config/heroku_targets.yml"))
88
+ end
89
+
90
+ def puts_and_system(cmd)
91
+ puts cmd
92
+ puts "-------------"
93
+ system_with_clean_env cmd
94
+ puts "-------------"
95
+ end
96
+
97
+ def puts_and_exec(cmd)
98
+ puts cmd
99
+ exec_with_clean_env(cmd)
100
+ end
101
+
102
+ protected
103
+
104
+ def deploy_message(target, deploy_ref_describe)
105
+ downtime = options[:migrate] ? "👷 There will be a very short maintenance downtime" : ""
106
+ message = <<-DEPLOY_MESSAGE
107
+ Deploying #{target.display_name} #{deploy_ref_describe}.
108
+ #{downtime} (in less than a minute from now).
109
+ DEPLOY_MESSAGE
110
+ message.gsub(/(\s|\n)+/, " ")
111
+ end
112
+
113
+ def system_with_clean_env(cmd)
114
+ if defined?(Bundler)
115
+ Bundler.with_clean_env { system cmd }
116
+ else
117
+ system cmd
118
+ end
119
+ end
120
+
121
+ def exec_with_clean_env(cmd)
122
+ if defined?(Bundler)
123
+ Bundler.with_clean_env { `#{cmd}` }
124
+ else
125
+ `#{cmd}`
126
+ end
127
+ end
128
+
129
+ def maintenance_on(target)
130
+ puts_and_system "heroku maintenance:on -a #{target.heroku_app}"
131
+ puts_and_system "heroku config:set #{Heroku::Configuration.maintenance_mode_env_var}=true -a #{target.heroku_app}"
132
+ end
133
+
134
+ def maintenance_off(target)
135
+ puts_and_system "heroku maintenance:off -a #{target.heroku_app}"
136
+ puts_and_system "heroku config:unset #{Heroku::Configuration.maintenance_mode_env_var} -a #{target.heroku_app}"
137
+ end
138
+ end
139
+
140
+ include Shared
141
+
142
+ class_option :verbose, type: :boolean, aliases: "v", default: true
143
+ default_command :help
144
+
145
+ desc "details", "collects and prints some information local and each target"
146
+
147
+ def details
148
+ puts
149
+ details = heroku_targets.targets.map { |name, target|
150
+ next if target.local?
151
+
152
+ print "."
153
+ [
154
+ "Heroku #{name}",
155
+ "(versions suppressed -- takes too long)" || exec_with_clean_env("heroku run 'rails -v && ruby -v' -a #{target.heroku_app}"),
156
+ exec_with_clean_env("heroku releases -n 1 -a #{target.heroku_app}").split("\n").last
157
+ ]
158
+ }
159
+ puts
160
+ details << [
161
+ "Local",
162
+ exec_with_clean_env("rails -v && ruby -v"),
163
+ exec_with_clean_env("git describe --always")
164
+ ]
165
+ details.each do |n, v, d|
166
+ puts "-" * 80
167
+ puts n
168
+ puts "-" * 80
169
+ puts v
170
+ puts d
171
+ end
172
+ end
173
+
174
+ desc "deploy TARGET (REF)", "deploy the latest to TARGET (optionally give a REF like a tag to deploy)"
175
+ method_option :migrate, default: true, desc: "Run with migrations", type: :boolean
176
+ method_option :maintenance, default: nil, desc: "Run with migrations", type: :boolean
177
+
178
+ def deploy(target_name, deploy_ref = nil)
179
+ target = lookup_heroku(target_name)
180
+ deploy_ref = check_deploy_ref(deploy_ref, target)
181
+ deploy_ref_description = deploy_ref_describe(deploy_ref)
182
+ maintenance = options[:maintenance].nil? && options[:migrate] || options[:maintenance]
183
+ puts "Deploy #{deploy_ref_description} to #{target} with migrate=#{options[:migrate]} maintenance=#{maintenance} "
184
+
185
+ invoke :list_deployed, [target_name, deploy_ref], {}
186
+ message = deploy_message(target, deploy_ref_description)
187
+ Configuration.before_deploying(self, target, deploy_ref_description)
188
+ set_message(target_name, message)
189
+ puts_and_system "git push -f #{target.git_remote} #{deploy_ref}^{}:master"
190
+
191
+ maintenance_on(target) if maintenance
192
+ if options[:migrate]
193
+ puts_and_system "heroku run rake db:migrate -a #{target.heroku_app}"
194
+ end
195
+
196
+ puts_and_system %{heroku config:set #{Heroku::Configuration.app_revision_env_var}=$(git describe --always #{deploy_ref}) -a #{target.heroku_app}}
197
+ if maintenance
198
+ maintenance_off(target)
199
+ else
200
+ puts_and_system "heroku restart -a #{target.heroku_app}"
201
+ end
202
+ set_message(target_name, nil)
203
+ Configuration.after_deploying(self, target, deploy_ref_description)
204
+ deploy_tracking(target_name, deploy_ref)
205
+ end
206
+
207
+ desc "maintenance ON|OFF", "turn maintenance mode on or off"
208
+ method_option :target_name, aliases: "a", desc: "Target (app or remote)"
209
+
210
+ def maintenance(on_or_off)
211
+ target = lookup_heroku(options[:target_name])
212
+ case on_or_off.upcase
213
+ when "ON"
214
+ maintenance_on(target)
215
+ when "OFF"
216
+ maintenance_off(target)
217
+ else
218
+ raise Thor::Error, "maintenance must be ON or OFF not #{on_or_off}"
219
+ end
220
+ end
221
+
222
+ desc "set_urls TARGET", "set and cache the error and maintenance page urls for TARGET"
223
+
224
+ def set_urls(target_name)
225
+ target = lookup_heroku(target_name)
226
+ time = Time.now.strftime("%Y%m%d-%H%M-%S")
227
+ url_hash = {
228
+ ERROR_PAGE_URL: "#{Heroku::Configuration.base_asset_url}/platform_error/#{time}",
229
+ MAINTENANCE_PAGE_URL: "#{Heroku::Configuration.base_asset_url}/platform_maintenance/#{time}"
230
+ }
231
+ url_hash.each do |_env, url|
232
+ puts_and_system "open #{url}"
233
+ end
234
+ puts_and_system(
235
+ "heroku config:set #{url_hash.map { |e, u| "#{e}=#{u}" }.join(" ")} -a #{target.heroku_app}"
236
+ )
237
+ end
238
+
239
+ no_commands do
240
+ def get_config_env(target, env_var)
241
+ puts_and_exec("heroku config:get #{env_var} -a #{target.heroku_app}").strip.presence
242
+ end
243
+
244
+ def deploy_ref_describe(deploy_ref)
245
+ `git describe #{deploy_ref}`.strip
246
+ end
247
+
248
+ def notify_bugsnag_of_deploy_tracking(deploy_ref, release_stage, repository, revision, revision_describe, target_name)
249
+ api_key = ENV["BUGSNAG_API_KEY"]
250
+ data = %W[
251
+ apiKey=#{api_key}
252
+ releaseStage=#{release_stage}
253
+ repository=#{repository}
254
+ revision=#{revision}
255
+ appVersion=#{revision_describe}
256
+ ].join("&")
257
+ if api_key.blank?
258
+ puts "\n" + ("*" * 80) + "\n"
259
+ command = "curl -d #{data} http://notify.bugsnag.com/deploy"
260
+ puts command
261
+ puts "\n" + ("*" * 80) + "\n"
262
+ puts "NB: can't notify unless you specify BUGSNAG_API_KEY and rerun"
263
+ puts " thor heroku:deploy_tracking #{target_name} #{deploy_ref}"
264
+ else
265
+ puts_and_system "curl -d \"#{data}\" http://notify.bugsnag.com/deploy"
266
+ end
267
+ end
268
+ end
269
+
270
+ desc "deploy_tracking TARGET (REF)", "set deploy tracking for TARGET and REF (used by deploy)"
271
+
272
+ def deploy_tracking(target_name, deploy_ref = nil)
273
+ target = lookup_heroku(target_name)
274
+ deploy_ref = check_deploy_ref(deploy_ref, target)
275
+ release_stage = target.staging? ? "staging" : "production"
276
+ revision = `git log -1 #{deploy_ref} --pretty=format:%H`
277
+ Heroku::Configuration.notify_of_deploy_tracking(
278
+ self,
279
+ deploy_ref: deploy_ref,
280
+ release_stage: target.trackable_release_stage,
281
+ revision: revision,
282
+ target: target,
283
+ target_name: target_name,
284
+ revision_describe: deploy_ref_describe(deploy_ref),
285
+ repository: target.repository
286
+ )
287
+ end
288
+
289
+ include HerokuRails::ThorUtils
290
+ desc "set_message TARGET (MESSAGE)", "set message (no-op by default)"
291
+
292
+ def set_message(target_name, message = nil)
293
+ # no-op -- define as override
294
+ end
295
+
296
+ desc "list_deployed TARGET (DEPLOY_REF)", "list what would be deployed to TARGET (optionally specify deploy_ref)"
297
+
298
+ def list_deployed(target_name, deploy_ref = nil)
299
+ target = lookup_heroku(target_name)
300
+ deploy_ref = check_deploy_ref(deploy_ref, target)
301
+ puts "------------------------------"
302
+ puts " Deploy to #{target}:"
303
+ puts "------------------------------"
304
+ system_with_clean_env "git --no-pager log $(heroku config:get #{Heroku::Configuration.app_revision_env_var} -a #{target.heroku_app})..#{deploy_ref}"
305
+ puts "------------------------------"
306
+ end
307
+
308
+ desc "about (TARGET)", "Describe available targets or one specific target"
309
+
310
+ def about(target_name = nil)
311
+ if target_name.nil?
312
+ puts "Targets: "
313
+ heroku_targets.targets.each_pair do |key, target|
314
+ puts " * #{key} (#{target})"
315
+ end
316
+ else
317
+ target = lookup_heroku(target_name)
318
+ puts "Target #{target_name}:"
319
+ puts " * display_name: #{target.display_name}"
320
+ puts " * heroku_app: #{target.heroku_app}"
321
+ puts " * git_remote: #{target.git_remote}"
322
+ puts " * deploy_ref: #{target.deploy_ref}"
323
+ end
324
+ puts
325
+ puts "(defined in config/heroku_targets.yml)"
326
+ end
327
+
328
+ class Sync < Thor
329
+ include Shared
330
+ class_option :from, type: :string, desc: "source target (production, staging...)", required: true, aliases: "f"
331
+
332
+ desc "down --from SOURCE_TARGET", "syncs db down from SOURCE_TARGET | thor heroku:sync -f production"
333
+
334
+ def down
335
+ invoke "grab", [], from: options[:from]
336
+ invoke "from_dump", [], from: options[:from]
337
+ end
338
+
339
+ desc "warn", "warn", hide: true
340
+
341
+ def warn
342
+ puts "should maybe 'rake db:drop_all_tables' first"
343
+ puts "if you have done some table-creating migrations that need tobe undone???"
344
+ end
345
+
346
+ desc "grab --from SOURCE_TARGET", "capture and download dump from SOURCE_TARGET", hide: true
347
+
348
+ def grab
349
+ source = lookup_heroku(options[:from])
350
+ capture_cmd = "heroku pg:backups:capture -a #{source.heroku_app}"
351
+ puts_and_system capture_cmd
352
+ invoke "download", [], from: options[:from]
353
+ end
354
+
355
+ desc "download --from SOURCE_TARGET", "download latest db snapshot on source_target"
356
+
357
+ def download
358
+ source = lookup_heroku(options[:from])
359
+ download_cmd = "curl -o #{source.dump_filename} `heroku pg:backups:public-url -a #{source.heroku_app}`"
360
+ puts_and_system download_cmd
361
+ end
362
+
363
+ desc "from_dump --from SOURCE_TARGET", "make the db the same as the last target dump from SOURCE_TARGET"
364
+
365
+ method_option :just_restore, default: false, desc: "Just do restore without post-actions", type: :boolean
366
+
367
+ def from_dump
368
+ invoke "warn", [], from: options[:from]
369
+ source = lookup_heroku(options[:from])
370
+ rails_env = ENV["RAILS_ENV"] || "development"
371
+ db_config = HerokuRails::DbConfiguration.new.config[rails_env]
372
+ db_username = db_config["username"]
373
+ db = db_config["database"]
374
+ db_username_params = db_username.blank? && "" || "-U #{db_username}"
375
+ puts_and_system "pg_restore --verbose --clean --no-acl --no-owner -h localhost #{db_username_params} -d #{db} #{source.dump_filename}"
376
+ Configuration.after_sync_down(self) unless options[:just_restore]
377
+ end
378
+
379
+ desc "dump_to_tmp", "dump to tmp directory"
380
+ method_option(:from, type: :string, default: "local", desc: "heroku target (defaults to local)", required: false, aliases: "f")
381
+ def dump_to_tmp
382
+ source = lookup_heroku(options[:from])
383
+ dump_local(source.dump_filename)
384
+ end
385
+
386
+ desc "to STAGING_TARGET --from=SOURCE_TARGET", "push db onto STAGING_TARGET from SOURCE_TARGET"
387
+
388
+ def to(to_target_name)
389
+ target = lookup_heroku_staging(to_target_name)
390
+ source = lookup_heroku(options[:from])
391
+
392
+ maintenance_on(target)
393
+
394
+ puts_and_system %(
395
+ heroku pg:copy #{source.heroku_app}::#{source.db_color} #{target.db_color} -a #{target.heroku_app} --confirm #{target.heroku_app}
396
+ )
397
+ Configuration.after_sync_to(self, target) unless options[:just_copy]
398
+ puts_and_system %(heroku restart -a #{target.heroku_app})
399
+ maintenance_off(target)
400
+ end
401
+
402
+ private
403
+
404
+ def dump_local(dumpfilepath)
405
+ puts "dumping postgres to #{dumpfilepath}"
406
+ rails_env = ENV["RAILS_ENV"] || "development"
407
+ db_config = HerokuRails::DbConfiguration.new.config[rails_env]
408
+ db_username = db_config["username"]
409
+ db = db_config["database"]
410
+ system_with_clean_env "pg_dump --verbose --clean --no-acl --no-owner -h localhost -U #{db_username} --format=c #{db} > #{dumpfilepath}"
411
+ end
412
+ end
413
+
414
+ class Db < Thor
415
+ include Shared
416
+
417
+ desc "drop_all_tables on STAGING_TARGET", "drop all tables on STAGING_TARGET"
418
+
419
+ def drop_all_tables(staging_target_name)
420
+ target = lookup_heroku_staging(staging_target_name)
421
+ generate_drop_tables_sql = `#{HerokuRails::DbConfiguration.new.generate_drop_tables_sql}`
422
+ cmd_string = %(heroku pg:psql -a #{target.heroku_app} -c "#{generate_drop_tables_sql}")
423
+ puts_and_system(cmd_string)
424
+ end
425
+
426
+ desc "anonymize STAGING_TARGET", "run anonymization scripts on STAGING_TARGET"
427
+
428
+ def anonymize(staging_target_name)
429
+ target = lookup_heroku_staging(staging_target_name)
430
+ puts_and_system %(
431
+ heroku run rake data:anonymize -a #{target.heroku_app}
432
+ )
433
+ end
434
+ end
435
+ end
@@ -0,0 +1,33 @@
1
+ module HerokuRails
2
+ module ThorUtils
3
+ protected
4
+
5
+ def puts_and_system(cmd)
6
+ puts cmd
7
+ puts "-------------"
8
+ system_with_clean_env cmd
9
+ puts "-------------"
10
+ end
11
+
12
+ def puts_and_exec(cmd)
13
+ puts cmd
14
+ exec_with_clean_env(cmd)
15
+ end
16
+
17
+ def system_with_clean_env(cmd)
18
+ if defined?(Bundler)
19
+ Bundler.with_clean_env { system cmd }
20
+ else
21
+ system cmd
22
+ end
23
+ end
24
+
25
+ def exec_with_clean_env(cmd)
26
+ if defined?(Bundler)
27
+ Bundler.with_clean_env { `#{cmd}` }
28
+ else
29
+ `#{cmd}`
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module HerokuRails
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
4
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5
+
6
+ require_relative "config/application"
7
+
8
+ Rails.application.load_tasks
9
+
10
+ load "vendor/heroku_rails/lib/tasks/db_drop_all_tables.rake"
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ spec = Gem::Specification.find_by_name "heroku_rails"
5
+ Thor::Util.load_thorfile(File.expand_path("lib/heroku_rails/tasks/heroku.thor", spec.gem_dir))
6
+
7
+ class Heroku
8
+ module MyConfig
9
+ # def notify_of_deploy_tracking(running_thor_task, release_stage:, revision:, revision_describe:, repository:, target:, target_name:, deploy_ref:)
10
+ # end
11
+
12
+ # def app_revision_env_var
13
+ # "APP_REVISION"
14
+ # end
15
+ #
16
+ # def after_sync_down(instance)
17
+ # super
18
+ # instance.puts_and_system "rake dev:dev_data"
19
+ # end
20
+ #
21
+ # def after_sync_to(instance, target)
22
+ # super
23
+ # instance.puts_and_system %(heroku run rake dev:staging_data -a #{target.heroku_app})
24
+ # end
25
+ #
26
+ # def before_deploying(instance, target, version)
27
+ # # override
28
+ # end
29
+ #
30
+ # def after_deploying(instance, target, version)
31
+ # # override
32
+ # end
33
+ end
34
+
35
+ module Configuration
36
+ class << self
37
+ prepend MyConfig
38
+ end
39
+ end
40
+
41
+ # desc "set_message TARGET (MESSAGE)", "sets a MESSAGE to display on the TARGET server. If you give no MESSAGE, it will clear the message"
42
+ #
43
+ # def set_message(target_name, message = nil)
44
+ # target = lookup_heroku(target_name)
45
+ # if message
46
+ # puts_and_system "heroku run rake data:util:set_message[\"#{message}\"] -a #{target.heroku_app}"
47
+ # else
48
+ # puts_and_system "heroku run rake data:util:set_message -a #{target.heroku_app}"
49
+ # end
50
+ # end
51
+ end
@@ -0,0 +1,21 @@
1
+ # find `database_url` by running `heroku pg:info --app=APP_NAME`
2
+ _defaults:
3
+ repository: https://github.com/siapbantu/heroku_rails
4
+ production:
5
+ heroku_app : some-heroku-production
6
+ git_remote : heroku-production
7
+ deploy_ref : origin/master
8
+ display_name : production (heroku_rails)
9
+ demo:
10
+ heroku_app : some-heroku-demo
11
+ git_remote : heroku-demo
12
+ deploy_ref : HEAD
13
+ display_name : demo (heroku_rails)
14
+ trackable_release_stage : demo
15
+ staging : true
16
+ staging:
17
+ heroku_app : some-heroku-staging
18
+ git_remote : heroku-staging
19
+ deploy_ref : HEAD
20
+ display_name : staging (heroku_rails)
21
+ staging : true
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heroku_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ahmad Ainul Rizki
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-07-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.18'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.18'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.17'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.17'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 12.3.3
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 12.3.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: standard
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.4.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.4.1
97
+ description:
98
+ email:
99
+ - human@shitiomatic.email
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - CODE_OF_CONDUCT.md
108
+ - Gemfile
109
+ - LICENSE
110
+ - README.md
111
+ - Rakefile
112
+ - bin/console
113
+ - bin/setup
114
+ - heroku_rails.gemspec
115
+ - lib/heroku_rails.rb
116
+ - lib/heroku_rails/db_configuration.rb
117
+ - lib/heroku_rails/heroku_targets.rb
118
+ - lib/heroku_rails/tasks/db_drop_all_tables.rake
119
+ - lib/heroku_rails/tasks/heroku.thor
120
+ - lib/heroku_rails/thor_utils.rb
121
+ - lib/heroku_rails/version.rb
122
+ - templates/Rakefile
123
+ - templates/heroku.thor
124
+ - templates/heroku_targets.yml
125
+ homepage: https://github.com/siapbantu/heroku_rails
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubygems_version: 3.1.2
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: Useless tool for easy deploy Ruby on Rails to Heroku
148
+ test_files: []