brut 0.18.2 → 0.19.0

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.
@@ -1,86 +0,0 @@
1
- require "brut/cli"
2
-
3
- class Brut::CLI::Apps::DeployBase < Brut::CLI::App
4
- def before_execute
5
- ENV["RACK_ENV"] = "production"
6
- end
7
-
8
- class GitChecks
9
- def initialize(out:, err:, executor:, warn_only: false)
10
- @out = out
11
- @err = err
12
- @executor = executor
13
- @warn_only = warn_only
14
- end
15
-
16
- def check!
17
- require_main_branch!
18
- require_no_local_changes!
19
- require_pushed_to_main!
20
- end
21
-
22
- private
23
-
24
- def require_main_branch!
25
- # XXX: This should be put into executor
26
- current_branch = `git branch --show-current`.strip.chomp
27
-
28
- if current_branch != "main"
29
- @err.puts "You are not on the 'main' branch, but on the '#{current_branch}' branch"
30
- @err.puts "You may only deploy from the 'main' branch"
31
- if @warn_only
32
- @err.puts "Ignoring"
33
- return
34
- else
35
- exit 1
36
- end
37
- end
38
- end
39
-
40
- def require_no_local_changes!
41
- @out.puts "Running 'git status' to reset local caches to account for Docker<->host oddities"
42
- @executor.system!("git status")
43
- local_changes = `git diff-index --name-only HEAD --`
44
- if local_changes.strip != ""
45
- @err.puts "You have local changes:"
46
- local_changes.split(/\n/).each do |change|
47
- @err.puts " #{change}"
48
- end
49
- @err.puts "Commit these, run bin/ci, then push to origin/main"
50
- if @warn_only
51
- @err.puts "Ignoring"
52
- return
53
- else
54
- exit 1
55
- end
56
- end
57
- end
58
-
59
- def require_pushed_to_main!
60
- # XXX: This should be put into executor
61
- git_status = `git rev-list --left-right --count origin/main...main`.strip.chomp
62
- remote_ahead, local_ahead = git_status.split(/\t/,2).map(&:to_i)
63
-
64
- if remote_ahead != 0
65
- @err.puts "There are commits in origin you don't have. Pull those in, re-run bin/ci, THEN deploy"
66
- if @warn_only
67
- @err.puts "Ignoring"
68
- return
69
- else
70
- exit 1
71
- end
72
- end
73
-
74
- if local_ahead != 0
75
- @out.puts "You have not pushed to origin. Do that before deploying"
76
- if @warn_only
77
- @err.puts "Ignoring"
78
- return
79
- else
80
- exit 1
81
- end
82
- end
83
- end
84
- end
85
- end
86
-
@@ -1,226 +0,0 @@
1
- require "brut/cli"
2
- class Brut::CLI::Apps::HerokuContainerBasedDeploy < Brut::CLI::Apps::DeployBase
3
- description "Deploy to Heroku using containers"
4
- default_command :deploy
5
- configure_only!
6
-
7
- # Better way:
8
- #
9
- # brut deploy check - do basic checks that a deploy should happen
10
- # brut deploy build - build artifacts for deploy e.g. Docker image
11
- # brut deploy deploy - do the actual deployment (does a check, then build first)
12
- # brut deploy - defaults to brut deploy deploy
13
- class Deploy < Brut::CLI::Command
14
- description "Build images, push them to Heroku, and deploy them"
15
-
16
- detailed_description %{
17
- Manages a deploy process based on using Heroku's Container Registry. See
18
-
19
- https://devcenter.heroku.com/articles/container-registry-and-runtime
20
-
21
- for details. You are assumed to understand this. This command will make the process somewhat easier.
22
-
23
- This will use deploy/Dockerfile as a template to create one Dockerfile for each process you want to run in Heroku. deploy/heroku_config.rb is where the processes and their commands are configured.
24
-
25
- The release phase is included automatically, based on bin/release.
26
- }
27
-
28
- opts.on("--platform=PLATFORM","Override default platform. Can be any Docker platform.")
29
- opts.on("--[no-]dry-run", "Print the commands that would be run and don't actually do anything. Implies --skip-checks")
30
- opts.on("--[no-]skip-checks", "Skip checks for code having been committed and pushed")
31
- opts.on("--[no-]deploy", "After images are pushed, actually deploy them")
32
- opts.on("--[no-]push", "After images are created, push them to Heroku's registry. If false, implies --no-deploy")
33
-
34
- def after_bootstrap(app:)
35
- @app_id = app.id
36
- @organization = app.organization
37
- end
38
-
39
- def execute
40
- options.set_default(:deploy, true)
41
- options.set_default(:push, true)
42
- if !options.push?
43
- options[:deploy] = false
44
- end
45
- local_repo_checks = Brut::CLI::Apps::DeployBase::GitChecks.new(
46
- out: @out,
47
- err: @err,
48
- executor: @executor,
49
- warn_only: options.skip_checks? || options.dry_run?
50
- )
51
- if options.dry_run?
52
- def system!(*args)
53
- out.puts "DRY RUN, NOT EXECUTING '#{args}'"
54
- end
55
- end
56
-
57
- version = begin
58
- git_guess = %{git rev-parse HEAD}
59
- stdout, stderr, status = Open3.capture3(git_guess)
60
- if status.success?
61
- stdout.strip
62
- else
63
- raise "Attempt to use git via command '#{git_guess}' to figure out the version failed: #{stdout}#{stderr}"
64
- end
65
- end
66
- short_version = version[0..7]
67
-
68
- platform = options.platform || "linux/amd64"
69
- heroku_app_name = @app_id
70
-
71
- out.puts "Reading HerokuConfig:"
72
- require_relative Brut.container.project_root / "deploy" / "heroku_config"
73
-
74
- additional_images = (HerokuConfig.additional_images || {}).map { |name,config|
75
- cmd = config.fetch(:cmd)
76
- out.puts " - #{name} will run #{cmd} in production"
77
- image_name = %{#{@organization}/#{@app_id}:#{short_version}-#{name}}
78
- [
79
- name,
80
- {
81
- cmd:,
82
- image_name:,
83
- dockerfile: "deploy/Dockerfile.#{name}",
84
- heroku_image_name: "registry.heroku.com/#{heroku_app_name}/#{name}",
85
- },
86
- ]
87
- }.to_h
88
-
89
- images = {
90
- "web" => {
91
- cmd: "bin/run",
92
- image_name: %{#{@organization}/#{@app_id}:#{short_version}-web},
93
- dockerfile: "deploy/Dockerfile.web",
94
- heroku_image_name: "registry.heroku.com/#{heroku_app_name}/web",
95
- },
96
- "release" => {
97
- cmd: "bin/release",
98
- image_name: %{#{@organization}/#{@app_id}:#{short_version}-release},
99
- dockerfile: "deploy/Dockerfile.release",
100
- heroku_image_name: "registry.heroku.com/#{heroku_app_name}/release",
101
- },
102
- }.merge(additional_images)
103
-
104
- out.puts " - release will run bin/release in production"
105
-
106
- local_repo_checks.check!
107
- require_heroku_login!(options)
108
- login_to_heroku_container_registry!(options)
109
-
110
- FileUtils.chdir Brut.container.project_root do
111
-
112
- out.puts "Generating Dockerfiles"
113
- images.each do |name,metadata|
114
- cmd = metadata.fetch(:cmd)
115
- dockerfile = metadata.fetch(:dockerfile)
116
-
117
- out.puts "Creating '#{dockerfile}' for '#{name}' that will use command '#{cmd}'"
118
-
119
- File.open(dockerfile,"w") do |file|
120
- file.puts "# DO NOT EDIT - THIS IS GENERATED"
121
- file.puts "# To make changes, modifiy deploy/Dockerfile and run #{$0}"
122
- file.puts File.read("deploy/Dockerfile")
123
- file.puts
124
- file.puts "# Added by #{$0}"
125
- file.puts %{CMD [ "bundle", "exec", "#{cmd}" ]}
126
- end
127
- end
128
-
129
- out.puts "Building images"
130
- docker_quiet_option = if global_options.log_level != "debug"
131
- "--quiet"
132
- else
133
- ""
134
- end
135
- images.each do |name,metadata|
136
- image_name = metadata.fetch(:image_name)
137
- dockerfile = metadata.fetch(:dockerfile)
138
-
139
-
140
- out.puts "Creating docker image with name '#{image_name}' and platform '#{platform}'"
141
- command = %{docker build #{docker_quiet_option} --build-arg app_git_sha1=#{version} --file #{Brut.container.project_root}/#{dockerfile} --platform #{platform} --tag #{image_name} .}
142
- system!(command)
143
- end
144
-
145
- out.puts "Taggging images for Heroku"
146
- images.each do |name,metadata|
147
- image_name = metadata.fetch(:image_name)
148
- heroku_image_name = metadata.fetch(:heroku_image_name)
149
-
150
- out.puts "Tagging '#{image_name}' with '#{heroku_image_name}' for Heroku"
151
- command = %{docker tag #{image_name} #{heroku_image_name}}
152
- system!(command)
153
- end
154
-
155
- if options.push?
156
- out.puts "Pushing to Heroku Registry"
157
- images.each do |name,metadata|
158
- heroku_image_name = metadata.fetch(:heroku_image_name)
159
-
160
- out.puts "Pushing '#{heroku_image_name}'"
161
-
162
- command = %{docker push #{docker_quiet_option} #{heroku_image_name}}
163
- system!(command)
164
- end
165
- else
166
- out.puts "Not pushing images"
167
- end
168
-
169
- names = images.map(&:first).join(" ")
170
- deploy_command = "heroku container:release #{names} -a #{heroku_app_name}"
171
- if options.deploy?
172
- out.puts "Deploying images to Heroku"
173
- system!(deploy_command)
174
- else
175
- out.puts "Not deploying. To deploy the images just pushed:"
176
- out.puts ""
177
- out.puts " #{deploy_command}"
178
- end
179
- end
180
- end
181
-
182
- private
183
-
184
- def require_heroku_login!(options)
185
- logged_into_heroku = system("heroku whoami > /dev/null 2>&1")
186
-
187
- if !logged_into_heroku
188
- out.puts "You are not logged into Heroku."
189
- out.puts
190
- out.puts "There are two ways to do this:"
191
- out.puts
192
- out.puts "1 - Set HEROKU_API_KEY in your environment. You can get a"
193
- out.puts " value in a lot of ways, but a comand-line based one is"
194
- out.puts
195
- out.puts " heroku authorizations:create -d 'container pushes' --expires-in 31536000"
196
- out.puts
197
- out.puts " This reuqires logging into the CLI via heroku auth:login, but using the"
198
- out.puts " key will prevent you from having to login every day."
199
- out.puts
200
- out.puts " See Brut's documentation for how to persist this value between rebuilds"
201
- out.puts " of your dev environment."
202
- out.puts
203
- out.puts "2 - heroku auth:login"
204
- out.puts
205
- out.puts " This is simpler, however you will need to do this every day."
206
- out.puts
207
- exit 1
208
- end
209
- out.puts "You are logged into Heroku"
210
- end
211
-
212
- def login_to_heroku_container_registry!(options)
213
- if !system("heroku container:login")
214
- out.puts "Failed to login to Heroku Container Registry"
215
- out.puts
216
- out.puts "Since you are logged into Heroku, this should've passed, so there"
217
- out.puts "may be something else wrong. The command used was"
218
- out.puts
219
- out.puts " heroku container:login"
220
- out.puts
221
- exit 1
222
- end
223
- out.puts "Logged into Heroku Container Registry"
224
- end
225
- end
226
- end
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler"
4
- Bundler.require
5
- require "pathname"
6
- require "brut/cli/apps/heroku_container_based_deploy"
7
-
8
- exit Brut::CLI.app(
9
- Brut::CLI::Apps::HerokuContainerBasedDeploy,
10
- project_root: Pathname($0).dirname / ".."
11
- )