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.
- checksums.yaml +4 -4
- data/lib/brut/cli/apps/build_assets.rb +63 -32
- data/lib/brut/cli/apps/db.rb +198 -78
- data/lib/brut/cli/apps/deploy.rb +215 -140
- data/lib/brut/cli/apps/new/app.rb +127 -41
- data/lib/brut/cli/apps/scaffold.rb +108 -105
- data/lib/brut/cli/apps/test.rb +45 -26
- data/lib/brut/cli/commands/base_command.rb +58 -22
- data/lib/brut/cli/commands/compound_command.rb +2 -4
- data/lib/brut/cli/commands/execution_context.rb +17 -10
- data/lib/brut/cli/commands/help.rb +112 -6
- data/lib/brut/cli/commands/output_error.rb +1 -1
- data/lib/brut/cli/execute_result.rb +4 -0
- data/lib/brut/cli/executor.rb +7 -7
- data/lib/brut/cli/logger.rb +122 -0
- data/lib/brut/cli/output.rb +9 -45
- data/lib/brut/cli/parsed_command_line.rb +33 -10
- data/lib/brut/cli/runner.rb +37 -8
- data/lib/brut/cli/terminal.rb +74 -0
- data/lib/brut/cli/terminal_theme.rb +131 -0
- data/lib/brut/cli.rb +7 -3
- data/lib/brut/framework/mcp.rb +4 -3
- data/lib/brut/front_end/asset_metadata.rb +9 -5
- data/lib/brut/spec_support/cli_command_support.rb +9 -3
- data/lib/brut/spec_support/e2e_test_server.rb +3 -3
- data/lib/brut/tui/script.rb +1 -1
- data/lib/brut/version.rb +1 -1
- metadata +18 -4
- data/lib/brut/cli/apps/deploy_base.rb +0 -86
- data/lib/brut/cli/apps/heroku_container_based_deploy.rb +0 -226
- data/templates/segments/Heroku/bin/deploy +0 -11
|
@@ -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
|