fishfinger

package module
Version: v0.0.0-...-3204a6c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 24, 2017 License: MIT Imports: 10 Imported by: 4

README

FishFinger

GoDoc Go Report Card

What is FishFinger ?

FishFinger is a Dockerfile and Docker-Compose lightweight programmatic library written in Go. This project provides easy to use abstractions of official Docker libraries with no other dependency than official Go library and official Docker libraries.

FishFinger also provides a one possible solution to deal with container initialization delay and makes room for further improvement through user-defined solutions.

This project is fully tested and comes with an extensive documentation.

Quickstart

First of all, you need to install and configure the latest Docker version on your machine.

Then go get this repository:

go get -d github.com/timtosi/fishfinger

Finally, check how to use the components provided:

Use a Compose File

The fishfinger.Compose component represents an abstraction of the libcompose library. It provides functions allowing the user to use a Compose file programmatically.

An extensive documentation of this component is available here.

Basic Compose Usage

Let's say you have a basic Compose file where you defined three redis services.

For instance, you could want to be able to write a test suite that will directly make use of your containers instead of simply mocking their behaviour through burdensome to write functions.

First of all, let's create a new Compose file handler by using the fishfinger.NewCompose function that takes the path to your Compose file as an argument.

func main() {
	c, err := fishfinger.NewCompose("./docker-compose.yaml")
	if err != nil {
		log.Fatal(err)
	}
}

Time to start services.

func main() {
	c, err := fishfinger.NewCompose("./docker-compose.yaml")
	if err != nil {
		log.Fatal(err)
	}
    if err := c.Start(); err != nil {
		log.Fatal(err)
	}
}

As no argument is provided, all services will be started, but Compose.Start function accepts a variable number of service name in argument and will start them in the order specified.

At this stage, you should have a neat redis cluster running. The Redis client ports you previously set for each of your services are the 6379 but you had to specify different available ports on your host machine. You can hard code the different ports OR you can use the Compose.Port function to find the correct port to use from a service name and the combination of the port exposed and the protocol used such as 6379/tcp. This function returns the full address in the following form <host>:<port>.

func main() {
	c, err := fishfinger.NewCompose("./docker-compose.yaml")
	if err != nil {
		log.Fatal(err)
	}
    if err := c.Start(); err != nil {
		log.Fatal(err)
	}
    addr, err := c.Port("redis-01", "6379/tcp")
	if err != nil {
		log.Fatal(err)
	}
}

The Compose.Port function will return the full address in the following form <host>:<port>. Now you can instanciate your redis client and do whatever stuff you want.

In the end, you maybe want to clean the containers created. Just use the Compose.Stop function just as you would use the Compose.Start function.

func main() {
	c, err := fishfinger.NewCompose("./docker-compose.yaml")
	if err != nil {
		log.Fatal(err)
	}
    if err := c.Start(); err != nil {
		log.Fatal(err)
	}
    addr, err := c.Port("redis-01", "6379/tcp")
	if err != nil {
		log.Fatal(err)
	}
    if err := c.Stop(); err != nil {
		log.Fatal(err)
	}
}

As no argument is provided, all services will be stopped.

Complete working code can be found here.

Backoff Compose Usage

When you want to use Docker in a programmatic way, you can encounter a particular issues regarding the definition of a container's ready state: the container state and the state of the software running inside the container are decoupled.

That means a container will be considered ready regardless of the software state, leading to programmatic errors. Moreover, that is not because the main dockerized software is running that you consider your container ready.

Let's say you have a basic Compose file where you defined a service running a SQL database. In this case, you are using it for testing purposes because you do not want to use a mocking driver. Basically, you could build an image with all your data preloaded but currently you do not want to rebuild your image each time you update your dataset because it's evolving frequently.

As you can see at line #40, in this example, you find a solution by using a container that mounts a volume where you put all .sql scripts you use for populating the database at initialization.

Here is a visualisation of the three ready states this Docker container has :

+--------------------+     +-------------+     +-------------------+
| CONTAINER IS READY | --> | MYSQL IS UP | --> | DATA ARE INSERTED |
+--------------------+     +-------------+     +-------------------+

You can't rely on Docker to know when your container is ready and you can't rely on a time constant because the data insertion step is variable depending on the data you will have to set.

There are several ways to tackle this problem and FishFinger allow you to resolve this easily by using the Compose.StartBackoff function.

First of all, let's create a new Compose file handler by using the fishfinger.NewCompose function that takes the path to your Compose file as an argument.

func main() {
	c, err := fishfinger.NewCompose("./docker-compose.yaml")
	if err != nil {
		log.Fatal(err)
	}
}

Now, time to start the MySQL service with the Compose.StartBackoff function.

func main() {
	c, err := fishfinger.NewCompose("./docker-compose.yaml")
	if err != nil {
		log.Fatal(err)
	}

	if err := c.StartBackoff(fishfinger.SocketBackoff, "datastore:9090/tcp"); err != nil {
		log.Fatal(err)
	}
}

This function takes two arguments: the Backoff function used and a variable number of strings composed of the service name, a port and a protocol used such as redis:6379/tcp.The Backoff function used here is the one provided by default by the Fishfinger project but you are expected to provide another one that suits your needs.

func SocketBackoff(c *Compose, service, port string) error {
	var (
		msg  string
		conn net.Conn
	)

	addr, err := c.Port(service, port)
	if err != nil {
		return err
	}
	for ; msg != "ready\n"; time.Sleep(5 * time.Second) {
		if conn, err = net.Dial("tcp", addr); err == nil {
			fmt.Fprintf(conn, "ready\n")
			msg, _ = bufio.NewReader(conn).ReadString('\n')
			conn.Close()
		}
		fmt.Printf("Retry connection in 5s.\n")
	}
	return nil
}

It's only keep trying to connect to a specific port exposed by the container. The fact is the function will not find any remote listener until all data is correctly loaded, as you can see here. In this way, you are assured everything is ready to be processed by the rest of your program.

Complete working code can be found here.

License

Every file provided here is available under the MIT License.

The logo is a derivative of "BLUE_GOPHER.png" by Ashley McNamara, used under CC BY. This logo is licensed under CC BY by Tim Tosi.

Not Good Enough ?

If you encouter any issue by using what is provided here, please let me know ! Help me to improve by sending your thoughts to timothee.tosi@gmail.com !

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SocketBackoff

func SocketBackoff(c *Compose, service, port string) error

SocketBackoff is an implementation of a `backoffFunc`. It retries to connect to the tcp port 9090 of `service` until the remote socket sends a message back.

NOTE: This function does not return until the socket is reached or an `error` occurs. FishFinger project offers a default implementation of a `backoffFunc` that will work for the most simple of use cases, although I strongly recommend you provide your own, safer implementation while doing real work.

Types

type Compose

type Compose struct {
	*project.Project
}

Compose is a structure using `libcompose` to manage a Docker environment from a compose-file.

func NewCompose

func NewCompose(composeFile string) (*Compose, error)

NewCompose returns a new `Compose` or an `error` if something occurs during setup. It uses `composeFile` as the path to the compose-file to up.

NOTE: .env files are not currently supported.

func (*Compose) Env

func (c *Compose) Env(service, varName string) (string, error)

Env returns the value of the environment variable `varName` set in container `service` by the compose-file. If `service` or `varName` does not exist, It returns an `error`.

func (*Compose) Info

func (c *Compose) Info(service string) (project.InfoSet, error)

Info returns a `project.InfoSet` about a specified `service`.

func (*Compose) Port

func (c *Compose) Port(service, port string) (string, error)

Port returns the host address where `service` is bound to `port`. If `service` does not exist or `port` is not bound, it returns an `error`.

NOTE: `port` MUST be of the following form `<portNb>/<protocol>` such as `80/tcp`.

func (*Compose) Start

func (c *Compose) Start(services ...string) error

Start launches all `services` specified. If `services` is empty, all services described in the compose-file are launched. If something occurs during any `services` start, it returns an `error`.

func (*Compose) StartBackoff

func (c *Compose) StartBackoff(backoffFunc func(*Compose, string, string) error, services ...string) error

StartBackoff launches all `services` specified and returns only when `backoffFunc` completes. If `services` is empty, all services described in the compose-file are launched and `backoffFunc` is used for each of them.

NOTE: This project offers default implementations of `backoffFunc` but user should provide an implementation according to the Docker images used.

`backoffFunc` takes as input this `*Compose`, service name and port/protocol:

`backoffFunc(c, "redis", "6379/tcp")`

`services` has format "service:port/protocol", e.g., "redis:6379/tcp".

func (*Compose) Status

func (c *Compose) Status(service string) (bool, error)

Status returns `true` if `service` is in running state, `false` otherwise. If `service` does not exist, it returns an `error`.

func (*Compose) Stop

func (c *Compose) Stop(services ...string) error

Stop removes all `services` specified. If `services` is empty, all services described in the compose-file are removed. If something occurs during any `services` stop, it returns an `error`.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL