From 70d4ee0c735b7ca7e6bb8bfb6b3d94520fa2f63b Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 27 Oct 2020 14:24:50 -0400 Subject: [PATCH 1/7] refactor: make more of the package exportable --- .gitignore | 2 +- Makefile | 2 +- commands/check_command.go | 26 + commands/commands.go | 71 +++ commands/delete_command.go | 17 + commands/exists_command.go | 19 + commands/expand_command.go | 35 ++ commands/export_command.go | 142 +++++ commands/list_command.go | 14 + commands/set_command.go | 18 + commands/show_command.go | 32 + export.go | 348 ----------- bindata.go => export/bindata.go | 24 +- export/export.go | 82 +++ export/export_launchd.go | 38 ++ export/export_runit.go | 90 +++ export/export_systemd.go | 55 ++ export/export_systemd_user.go | 40 ++ export/export_sysv.go | 38 ++ export/export_upstart.go | 60 ++ .../templates}/launchd/launchd.plist.tmpl | 0 .../templates}/runit/log/run.tmpl | 0 .../templates}/runit/run.tmpl | 0 .../systemd-user/default/program.service.tmpl | 0 .../systemd/default/control.target.tmpl | 0 .../systemd/default/program.service.tmpl | 0 .../templates}/sysv/default/init.sh.tmpl | 0 .../upstart/default/control.conf.tmpl | 0 .../upstart/default/process-type.conf.tmpl | 0 .../upstart/default/program.conf.tmpl | 0 fixtures/strict/Procfile | 10 + fixtures/v1/Procfile | 10 + main.go | 569 +----------------- procfile/entry.go | 35 ++ procfile/io.go | 79 +++ procfile/parse.go | 120 ++++ 36 files changed, 1059 insertions(+), 917 deletions(-) create mode 100644 commands/check_command.go create mode 100644 commands/commands.go create mode 100644 commands/delete_command.go create mode 100644 commands/exists_command.go create mode 100644 commands/expand_command.go create mode 100644 commands/export_command.go create mode 100644 commands/list_command.go create mode 100644 commands/set_command.go create mode 100644 commands/show_command.go delete mode 100644 export.go rename bindata.go => export/bindata.go (98%) create mode 100644 export/export.go create mode 100644 export/export_launchd.go create mode 100644 export/export_runit.go create mode 100644 export/export_systemd.go create mode 100644 export/export_systemd_user.go create mode 100644 export/export_sysv.go create mode 100644 export/export_upstart.go rename {templates => export/templates}/launchd/launchd.plist.tmpl (100%) rename {templates => export/templates}/runit/log/run.tmpl (100%) rename {templates => export/templates}/runit/run.tmpl (100%) rename {templates => export/templates}/systemd-user/default/program.service.tmpl (100%) rename {templates => export/templates}/systemd/default/control.target.tmpl (100%) rename {templates => export/templates}/systemd/default/program.service.tmpl (100%) rename {templates => export/templates}/sysv/default/init.sh.tmpl (100%) rename {templates => export/templates}/upstart/default/control.conf.tmpl (100%) rename {templates => export/templates}/upstart/default/process-type.conf.tmpl (100%) rename {templates => export/templates}/upstart/default/program.conf.tmpl (100%) create mode 100644 fixtures/strict/Procfile create mode 100644 fixtures/v1/Procfile create mode 100644 procfile/entry.go create mode 100644 procfile/io.go create mode 100644 procfile/parse.go diff --git a/.gitignore b/.gitignore index efee0c0..f4207f3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,6 @@ validation/* # .env files .env* -*Procfile +*.Procfile procfile-util diff --git a/Makefile b/Makefile index 9ee5b0d..9ab3fec 100644 --- a/Makefile +++ b/Makefile @@ -165,4 +165,4 @@ validate: prebuild: go get -u github.com/go-bindata/go-bindata/... - go-bindata templates/... + cd export && go-bindata -pkg export templates/... diff --git a/commands/check_command.go b/commands/check_command.go new file mode 100644 index 0000000..974c557 --- /dev/null +++ b/commands/check_command.go @@ -0,0 +1,26 @@ +package commands + +import ( + "fmt" + "os" + "strings" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + + +func CheckCommand(entries []procfile.ProcfileEntry) bool { + if len(entries) == 0 { + fmt.Fprintf(os.Stderr, "no processes defined\n") + return false + } + + names := []string{} + for _, entry := range entries { + names = append(names, entry.Name) + } + + processNames := strings.Join(names[:], ", ") + fmt.Printf("valid procfile detected %v\n", processNames) + return true +} diff --git a/commands/commands.go b/commands/commands.go new file mode 100644 index 0000000..f4cf125 --- /dev/null +++ b/commands/commands.go @@ -0,0 +1,71 @@ +package commands + +import ( + "io/ioutil" + "os" + "strconv" + "strings" + + "github.com/joho/godotenv" + "github.com/josegonzalez/go-procfile-util/procfile" +) + +const portEnvVar = "PORT" + +func expandEnv(e procfile.ProcfileEntry, envPath string, allowEnv bool, defaultPort int) (string, error) { + baseExpandFunc := func(key string) string { + if key == "PS" { + return os.Getenv("PS") + } + if key == portEnvVar { + return strconv.Itoa(defaultPort) + } + return "" + } + + expandFunc := func(key string) string { + return baseExpandFunc(key) + } + + if allowEnv { + expandFunc = func(key string) string { + value := os.Getenv(key) + if value == "" { + value = baseExpandFunc(key) + } + return value + } + } + + if envPath != "" { + b, err := ioutil.ReadFile(envPath) + if err != nil { + return "", err + } + + content := string(b) + env, err := godotenv.Unmarshal(content) + if err != nil { + return "", err + } + + expandFunc = func(key string) string { + if val, ok := env[key]; ok { + return val + } + value := "" + if allowEnv { + value = os.Getenv(key) + } + if value == "" { + value = baseExpandFunc(key) + } + return value + } + } + + os.Setenv("PS", e.Name) + os.Setenv("EXPENV_PARENTHESIS", "$(") + s := strings.Replace(e.Command, "$(", "${EXPENV_PARENTHESIS}", -1) + return os.Expand(s, expandFunc), nil +} diff --git a/commands/delete_command.go b/commands/delete_command.go new file mode 100644 index 0000000..d8f08c3 --- /dev/null +++ b/commands/delete_command.go @@ -0,0 +1,17 @@ +package commands + +import ( + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func DeleteCommand(entries []procfile.ProcfileEntry, processType string, writePath string, stdout bool, delimiter string, path string) bool { + var validEntries []procfile.ProcfileEntry + for _, entry := range entries { + if processType == entry.Name { + continue + } + validEntries = append(validEntries, entry) + } + + return procfile.OutputProcfile(path, writePath, delimiter, stdout, validEntries) +} diff --git a/commands/exists_command.go b/commands/exists_command.go new file mode 100644 index 0000000..90b0192 --- /dev/null +++ b/commands/exists_command.go @@ -0,0 +1,19 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ExistsCommand(entries []procfile.ProcfileEntry, processType string) bool { + for _, entry := range entries { + if processType == entry.Name { + return true + } + } + + fmt.Fprint(os.Stderr, "no matching process entry found\n") + return false +} diff --git a/commands/expand_command.go b/commands/expand_command.go new file mode 100644 index 0000000..b0ebc2f --- /dev/null +++ b/commands/expand_command.go @@ -0,0 +1,35 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ExpandCommand(entries []procfile.ProcfileEntry, envPath string, allowGetenv bool, processType string, defaultPort int, delimiter string) bool { + hasErrors := false + var expandedEntries []procfile.ProcfileEntry + for _, entry := range entries { + command, err := expandEnv(entry, envPath, allowGetenv, defaultPort) + if err != nil { + fmt.Fprintf(os.Stderr, "error processing command: %s\n", err) + hasErrors = true + } + + entry.Command = command + expandedEntries = append(expandedEntries, entry) + } + + if hasErrors { + return false + } + + for _, entry := range expandedEntries { + if processType == "" || processType == entry.Name { + fmt.Printf("%v%v %v\n", entry.Name, delimiter, entry.Command) + } + } + + return true +} diff --git a/commands/export_command.go b/commands/export_command.go new file mode 100644 index 0000000..b19f61f --- /dev/null +++ b/commands/export_command.go @@ -0,0 +1,142 @@ +package commands + +import ( + "fmt" + "io/ioutil" + "os" + "os/user" + "strconv" + "strings" + + "github.com/josegonzalez/go-procfile-util/export" + "github.com/josegonzalez/go-procfile-util/procfile" + "github.com/joho/godotenv" +) + +func ExportCommand(entries []procfile.ProcfileEntry, app string, description string, envPath string, format string, formation string, group string, home string, limitCoredump string, limitCputime string, limitData string, limitFileSize string, limitLockedMemory string, limitOpenFiles string, limitUserProcesses string, limitPhysicalMemory string, limitStackSize string, location string, logPath string, nice string, prestart string, workingDirectoryPath string, runPath string, timeout int, processUser string, defaultPort int) bool { + if format == "" { + fmt.Fprintf(os.Stderr, "no format specified\n") + return false + } + if location == "" { + fmt.Fprintf(os.Stderr, "no output location specified\n") + return false + } + + formats := map[string]export.ExportFunc{ + "launchd": export.ExportLaunchd, + "runit": export.ExportRunit, + "systemd": export.ExportSystemd, + "systemd-user": export.ExportSystemdUser, + "sysv": export.ExportSysv, + "upstart": export.ExportUpstart, + } + + if _, ok := formats[format]; !ok { + fmt.Fprintf(os.Stderr, "invalid format type: %s\n", format) + return false + } + + formations, err := procfile.ParseFormation(formation) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if processUser == "" { + processUser = app + } + + if group == "" { + group = app + } + + u, err := user.Current() + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if home == "" { + home = "/home/" + u.Username + } + + env := make(map[string]string) + if envPath != "" { + b, err := ioutil.ReadFile(envPath) + if err != nil { + fmt.Fprintf(os.Stderr, "error reading env file: %s\n", err) + return false + } + + content := string(b) + env, err = godotenv.Unmarshal(content) + if err != nil { + fmt.Fprintf(os.Stderr, "error parsing env file: %s\n", err) + return false + } + } + + vars := make(map[string]interface{}) + vars["app"] = app + vars["description"] = description + vars["env"] = env + vars["group"] = group + vars["home"] = home + vars["log"] = logPath + vars["location"] = location + vars["limit_coredump"] = limitCoredump + vars["limit_cputime"] = limitCputime + vars["limit_data"] = limitData + vars["limit_file_size"] = limitFileSize + vars["limit_locked_memory"] = limitLockedMemory + vars["limit_open_files"] = limitOpenFiles + vars["limit_user_processes"] = limitUserProcesses + vars["limit_physical_memory"] = limitPhysicalMemory + vars["limit_stack_size"] = limitStackSize + vars["nice"] = nice + vars["prestart"] = prestart + vars["working_directory"] = workingDirectoryPath + vars["timeout"] = strconv.Itoa(timeout) + vars["ulimit_shell"] = ulimitShell(limitCoredump, limitCputime, limitData, limitFileSize, limitLockedMemory, limitOpenFiles, limitUserProcesses, limitPhysicalMemory, limitStackSize) + vars["user"] = processUser + + if fn, ok := formats[format]; ok { + return fn(app, entries, formations, location, defaultPort, vars) + } + + return false +} + +func ulimitShell(limitCoredump string, limitCputime string, limitData string, limitFileSize string, limitLockedMemory string, limitOpenFiles string, limitUserProcesses string, limitPhysicalMemory string, limitStackSize string) string { + s := []string{} + if limitCoredump != "" { + s = append(s, "ulimit -c ${limit_coredump}") + } + if limitCputime != "" { + s = append(s, "ulimit -t ${limit_cputime}") + } + if limitData != "" { + s = append(s, "ulimit -d ${limit_data}") + } + if limitFileSize != "" { + s = append(s, "ulimit -f ${limit_file_size}") + } + if limitLockedMemory != "" { + s = append(s, "ulimit -l ${limit_locked_memory}") + } + if limitOpenFiles != "" { + s = append(s, "ulimit -n ${limit_open_files}") + } + if limitUserProcesses != "" { + s = append(s, "ulimit -u ${limit_user_processes}") + } + if limitPhysicalMemory != "" { + s = append(s, "ulimit -m ${limit_physical_memory}") + } + if limitStackSize != "" { + s = append(s, "ulimit -s ${limit_stack_size}") + } + + return strings.Join(s, "\n") +} diff --git a/commands/list_command.go b/commands/list_command.go new file mode 100644 index 0000000..7a0d6f2 --- /dev/null +++ b/commands/list_command.go @@ -0,0 +1,14 @@ +package commands + +import ( + "fmt" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ListCommand(entries []procfile.ProcfileEntry) bool { + for _, entry := range entries { + fmt.Printf("%v\n", entry.Name) + } + return true +} diff --git a/commands/set_command.go b/commands/set_command.go new file mode 100644 index 0000000..d527d45 --- /dev/null +++ b/commands/set_command.go @@ -0,0 +1,18 @@ +package commands + +import ( + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func SetCommand(entries []procfile.ProcfileEntry, processType string, command string, writePath string, stdout bool, delimiter string, path string) bool { + var validEntries []procfile.ProcfileEntry + validEntries = append(validEntries, procfile.ProcfileEntry{processType, command}) + for _, entry := range entries { + if processType == entry.Name { + continue + } + validEntries = append(validEntries, entry) + } + + return procfile.OutputProcfile(path, writePath, delimiter, stdout, validEntries) +} \ No newline at end of file diff --git a/commands/show_command.go b/commands/show_command.go new file mode 100644 index 0000000..8c72a54 --- /dev/null +++ b/commands/show_command.go @@ -0,0 +1,32 @@ +package commands + +import ( + "fmt" + "os" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ShowCommand(entries []procfile.ProcfileEntry, envPath string, allowGetenv bool, processType string, defaultPort int) bool { + var foundEntry procfile.ProcfileEntry + for _, entry := range entries { + if processType == entry.Name { + foundEntry = entry + break + } + } + + if foundEntry == (procfile.ProcfileEntry{}) { + fmt.Fprintf(os.Stderr, "no matching process entry found\n") + return false + } + + command, err := expandEnv(foundEntry, envPath, allowGetenv, defaultPort) + if err != nil { + fmt.Fprintf(os.Stderr, "error processing command: %s\n", err) + return false + } + + fmt.Printf("%v\n", command) + return true +} diff --git a/export.go b/export.go deleted file mode 100644 index 79db037..0000000 --- a/export.go +++ /dev/null @@ -1,348 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strconv" - "text/template" -) - -func exportLaunchd(app string, entries []ProcfileEntry, formations map[string]FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { - l, err := loadTemplate("launchd", "templates/launchd/launchd.plist.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - if _, err := os.Stat(location + "/Library/LaunchDaemons/"); os.IsNotExist(err) { - os.MkdirAll(location+"/Library/LaunchDaemons/", os.ModePerm) - } - - for i, entry := range entries { - num := 1 - count := processCount(entry, formations) - - for num <= count { - processName := fmt.Sprintf("%s-%d", entry.Name, num) - port := portFor(i, num, defaultPort) - config := templateVars(app, entry, processName, num, port, vars) - if !writeOutput(l, fmt.Sprintf("%s/Library/LaunchDaemons/%s-%s.plist", location, app, processName), config) { - return false - } - - num += 1 - } - } - - return true -} - -func exportRunit(app string, entries []ProcfileEntry, formations map[string]FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { - r, err := loadTemplate("run", "templates/runit/run.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - l, err := loadTemplate("log", "templates/runit/log/run.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - if _, err := os.Stat(location + "/service"); os.IsNotExist(err) { - os.MkdirAll(location+"/service", os.ModePerm) - } - - for i, entry := range entries { - num := 1 - count := processCount(entry, formations) - - for num <= count { - processDirectory := fmt.Sprintf("%s-%s-%d", app, entry.Name, num) - folderPath := location + "/service/" + processDirectory - processName := fmt.Sprintf("%s-%d", entry.Name, num) - - fmt.Println("creating:", folderPath) - os.MkdirAll(folderPath, os.ModePerm) - - fmt.Println("creating:", folderPath+"/env") - os.MkdirAll(folderPath+"/env", os.ModePerm) - - fmt.Println("creating:", folderPath+"/log") - os.MkdirAll(folderPath+"/log", os.ModePerm) - - port := portFor(i, num, defaultPort) - config := templateVars(app, entry, processName, num, port, vars) - - if !writeOutput(r, fmt.Sprintf("%s/run", folderPath), config) { - return false - } - - env, ok := config["env"].(map[string]string) - if !ok { - fmt.Fprintf(os.Stderr, "invalid env map\n") - return false - } - - env["PORT"] = strconv.Itoa(port) - env["PS"] = app + "-" + processName - - for key, value := range env { - fmt.Println("writing:", folderPath+"/env/"+key) - f, err := os.Create(folderPath + "/env/" + key) - if err != nil { - fmt.Fprintf(os.Stderr, "error creating file: %s\n", err) - return false - } - defer f.Close() - - if _, err = f.WriteString(value); err != nil { - fmt.Fprintf(os.Stderr, "error writing output: %s\n", err) - return false - } - - if err = f.Sync(); err != nil { - fmt.Fprintf(os.Stderr, "error syncing output: %s\n", err) - return false - } - } - - if !writeOutput(l, fmt.Sprintf("%s/log/run", folderPath), config) { - return false - } - - num += 1 - } - } - - return true -} - -func exportSystemd(app string, entries []ProcfileEntry, formations map[string]FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { - t, err := loadTemplate("target", "templates/systemd/default/control.target.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - s, err := loadTemplate("service", "templates/systemd/default/program.service.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - if _, err := os.Stat(location + "/etc/systemd/system/"); os.IsNotExist(err) { - os.MkdirAll(location+"/etc/systemd/system/", os.ModePerm) - } - - processes := []string{} - for i, entry := range entries { - num := 1 - count := processCount(entry, formations) - - for num <= count { - processName := fmt.Sprintf("%s-%d", entry.Name, num) - fileName := fmt.Sprintf("%s.%d", entry.Name, num) - processes = append(processes, fmt.Sprintf(app+"-%s.service", fileName)) - - port := portFor(i, num, defaultPort) - config := templateVars(app, entry, processName, num, port, vars) - if !writeOutput(s, fmt.Sprintf("%s/etc/systemd/system/%s-%s.service", location, app, fileName), config) { - return false - } - - num += 1 - } - } - - config := vars - config["processes"] = processes - if writeOutput(t, fmt.Sprintf("%s/etc/systemd/system/%s.target", location, app), config) { - fmt.Println("You will want to run 'systemctl --system daemon-reload' to activate the service on the target host") - return true - } - - return true -} - -func exportSystemdUser(app string, entries []ProcfileEntry, formations map[string]FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { - s, err := loadTemplate("service", "templates/systemd-user/default/program.service.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - path := vars["home"].(string) + "/.config/systemd/user/" - if _, err := os.Stat(location + path); os.IsNotExist(err) { - os.MkdirAll(location+path, os.ModePerm) - } - - for i, entry := range entries { - num := 1 - count := processCount(entry, formations) - - for num <= count { - processName := fmt.Sprintf("%s-%d", entry.Name, num) - port := portFor(i, num, defaultPort) - config := templateVars(app, entry, processName, num, port, vars) - if !writeOutput(s, fmt.Sprintf("%s%s%s-%s.service", location, path, app, processName), config) { - return false - } - - num += 1 - } - } - - fmt.Println("You will want to run 'systemctl --user daemon-reload' to activate the service on the target host") - return true -} - -func exportSysv(app string, entries []ProcfileEntry, formations map[string]FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { - l, err := loadTemplate("launchd", "templates/sysv/default/init.sh.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - if _, err := os.Stat(location + "/etc/init.d/"); os.IsNotExist(err) { - os.MkdirAll(location+"/etc/init.d/", os.ModePerm) - } - - for i, entry := range entries { - num := 1 - count := processCount(entry, formations) - - for num <= count { - processName := fmt.Sprintf("%s-%d", entry.Name, num) - port := portFor(i, num, defaultPort) - config := templateVars(app, entry, processName, num, port, vars) - if !writeOutput(l, fmt.Sprintf("%s/etc/init.d/%s-%s", location, app, processName), config) { - return false - } - - num += 1 - } - } - - return true -} -func exportUpstart(app string, entries []ProcfileEntry, formations map[string]FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { - p, err := loadTemplate("program", "templates/upstart/default/program.conf.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - c, err := loadTemplate("app", "templates/upstart/default/control.conf.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - t, err := loadTemplate("process-type", "templates/upstart/default/process-type.conf.tmpl") - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - if _, err := os.Stat(location + "/etc/init/"); os.IsNotExist(err) { - os.MkdirAll(location+"/etc/init/", os.ModePerm) - } - - for i, entry := range entries { - num := 1 - count := processCount(entry, formations) - - if count > 0 { - config := vars - config["process_type"] = entry.Name - if !writeOutput(t, fmt.Sprintf("%s/etc/init/%s-%s.conf", location, app, entry.Name), config) { - return false - } - } - - for num <= count { - processName := fmt.Sprintf("%s-%d", entry.Name, num) - fileName := fmt.Sprintf("%s-%d", entry.Name, num) - port := portFor(i, num, defaultPort) - config := templateVars(app, entry, processName, num, port, vars) - if !writeOutput(p, fmt.Sprintf("%s/etc/init/%s-%s.conf", location, app, fileName), config) { - return false - } - - num += 1 - } - } - - config := vars - return writeOutput(c, fmt.Sprintf("%s/etc/init/%s.conf", location, app), config) -} -func processCount(entry ProcfileEntry, formations map[string]FormationEntry) int { - count := 0 - if f, ok := formations["all"]; ok { - count = f.Count - } - if f, ok := formations[entry.Name]; ok { - count = f.Count - } - return count -} - -func portFor(processIndex int, instance int, base int) int { - return 5000 + (processIndex * 100) + (instance - 1) -} - -func templateVars(app string, entry ProcfileEntry, processName string, num int, port int, vars map[string]interface{}) map[string]interface{} { - config := vars - config["args"] = entry.args() - config["args_escaped"] = entry.argsEscaped() - config["command"] = entry.Command - config["command_list"] = entry.commandList() - config["num"] = num - config["port"] = port - config["process_name"] = processName - config["process_type"] = entry.Name - config["program"] = entry.program() - config["ps"] = app + "-" + entry.Name + "." + strconv.Itoa(num) - if config["description"] == "" { - config["description"] = fmt.Sprintf("%s.%s process for %s", entry.Name, strconv.Itoa(num), app) - } - - return config -} - -func writeOutput(t *template.Template, outputPath string, variables map[string]interface{}) bool { - fmt.Println("writing:", outputPath) - f, err := os.Create(outputPath) - if err != nil { - fmt.Fprintf(os.Stderr, "error creating file: %s\n", err) - return false - } - defer f.Close() - - if err = t.Execute(f, variables); err != nil { - fmt.Fprintf(os.Stderr, "error writing output: %s\n", err) - return false - } - - if err := os.Chmod(outputPath, 0755); err != nil { - fmt.Fprintf(os.Stderr, "error setting mode: %s\n", err) - return false - } - - return true -} - -func loadTemplate(name string, filename string) (*template.Template, error) { - asset, err := Asset(filename) - if err != nil { - return nil, err - } - - t, err := template.New(name).Parse(string(asset)) - if err != nil { - return nil, fmt.Errorf("error parsing template: %s", err) - } - - return t, nil -} diff --git a/bindata.go b/export/bindata.go similarity index 98% rename from bindata.go rename to export/bindata.go index 328249c..e0f039f 100644 --- a/bindata.go +++ b/export/bindata.go @@ -1,4 +1,4 @@ -// Code generated for package main by go-bindata DO NOT EDIT. (@generated) +// Code generated for package export by go-bindata DO NOT EDIT. (@generated) // sources: // templates/launchd/launchd.plist.tmpl // templates/runit/log/run.tmpl @@ -10,7 +10,7 @@ // templates/upstart/default/control.conf.tmpl // templates/upstart/default/process-type.conf.tmpl // templates/upstart/default/program.conf.tmpl -package main +package export import ( "bytes" @@ -101,7 +101,7 @@ func templatesLaunchdLaunchdPlistTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/launchd/launchd.plist.tmpl", size: 1294, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + info := bindataFileInfo{name: "templates/launchd/launchd.plist.tmpl", size: 1294, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -121,7 +121,7 @@ func templatesRunitLogRunTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/runit/log/run.tmpl", size: 186, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + info := bindataFileInfo{name: "templates/runit/log/run.tmpl", size: 186, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -141,7 +141,7 @@ func templatesRunitRunTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/runit/run.tmpl", size: 154, mode: os.FileMode(420), modTime: time.Unix(1584472497, 0)} + info := bindataFileInfo{name: "templates/runit/run.tmpl", size: 154, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -161,7 +161,7 @@ func templatesSystemdDefaultControlTargetTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/systemd/default/control.target.tmpl", size: 106, mode: os.FileMode(420), modTime: time.Unix(1584472497, 0)} + info := bindataFileInfo{name: "templates/systemd/default/control.target.tmpl", size: 106, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -181,7 +181,7 @@ func templatesSystemdDefaultProgramServiceTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/systemd/default/program.service.tmpl", size: 876, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + info := bindataFileInfo{name: "templates/systemd/default/program.service.tmpl", size: 876, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -201,7 +201,7 @@ func templatesSystemdUserDefaultProgramServiceTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/systemd-user/default/program.service.tmpl", size: 551, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + info := bindataFileInfo{name: "templates/systemd-user/default/program.service.tmpl", size: 551, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -221,7 +221,7 @@ func templatesSysvDefaultInitShTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/sysv/default/init.sh.tmpl", size: 6176, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + info := bindataFileInfo{name: "templates/sysv/default/init.sh.tmpl", size: 6176, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -241,7 +241,7 @@ func templatesUpstartDefaultControlConfTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/upstart/default/control.conf.tmpl", size: 76, mode: os.FileMode(420), modTime: time.Unix(1584472497, 0)} + info := bindataFileInfo{name: "templates/upstart/default/control.conf.tmpl", size: 76, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -261,7 +261,7 @@ func templatesUpstartDefaultProcessTypeConfTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/upstart/default/process-type.conf.tmpl", size: 120, mode: os.FileMode(420), modTime: time.Unix(1584472497, 0)} + info := bindataFileInfo{name: "templates/upstart/default/process-type.conf.tmpl", size: 120, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -281,7 +281,7 @@ func templatesUpstartDefaultProgramConfTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/upstart/default/program.conf.tmpl", size: 983, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + info := bindataFileInfo{name: "templates/upstart/default/program.conf.tmpl", size: 983, mode: os.FileMode(420), modTime: time.Unix(1603738030, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/export/export.go b/export/export.go new file mode 100644 index 0000000..e8c45c3 --- /dev/null +++ b/export/export.go @@ -0,0 +1,82 @@ +package export + +import ( + "fmt" + "os" + "strconv" + "text/template" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +type ExportFunc func(string, []procfile.ProcfileEntry, map[string]procfile.FormationEntry, string, int, map[string]interface{}) bool + +func processCount(entry procfile.ProcfileEntry, formations map[string]procfile.FormationEntry) int { + count := 0 + if f, ok := formations["all"]; ok { + count = f.Count + } + if f, ok := formations[entry.Name]; ok { + count = f.Count + } + return count +} + +func portFor(processIndex int, instance int, base int) int { + return 5000 + (processIndex * 100) + (instance - 1) +} + +func templateVars(app string, entry procfile.ProcfileEntry, processName string, num int, port int, vars map[string]interface{}) map[string]interface{} { + config := vars + config["args"] = entry.Args() + config["args_escaped"] = entry.ArgsEscaped() + config["command"] = entry.Command + config["command_list"] = entry.CommandList() + config["num"] = num + config["port"] = port + config["process_name"] = processName + config["process_type"] = entry.Name + config["program"] = entry.Program() + config["ps"] = app + "-" + entry.Name + "." + strconv.Itoa(num) + if config["description"] == "" { + config["description"] = fmt.Sprintf("%s.%s process for %s", entry.Name, strconv.Itoa(num), app) + } + + return config +} + +func writeOutput(t *template.Template, outputPath string, variables map[string]interface{}) bool { + fmt.Println("writing:", outputPath) + f, err := os.Create(outputPath) + if err != nil { + fmt.Fprintf(os.Stderr, "error creating file: %s\n", err) + return false + } + defer f.Close() + + if err = t.Execute(f, variables); err != nil { + fmt.Fprintf(os.Stderr, "error writing output: %s\n", err) + return false + } + + if err := os.Chmod(outputPath, 0755); err != nil { + fmt.Fprintf(os.Stderr, "error setting mode: %s\n", err) + return false + } + + return true +} + +func loadTemplate(name string, filename string) (*template.Template, error) { + asset, err := Asset(filename) + if err != nil { + return nil, err + } + + t, err := template.New(name).Parse(string(asset)) + if err != nil { + return nil, fmt.Errorf("error parsing template: %s", err) + } + + return t, nil +} diff --git a/export/export_launchd.go b/export/export_launchd.go new file mode 100644 index 0000000..433aa34 --- /dev/null +++ b/export/export_launchd.go @@ -0,0 +1,38 @@ +package export + +import ( + "fmt" + "os" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ExportLaunchd(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + l, err := loadTemplate("launchd", "templates/launchd/launchd.plist.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/Library/LaunchDaemons/"); os.IsNotExist(err) { + os.MkdirAll(location+"/Library/LaunchDaemons/", os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(l, fmt.Sprintf("%s/Library/LaunchDaemons/%s-%s.plist", location, app, processName), config) { + return false + } + + num += 1 + } + } + + return true +} diff --git a/export/export_runit.go b/export/export_runit.go new file mode 100644 index 0000000..a979813 --- /dev/null +++ b/export/export_runit.go @@ -0,0 +1,90 @@ +package export + +import ( + "fmt" + "os" + "strconv" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ExportRunit(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + r, err := loadTemplate("run", "templates/runit/run.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + l, err := loadTemplate("log", "templates/runit/log/run.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/service"); os.IsNotExist(err) { + os.MkdirAll(location+"/service", os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processDirectory := fmt.Sprintf("%s-%s-%d", app, entry.Name, num) + folderPath := location + "/service/" + processDirectory + processName := fmt.Sprintf("%s-%d", entry.Name, num) + + fmt.Println("creating:", folderPath) + os.MkdirAll(folderPath, os.ModePerm) + + fmt.Println("creating:", folderPath+"/env") + os.MkdirAll(folderPath+"/env", os.ModePerm) + + fmt.Println("creating:", folderPath+"/log") + os.MkdirAll(folderPath+"/log", os.ModePerm) + + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + + if !writeOutput(r, fmt.Sprintf("%s/run", folderPath), config) { + return false + } + + env, ok := config["env"].(map[string]string) + if !ok { + fmt.Fprintf(os.Stderr, "invalid env map\n") + return false + } + + env["PORT"] = strconv.Itoa(port) + env["PS"] = app + "-" + processName + + for key, value := range env { + fmt.Println("writing:", folderPath+"/env/"+key) + f, err := os.Create(folderPath + "/env/" + key) + if err != nil { + fmt.Fprintf(os.Stderr, "error creating file: %s\n", err) + return false + } + defer f.Close() + + if _, err = f.WriteString(value); err != nil { + fmt.Fprintf(os.Stderr, "error writing output: %s\n", err) + return false + } + + if err = f.Sync(); err != nil { + fmt.Fprintf(os.Stderr, "error syncing output: %s\n", err) + return false + } + } + + if !writeOutput(l, fmt.Sprintf("%s/log/run", folderPath), config) { + return false + } + + num += 1 + } + } + + return true +} diff --git a/export/export_systemd.go b/export/export_systemd.go new file mode 100644 index 0000000..1c85da7 --- /dev/null +++ b/export/export_systemd.go @@ -0,0 +1,55 @@ +package export + +import ( + "fmt" + "os" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ExportSystemd(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + t, err := loadTemplate("target", "templates/systemd/default/control.target.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + s, err := loadTemplate("service", "templates/systemd/default/program.service.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/etc/systemd/system/"); os.IsNotExist(err) { + os.MkdirAll(location+"/etc/systemd/system/", os.ModePerm) + } + + processes := []string{} + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + fileName := fmt.Sprintf("%s.%d", entry.Name, num) + processes = append(processes, fmt.Sprintf(app+"-%s.service", fileName)) + + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(s, fmt.Sprintf("%s/etc/systemd/system/%s-%s.service", location, app, fileName), config) { + return false + } + + num += 1 + } + } + + config := vars + config["processes"] = processes + if writeOutput(t, fmt.Sprintf("%s/etc/systemd/system/%s.target", location, app), config) { + fmt.Println("You will want to run 'systemctl --system daemon-reload' to activate the service on the target host") + return true + } + + return true +} diff --git a/export/export_systemd_user.go b/export/export_systemd_user.go new file mode 100644 index 0000000..56f7883 --- /dev/null +++ b/export/export_systemd_user.go @@ -0,0 +1,40 @@ +package export + +import ( + "fmt" + "os" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ExportSystemdUser(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + s, err := loadTemplate("service", "templates/systemd-user/default/program.service.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + path := vars["home"].(string) + "/.config/systemd/user/" + if _, err := os.Stat(location + path); os.IsNotExist(err) { + os.MkdirAll(location+path, os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(s, fmt.Sprintf("%s%s%s-%s.service", location, path, app, processName), config) { + return false + } + + num += 1 + } + } + + fmt.Println("You will want to run 'systemctl --user daemon-reload' to activate the service on the target host") + return true +} diff --git a/export/export_sysv.go b/export/export_sysv.go new file mode 100644 index 0000000..49ac5cc --- /dev/null +++ b/export/export_sysv.go @@ -0,0 +1,38 @@ +package export + +import ( + "fmt" + "os" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ExportSysv(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + l, err := loadTemplate("launchd", "templates/sysv/default/init.sh.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/etc/init.d/"); os.IsNotExist(err) { + os.MkdirAll(location+"/etc/init.d/", os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(l, fmt.Sprintf("%s/etc/init.d/%s-%s", location, app, processName), config) { + return false + } + + num += 1 + } + } + + return true +} diff --git a/export/export_upstart.go b/export/export_upstart.go new file mode 100644 index 0000000..22fce4d --- /dev/null +++ b/export/export_upstart.go @@ -0,0 +1,60 @@ +package export + +import ( + "fmt" + "os" + + "github.com/josegonzalez/go-procfile-util/procfile" +) + +func ExportUpstart(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + p, err := loadTemplate("program", "templates/upstart/default/program.conf.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + c, err := loadTemplate("app", "templates/upstart/default/control.conf.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + t, err := loadTemplate("process-type", "templates/upstart/default/process-type.conf.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/etc/init/"); os.IsNotExist(err) { + os.MkdirAll(location+"/etc/init/", os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + if count > 0 { + config := vars + config["process_type"] = entry.Name + if !writeOutput(t, fmt.Sprintf("%s/etc/init/%s-%s.conf", location, app, entry.Name), config) { + return false + } + } + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + fileName := fmt.Sprintf("%s-%d", entry.Name, num) + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(p, fmt.Sprintf("%s/etc/init/%s-%s.conf", location, app, fileName), config) { + return false + } + + num += 1 + } + } + + config := vars + return writeOutput(c, fmt.Sprintf("%s/etc/init/%s.conf", location, app), config) +} diff --git a/templates/launchd/launchd.plist.tmpl b/export/templates/launchd/launchd.plist.tmpl similarity index 100% rename from templates/launchd/launchd.plist.tmpl rename to export/templates/launchd/launchd.plist.tmpl diff --git a/templates/runit/log/run.tmpl b/export/templates/runit/log/run.tmpl similarity index 100% rename from templates/runit/log/run.tmpl rename to export/templates/runit/log/run.tmpl diff --git a/templates/runit/run.tmpl b/export/templates/runit/run.tmpl similarity index 100% rename from templates/runit/run.tmpl rename to export/templates/runit/run.tmpl diff --git a/templates/systemd-user/default/program.service.tmpl b/export/templates/systemd-user/default/program.service.tmpl similarity index 100% rename from templates/systemd-user/default/program.service.tmpl rename to export/templates/systemd-user/default/program.service.tmpl diff --git a/templates/systemd/default/control.target.tmpl b/export/templates/systemd/default/control.target.tmpl similarity index 100% rename from templates/systemd/default/control.target.tmpl rename to export/templates/systemd/default/control.target.tmpl diff --git a/templates/systemd/default/program.service.tmpl b/export/templates/systemd/default/program.service.tmpl similarity index 100% rename from templates/systemd/default/program.service.tmpl rename to export/templates/systemd/default/program.service.tmpl diff --git a/templates/sysv/default/init.sh.tmpl b/export/templates/sysv/default/init.sh.tmpl similarity index 100% rename from templates/sysv/default/init.sh.tmpl rename to export/templates/sysv/default/init.sh.tmpl diff --git a/templates/upstart/default/control.conf.tmpl b/export/templates/upstart/default/control.conf.tmpl similarity index 100% rename from templates/upstart/default/control.conf.tmpl rename to export/templates/upstart/default/control.conf.tmpl diff --git a/templates/upstart/default/process-type.conf.tmpl b/export/templates/upstart/default/process-type.conf.tmpl similarity index 100% rename from templates/upstart/default/process-type.conf.tmpl rename to export/templates/upstart/default/process-type.conf.tmpl diff --git a/templates/upstart/default/program.conf.tmpl b/export/templates/upstart/default/program.conf.tmpl similarity index 100% rename from templates/upstart/default/program.conf.tmpl rename to export/templates/upstart/default/program.conf.tmpl diff --git a/fixtures/strict/Procfile b/fixtures/strict/Procfile new file mode 100644 index 0000000..6e9c4fc --- /dev/null +++ b/fixtures/strict/Procfile @@ -0,0 +1,10 @@ +# comment +#comment +web: python app.py + +# comment +worker: python worker.py + +worker-2: python worker.py 2 + +worker-3: python worker.py 3 \ No newline at end of file diff --git a/fixtures/v1/Procfile b/fixtures/v1/Procfile new file mode 100644 index 0000000..0b7828f --- /dev/null +++ b/fixtures/v1/Procfile @@ -0,0 +1,10 @@ +# comment +#comment +web: python app.py + +# comment +worker: python worker.py + +worker_2: python worker.py 2 + +worker-3: python worker.py 3 \ No newline at end of file diff --git a/main.go b/main.go index d7864d5..7813e44 100644 --- a/main.go +++ b/main.go @@ -1,570 +1,30 @@ package main import ( - "bufio" "fmt" - "io/ioutil" "os" - "os/user" - "regexp" - "sort" "strconv" - "strings" "github.com/akamensky/argparse" - "github.com/andrew-d/go-termutil" - "github.com/joho/godotenv" - "gopkg.in/alessio/shellescape.v1" + "github.com/josegonzalez/go-procfile-util/procfile" + "github.com/josegonzalez/go-procfile-util/commands" ) -// ProcfileEntry is a struct containing a process type and the corresponding command -type ProcfileEntry struct { - Name string - Command string -} - -// FormationEntry is a struct containing a process type and the corresponding count -type FormationEntry struct { - Name string - Count int -} - -type exportFunc func(string, []ProcfileEntry, map[string]FormationEntry, string, int, map[string]interface{}) bool - -func (p *ProcfileEntry) commandList() []string { - return strings.Fields(p.Command) -} - -func (p *ProcfileEntry) program() string { - return strings.Fields(p.Command)[0] -} - -func (p *ProcfileEntry) args() string { - return strings.Join(strings.Fields(p.Command)[1:], " ") -} - -func (p *ProcfileEntry) argsEscaped() string { - return shellescape.Quote(p.args()) -} - -const portEnvVar = "PORT" - // Version contains the procfile-util version var Version string -// Loglevel stores the current app log level -var Loglevel = "info" - -func logMessage(message string, level string) { - if level == "info" { - fmt.Println(message) - return - } - - if Loglevel == "debug" { - fmt.Fprintf(os.Stderr, fmt.Sprintf("%v\n", message)) - } -} - -func debugMessage(message string) { - logMessage(message, "debug") -} - -func infoMessage(message string) { - logMessage(message, "info") -} - -// GetProcfileContent returns the content at a path as a string -func GetProcfileContent(path string) (string, error) { - debugMessage(fmt.Sprintf("Attempting to read input from file: %v", path)) - f, err := os.Open(path) - if err != nil { - if !termutil.Isatty(os.Stdin.Fd()) { - debugMessage("Reading input from stdin") - bytes, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return "", err - } - return string(bytes), nil - } - return "", err - } - defer f.Close() - - lines := []string{} - scanner := bufio.NewScanner(f) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - err = scanner.Err() - return strings.Join(lines, "\n"), err -} - -func outputProcfile(path string, writePath string, delimiter string, stdout bool, entries []ProcfileEntry) bool { - if writePath != "" && stdout { - fmt.Fprintf(os.Stderr, "cannot specify both --stdout and --write-path flags\n") - return false - } - - sort.Slice(entries, func(i, j int) bool { - return entries[i].Name < entries[j].Name - }) - - if stdout { - for _, entry := range entries { - fmt.Printf("%v%v %v\n", entry.Name, delimiter, entry.Command) - } - return true - } - - if writePath != "" { - path = writePath - } - - if err := writeProcfile(path, delimiter, entries); err != nil { - fmt.Fprintf(os.Stderr, "error writing procfile: %s\n", err) - return false - } - - return true -} - -func writeProcfile(path string, delimiter string, entries []ProcfileEntry) error { - debugMessage(fmt.Sprintf("Attempting to write output to file: %v", path)) - file, err := os.Create(path) - if err != nil { - return err - } - defer file.Close() - - w := bufio.NewWriter(file) - for _, entry := range entries { - fmt.Fprintln(w, fmt.Sprintf("%v%v %v", entry.Name, delimiter, entry.Command)) - } - return w.Flush() -} - -func parseProcfile(path string, delimiter string, strict bool) ([]ProcfileEntry, error) { - var entries []ProcfileEntry - text, err := GetProcfileContent(path) +func parseProcfile(path string, delimiter string, strict bool) ([]procfile.ProcfileEntry, error) { + var entries []procfile.ProcfileEntry + text, err := procfile.GetProcfileContent(path) if err != nil { return entries, err } - return ParseProcfile(text, delimiter, strict) -} - -// ParseProcfile parses text as a procfile and returns a list of procfile entries -func ParseProcfile(text string, delimiter string, strict bool) ([]ProcfileEntry, error) { - var entries []ProcfileEntry - reCmd, _ := regexp.Compile(`^([a-z0-9]([-a-z0-9]*[a-z0-9])?)` + delimiter + `\s*(.+)$`) - reOldCmd, _ := regexp.Compile(`^([A-Za-z0-9_-]+)` + delimiter + `\s*(.+)$`) - - reComment, _ := regexp.Compile(`^(.*)\s#.+$`) - - 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 - } - - oldParams := reOldCmd.FindStringSubmatch(line) - params := reCmd.FindStringSubmatch(line) - isCommand := len(params) == 4 - isOldCommand := len(oldParams) == 3 - isComment := strings.HasPrefix(line, "#") - if isComment { - continue - } - - if !isCommand { - if !isOldCommand { - return entries, fmt.Errorf("invalid line in procfile, line %d", lineNumber) - } - - if strict { - return entries, fmt.Errorf("process name contains invalid characters, line %d", lineNumber) - } - } - - name := "" - cmd := "" - if strict { - name, cmd = params[1], params[3] - } else { - name, cmd = oldParams[1], oldParams[2] - } - - 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, line %d", lineNumber) - } - names[name] = true - - commentParams := reComment.FindStringSubmatch(cmd) - if len(commentParams) == 2 { - 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}) - } - - if scanner.Err() != nil { - return entries, scanner.Err() - } - - if len(entries) == 0 { - return entries, fmt.Errorf("no entries found in Procfile") - } - - sort.Slice(entries, func(i, j int) bool { - return entries[i].Name < entries[j].Name - }) - - return entries, nil -} - -func expandEnv(e ProcfileEntry, envPath string, allowEnv bool, defaultPort int) (string, error) { - baseExpandFunc := func(key string) string { - if key == "PS" { - return os.Getenv("PS") - } - if key == portEnvVar { - return strconv.Itoa(defaultPort) - } - return "" - } - - expandFunc := func(key string) string { - return baseExpandFunc(key) - } - - if allowEnv { - debugMessage("Allowing getenv variable expansion") - expandFunc = func(key string) string { - value := os.Getenv(key) - if value == "" { - value = baseExpandFunc(key) - } - return value - } - } - - if envPath != "" { - b, err := ioutil.ReadFile(envPath) - if err != nil { - return "", err - } - - content := string(b) - env, err := godotenv.Unmarshal(content) - if err != nil { - return "", err - } - - debugMessage("Allowing .env variable expansion") - expandFunc = func(key string) string { - if val, ok := env[key]; ok { - return val - } - value := "" - if allowEnv { - value = os.Getenv(key) - } - if value == "" { - value = baseExpandFunc(key) - } - return value - } - } - - os.Setenv("PS", e.Name) - os.Setenv("EXPENV_PARENTHESIS", "$(") - s := strings.Replace(e.Command, "$(", "${EXPENV_PARENTHESIS}", -1) - return os.Expand(s, expandFunc), nil -} - -func checkCommand(entries []ProcfileEntry) bool { - if len(entries) == 0 { - fmt.Fprintf(os.Stderr, "no processes defined\n") - return false - } - - names := []string{} - for _, entry := range entries { - names = append(names, entry.Name) - } - - processNames := strings.Join(names[:], ", ") - fmt.Printf("valid procfile detected %v\n", processNames) - return true -} - -func existsCommand(entries []ProcfileEntry, processType string) bool { - for _, entry := range entries { - if processType == entry.Name { - return true - } - } - - fmt.Fprint(os.Stderr, "no matching process entry found\n") - return false -} - -func expandCommand(entries []ProcfileEntry, envPath string, allowGetenv bool, processType string, defaultPort int, delimiter string) bool { - hasErrors := false - var expandedEntries []ProcfileEntry - for _, entry := range entries { - command, err := expandEnv(entry, envPath, allowGetenv, defaultPort) - if err != nil { - fmt.Fprintf(os.Stderr, "error processing command: %s\n", err) - hasErrors = true - } - - entry.Command = command - expandedEntries = append(expandedEntries, entry) - } - - if hasErrors { - return false - } - - for _, entry := range expandedEntries { - if processType == "" || processType == entry.Name { - fmt.Printf("%v%v %v\n", entry.Name, delimiter, entry.Command) - } - } - - return true -} - -func exportCommand(entries []ProcfileEntry, app string, description string, envPath string, format string, formation string, group string, home string, limitCoredump string, limitCputime string, limitData string, limitFileSize string, limitLockedMemory string, limitOpenFiles string, limitUserProcesses string, limitPhysicalMemory string, limitStackSize string, location string, logPath string, nice string, prestart string, workingDirectoryPath string, runPath string, timeout int, processUser string, defaultPort int) bool { - if format == "" { - fmt.Fprintf(os.Stderr, "no format specified\n") - return false - } - if location == "" { - fmt.Fprintf(os.Stderr, "no output location specified\n") - return false - } - - formats := map[string]exportFunc{ - "launchd": exportLaunchd, - "runit": exportRunit, - "systemd": exportSystemd, - "systemd-user": exportSystemdUser, - "sysv": exportSysv, - "upstart": exportUpstart, - } - - if _, ok := formats[format]; !ok { - fmt.Fprintf(os.Stderr, "invalid format type: %s\n", format) - return false - } - - formations, err := parseFormation(formation) - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - if processUser == "" { - processUser = app - } - - if group == "" { - group = app - } - - u, err := user.Current() - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - return false - } - - if home == "" { - home = "/home/" + u.Username - } - - env := make(map[string]string) - if envPath != "" { - b, err := ioutil.ReadFile(envPath) - if err != nil { - fmt.Fprintf(os.Stderr, "error reading env file: %s\n", err) - return false - } - - content := string(b) - env, err = godotenv.Unmarshal(content) - if err != nil { - fmt.Fprintf(os.Stderr, "error parsing env file: %s\n", err) - return false - } - } - - vars := make(map[string]interface{}) - vars["app"] = app - vars["description"] = description - vars["env"] = env - vars["group"] = group - vars["home"] = home - vars["log"] = logPath - vars["location"] = location - vars["limit_coredump"] = limitCoredump - vars["limit_cputime"] = limitCputime - vars["limit_data"] = limitData - vars["limit_file_size"] = limitFileSize - vars["limit_locked_memory"] = limitLockedMemory - vars["limit_open_files"] = limitOpenFiles - vars["limit_user_processes"] = limitUserProcesses - vars["limit_physical_memory"] = limitPhysicalMemory - vars["limit_stack_size"] = limitStackSize - vars["nice"] = nice - vars["prestart"] = prestart - vars["working_directory"] = workingDirectoryPath - vars["timeout"] = strconv.Itoa(timeout) - vars["ulimit_shell"] = ulimitShell(limitCoredump, limitCputime, limitData, limitFileSize, limitLockedMemory, limitOpenFiles, limitUserProcesses, limitPhysicalMemory, limitStackSize) - vars["user"] = processUser - - if fn, ok := formats[format]; ok { - return fn(app, entries, formations, location, defaultPort, vars) - } - - return false -} - -func ulimitShell(limitCoredump string, limitCputime string, limitData string, limitFileSize string, limitLockedMemory string, limitOpenFiles string, limitUserProcesses string, limitPhysicalMemory string, limitStackSize string) string { - s := []string{} - if limitCoredump != "" { - s = append(s, "ulimit -c ${limit_coredump}") - } - if limitCputime != "" { - s = append(s, "ulimit -t ${limit_cputime}") - } - if limitData != "" { - s = append(s, "ulimit -d ${limit_data}") - } - if limitFileSize != "" { - s = append(s, "ulimit -f ${limit_file_size}") - } - if limitLockedMemory != "" { - s = append(s, "ulimit -l ${limit_locked_memory}") - } - if limitOpenFiles != "" { - s = append(s, "ulimit -n ${limit_open_files}") - } - if limitUserProcesses != "" { - s = append(s, "ulimit -u ${limit_user_processes}") - } - if limitPhysicalMemory != "" { - s = append(s, "ulimit -m ${limit_physical_memory}") - } - if limitStackSize != "" { - s = append(s, "ulimit -s ${limit_stack_size}") - } - - return strings.Join(s, "\n") -} - -func parseFormation(formation string) (map[string]FormationEntry, error) { - entries := make(map[string]FormationEntry) - for _, formation := range strings.Split(formation, ",") { - parts := strings.Split(formation, "=") - if len(parts) != 2 { - return entries, fmt.Errorf("invalid formation: %s", formation) - } - - i, err := strconv.Atoi(parts[1]) - if err != nil { - return entries, fmt.Errorf("invalid formation: %s", err) - } - - entries[parts[0]] = FormationEntry{ - Name: parts[0], - Count: i, - } - } - - return entries, nil -} - -func deleteCommand(entries []ProcfileEntry, processType string, writePath string, stdout bool, delimiter string, path string) bool { - var validEntries []ProcfileEntry - for _, entry := range entries { - if processType == entry.Name { - continue - } - validEntries = append(validEntries, entry) - } - - return outputProcfile(path, writePath, delimiter, stdout, validEntries) -} - -func listCommand(entries []ProcfileEntry) bool { - for _, entry := range entries { - fmt.Printf("%v\n", entry.Name) - } - return true -} - -func setCommand(entries []ProcfileEntry, processType string, command string, writePath string, stdout bool, delimiter string, path string) bool { - var validEntries []ProcfileEntry - validEntries = append(validEntries, ProcfileEntry{processType, command}) - for _, entry := range entries { - if processType == entry.Name { - continue - } - validEntries = append(validEntries, entry) - } - - return outputProcfile(path, writePath, delimiter, stdout, validEntries) -} - -func showCommand(entries []ProcfileEntry, envPath string, allowGetenv bool, processType string, defaultPort int) bool { - var foundEntry ProcfileEntry - for _, entry := range entries { - if processType == entry.Name { - foundEntry = entry - break - } - } - - if foundEntry == (ProcfileEntry{}) { - fmt.Fprintf(os.Stderr, "no matching process entry found\n") - return false - } - - command, err := expandEnv(foundEntry, envPath, allowGetenv, defaultPort) - if err != nil { - fmt.Fprintf(os.Stderr, "error processing command: %s\n", err) - return false - } - - fmt.Printf("%v\n", command) - return true + return procfile.ParseProcfile(text, delimiter, strict) } func main() { parser := argparse.NewParser("procfile-util", "A procfile parsing tool") - loglevelFlag := parser.Selector("l", "loglevel", []string{"info", "debug"}, &argparse.Options{Default: "info", Help: "loglevel to use"}) procfileFlag := parser.String("P", "procfile", &argparse.Options{Default: "Procfile", Help: "path to a procfile"}) delimiterFlag := parser.String("D", "delimiter", &argparse.Options{Default: ":", Help: "delimiter in use within procfile"}) defaultPortFlag := parser.String("d", "default-port", &argparse.Options{Default: "5000", Help: "default port to use"}) @@ -637,7 +97,6 @@ func main() { return } - Loglevel = *loglevelFlag entries, err := parseProcfile(*procfileFlag, *delimiterFlag, *strictFlag) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) @@ -658,21 +117,21 @@ func main() { success := false if checkCmd.Happened() { - success = checkCommand(entries) + success = commands.CheckCommand(entries) } else if deleteCmd.Happened() { - success = deleteCommand(entries, *processTypeDeleteFlag, *writePathDeleteFlag, *stdoutDeleteFlag, *delimiterFlag, *procfileFlag) + success = commands.DeleteCommand(entries, *processTypeDeleteFlag, *writePathDeleteFlag, *stdoutDeleteFlag, *delimiterFlag, *procfileFlag) } else if existsCmd.Happened() { - success = existsCommand(entries, *processTypeExistsFlag) + success = commands.ExistsCommand(entries, *processTypeExistsFlag) } else if expandCmd.Happened() { - success = expandCommand(entries, *envPathExpandFlag, *allowGetenvExpandFlag, *processTypeExpandFlag, defaultPort, *delimiterFlag) + success = commands.ExpandCommand(entries, *envPathExpandFlag, *allowGetenvExpandFlag, *processTypeExpandFlag, defaultPort, *delimiterFlag) } else if exportCmd.Happened() { - success = exportCommand(entries, *appExportFlag, *descriptionExportFlag, *envPathExportFlag, *formatExportFlag, *formationExportFlag, *groupExportFlag, *homeExportFlag, *limitCoredumpExportFlag, *limitCputimeExportFlag, *limitDataExportFlag, *limitFileSizeExportFlag, *limitLockedMemoryExportFlag, *limitOpenFilesExportFlag, *limitUserProcessesExportFlag, *limitPhysicalMemoryExportFlag, *limitStackSizeExportFlag, *locationExportFlag, *logPathExportFlag, *niceExportFlag, *prestartExportFlag, *workingDirectoryPathExportFlag, *runExportFlag, *timeoutExportFlag, *userExportFlag, defaultPort) + success = commands.ExportCommand(entries, *appExportFlag, *descriptionExportFlag, *envPathExportFlag, *formatExportFlag, *formationExportFlag, *groupExportFlag, *homeExportFlag, *limitCoredumpExportFlag, *limitCputimeExportFlag, *limitDataExportFlag, *limitFileSizeExportFlag, *limitLockedMemoryExportFlag, *limitOpenFilesExportFlag, *limitUserProcessesExportFlag, *limitPhysicalMemoryExportFlag, *limitStackSizeExportFlag, *locationExportFlag, *logPathExportFlag, *niceExportFlag, *prestartExportFlag, *workingDirectoryPathExportFlag, *runExportFlag, *timeoutExportFlag, *userExportFlag, defaultPort) } else if listCmd.Happened() { - success = listCommand(entries) + success = commands.ListCommand(entries) } else if setCmd.Happened() { - success = setCommand(entries, *processTypeSetFlag, *commandSetFlag, *writePathSetFlag, *stdoutSetFlag, *delimiterFlag, *procfileFlag) + success = commands.SetCommand(entries, *processTypeSetFlag, *commandSetFlag, *writePathSetFlag, *stdoutSetFlag, *delimiterFlag, *procfileFlag) } else if showCmd.Happened() { - success = showCommand(entries, *envPathShowFlag, *allowGetenvShowFlag, *processTypeShowFlag, defaultPort) + success = commands.ShowCommand(entries, *envPathShowFlag, *allowGetenvShowFlag, *processTypeShowFlag, defaultPort) } else { fmt.Print(parser.Usage(err)) } diff --git a/procfile/entry.go b/procfile/entry.go new file mode 100644 index 0000000..a1f849e --- /dev/null +++ b/procfile/entry.go @@ -0,0 +1,35 @@ +package procfile + +import ( + "strings" + + "gopkg.in/alessio/shellescape.v1" +) + +// ProcfileEntry is a struct containing a process type and the corresponding command +type ProcfileEntry struct { + Name string + Command string +} + +func (p *ProcfileEntry) CommandList() []string { + return strings.Fields(p.Command) +} + +func (p *ProcfileEntry) Program() string { + return strings.Fields(p.Command)[0] +} + +func (p *ProcfileEntry) Args() string { + return strings.Join(strings.Fields(p.Command)[1:], " ") +} + +func (p *ProcfileEntry) ArgsEscaped() string { + return shellescape.Quote(p.Args()) +} + +// FormationEntry is a struct containing a process type and the corresponding count +type FormationEntry struct { + Name string + Count int +} diff --git a/procfile/io.go b/procfile/io.go new file mode 100644 index 0000000..03d0dc6 --- /dev/null +++ b/procfile/io.go @@ -0,0 +1,79 @@ +package procfile + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "sort" + "strings" + + "github.com/andrew-d/go-termutil" +) + +// GetProcfileContent returns the content at a path as a string +func GetProcfileContent(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + if !termutil.Isatty(os.Stdin.Fd()) { + bytes, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return "", err + } + return string(bytes), nil + } + return "", err + } + defer f.Close() + + lines := []string{} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + err = scanner.Err() + return strings.Join(lines, "\n"), err +} + +func OutputProcfile(path string, writePath string, delimiter string, stdout bool, entries []ProcfileEntry) bool { + if writePath != "" && stdout { + fmt.Fprintf(os.Stderr, "cannot specify both --stdout and --write-path flags\n") + return false + } + + sort.Slice(entries, func(i, j int) bool { + return entries[i].Name < entries[j].Name + }) + + if stdout { + for _, entry := range entries { + fmt.Printf("%v%v %v\n", entry.Name, delimiter, entry.Command) + } + return true + } + + if writePath != "" { + path = writePath + } + + if err := writeProcfile(path, delimiter, entries); err != nil { + fmt.Fprintf(os.Stderr, "error writing procfile: %s\n", err) + return false + } + + return true +} + +func writeProcfile(path string, delimiter string, entries []ProcfileEntry) error { + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + + w := bufio.NewWriter(file) + for _, entry := range entries { + fmt.Fprintln(w, fmt.Sprintf("%v%v %v", entry.Name, delimiter, entry.Command)) + } + return w.Flush() +} diff --git a/procfile/parse.go b/procfile/parse.go new file mode 100644 index 0000000..72f8c15 --- /dev/null +++ b/procfile/parse.go @@ -0,0 +1,120 @@ +package procfile + +import ( + "bufio" + "fmt" + "regexp" + "sort" + "strconv" + "strings" +) + +func ParseFormation(formation string) (map[string]FormationEntry, error) { + entries := make(map[string]FormationEntry) + for _, formation := range strings.Split(formation, ",") { + parts := strings.Split(formation, "=") + if len(parts) != 2 { + return entries, fmt.Errorf("invalid formation: %s", formation) + } + + i, err := strconv.Atoi(parts[1]) + if err != nil { + return entries, fmt.Errorf("invalid formation: %s", err) + } + + entries[parts[0]] = FormationEntry{ + Name: parts[0], + Count: i, + } + } + + return entries, nil +} + +// ParseProcfile parses text as a procfile and returns a list of procfile entries +func ParseProcfile(text string, delimiter string, strict bool) ([]ProcfileEntry, error) { + var entries []ProcfileEntry + reCmd, _ := regexp.Compile(`^([a-z0-9]([-a-z0-9]*[a-z0-9])?)` + delimiter + `\s*(.+)$`) + reOldCmd, _ := regexp.Compile(`^([A-Za-z0-9_-]+)` + delimiter + `\s*(.+)$`) + + reComment, _ := regexp.Compile(`^(.*)\s#.+$`) + + 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 + } + + oldParams := reOldCmd.FindStringSubmatch(line) + params := reCmd.FindStringSubmatch(line) + isCommand := len(params) == 4 + isOldCommand := len(oldParams) == 3 + isComment := strings.HasPrefix(line, "#") + if isComment { + continue + } + + if !isCommand { + if !isOldCommand { + return entries, fmt.Errorf("invalid line in procfile, line %d", lineNumber) + } + + if strict { + return entries, fmt.Errorf("process name contains invalid characters, line %d", lineNumber) + } + } + + name := "" + cmd := "" + if strict { + name, cmd = params[1], params[3] + } else { + name, cmd = oldParams[1], oldParams[2] + } + + 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, line %d", lineNumber) + } + names[name] = true + + commentParams := reComment.FindStringSubmatch(cmd) + if len(commentParams) == 2 { + 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}) + } + + if scanner.Err() != nil { + return entries, scanner.Err() + } + + if len(entries) == 0 { + return entries, fmt.Errorf("no entries found in Procfile") + } + + sort.Slice(entries, func(i, j int) bool { + return entries[i].Name < entries[j].Name + }) + + return entries, nil +} From beb9fd5665c649b154e42ef30ec5f7d2f59c6752 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 27 Oct 2020 14:37:27 -0400 Subject: [PATCH 2/7] fix: properly reference go module :facepalm: --- commands/check_command.go | 2 +- commands/commands.go | 3 ++- commands/delete_command.go | 2 +- commands/exists_command.go | 2 +- commands/expand_command.go | 2 +- commands/export_command.go | 5 +++-- commands/list_command.go | 2 +- commands/set_command.go | 2 +- commands/show_command.go | 2 +- export/export.go | 2 +- export/export_launchd.go | 2 +- export/export_runit.go | 2 +- export/export_systemd.go | 2 +- export/export_systemd_user.go | 2 +- export/export_sysv.go | 2 +- export/export_upstart.go | 2 +- main.go | 5 +++-- 17 files changed, 22 insertions(+), 19 deletions(-) diff --git a/commands/check_command.go b/commands/check_command.go index 974c557..bc80d1f 100644 --- a/commands/check_command.go +++ b/commands/check_command.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) diff --git a/commands/commands.go b/commands/commands.go index f4cf125..033fcf1 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -6,8 +6,9 @@ import ( "strconv" "strings" + "procfile-util/procfile" + "github.com/joho/godotenv" - "github.com/josegonzalez/go-procfile-util/procfile" ) const portEnvVar = "PORT" diff --git a/commands/delete_command.go b/commands/delete_command.go index d8f08c3..9e6fe95 100644 --- a/commands/delete_command.go +++ b/commands/delete_command.go @@ -1,7 +1,7 @@ package commands import ( - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func DeleteCommand(entries []procfile.ProcfileEntry, processType string, writePath string, stdout bool, delimiter string, path string) bool { diff --git a/commands/exists_command.go b/commands/exists_command.go index 90b0192..faba651 100644 --- a/commands/exists_command.go +++ b/commands/exists_command.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ExistsCommand(entries []procfile.ProcfileEntry, processType string) bool { diff --git a/commands/expand_command.go b/commands/expand_command.go index b0ebc2f..d6706ce 100644 --- a/commands/expand_command.go +++ b/commands/expand_command.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ExpandCommand(entries []procfile.ProcfileEntry, envPath string, allowGetenv bool, processType string, defaultPort int, delimiter string) bool { diff --git a/commands/export_command.go b/commands/export_command.go index b19f61f..2e114b0 100644 --- a/commands/export_command.go +++ b/commands/export_command.go @@ -8,8 +8,9 @@ import ( "strconv" "strings" - "github.com/josegonzalez/go-procfile-util/export" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/export" + "procfile-util/procfile" + "github.com/joho/godotenv" ) diff --git a/commands/list_command.go b/commands/list_command.go index 7a0d6f2..f2fba53 100644 --- a/commands/list_command.go +++ b/commands/list_command.go @@ -3,7 +3,7 @@ package commands import ( "fmt" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ListCommand(entries []procfile.ProcfileEntry) bool { diff --git a/commands/set_command.go b/commands/set_command.go index d527d45..9e3f694 100644 --- a/commands/set_command.go +++ b/commands/set_command.go @@ -1,7 +1,7 @@ package commands import ( - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func SetCommand(entries []procfile.ProcfileEntry, processType string, command string, writePath string, stdout bool, delimiter string, path string) bool { diff --git a/commands/show_command.go b/commands/show_command.go index 8c72a54..53a59ae 100644 --- a/commands/show_command.go +++ b/commands/show_command.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ShowCommand(entries []procfile.ProcfileEntry, envPath string, allowGetenv bool, processType string, defaultPort int) bool { diff --git a/export/export.go b/export/export.go index e8c45c3..b64afac 100644 --- a/export/export.go +++ b/export/export.go @@ -6,7 +6,7 @@ import ( "strconv" "text/template" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) type ExportFunc func(string, []procfile.ProcfileEntry, map[string]procfile.FormationEntry, string, int, map[string]interface{}) bool diff --git a/export/export_launchd.go b/export/export_launchd.go index 433aa34..80ce106 100644 --- a/export/export_launchd.go +++ b/export/export_launchd.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ExportLaunchd(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { diff --git a/export/export_runit.go b/export/export_runit.go index a979813..445e4ba 100644 --- a/export/export_runit.go +++ b/export/export_runit.go @@ -5,7 +5,7 @@ import ( "os" "strconv" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ExportRunit(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { diff --git a/export/export_systemd.go b/export/export_systemd.go index 1c85da7..55231c2 100644 --- a/export/export_systemd.go +++ b/export/export_systemd.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ExportSystemd(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { diff --git a/export/export_systemd_user.go b/export/export_systemd_user.go index 56f7883..48c1458 100644 --- a/export/export_systemd_user.go +++ b/export/export_systemd_user.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ExportSystemdUser(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { diff --git a/export/export_sysv.go b/export/export_sysv.go index 49ac5cc..bfc0905 100644 --- a/export/export_sysv.go +++ b/export/export_sysv.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ExportSysv(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { diff --git a/export/export_upstart.go b/export/export_upstart.go index 22fce4d..b044210 100644 --- a/export/export_upstart.go +++ b/export/export_upstart.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/josegonzalez/go-procfile-util/procfile" + "procfile-util/procfile" ) func ExportUpstart(app string, entries []procfile.ProcfileEntry, formations map[string]procfile.FormationEntry, location string, defaultPort int, vars map[string]interface{}) bool { diff --git a/main.go b/main.go index 7813e44..2825494 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,10 @@ import ( "os" "strconv" + "procfile-util/procfile" + "procfile-util/commands" + "github.com/akamensky/argparse" - "github.com/josegonzalez/go-procfile-util/procfile" - "github.com/josegonzalez/go-procfile-util/commands" ) // Version contains the procfile-util version From 2f76e170dc10081e731c9892128383b8c977b86a Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 27 Oct 2020 14:41:38 -0400 Subject: [PATCH 3/7] tests: add initial test harness --- Makefile | 1 + fixtures/comments.Procfile | 57 +++++++++++++++++++++++++ fixtures/multiple.Procfile | 4 ++ fixtures/port.Procfile | 2 + fixtures/strict/invalid.Procfile | 10 +++++ fixtures/strict/single-invalid.Procfile | 1 + fixtures/strict/single.Procfile | 1 + fixtures/v1/single.Procfile | 1 + test.bats | 56 ++++++++++++++++++++++++ 9 files changed, 133 insertions(+) create mode 100644 fixtures/comments.Procfile create mode 100644 fixtures/multiple.Procfile create mode 100644 fixtures/port.Procfile create mode 100644 fixtures/strict/invalid.Procfile create mode 100644 fixtures/strict/single-invalid.Procfile create mode 100644 fixtures/strict/single.Procfile create mode 100644 fixtures/v1/single.Procfile create mode 100644 test.bats diff --git a/Makefile b/Makefile index 9ab3fec..6345085 100644 --- a/Makefile +++ b/Makefile @@ -162,6 +162,7 @@ validate: ls -lah build/deb build/rpm validation sha1sum build/deb/$(NAME)_$(VERSION)_amd64.deb sha1sum build/rpm/$(NAME)-$(VERSION)-1.x86_64.rpm + bats test.bats prebuild: go get -u github.com/go-bindata/go-bindata/... diff --git a/fixtures/comments.Procfile b/fixtures/comments.Procfile new file mode 100644 index 0000000..af4deb0 --- /dev/null +++ b/fixtures/comments.Procfile @@ -0,0 +1,57 @@ +############################### +# DEVELOPMENT # +############################### + +# Procfile for development using the new threaded worker (scheduler, twitter stream and delayed job) +cron: node worker.js +web: node web.js # testing inline comment +wor-ker: node worker.js +# -wor-ker2: node worker.js +# -wor-ker_2: node worker.js +custom: echo -n +2custom: echo -n +# -3custom-: echo -n +release: touch /app/release.test + +# Old version with separate processes (use this if you have issues with the threaded version) +# web: bundle exec rails server +# schedule: bundle exec rails runner bin/schedule.rb +# twitter: bundle exec rails runner bin/twitter_stream.rb +# dj: bundle exec script/delayed_job run + +############################### +# PRODUCTION # +############################### + +# You need to copy or link config/unicorn.rb.example to config/unicorn.rb for both production versions. +# Have a look at the deployment guides, if you want to set up huginn on your server: +# https://github.com/cantino/huginn/doc + +# Using the threaded worker (consumes less RAM but can run slower) +# web: bundle exec unicorn -c config/unicorn.rb +# jobs: bundle exec rails runner bin/threaded.rb + +# Old version with separate processes (use this if you have issues with the threaded version) +# web: bundle exec unicorn -c config/unicorn.rb +# schedule: bundle exec rails runner bin/schedule.rb +# twitter: bundle exec rails runner bin/twitter_stream.rb +# dj: bundle exec script/delayed_job run + +############################### +# Multiple DelayedJob workers # +############################### +# Per default Huginn can just run one agent at a time. Using a lot of agents or calling slow +# external services frequently might require more DelayedJob workers (an indicator for this is +# a backlog in your 'Job Management' page). +# Every uncommented line starts an additional DelayedJob worker. This works for development, production +# and for the threaded and separate worker processes. Keep in mind one worker needs about 300MB of RAM. +# +#dj2: bundle exec script/delayed_job -i 2 run +#dj3: bundle exec script/delayed_job -i 3 run +#dj4: bundle exec script/delayed_job -i 4 run +#dj5: bundle exec script/delayed_job -i 5 run +#dj6: bundle exec script/delayed_job -i 6 run +#dj7: bundle exec script/delayed_job -i 7 run +#dj8: bundle exec script/delayed_job -i 8 run +#dj9: bundle exec script/delayed_job -i 9 run +#dj10: bundle exec script/delayed_job -i 10 run diff --git a/fixtures/multiple.Procfile b/fixtures/multiple.Procfile new file mode 100644 index 0000000..b0b92f8 --- /dev/null +++ b/fixtures/multiple.Procfile @@ -0,0 +1,4 @@ +web: bundle exec puma -C config/puma.rb +webpacker: bundle exec bin/webpack +worker: rake jobs:work +release: rails db:migrate diff --git a/fixtures/port.Procfile b/fixtures/port.Procfile new file mode 100644 index 0000000..aebdb51 --- /dev/null +++ b/fixtures/port.Procfile @@ -0,0 +1,2 @@ +web: node web.js --port $PORT +worker: node worker.js diff --git a/fixtures/strict/invalid.Procfile b/fixtures/strict/invalid.Procfile new file mode 100644 index 0000000..0b7828f --- /dev/null +++ b/fixtures/strict/invalid.Procfile @@ -0,0 +1,10 @@ +# comment +#comment +web: python app.py + +# comment +worker: python worker.py + +worker_2: python worker.py 2 + +worker-3: python worker.py 3 \ No newline at end of file diff --git a/fixtures/strict/single-invalid.Procfile b/fixtures/strict/single-invalid.Procfile new file mode 100644 index 0000000..ced8e09 --- /dev/null +++ b/fixtures/strict/single-invalid.Procfile @@ -0,0 +1 @@ +we_b: worker.js diff --git a/fixtures/strict/single.Procfile b/fixtures/strict/single.Procfile new file mode 100644 index 0000000..1eaa069 --- /dev/null +++ b/fixtures/strict/single.Procfile @@ -0,0 +1 @@ +web: worker.js diff --git a/fixtures/v1/single.Procfile b/fixtures/v1/single.Procfile new file mode 100644 index 0000000..b3be750 --- /dev/null +++ b/fixtures/v1/single.Procfile @@ -0,0 +1 @@ +command: avahi-register diff --git a/test.bats b/test.bats new file mode 100644 index 0000000..6245fcd --- /dev/null +++ b/test.bats @@ -0,0 +1,56 @@ +#!/usr/bin/env bats + +export SYSTEM_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')" +export PROCFILE_BIN="build/$SYSTEM_NAME/procfile-util" + +setup_file() { + make prebuild $PROCFILE_BIN +} + +teardown_file() { + make clean +} + +@test "[lax] comments" { + run $PROCFILE_BIN check -P fixtures/comments.Procfile + [[ "$status" -eq 0 ]] + [[ "$output" == "valid procfile detected 2custom, cron, custom, release, web, wor-ker" ]] +} + +@test "[lax] multiple" { + run $PROCFILE_BIN check -P fixtures/multiple.Procfile + [[ "$status" -eq 0 ]] + [[ "$output" == "valid procfile detected release, web, webpacker, worker" ]] +} + +@test "[lax] port" { + run $PROCFILE_BIN check -P fixtures/port.Procfile + [[ "$status" -eq 0 ]] + [[ "$output" == "valid procfile detected web, worker" ]] + + run $PROCFILE_BIN show -P fixtures/port.Procfile -p web + [[ "$status" -eq 0 ]] + [[ "$output" == "node web.js --port 5000" ]] +} + +@test "[strict] comments" { + run $PROCFILE_BIN check -S -P fixtures/comments.Procfile + [[ "$status" -eq 0 ]] + [[ "$output" == "valid procfile detected 2custom, cron, custom, release, web, wor-ker" ]] +} + +@test "[strict] multiple" { + run $PROCFILE_BIN check -S -P fixtures/multiple.Procfile + [[ "$status" -eq 0 ]] + [[ "$output" == "valid procfile detected release, web, webpacker, worker" ]] +} + +@test "[strict] port" { + run $PROCFILE_BIN check -S -P fixtures/port.Procfile + [[ "$status" -eq 0 ]] + [[ "$output" == "valid procfile detected web, worker" ]] + + run $PROCFILE_BIN show -S -P fixtures/port.Procfile -p web + [[ "$status" -eq 0 ]] + [[ "$output" == "node web.js --port 5000" ]] +} From 67506492071ae24b56d27bca8e06dab77fce1bba Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 27 Oct 2020 14:50:54 -0400 Subject: [PATCH 4/7] fix: add bats to build env --- Dockerfile.build | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile.build b/Dockerfile.build index d1d5f18..cb7b272 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -7,4 +7,6 @@ RUN apt-get update \ RUN gem install --no-ri --no-rdoc --quiet rake fpm package_cloud +RUN sudo apt-get install -y bats + WORKDIR /src From c9c34098664d49e0fa927cab3b26bcf1acfb06a2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 27 Oct 2020 14:51:06 -0400 Subject: [PATCH 5/7] fix: do not ignore Procfiles in fixtures --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f4207f3..5dd10f5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,6 @@ validation/* .env* *.Procfile +!fixtures/**/*Procfile procfile-util From 79c91b3e8e4a768c3b061294ab6ceb0bff41f163 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 27 Oct 2020 14:54:52 -0400 Subject: [PATCH 6/7] fix: install bats at the right time --- Dockerfile.build | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile.build b/Dockerfile.build index cb7b272..8ac4667 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -1,12 +1,10 @@ FROM golang:1.12.0-stretch RUN apt-get update \ - && apt install apt-transport-https build-essential curl gnupg2 lintian rpm rsync rubygems-integration ruby-dev ruby -qy \ + && apt install apt-transport-https bats build-essential curl gnupg2 lintian rpm rsync rubygems-integration ruby-dev ruby -qy \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* RUN gem install --no-ri --no-rdoc --quiet rake fpm package_cloud -RUN sudo apt-get install -y bats - WORKDIR /src From f0e5865d6f33b2f229ef1ff6978663d971aa965b Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 27 Oct 2020 15:01:11 -0400 Subject: [PATCH 7/7] Release 0.11.0 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6345085..bc3aff4 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.10.1 +BASE_VERSION ?= 0.11.0 IMAGE_NAME ?= $(MAINTAINER)/$(REPOSITORY) PACKAGECLOUD_REPOSITORY ?= dokku/dokku-betafish