Flavio Poletti bio photo

Flavio Poletti

Irreducible Perler.

Email Comics Twitter LinkedIn MetaCPAN Github Stackoverflow Pinterest

This is the first post about dibs (first here and second here) and it should be clear by now that the main goal of Dibs (at least “over” using a plain Dockerfile) is reuse. This time we take an introductory look at packs, which per-se allow easily reusing stuff in a slightly less copy & paste way; we will see in a future installment how this can be further leveraged for a more modern way of sharing and reusing things.

Curious about the whole Dibs Saga? See a list of all posts on dibs.

Table of Contents

First Look At Packs

We already looked at packs, e.g. in our first article we can find this:

actions:
# ...
   build:
      # ...
      - name: build
        pack:
           run: |
              #!/bin/sh
              set -e
              exec >&2
              apk --no-cache add build-base wget perl perl-dev
              adduser -D -h /app ada
              wget -O /bin/cpanm --no-check-certificate https://cpanmin.us/
              chmod +x /bin/cpanm
              cat >/tmp/as-ada.sh <<'END'
              cd /app
              cp -R /tmp/src/* .
              cpanm -l local --notest --installdeps .
              END
              chmod +x /tmp/as-ada.sh
              su - ada /tmp/as-ada.sh
              cp -a /app /tmp/cache

It turns out that a sketch action (any sketch action!) needs a pack to know what should be factually done inside the container. Hence, you will always see something like this:

actions:
# ...
   some-sketch-action:
      pack: # ... whatever defines the pack
      # ...

The example above with the run section was just possibly the simpler kind of pack, that is one whose implementation is fit directly inside Dibs’s configuration file. There can be more, of course.

A Library Of Packs

The next “simple” place where you can put a pack is an executable program (be it a shell script of anything that you can run inside the target container). Where should you place this file eventually? There are a few options, but for the moment we will play it easy and opt for the pack sub-directory of the project directory.

Evolving our previous example, we can fit the shell script in a file inside pack, e.g. pack/kickoff.sh:

#!/bin/sh
set -e
exec >&2
apk --no-cache add build-base wget perl perl-dev
adduser -D -h /app ada
wget -O /bin/cpanm --no-check-certificate https://cpanmin.us/
chmod +x /bin/cpanm
cat >/tmp/as-ada.sh <<'END'
cd /app
cp -R /tmp/src/* .
cpanm -l local --notest --installdeps .
END
chmod +x /tmp/as-ada.sh
su - ada /tmp/as-ada.sh
cp -a /app /tmp/cache

Then, we change the configuration file to use it:

actions:
# ...
   build:
      # ...
      - name: build
        pack:
            project: kickoff.sh

The same script might be reused multiple times across the same configuration file. We already learned to use YAML variables to avoid repetition, but there’s a semantic way to do this as well, i.e. put definition of packs inside a packs section at the top level, like this:

packs:
   kickoff: # we choose the "explicit" way, it's the same as above
      type: project
      path: kickoff.sh
actions:
# ...
   build:
      # ...
      - name: build
        pack: kickoff    # same name inside "packs" above

In this way, you can save your scripts as standalone files and manage (e.g. edit, track, etc.) individually with little effort to eventually call them during container build-up.

args: Making Packs More Reusable

A program that only does one thing in the same way might become dull very quickly and leave you the temptation to copy and paste to get something slightly different. Dibs actively wants you to stop this waste!

When you call a program in a pack, you can also pass arguments that will appear as command-line arguments to the program itself. As an example, save this as pack/alpine-packs.sh:

#!/bin/sh
set -e
exec >&2
apk --no-cache add "$@"

Now you can have this kind of definition:

packs:
   alpine_packs:
      type: project
      path: alpine-packs.sh
actions:
# ...
   build:
      # ...
      - name: Install OS packs
        pack: alpine_packs
        args:
           - build-base
           - wget
           - perl
           - perl-dev
   # ...
   bundle:
      # ...
      - name: Install OS packs
        pack: alpine_packs
        args:
           - perl

… And, Of Course, Environment Variables

Command-line arguments via options args are not the only way to tell your programs about behaving differently. Another way would be defining environment variables instead, via the env key. The following example transforms the one in the previous section to use an environment variable with the list of space-separated packages to install.

Save this as pack/alpine-packs-env.sh:

#!/bin/sh
set -e
exec >&2
# CAUTION: no quotes around expansion of ALPINE_PACKAGES
apk --no-cache add $ALPINE_PACKAGES

Now you can have this kind of definition:

packs:
   alpine_packs_via_environment:
      type: project
      path: alpine-packs-env.sh
actions:
# ...
   build:
      # ...
      - name: Install OS packs
        pack: alpine_packs_via_environment
        env:
           - ALPINE_PACKAGES: 'build-base wget perl perl-dev'
   # ...
   bundle:
      # ...
      - name: Install OS packs
        pack: alpine_packs_via_environment
        env:
           - ALPINE_PACKAGES: 'perl'

Enough For Now

Today we learned:

  • you can place your programs (e.g. scripts) in standalone files inside the project tree and avoid bloating your configuration file
  • it is very, very easy to use these outside-placed programs from sketches
  • you have at least a couple ways to make these programs generic, i.e. either make them accept command-line arguments, or work based on environment variables.

There’s more than this… but it’s enough for now. As usual, let me know what you think in the comments.