I'm currently having an issue with testing Docker image builds by means of Serverspec. In a nutshell, what I want to do is ensuring that during the image build the Ruby gems build cache gets cleared explicitly, e.g. by issuing rm -rf /usr/lib/ruby/gems/*/cache/*.gem
in the Dockerfile.
The Dockerfile skeleton I'm working with looks like this:
# Dockerfile
FROM alpine:3.7
RUN apk add --no-cache \
dumb-init \
ruby \
&& apk add --no-cache --virtual .build-deps \
build-base \
ruby-dev
RUN gem install --no-rdoc --no-ri json \
&& gem install --no-rdoc --no-ri oj
RUN apk del .build-deps \
&& rm -rf /var/cache/apk/* \
/tmp/* /var/tmp/*
Before adding the gem cache removal command, I implemented the following Serverspec test to be able to start from a failing test:
# ./spec/Dockerfile_spec.rb
describe "Dockerfile" do
before(:all) do
@image = Docker::Image.build_from_dir('.')
@image.tag(repo: 'demo', tag: 'latest')
set :os, family: :alpine
set :backend, :docker
set :docker_image, @image.id
end
it "removes build dependencies during cleanup" do
# Some more assertions
# ...
expect(Dir.glob('/usr/lib/ruby/gems/*/cache/*.gem').size).to eq 0
end
end
Assuming the Dockerfile from above, the tests run green:
$ bundle exec rspec spec/Dockerfile_spec.rb
....
Finished in 3.95 seconds (files took 0.24091 seconds to load)
4 examples, 0 failures
However, this should not be the case since the gem cache has not yet been cleared and thus is not empty, i.e. I'd expect the corresponding assertion to fail during test execution.
This can easily be verified by launching a container from the freshly built image and checking the gems cache directory:
$ docker run --rm -it demo:latest sh
/ # ls /usr/lib/ruby/gems/2.4.0/cache/
json-2.1.0.gem oj-3.4.0.gem
Testing the other way around and expecting a non-empty directory, the execution fails with an error message:
# ./spec/Dockerfile.rb
it "removes build dependencies during cleanup" do
# Some more assertions
# ...
expect(Dir.glob('/usr/lib/ruby/gems/*/cache/*.gem').size).to_not eq 0
end
# Command line
$ bundle exec rspec spec/Dockerfile_spec.rb
.F..
Failures:
1) Dockerfile removes build dependencies during cleanup
Failure/Error: expect(Dir.glob('/usr/lib/ruby/gems/*/cache/*.gem').size).to_not eq 0
expected: value != 0
got: 0
(compared using ==)
So obviously, the Dir.glob()
command does not return the correct number of files in the gems cache directory.
Funny enough, running the Dir.glob()
command manually inside the container returns the expected result:
$ docker run --rm -it demo:latest sh
/ # apk add --no-cache ruby-irb
...
/ # irb
irb(main):001:0> Dir.glob('/usr/lib/ruby/gems/*/cache/*.gem').size
=> 2
First, this made me think that the test is not properly executed within the container but on the host instead, but further experiments could not confirm this.
You have any ideas? Could this be a Serverspec/Rspec issue?
Thx!
First, this made me think that the test is not properly executed within the container but on the host instead, but further experiments could not confirm this.
I finally found out that this assumption was wrong. Actually, the Dir.glob()
calls are executed outside the container, for whatever reason.
I finally figured out what went wrong, and actually it's pretty simple:
Let's assume that we have a single Serverspec test case like this one:
it "removes build dependencies during cleanup" do
# Some more assertions
# ...
expect(Dir.glob('/usr/lib/ruby/gems/*/cache/*.gem').size).to_not eq 0
end
Now what's happening during execution is that the Dir.glob()
call is evaluated before the expect()
routine is invoked. Consequently, as I already pointed out in my post, the Dir.glob()
command is run outside of the container's scope and checks the corresponding host directory instead.
If you ever want to check if a container folder is empty or not, you can achieve this as follows:
it "clears gem/apk caches as well as tmp files/dirs" do
expect(command('ls /usr/lib/ruby/gems/*/cache/*.gem | wc -l').stdout).to eq "0\n"
end
Admittedly, this is probably not the most beautiful and elegant solution but basically does the job. If you have other ideas and proposals, feel free to post them.