discharger 0.1.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: 48d8ec34c0e97892c1dd06c2a23e5d5f61028949d04638d848e62258c1c9f89c
4
+ data.tar.gz: d7a1d3338e2fe6bbbad2088e29017c1cefd6c5dac70b4dc45fc3f9429417f581
5
+ SHA512:
6
+ metadata.gz: ab11db3d3ebfbaf567bafa691bf390a5c7b663f4d7b3d935e0996d057fe68472dc24e06b9b1c53fe1a41ec5de4007a78307c6243fff9f8eb9d71f22c86faf9f5
7
+ data.tar.gz: dc349824f12833069806e1b1a6fdad4fd1b17e53f349f155724a1c42a220500c6670cbcce43c90b64a331c6cd26cc804f1c84eeac5dd18bb04b656e6413cbaab
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2024 SOFware LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Discharger
2
+ Code supporting tasks that discharge code for deployment.
3
+
4
+ ## Usage
5
+
6
+ Add `require "discharger/task"` to your Rakefile.
7
+
8
+ Then build the discharger task
9
+
10
+ ```ruby
11
+ require "discharger/task"
12
+
13
+ Discharger::Task.create do |task|
14
+ task.version_file = "config/application.rb"
15
+ task.release_message_channel = "#some-slack-channel"
16
+ task.version_constant = "MyApp::VERSION"
17
+ task.app_name = "My App name"
18
+ task.commit_identifier = -> { `git rev-parse HEAD`.strip }
19
+ task.pull_request_url = "https://github.com/SOFware/some-app"
20
+ end
21
+ ```
22
+
23
+ It will make Rake tasks available to push code to branches and notify Slack channels.
24
+
25
+ ```bash
26
+ $ rake -T release
27
+ rake release # ---------- STEP 3 ----------
28
+ rake release:build # Release the current version to stage
29
+ rake release:prepare # ---------- STEP 1 ----------
30
+ rake release:slack[text,channel,emoji] # Send a message to Slack
31
+ rake release:stage # ---------- STEP 2 ----------
32
+ ```
33
+
34
+ ## Installation
35
+ Add this line to your application's Gemfile:
36
+
37
+ ```ruby
38
+ gem "discharger"
39
+ ```
40
+
41
+ And then execute:
42
+ ```bash
43
+ $ bundle
44
+ ```
45
+
46
+ Or install it yourself as:
47
+ ```bash
48
+ $ gem install discharger
49
+ ```
50
+
51
+ ## Contributing
52
+
53
+ This gem is managed with [Reissue](https://github.com/SOFware/reissue).
54
+
55
+ Bug reports and pull requests are welcome on GitHub.
56
+
57
+ ## License
58
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ task default: :test
6
+
7
+ require "reissue/gem"
8
+
9
+ Reissue::Task.create :reissue do |task|
10
+ task.version_file = "lib/discharger/version.rb"
11
+ task.commit = true
12
+ end
@@ -0,0 +1,9 @@
1
+ module Discharger
2
+ class Railtie < ::Rails::Railtie
3
+ config.before_configuration do
4
+ if ENV["SLACK_API_TOKEN_RELEASES"].nil?
5
+ raise "SLACK_API_TOKEN_RELEASES must be set in the environment"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,299 @@
1
+ require "rake/tasklib"
2
+
3
+ module Discharger
4
+ class Task < Rake::TaskLib
5
+ def self.create(name = :release, &block)
6
+ task = new(name)
7
+ task.instance_eval(&block) if block
8
+ task.define
9
+ task
10
+ end
11
+
12
+ attr_accessor :name
13
+
14
+ attr_accessor :description
15
+
16
+ attr_accessor :working_branch
17
+ attr_accessor :staging_branch
18
+ attr_accessor :production_branch
19
+
20
+ attr_accessor :release_message_channel
21
+ attr_accessor :version_constant
22
+
23
+ attr_accessor :chat_token
24
+ attr_accessor :app_name
25
+ attr_accessor :commit_identifier
26
+ attr_accessor :pull_request_url
27
+
28
+ # Reissue settings
29
+ attr_accessor :version_file
30
+ attr_accessor :version_limit
31
+ attr_accessor :version_redo_proc
32
+ attr_accessor :changelog_file
33
+ attr_accessor :updated_paths
34
+ attr_accessor :commit
35
+ attr_accessor :commit_finalize
36
+
37
+ def initialize(name = :release)
38
+ @name = name
39
+ @working_branch = "develop"
40
+ @staging_branch = "stage"
41
+ @production_branch = "main"
42
+ @description = "Release the current version to #{staging_branch}"
43
+ end
44
+
45
+ # Run a multiple system commands and return true if all commands succeed
46
+ # If any command fails, the method will return false and stop executing
47
+ # any further commands.
48
+ #
49
+ # Provide a block to evaluate the output of the command and return true
50
+ # if the command was successful. If the block returns false, the method
51
+ # will return false and stop executing any further commands.
52
+ #
53
+ # @param *steps [Array<Array<String>>] an array of commands to run
54
+ # @param block [Proc] a block to evaluate the output of the command
55
+ # @return [Boolean] true if all commands succeed, false otherwise
56
+ #
57
+ # @example
58
+ # syscall(
59
+ # ["echo", "Hello, World!"],
60
+ # ["ls", "-l"]
61
+ # )
62
+ def syscall(*steps)
63
+ success = false
64
+ stdout, stderr, status = nil
65
+ steps.each do |*cmd|
66
+ puts cmd.join(" ").bg(:green).black
67
+ stdout, stderr, status = Open3.capture3(*cmd)
68
+ if status.success?
69
+ puts stdout
70
+ else
71
+ puts stderr
72
+ success = false
73
+ abort(stderr)
74
+ end
75
+ end
76
+ if block_given?
77
+ success = !!yield(stdout, stderr, status)
78
+ # If the error reports that a rule was bypassed, consider the command successful
79
+ # because we are bypassing the rule intentionally when merging the release branch
80
+ # to the production branch.
81
+ success = true if stderr.match?(/bypassed rule violations/i)
82
+ abort(stderr) unless success
83
+ end
84
+ success
85
+ end
86
+
87
+ # Echo a message to the console
88
+ #
89
+ # @param message [String] the message to echo
90
+ # return [TrueClass]
91
+ def sysecho(message)
92
+ system "echo", message
93
+ true
94
+ end
95
+
96
+ def define
97
+ require "slack-ruby-client"
98
+ Slack.configure do |config|
99
+ config.token = chat_token
100
+ end
101
+
102
+ desc <<~DESC
103
+ ---------- STEP 3 ----------
104
+ Release the current version to production
105
+
106
+ This task rebases the production branch on the staging branch and tags the
107
+ current version. The production branch and the tag will be pushed to the
108
+ remote repository.
109
+
110
+ After the release is complete, a new branch will be created to bump the
111
+ version for the next release.
112
+ DESC
113
+ task "#{name}": [:environment] do
114
+ current_version = Object.const_get(version_constant)
115
+ sysecho <<~MSG
116
+ Releasing version #{current_version} to production.
117
+
118
+ This will tag the current version and push it to the production branch.
119
+ MSG
120
+ sysecho "Are you ready to continue? (Press Enter to continue, Type 'x' and Enter to exit)".bg(:yellow).black
121
+ input = $stdin.gets
122
+ exit if input.chomp.match?(/^x/i)
123
+
124
+ continue = syscall(
125
+ "git checkout #{working_branch}",
126
+ "git branch -D #{staging_branch} 2> /dev/null || true",
127
+ "git branch -D #{production_branch} 2> /dev/null || true",
128
+ "git fetch origin #{staging_branch}:#{staging_branch} #{production_branch}:#{production_branch}",
129
+ "git checkout #{production_branch}",
130
+ "git rebase #{staging_branch}",
131
+ "git tag -a v#{current_version} -m 'Release #{current_version}'",
132
+ "git push origin #{production_branch}; git push origin v#{current_version}"
133
+ ) do
134
+ Rake::Task["slack"].invoke("Released #{Qualify.name} #{current_version} to production.", release_message_channel, ":chipmunk:")
135
+ syscall "git checkout #{working_branch}"
136
+ end
137
+
138
+ abort "Release failed." unless continue
139
+
140
+ sysecho <<~MSG
141
+ Version #{current_version} released to production.
142
+
143
+ Preparing to bump the version for the next release.
144
+
145
+ MSG
146
+
147
+ new_version_branch = "bump/begin-#{current_version.tr(".", "-")}"
148
+
149
+ continue = syscall("git checkout -b #{new_version_branch}") do
150
+ Rake::Task["reissue"].invoke
151
+ end
152
+
153
+ abort "Bump failed." unless continue
154
+
155
+ pr_url = "#{pull_request_url}/compare/#{working_branch}...#{new_version_branch}?expand=1&title=Begin%20#{current_version}"
156
+
157
+ syscall("git push origin #{new_version_branch} --force") do
158
+ sysecho <<~MSG
159
+ Branch #{new_version_branch} created.
160
+
161
+ Open a PR to #{working_branch} to mark the version and update the chaneglog
162
+ for the next release.
163
+
164
+ Opening PR: #{pr_url}
165
+ MSG
166
+ end.then do |success|
167
+ syscall "open #{pr_url}" if success
168
+ end
169
+ end
170
+
171
+ namespace name do
172
+ desc description
173
+ task build: :environment do
174
+ syscall(
175
+ "git fetch origin #{working_branch}",
176
+ "git checkout #{working_branch}",
177
+ "git branch -D #{staging_branch} 2> /dev/null || true",
178
+ "git checkout -b #{staging_branch}",
179
+ "git push origin #{staging_branch} --force"
180
+ ) do
181
+ Rake::Task["slack"].invoke("Building #{app_name} #{commit_identifier.call} on #{staging_branch}.", release_message_channel)
182
+ syscall "git checkout #{working_branch}"
183
+ end
184
+ end
185
+
186
+ desc "Send a message to Slack."
187
+ task :slack, [:text, :channel, :emoji] => :environment do |_, args|
188
+ args.with_defaults(
189
+ channel: release_message_channel,
190
+ emoji: nil
191
+ )
192
+ client = Slack::Web::Client.new
193
+ options = args.to_h
194
+ options[:icon_emoji] = options.delete(:emoji) if options[:emoji]
195
+
196
+ sysecho "Sending message to Slack:".bg(:green).black + " #{args[:text]}"
197
+ result = client.chat_postMessage(**options)
198
+ sysecho %(Message sent: #{result["ts"]})
199
+ end
200
+
201
+ desc <<~DESC
202
+ ---------- STEP 1 ----------
203
+ Prepare the current version for release to production (#{production_branch})
204
+
205
+ This task will create a new branch to prepare the release. The CHANGELOG
206
+ will be updated and the version will be bumped. The branch will be pushed
207
+ to the remote repository.
208
+
209
+ After the branch is created, open a PR to #{working_branch} to finalize
210
+ the release.
211
+ DESC
212
+ task prepare: [:environment] do
213
+ current_version = Object.const_get(version_constant)
214
+ finish_branch = "bump/finish-#{current_version.tr(".", "-")}"
215
+
216
+ syscall "git fetch origin #{working_branch}",
217
+ "git checkout #{working_branch}",
218
+ "git checkout -b #{finish_branch}"
219
+
220
+ sysecho <<~MSG
221
+ Branch #{finish_branch} created.
222
+
223
+ Check the contents of the CHANGELOG and ensure that the text is correct.
224
+
225
+ If you need to make changes, edit the CHANGELOG and save the file.
226
+ Then return here to continue with this commit.
227
+ MSG
228
+ sysecho "Are you ready to continue? (Press Enter to continue, Type 'x' and Enter to exit)".bg(:yellow).black
229
+ input = $stdin.gets
230
+ exit if input.chomp.match?(/^x/i)
231
+
232
+ Rake::Task["reissue:finalize"].invoke
233
+
234
+ params = {
235
+ expand: 1,
236
+ title: "Finish version #{current_version}",
237
+ body: <<~BODY
238
+ Completing development for #{current_version}.
239
+ BODY
240
+ }
241
+
242
+ pr_url = "#{pull_request_url}/compare/#{finish_branch}?#{params.to_query}"
243
+
244
+ continue = syscall "git push origin #{finish_branch} --force" do
245
+ sysecho <<~MSG
246
+ Branch #{finish_branch} created.
247
+ Open a PR to #{working_branch} to finalize the release.
248
+
249
+ #{pr_url}
250
+
251
+ Once the PR is merged, pull down #{working_branch} and run
252
+ 'rake #{name}:stage'
253
+ to stage the release branch.
254
+ MSG
255
+ end
256
+ if continue
257
+ syscall "git checkout #{working_branch}",
258
+ "open #{pr_url}"
259
+ end
260
+ end
261
+
262
+ desc <<~DESC
263
+ ---------- STEP 2 ----------
264
+ Stage the release branch
265
+
266
+ This task will update Stage, open a PR, and instruct you on the next steps.
267
+
268
+ NOTE: If you just want to update the stage environment but aren't ready to release, run:
269
+
270
+ bin/rails #{name}:build
271
+ DESC
272
+ task stage: [:environment] do
273
+ Rake::Task["build"].invoke
274
+ current_version = Object.const_get(version_constant)
275
+
276
+ params = {
277
+ expand: 1,
278
+ title: "Release #{current_version} to production",
279
+ body: <<~BODY
280
+ Deploy #{current_version} to production.
281
+ BODY
282
+ }
283
+
284
+ pr_url = "#{pull_request_url}/compare/#{production_branch}...#{staging_branch}?#{params.to_query}"
285
+
286
+ sysecho <<~MSG
287
+ Branch #{staging_branch} updated.
288
+ Open a PR to #{production_branch} to release the version.
289
+
290
+ Opening PR: #{pr_url}
291
+
292
+ Once the PR is **approved**, run 'rake release' to release the version.
293
+ MSG
294
+ syscall "open #{pr_url}"
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,3 @@
1
+ module Discharger
2
+ VERSION = "0.1.0"
3
+ end
data/lib/discharger.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "discharger/version"
2
+ require "discharger/railtie"
3
+
4
+ module Discharger
5
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: discharger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jim Gay
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-09-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: open3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 7.2.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 7.2.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rainbow
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: reissue
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: slack-ruby-client
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Code supporting deployments.
84
+ email:
85
+ - jim@saturnflyer.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - lib/discharger.rb
94
+ - lib/discharger/railtie.rb
95
+ - lib/discharger/task.rb
96
+ - lib/discharger/version.rb
97
+ homepage: https://github.com/SOFware/discharger
98
+ licenses: []
99
+ metadata:
100
+ homepage_uri: https://github.com/SOFware/discharger
101
+ source_code_uri: https://github.com/SOFware/discharger.git
102
+ changelog_uri: https://github.com/SOFware/discharger/blob/main/CHANGELOG.md
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.5.9
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Tasks for discharging an application for deployment.
122
+ test_files: []