Skip to content
/ ephemerald Public

Ephemeral docker-based server instances for (parallel) testing

License

Notifications You must be signed in to change notification settings

boz/ephemerald

Repository files navigation

ephemerald

Ephemerald manages pools of short-lived servers to be used for testing purposes. It was built to allow paralallel integration tests.

asciicast

It has REST API for accessing server instances from any language and comes with a built-in go client. See the examples directory for example configurations and client usage.

The ephemerald server can run on a remote host; container connection parameters are rewritten so that the client connects to the right place. This way ephemerald can run on a large server and be used from less powerful development machines.

Running

To run the server, supply a configuration file:

$ ephemerald -c config.yaml

Press Q to quit the server.

Flags

  • --help print help message.
  • -p <port> changes the listen port. Defaults to 6000
  • --ui stream will dump the event steam to the console in lieu of a curses-esque UI.
  • --ui none will not print any UI information (useful with --log-file /dev/stdout)
  • --log-file <path> write logs to file at path. Defaults to /dev/null
  • --log-level <level> log level. defaults to info. Options are debug,info,warn,error

Note: use Ctrl-C to stop the server wen not in --ui tui mode (SIGINT,SIGQUIT always work too)

For example, to see only log messages (at debug level) use:

$ ephememerald --ui none --log-level debug --log-file /dev/stdout -c config.yaml

Configuration

Container pools are configured in a yaml (or json) file. Each pool has options for the container parameters and for lifecycle actions.

The following configuration creates a single pool called "pg" which maintains five containers from the "postgres" image and exposes port 5432 to clients. See the params and actions below for documentation on those fields.

pools:
  pg:
    image: postgres
    size: 5
    port: 5432
    params:
      username: postgres
      database: postgres
      url: postgres://{{.Username}}:@{{.Hostname}}:{{.Port}}/{{.Database}}?sslmode=disable
    actions:
      healthcheck:
        type: postgres.ping
        retries: 10
        delay:   50ms
      initialize:
        type:    exec
        path:    make
        args:    [ 'db:migrate' ]
        env:     [ 'DATABASE_URL={{.Url}}' ]
        timeout: 10s
      reset:
        type: postgres.truncate

See example/config.yaml for a full working configuration.

Params

The params entry allows for declaring parameters needed for connecting to the service. There are three fields with arbitrary values: username, password, database.

The url can be a golang template and will be executed with access to the following fields:

Name Value
Hostname The hostname that the container can be connected at
Port The (automatically-generated) port number that is mapped to the exposed container port
Username The username field declared in params
Password The password field declared in params
Database The database field declared in params

A params section for postgres may look like this:

username: postgres
database: postgres
url: postgres://{{.Username}}:{{.Password}}@{{.Hostname}}:{{.Port}}/{{.Database}}?sslmode=disable

Container

The container section is passed-through to docker when creating the container. The available options are:

  • labels
  • env
  • cmd
  • volumes
  • entrypoint
  • user
  • capadd
  • capdrop

Lifecycle Actions

There are three lifecycle actions: healthcheck, initialize, and reset.

  • healthcheck is used to determine when the container is ready to be used.
  • initialize is used to initialize the container (run migrations, etc...)
  • reset may be used to revert the container to a state where it can be used again.

All of them are optional (though healthcheck should be used). If reset is not given, the container will be killed and a new one will be created to replace it.

Each action has, at a minimum, the following three parameters:

Name Default Description
retries 3 number of times to retry the action
timeout 1s amount of time to allow for the action
delay 500ms amount of time to delay before retrying

timeout and delay are durations; they must have a unit suffix as described here.

Note: actions may have different defaults for these fields.

noop

Does nothing. Useful as the reset action so that a container is always reused.

exec

Execute a command on the host operating system. Useful for running migrations to initialize a database.

Extra Parameters:

Name Default Description
command "" command to execute
args [] command-line arguments
env [] environment variables
dir "" directory to execute in.

The env entries may be templates with access to the same fields as the params url template. Additionally, the following environment variables are always set:

  • EPHEMERALD_ID
  • EPHEMERALD_HOSTNAME
  • EPHEMERALD_PORT
  • EPHEMERALD_USERNAME
  • EPHEMERALD_PASSWORD
  • EPHEMERALD_DATABASE
  • EPHEMERALD_URL

If dir is not set, the working directory of the server isused.

http.get

Run a HTTP GET request.

Extra Parameters:

Name Default Description
url "" url to request

If url is blank, the url from the params is used.

If url is not blank, it may be a template which has access to the same fields that params url template does.

tcp.connect

Connect to the exposed container port over TCP.

postgres.exec

Executes a query on the database.

Extra Parameters:

Name Default Description
command "SELECT 1=1" query to execute
args [] values to be escaped with positional arguments in command.

Example:

type:     postgres.exec
command: 'INSERT INTO users (name) VALUES ($1)'
args:    "Robert'); DROP TABLE STUDENTS;--"

postgres.ping

Pings the database. Useful for healthcheck.

postgres.truncate

Runs TRUNCATE TABLE x CASCADE for all tables x.

Extra Parameters:

Name Default Description
exclude [] an array of table names to not truncate (eg migration versions)

redis.exec

Execute a redis command.

Extra Parameters:

Name Default Description
command "PING" redis command to execute

redis.ping

This is an alias for redis.exec.

redis.truncate

This is an alias for redis.exec with a default command of "FLUSHALL".

API

There is a REST API for clients to checkout and return items from one or more pools.

Checkout

POST /checkout/{pool} checks out an instance from the given pool and returns that instance's parameters:

$ curl -s -XPOST localhost:6000/checkout/postgres | jq
{
  "id":"8482c266192f013346d03f71b2aa6d4b647909e3502ac525039bdd0fe9fcac30",
  "hostname":"localhost",
  "port":"34031",
  "username":"postgres",
  "database":"postgres",
  "url":"postgres://postgres:@localhost:34031/postgres?sslmode=disable"
}

Return

DELETE /return/{pool}/{id} returns the instance given by id to the pool pool:

$ curl -s -XDELETE localhost:6000/return/postgres/8482c266192f013346d03f71b2aa6d4b647909e3502ac525039bdd0fe9fcac30

Batch Checkout

POST /checkout checks out an instance from every configured pool.

$ curl -s -XPOST localhost:6000/checkout | tee checkout.json | jq
{
  "postgres": {
    "id": "2dedf5dbe9cc8d7a0cd71ed75455c7310db79aea44925562b82c01b959d85e7e",
    "hostname": "localhost",
    "port": "34023",
    "username": "postgres",
    "database": "postgres",
    "url": "postgres://postgres:@localhost:34023/postgres?sslmode=disable"
  },
  "redis": {
    "id": "a8dbf5043c7145510f48ccffa6f1e20b9f2c8140dda73d567a29dc2ec823ca46",
    "hostname": "localhost",
    "port": "34019",
    "database": "0",
    "url": "redis://localhost:34019/0"
  },
  "vault": {
    "id": "11f4752d5e0b762c65b05809c9500a6e0a20ee4a79b861638a084adf77dbfb78",
    "hostname": "localhost",
    "port": "34021",
    "url": "http://localhost:34021"
  }
}

Batch Return

DELETE /return returns multiple instances at once. Meant to be used with batch checkout.

$ cat checkout.json
{
  "postgres": {
    "id": "2dedf5dbe9cc8d7a0cd71ed75455c7310db79aea44925562b82c01b959d85e7e"
  },
  "redis": {
    "id": "a8dbf5043c7145510f48ccffa6f1e20b9f2c8140dda73d567a29dc2ec823ca46"
  },
  "vault": {
    "id": "11f4752d5e0b762c65b05809c9500a6e0a20ee4a79b861638a084adf77dbfb78"
  }
}
$ curl -XDELETE -H'Content-Type: application/json' -d @checkout.json localhost:6000/return

Note that the complete response from batch checkout may be sent. The only requirement is the id field for each pool instance.

Building

$ govendor get -d github.com/boz/ephemerald/...
$ cd $GOPATH/src/github.com/boz/ephemerald
$ make server example

Run the example server and client in separate terminals

$ ./ephemerald/ephemerald -c _example/config.yaml
$ ./_example/example

Installing

Source

Follow the building steps then run make install:

$ make install

Binary

Download the latest release for your system. Unpack the archive and put the binary in your path.

$ release="https://github.com/boz/ephemerald/releases/download/v0.3.1/ephemerald_Linux_x86_64.tar.gz"
$ curl -L "$release" | tar -C /tmp -zxv
$ /tmp/ephemerald -c config.yaml

Homebrew

$ brew install boz/repo/ephemerald

TODO

  • Configuration
    • Current parsing is a disaster
    • Allow yaml
    • Allow built-in defaults (postgres, redis, etc...)
  • Polish/Optimize/Cleanup/Refactor UI.
  • Re-add remote actions (websockets API)
  • Clients: nodejs, ruby, python, etc...
  • Documentation
  • Tests