Skip to content

Commit fabca6d

Browse files
authored
Replace regex with winnow based parser (#255)
* Introduce Procfile format specification This file is intended to define valid `Procfile` inputs and parser behavior separate from implementation. It may be updated in the future based on upstream requirements (CNB spec or kubernetes spec) or through future discussion. * Replace regex with full parser * Emit warnings from parser in the buildpack * Format parsing error * Rename error struct * Fix use statements * Changelog
1 parent af236d5 commit fabca6d

9 files changed

+551
-64
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Regex based parser has been removed in favor of a `winnow` based parser combinator, the format is now more strict. See [SPEC.md](spec.md) for more details. ([#255](https://github.com/heroku/buildpacks-procfile/pull/255))
13+
- Keys starting with spaces now emit a warning
14+
- Underscore key characters (`_`) are now converted to hyphens (`-`) and emit a warning
15+
- Uppercase key characters are now converted to lowercase and emit a warning
16+
- Invalid keys now error, previously they were ignored
17+
1018
## [3.2.0] - 2024-12-20
1119

1220
### Changed

CONTRIBUTING.md

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ the recommendations and requirements for how to best contribute to Heroku
55
Cloud Native Buildpacks. We strive to obey these as best as possible. As
66
always, thanks for contributing.
77

8+
## CNB Procfile format
9+
10+
The format of a Procfile is defined in the [SPEC.md](SPEC.md) file. If parsing behavior differs between that description it is a bug and either the specification or parser must be updated.
11+
812
## Governance Model: Salesforce Sponsored
913

1014
The intent and goal of open sourcing this project is to increase the contributor

Cargo.lock

+27-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ pedantic = { level = "warn", priority = -1 }
1414
unwrap_used = "warn"
1515

1616
[dependencies]
17+
annotate-snippets = "0.11.5"
1718
bullet_stream = "0.3"
1819
fs-err = "3"
1920
indoc = "2"
2021
libcnb = { version = "0.26", features = ["trace"] }
2122
libherokubuildpack = { version = "0.26", default-features = false, features = ["error", "log"] }
2223
linked-hash-map = "0.5"
23-
regex = "1"
24+
winnow = "0.6.22"
2425

2526
[dev-dependencies]
2627
libcnb-test = "0.26"

SPEC.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# CNB Procfile format specification
2+
3+
This document outlines the format of a `Procfile`, which defines names to process types. For example:
4+
5+
```
6+
web: rails s
7+
worker: bundle exec sidekiq
8+
```
9+
10+
## Differences from Heroku "classic" Procfile
11+
12+
The classic `Procfile` has no formal specification. It is loosely defined based on a regex `"^[[:space:]]*([a-zA-Z0-9_-]+):?\\s+(.*)[[:space:]]*`. This specification is informed by the CNB specification for process names and [kubernetes](https://github.com/heroku/buildpacks-procfile/issues/251).
13+
14+
## Specification
15+
16+
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt).
17+
18+
- Spaces
19+
- The term spaces refers to non-newline whitespace characters such as tab `'\t'` or space `'\s'` characters.
20+
- Each line MUST contain a comment, empty line, or key/value pair.
21+
- Comments
22+
- A line MAY contain a comment
23+
- Comments MUST contain a `#` as the first visible character on each line.
24+
- A comment MAY be proceeded by one or more spaces.
25+
- Empty line
26+
- A Procfile MAY contain empty lines
27+
- An empty line MUST be zero or more spaces followed by a line ending or end of file (EOF).
28+
- Key/Value pairs
29+
- A line MAY contain a key/value pair where the key represents the name of a process and the value represents a command
30+
- A key MUST be separated from its value by a colon (`:`) followed by zero or more spaces.
31+
- Duplicate keys MUST be allowed and the last entry MUST take precedence. A warning SHOULD be issued.
32+
- Key
33+
- A key's first and last character MUST be a lowercase alphanumeric (a-z0-9) character (but not `-`).
34+
- All other key (middle) characters MUST be lowercase alphanumeric (a-z0-9) characters or hyphen `-`.
35+
- Key length MUST be within the range `1..=63`
36+
- An implementation MAY accept `_` as a middle character provided it converts it to `-` and issues a warning.
37+
- An implementation MAY accept an uppercase character provided it is converted to lowercase characters and issues a warning.
38+
- A key MAY be preceded with zero or more spaces provided they are not included in the return key and a warning is issued.
39+
- Value
40+
- A value MUST contain 1 or more non-whitespace characters.
41+
- A value MUST be terminated by a newline or EOF.

src/error.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use crate::launch::ProcfileConversionError;
2-
use crate::procfile::ProcfileParsingError;
2+
use crate::procfile::ProcfileError;
33
use bullet_stream::Print;
44
use indoc::formatdoc;
55

66
#[derive(Debug)]
77
pub(crate) enum ProcfileBuildpackError {
88
CannotReadProcfileContents(std::io::Error),
9-
ProcfileParsingError(ProcfileParsingError),
9+
ProcfileParsingError(ProcfileError),
1010
ProcfileConversionError(ProcfileConversionError),
1111
}
1212

@@ -23,9 +23,16 @@ pub(crate) fn error_handler(buildpack_error: ProcfileBuildpackError) {
2323
Underlying cause was: {io_error}
2424
"});
2525
}
26-
// There are currently no ways in which parsing can fail, however we will add some in the future:
27-
// https://github.com/heroku/buildpacks-procfile/issues/73
28-
ProcfileBuildpackError::ProcfileParsingError(parsing_error) => match parsing_error {},
26+
ProcfileBuildpackError::ProcfileParsingError(parsing_error) =>
27+
build_output.error(formatdoc! {"
28+
Invalid Procfile format
29+
30+
The provided `Procfile` contains an invalid format and the buildpack cannot continue.
31+
32+
To fix this problem please correct the following error and commit the results to git:
33+
34+
{parsing_error}
35+
"}),
2936
ProcfileBuildpackError::ProcfileConversionError(conversion_error) => match conversion_error
3037
{
3138
ProcfileConversionError::InvalidProcessType(libcnb_error) => {

src/main.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ impl Buildpack for ProcfileBuildpack {
4343
.map_err(ProcfileBuildpackError::ProcfileParsingError)
4444
})?;
4545

46-
if procfile.is_empty() {
47-
bullet = bullet.sub_bullet("Empty file, no processes defined");
48-
} else {
49-
for (name, command) in &procfile.processes {
50-
bullet = bullet.sub_bullet(format!("{name}: {}", style::command(command)));
51-
}
46+
let warning_prefix = style::important("WARNING:");
47+
for message in &procfile.warnings {
48+
bullet = bullet.sub_bullet(format!("{warning_prefix} {message}"));
49+
}
50+
51+
for (name, command) in &procfile.processes {
52+
bullet = bullet.sub_bullet(format!("{name}: {cmd}", cmd = style::command(command)));
5253
}
5354
bullet.done().done();
5455

0 commit comments

Comments
 (0)