parity_logandsprice 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5fe0758511222cfb3bedfad544400e1eadc25052cd838738ac13df827db8d5e1
4
+ data.tar.gz: 6b3fa0cd17b949d9ffa022269130be777776e6c50d31ab32707762094be405f6
5
+ SHA512:
6
+ metadata.gz: 9f78ca7854a340b404e862d127c8ff2f588d6ea93d59262d2d1a89fbb32e994423b1e0683cd94601dc15b2cfb49256aa8226c7983e09ce9fc0e75f287ea9d211
7
+ data.tar.gz: bab0ce042de415c8fed0eb2adc3e16be9204ab4bc1216469779d17fd87b404056e89b561c2978f3f8216e13def603ba38f38645b57e52f42c1f805418e0ddbb8
data/README.md ADDED
@@ -0,0 +1,198 @@
1
+ Parity
2
+ ======
3
+
4
+ [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
5
+
6
+ Shell commands for development, staging, and production parity for Heroku apps.
7
+
8
+ Install
9
+ -------
10
+
11
+ gem install parity
12
+
13
+ Or bundle it in your project:
14
+
15
+ gem "parity"
16
+
17
+ [releases]: https://github.com/thoughtbot/parity/releases
18
+
19
+ Parity requires these command-line programs:
20
+
21
+ git
22
+ heroku
23
+ pg_restore
24
+
25
+ Usage
26
+ -----
27
+
28
+ Backup a database:
29
+
30
+ production backup
31
+ staging backup
32
+
33
+ Restore a production or staging database backup into development:
34
+
35
+ development restore production
36
+ development restore staging
37
+
38
+ Or, if `restore-from` reads better to you, it's the same thing:
39
+
40
+ development restore-from production
41
+ development restore-from staging
42
+
43
+ * Note that the `restore` command will use the most recent backup (from _staging_ or _production_). You may first need to create a more recent backup before restoring, to prevent download of a very old backup.
44
+
45
+ Push your local development database backup up to staging:
46
+
47
+ staging restore development
48
+
49
+ Deploy main to production (note that prior versions of Parity would run
50
+ database migrations, that's now better handled using [Heroku release phase]):
51
+
52
+ production deploy
53
+
54
+ [Heroku release phase]: https://devcenter.heroku.com/articles/release-phase
55
+
56
+ Deploy the current branch to staging:
57
+
58
+ staging deploy
59
+
60
+ _Note that deploys to non-production environments use `git push --force`._
61
+
62
+ Open a console:
63
+
64
+ production console
65
+ staging console
66
+ pr_app 1234 console
67
+
68
+ Migrate a database and restart the dynos:
69
+
70
+ production migrate
71
+ staging migrate
72
+ pr_app 1234 migrate
73
+
74
+ Tail a log:
75
+
76
+ production tail
77
+ staging tail
78
+ pr_app 1234 tail
79
+
80
+ The scripts also pass through, so you can do anything with them that you can do
81
+ with `heroku ______ --remote staging` or `heroku ______ --remote production`:
82
+
83
+ watch production ps
84
+ staging open
85
+
86
+ You can optionally parallelize a DB restore by passing `--parallelize`
87
+ as a flag to the `development` or `production` commands:
88
+ ```
89
+ development restore-from production --parallelize
90
+ ```
91
+
92
+ [2]: http://redis.io/commands
93
+
94
+ Convention
95
+ ----------
96
+
97
+ Parity expects:
98
+
99
+ * A `staging` remote pointing to the staging Heroku app.
100
+ * A `production` remote pointing to the production Heroku app.
101
+ ```
102
+ heroku git:remote -r staging -a your-staging-app
103
+ heroku git:remote -r production -a your-production-app
104
+ ```
105
+ * There is a `config/database.yml` file that can be parsed as YAML for
106
+ `['development']['database']` or, alternatively, `['development']['primary']['database']` for multi-database configurations.
107
+
108
+ Pipelines
109
+ ---------
110
+
111
+ If you deploy review applications with Heroku pipelines, run commands against
112
+ those applications with the `pr_app` command, followed by the PR number for your
113
+ application:
114
+
115
+ ```
116
+ pr_app 1234 console
117
+ ```
118
+
119
+ This command assumes that your review applications have a name derived from the
120
+ name of the application your `staging` Git remote points at.
121
+
122
+ Customization
123
+ -------------
124
+
125
+ If you have Heroku environments beyond staging and production (such as a feature
126
+ environment for each developer), you can add a [binstub] to the `bin` folder of
127
+ your application. Custom environments share behavior with staging: they can be
128
+ backed up and can restore from production.
129
+
130
+ Using feature environments requires including Parity as a gem in your
131
+ application's Gemfile.
132
+
133
+ ```ruby
134
+ gem "parity"
135
+ ```
136
+
137
+ [binstub]: https://github.com/sstephenson/rbenv/wiki/Understanding-binstubs
138
+
139
+ Here's an example binstub for a 'feature-geoff' environment, hosted at
140
+ myapp-feature-geoff.herokuapp.com.
141
+
142
+ ```ruby
143
+ #!/usr/bin/env ruby
144
+
145
+ require "parity"
146
+
147
+ if ARGV.empty?
148
+ puts Parity::Usage.new
149
+ else
150
+ Parity::Environment.new("feature-geoff", ARGV).run
151
+ end
152
+ ```
153
+
154
+ Issues
155
+ ------
156
+ Please fill out our [issues template](.github/issue_template.md) if you are
157
+ having problems.
158
+
159
+ Contributing
160
+ ------------
161
+
162
+ Please see [`CONTRIBUTING.md`](CONTRIBUTING.md) for details.
163
+
164
+ Version History
165
+ ---------------
166
+
167
+ Please see the [releases page](https://github.com/thoughtbot/parity/releases)
168
+ for the version history, along with a description of the changes in each
169
+ release.
170
+
171
+ Releasing
172
+ ---------
173
+
174
+ See guidelines in [`RELEASING.md`](RELEASING.md) for details
175
+
176
+ License
177
+ -------
178
+
179
+ Parity is © 2013-2021 thoughtbot, inc.
180
+ It is free software,
181
+ and may be redistributed under the terms specified in the [LICENSE] file.
182
+
183
+ [LICENSE]: LICENSE
184
+
185
+ About thoughtbot
186
+ ----------------
187
+
188
+ ![thoughtbot](http://presskit.thoughtbot.com/images/thoughtbot-logo-for-readmes.svg)
189
+
190
+ Parity is maintained and funded by thoughtbot, inc.
191
+ The names and logos for thoughtbot are trademarks of thoughtbot, inc.
192
+
193
+ We are passionate about open source software.
194
+ See [our other projects][community].
195
+ We are [available for hire][hire].
196
+
197
+ [community]: https://thoughtbot.com/community?utm_source=github
198
+ [hire]: https://thoughtbot.com?utm_source=github
data/bin/development ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.join("..", "..", "lib"), __FILE__)
4
+ require "parity"
5
+
6
+ if ARGV.empty?
7
+ puts Parity::Usage.new
8
+ else
9
+ exit Parity::Environment.new('development', ARGV).run
10
+ end
data/bin/pr_app ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.join("..", "..", "lib"), __FILE__)
4
+ require "open3"
5
+ require "parity"
6
+
7
+ if ARGV.empty?
8
+ puts Parity::Usage.new
9
+ else
10
+ review_app_number = ARGV.first
11
+ staging_git_remote = Open3.capture3("git remote get-url staging")[0].strip
12
+ review_app_prefix = staging_git_remote[/(?<=https:\/\/git\.heroku\.com\/|git@heroku\.com:)(.*)/].gsub(/-staging.git/, "")
13
+
14
+ puts "*"*60
15
+ puts "review_app_prefix: #{review_app_prefix}"
16
+ puts "review_app_number: #{review_app_number}"
17
+ puts "ARGV: #{ARGV}"
18
+ puts "*"*60
19
+ exit Parity::Environment.new(
20
+ "#{review_app_prefix}-pr-#{review_app_number}",
21
+ ARGV.drop(1),
22
+ app_argument: "--app",
23
+ ).run
24
+ end
data/bin/production ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.join("..", "..", "lib"), __FILE__)
4
+ require "parity"
5
+
6
+ if ARGV.empty?
7
+ puts Parity::Usage.new
8
+ else
9
+ exit Parity::Environment.new('production', ARGV).run
10
+ end
data/bin/staging ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.join("..", "..", "lib"), __FILE__)
4
+ require "parity"
5
+
6
+ if ARGV.empty?
7
+ puts Parity::Usage.new
8
+ else
9
+ exit Parity::Environment.new('staging', ARGV).run
10
+ end
@@ -0,0 +1,147 @@
1
+ require "etc"
2
+
3
+ module Parity
4
+ class Backup
5
+ BLANK_ARGUMENTS = "".freeze
6
+ DATABASE_YML_RELATIVE_PATH = "config/database.yml".freeze
7
+ DEVELOPMENT_ENVIRONMENT_KEY_NAME = "development".freeze
8
+ PRIMARY_DATABASE_KEY_NAME = "primary".freeze
9
+ DATABASE_KEY_NAME = "database".freeze
10
+
11
+ def initialize(args)
12
+ puts "*"*60
13
+ puts args
14
+ puts "*"*60
15
+ @from, @to = args.values_at(:from, :to)
16
+ @additional_args = args[:additional_args] || BLANK_ARGUMENTS
17
+ @parallelize = args[:parallelize] || false
18
+ @app_argument = args[:app_argument]
19
+ end
20
+
21
+ def restore
22
+ if to == DEVELOPMENT_ENVIRONMENT_KEY_NAME
23
+ restore_to_development
24
+ elsif from == DEVELOPMENT_ENVIRONMENT_KEY_NAME
25
+ restore_from_development
26
+ else
27
+ restore_to_remote_environment
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :additional_args, :from, :to, :parallelize, :app_argument
34
+
35
+ alias :parallelize? :parallelize
36
+
37
+ def restore_from_development
38
+ reset_remote_database
39
+ Kernel.system(
40
+ "heroku pg:push #{development_db} DATABASE_URL #{app_argument} #{to} "\
41
+ "#{additional_args}",
42
+ )
43
+ end
44
+
45
+ def restore_to_development
46
+ ensure_temp_directory_exists
47
+ download_remote_backup
48
+ wipe_development_database
49
+ restore_from_local_temp_backup
50
+ delete_local_temp_backup
51
+ delete_rails_production_environment_settings
52
+ end
53
+
54
+ def wipe_development_database
55
+ Kernel.system(
56
+ "dropdb --if-exists #{development_db} && createdb #{development_db}",
57
+ )
58
+ end
59
+
60
+ def reset_remote_database
61
+ Kernel.system(
62
+ "heroku pg:reset #{app_argument} #{to} #{additional_args} "\
63
+ "--confirm #{heroku_app_name}",
64
+ )
65
+ end
66
+
67
+ def heroku_app_name
68
+ if app_argument == "--app"
69
+ to
70
+ else
71
+ HerokuAppName.new(to).to_s
72
+ end
73
+ end
74
+
75
+ def ensure_temp_directory_exists
76
+ Kernel.system("mkdir -p tmp")
77
+ end
78
+
79
+ def download_remote_backup
80
+ Kernel.system(
81
+ "curl -o tmp/latest.backup \"$(heroku pg:backups:url #{app_argument} #{from})\"",
82
+ )
83
+ end
84
+
85
+ def restore_from_local_temp_backup
86
+ Kernel.system(
87
+ "pg_restore tmp/latest.backup --verbose --no-acl --no-owner "\
88
+ "--dbname #{development_db} --jobs=#{processor_cores} "\
89
+ "#{additional_args}",
90
+ )
91
+ end
92
+
93
+ def delete_local_temp_backup
94
+ Kernel.system("rm tmp/latest.backup")
95
+ end
96
+
97
+ def delete_rails_production_environment_settings
98
+ Kernel.system(<<-SHELL)
99
+ psql #{development_db} -c "CREATE TABLE IF NOT EXISTS public.ar_internal_metadata (key character varying NOT NULL, value character varying, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key)); UPDATE ar_internal_metadata SET value = 'development' WHERE key = 'environment'"
100
+ SHELL
101
+ end
102
+
103
+ def restore_to_remote_environment
104
+ reset_remote_database
105
+ Kernel.system(
106
+ "heroku pg:backups:restore #{backup_from} #{app_argument} #{to} "\
107
+ "#{additional_args}",
108
+ )
109
+ end
110
+
111
+ def backup_from
112
+ "`#{remote_db_backup_url}` DATABASE"
113
+ end
114
+
115
+ def remote_db_backup_url
116
+ "heroku pg:backups:url #{app_argument} #{from}"
117
+ end
118
+
119
+ def development_db
120
+ db_configuration = YAML.safe_load(database_yaml_file, aliases: true).
121
+ fetch(DEVELOPMENT_ENVIRONMENT_KEY_NAME)
122
+
123
+ if db_configuration.key?(PRIMARY_DATABASE_KEY_NAME)
124
+ db_configuration[PRIMARY_DATABASE_KEY_NAME].
125
+ fetch(DATABASE_KEY_NAME)
126
+ else
127
+ db_configuration.fetch(DATABASE_KEY_NAME)
128
+ end
129
+ end
130
+
131
+ def database_yaml_file
132
+ ERB.new(IO.read(DATABASE_YML_RELATIVE_PATH)).result
133
+ end
134
+
135
+ def processor_cores
136
+ if parallelize? && ruby_version_over_2_2?
137
+ Etc.nprocessors
138
+ else
139
+ 1
140
+ end
141
+ end
142
+
143
+ def ruby_version_over_2_2?
144
+ Etc.respond_to?(:nprocessors)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,147 @@
1
+ require "parity/backup"
2
+
3
+ module Parity
4
+ class Environment
5
+ def initialize(environment, subcommands, app_argument: "--remote")
6
+ self.environment = environment
7
+ self.subcommand = subcommands[0]
8
+ self.arguments = subcommands[1..-1]
9
+ self.app_argument = app_argument
10
+ end
11
+
12
+ def run
13
+ run_command || false
14
+ end
15
+
16
+ private
17
+
18
+ PROTECTED_ENVIRONMENTS = %w(development production)
19
+
20
+ attr_accessor :app_argument, :environment, :subcommand, :arguments
21
+
22
+ def run_command
23
+ if self.class.private_method_defined?(methodized_subcommand)
24
+ send(methodized_subcommand)
25
+ else
26
+ run_via_cli
27
+ end
28
+ end
29
+
30
+ def open
31
+ run_via_cli
32
+ end
33
+
34
+ def run_via_cli
35
+ Kernel.exec("heroku", subcommand, *arguments, app_argument, environment)
36
+ end
37
+
38
+ def backup
39
+ Kernel.system("heroku pg:backups:capture #{app_argument} #{environment}")
40
+ end
41
+
42
+ def deploy
43
+ if production?
44
+ Kernel.system("git push production #{branch_ref}")
45
+ else
46
+ Kernel.system(
47
+ "git push #{environment} HEAD:#{branch_ref} --force",
48
+ )
49
+ end
50
+ end
51
+
52
+ def branch_ref
53
+ main_ref_exists = system("git show-ref --verify --quiet refs/heads/main")
54
+ master_ref_exists = system(
55
+ "git show-ref --verify --quiet refs/heads/master",
56
+ )
57
+
58
+ if main_ref_exists && !master_ref_exists
59
+ "main"
60
+ else
61
+ "master"
62
+ end
63
+ end
64
+
65
+ def restore
66
+ if production? && !forced?
67
+ $stdout.puts "Parity does not support restoring backups into your "\
68
+ "production environment. Use `--force` to override."
69
+ else
70
+ Backup.new(
71
+ from: arguments.first,
72
+ to: environment,
73
+ parallelize: parallelize?,
74
+ additional_args: additional_restore_arguments,
75
+ app_argument: app_argument
76
+ ).restore
77
+ end
78
+ end
79
+
80
+ alias :restore_from :restore
81
+
82
+ def production?
83
+ environment == "production"
84
+ end
85
+
86
+ def forced?
87
+ arguments.include?("--force")
88
+ end
89
+
90
+ def parallelize?
91
+ arguments.include?("--parallelize")
92
+ end
93
+
94
+ def additional_restore_arguments
95
+ (arguments.drop(1) - ["--force", "--parallelize"] +
96
+ [restore_confirmation_argument]).compact.join(" ")
97
+ end
98
+
99
+ def restore_confirmation_argument
100
+ unless PROTECTED_ENVIRONMENTS.include?(environment) || from_development?
101
+ "--confirm #{app_argument == "--app" ? environment : heroku_app_name}"
102
+ end
103
+ end
104
+
105
+ def from_development?
106
+ arguments.first == "development"
107
+ end
108
+
109
+ def console
110
+ Kernel.system(
111
+ command_for_remote(
112
+ "run bundle exec rails console #{arguments.join(' ')}",
113
+ ),
114
+ )
115
+ end
116
+
117
+ def migrate
118
+ Kernel.system(%{
119
+ #{command_for_remote('run rake db:migrate')} &&
120
+ #{command_for_remote('restart')}
121
+ })
122
+ end
123
+
124
+ def tail
125
+ Kernel.system(
126
+ command_for_remote("logs --tail #{arguments.join(' ')}"),
127
+ )
128
+ end
129
+
130
+ def heroku_app_name
131
+ if app_argument == "--app"
132
+ environment
133
+ else
134
+ HerokuAppName.new(environment).to_s
135
+ end
136
+
137
+ end
138
+
139
+ def command_for_remote(command)
140
+ "heroku #{command} #{app_argument} #{environment}"
141
+ end
142
+
143
+ def methodized_subcommand
144
+ subcommand.gsub("-", "_").to_sym
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,18 @@
1
+ module Parity
2
+ class HerokuAppName
3
+ def initialize(environment)
4
+ @environment = environment
5
+ end
6
+
7
+ def to_s
8
+ @heroku_app_name ||= Open3.
9
+ capture3("heroku info --remote #{environment}")[0].
10
+ split("\n")[0].
11
+ gsub(/(\s|=)+/, "")
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :environment
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Parity
2
+ class Usage
3
+ def to_s
4
+ File.read(readme).match(/Usage\n-----\n(.+)\nConvention\n------/m)[1]
5
+ end
6
+
7
+ private
8
+
9
+ def readme
10
+ File.join(File.dirname(__FILE__), '..', '..', 'README.md')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Parity
2
+ VERSION = "3.4.1".freeze
3
+ end
data/lib/parity.rb ADDED
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH << File.expand_path("..", __FILE__)
2
+
3
+ require "parity/heroku_app_name"
4
+ require "parity/version"
5
+ require "parity/environment"
6
+ require "parity/usage"
7
+ require "erb"
8
+ require "open3"
9
+ require "pathname"
10
+ require "uri"
11
+ require "yaml"
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parity_logandsprice
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.4.1
5
+ platform: ruby
6
+ authors:
7
+ - Dan Croak
8
+ - Geoff Harcourt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2021-11-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: |2
15
+ Development/staging/production parity makes it easier for
16
+ those who write the code to deploy the code.
17
+ email:
18
+ - logandsprice@gmail.com.com
19
+ executables:
20
+ - development
21
+ - staging
22
+ - production
23
+ - pr_app
24
+ extensions: []
25
+ extra_rdoc_files: []
26
+ files:
27
+ - README.md
28
+ - bin/development
29
+ - bin/pr_app
30
+ - bin/production
31
+ - bin/staging
32
+ - lib/parity.rb
33
+ - lib/parity/backup.rb
34
+ - lib/parity/environment.rb
35
+ - lib/parity/heroku_app_name.rb
36
+ - lib/parity/usage.rb
37
+ - lib/parity/version.rb
38
+ homepage: https://github.com/logandsprice/parity
39
+ licenses:
40
+ - MIT
41
+ metadata: {}
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 2.2.0
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubygems_version: 3.1.4
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: Shell commands for development, staging, and production parity.
61
+ test_files: []