├── integration_tests ├── tests │ ├── test_reap_zombies │ │ ├── zombie.sh │ │ ├── slow-child.sh │ │ ├── slow-zombie.sh │ │ ├── containerpilot.json5 │ │ ├── docker-compose.yml │ │ └── run.sh │ ├── test_coprocess │ │ ├── coprocess.sh │ │ ├── docker-compose.yml │ │ └── run.sh │ ├── test_no_command │ │ ├── docker-compose.yml │ │ └── run.sh │ ├── test_envvars │ │ ├── docker-compose.yml │ │ ├── run.sh │ │ └── containerpilot.json5 │ ├── test_tasks │ │ ├── task.sh │ │ ├── docker-compose.yml │ │ ├── containerpilot.json5 │ │ └── run.sh │ ├── test_version_flag │ │ ├── docker-compose.yml │ │ └── run.sh │ ├── test_telemetry │ │ ├── check.sh │ │ ├── docker-compose.yml │ │ ├── containerpilot.json5 │ │ └── run.sh │ ├── test_reopen │ │ ├── run.sh │ │ ├── docker-compose.yml │ │ └── test_reopen.sh │ ├── test_config_reload │ │ ├── docker-compose.yml │ │ └── run.sh │ ├── test_logging │ │ ├── docker-compose.yml │ │ ├── containerpilot.json5 │ │ └── run.sh │ ├── test_sigterm │ │ ├── docker-compose.yml │ │ ├── containerpilot.json5 │ │ └── run.sh │ └── test_discovery_consul │ │ ├── docker-compose.yml │ │ ├── run.sh │ │ └── containerpilot.json5 ├── fixtures │ ├── app │ │ ├── sensor.sh │ │ ├── containerpilot-with-file-log.json5 │ │ ├── reload-containerpilot.sh │ │ ├── containerpilot-with-coprocess.json5 │ │ ├── Dockerfile │ │ ├── reload-app.sh │ │ └── containerpilot.json5 │ ├── consul │ │ ├── Dockerfile │ │ └── assert │ └── nginx │ │ ├── Dockerfile │ │ └── etc │ │ ├── nginx │ │ └── nginx.conf │ │ ├── nginx-with-consul.json5 │ │ └── nginx-consul.ctmpl └── README.md ├── sup ├── README.md └── sup.go ├── core ├── README.md ├── signals.go ├── testdata │ └── test.sh ├── signals_test.go ├── flags_test.go └── flags.go ├── jobs ├── README.md ├── testdata │ ├── TestJobConfigPeriodicTask.json5 │ ├── TestJobConfigServiceNonAdvertised.json5 │ ├── test_tasks.sh │ ├── test_coprocess.sh │ ├── TestErrJobConfigConsulEnableTagOverride.json5 │ ├── TestErrJobConfigConsulDeregisterCriticalServiceAfter.json5 │ ├── TestJobConfigConsulExtras.json5 │ ├── TestJobConfigServiceWithArrayExec.json5 │ ├── test.sh │ ├── TestJobConfigServiceWithStopping.json5 │ ├── TestJobConfigServiceWithPreStart.json5 │ └── TestJobConfigSmokeTest.json5 └── status.go ├── scripts ├── unit_test.sh ├── add_dep.sh ├── lint.sh ├── cover.sh └── docs.py ├── client ├── README.md └── client.go ├── config ├── README.md ├── services │ ├── docs.go │ ├── README.md │ ├── names.go │ └── names_test.go ├── decode │ ├── README.md │ ├── decode_test.go │ └── decode.go ├── logger │ ├── README.md │ ├── logging_test.go │ └── logging.go ├── timing │ ├── README.md │ ├── duration.go │ └── duration_test.go ├── template │ ├── README.md │ └── template_test.go └── testdata │ └── test.json5 ├── events ├── README.md ├── subscriber.go ├── eventcode_string.go ├── timer.go ├── events_test.go ├── handler.go ├── events.go └── bus.go ├── tests ├── README.md ├── mocks │ ├── README.md │ └── discovery.go └── tests.go ├── commands ├── README.md ├── testdata │ └── test.sh ├── args.go ├── args_test.go └── commands_test.go ├── control ├── README.md ├── config.go ├── config_test.go └── control_test.go ├── version ├── README.md └── version.go ├── watches ├── README.md ├── testdata │ ├── TestWatchesParse.json5 │ └── test.sh ├── config_test.go ├── config.go ├── watches_test.go └── watches.go ├── discovery ├── README.md ├── discovery.go ├── service.go └── config.go ├── telemetry ├── README.md ├── testdata │ ├── TestTelemetryConfigParse.json5 │ └── test.sh ├── telemetry_test.go ├── telemetry_config_test.go ├── status.go ├── metrics_config_test.go ├── telemetry_config.go ├── metrics_config.go ├── status_test.go ├── metrics.go └── telemetry.go ├── subcommands ├── README.md └── subcommands.go ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── Dockerfile ├── docs └── 30-configuration │ ├── 39-config-examples.md │ ├── examples │ ├── periodic-tasks.json5 │ ├── stopping.json5 │ ├── service-reg-only.json5 │ ├── database-config.json5 │ ├── nginx-upstreams.json5 │ └── consul-agent.json5 │ ├── README.md │ ├── 31-installation.md │ ├── 35-watches.md │ ├── 33-consul.md │ └── 38-logging.md ├── .travis.yml ├── .gitignore ├── glide.yaml ├── CONTRIBUTING.md ├── main.go ├── CHANGELOG.md └── glide.lock /integration_tests/tests/test_reap_zombies/zombie.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sleep 1 & exec /bin/sleep 2 3 | -------------------------------------------------------------------------------- /integration_tests/tests/test_reap_zombies/slow-child.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sleep 1 & 4 | sleep 1 & 5 | -------------------------------------------------------------------------------- /integration_tests/fixtures/app/sensor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /bin/containerpilot -putmetric 'containerpilot_app_some_counter=42' 4 | -------------------------------------------------------------------------------- /integration_tests/fixtures/consul/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM consul:latest 2 | RUN apk --no-cache add jq curl 3 | COPY assert /bin/assert 4 | -------------------------------------------------------------------------------- /integration_tests/tests/test_reap_zombies/slow-zombie.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /slow-child.sh & 4 | tail -F # need to make sure job is long-lived 5 | -------------------------------------------------------------------------------- /integration_tests/tests/test_coprocess/coprocess.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while true 4 | do 5 | echo "coprocess $1" 6 | sleep 1 7 | done 8 | -------------------------------------------------------------------------------- /sup/README.md: -------------------------------------------------------------------------------- 1 | ## sup 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/sup) 4 | -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 | ## core 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/core) 4 | -------------------------------------------------------------------------------- /jobs/README.md: -------------------------------------------------------------------------------- 1 | ## jobs 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/jobs) 4 | -------------------------------------------------------------------------------- /scripts/unit_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go test -v $(go list ./... | grep -v '/vendor\|_test' | sed 's+_/'$(pwd)'+github.com/joyent/containerpilot+') -bench . 4 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | ## client 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/client) 4 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | ## config 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/config) 4 | -------------------------------------------------------------------------------- /events/README.md: -------------------------------------------------------------------------------- 1 | ## events 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/events) 4 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | ## tests 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/tests) 4 | -------------------------------------------------------------------------------- /commands/README.md: -------------------------------------------------------------------------------- 1 | ## commands 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/commands) 4 | -------------------------------------------------------------------------------- /control/README.md: -------------------------------------------------------------------------------- 1 | ## control 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/control) 4 | -------------------------------------------------------------------------------- /version/README.md: -------------------------------------------------------------------------------- 1 | ## version 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/version) 4 | -------------------------------------------------------------------------------- /watches/README.md: -------------------------------------------------------------------------------- 1 | ## watches 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/watches) 4 | -------------------------------------------------------------------------------- /config/services/docs.go: -------------------------------------------------------------------------------- 1 | // Package services contains miscellaneous configuration validation for 2 | // service names and IP addresses registered with Consul 3 | package services 4 | -------------------------------------------------------------------------------- /discovery/README.md: -------------------------------------------------------------------------------- 1 | ## discovery 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/discovery) 4 | -------------------------------------------------------------------------------- /scripts/add_dep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | if [ -z "$DEP" ]; then 4 | echo "No dependency provided. Expected: DEP=" 5 | exit 1 6 | fi 7 | glide get ${DEP} 8 | -------------------------------------------------------------------------------- /telemetry/README.md: -------------------------------------------------------------------------------- 1 | ## telemetry 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/telemetry) 4 | -------------------------------------------------------------------------------- /tests/mocks/README.md: -------------------------------------------------------------------------------- 1 | ## mocks 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/tests/mocks) 4 | -------------------------------------------------------------------------------- /config/decode/README.md: -------------------------------------------------------------------------------- 1 | ## decode 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/config/decode) 4 | -------------------------------------------------------------------------------- /config/logger/README.md: -------------------------------------------------------------------------------- 1 | ## logger 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/config/logger) 4 | -------------------------------------------------------------------------------- /config/timing/README.md: -------------------------------------------------------------------------------- 1 | ## timing 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/config/timing) 4 | -------------------------------------------------------------------------------- /jobs/testdata/TestJobConfigPeriodicTask.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: "taskD", 4 | exec: "/bin/taskD", 5 | when: { 6 | interval: "1s" 7 | } 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /subcommands/README.md: -------------------------------------------------------------------------------- 1 | ## subcommands 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/subcommands) 4 | -------------------------------------------------------------------------------- /config/services/README.md: -------------------------------------------------------------------------------- 1 | ## services 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/config/services) 4 | -------------------------------------------------------------------------------- /config/template/README.md: -------------------------------------------------------------------------------- 1 | ## template 2 | 3 | [![GoDoc](https://godoc.org/github.com/joyent/containerpilot?status.svg)](https://godoc.org/github.com/joyent/containerpilot/config/template) 4 | -------------------------------------------------------------------------------- /scripts/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | result=0 3 | for pkg in $(glide novendor) 4 | do 5 | golint -set_exit_status "$pkg" || result=1 6 | go vet "$pkg" || result=1 7 | done 8 | exit $result 9 | -------------------------------------------------------------------------------- /jobs/testdata/TestJobConfigServiceNonAdvertised.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | // this job is a non-advertised persistent job 4 | name: "coprocessC", 5 | exec: "/bin/coprocessC", 6 | restarts: "unlimited" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /integration_tests/tests/test_no_command/docker-compose.yml: -------------------------------------------------------------------------------- 1 | app: 2 | image: "cpfix_app" 3 | mem_limit: 128m 4 | volumes: 5 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 6 | command: /bin/containerpilot 7 | -------------------------------------------------------------------------------- /watches/testdata/TestWatchesParse.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: "upstreamA", 4 | interval: 11, 5 | tag: "dev" 6 | }, 7 | { 8 | name: "upstreamB", 9 | interval: 79, 10 | dc: "us-east-1" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /integration_tests/tests/test_envvars/docker-compose.yml: -------------------------------------------------------------------------------- 1 | test: 2 | image: cpfix_app 3 | volumes: 4 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 5 | - './containerpilot.json5:/etc/containerpilot.json5' 6 | mem_limit: 128m 7 | -------------------------------------------------------------------------------- /integration_tests/tests/test_tasks/task.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SLEEP=${1:-"1"} 4 | FILE=${2:-"/tmp/test"} 5 | 6 | trap "echo INTERRUPTED >> $FILE" SIGINT SIGTERM SIGQUIT 7 | 8 | date +%s.%N >> "$FILE" 9 | sleep "$SLEEP" & 10 | wait 11 | -------------------------------------------------------------------------------- /integration_tests/tests/test_version_flag/docker-compose.yml: -------------------------------------------------------------------------------- 1 | app: 2 | image: "cpfix_consul" 3 | mem_limit: 128m 4 | volumes: 5 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 6 | command: /bin/containerpilot -version 7 | -------------------------------------------------------------------------------- /integration_tests/tests/test_telemetry/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | IP="$1" 5 | curl -s "${IP}:9090/status" | json -a .Services.0.Status | grep "healthy" 6 | curl -s "${IP}:9090/status" | json -a .Services.1.Status | grep "healthy" 7 | -------------------------------------------------------------------------------- /integration_tests/tests/test_version_flag/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose run app 4 | TEST_ID=$(docker ps -a | awk -F' +' '/testversionflag/{print $1}') 5 | docker logs "$TEST_ID" | grep dev-build-not-for-release 6 | result=$? 7 | 8 | exit $result 9 | -------------------------------------------------------------------------------- /events/subscriber.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | // Subscriber is an interface for types that will receive messages 4 | // from an EventBus 5 | type Subscriber interface { 6 | Subscribe(*EventBus, ...bool) 7 | Unsubscribe(*EventBus, ...bool) 8 | Receive(Event) 9 | Quit() 10 | } 11 | -------------------------------------------------------------------------------- /integration_tests/tests/test_envvars/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # run and make sure we get the env var out but print 5 | # the full result if it fails, for debugging 6 | result=$(docker-compose run test) 7 | echo "$result" | grep CONTAINERPILOT_TESTENVVAR_IP || (echo "$result" && exit 1) 8 | -------------------------------------------------------------------------------- /telemetry/testdata/TestTelemetryConfigParse.json5: -------------------------------------------------------------------------------- 1 | { 2 | port: 8000, 3 | interfaces: ["inet", "lo0"], 4 | metrics: [ 5 | { 6 | namespace: "telemetry", 7 | subsystem: "telemetry", 8 | name: "TestTelemetryParse", 9 | help: "help", 10 | type: "counter" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /integration_tests/tests/test_no_command/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | logFail() { 4 | echo "${1}" 5 | exit 1 6 | } 7 | 8 | docker-compose run -d app 9 | id=$(docker-compose ps -q app) 10 | docker logs "$id" | grep -qv panic 11 | result=$? 12 | docker stop "$id" || logFail 'should still be running' 13 | 14 | exit $result 15 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // Package version only provides some package variables set at build time 2 | package version 3 | 4 | var ( 5 | // Version is the version for this build, set at build time via LDFLAGS 6 | Version string 7 | // GitHash is the short-form commit hash of this build, set at build time 8 | GitHash string 9 | ) 10 | -------------------------------------------------------------------------------- /integration_tests/tests/test_envvars/containerpilot.json5: -------------------------------------------------------------------------------- 1 | { 2 | consul: "127.0.0.1:8500", 3 | logging: { level: "DEBUG" }, 4 | jobs: [ 5 | { 6 | name: "testenvvar", 7 | port: 8500, 8 | exec: "env", 9 | health: { 10 | exec: "true", 11 | interval: 10, 12 | ttl: 25 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /telemetry/testdata/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | trap 'exit 2' SIGTERM 4 | 5 | usage() { 6 | cat <> $2 16 | } 17 | 18 | printDots() { 19 | for i in {1..10}; do 20 | echo -n "." >> "$1" 21 | sleep 0.1 22 | done 23 | } 24 | 25 | cmd="${1:-usage}" 26 | shift 27 | $cmd "$@" 28 | -------------------------------------------------------------------------------- /jobs/testdata/test_coprocess.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | trap 'exit 2' SIGTERM 4 | 5 | usage() { 6 | cat <> $2 16 | } 17 | 18 | printDots() { 19 | for i in {1..10}; do 20 | echo -n "." >> "$1" 21 | sleep 0.1 22 | done 23 | } 24 | 25 | cmd="${1:-usage}" 26 | shift 27 | $cmd "$@" 28 | -------------------------------------------------------------------------------- /jobs/testdata/TestErrJobConfigConsulEnableTagOverride.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: "serviceA", 4 | port: 8080, 5 | interfaces: "inet", 6 | health: { 7 | exec: ["/bin/to/healthcheck/for/service/A.sh", "A1", "A2"], 8 | interval: 30, 9 | ttl: 19, 10 | timeout: "100ms" 11 | }, 12 | tags: ["tag1","tag2"], 13 | consul: { 14 | enableTagOverride: "nope" 15 | } 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /integration_tests/tests/test_reopen/docker-compose.yml: -------------------------------------------------------------------------------- 1 | app: 2 | image: "cpfix_app" 3 | mem_limit: 128m 4 | volumes: 5 | - './test_reopen.sh:/tmp/test_reopen.sh' 6 | environment: 7 | # needs to be in the container already or we'll potentially 8 | # error when we try to rewrite it; update in the future 9 | # with an env var change to the args instead 10 | - CONTAINERPILOT=/etc/containerpilot-with-file-log.json5 -------------------------------------------------------------------------------- /integration_tests/tests/test_config_reload/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | 5 | consul: 6 | image: "cpfix_consul" 7 | mem_limit: 128m 8 | hostname: consul 9 | command: agent -dev -client 0.0.0.0 -bind 0.0.0.0 10 | 11 | app: 12 | image: "cpfix_app" 13 | mem_limit: 512m 14 | links: 15 | - consul:consul 16 | volumes: 17 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 18 | -------------------------------------------------------------------------------- /jobs/testdata/TestErrJobConfigConsulDeregisterCriticalServiceAfter.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: "serviceA", 4 | port: 8080, 5 | interfaces: "inet", 6 | health: { 7 | exec: ["/bin/to/healthcheck/for/service/A.sh", "A1", "A2"], 8 | interval: 30, 9 | ttl: 19, 10 | timeout: "100ms" 11 | }, 12 | tags: ["tag1","tag2"], 13 | consul: { 14 | deregisterCriticalServiceAfter: "nope" 15 | } 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /scripts/cover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | OUT=${OUT:-cover/cover.out} 3 | TMP=${TMP:-cover/temp.out} 4 | echo "mode: set" > $OUT 5 | for pkg in $(go list ./... | grep -v '/vendor/\|_test' | sed 's+_/'$(pwd)'+github.com/joyent/containerpilot+'); 6 | do 7 | go test -v -coverprofile=$TMP $pkg 8 | if [ -f $TMP ]; then 9 | cat $TMP | grep -v "mode: set" >> $OUT 10 | fi 11 | done 12 | rm -rf $TMP 13 | go tool cover -html=cover/cover.out -o cover/cover.html 14 | -------------------------------------------------------------------------------- /integration_tests/tests/test_reap_zombies/containerpilot.json5: -------------------------------------------------------------------------------- 1 | { 2 | consul: "consul:8500", 3 | jobs: [ 4 | { 5 | name: "zombies", 6 | port: 8000, 7 | exec: ["tail", "-f"], 8 | health: { 9 | exec: "/zombie.sh", 10 | interval: 1, 11 | ttl: 5 12 | }, 13 | tags: ["application"] 14 | }, 15 | { 16 | name: "slow-zombies", 17 | exec: "/slow-zombie.sh" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /jobs/testdata/TestJobConfigConsulExtras.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: "serviceA", 4 | port: 8080, 5 | interfaces: ["inet", "lo0"], 6 | exec: "/bin/serviceA", 7 | health: { 8 | exec: "/bin/to/healthcheck/for/service/A.sh", 9 | interval: 10, 10 | ttl: 30, 11 | }, 12 | tags: ["tag1","tag2"], 13 | consul: { 14 | deregisterCriticalServiceAfter: "10m", 15 | enableTagOverride: true 16 | } 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /jobs/testdata/TestJobConfigServiceWithArrayExec.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | // fields describing this job are arrays wherever permitted, 4 | // so that we exercise the parsing for arrays 5 | name: "serviceB", 6 | port: 5000, 7 | interfaces: ["ethwe","eth0", "inet", "lo0"], 8 | exec: ["/bin/serviceB", "B"], 9 | health: { 10 | exec: ["/bin/healthCheckB.sh", "B1", "B2"], 11 | interval: 20, 12 | ttl: 103, 13 | timeout: "2s" 14 | } 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thanks for contributing to ContainerPilot! Please include with your pull request: 2 | 3 | - A description of what you did for the changelog 4 | - An explanation of why ContainerPilot needs this change 5 | - How to verify that it works (most PRs need tests!) 6 | - A link to the GitHub issue that it addresses 7 | 8 | If you're contributing a new feature, it's usually better to open an issue to discuss the feature rather than open a pull request unless the feature is trivial. 9 | -------------------------------------------------------------------------------- /integration_tests/tests/test_logging/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | 5 | consul: 6 | image: "cpfix_consul" 7 | mem_limit: 128m 8 | hostname: consul 9 | command: agent -dev -client 0.0.0.0 -bind 0.0.0.0 10 | 11 | app: 12 | image: "cpfix_app" 13 | mem_limit: 128m 14 | links: 15 | - consul:consul 16 | volumes: 17 | - './containerpilot.json5:/etc/containerpilot.json5' 18 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 19 | -------------------------------------------------------------------------------- /core/signals.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | ) 8 | 9 | // HandleSignals listens for and captures signals used for orchestration 10 | func (a *App) handleSignals() { 11 | sig := make(chan os.Signal, 1) 12 | signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT) 13 | go func() { 14 | for signal := range sig { 15 | switch signal { 16 | case syscall.SIGINT: 17 | a.Terminate() 18 | case syscall.SIGTERM: 19 | a.Terminate() 20 | } 21 | } 22 | }() 23 | } 24 | -------------------------------------------------------------------------------- /integration_tests/tests/test_tasks/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | 5 | consul: 6 | image: "cpfix_consul" 7 | mem_limit: 128m 8 | hostname: consul 9 | command: agent -dev -client 0.0.0.0 -bind 0.0.0.0 10 | 11 | app: 12 | image: "cpfix_app" 13 | mem_limit: 128m 14 | links: 15 | - consul:consul 16 | volumes: 17 | - './task.sh:/task.sh' 18 | - './containerpilot.json5:/etc/containerpilot.json5' 19 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 20 | -------------------------------------------------------------------------------- /integration_tests/fixtures/app/reload-containerpilot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | single() { 5 | /bin/containerpilot -reload > /dev/null 2>&1 6 | exit 0 7 | } 8 | 9 | multi() { 10 | # we mask the output here because we expect many many lines that 11 | # say "dial unix /var/run/containerpilot.sock: connect: no such 12 | # file or directory" while the config is being reloaded 13 | for i in {1..200}; do 14 | /bin/containerpilot -reload > /dev/null 2>&1 15 | done 16 | exit 0 17 | } 18 | 19 | cmd="$1" 20 | "$cmd" 21 | -------------------------------------------------------------------------------- /integration_tests/tests/test_telemetry/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | 5 | consul: 6 | image: "cpfix_consul" 7 | mem_limit: 128m 8 | hostname: consul 9 | command: agent -dev -client 0.0.0.0 -bind 0.0.0.0 10 | 11 | app: 12 | image: "cpfix_app" 13 | mem_limit: 128m 14 | links: 15 | - consul:consul 16 | ports: 17 | - 9090:9090 18 | volumes: 19 | - './containerpilot.json5:/etc/containerpilot.json5' 20 | - './check.sh:/check.sh' 21 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.8 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y unzip \ 5 | && go get github.com/golang/lint/golint \ 6 | && curl -Lo /tmp/glide.tgz "https://github.com/Masterminds/glide/releases/download/v0.12.3/glide-v0.12.3-linux-amd64.tar.gz" \ 7 | && tar -C /usr/bin -xzf /tmp/glide.tgz --strip=1 linux-amd64/glide \ 8 | && curl --fail -Lso consul.zip "https://releases.hashicorp.com/consul/0.7.5/consul_0.7.5_linux_amd64.zip" \ 9 | && unzip consul.zip -d /usr/bin 10 | 11 | ENV CGO_ENABLED 0 12 | ENV GOPATH /go:/cp 13 | -------------------------------------------------------------------------------- /core/testdata/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | trap 'exit 2' SIGTERM 4 | 5 | usage() { 6 | cat < docker-compose 18 | - chmod +x docker-compose 19 | - sudo mv docker-compose /usr/local/bin 20 | 21 | install: 22 | - make vendor 23 | 24 | script: 25 | - make lint build 26 | - make test 27 | - make integration 28 | -------------------------------------------------------------------------------- /commands/testdata/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | trap 'exit 2' SIGTERM 4 | 5 | usage() { 6 | cat <= EventCode(len(eventCodeindex)-1) { 13 | return fmt.Sprintf("EventCode(%d)", i) 14 | } 15 | return eventCodename[eventCodeindex[i]:eventCodeindex[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | .DS_Store 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | 27 | # more build and test outputs 28 | build/ 29 | release/ 30 | cover/ 31 | vendor/ 32 | .glide 33 | *.tar.gz 34 | *.log 35 | cover.out 36 | cover.html 37 | 38 | # fake paths from overlaying volumes in Docker 39 | /src 40 | 41 | # IDE files 42 | .idea 43 | *.iml 44 | -------------------------------------------------------------------------------- /integration_tests/fixtures/app/containerpilot-with-coprocess.json5: -------------------------------------------------------------------------------- 1 | { 2 | consul: "consul:8500", 3 | logging: { 4 | level: "DEBUG", 5 | format: "text" 6 | }, 7 | jobs: [ 8 | { 9 | name: "app", 10 | port: 8000, 11 | exec: [ 12 | "/usr/local/bin/node", 13 | "/usr/local/bin/http-server", "/srv", "-p", "8000"], 14 | health: { 15 | exec: "/usr/bin/curl --fail -s -o /dev/null http://localhost:8000", 16 | interval: 1, 17 | ttl: 5 18 | } 19 | }, 20 | { 21 | name: "coprocess", 22 | exec: ["/bin/coprocess.sh", "arg1"], 23 | restarts: 1 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /integration_tests/tests/test_reap_zombies/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | 5 | consul: 6 | image: "cpfix_consul" 7 | mem_limit: 128m 8 | hostname: consul 9 | command: agent -dev -client 0.0.0.0 -bind 0.0.0.0 10 | 11 | zombies: 12 | image: alpine:3.5 13 | mem_limit: 128m 14 | links: 15 | - consul:consul 16 | volumes: 17 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 18 | - './containerpilot.json5:/etc/containerpilot.json5' 19 | - './zombie.sh:/zombie.sh' 20 | - './slow-zombie.sh:/slow-zombie.sh' 21 | - './slow-child.sh:/slow-child.sh' 22 | command: /bin/containerpilot -config /etc/containerpilot.json5 23 | -------------------------------------------------------------------------------- /integration_tests/tests/test_sigterm/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | 5 | consul: 6 | image: "cpfix_consul" 7 | mem_limit: 128m 8 | hostname: consul 9 | command: agent -dev -client 0.0.0.0 -bind 0.0.0.0 10 | 11 | app: 12 | image: "cpfix_app" 13 | mem_limit: 128m 14 | links: 15 | - consul:consul 16 | volumes: 17 | - './containerpilot.json5:/etc/containerpilot.json5' 18 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 19 | 20 | test: 21 | image: "cpfix_test_probe" 22 | mem_limit: 128m 23 | links: 24 | - consul:consul 25 | - app:app 26 | volumes: 27 | - '/var/run/docker.sock:/var/run/docker.sock' 28 | -------------------------------------------------------------------------------- /config/services/names_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestValidateName(t *testing.T) { 8 | 9 | var validNames = []string{ 10 | "myService", 11 | "my-service", 12 | "my-service-123", 13 | } 14 | for _, name := range validNames { 15 | if err := ValidateName(name); err != nil { 16 | t.Errorf("expected no error for name '%v' but got %v", name, err) 17 | } 18 | } 19 | 20 | var invalidNames = []string{ 21 | "my_service", 22 | "-my-service", 23 | "my%service", 24 | } 25 | for _, name := range invalidNames { 26 | if err := ValidateName(name); err == nil { 27 | t.Errorf("expected error for name '%v' but got nil", name) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jobs/status.go: -------------------------------------------------------------------------------- 1 | package jobs 2 | 3 | // JobStatus is an enum of job health status 4 | type JobStatus int 5 | 6 | // JobStatus enum 7 | const ( 8 | statusIdle JobStatus = iota // will be default value before starting 9 | statusUnknown 10 | statusHealthy 11 | statusUnhealthy 12 | statusMaintenance 13 | statusAlwaysHealthy 14 | ) 15 | 16 | func (i JobStatus) String() string { 17 | switch i { 18 | case 2: 19 | return "healthy" 20 | case 3: 21 | return "unhealthy" 22 | case 4: 23 | return "maintenance" 24 | case 5: 25 | // for hardcoded "always healthy" jobs 26 | return "healthy" 27 | default: 28 | // both idle and unknown return unknown for purposes of serialization 29 | return "unknown" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /integration_tests/tests/test_coprocess/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | 5 | consul: 6 | image: "cpfix_consul" 7 | mem_limit: 128m 8 | hostname: consul 9 | command: agent -dev -client 0.0.0.0 -bind 0.0.0.0 10 | 11 | app: 12 | image: "cpfix_app" 13 | mem_limit: 128m 14 | links: 15 | - consul:consul 16 | environment: 17 | # needs to be in the container already or we'll potentially 18 | # error when we try to rewrite it; update in the future 19 | # with an env var change to the args instead 20 | - CONTAINERPILOT=/etc/containerpilot-with-coprocess.json5 21 | volumes: 22 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 23 | - './coprocess.sh:/bin/coprocess.sh' 24 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/joyent/containerpilot 2 | homepage: https://www.joyent.com/containerpilot 3 | license: MPL-2.0 4 | import: 5 | - package: github.com/sirupsen/logrus 6 | version: 1.0.0 7 | - package: github.com/hashicorp/consul 8 | version: ~0.8.4 9 | subpackages: 10 | - api 11 | - package: github.com/mitchellh/mapstructure 12 | version: d2dd0262208475919e1a362f675cfc0e7c10e905 13 | - package: github.com/prometheus/client_golang 14 | version: 0.8.0 15 | subpackages: 16 | - prometheus 17 | - package: github.com/flynn/json5 18 | version: 7620272ed63390e979cf5882d2fa0506fe2a8db5 19 | testImport: 20 | - package: github.com/stretchr/testify 21 | version: v1.1.4 22 | subpackages: 23 | - assert 24 | - package: github.com/client9/reopen 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thanks for reporting an issue with ContainerPilot! 2 | 3 | If you're reporting a problem, having trouble with a configuration, or think you've found a bug, please be sure to include the following information: 4 | 5 | - what is happening and what you expect to see 6 | - the output of `containerpilot -version` 7 | - the ContainerPilot configuration you're using 8 | - the output of any logs you can share; if you can it would be very helpful to turn on debug logging by adding `logging: { level: "DEBUG"}` to your ContainerPilot configuration. 9 | 10 | If you can't provide some of this information because it's private, include what you can and we'll try to assist anyways. If you're a Joyent customer you can also reach out to Joyent's support team. 11 | -------------------------------------------------------------------------------- /integration_tests/tests/test_sigterm/containerpilot.json5: -------------------------------------------------------------------------------- 1 | { 2 | consul: "consul:8500", 3 | logging: { 4 | level: "DEBUG", 5 | format: "text" 6 | }, 7 | jobs: [ 8 | { 9 | name: "app", 10 | port: 8000, 11 | exec: "sleep 60", 12 | health: { 13 | exec: "true", 14 | interval: 1, 15 | ttl: 5, 16 | }, 17 | }, 18 | { 19 | name: "preStop", 20 | exec: "echo 'preStop fired on app stopping'", 21 | when: { 22 | source: "app", 23 | once: "stopping" 24 | }, 25 | }, 26 | { 27 | name: "postStop", 28 | exec: "echo 'postStop fired on app stopped'", 29 | when: { 30 | source: "app", 31 | once: "stopped" 32 | }, 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /commands/args.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "github.com/joyent/containerpilot/config/decode" 8 | ) 9 | 10 | // ParseArgs parses the executable and its arguments from supported 11 | // types. 12 | func ParseArgs(raw interface{}) (executable string, args []string, err error) { 13 | switch t := raw.(type) { 14 | case string: 15 | if t != "" { 16 | args = strings.Split(strings.TrimSpace(t), " ") 17 | } 18 | default: 19 | args, err = decode.ToStrings(raw) 20 | } 21 | if len(args) == 0 { 22 | err = errors.New("received zero-length argument") 23 | } else if len(args) == 1 { 24 | executable = args[0] 25 | args = nil 26 | } else { 27 | executable = args[0] 28 | args = args[1:] 29 | } 30 | return executable, args, err 31 | } 32 | -------------------------------------------------------------------------------- /jobs/testdata/TestJobConfigServiceWithStopping.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: "serviceA", 4 | port: 8080, 5 | interfaces: ["inet", "lo0"], 6 | exec: "/bin/serviceA.sh", 7 | when: { 8 | source: "preStart", 9 | once: "exitSuccess", 10 | }, 11 | health: { 12 | interval: 1, 13 | ttl: 5, 14 | exec: "/bin/checkA.sh" 15 | } 16 | }, 17 | { 18 | name: "preStart", 19 | exec: "/bin/to/preStart.sh arg1 arg2" 20 | }, 21 | { 22 | name: "preStop", 23 | when: { 24 | source: "serviceA", 25 | once: "stopping" 26 | }, 27 | exec: ["/bin/to/preStop.sh","arg1","arg2"] 28 | }, 29 | { 30 | name: "postStop", 31 | when: { 32 | source: "serviceA", 33 | once: "stopped" 34 | }, 35 | exec: ["/bin/to/postStop.sh"] 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /integration_tests/tests/test_reopen/test_reopen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CONTAINERPILOT_LOGFILE="/tmp/containerpilot.log" 4 | CONTAINERPILOT_ROTATEDLOGFILE="/tmp/containerpilot.log.1" 5 | 6 | sleep 2 7 | logs=`cat $CONTAINERPILOT_LOGFILE` 8 | nb_lines=`cat $CONTAINERPILOT_LOGFILE | wc -l` 9 | if [ ! -f $CONTAINERPILOT_LOGFILE ] || [[ $logs != *"hello world"* ]]; then 10 | exit 1 11 | fi 12 | 13 | #rotate logs 14 | mv $CONTAINERPILOT_LOGFILE $CONTAINERPILOT_ROTATEDLOGFILE 15 | 16 | sleep 2 17 | nb_lines_rotated=`cat $CONTAINERPILOT_ROTATEDLOGFILE | wc -l` 18 | if (( $nb_lines_rotated <= $nb_lines )); then 19 | exit 1 20 | fi 21 | 22 | #signal containerpilot to reopen file 23 | kill -SIGUSR1 1 24 | 25 | sleep 2 26 | logs=`cat $CONTAINERPILOT_LOGFILE` 27 | if [ ! -f $CONTAINERPILOT_LOGFILE ] || [[ $logs != *"hello world"* ]]; then 28 | exit 1 29 | fi 30 | 31 | exit 0 -------------------------------------------------------------------------------- /jobs/testdata/TestJobConfigServiceWithPreStart.json5: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | name: "serviceA", 4 | port: 8080, 5 | interfaces: ["inet", "lo0"], 6 | exec: "/bin/serviceA.sh", 7 | when: { 8 | source: "preStart", 9 | once: "exitSuccess", 10 | }, 11 | health: { 12 | exec: "/bin/healthCheckA.sh A1 A2", 13 | interval: 10, 14 | ttl: 30, 15 | }, 16 | tags: ["tag1","tag2"] 17 | }, 18 | { 19 | name: "preStart", 20 | exec: "/bin/to/preStart.sh arg1 arg2" 21 | }, 22 | { 23 | name: "preStop", 24 | when: { 25 | source: "serviceA", 26 | once: "stopping" 27 | }, 28 | exec: ["/bin/to/preStop.sh","arg1","arg2"] 29 | }, 30 | { 31 | name: "postStop", 32 | when: { 33 | source: "serviceA", 34 | once: "stopped" 35 | }, 36 | exec: ["/bin/to/postStop.sh"] 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /control/config.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/joyent/containerpilot/config/decode" 7 | ) 8 | 9 | // DefaultSocket is the default location of the unix domain socket file 10 | var DefaultSocket = "/var/run/containerpilot.socket" 11 | 12 | // Config represents the location on the file system which serves the Unix 13 | // control socket file. 14 | type Config struct { 15 | SocketPath string `mapstructure:"socket"` 16 | } 17 | 18 | // NewConfig parses a json config into a validated Config used by control 19 | // Server. 20 | func NewConfig(raw interface{}) (*Config, error) { 21 | cfg := &Config{SocketPath: DefaultSocket} // defaults 22 | if raw == nil { 23 | return cfg, nil 24 | } 25 | 26 | if err := decode.ToStruct(raw, cfg); err != nil { 27 | return nil, fmt.Errorf("control config parsing error: %v", err) 28 | } 29 | 30 | return cfg, nil 31 | } 32 | -------------------------------------------------------------------------------- /integration_tests/fixtures/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:slim 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y \ 5 | curl netcat-openbsd && \ 6 | rm -rf /var/lib/apt/lists/* 7 | 8 | RUN npm install -g json http-server 9 | 10 | COPY build/containerpilot /bin/containerpilot 11 | COPY containerpilot.json5 /etc/containerpilot.json5 12 | COPY containerpilot-with-coprocess.json5 /etc/containerpilot-with-coprocess.json5 13 | COPY containerpilot-with-file-log.json5 /etc/containerpilot-with-file-log.json5 14 | COPY reload-app.sh /reload-app.sh 15 | COPY reload-containerpilot.sh /reload-containerpilot.sh 16 | COPY sensor.sh /sensor.sh 17 | 18 | ENV CONTAINERPILOT=/etc/containerpilot.json5 19 | 20 | # default port, allows us to override in docker-compose and also test 21 | # env var interpolation in the command args 22 | ENV APP_PORT=8000 23 | 24 | ENTRYPOINT [ "/bin/containerpilot" ] 25 | -------------------------------------------------------------------------------- /integration_tests/fixtures/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | # Nginx container including ContainerPilot 2 | FROM alpine:3.3 3 | 4 | # install nginx and tooling we need 5 | RUN apk update && apk add \ 6 | nginx \ 7 | curl \ 8 | unzip \ 9 | && rm -rf /var/cache/apk/* 10 | 11 | # we use consul-template to re-write our Nginx virtualhost config 12 | RUN curl -Lo /tmp/consul_template_0.14.0_linux_amd64.zip https://releases.hashicorp.com/consul-template/0.14.0/consul-template_0.14.0_linux_amd64.zip && \ 13 | unzip /tmp/consul_template_0.14.0_linux_amd64.zip && \ 14 | mv consul-template /bin 15 | 16 | # add ContainerPilot build and configuration 17 | COPY build/containerpilot /bin/containerpilot 18 | COPY etc /etc 19 | 20 | EXPOSE 80 21 | 22 | # by default use nginx-with-consul.json, allows for override in docker-compose 23 | ENV CONTAINERPILOT=/etc/nginx-with-consul.json5 24 | 25 | ENTRYPOINT [ "/bin/containerpilot" ] 26 | -------------------------------------------------------------------------------- /integration_tests/tests/test_discovery_consul/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | 5 | consul: 6 | image: "cpfix_consul" 7 | mem_limit: 128m 8 | hostname: consul 9 | command: agent -dev -client 0.0.0.0 -bind 0.0.0.0 10 | 11 | app: 12 | image: "cpfix_app" 13 | mem_limit: 128m 14 | links: 15 | - consul:consul 16 | volumes: 17 | - './containerpilot.json5:/etc/containerpilot.json5' 18 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 19 | 20 | nginx: 21 | image: "cpfix_nginx" 22 | mem_limit: 128m 23 | links: 24 | - consul:consul 25 | volumes: 26 | - '${CONTAINERPILOT_BIN}:/bin/containerpilot:ro' 27 | 28 | test: 29 | image: "cpfix_test_probe" 30 | mem_limit: 128m 31 | links: 32 | - app:app 33 | - nginx:nginx 34 | - consul:consul 35 | volumes: 36 | - '/var/run/docker.sock:/var/run/docker.sock' 37 | -------------------------------------------------------------------------------- /docs/30-configuration/examples/periodic-tasks.json5: -------------------------------------------------------------------------------- 1 | /* 2 | This example demonstrates how a user can configure periodic tasks 3 | and set up jobs that respond to failure of those tasks 4 | */ 5 | { 6 | consul: "consul:8500", 7 | jobs: [ 8 | { 9 | name: "task1", 10 | exec: "/bin/run/some/task.sh" 11 | restarts: "unlimited", 12 | when: { 13 | interval: "60s" 14 | } 15 | }, 16 | { 17 | name: "alarm-task1", 18 | exec: [ 19 | // please don't really post production alarms to Slack, it's a silly example 20 | "/usr/bin/curl", "--fail", "-sL", 21 | "-XPOST", "-H", "'Content-Type': application/json", 22 | "--data", "'{\"text\": \"oh no!\"}'", 23 | "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXX" 24 | ], 25 | when: { 26 | source: "task1", 27 | each: "exitFailed" 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /integration_tests/tests/test_sigterm/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | function finish { 6 | local result=$? 7 | if [[ "$result" -ne 0 ]]; then docker logs "$app" | tee app.log; fi 8 | exit $result 9 | } 10 | 11 | trap finish EXIT 12 | 13 | 14 | # start up consul and wait for leader election 15 | docker-compose up -d consul 16 | consul=$(docker-compose ps -q consul) 17 | docker exec -it "$consul" assert ready 18 | 19 | # start up app and wait for it to register 20 | docker-compose up -d app 21 | app=$(docker-compose ps -q app) 22 | docker exec -it "$consul" assert service app 1 23 | 24 | # sigterm app container 25 | docker stop "$app" 26 | 27 | # and verify it's exited gracefully from consul, and that 28 | # both preStop and postStop jobs have executed 29 | docker exec -it "$consul" assert service app 1 30 | docker logs "$app" | grep "msg=\"'preStop fired on app stopping" 31 | docker logs "$app" | grep "msg=\"'postStop fired on app stopped" 32 | -------------------------------------------------------------------------------- /control/config_test.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/joyent/containerpilot/tests" 8 | ) 9 | 10 | func TestControlConfigDefault(t *testing.T) { 11 | cfg, err := NewConfig(nil) 12 | if err != nil { 13 | t.Fatalf("could not parse control config JSON: %s", err) 14 | } 15 | 16 | if strings.Compare(cfg.SocketPath, DefaultSocket) != 0 { 17 | t.Fatal("parsed socket does not match default socket") 18 | } 19 | } 20 | 21 | func TestControlConfigParse(t *testing.T) { 22 | testSocket := "/var/run/cp3.sock" 23 | testRaw := tests.DecodeRaw(`{ "socket": "/var/run/cp3.sock" }`) 24 | if testRaw == nil { 25 | t.Fatal("parsed empty control config JSON") 26 | } 27 | 28 | cfg, err := NewConfig(testRaw) 29 | if err != nil { 30 | t.Fatalf("could not parse control config JSON: %s", err) 31 | } 32 | 33 | if strings.Compare(cfg.SocketPath, testSocket) != 0 { 34 | t.Fatal("parsed socket does not match custom socket") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /integration_tests/tests/test_telemetry/containerpilot.json5: -------------------------------------------------------------------------------- 1 | { 2 | consul: "consul:8500", 3 | logging: { 4 | level: "DEBUG", 5 | format: "text" 6 | }, 7 | jobs: [ 8 | { 9 | name: "app", 10 | port: 8000, 11 | exec: [ 12 | "/usr/local/bin/node", 13 | "/usr/local/bin/http-server", "/srv", "-p", "8000"], 14 | health: { 15 | exec: "true", 16 | interval: 1, 17 | ttl: 5 18 | } 19 | }, 20 | { 21 | name: "sensor", 22 | exec: ["/sensor.sh", "count"], 23 | when: { 24 | interval: "1s" 25 | } 26 | } 27 | ], 28 | watches: [ 29 | { 30 | name: "otherapp", 31 | interval: 1 32 | } 33 | ], 34 | telemetry: { 35 | port: 9090, 36 | metrics: [ 37 | { 38 | namespace: "containerpilot", 39 | subsystem: "app", 40 | name: "some_counter", 41 | help: "help text", 42 | type: "counter" 43 | } 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /integration_tests/tests/test_logging/containerpilot.json5: -------------------------------------------------------------------------------- 1 | { 2 | consul: "consul:8500", 3 | logging: { 4 | level: "DEBUG", 5 | format: "text" 6 | }, 7 | jobs: [ 8 | { 9 | name: "job1", 10 | // this echo gets wrapped in a log line 11 | exec: "echo job1 exec", 12 | }, 13 | { 14 | name: "job2", 15 | // this echo is not wrapped in a log line 16 | exec: "echo job2 exec", 17 | logging: { raw: true }, 18 | }, 19 | { 20 | name: "job3", 21 | port: 8000, 22 | exec: "sleep 5", 23 | health: { 24 | // this echo is not wrapped in a log line 25 | exec: "echo job3 health", 26 | logging: { raw: true }, 27 | interval: 1, 28 | ttl: 5 29 | } 30 | }, 31 | { 32 | name: "job4", 33 | port: 9000, 34 | exec: "sleep 5", 35 | health: { 36 | // this echo gets wrapped in a log line 37 | exec: "echo job4 health", 38 | interval: 1, 39 | ttl: 5 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /integration_tests/fixtures/app/reload-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # wait a few seconds for the Consul container to become available 4 | n=0 5 | while true 6 | do 7 | if [ n == 10 ]; then 8 | echo "Timed out waiting for Consul" 9 | exit 1; 10 | fi 11 | curl -Ls --fail http://consul:8500/v1/status/leader | grep 8300 && break 12 | n=$((n+1)) 13 | sleep 1 14 | done 15 | 16 | # get all the healthy application servers and write the json to file 17 | curl -s consul:8500/v1/health/service/app?passing | json > /tmp/lastQuery.json 18 | 19 | cat < /srv/index.html 20 | 21 | 22 | ContainerPilot Demo 23 | 28 | 29 | 30 |

ContainerPilot Demo

31 |

This page served by app server: $(hostname)

32 | Last service health check changed at $(date): 33 |
34 | $(cat /tmp/lastQuery.json)
35 | 
36 |