-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathuser_errors.rs
375 lines (302 loc) · 14 KB
/
user_errors.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
use std::process::Command;
#[allow(clippy::wildcard_imports)]
use commons::output::{
build_log::*,
fmt::{self, DEBUG_INFO},
};
use crate::{DetectError, RubyBuildpackError};
use fun_run::{CmdError, CommandWithName};
use indoc::formatdoc;
pub(crate) fn on_error(err: libcnb::Error<RubyBuildpackError>) {
let log = BuildLog::new(std::io::stdout()).without_buildpack_name();
match cause(err) {
Cause::OurError(error) => log_our_error(log, error),
Cause::FrameworkError(error) =>
log
.section(DEBUG_INFO)
.step(&error.to_string())
.announce()
.error(&formatdoc! {"
Error: heroku/buildpack-ruby internal buildpack error
The framework used by this buildpack encountered an unexpected error.
This type of error usually indicates there's nothing wrong with your application.
If you can’t deploy to Heroku due to this issue please check the official Heroku
status page https://status.heroku.com/ to see if there is an ongoing incident. Once
all incidents have resolved please retry your build.
If the issue persists, please try to reproduce the behavior locally using the `pack`
CLI. If you can reproduce the behavior locally and believe you've found a bug in the
buildpack or the framework please open an issue on the buildpack's GitHub repository.
"}),
};
}
#[allow(clippy::too_many_lines)]
fn log_our_error(mut log: Box<dyn StartedLogger>, error: RubyBuildpackError) {
let git_branch_url =
fmt::url("https://devcenter.heroku.com/articles/git#deploy-from-a-branch-besides-main");
let ruby_versions_url =
fmt::url("https://devcenter.heroku.com/articles/ruby-support#ruby-versions");
let rubygems_status_url = fmt::url("https://status.rubygems.org/");
match error {
RubyBuildpackError::BuildpackDetectionError(DetectError::Gemfile(error)) => {
log.announce().error(&formatdoc! {"
Error: `Gemfile` found with error
There was an error trying to read the contents of the application's Gemfile. \
The buildpack cannot continue if the Gemfile is unreadable.
{error}
Debug using the above information and try again.
"});
}
RubyBuildpackError::BuildpackDetectionError(DetectError::PackageJson(error)) => {
log.announce().error(&formatdoc! {"
Error: `package.json` found with error
The Ruby buildpack detected a package.json file but it is not readable \
due to the following errors:
{error}
If your application does not need any node dependencies installed, \
you may delete this file and try again.
If you are expecting node dependencies to be installed, please \
debug using the above information and try again.
"});
}
RubyBuildpackError::BuildpackDetectionError(DetectError::GemfileLock(error)) => {
log.announce().error(&formatdoc! {"
Error: `Gemfile.lock` found with error
There was an error trying to read the contents of the application's Gemfile.lock. \
The buildpack cannot continue if the Gemfile is unreadable.
{error}
Debug using the above information and try again.
"});
}
RubyBuildpackError::BuildpackDetectionError(DetectError::YarnLock(error)) => {
log.announce().error(&formatdoc! {"
Error: `yarn.lock` found with error
The Ruby buildpack detected a yarn.lock file but it is not readable \
due to the following errors:
{error}
If your application does not need yarn installed, you \
may delete this file and try again.
If you are expecting yarn to be installed, please \
debug using the above information and try again.
"});
}
RubyBuildpackError::MissingGemfileLock(path, error) => {
log = log
.section(&format!(
"Could not find {}, details:",
fmt::value(path.to_string_lossy())
))
.step(&error.to_string())
.end_section();
if let Some(dir) = path.parent() {
log = debug_cmd(
log.section(&format!(
"{DEBUG_INFO} Contents of the {} directory",
fmt::value(dir.to_string_lossy())
)),
Command::new("ls").args(["la", &dir.to_string_lossy()]),
);
}
log.announce().error(&formatdoc! {"
Error: `Gemfile.lock` not found
A `Gemfile.lock` file is required and was not found in the root of your application.
If you have a `Gemfile.lock` in your application, ensure it is tracked in Git and
that you’re pushing the correct branch.
For more information:
{git_branch_url}
"});
}
RubyBuildpackError::RubyInstallError(error) => {
// Future:
// - In the future use a manifest file to list if version is available on a different stack
// - In the future add a "did you mean" Levenshtein distance to see if they typoed like "3.6.0" when they meant "3.0.6"
log.section(DEBUG_INFO)
.step(&error.to_string())
.announce()
.error(&formatdoc! {"
Error installing Ruby
Could not install the detected Ruby version. Ensure that you're using a supported
ruby version and try again.
Supported ruby versions:
{ruby_versions_url}
"});
}
RubyBuildpackError::GemInstallBundlerCommandError(error) => {
log = log
.section(DEBUG_INFO)
.step(&error.to_string())
.end_section();
log = debug_cmd(log.section(DEBUG_INFO), Command::new("gem").arg("env"));
log.announce().error(&formatdoc! {"
Error installing bundler
The ruby package managment tool, `bundler`, failed to install. Bundler is required
to install your application's dependencies listed in the `Gemfile`.
Check the status page of RubyGems.org:
{rubygems_status_url}
Once all incidents have been resolved, please retry your build.
"});
}
RubyBuildpackError::BundleInstallCommandError(error) => {
// Future:
// - Grep error output for common things like using sqlite3, use classic buildpack
let local_command = local_command_debug(&error);
log
.section(DEBUG_INFO)
.step(&error.to_string())
.end_section()
.announce()
.error(&formatdoc! {"
Error installing your applications's dependencies
Could not install gems to the system via bundler. Gems are dependencies
your application listed in the `Gemfile` and resolved in the `Gemfile.lock`.
{local_command}
If you believe that your application is correct, ensure all files are tracked in Git and
that you’re pushing the correct branch:
{git_branch_url}
Use the information above to debug further.
"});
}
RubyBuildpackError::BundleInstallDigestError(path, error) => {
log = log
.section(DEBUG_INFO)
.step(&error.to_string())
.end_section();
if let Some(dir) = path.parent() {
log = debug_cmd(
log.section(&format!(
"{DEBUG_INFO} Contents of the {} directory",
fmt::value(dir.to_string_lossy())
)),
Command::new("ls").args(["la", &dir.to_string_lossy()]),
);
}
log.announce().error(&formatdoc! {"
Error generating file digest
An error occurred while generating a file digest. To provide the fastest possible
install experience, the Ruby buildpack converts your `Gemfile` and `Gemfile.lock`
into a digest to use in cache invalidation.
Ensure that the permissions on the files in your application directory are correct and that
all symlinks correctly resolve.
If you're unable to resolve this error, you can disable the the digest feature by
setting the environment variable:
HEROKU_SKIP_BUNDLE_DIGEST=1
"});
}
RubyBuildpackError::RakeDetectError(error) => {
// Future:
// - Annotate with information on requiring test or development only gems in the Rakefile
let local_command = local_command_debug(&error);
log = log
.section(DEBUG_INFO)
.step(&error.to_string())
.end_section();
log.announce().error(&formatdoc! {"
Error detecting rake tasks
The Ruby buildpack uses rake task information from your application to guide
build logic. Without this information, the Ruby buildpack cannot continue.
{local_command}
Use the information above to debug further.
"});
}
RubyBuildpackError::RakeAssetsPrecompileFailed(error) => {
let local_command = local_command_debug(&error);
log = log
.section(DEBUG_INFO)
.step(&error.to_string())
.end_section();
log.announce().error(&formatdoc! {"
Error compiling assets
An error occured while compiling assets via rake command.
{local_command}
Use the information above to debug further.
"});
}
RubyBuildpackError::InAppDirCacheError(error) => {
// Future:
// - Separate between failures in layer dirs or in app dirs, if we can isolate to an app dir we could debug more
// to determine if there's bad permissions or bad file symlink
log = log
.section(DEBUG_INFO)
.step(&error.to_string())
.end_section();
log.announce().error(&formatdoc! {"
Error caching frontend assets
An error occurred while attempting to cache frontend assets, and the Ruby buildpack
cannot continue.
Ensure that the permissions on the files in your application directory are correct and that
all symlinks correctly resolve.
"});
}
RubyBuildpackError::GemListGetError(error) => {
log = log
.section(DEBUG_INFO)
.step(&error.to_string())
.end_section();
log = debug_cmd(log.section(DEBUG_INFO), Command::new("gem").arg("env"));
log = debug_cmd(log.section(DEBUG_INFO), Command::new("bundle").arg("env"));
log.announce().error(&formatdoc! {"
Error detecting dependencies
The Ruby buildpack requires information about your application’s dependencies to
complete the build. Without this information, the Ruby buildpack cannot continue.
Use the information above to debug further.
"});
}
RubyBuildpackError::MetricsAgentError(error) => {
log.section(DEBUG_INFO)
.step(&error.to_string())
.end_section()
.announce()
.error(&formatdoc! {"
Error: Could not install Statsd agent
An error occured while downloading and installing the metrics agent
the buildpack cannot continue.
"});
}
}
}
#[derive(Debug)]
enum Cause {
OurError(RubyBuildpackError),
FrameworkError(libcnb::Error<RubyBuildpackError>),
}
fn cause(err: libcnb::Error<RubyBuildpackError>) -> Cause {
match err {
libcnb::Error::BuildpackError(err) => Cause::OurError(err),
err => Cause::FrameworkError(err),
}
}
fn local_command_debug(error: &CmdError) -> String {
let cmd_name = replace_app_path_with_relative(fmt::command(error.name()));
formatdoc! {"
Ensure you can run the following command locally with no errors before attempting another build:
{cmd_name}
"}
}
fn replace_app_path_with_relative(contents: impl AsRef<str>) -> String {
let app_path_re = regex::Regex::new("/workspace/").expect("Internal error: regex");
app_path_re.replace_all(contents.as_ref(), "./").to_string()
}
fn debug_cmd(log: Box<dyn SectionLogger>, command: &mut Command) -> Box<dyn StartedLogger> {
let mut stream = log.step_timed_stream(&format!(
"Running debug command {}",
fmt::command(command.name())
));
match command.stream_output(stream.io(), stream.io()) {
Ok(_) => stream.finish_timed_stream().end_section(),
Err(e) => stream
.finish_timed_stream()
.step(&e.to_string())
.end_section(),
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_relative_path() {
let expected = r#"BUNDLE_DEPLOYMENT="1" BUNDLE_GEMFILE="./Gemfile" BUNDLE_WITHOUT="development:test" bundle install"#;
let actual = replace_app_path_with_relative(
r#"BUNDLE_DEPLOYMENT="1" BUNDLE_GEMFILE="/workspace/Gemfile" BUNDLE_WITHOUT="development:test" bundle install"#,
);
assert_eq!(expected, &actual);
}
}