Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/jnthn/raku-dev-containerized-service

Uses containers to provide services (such as databases) to ease getting a local development environment set up.
https://github.com/jnthn/raku-dev-containerized-service

Last synced: 5 days ago
JSON representation

Uses containers to provide services (such as databases) to ease getting a local development environment set up.

Awesome Lists containing this project

README

        

# Dev::ContainerizedService

This module aims to ease the process of setting up services (such as Postgres)
for the purpose of having a local development environment for Raku projects.
For example, one might have a Raku web application that uses a database. In
order to try out the application locally, a database instance needs to be set
up. Ideally this should be effortless and also isolated.

As the name suggests, this module achieves its aims using containers. It
depends on nothing more than Raku and having a functioning `docker`
installation.

## Usage

### Getting Started

Let's assume we have a web application that uses a Postgres database and expects
that the `DB_CONN_INFO` environment variable will be populated with a connection
string.

To make a development environment configuration using this module, we create a
script `devenv.raku`:

```raku
#!/usr/bin/env raku
use Dev::ContainerizedService;

service 'postgres', :tag<13.0>, -> (:$conninfo, *%) {
env 'DB_CONN_INFO', $conninfo;
}
```

The `service` function specifies the service ID, a Docker image tag, and a block that
should be called when the service is up and running. The `env` function, located in a
service, specifies an environment variable to be set.

We can then (assuming `chmod +x devenv.raku`) use the script as follows:

```
./devenv.raku run raku -Ilib service.raku
```

This will:

1. Pull the Postgres docker container if required
2. Run the container, setting up a database user/password and binding it to a free
port
3. Run `raku -Ilib service.raku` with the `DB_CONN_INFO` environment variable set

If using the `cro` development tool, one could do:

```
./devenv.raku run cro run
```

### Additional Actions

The service block is run after the container is started (service implementations
include readiness checks). As well as - or instead of - specifying environment
variables to pass to the process, one can write any Raku code there. For
example, one could run database migrations (in the case where it's desired to
have them explicitly applied to production, rather than having them applied at
application startup time).

### Retaining data

By default, any created databases are not persisted once the `run` command is
completed. To change this, alter the configuration file to specify a project name
(the name of your application) and call `store`:

```raku
#!/usr/bin/env raku
use Dev::ContainerizedService;

project 'my-app';
store;

service 'postgres', :tag<13.0>, -> (:$conninfo, *%) {
env 'DB_CONN_INFO', $conninfo;
}
```

Now when using `./devenv.raku run ...`, for services that support it, Docker
volume(s) will be created and the generated password(s) for services will be
saved (in your home directory). These will be reused on subsequent runs.

To clean up this storage, use:

```
./devenv.raku delete
```

Which will remove any created volumes along with saved settings.

### Showing produced configuration

When using storage, it is also possible to see the most recently passed service
settings for each service by using:

```
./devenv.raku show
```

The output looks like this:

```
postgres
conninfo: host=localhost port=29249 user=test password=xxlkC2MrOv4yJ3vP1V-pVI7 dbname=test
dbname: test
host: localhost
password: xxlkC2MrOv4yJ3vP1V-pVI7
port: 29249
user: test
```

When used while `run` is active, this is handy for obtaining connection string
information in order to connect to the database using tools of your choice.

### Tools

Some service specifications also come with a way to run related tools. For
example, the `postgres` specification can run the `psql` command line client
(using the version in the container, to be sure of server compatibility),
injecting the correct credentials. Thus:

```
./devenv.raku tool postgres client
```

Is sufficient to launch the client to look at the database. Note that this
only works when the service is running (so one would run it in one terminal
window, and then use the tool subcommand in another).

### Multiple stores

Calling:

```raku
store;
```

Is equivalent to calling:

```raku
store 'default';
```

That is, it specifies the name of a default store. It is possible to have multiple
independent stores, which are crated using the `--store` argument before the `run`
subcommand:

```
./devenv.raku --store=bug42 run cro run
```

To see the created stores, use:

```
./devenv.raku stores
```

To show the produced service configuration for a particular store, use:

```
./devenv.raku --store=bug42 show
```

To use a tool against a particular store, use:

```
./devenv.raku --store=bug42 tool postgres client
```

To delete a particular store, rather than the default one, use:

```
./devenv.raku --store=bug42 delete
```

### Multiple instances of a given service

One can have multiple instances of a given service. When doing this, it is wise
to assign them names (otherwise names like `postgres-2` will be generated, and
this will not be too informative in `show` output):

```raku
service 'postgres', :tag<13.0>, :name -> (:$conninfo, *%) {
env 'PRODUCT_DB_CONN_INFO', $conninfo;
}

service 'postgres', :tag<13.0>, :name -> (:$conninfo, *%) {
env 'BILLING_DB_CONN_INFO', $conninfo;
}
```

These names are used in the `tool` subcommand:

```
./devenv.raku -tool pg-billing client
```

### Is this magic?

Not really; the `Dev::ContainerizedService` module exports a `MAIN` sub, which is
how it gets to provide the program entrypoint.

## Available Services

### Postgres

Either obtain a connection string:

```raku
service 'postgres', :tag<13.0>, -> (:$conninfo, *%) {
env 'DB_CONN_INFO', $conninfo;
}
```

Or the individual parts of the database connection details:

```raku
service 'postgres', :tag<13.0>, -> (:$host, :$port, :$user, :$password, :$dbname, *%) {
env 'DB_HOST', $host;
env 'DB_PORT', $port;
env 'DB_USER', $user;
env 'DB_PASS', $password;
env 'DB_NAME', $dbname;
}
```

Postgres supports storage of the database between runs when `store` is used.

The `client` tool is available, and runs the `psql` client:

```
./devenv.raku tool postgres client
```

### Redis

Obtain the host and port of the started instance:

```raku
service 'redis', :tag<7.0>, -> (:$host, :$port) {
env 'REDIS_HOST', $host;
env 'REDIS_PORT', $port;
}
```

Redis is currently always in-memory and will never be stored.

## The service I want isn't here!

1. Fork this repository.
2. Add a module `Dev::ContainerizedService::Spec::Foo`, and in it write a
class of the same name that does `Dev::ContainerizedService::Spec`. See
the role's documentation as well as other specs as an example.
3. Add a mapping to the `constant %specs` in `Dev::ContainerizedService`.
4. Write a test to make sure it works.
5. Add an example to the `README.md`.
6. Submit a pull request.