Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entrypoint doesn't seem to be executed #12

Open
wibimaster opened this issue Apr 29, 2017 · 7 comments
Open

Entrypoint doesn't seem to be executed #12

wibimaster opened this issue Apr 29, 2017 · 7 comments

Comments

@wibimaster
Copy link

wibimaster commented Apr 29, 2017

Dockerspec Version

0.4.1

Ruby Version

ruby 2.3.4p301 (2017-03-30 revision 58214) [x86_64-linux]

Platform Details

Alpine Linux v3.4

Scenario

Try to build and test a Dockerfile with an entrypoint that create a user with Gosu.

Steps to Reproduce

Dockerfile:

FROM ubuntu:16.04

RUN apt-get update && apt-get install curl -y

RUN gpg --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4
RUN curl -o /usr/local/bin/gosu -SL "https://github.com/tianon/gosu/releases/download/1.4/gosu-$(dpkg --print-architecture)" \
    && curl -o /usr/local/bin/gosu.asc -SL "https://github.com/tianon/gosu/releases/download/1.4/gosu-$(dpkg --print-architecture).asc" \
    && gpg --verify /usr/local/bin/gosu.asc \
    && rm /usr/local/bin/gosu.asc \
    && chmod +x /usr/local/bin/gosu

COPY entrypoint.sh /usr/local/bin/entrypoint.sh

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

CMD ["bash"]

entrypoint.sh:

#!/bin/bash

# Add local user
# Either use the LOCAL_USER_ID if passed in at runtime or fallback

IFS=':' read -r -a array <<< "$LOCAL_USER_ID"
uid=$([[ ! -z ${array[0]} ]] && echo ${array[0]} || echo 9001);
gid=$([[ ! -z ${array[1]} ]] && echo ${array[1]} || echo 9001);

echo "Starting with user : UID ${uid} - GID ${gid}"

if [ ! $(getent group $gid) ]; then
    echo "GID ${gid} does not exists"
    groupadd -g $gid -o group
    echo "GID ${gid} created"
fi

useradd --shell /bin/bash -u $uid -o -c "" -m user
echo "UID ${uid} created"

export HOME=/home/user

exec /usr/local/bin/gosu $uid:$gid "$@"

spec/image_spec.rb:

require 'dockerspec/serverspec'

set :os, family: :ubuntu
set :backend, :docker

$project_root = File.expand_path(File.join(__FILE__, '..', '..'))

describe 'My Dockerfile' do
  describe docker_build($project_root) do
    it { should have_user 'nobody' }
    it { should have_entrypoint '/usr/local/bin/entrypoint.sh' }

    describe docker_run(described_image, env: {"LOCAL_USER_ID" => "6000:6000"}) do
      describe command('id -u') do
      	its(:stdout) { should match /6000/ }
      end
    end
  end
end

Expected Result

id -u command should returns 6000 (or, if env fail, 9001)

Actual Result

id -u command returns 0 (root)

@wibimaster
Copy link
Author

Small addition:
If I run the docker builded and type id -u in the shell, it works ; If I run the docker builded with the command id -u, it works too. It fail only in the test with dockerspec :/

@wibimaster
Copy link
Author

wibimaster commented Apr 30, 2017

Hi,
new test did today :

      describe command('/usr/local/bin/gosu 6000:6000 id -u') do
      	its(:stdout) { should match /6000/ }
      end

This works. So this is not a "gosu" problem :/

I tried many things with serverspec and docker-api directly, but cannot create a successful test...
Not sure in which project the problem is.

@wibimaster
Copy link
Author

Hi,
after searching a week on that, I think the problem is maybe on Docker itself.

Dockerspec depends on Serverspec, which depends on SpecInfra.
On the last, I found the docker backend :
https://github.com/mizzy/specinfra/blob/master/lib/specinfra/backend/docker.rb#L90

As you can see, it calls Docker.exec command.

After some Googling, I found this issue :
moby/moby#18136

It's not the same problem, but this sentence challenged me :

I think most people and myself included would not want to see the entrypoint scripts run twice as this might cause serious problems.

So, when the container is already started, launch an "exec" command on it does not re-call the entrypoint, and in my case, the "gosu" script does not precede the "exec" command.
Because it starts a new shell as root.

The only "bad" solution I see is to preceed by myself the command tested with the entrypoint, but it's not really a good practice I think...

Do you have any better idea ?

At least, how to get dynamically the entrypoint to add it before the commands tested ?

Thanks !

@zuazo
Copy link
Owner

zuazo commented May 2, 2017

Hi @wibimaster,

First of all, thanks for making such a detailed report 😉

As you have deduced, the problem is that command executes the command in the container itself, not in the entrypoint, and that does not look like it's going to be easy to change.

I can think of two possible solutions to your problem:

  1. Run the entrypoint yourself:
      describe command('/usr/local/bin/entrypoint.sh id -u') do
        its(:stdout) { should match /6000/ }
      end

Maybe adding a simple entrypoint_command wrapper would help. Not sure about it 🤔 What do you think?

  1. Another possibility is to check the shell opened by the entrypoint:
      describe process('bash') do
        it { should be_running }
        its(:user) { should eq 'user' }
      end

@wibimaster
Copy link
Author

Thanks,

I see multiple possibilities :

  • For my particular case, maybe use the "--user" flag instead of "gosu". I don't know if the --user flag is reused when Serverspec throw an "exec" in the container.
  • Run the entrypoint myself is a possibility, but a little ugly
  • Check the shell opened, I didn't know I can do that ; can I test the UID and the GID with this method ?
  • Another way would be to execute commands in the shell already opened ; not really good, and not sure we can do that.

An entrypoint_command is, I think, helpful in this case. But the difference between commands at run and exec is maybe larger, like environment variable per process', etc.

The best way to achieve this is to get the capability to test commands at run and not with exec in the container.
It should be a choice.

Because, what my problem expose, is the 2 ways we can use a Docker image, and if the test hadn't show me the difference between "run" and "exec", I could have had the problem in production.

I mean, if I used my docker image with commands at run, or if I run a shell and exec commands after, I have 2 distincts workflows with, in my case, 2 differents users.

Is there a way to do this :

    # id -u at run
    describe docker_run(described_image, env: {"LOCAL_USER_ID" => "6000:6000"}, cmd: ['id -u']) do
      	its(:stdout) { should match /6000/ }
    end
    # id -u in exec
    describe docker_run(described_image, env: {"LOCAL_USER_ID" => "6000:6000"}, cmd: ['bash']) do
      describe command('id -u') do
      	its(:stdout) { should match /0/ }
      end
    end

@zuazo
Copy link
Owner

zuazo commented May 2, 2017

Maybe you could create multiple Dockerspec files for tests with different CMD / ENV instructions. See #10 for a more detailed explanation.

Check the shell opened, I didn't know I can do that ; can I test the UID and the GID with this method ?

See the process helper of Serverspec documentation. Seems not to be supported, but maybe it is not so difficult to implement.

Anyway, you can also use a shell command to check its uid:

      describe command('ps -o uid $(pidof bash)') do
        its(:stdout) { should match /6000/ }
      end

The best way to achieve this is to get the capability to test commands at run and not with exec in the container. It should be a choice.

But this seems to be more a limitation of Docker than something that can be implemented in this gem, sorry 😞

Is there a way to do this?

    describe docker_run(described_image, env: {"LOCAL_USER_ID" => "6000:6000"}, cmd: ['id -u']) do
      	# [...]
    end

There could be a way to implement it, but currently is not possible to change those values in the docker_run.

@wibimaster
Copy link
Author

Hi,

I like this way you wrote :

      describe command('ps -o uid $(pidof bash)') do
        its(:stdout) { should match /6000/ }
      end

:)

For my particular case, I found another solution provided by SpecInfra :

set :docker_container_exec_options, {'User' => '6000:6000'}

But if we want a more generic patch, it's possible but we need to patch SpecInfra itself ; not so complicated, but not sure the owner would accept a patch like this...

For the moment, the "User" trick is sufficient for me :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants