Skip to content

Commit

Permalink
Added output logging and error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
colincasey committed Sep 6, 2023
1 parent f5e955a commit 8447d0c
Show file tree
Hide file tree
Showing 11 changed files with 667 additions and 654 deletions.
780 changes: 271 additions & 509 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ publish = false
[workspace.dependencies]
heroku-nodejs-utils = { path = "./common/nodejs-utils" }
indoc = "2"
libcnb-test = { git = "https://github.com/heroku/libcnb.rs", branch = "libcnb_test_local_and_meta_buildpack_support" }
libcnb = "0.13"
libherokubuildpack = "0.13"
libcnb-test = { git = "https://github.com/heroku/libcnb.rs", branch = "libcnb_test_meta_buildpack_support" }
libcnb = "=0.14"
libcnb-data = { git = "https://github.com/heroku/libcnb.rs", branch = "libcnb_test_meta_buildpack_support" }
libherokubuildpack = "=0.14"
serde = "1"
serde_json = "1"
test_support = { path = "./test_support" }
tempfile = "3"
thiserror = "1"
toml = "0.7"
ureq = "2"
commons = { git = "https://github.com/heroku/buildpacks-ruby", branch = "schneems/logging-state-machine-continued" }

[profile.release]
strip = true
6 changes: 5 additions & 1 deletion buildpacks/nodejs-engine/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ impl Buildpack for NodeJsEngineBuildpack {
type Error = NodeJsEngineBuildpackError;

fn detect(&self, context: DetectContext<Self>) -> libcnb::Result<DetectResult, Self::Error> {
let mut plan_builder = BuildPlanBuilder::new().provides("node").provides("npm");
let mut plan_builder = BuildPlanBuilder::new()
.provides("node")
.provides("npm")
.or()
.provides("node");

// If there are common node artifacts, this buildpack should both
// provide and require node so that it may be used without other
Expand Down
1 change: 1 addition & 0 deletions buildpacks/nodejs-npm-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition.workspace = true
publish.workspace = true

[dependencies]
commons.workspace = true
heroku-nodejs-utils.workspace = true
libcnb.workspace = true
libherokubuildpack.workspace = true
Expand Down
38 changes: 17 additions & 21 deletions buildpacks/nodejs-npm-engine/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
use commons::fun_run::{CmdError, CommandWithName};
use heroku_nodejs_utils::vrs::Version;
use libcnb::Env;
use std::process::{Command, ExitStatus};

type Result<T> = std::result::Result<T, Error>;
use std::process::Command;

#[derive(Debug)]
pub(crate) enum Error {
Wait(std::io::Error),
Exit(ExitStatus),
Parse(String),
NpmVersionCommand(CmdError),
ParseNpmVersion(String),
NodeVersionCommand(CmdError),
ParseNodeVersion(String),
}

pub(crate) fn node_version(env: &Env) -> Result<Version> {
pub(crate) fn node_version(env: &Env) -> Result<Version, Error> {
exec_version_command("node", env)
.map_err(Error::NodeVersionCommand)
.and_then(|output| output.parse().map_err(|_| Error::ParseNpmVersion(output)))
}

pub(crate) fn npm_version(env: &Env) -> Result<Version> {
pub(crate) fn npm_version(env: &Env) -> Result<Version, Error> {
exec_version_command("npm", env)
.map_err(Error::NpmVersionCommand)
.and_then(|output| output.parse().map_err(|_| Error::ParseNodeVersion(output)))
}

fn exec_version_command(program: &str, env: &Env) -> Result<Version> {
let output = Command::new(program)
fn exec_version_command(program: &str, env: &Env) -> Result<String, CmdError> {
Command::new(program)
.args(["--version"])
.envs(env)
.output()
.map_err(Error::Wait)?;

if !output.status.success() {
return Err(Error::Exit(output.status));
}

let stdout = String::from_utf8_lossy(&output.stdout);
stdout
.parse::<Version>()
.map_err(|_| Error::Parse(stdout.into_owned()))
.named_output()
.and_then(|output| output.nonzero_captured())
.map(|output| output.stdout_lossy())
}
237 changes: 232 additions & 5 deletions buildpacks/nodejs-npm-engine/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,247 @@
use crate::cmd;
use crate::layers::npm_engine::NpmEngineLayerError;
use commons::output::build_log::{BuildLog, Logger, StartedLogger};
use commons::output::fmt::DEBUG_INFO;
use heroku_nodejs_utils::package_json::PackageJsonError;
use heroku_nodejs_utils::vrs::Requirement;
use indoc::formatdoc;
use libcnb::Error;
use std::fmt::Display;
use std::io::stdout;

const OPEN_A_SUPPORT_TICKET: &str =
"open a support ticket and include the full log output of this build";

const TRY_BUILDING_AGAIN: &str = "try again and to see if the error resolves itself";

#[derive(Debug)]
pub(crate) enum NpmEngineBuildpackError {
PackageJson(PackageJsonError),
MissingNpmEngineRequirement,
InventoryParse(toml::de::Error),
NpmVersionResolve(Requirement),
NpmSetupLayer(NpmEngineLayerError),
NodeVersionCommand(cmd::Error),
NpmVersionCommand(cmd::Error),
NpmEngineLayer(NpmEngineLayerError),
Command(cmd::Error),
}

pub(crate) fn on_error(error: Error<NpmEngineBuildpackError>) {
let logger = BuildLog::new(stdout()).without_buildpack_name();
match error {
Error::BuildpackError(buildpack_error) => on_buildpack_error(buildpack_error, logger),
framework_error => on_framework_error(framework_error, logger),
}
}

fn on_buildpack_error(error: NpmEngineBuildpackError, logger: Box<dyn StartedLogger>) {
match error {
NpmEngineBuildpackError::PackageJson(e) => on_package_json_error(e, logger),
NpmEngineBuildpackError::MissingNpmEngineRequirement => {
on_missing_npm_engine_requirement_error(logger)
}
NpmEngineBuildpackError::InventoryParse(e) => on_inventory_parse_error(e, logger),
NpmEngineBuildpackError::NpmVersionResolve(requirement) => {
on_npm_version_resolve_error(requirement, logger)
}
NpmEngineBuildpackError::NpmEngineLayer(e) => on_npm_engine_layer_error(e, logger),
NpmEngineBuildpackError::Command(e) => on_command_error(e, logger),
}
}

fn on_package_json_error(error: PackageJsonError, logger: Box<dyn StartedLogger>) {
match error {
PackageJsonError::AccessError(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Failed to read package.json
An unexpected error occurred while reading package.json.
Please {TRY_BUILDING_AGAIN}.
"});
}
PackageJsonError::ParseError(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Failed to parse package.json
An unexpected error occurred while parsing package.json.
Please {TRY_BUILDING_AGAIN}.
"});
}
}
}

fn on_missing_npm_engine_requirement_error(logger: Box<dyn StartedLogger>) {
logger.announce().error(&formatdoc! {"
Missing `engines.npm` key in package.json
This buildpack should only run when `engines.npm` is present in package.json. If you do not
have this field declared then please {OPEN_A_SUPPORT_TICKET}.
"});
}

fn on_inventory_parse_error(error: toml::de::Error, logger: Box<dyn StartedLogger>) {
print_error_details(logger, error)
.announce()
.error(&formatdoc! {"
Failed to load npm inventory
An unexpected error occurred while loading the available npm versions.
Please {TRY_BUILDING_AGAIN}.
"})
}

fn on_npm_version_resolve_error(requirement: Requirement, logger: Box<dyn StartedLogger>) {
logger.announce().error(&formatdoc! {"
Could not find a suitable version of npm that matches the requested version declared in
package.json ({requirement}).
Please verify that the requested version range matches a published version of
npm by checking https://www.npmjs.com/package/npm?activeTab=versions or trying the
following command:
npm show 'npm@{requirement}' versions
If no matching version exists then please update the `engines.npm` field in package.json to
a different version range that does have a match.
If a matching version exists then please {OPEN_A_SUPPORT_TICKET}.
"})
}

fn on_npm_engine_layer_error(error: NpmEngineLayerError, logger: Box<dyn StartedLogger>) {
match error {
NpmEngineLayerError::Download(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Failed to download npm
An unexpected error occurred while downloading the npm package. In some cases, this
happens due to an unstable network connection.
Please {TRY_BUILDING_AGAIN}.
"});
}
NpmEngineLayerError::OpenTarball(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Could not open the downloaded npm package file
An unexpected error occurred while opening the downloaded npm package file.
Please {TRY_BUILDING_AGAIN}.
"});
}
NpmEngineLayerError::DecompressTarball(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Could not extract the downloaded npm package contents
An unexpected error occurred while extracting the contents of the downloaded npm
package file.
Please {TRY_BUILDING_AGAIN}.
"});
}
NpmEngineLayerError::RemoveExistingNpmInstall(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Unable to remove the existing npm installation
An unexpected error occurred while removing the existing npm installation.
Please {TRY_BUILDING_AGAIN}.
"});
}
NpmEngineLayerError::InstallNpm(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Unable to install the downloaded npm package
An unexpected error occurred while installing the downloaded npm package.
Please {TRY_BUILDING_AGAIN}.
"});
}
}
}

fn on_command_error(error: cmd::Error, logger: Box<dyn StartedLogger>) {
match error {
cmd::Error::NpmVersionCommand(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Failed to determine npm version information
An unexpected error occurred while executing `npm --version`.
Please {TRY_BUILDING_AGAIN}.
"});
}
cmd::Error::ParseNpmVersion(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Failed to parse npm version information
An unexpected error occurred while parsing the output from `npm --version`.
Please {TRY_BUILDING_AGAIN}.
"});
}
cmd::Error::NodeVersionCommand(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Failed to determine Node version information
An unexpected error occurred while executing `node --version`.
Please {TRY_BUILDING_AGAIN}.
"});
}
cmd::Error::ParseNodeVersion(e) => {
print_error_details(logger, e)
.announce()
.error(&formatdoc! {"
Failed to parse Node version information
An unexpected error occurred while parsing the output from `node --version`.
Please {TRY_BUILDING_AGAIN}.
"});
}
}
}

fn on_framework_error(error: Error<NpmEngineBuildpackError>, logger: Box<dyn StartedLogger>) {
print_error_details(logger, error)
.announce()
.error(&formatdoc! {"
Internal buildpack error
An unexpected internal error was reported by the framework used by this buildpack.
Please {OPEN_A_SUPPORT_TICKET}.
"});
}

pub(crate) fn on_error(_error: Error<NpmEngineBuildpackError>) {
todo!()
fn print_error_details(
logger: Box<dyn StartedLogger>,
error: impl Display,
) -> Box<dyn StartedLogger> {
logger
.section(DEBUG_INFO)
.step(&error.to_string())
.end_section()
}
Loading

0 comments on commit 8447d0c

Please sign in to comment.