heroku_tool 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a4a4df4ad020a5fcd4575232f8654db71c229fc14a1b68d502ee86a43225ced7
4
+ data.tar.gz: 6c6bf214ab072c875b5c5d80d79b2021f2483f7c1c4a0fe16363a033d2e6ac7a
5
+ SHA512:
6
+ metadata.gz: ef2ccc7731972da7546f744c90bfee348fcba8ed56d46a25683f0a6ab9306839f4778da7abe573e6905e949b105f96f4a834c79acb1f6ad6655c59cbc9824e4c
7
+ data.tar.gz: 06f6742e8c5567555db6484da17b4433c3abe95d6f168603982a66e11c83216f6cf523eed85d3da5209402058dcff97a52615527a11a44c29549c98419064b4f
data/.gitignore ADDED
@@ -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
data/.travis.yml ADDED
@@ -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
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## v0.4.0
4
+
5
+ Dropped thor < 1.0
6
+
7
+ ## v0.3.0
8
+
9
+ Works with thor <= 1.0
10
+
11
+ ## v0.2.0
12
+
13
+ Now assumes a default of main as the main branch.
14
+ Works after heroku git has had its repo reset (e.g. after switching from master to main, see https://help.heroku.com/O0EXQZTA/how-do-i-switch-branches-from-master-to-main).
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at tim@red56.uk. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
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_tool.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Tim Diggins
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # HerokuTool
2
+
3
+ Tool for configurable one-shot deployment and db managment with heroku and rails
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_tool'
13
+ ```
14
+
15
+ (you can add it to development/test only if you want)
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install heroku_tool
24
+
25
+ ## Configuration
26
+
27
+ > TODO: Some of these manual configuration steps, would be nicer to make a bit more automatic perhaps by making this a Rails engine.
28
+
29
+ 1) Your database configuration (config/database.yml) needs to have a username if you want to use the db configurations
30
+
31
+ 2) Append `load "heroku_tool/tasks/db_drop_all_tables.rake"` to the end of Rakefile (see lib/templates/Rakefile if you are adding this gem in development/test only)
32
+
33
+ 3) Copy templates into codebase:
34
+
35
+ cp $(bundle show heroku_tool)/templates/heroku.thor ./lib/tasks
36
+ cp $(bundle show heroku_tool)/templates/heroku_targets.yml ./config
37
+
38
+ 4) update heroku_targets.yml with your staging and production targets.
39
+ My set up for this is to have staging deploy the local version, but production
40
+ deploy the origin/main.
41
+
42
+ NB: master vs main
43
+ heroku-tool (as of 0.2.0) assumes the new current standard of main as the main branch, but if you're on the older standard of "master" then adjust your deploy refs to origin/master and the heroku_target_ref: in defaults to be ref/heads/master
44
+
45
+ > TODO: more detail
46
+
47
+ 5) You may want to set up a smoke test that your heroku targets are valid
48
+
49
+ require "heroku_tool/heroku_targets"
50
+
51
+ RSpec.describe "heroku_targets.yml" do
52
+ it "is valid (smoke test)" do
53
+ HerokuTool::HerokuTargets.from_file(Rails.root.join("config/heroku_targets.yml"))
54
+ end
55
+ end
56
+
57
+ ## Usage
58
+
59
+ ### Deploy
60
+
61
+ Deploy the latest with db migrate *(but see below) during maintenance
62
+
63
+ thor heroku:deploy staging
64
+
65
+ or without maintenance*
66
+
67
+ thor heroku:deploy staging --no-maintenance
68
+
69
+ or without migrating*
70
+
71
+ thor heroku:deploy staging --no-migrate
72
+
73
+ or a specific tag/branch
74
+
75
+ thor heroku:deploy staging hotfix-branch
76
+
77
+ *Note on db:migrate:
78
+
79
+ It is very possible to run migrations as part of the heroku release phase:
80
+ * https://mentalized.net/journal/2017/04/22/run-rails-migrations-on-heroku-deploy/
81
+ * https://devcenter.heroku.com/articles/release-phase#design-considerations
82
+
83
+ In this case you should set `migrate_in_release_phase: true` in your defaults (see templates/heroku_targets.yml)
84
+ and you won't run migrate as a separate step.
85
+
86
+ ### Sync
87
+
88
+ Sync a database down from remote to local
89
+
90
+ thor heroku:sync:grab -f staging
91
+ # when it finishes
92
+ rake db:drop_all_tables && thor heroku:sync:from_dump -f staging
93
+
94
+ _FYI rake db:drop_all_tables is handy as it doesn't require you to disconnect any running processes from the database._
95
+
96
+ Copy production db to staging
97
+
98
+ thor heroku:sync:to staging -f production
99
+
100
+ NB: this won't work from a staging to a production environment (failsafe)
101
+
102
+
103
+ ## Development
104
+
105
+ 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.
106
+
107
+ 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).
108
+
109
+ Ensure standards before PR:
110
+
111
+ bundle exec standardrb --fix
112
+
113
+ ## Contributing
114
+
115
+ Bug reports and pull requests are welcome on GitHub at https://github.com/red56/heroku_tool. 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.
116
+
117
+ ## License
118
+
119
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
120
+
121
+ ## Code of Conduct
122
+
123
+ Everyone interacting in the HerokuTool project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/red56/heroku_tool/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -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
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "heroku_tool"
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__)
data/bin/setup ADDED
@@ -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,30 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "heroku_tool/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "heroku_tool"
7
+ spec.version = HerokuTool::VERSION
8
+ spec.authors = ["Tim Diggins"]
9
+ spec.email = ["tim@red56.uk"]
10
+
11
+ spec.summary = "Tool for configurable one-shot deployment and db managment with heroku and rails"
12
+ spec.homepage = "https://github.com/red56/heroku_tool"
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", "~> 1.0"
25
+ spec.add_dependency "activesupport"
26
+
27
+ spec.add_development_dependency "rake", ">= 12.3.3"
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ spec.add_development_dependency "standard", "~> 0.4.1"
30
+ end
@@ -0,0 +1,33 @@
1
+ # NB need to specify a username in the database.yml if you want to use any of these commands
2
+ module HerokuTool
3
+ class DbConfiguration
4
+ def config
5
+ db_config_from_file = ERB.new(File.read("config/database.yml")).result
6
+ @config ||= YAML.safe_load(db_config_from_file, [], [], true)
7
+ end
8
+
9
+ def generate_drop_tables_sql
10
+ sql = %(select 'DROP TABLE IF EXISTS \\"' || tablename || '\\" CASCADE;' from pg_tables where schemaname = 'public')
11
+ %(psql #{user_arg} #{database} -t -c "#{sql}")
12
+ end
13
+
14
+ def user_arg
15
+ username = db_config["username"]
16
+ username.present? && "-U #{username}" || ""
17
+ end
18
+
19
+ def database
20
+ db_config["database"]
21
+ end
22
+
23
+ private
24
+
25
+ def db_config
26
+ config[env]
27
+ end
28
+
29
+ def env
30
+ ENV["RAILS_ENV"] || "development"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/all"
4
+ require "yaml"
5
+
6
+ module HerokuTool
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 heroku_target_ref
99
+ @values[:heroku_target_ref] || "refs/heads/main"
100
+ end
101
+
102
+ def migrate_in_release_phase
103
+ @values[:migrate_in_release_phase]
104
+ end
105
+
106
+ def to_s
107
+ display_name
108
+ end
109
+
110
+ def dump_filename
111
+ File.expand_path("tmp/latest_#{heroku_app}_backup.dump")
112
+ end
113
+
114
+ def trackable_release_stage
115
+ @values[:trackable_release_stage].presence || (staging? ? "staging" : "production")
116
+ end
117
+ end
118
+
119
+ class LocalProxy < HerokuTarget
120
+ def initialize(defaults)
121
+ super(defaults.merge(staging: true, heroku_app: "local"), "local")
122
+ end
123
+
124
+ def local?
125
+ true
126
+ end
127
+ end
128
+ end
129
+ 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("../../db_configuration.rb", __FILE__)
7
+ abort("Don't run this on production") if Rails.env.production?
8
+
9
+ db_config = HerokuTool::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,459 @@
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 platform_maintenance_urls(asset_host)
12
+ time = Time.now.strftime("%Y%m%d-%H%M-%S")
13
+ {
14
+ ERROR_PAGE_URL: "https://#{asset_host}/platform_error/#{time}",
15
+ MAINTENANCE_PAGE_URL: "https://#{asset_host}/platform_maintenance/#{time}"
16
+ }
17
+ end
18
+
19
+ def maintenance_mode_env_var
20
+ "X_HEROKU_TOOL_MAINTENANCE_MODE"
21
+ end
22
+
23
+ def app_revision_env_var
24
+ raise "choose between APP_REVISION and COMMIT_HASH"
25
+ end
26
+
27
+ def before_deploying(_instance, target, version, description: nil)
28
+ puts "about to deploy #{version} to #{target.name}"
29
+ puts " #{description}" if description
30
+ end
31
+
32
+ def after_deploying(_instance, target, version, description: nil)
33
+ puts "deployed #{version} to #{target.name}"
34
+ puts " #{description}" if description
35
+ end
36
+
37
+ def notify_of_deploy_tracking(running_thor_task, release_stage:, revision:, revision_describe:, repository:, target:, target_name:, deploy_ref:)
38
+ if ENV["BUGSNAG_API_KEY"].present?
39
+ running_thor_task.notify_bugsnag_of_deploy_tracking(deploy_ref, release_stage, repository, revision, revision_describe, target_name)
40
+ else
41
+ puts "can't notify of deploy tracking: env var not present: BUGSNAG_API_KEY"
42
+ end
43
+ end
44
+
45
+ def after_sync_down(instance)
46
+ # could add source ?
47
+ instance.puts_and_system "rake db:migrate"
48
+ instance.puts_and_system "rake db:test:prepare"
49
+ end
50
+
51
+ def after_sync_to(instance, target)
52
+ # could add source ?
53
+ instance.puts_and_system %(heroku run rake db:migrate -a #{target.heroku_app})
54
+ end
55
+ end
56
+ end
57
+
58
+ module Shared
59
+ attr_accessor :implied_source, :implied_target
60
+
61
+ include HerokuTool::ThorUtils
62
+
63
+ def self.exit_on_failure?
64
+ true
65
+ end
66
+
67
+ def lookup_heroku_staging(staging_target_name)
68
+ heroku_targets.staging_targets[staging_target_name] || raise_missing_target(staging_target_name, true)
69
+ end
70
+
71
+ def lookup_heroku(target_name)
72
+ heroku_targets.targets[target_name] || raise_missing_target(target_name, false)
73
+ end
74
+
75
+ def check_deploy_ref(deploy_ref, target = implied_target)
76
+ if deploy_ref && deploy_ref[0] == "-"
77
+ raise Thor::Error, "Invalid deploy ref '#{deploy_ref}'"
78
+ end
79
+
80
+ deploy_ref || target.deploy_ref
81
+ end
82
+
83
+ def raise_missing_target(target_name, staging)
84
+ if staging
85
+ description = "Staging target_name '#{target_name}'"
86
+ targets = heroku_targets.staging_targets.keys
87
+ else
88
+ description = "Target '#{target_name}'"
89
+ targets = heroku_targets.targets.keys
90
+ end
91
+ msg = "#{description} was not found. Valid targets are #{targets.collect { |t| "'#{t}'" }.join(",")}"
92
+ raise Thor::Error, msg
93
+ end
94
+
95
+ def heroku_targets
96
+ @heroku_targets ||= HerokuTool::HerokuTargets.from_file(File.expand_path("config/heroku_targets.yml"))
97
+ end
98
+
99
+ protected
100
+
101
+ def deploy_message(deploy_ref_describe, target = implied_target)
102
+ downtime = migrate_outside_of_release_phase?(target) ? "👷 There will be a very short maintenance downtime" : ""
103
+ message = <<-DEPLOY_MESSAGE
104
+ Deploying #{target.display_name} #{deploy_ref_describe}.
105
+ #{downtime} (in less than a minute from now).
106
+ DEPLOY_MESSAGE
107
+ message.gsub(/(\s|\n)+/, " ")
108
+ end
109
+
110
+ def migrate_outside_of_release_phase?(target = implied_target)
111
+ target.migrate_in_release_phase ? false : options[:migrate]
112
+ end
113
+
114
+ def maintenance_on(target = implied_target)
115
+ puts_and_system "heroku maintenance:on -a #{target.heroku_app}"
116
+ puts_and_system "heroku config:set #{Heroku::Configuration.maintenance_mode_env_var}=true -a #{target.heroku_app}"
117
+ end
118
+
119
+ def maintenance_off(target = implied_target)
120
+ puts_and_system "heroku maintenance:off -a #{target.heroku_app}"
121
+ puts_and_system "heroku config:unset #{Heroku::Configuration.maintenance_mode_env_var} -a #{target.heroku_app}"
122
+ end
123
+ end
124
+
125
+ include Shared
126
+
127
+ class_option :verbose, type: :boolean, aliases: "v", default: true
128
+ default_command :help
129
+
130
+ desc "configs", "collects configs as text files in tmp"
131
+
132
+ def configs
133
+ remote_targets = heroku_targets.targets.reject { |_name, target| target.local? }
134
+ remote_targets.each_with_index do |(_name, target), index|
135
+ print_output_progress(remote_targets, index)
136
+ cmd = "heroku config -s -a #{target.heroku_app} > tmp/config.#{target.heroku_app}.txt"
137
+ exec_with_clean_env(cmd)
138
+ end
139
+ print_output_progress(remote_targets)
140
+ puts ""
141
+ end
142
+
143
+ desc "details", "collects and prints some information local and each target"
144
+
145
+ def details
146
+ puts
147
+ details = heroku_targets.targets.map { |name, target|
148
+ next if target.local?
149
+
150
+ print "."
151
+ [
152
+ "Heroku #{name}",
153
+ "(versions suppressed -- takes too long)" || exec_with_clean_env("heroku run 'rails -v && ruby -v' -a #{target.heroku_app}"),
154
+ exec_with_clean_env("heroku releases -n 1 -a #{target.heroku_app}").split("\n").last
155
+ ]
156
+ }
157
+ puts
158
+ details << [
159
+ "Local",
160
+ exec_with_clean_env("rails -v && ruby -v"),
161
+ exec_with_clean_env("git describe --always")
162
+ ]
163
+ details.each do |n, v, d|
164
+ puts "-" * 80
165
+ puts n
166
+ puts "-" * 80
167
+ puts v
168
+ puts d
169
+ end
170
+ end
171
+
172
+ desc "deploy TARGET (REF)", "deploy the latest to TARGET (optionally give a REF like a tag to deploy)"
173
+ method_option :migrate, default: true, desc: "Run with migrations (unless part of release phase)", type: :boolean
174
+ method_option :maintenance, default: nil, desc: "Maintenance step", type: :boolean
175
+
176
+ def deploy(target_name, deploy_ref = nil)
177
+ self.implied_target = lookup_heroku(target_name)
178
+ deploy_ref = check_deploy_ref(deploy_ref, implied_target)
179
+ deploy_ref_description = deploy_ref_describe(deploy_ref)
180
+ maintenance = options[:maintenance].nil? && migrate_outside_of_release_phase?(implied_target) || options[:maintenance] || false
181
+ puts "Deploy #{deploy_ref_description} to #{implied_target} with migrate=#{implied_target.migrate_in_release_phase ? "(during release phase)" : migrate_outside_of_release_phase?(implied_target)} maintenance=#{maintenance} "
182
+
183
+ invoke :list_deployed, [target_name, deploy_ref], {}
184
+ message = deploy_message(deploy_ref_description)
185
+ Configuration.before_deploying(self, implied_target, deploy_ref_description)
186
+ set_message(target_name, message)
187
+ puts_and_system "git push -f #{implied_target.git_remote} #{deploy_ref}^{}:#{implied_target.heroku_target_ref}"
188
+
189
+ maintenance_on if maintenance
190
+ if migrate_outside_of_release_phase?
191
+ puts_and_system "heroku run rake db:migrate -a #{implied_target.heroku_app}"
192
+ end
193
+
194
+ puts_and_system %{heroku config:set #{Heroku::Configuration.app_revision_env_var}=$(git describe --always #{deploy_ref}) -a #{implied_target.heroku_app}}
195
+ if maintenance
196
+ maintenance_off
197
+ else
198
+ puts_and_system "heroku restart -a #{implied_target.heroku_app}"
199
+ end
200
+ set_message(target_name, nil)
201
+ Configuration.after_deploying(self, implied_target, deploy_ref_description)
202
+ deploy_tracking(target_name, deploy_ref)
203
+ end
204
+
205
+ desc "maintenance ON|OFF", "turn maintenance mode on or off"
206
+ method_option :target_name, aliases: "a", desc: "Target (app or remote)"
207
+
208
+ def maintenance(on_or_off)
209
+ self.implied_target = lookup_heroku(options[:target_name])
210
+ case on_or_off.upcase
211
+ when "ON"
212
+ maintenance_on
213
+ when "OFF"
214
+ maintenance_off
215
+ else
216
+ raise Thor::Error, "maintenance must be ON or OFF not #{on_or_off}"
217
+ end
218
+ end
219
+
220
+ desc "set_urls TARGET", "set and cache the error and maintenance page urls for TARGET"
221
+
222
+ def set_urls(target_name)
223
+ self.implied_target = lookup_heroku(target_name)
224
+ unless asset_host.presence
225
+ puts "asset host (ASSET_HOST) not found on #{implied_target.heroku_app}"
226
+ return
227
+ end
228
+ url_hash = Configuration.platform_maintenance_urls(asset_host)
229
+ url_hash.each do |_env, url|
230
+ puts_and_system "open #{url}"
231
+ end
232
+ puts_and_system(
233
+ "heroku config:set #{url_hash.map { |e, u| "#{e}=#{u}" }.join(" ")} -a #{implied_target.heroku_app}"
234
+ )
235
+ end
236
+
237
+ no_commands do
238
+ def asset_host(target = implied_target)
239
+ if target == implied_target
240
+ @asset_host ||= fetch_asset_host(target)
241
+ else
242
+ fetch_asset_host(target)
243
+ end
244
+ end
245
+
246
+ def get_config_env(target, env_var)
247
+ puts_and_exec("heroku config:get #{env_var} -a #{target.heroku_app}").strip.presence
248
+ end
249
+
250
+ def deploy_ref_describe(deploy_ref)
251
+ `git describe #{deploy_ref}`.strip
252
+ end
253
+
254
+ def notify_bugsnag_of_deploy_tracking(deploy_ref, release_stage, repository, revision, revision_describe, target_name)
255
+ api_key = ENV["BUGSNAG_API_KEY"]
256
+ data = %W[
257
+ apiKey=#{api_key}
258
+ releaseStage=#{release_stage}
259
+ repository=#{repository}
260
+ revision=#{revision}
261
+ appVersion=#{revision_describe}
262
+ ].join("&")
263
+ if api_key.blank?
264
+ puts "\n" + ("*" * 80) + "\n"
265
+ command = "curl -d #{data} http://notify.bugsnag.com/deploy"
266
+ puts command
267
+ puts "\n" + ("*" * 80) + "\n"
268
+ puts "NB: can't notify unless you specify BUGSNAG_API_KEY and rerun"
269
+ puts " thor heroku:deploy_tracking #{target_name} #{deploy_ref}"
270
+ else
271
+ puts_and_system "curl -d \"#{data}\" http://notify.bugsnag.com/deploy"
272
+ end
273
+ end
274
+ end
275
+
276
+ desc "deploy_tracking TARGET (REF)", "set deploy tracking for TARGET and REF (used by deploy)"
277
+
278
+ def deploy_tracking(target_name, deploy_ref = nil)
279
+ self.implied_target = lookup_heroku(target_name)
280
+ deploy_ref = check_deploy_ref(deploy_ref)
281
+ revision = `git log -1 #{deploy_ref} --pretty=format:%H`
282
+ Heroku::Configuration.notify_of_deploy_tracking(
283
+ self,
284
+ deploy_ref: deploy_ref,
285
+ release_stage: implied_target.trackable_release_stage,
286
+ revision: revision,
287
+ target: implied_target,
288
+ target_name: target_name,
289
+ revision_describe: deploy_ref_describe(deploy_ref),
290
+ repository: implied_target.repository
291
+ )
292
+ end
293
+
294
+ include HerokuTool::ThorUtils
295
+ desc "set_message TARGET (MESSAGE)", "set message (no-op by default)"
296
+
297
+ def set_message(target_name, message = nil)
298
+ # no-op -- define as override
299
+ end
300
+
301
+ desc "list_deployed TARGET (DEPLOY_REF)", "list what would be deployed to TARGET (optionally specify deploy_ref)"
302
+
303
+ def list_deployed(target_name, deploy_ref = nil)
304
+ self.implied_target = lookup_heroku(target_name)
305
+ deploy_ref = check_deploy_ref(deploy_ref)
306
+ puts "------------------------------"
307
+ puts " Deploy to #{implied_target}:"
308
+ puts "------------------------------"
309
+ system_with_clean_env "git --no-pager log $(heroku config:get #{Heroku::Configuration.app_revision_env_var} -a #{implied_target.heroku_app})..#{deploy_ref}"
310
+ puts "------------------------------"
311
+ end
312
+
313
+ desc "about (TARGET)", "Describe available targets or one specific target"
314
+
315
+ def about(target_name = nil)
316
+ if target_name.nil?
317
+ puts "Targets: "
318
+ heroku_targets.targets.each_pair do |key, target|
319
+ puts " * #{key} (#{target})"
320
+ end
321
+ else
322
+ self.implied_target = lookup_heroku(target_name)
323
+ puts "Target #{target_name}:"
324
+ puts " * display_name: #{implied_target.display_name}"
325
+ puts " * heroku_app: #{implied_target.heroku_app}"
326
+ puts " * git_remote: #{implied_target.git_remote}"
327
+ puts " * deploy_ref: #{implied_target.deploy_ref}"
328
+ end
329
+ puts
330
+ puts "(defined in config/heroku_targets.yml)"
331
+ end
332
+
333
+ class Sync < Thor
334
+ include Shared
335
+ class_option :from, type: :string, desc: "source target (production, staging...)", required: true, aliases: "f"
336
+
337
+ desc "down --from SOURCE_TARGET", "syncs db down from SOURCE_TARGET | thor heroku:sync -f production"
338
+
339
+ def down
340
+ invoke "grab", [], from: options[:from]
341
+ invoke "from_dump", [], from: options[:from]
342
+ end
343
+
344
+ desc "warn", "warn", hide: true
345
+
346
+ def warn
347
+ puts "should maybe 'rake db:drop_all_tables' first"
348
+ puts "if you have done some table-creating migrations that need tobe undone???"
349
+ end
350
+
351
+ desc "capture --from SOURCE_TARGET", "capture a backup (remotely) from SOURCE_TARGET", hide: true
352
+
353
+ def capture
354
+ source = lookup_heroku(options[:from])
355
+ capture_cmd = "heroku pg:backups:capture -a #{source.heroku_app}"
356
+ puts_and_system capture_cmd
357
+ end
358
+
359
+ desc "grab --from SOURCE_TARGET", "capture and download dump from SOURCE_TARGET", hide: true
360
+
361
+ def grab
362
+ invoke "capture", [], from: options[:from]
363
+ invoke "download", [], from: options[:from]
364
+ end
365
+
366
+ desc "download --from SOURCE_TARGET", "download latest db snapshot on source_target"
367
+
368
+ def download
369
+ source = lookup_heroku(options[:from])
370
+ download_cmd = "curl -o #{source.dump_filename} `heroku pg:backups:public-url -a #{source.heroku_app}`"
371
+ puts_and_system download_cmd
372
+ end
373
+
374
+ desc "from_dump --from SOURCE_TARGET", "make the db the same as the last target dump from SOURCE_TARGET"
375
+
376
+ method_option :just_restore, default: false, desc: "Just do restore without post-actions", type: :boolean
377
+
378
+ def from_dump
379
+ invoke "warn", [], from: options[:from]
380
+ source = lookup_heroku(options[:from])
381
+ rails_env = ENV["RAILS_ENV"] || "development"
382
+ db_config = HerokuTool::DbConfiguration.new.config[rails_env]
383
+ db_username = db_config["username"]
384
+ db = db_config["database"]
385
+ db_username_params = db_username.blank? && "" || "-U #{db_username}"
386
+ puts_and_system "pg_restore --verbose --clean --no-acl --no-owner -h localhost #{db_username_params} -d #{db} #{source.dump_filename}"
387
+ Configuration.after_sync_down(self) unless options[:just_restore]
388
+ end
389
+
390
+ desc "dump_to_tmp", "dump to tmp directory"
391
+ method_option(:from, type: :string, default: "local", desc: "heroku target (defaults to local)", required: false, aliases: "f")
392
+
393
+ def dump_to_tmp
394
+ source = lookup_heroku(options[:from])
395
+ dump_local(source.dump_filename)
396
+ end
397
+
398
+ desc "to STAGING_TARGET --from=SOURCE_TARGET", "push db onto STAGING_TARGET from SOURCE_TARGET"
399
+
400
+ def to(to_target_name)
401
+ self.implied_target = lookup_heroku_staging(to_target_name)
402
+ self.implied_source = lookup_heroku(options[:from])
403
+
404
+ maintenance_on # target, not source
405
+
406
+ puts_and_system %(
407
+ heroku pg:copy #{implied_source.heroku_app}::#{implied_source.db_color} #{implied_target.db_color} -a #{implied_target.heroku_app} --confirm #{implied_target.heroku_app}
408
+ )
409
+ Configuration.after_sync_to(self, implied_target) unless options[:just_copy]
410
+ puts_and_system %(heroku restart -a #{implied_target.heroku_app})
411
+ maintenance_off
412
+ end
413
+
414
+ private
415
+
416
+ def dump_local(dumpfilepath)
417
+ puts "dumping postgres to #{dumpfilepath}"
418
+ rails_env = ENV["RAILS_ENV"] || "development"
419
+ db_config = HerokuTool::DbConfiguration.new.config[rails_env]
420
+ db_username = db_config["username"]
421
+ db = db_config["database"]
422
+ system_with_clean_env "pg_dump --verbose --clean --no-acl --no-owner -h localhost -U #{db_username} --format=c #{db} > #{dumpfilepath}"
423
+ end
424
+ end
425
+
426
+ class Db < Thor
427
+ include Shared
428
+
429
+ desc "drop_all_tables on STAGING_TARGET", "drop all tables on STAGING_TARGET"
430
+
431
+ def drop_all_tables(staging_target_name)
432
+ self.implied_target = lookup_heroku_staging(staging_target_name)
433
+ generate_drop_tables_sql = `#{HerokuTool::DbConfiguration.new.generate_drop_tables_sql}`
434
+ cmd_string = %(heroku pg:psql -a #{implied_target.heroku_app} -c "#{generate_drop_tables_sql}")
435
+ puts_and_system(cmd_string)
436
+ end
437
+
438
+ desc "anonymize STAGING_TARGET", "run anonymization scripts on STAGING_TARGET"
439
+
440
+ def anonymize(staging_target_name)
441
+ self.implied_target = lookup_heroku_staging(staging_target_name)
442
+ puts_and_system %(
443
+ heroku run rake data:anonymize -a #{implied_target.heroku_app}
444
+ )
445
+ end
446
+ end
447
+
448
+ private
449
+
450
+ def fetch_asset_host(target)
451
+ get_config_env(target, "ASSET_HOST")
452
+ end
453
+
454
+ def print_output_progress(remote_targets, index = nil)
455
+ index ||= remote_targets.length
456
+ remainder = remote_targets.length - index
457
+ print "\routputting configs to tmp/config.*.txt: #{index}/#{remote_targets.count} ▕#{'██' * index}#{" " * remainder}▏\r"
458
+ end
459
+ end
@@ -0,0 +1,37 @@
1
+ module HerokuTool
2
+ module ThorUtils
3
+ def puts_and_system(cmd)
4
+ puts cmd
5
+ puts "-------------"
6
+ system_with_clean_env cmd
7
+ puts "-------------"
8
+ end
9
+
10
+ protected
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) && Bundler.respond_to?(:with_unbundled_env)
19
+ Bundler.with_unbundled_env { system cmd }
20
+ elsif defined?(Bundler)
21
+ Bundler.with_clean_env { system cmd }
22
+ else
23
+ system cmd
24
+ end
25
+ end
26
+
27
+ def exec_with_clean_env(cmd)
28
+ if defined?(Bundler) && Bundler.respond_to?(:with_unbundled_env)
29
+ Bundler.with_unbundled_env { `#{cmd}` }
30
+ elsif defined?(Bundler)
31
+ Bundler.with_clean_env { `#{cmd}` }
32
+ else
33
+ `#{cmd}`
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module HerokuTool
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "heroku_tool/db_configuration"
2
+ require "heroku_tool/heroku_targets"
3
+ require "heroku_tool/version"
4
+
5
+ module HerokuTool
6
+ class Error < StandardError; end
7
+ end
@@ -0,0 +1,15 @@
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
+ begin
11
+ spec = Gem::Specification.find_by_name "heroku_tool"
12
+ load File.expand_path("lib/heroku_tool/tasks/db_drop_all_tables.rake", spec.gem_dir)
13
+ rescue LoadError
14
+ # not loading db_drop_all_tables -- not defined in this environment?
15
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "thor"
5
+ spec = Gem::Specification.find_by_name "heroku_tool"
6
+ Thor::Util.load_thorfile(File.expand_path("lib/heroku_tool/tasks/heroku.thor", spec.gem_dir))
7
+ rescue LoadError, StandardError
8
+ puts "Not loading HerokuTool"
9
+ end
10
+
11
+ # respecifying Heroku tasks
12
+ class Heroku
13
+ module MyConfig
14
+ include HerokuTool::ThorUtils if defined?(HerokuTool)
15
+
16
+ def platform_maintenance_urls(asset_host)
17
+ time = Time.now.strftime("%Y%m%d-%H%M-%S")
18
+ {
19
+ ERROR_PAGE_URL: "https://#{asset_host}/platform_error/#{time}",
20
+ MAINTENANCE_PAGE_URL: "https://#{asset_host}/platform_maintenance/#{time}"
21
+ }
22
+ end
23
+
24
+ def maintenance_mode_env_var
25
+ "X_HEROKU_TOOL_MAINTENANCE_MODE"
26
+ end
27
+
28
+ # def notify_of_deploy_tracking(running_thor_task, release_stage:, revision:, revision_describe:, repository:, target:, target_name:, deploy_ref:)
29
+ # end
30
+
31
+ # def app_revision_env_var
32
+ # "APP_REVISION"
33
+ # end
34
+ #
35
+ # def after_sync_down(instance)
36
+ # super
37
+ # instance.puts_and_system "rake dev:dev_data"
38
+ # end
39
+ #
40
+ # def after_sync_to(instance, target)
41
+ # super
42
+ # instance.puts_and_system %(heroku run rake dev:staging_data -a #{target.heroku_app})
43
+ # end
44
+ #
45
+ # def before_deploying(instance, target, version)
46
+ # # override
47
+ # end
48
+ #
49
+ # def after_deploying(instance, target, version)
50
+ # # override
51
+ # end
52
+ end
53
+
54
+ module Configuration
55
+ class << self
56
+ prepend MyConfig
57
+ end
58
+ end
59
+
60
+ # desc "set_message TARGET (MESSAGE)", "sets a MESSAGE to display on the TARGET server. If you give no MESSAGE, it will clear the message"
61
+ #
62
+ # def set_message(target_name, message = nil)
63
+ # target = lookup_heroku(target_name)
64
+ # if message
65
+ # puts_and_system "heroku run rake data:util:set_message[\"#{message}\"] -a #{target.heroku_app}"
66
+ # else
67
+ # puts_and_system "heroku run rake data:util:set_message -a #{target.heroku_app}"
68
+ # end
69
+ # end
70
+ end
@@ -0,0 +1,27 @@
1
+ # find `database_url` by running `heroku pg:info --app=APP_NAME`
2
+ _defaults:
3
+ repository: https://github.com/red56/heroku_tool
4
+ # what you are deploying to -- this should either be refs/head/main (the default) or refs/head/master
5
+ heroku_target_ref: refs/heads/main
6
+ # set to true if you are doing migrate as part of the heroku release phase, otherwise leave as false, or delete
7
+ # https://mentalized.net/journal/2017/04/22/run-rails-migrations-on-heroku-deploy/
8
+ # https://devcenter.heroku.com/articles/release-phase
9
+ migrate_in_release_phase: false
10
+ production:
11
+ heroku_app : some-heroku-production
12
+ git_remote : heroku-production
13
+ deploy_ref : origin/main
14
+ display_name : production (heroku_tool)
15
+ demo:
16
+ heroku_app : some-heroku-demo
17
+ git_remote : heroku-demo
18
+ deploy_ref : HEAD
19
+ display_name : demo (heroku_tool)
20
+ trackable_release_stage : demo
21
+ staging : true
22
+ staging:
23
+ heroku_app : some-heroku-staging
24
+ git_remote : heroku-staging
25
+ deploy_ref : HEAD
26
+ display_name : staging (heroku_tool)
27
+ staging : true
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heroku_tool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Tim Diggins
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-02-06 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: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
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: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 12.3.3
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 12.3.3
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: standard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.4.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.4.1
83
+ description:
84
+ email:
85
+ - tim@red56.uk
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - CHANGELOG.md
94
+ - CODE_OF_CONDUCT.md
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - bin/console
100
+ - bin/setup
101
+ - heroku_tool.gemspec
102
+ - lib/heroku_tool.rb
103
+ - lib/heroku_tool/db_configuration.rb
104
+ - lib/heroku_tool/heroku_targets.rb
105
+ - lib/heroku_tool/tasks/db_drop_all_tables.rake
106
+ - lib/heroku_tool/tasks/heroku.thor
107
+ - lib/heroku_tool/thor_utils.rb
108
+ - lib/heroku_tool/version.rb
109
+ - templates/Rakefile
110
+ - templates/heroku.thor
111
+ - templates/heroku_targets.yml
112
+ homepage: https://github.com/red56/heroku_tool
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubygems_version: 3.1.6
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Tool for configurable one-shot deployment and db managment with heroku and
135
+ rails
136
+ test_files: []