Format the output of "docker ps" or "docker container ls" using the slice function in go template

I have a strange result with docker container ls --format when I want to shorten the image id or name. I will continue to investigate the issue but I think it is interesting enough to share.

Start a PHP container

docker run -d --name php php:8.0-apache

Note: It doesn’t matter which container I start. I chose this because the name of the image is long enough. You will see.

Show the first 11 characters of the images of the running containers:

docker container ls --all --no-trunc --filter "ancestor=php:8.0-apache" --format '{{ slice .Image 0 11 }}'

Note: I used --filter to make sure the error message is not caused by another image

The result:

failed to execute template: template: :1:3: executing "" at <slice .Image 0 11>: error calling slice: index out of range: 1

However, when I want to get the first 10 characters it works:

docker container ls --all --no-trunc --filter "ancestor=php:8.0-apache" --format '{{ slice .Image 0 10 }}'

The result:

php:8.0-ap

So i thought I used slice incorrectly but slice works with a static string:

docker container ls --all --no-trunc --filter "ancestor=php:8.0-apache" --format '{{ slice "php:8.0-apache" 0 11 }}'

Then I thought .Image was actually an object so that was why I coudn’t manipulate it as a string. So I found this:

It is about the last characters not the first but it was the closest I found.

“pyhedgehog” wrote this:

.Image field is of some strange type (not string), so you should evaluate it to string (using print )

But using print doesn’t change anything:

docker container ls --all --no-trunc --filter "ancestor=php:8.0-apache" --format '{{ $i:=(print .Image) }}{{ slice $i 0 11 }}'

Result:

failed to execute template: template: :1:27: executing "" at <slice $i 0 11>: error calling slice: index out of range: 11

But… in the template string on stackoverflow there is an “if” statement. So I tried to play with that:

docker container ls --all --no-trunc --filter "ancestor=php:8.0-apache" --format '{{ if ge (.Image|len) 11 }}{{ slice .Image 0 11 }}{{end}}'

Note: I checked if the length of the image name was greater then 11 which always true for this image. It worked:

php:8.0-apa

If I try it with 10 iin the condition nstead of 11, it failes again:

docker container ls --all --no-trunc --filter "ancestor=php:8.0-apache" --format '{{ if ge (.Image|len) 10 }}{{ slice .Image 0 11 }}{{end}}'

Result:

failed to execute template: template: :1:30: executing "" at <slice .Image 0 11>: error calling slice: index out of range: 11

It seems the implicit conversion happens when I check the length of the name. The number in the condition must be greater then 10 to be able to slice more than 10 characters.

I also tried this:

docker container ls --all --no-trunc --filter "ancestor=php:8.0-apache" --format '{{ slice (printf "%s" .Image ) 0 11 }}'

Failed again.
I couldn’t explain it so I tried to use slice with docker image ls --format. Surprisingly it worked without errors.

docker image ls --filter "reference=php:8.0-apache" --format '{{slice (printf "%s:%s" .Repository .Tag) 0 11}}'

I could use the printf function to get the first characters of the name, but I also want to set an offset since I am working on a demonstration in which I have Image IDs starting with sha256: which I also want to cut out and I couldn’t do it with printf. This last sentence is just an explanation why I am playing with slice, not a question.

Can anyone explain it? I could use the ugly workaround with the condition but that looks bad in a demonstration.

2 Likes

Very interesting research.

This one smells strongly like a bug in container ls.

Is it possible that the default slice “barrier” has a capacity of 10 and that wrapping it in a length comparision accidently triggered a higher capacity? Setting the upper bondary to (len .Image) has the same effect:

docker container ls --no-trunc --filter "ancestor=php:8.0-apache" --format '{{ slice .Image 0 (len .Image) }}'

I don’t think this is related to type of .Image itself, because the (printf "%s" .Image) definitly is a string, and the slice still hits the capacity barrier on it.

If you want, I have an even ugglier workaround :smiley:

docker container ls --no-trunc --filter "ancestor=php:8.0-apache" --format '{{json .}}' | jq -r '.Image | .[0:10]'

I like jq and I thought of it as a solution but I try to avoid additional tools since I don’t want to force people who read my tutorial to install more packages if there is another way.

I also want to format the output as a “table” which is available in the format string. I could use column to achieve something similar but that would complicate the final command more. When I create a tutorial I try to keep it as simple as it is possible.

Everything is possible at this point :slight_smile: But why would it work with static string? I haven’t read the source code yet and I am not mentally ready to do it today.

Thanks. I didn’t know that. Unfortunately it won’t help but maybe I can figure something out. If not, that’s OK. I can mention this issue and use a more complex solution.

Thanks for the suggestion.

Good question…

I don’t understand why those two are not producing the same result:

docker container ls --filter "ancestor=php:8.0-apache" --format '{{ $i:="php:8.0-apache"}}{{slice $i 0 12}}'
docker container ls --filter "ancestor=php:8.0-apache" --format '{{ $i:=(printf "%s" .Image)}}{{slice $i 0 12}}'

While the first variation works, the second does not. Even though I would expect printf "%s" .Image to create a new string, identical to the static string, but it is treated differently.

I hope you find the root cause, or at least a statisfying understanding what causes it :slight_smile:

This works too

docker container ls --filter "ancestor=php:8.0-apache" --format '{{ $i:=(printf "%s" "php:8.0-apache")}}{{slice $i 0 12}}'

So “printf” in parentheses doesn’t create an object. I wasn’t sure until now.

I have a theory after working through this exact problem. I want to use this to set an environment variable in a YAML file:
SOME_ENV_VAR: "{{ slice .Service.Name 12 }}"
It failed, but your “ugly” workaround is fine:
SOME_ENV_VAR: {{ if ge (.Service.Name|len) 13 }}{{ slice .Service.Name 13 }}{{end}}

The theory:
Things are getting porcessed twice. On the first pass, “.Service.Name” is treated as a string literal. 13 is greater than the length of “.Service.Name” and we get the error.
The “if” statement stops that processing from happening until later on when “.Service.Name” is populated with an actual value.
As this is hitting me doing docker stack deploy..., I don’t think is it anything to do with docker container ls and it is something much more fundamental.

Does this seem reasonable?

1 Like

.Service.Name is exactly 13 characters long if you count the leading dot and I used .Image 6 characters long, much shorter than 11. :slight_smile:

But thank you for your idea, these are useful and help us to see the issue from an other point of view. And the fact that your issue was a little bit different with the same result is important. You also reminded me of this issue which I completely forgot. I have to check if this issue is reported on GitHub and then report it if I can’t find anything.

Probably here

but you experienced the issue in a YAML, so I guess you are right and the real problem is in a common library that renders go templates.

If you don’t want to wait, feel free to report it without waiting for me. If yo do that, please, share the issue link here so I won’t report it again.

update

I have read my previous posts again and it looks like I already had an idea, but didn’t feel like I wanted to report it and I stopped looking for an explanation.

1 Like

You are quite right (I blame there being too much blood in my coffee stream) but I can’t think of any other explanation that some kind of double-execution with the first having some kind of placeholder value. So I tried this:
SOME_ENV_VAR: {{ if le (.Service.Name|len) 11 }}{{ print .Service.Name }}{{end}}
And got a big, fat nothing. I have no clue what is happening but I logged issue 3882.

1 Like

Thank you for creating the issue.Yesterday I started to browse the source code of the CLI, but I didn’t have enough time to actually find something. Let’s hope the developers know what is going on here.