From 5c3e32aee2dd74315f2578f850100f9171a98975 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 23 Sep 2020 23:25:15 -0400 Subject: [PATCH 1/3] refactor: make procfile validation much more explicit - only allow comments, whitespace, and process-type/command combinations - strictly parse process types as dns label names - validate that commands and non-zero Additionally, output the line number that validates the rules for easier debugging. --- main.go | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 6d4a931..39e8870 100644 --- a/main.go +++ b/main.go @@ -143,7 +143,7 @@ func writeProcfile(path string, delimiter string, entries []procfileEntry) error func parseProcfile(path string, delimiter string) ([]procfileEntry, error) { var entries []procfileEntry - reCmd, _ := regexp.Compile(`^([A-Za-z0-9_-]+)` + delimiter + `\s*(.+)$`) + reCmd, _ := regexp.Compile(`^([a-z0-9]([-a-z0-9]*[a-z0-9])?)` + delimiter + `\s*(.+)$`) reComment, _ := regexp.Compile(`^(.*)\s#.+$`) text, err := getProcfile(path) @@ -151,24 +151,36 @@ func parseProcfile(path string, delimiter string) ([]procfileEntry, error) { return entries, err } + lineNumber := 0 names := make(map[string]bool) scanner := bufio.NewScanner(strings.NewReader(text)) for scanner.Scan() { + lineNumber++ line := scanner.Text() + line = strings.TrimSpace(line) + if len(line) == 0 { continue } params := reCmd.FindStringSubmatch(line) - if len(params) != 3 { - debugMessage(fmt.Sprintf("No matching params in line: %v", line)) + isCommand := len(params) == 4 + isComment := strings.HasPrefix(line, "#") + if isComment { continue } - name, cmd := params[1], params[2] + if !isCommand { + return entries, fmt.Errorf("invalid line in procfile, line %d", lineNumber) + } + + name, cmd := params[1], params[3] + if len(name) > 63 { + return entries, fmt.Errorf("process name over 63 characters, line %d", lineNumber) + } if names[name] { - return entries, fmt.Errorf("process names must be unique") + return entries, fmt.Errorf("process names must be unique, line %d", lineNumber) } names[name] = true @@ -177,6 +189,15 @@ func parseProcfile(path string, delimiter string) ([]procfileEntry, error) { cmd = commentParams[1] } + cmd = strings.TrimSpace(cmd) + if strings.HasPrefix(cmd, "#") { + return entries, fmt.Errorf("comment specified in place of command, line %d", lineNumber) + } + + if len(cmd) == 0 { + return entries, fmt.Errorf("no command specified, line %d", lineNumber) + } + entries = append(entries, procfileEntry{name, cmd}) } From a4883145482e99c8c188fb71d5cd50eb74679993 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 23 Sep 2020 23:26:19 -0400 Subject: [PATCH 2/3] docs: document the Procfile format Since this isn't well-specified online, explicitly state what is a valid Procfile so that users aren't confused as to why their files do not validate. --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 6f1eb69..d8a9b40 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,41 @@ Install it using the "go get" command: go get github.com/josegonzalez/go-procfile-util +## What is a Procfile + +A Procfile is a file that was [promoted by Heroku](https://blog.heroku.com/the_new_heroku_1_process_model_procfile) for their platform as an easy way to specify one or more distinct processes to run within Heroku. This format has since been picked up by various other tools and platforms. + +The `procfile-util` tool expects a Procfile to be defined as one or more lines containing one of: + +- a comment (preceeded by a `#` symbol) +- a process-type/command combination (with optional trailing whitespace or trailing comment) + - when there is a trailing comment, the `#` symbol _must_ be preceeded by one or more `whitespace` characters. +- a blank line (with optional trailing whitespace) + +Comments and blank lines are ignored, while process-type/command combinations look like the following: + +``` +: +``` + +The syntax is defined as follows: + +- `` – a valid DNS Label Name as per [RFC 1123](https://tools.ietf.org/html/rfc1123), a process type is a name for your command, such as `web`, `worker`, `urgentworker`, `clock`, etc. +- `` – a command used to launch the process, such as `rake jobs:work` + +This syntax differs common interpretations of validd `` values in that we define the process type as a DNS Label name, versus the regex `[A-Za-z0-9_]+`. The reason for this is that processes defined within Procfiles are commonly used in DNS entries. Rather than have a second level of platform-specific validation in place, this project implicitly defines the format for the process-type. + +Given the above, a valid process type can be generalized to the following rules (as taken from the [Kubernetes documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names)): + +- contain at most 63 characters +- contain only lowercase alphanumeric characters or '-' +- start with an alphanumeric character +- end with an alphanumeric character + +Additionally, should a Procfile contain a line that does not match one of the three patterns mentioned above, the entire Procfile is considered invalid, and will not be processed. This is to avoid issues where a Procfile may contain merge conflicts or other improper content, thus resulting in unwanted runtime behavior for applications. + +Finally, process types within a Procfile may not overlap and must be unique. Rather than assuming that the last or first specified is correct, `procfile-util` will fail to parse the Procfile with the relevant error. + ## Usage All commands take a `-P` or `--procfile` flag to specify an alternative `Procfile` path. If not specified, `procfile-util` will attempt to read the `Procfile` from the current directory. From 97399e0818428cefd70503cb71a6ef1971db74c0 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 23 Sep 2020 23:31:19 -0400 Subject: [PATCH 3/3] Release 0.9.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9d33667..fc20608 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ MAINTAINER_NAME = Jose Diaz-Gonzalez REPOSITORY = go-procfile-util HARDWARE = $(shell uname -m) SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]') -BASE_VERSION ?= 0.8.2 +BASE_VERSION ?= 0.9.0 IMAGE_NAME ?= $(MAINTAINER)/$(REPOSITORY) PACKAGECLOUD_REPOSITORY ?= dokku/dokku-betafish