Idempotent management operations?

I am using incus 6.0.2 LTS extensively for scripted container builds, and there are a number of cases where it would be helpful to have some basic idempotent operations.

The simplest case is when I want to ensure that a container is running, regardless of whether it was previous running or not. incus start foo gives an error if foo is already running. I don’t want to do:

incus start "$container" || true

because that will ignore all possible startup errors. So I end up doing something fragile like:

[ "$(incus list -c s -f csv name=$container)" = "RUNNING" ] || incus start "$container"

(which also has a race condition). Similarly for stopping a container which may or may not already be stopped.

Another common case is to ensure that a container is deleted, before I rebuild it. Again, deleting a non-existing container fails, so:

# not ideal
incus delete -f "$container" || true

# a bit better
incus info "$container" && incus delete -f "$container"

It would make things a lot tidier if I could do:

incus start --idempotent "$container"
incus stop --idempotent "$container"
incus delete -f --idempotent "$container"

(I’m not worried about the actual flag names; things like --start-if-stopped would be OK too)

Those are the most basic operations. Potentially there could also be:

incus config device add --idempotent "$container" http proxy "listen=tcp:[::]:20080" "connect=tcp:[::]:80"

If the device already exists with those settings, do nothing instead of giving an error. Potentially it could go further:

  1. If the device already exists with the same type (proxy) then replace the settings, like incus config device set (and unset anything not listed)
  2. If the device already exists with a different type, then delete and add

Those latter cases are less important to me; incus config device remove --idempotent could be useful though.

As an alternative, I looked at incus config edit to set all devices in one go, but that requires an external merge of all other non-device config keys:

incus config edit "$container" <<EOS
devices:
  eth0:
    ... stuff here
EOS
Error: Volatile idmap keys can't be deleted by the user

Maybe using yq could achieve that. (AFAICS there’s no incus config show --format json to use with jq)

Anyway… just a suggestion. Thanks for all the great work on incus!

Yeah, we had an issue open for that for years back in the LXD repo and never really made any progress with it.

I think our thought back then was to try to catch those errors and return a different error code, so not necessarily making things idempotent but making it possible to detect what happened at least without having to parse the error.

The problem with that (and with any other implementation of something similar) was that we don’t have particularly unique http status codes when this happens, so it makes it slightly tricky for the CLI to know why the request failed and if it’s safe to ignore (or return a different return code).

Sure: one approach is for the API to return a special result code which means “the container was already running”, and then have a client flag which says to ignore this condition (*).

The other is to pass to the API a flag which says “it’s fine if this container is already running, do nothing” (i.e. the idempotency approach)

(*) I strongly prefer that the CLI tool does not return a non-zero exit code for this condition. I’m running with -e so that unchecked codes cause the script to fail. The shell code to catch it and test for a specific non-zero exit code is ugly. I have to do this for cloud-init status, which returns 0 or 2 as success or partial success:

  rc=0
  incus exec "$vm" -- cloud-init status --wait --format json || rc="$?"
  if [ "$rc" -ne 0 -a "$rc" -ne 2 ]; then
    echo "*** cloud-init failed ($rc) ***"
    return 1
  fi

instead of simply:

  incus exec "$vm" -- cloud-init status --wait --format json