Skip to content

Commit

Permalink
add parser and depgraph implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Eslam-Nawara committed Dec 17, 2022
1 parent 3d4aa5e commit 7e80ddf
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 2 deletions.
117 changes: 116 additions & 1 deletion foreman.go
Original file line number Diff line number Diff line change
@@ -1 +1,116 @@
package foreman
package foreman

import (
"errors"
"log"
"os"
"os/signal"
"sync"
"syscall"
"time"

"github.com/Eslam-Nawara/foreman/internal/depgraph"
parser "github.com/Eslam-Nawara/foreman/internal/procparser"
"gopkg.in/yaml.v3"
)

const interval = 50 * time.Millisecond

type Foreman struct {
services map[string]parser.Service
verbose bool
logger *log.Logger
servicesMutex sync.Mutex
}

func New(filePath string, verbose bool) (*Foreman, error) {
foreman := &Foreman{
services: map[string]parser.Service{},
servicesMutex: sync.Mutex{},
logger: log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime),
verbose: verbose,
}

yamlMap := make(map[string]map[string]any)

data, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}

err = yaml.Unmarshal([]byte(data), yamlMap)
if err != nil {
return nil, err
}

for serviceName, serviceInfo := range yamlMap {
service := parser.ParseService(serviceInfo)
service.Name = serviceName
foreman.services[serviceName] = service
}
return foreman, nil
}

// start all services from yaml file
func (foreman *Foreman) Start() error {
var wg sync.WaitGroup
graph := foreman.buildDepGraph()

if depgraph.IsCyclic(graph) {
return errors.New("Cyclic dependencies detected")
}

services := depgraph.TopSort(graph)

for _, serviceName := range services {
wg.Add(1)
go func(serviceName string) {
defer wg.Done()
foreman.runService(serviceName)
}(serviceName)
}

go func() {
sigChan := make(chan os.Signal)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
foreman.Exit(1)
}()
wg.Wait()
return nil
}

// start one service and wait for it
func (foreman *Foreman) runService(serviceName string) error {
return nil
}

func (foreman *Foreman) checker(serviceName string, stopChecker chan bool) {
}

func (foreman *Foreman) buildDepGraph() map[string][]string {
graph := make(map[string][]string)
for serviceName, service := range foreman.services {
graph[serviceName] = service.Deps
}
return graph
}

func (f *Foreman) vLog(msg string) {
if f.verbose {
f.logger.Print(msg)
}
}

// ُExit kills all the running services and checkers.
// exits foreman with the given exit status.
func (f *Foreman) Exit(exitStatus int) {
f.servicesMutex.Lock()
for _, service := range f.services {
if service.Process != nil {
syscall.Kill(-service.Process.Pid, syscall.SIGINT)
}
}
f.servicesMutex.Unlock()
os.Exit(exitStatus)
}
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/Eslam-Nawara/foreman

go 1.19

require (
github.com/shirou/gopsutil v3.21.11+incompatible
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/sys v0.3.0 // indirect
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
56 changes: 56 additions & 0 deletions internal/depgraph/depgraph.go
Original file line number Diff line number Diff line change
@@ -1 +1,57 @@
package depgraph

const (
unvisited = 0
visited = 1
currentlyVisiting = 2
)

func TopSort(graph map[string][]string) []string {
var out []string
state := make(map[string]int)
var ts func(string)

ts = func(node string) {
if state[node] == visited {
return
}
state[node] = visited
for _, child := range graph[node] {
ts(child)
}
out = append(out, node)
}

for node := range graph {
ts(node)
}

return out
}

func IsCyclic(graph map[string][]string) bool {
state := make(map[string]int)
cyclic := false
var dfs func(string)

dfs = func(node string) {
if state[node] == visited {
return
}
if state[node] == currentlyVisiting {
cyclic = true
return
}

state[node] = currentlyVisiting
for _, child := range graph[node] {
dfs(child)
}
state[node] = visited
}

for node := range graph {
dfs(node)
}
return cyclic
}
70 changes: 69 additions & 1 deletion internal/procparser/procfile_parser.go
Original file line number Diff line number Diff line change
@@ -1 +1,69 @@
package procparser
package procparser

import (
"fmt"
"os"
)

type (
Service struct {
Name string
Status bool
Process *os.Process
Cmd string
RunOnce bool
Checks ServiceChecks
Deps []string
}

ServiceChecks struct {
Cmd string
TcpPorts []string
UdpPorts []string
}
)

func ParseService(serviceMap map[string]any) Service {
service := Service{}

for key, value := range serviceMap {
switch key {
case "cmd":
service.cmd = value.(string)
case "run_once":
service.RunOnce = value.(bool)
case "deps":
for _, dep := range value.([]any) {
service.Deps = append(service.Deps, dep.(string))
}
case "checks":
service.Checks = parseChecks(value)
}
}
return service
}

func parseChecks(serviceChecks any) ServiceChecks {
checksMap := ServiceChecks{}

for key, value := range serviceChecks.(map[string]any) {
switch key {
case "cmd":
checksMap.Cmd = value.(string)
case "tcp_ports":
checksMap.TcpPorts = parsePorts(value)
case "udp_ports":
checksMap.UdpPorts = parsePorts(value)
}
}
return checksMap
}

func parsePorts(ports any) []string {
var parsedPorts []string
for _, port := range ports.([]any) {
parsedPorts = append(parsedPorts, fmt.Sprint(port.(int)))
}

return parsedPorts
}

0 comments on commit 7e80ddf

Please sign in to comment.