Skip to content

Commit

Permalink
Merge branch 'release/0.11.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
nbari committed Mar 31, 2017
2 parents 5f31d5e + a9f88d2 commit 789a46c
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 36 deletions.
94 changes: 81 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,95 @@ https://immortal.run/

[ ![Download](https://api.bintray.com/packages/nbari/immortal/immortal/images/download.svg) ](https://bintray.com/nbari/immortal/immortal/_latestVersion)

If services need to run on behalf other system user `www, nobody, www-data`,
not `root`, **immortal** should be compiled from source for the desired
target/architecture, otherwise, this error may be returned:

Error looking up user: "www". user: Lookup requires cgo

See more: https://golang.org/cmd/cgo/

If using FreeBSD or macOS you can install using [pkg/ports](http://immortal.run/freebsd/)
or [homebrew](http://immortal.run/mac/), for other platforms work is in progress,
any help for would be appreciated.

## Compile from source

Setup go environment https://golang.org/doc/install

> go >= 1.7 is required
For example using $HOME/go for your workspace

$ export GOPATH=$HOME/go

Create the directory:

$ mkdir -p $HOME/go/src/github.com/immortal

Clone project into that directory:

$ git clone git@github.com:immortal/immortal.git $HOME/go/src/github.com/immortal/immortal

Build by just typing make:

$ cd $HOME/go/src/github.com/immortal/immortal
$ make

To install/uninstall:

$ make install
$ make uninstall

# configuration example

Content of file `/usr/local/etc/immortal/www.yml`:

```yaml
# pkg install go-www
cmd: www
cwd: /usr/ports
log:
file: /var/log/www.log
age: 10 # seconds
num: 7 # int
size: 1 # MegaBytes
wait: 1
require:
- foo
- bar
```

If `foo` and `bar` are not running, the service `www` will not be started.

> `foo` and `bar` are the names for the services defined on the same path www.yaml is located, foo.yml & bar.yml
# Paths

When using immortaldir:

/usr/local/etc/immortal
|--api1.yml
|--api2.yml
`--api3.yml
|--foo.yml
|--bar.yml
`--www.yml

The name of the `file.yml` will be used to reference the service to be
daemonized excluding the extension `.yml`.:

The name of the `file.yml` will be used to reference the service to be daemonized.
foo
bar
www

## /var/run/immortal/<name>

/var/run/immortal
|--api1
|--foo
| |-lock
| `-immortal.sock
|--api2
|--bar
| |-lock
| `-immortal.sock
`--api3
`--www
|-lock
`-immortal.sock

Expand All @@ -42,14 +110,14 @@ structure:

~/.immortal
|--(pid)
| `--supervise
| `--immortal.sock
| |--lock
| `--immortal.sock
|--(pid)
| `--supervise
| `--immortal.sock
| |--lock
| `--immortal.sock
`--(pid)
`--supervise
`--immortal.sock
|--lock
`--immortal.sock

# immortalctl

Expand Down
19 changes: 19 additions & 0 deletions cmd/immortal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"log/syslog"
"os"
"os/user"
"path/filepath"
"strings"

"github.com/immortal/immortal"
)
Expand Down Expand Up @@ -43,6 +45,23 @@ func main() {
defer logger.Close()
}

// check for required services to be UP otherwise don't start
if len(cfg.Require) > 0 {
down := []string{}
ctl := &immortal.Controller{}
for _, r := range cfg.Require {
socket := filepath.Join(immortal.GetSdir(), r, "immortal.sock")
if status, err := ctl.GetStatus(socket); err != nil {
down = append(down, r)
} else if status.Up == "" {
down = append(down, r)
}
}
if len(down) > 0 {
log.Fatalf("required services are not UP: %s", strings.Join(down, ", "))
}
}

// fork
if os.Getppid() > 1 {
if pid, err := immortal.Fork(); err != nil {
Expand Down
5 changes: 3 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ type Config struct {
Log `yaml:",omitempty" json:",omitempty"`
Logger string `yaml:",omitempty" json:",omitempty"`
Pid `yaml:",omitempty" json:",omitempty"`
User string `yaml:",omitempty" json:",omitempty"`
Wait int `yaml:",omitempty"`
Require []string `yaml:",omitempty"`
User string `yaml:",omitempty" json:",omitempty"`
Wait int `yaml:",omitempty"`
command []string
ctl string
log bool
Expand Down
11 changes: 11 additions & 0 deletions example/require.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
cmd: /tmp/stdout
cwd: /tmp/
log:
file: /tmp/app.log
age: 10 # seconds
num: 7 # int
size: 1 # MegaBytes
wait: 2 # wait 2 seconds before starting
require:
- foo
- bar
10 changes: 10 additions & 0 deletions funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,13 @@ func inSlice(s []string, item string) bool {
}
return false
}

// GetSdir return the main supervise directory, defaults to /var/run/immortal
func GetSdir() string {
// if IMMORTAL_SDIR env is set, use it as default sdir
sdir := os.Getenv("IMMORTAL_SDIR")
if sdir == "" {
sdir = "/var/run/immortal"
}
return sdir
}
5 changes: 3 additions & 2 deletions man/immortal.8
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ Options start with one dash. Many of the options require an additional value nex
DBHOST=10.0.0.1
DBPASS=secret


-f <pidfile>
Follow PID in <pidfile>.
In some cases it is required to supervise applications that daemonize by default. Normally this kind of applications create a pidfile every time they fork, the one can be used to follow subsequent forks and avoid creating a race condition between the supervisor trying to start again the process and the forks created by the application.
Expand Down Expand Up @@ -83,7 +82,9 @@ Options start with one dash. Many of the options require an additional value nex
logger: <command> # option -logger
user: <user> # option -u
wait: <int> # option -s

require: # list of services that need to be running before starting
- foo
- bar

-ctl /var/run/immortal/<service>
Path where the supervise directory will be created. This directory is unique per service and is used to manage the service via a Unix socket besides preventing running multiple times the same service by using a lock.
Expand Down
8 changes: 2 additions & 6 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,13 @@ func ParseArgs(p Parser, fs *flag.FlagSet) (cfg *Config, err error) {
return
}

// if -ctl defaults to /var/run/immortal
// if -ctl, defaults to /var/run/immortal
var sdir string
if flags.Ctl != "" {
if s := filepath.Clean(flags.Ctl); strings.HasPrefix(s, "/") {
sdir = s
} else {
sdirEnv := os.Getenv("IMMORTAL_SDIR")
if sdirEnv == "" {
sdirEnv = "/var/run/immortal"
}
sdir = filepath.Join(sdirEnv, filepath.Base(s))
sdir = filepath.Join(GetSdir(), filepath.Base(s))
}
}

Expand Down
69 changes: 69 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,72 @@ func TestParseParseYmlioutil(t *testing.T) {
t.Error("Expecting error")
}
}

func TestParseYamlRequire(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "TestParseYamlRequire")
if err != nil {
t.Error(err)
}
defer os.Remove(tmpfile.Name())
yaml := []byte(`
cmd: command
wait: 1
require:
- service1
- service2`)
err = ioutil.WriteFile(tmpfile.Name(), yaml, 0644)
if err != nil {
t.Error(err)
}
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"cmd", "-c", tmpfile.Name()}
parser := &Parse{
UserLookup: MockLookup,
}
var helpCalled = false
fs := flag.NewFlagSet("TestParseArgsYamlUsrErr", flag.ContinueOnError)
fs.Usage = func() { helpCalled = true }
cfg, err := ParseArgs(parser, fs)
if err != nil {
t.Error(err)
}
if helpCalled {
t.Error("help called for regular flag")
}
expect(t, len(cfg.Require), 2)
expect(t, cfg.Require[0], "service1")
expect(t, cfg.Require[1], "service2")
}

func TestParseYamlRequireEmpty(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "TestParseYamlRequire")
if err != nil {
t.Error(err)
}
defer os.Remove(tmpfile.Name())
yaml := []byte(`
cmd: command
wait: 1`)
err = ioutil.WriteFile(tmpfile.Name(), yaml, 0644)
if err != nil {
t.Error(err)
}
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"cmd", "-c", tmpfile.Name()}
parser := &Parse{
UserLookup: MockLookup,
}
var helpCalled = false
fs := flag.NewFlagSet("TestParseArgsYamlUsrErr", flag.ContinueOnError)
fs.Usage = func() { helpCalled = true }
cfg, err := ParseArgs(parser, fs)
if err != nil {
t.Error(err)
}
if helpCalled {
t.Error("help called for regular flag")
}
expect(t, len(cfg.Require), 0)
}
10 changes: 3 additions & 7 deletions scandir.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,9 @@ func NewScanDir(path string) (*ScanDir, error) {
}
defer d.Close()

// if IMMORTAL_SDIR env is set, use it as default sdir
sdir := os.Getenv("IMMORTAL_SDIR")
if sdir == "" {
sdir = "/var/run/immortal"
}

return &ScanDir{
scandir: dir,
sdir: sdir,
sdir: GetSdir(),
services: map[string]string{},
timeMultipler: 5,
}, nil
Expand Down Expand Up @@ -118,6 +112,8 @@ func (s *ScanDir) Scaner(ctl Control) {
// try to start before via socket
if _, err := ctl.SendSignal(filepath.Join(s.sdir, name, "immortal.sock"), "start"); err != nil {
if out, err := ctl.Run(fmt.Sprintf("immortal -c %s -ctl %s", path, name)); err != nil {
// keep retrying
delete(s.services, name)
log.Println(err)
} else {
log.Printf("%s\n", out)
Expand Down
9 changes: 3 additions & 6 deletions scandir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ func TestScaner(t *testing.T) {
t.Fatal(err)
}
s.Scaner(ctl)
expect(t, "0af0f52bb73880b58d20ec86a9c5b1dc", s.services["run"])
// if error while starting, the service will be removed in order to keep retrying
expect(t, len(s.services), 0)
re = regexp.MustCompile(`return error 1`)
expect(t, "return error 1", re.FindString(buf.String()))
buf.Reset()
Expand All @@ -132,9 +133,6 @@ func TestScaner(t *testing.T) {
t.Fatal(err)
}
s.Scaner(ctl)
re = regexp.MustCompile(`Exiting: run`)
expect(t, "Exiting: run", re.FindString(buf.String()))
buf.Reset()
ctl.i++
ctl.j = -1
expect(t, 0, len(s.services))
Expand All @@ -144,15 +142,14 @@ func TestScaner(t *testing.T) {
t.Fatal(err)
}
s.Scaner(ctl)
expect(t, "9944429f23907af240460d0583a27cd2", s.services["run"])
expect(t, len(s.services), 0)
re = regexp.MustCompile(`Starting: run`)
expect(t, "Starting: run", re.FindString(buf.String()))
re = regexp.MustCompile(`can't start`)
expect(t, "can't start", re.FindString(buf.String()))
buf.Reset()
ctl.i++
ctl.j = -1
expect(t, 1, len(s.services))

// scan again and send signal START because it has passed less than 5 sec
s.Scaner(ctl)
Expand Down

0 comments on commit 789a46c

Please sign in to comment.