Open Source Consultant, Software Developer and System Administrator

If you are interrested in hiring a consultant for the effective use of open source software on an enterprise grade, take a look around in the About section to see, what I have to offer.

Blog and snippets

Various snippets or code parts I found useful, so I keep them here for reference.

Proxy ports into a running Docker(-Compose) container

Publishing ports in Docker is done only once at the beginning on the creation of the container. Yet sometimes there is the need to talk to the service inside a container, but the container itself does not have the proper tools to do what you want with it built-in. Yet re-creating the container just to publish the ports, maybe manifesting it temporarily in some docker-compose.yml, and then forgetting about it to open up some security problem is not great.

So the following script allows to temporally proxy to a port of a running container, whether for manually ran containers or the ones in a network from Docker-Compose. It tries to find the network and attaches to it, unless it's bridge, and uses a socat to run the proxy. The arguments are the container name or id, the port to proxy to, and an optional port where to publish (if not given, Docker will pick one).

#!/bin/sh

set -eu

if test ! "$#" -ge 2; then
    echo "$0 <container> <target_port> (<local_port>)"
    exit 1
fi

container="$1"
port="$2"

network=$(docker inspect "$container" | jq -r '.[0].NetworkSettings.Networks | keys[0]')

inner_port=8080
publish="$inner_port"
if test "$#" -ge 3; then
    publish="$3:$publish"
fi

docker run \
    --rm \
    --publish="$publish" \
    --link="$container:target" \
    $(if test "$network" != "bridge"; then echo "--network=$network"; fi) \
    alpine/socat \
        "tcp-listen:$inner_port,fork,reuseaddr" \
        "tcp-connect:target:$port"

Remove additional Groovy record features

Since Groovy 4 record types are supported like in Java. Out of the box they already come with additional features, that may or may not be useful for your current project. So the easy way to disable all of those features is to just define the record in a .java file or under a source tree, that only the Java compiler sees.

Yet nearly all differences to record .class generated with the Java compiler can be configured. To generate the same result with the Groovy compiler (or nearly the same: there are some annotations), use the following annotations for the record (or make them more easily available via a groovy.transform.AnnotationCollector).

@TupleConstructor(           // disable map and default-value c'tor
        defaults = false,    // do not generate c'tor versions, that default to null values
        namedVariant = false // do not generate a c'tor with faux named arguments (AKA map c'tor)
)
@RecordOptions(              // do not generate additional methods for access to the fields
        toList = false,      // list of fields values
        toMap = false,       // map from field name to value
        size = false,        // amount of defined fields
        getAt = false        // access attributes in definition order by index
)

Automatically generate constructors for services with Groovy

When using dependency injection frameworks like Spring or Micronaut, you will often have services, that depend on services, …. While back in the days, annotating fields with @Inject or @Autowired was acceptable, it felt out of favour, because you would never know what a service really depends on. So nowadays injection is usually done via the constructor.

But this also means, that you will find yourself often fiddling around with constructors and orders of arguments. You also will find yourself falling back to the help of your IDE. But if you have the slightest sense of order, you will also find, that the generated code does not keep your order of things: e.g. IntelliJ always adds new fields to the end of the c'tor - and also the assignment to the field inside the c'tor. So you either delete the whole c'tor and recreate again or you will end up using more IDE features to re-arrange your code.

Given that usually those c'tors are only used by DI automatically and maybe in your tests, can't we do better and just let the compiler generate the c'tor for us -- instead of moving words around with your editors?

Groovy comes with TupleConstructor, which can create a c'tor with explicit arguments (in contrast to MapConstructor, which is used to construct via passed in maps to make it look like Groovy would support named arguments). The defaults of TupleConstructor are very un-strict, though. By default, it creates a c'tor for each additional property where the default is null. Usually not what we want with DI, because it does not know which c'tor to use now or might use the wrong one and we end up in the place, that we wanted to avoid when going with c'tor based injection.

But luckily TupleConstructor can be configured to high degree. These settings will give you a pretty good default:

@groovy.transform.TupleConstructor(
        includeFields = true,  // add fields to the c'tor
        defaults = false // do not generate versions of the c'tor with null assignments
)

So now instead of writing this every time, you can make this a new annotation to easily use (and still be able to override the nuances, if you have to), with AnnotationCollector. E.g.

@groovy.transform.TupleConstructor(
        includeFields = true,  // add fields to the c'tor
        defaults = false // do not generate versions of the c'tor with null assignments
)
@groovy.transform.AnnotationCollector
@interface ServiceConstructor {}

Now you can use this annotation instead:

@Service
@ServiceConstructor(
        excludes = ['message'] // if you have transients, add them here
)
class MyService {

    protected final HelloService helloService

    transient protected String message

    @PostConstruct
    void init() {
        message = helloService.sayHello("World")
    }
}

This will generate the c'tor you expect: MyService(HelloService helloService). And if you need your FormatService too, you just add it as a field -- at the place you like. And the c'tor will honour your order in the code.

One downside though is the loss of making your IDE show the usages of the c'tor -- but then, this is often only useful for tests. In that case you can still use your IDE to generate the c'tor, maybe adjust, find the usages and fix them and then delete the c'tor again. There sadly seems not to be a flag to make TupleConstructor throw, if there are existing c'tors. It will by default just silently not generate a c'tor, which is not the worst.