├── spec ├── fixtures │ ├── files │ │ ├── file_example1 │ │ └── file_example2 │ ├── Dockerfile1.erb │ ├── WrongDockerfile │ ├── Dockerfile1 │ ├── docker-compose.yml │ ├── Dockerfile │ ├── error_example.log │ └── container1.json ├── unit │ ├── docker_gem_spec.rb │ ├── version_spec.rb │ ├── helper │ │ ├── docker_spec.rb │ │ ├── ci_spec.rb │ │ └── rspec_example_helpers_spec.rb │ ├── builder │ │ ├── logger_spec.rb │ │ ├── logger │ │ │ ├── silent_spec.rb │ │ │ ├── debug_spec.rb │ │ │ ├── info_spec.rb │ │ │ └── ci_spec.rb │ │ ├── image_gc_spec.rb │ │ ├── matchers │ │ │ └── helpers_spec.rb │ │ └── config_helpers_spec.rb │ ├── engine │ │ ├── base_spec.rb │ │ ├── infrataster_spec.rb │ │ ├── specinfra │ │ │ ├── backend_hack_spec.rb │ │ │ └── backend_spec.rb │ │ └── specinfra_spec.rb │ ├── runner │ │ ├── base_spec.rb │ │ ├── config_helpers_spec.rb │ │ ├── serverspec │ │ │ └── compose_spec.rb │ │ └── compose_spec.rb │ ├── rspec │ │ └── resources │ │ │ └── its_container_spec.rb │ ├── docker_exception_parser_spec.rb │ ├── configuration_spec.rb │ └── engine_list_spec.rb ├── integration │ ├── dockerfiles │ │ ├── from_template_spec.rb │ │ ├── from_image_id_spec.rb │ │ ├── wrong_dockerfile_spec.rb │ │ ├── from_tag_spec.rb │ │ ├── from_file_spec.rb │ │ ├── with_env_spec.rb │ │ ├── from_dir_spec.rb │ │ └── from_string_spec.rb │ └── builder │ │ └── matchers_spec.rb ├── spec_helper.rb └── support │ └── dockerspec_tests.rb ├── .rubocop.yml ├── circle.yml ├── .gitignore ├── .yardopts ├── Gemfile ├── .travis.yml ├── TODO.md ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── TESTING.md ├── lib ├── dockerspec │ ├── runner.rb │ ├── runner │ │ ├── serverspec │ │ │ ├── rspec.rb │ │ │ ├── rspec │ │ │ │ └── settings.rb │ │ │ ├── compose.rb │ │ │ ├── docker.rb │ │ │ └── base.rb │ │ ├── serverspec.rb │ │ └── config_helpers.rb │ ├── rspec.rb │ ├── docker_gem.rb │ ├── infrataster.rb │ ├── version.rb │ ├── rspec │ │ ├── settings.rb │ │ ├── configuration.rb │ │ └── resources │ │ │ └── its_container.rb │ ├── serverspec.rb │ ├── helper │ │ ├── docker.rb │ │ ├── ci.rb │ │ ├── rspec_example_helpers.rb │ │ └── multiple_sources_description.rb │ ├── builder │ │ ├── logger │ │ │ ├── silent.rb │ │ │ ├── debug.rb │ │ │ ├── ci.rb │ │ │ └── info.rb │ │ ├── logger.rb │ │ ├── image_gc.rb │ │ ├── matchers │ │ │ └── helpers.rb │ │ └── matchers.rb │ ├── exceptions.rb │ ├── engine │ │ ├── specinfra │ │ │ ├── backend_hack.rb │ │ │ └── backend.rb │ │ ├── infrataster.rb │ │ ├── specinfra.rb │ │ └── base.rb │ ├── engine_list.rb │ ├── configuration.rb │ └── docker_exception_parser.rb └── dockerspec.rb ├── CONTRIBUTING.md ├── dockerspec.gemspec ├── Rakefile └── CHANGELOG.md /spec/fixtures/files/file_example1: -------------------------------------------------------------------------------- 1 | File Example 1 2 | -------------------------------------------------------------------------------- /spec/fixtures/files/file_example2: -------------------------------------------------------------------------------- 1 | File Example 2 2 | -------------------------------------------------------------------------------- /spec/fixtures/Dockerfile1.erb: -------------------------------------------------------------------------------- 1 | <%= 'FROM alpine:3.2' %> 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - vendor/**/* 4 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | services: 3 | - docker 4 | ruby: 5 | version: 2.2.3 6 | -------------------------------------------------------------------------------- /spec/fixtures/WrongDockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.2 2 | 3 | RUN apk add --update wrong-package-name 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | coverage 4 | *.swp 5 | *.swo 6 | *~ 7 | .inch 8 | .yardoc 9 | doc 10 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | - 3 | CHANGELOG.md 4 | CONTRIBUTING.md 5 | README.md 6 | TESTING.md 7 | TODO.md 8 | -------------------------------------------------------------------------------- /spec/fixtures/Dockerfile1: -------------------------------------------------------------------------------- 1 | FROM alpine:3.2 2 | 3 | CMD ["sh", "-c", "sleep 5 ; echo STDOUT ; echo STDERR >&2 ; sleep 2000"] 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # -*- mode: ruby -*- 3 | # vi: set ft=ruby : 4 | 5 | source 'https://rubygems.org' 6 | 7 | gemspec 8 | -------------------------------------------------------------------------------- /spec/fixtures/docker-compose.yml: -------------------------------------------------------------------------------- 1 | wordpress: 2 | image: wordpress:4.7.3-apache 3 | links: 4 | - db:mysql 5 | ports: 6 | - 8080:80 7 | 8 | db: 9 | image: mariadb:10.1.22 10 | environment: 11 | - MYSQL_ROOT_PASSWORD=example 12 | 13 | alpine: 14 | build: . 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | rvm: 4 | - 2.2 5 | - 2.3 6 | 7 | env: 8 | matrix: 9 | - SERVERSPEC="true" 10 | - INFRATASTER="true" 11 | - ALL="true" 12 | 13 | matrix: 14 | exclude: 15 | - rvm: 2.2 16 | env: SERVERSPEC="true" 17 | - rvm: 2.2 18 | env: INFRATASTER="true" 19 | 20 | sudo: required 21 | 22 | services: docker 23 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO for Dockerspec 2 | 3 | * [ ] Log parsing contains `"\x00"` chars. 4 | * [ ] Integrate with [Inspec](https://www.chef.io/inspec/). 5 | * [ ] Test resources for built images supported inside `docker_compose`. 6 | * [ ] Add `docker_context`, `docker_describe` to avoid starting all containers at the same time. 7 | * [ ] Add a Runner logger. 8 | * [ ] Create `stub_*` methods for unit tests. 9 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | [Describe what this change achieves] 4 | 5 | ### Issues Resolved 6 | 7 | [List any existing issues this PR resolves] 8 | 9 | ### Contribution Check List 10 | 11 | - [ ] All tests pass. 12 | - [ ] New functionality includes testing. 13 | - [ ] New functionality has been documented in the README and metadata if applicable. 14 | 15 | See [CONTRIBUTING.md](https://github.com/zuazo/dockerspec/blob/master/CONTRIBUTING.md). 16 | -------------------------------------------------------------------------------- /spec/fixtures/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.2 2 | MAINTAINER John Doe "john.doe@example.com" 3 | 4 | LABEL testing=docker serverspec=true description="My Container" 5 | 6 | ENV PATH /usr/sbin:/usr/bin:/sbin:/bin 7 | 8 | ENV container=docker CRACKER="RANDOM;PATH=/tmp/bin:/sbin:/bin" 9 | 10 | WORKDIR /opt 11 | 12 | USER nobody 13 | 14 | EXPOSE 80 15 | 16 | VOLUME /volume1 /volume2 17 | 18 | ADD files/file_example1 /tmp/file_example1 19 | 20 | COPY files/file_example2 /tmp/file_example2 21 | 22 | RUN ["echo", "running"] 23 | 24 | ENTRYPOINT ["sleep"] 25 | CMD ["2", "2000"] 26 | ONBUILD RUN echo onbuild 27 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ## Installing the Requirements 4 | 5 | You can install gem dependencies with bundler: 6 | 7 | $ gem install bundler 8 | $ bundler install 9 | 10 | ## Generate Documentation 11 | 12 | $ bundle exec rake doc 13 | 14 | This will generate the HTML documentation in the `doc/` directory. 15 | 16 | ## All the Tests 17 | 18 | $ bundle exec rake test 19 | 20 | ## Running the Syntax Style Tests 21 | 22 | $ bundle exec rake style 23 | 24 | ## Running the Unit Tests 25 | 26 | $ bundle exec rake unit 27 | 28 | ## Running the Integration Tests 29 | 30 | $ bundle exec rake integration 31 | -------------------------------------------------------------------------------- /spec/fixtures/error_example.log: -------------------------------------------------------------------------------- 1 | {"stream":"Step 1 : FROM alpine:3.2\n"} 2 | {"stream":" ---\u003e d6ead20d5571\n"} 3 | {"stream":"Step 2 : RUN apk add --update wrong-package-name\n"} 4 | {"stream":" ---\u003e Running in 290a46fa8bf4\n"} 5 | {"stream":"fetch http://dl-4.alpinelinux.org/alpine/v3.2/main/x86_64/APKINDEX.tar.gz\n"} 6 | {"stream":"ERROR: unsatisfiable constraints:\n"} 7 | {"stream":" wrong-package-name (missing):\n required by: world[wrong-package-name]\n"} 8 | {"errorDetail":{"message":"The command '/bin/sh -c apk add --update wrong-package-name' returned a non-zero code: 1"},"error":"The command '/bin/sh -c apk add --update wrong-package-name' returned a non-zero code: 1"} 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Dockerspec Version 2 | [Version of the gem where you are encountering the issue] 3 | 4 | ### Ruby Version 5 | [Version of Ruby in your environment] 6 | 7 | ### Platform Details 8 | [Operating system distribution and release version] 9 | 10 | ### Scenario 11 | [What you are trying to achieve and you can't?] 12 | 13 | ### Steps to Reproduce 14 | [If you are filing an issue what are the things we need to do in order to repro your problem? How are you using this gem or any resources it includes?] 15 | 16 | ### Expected Result 17 | [What are you expecting to happen as the consequence of above reproduction steps?] 18 | 19 | ### Actual Result 20 | [What actually happens after the reproduction steps? Include the error output or a link to a gist if possible.] 21 | -------------------------------------------------------------------------------- /lib/dockerspec/runner.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/runner/docker' 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 1. [Fork the repository on GitHub](https://help.github.com/articles/fork-a-repo). 4 | 2. Create a named feature branch (`$ git checkout -b my-new-feature`). 5 | 3. Write tests for your change (if applicable). 6 | 4. Write your change. 7 | 5. Add documentation to your change. 8 | 6. [Run the tests](https://github.com/zuazo/dockerspec/blob/master/TESTING.md), ensuring they all pass (`$ bundle exec rake`). Try as much as possible **not to reduce coverage**. 9 | 7. Commit your change (`$ git commit -am 'Add some feature'`). 10 | 8. Push to the branch (`$ git push origin my-new-feature`). 11 | 9. [Submit a Pull Request using GitHub](https://help.github.com/articles/creating-a-pull-request). 12 | 13 | You can see the [TODO.md](https://github.com/zuazo/dockerspec/blob/master/TODO.md) file if you're looking for inspiration. 14 | -------------------------------------------------------------------------------- /lib/dockerspec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/version' 21 | require 'dockerspec/rspec' 22 | -------------------------------------------------------------------------------- /lib/dockerspec/runner/serverspec/rspec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/runner/serverspec/rspec/settings' 21 | -------------------------------------------------------------------------------- /lib/dockerspec/rspec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/rspec/resources' 21 | require 'dockerspec/rspec/configuration' 22 | -------------------------------------------------------------------------------- /lib/dockerspec/runner/serverspec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/runner/serverspec/docker' 21 | require 'dockerspec/runner/serverspec/compose' 22 | -------------------------------------------------------------------------------- /lib/dockerspec/docker_gem.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'docker' 21 | 22 | # 23 | # We tweak the timeout to get along better with CI environments. 24 | # 25 | ::Docker.options[:read_timeout] = 50 * 60 # 50 mins 26 | -------------------------------------------------------------------------------- /lib/dockerspec/runner/serverspec/rspec/settings.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'rspec' 21 | 22 | # 23 | # Add some RSpec custom settings for {Dockerspec::Serverspec}. 24 | # 25 | RSpec.configure do |c| 26 | c.add_setting :family 27 | end 28 | -------------------------------------------------------------------------------- /lib/dockerspec/infrataster.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec' 21 | require 'dockerspec/engine/infrataster' 22 | 23 | # 24 | # Use Infrataster to run the tests (engine). 25 | # 26 | Dockerspec::Configuration.add_engine Dockerspec::Engine::Infrataster 27 | -------------------------------------------------------------------------------- /spec/unit/docker_gem_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe 'docker-api Gem configuration' do 23 | it 'sets read timeout to 50 minutes' do 24 | expect(::Docker.options[:read_timeout]).to be >= 50 * 60 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/dockerspec/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | # 21 | # The main module with methods to run tests against built images and running 22 | # containers. 23 | # 24 | module Dockerspec 25 | # 26 | # Dockerspec Ruby Gem version. 27 | # 28 | VERSION = '0.6.0.dev'.freeze 29 | end 30 | -------------------------------------------------------------------------------- /spec/unit/version_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::VERSION do 23 | it { should match(/^[0-9]+\.[0-9]+\.[0-9]+/) } 24 | 25 | it 'is a valid version' do 26 | expect { described_class }.to_not raise_error 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/dockerspec/rspec/settings.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'rspec' 21 | 22 | # 23 | # Add some RSpec custom settings for {Dockerspec}. 24 | # 25 | RSpec.configure do |c| 26 | c.add_setting :dockerfile_path 27 | c.add_setting :rm_build 28 | c.add_setting :log_level 29 | 30 | # Docker Compose settings: 31 | c.add_setting :docker_wait 32 | c.add_setting :container_name 33 | end 34 | -------------------------------------------------------------------------------- /lib/dockerspec/rspec/configuration.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | # 21 | # Some very opinionated RSpec configuration. 22 | # 23 | # This may change in the future. 24 | # 25 | RSpec.configure do |config| 26 | config.color = true 27 | config.formatter = :documentation if config.formatters.empty? 28 | config.tty = $stdout.tty? if config.tty.nil? 29 | 30 | # rspec-retry 31 | config.verbose_retry = true if ENV['CI'] == 'true' 32 | config.default_sleep_interval = 1 33 | end 34 | -------------------------------------------------------------------------------- /lib/dockerspec/serverspec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec' 21 | require 'dockerspec/runner/serverspec' 22 | require 'dockerspec/engine/specinfra' 23 | 24 | # 25 | # Use Serverpec to start the containers (runner) and to run the tests (engine). 26 | # 27 | Dockerspec::Configuration.docker_runner = Dockerspec::Runner::Serverspec::Docker 28 | Dockerspec::Configuration.compose_runner = 29 | Dockerspec::Runner::Serverspec::Compose 30 | Dockerspec::Configuration.add_engine Dockerspec::Engine::Specinfra 31 | -------------------------------------------------------------------------------- /spec/integration/dockerfiles/from_template_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | serverspec_tests do 23 | describe 'Build a Dockerfile from a template' do 24 | template = DockerspecTests.fixture_file('Dockerfile1.erb') 25 | 26 | describe docker_build(template: template, tag: 'from_template_spec') do 27 | describe docker_run('from_template_spec') do 28 | describe package('alpine-base') do 29 | it { should be_installed } 30 | end 31 | 32 | it 'is a Linux distro' do 33 | expect(command('uname').stdout).to include 'Linux' 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/helper/docker_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Helper::Docker do 23 | let(:docker_info) { {} } 24 | before { allow(::Docker).to receive(:info).and_return(docker_info) } 25 | 26 | context '.lxc_execution_driver?' do 27 | subject { described_class.lxc_execution_driver? } 28 | 29 | context 'with native driver' do 30 | before { docker_info['ExecutionDriver'] = 'native-0.2' } 31 | it { should be false } 32 | end 33 | 34 | context 'with LXC driver' do 35 | before { docker_info['ExecutionDriver'] = 'lxc-1.0.6' } 36 | it { should be true } 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/integration/dockerfiles/from_image_id_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | path = DockerspecTests.fixture_dir 23 | image = ::Docker::Image.build_from_dir(path) 24 | 25 | serverspec_tests do 26 | describe 'Build a Dockerfile from an image ID' do 27 | describe docker_build(id: image.id, tag: 'from_image_id_spec') do 28 | describe docker_run('from_image_id_spec') do 29 | describe package('alpine-base') do 30 | it { should be_installed } 31 | end 32 | 33 | it 'is a Linux distro' do 34 | expect(command('uname').stdout).to include 'Linux' 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/dockerspec/helper/docker.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/docker_gem' 21 | 22 | module Dockerspec 23 | # 24 | # Some generic helpers for {Dockerspec} classes. 25 | # 26 | module Helper 27 | # 28 | # Some helper methods to get information of the Docker environment in the 29 | # system. 30 | # 31 | module Docker 32 | # 33 | # Detects if we are using the Docker LXC execution driver. 34 | # 35 | # @return [Boolean] `true` if we are using the LXC driver. 36 | # 37 | # @api public 38 | # 39 | def self.lxc_execution_driver? 40 | ::Docker.info['ExecutionDriver'].start_with?('lxc') 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/dockerspec/builder/logger/silent.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module Dockerspec 21 | class Builder 22 | class Logger 23 | # 24 | # A {Dockerspec::Builder} logger that outputs nothing. 25 | # 26 | class Silent 27 | # 28 | # Creates a Silent logger instance. 29 | # 30 | # @param _args [Mixed] ignored. 31 | # 32 | # @return void 33 | # 34 | # @api private 35 | # 36 | def initialize(*_args); end 37 | 38 | # 39 | # Prints nothing. 40 | # 41 | # @param _chunk [Mixed] ignored. 42 | # 43 | # @return void 44 | # 45 | # @api private 46 | # 47 | def print_chunk(_chunk); end 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'simplecov' 21 | if ENV['TRAVIS'] && RUBY_VERSION >= '2.0' 22 | require 'coveralls' 23 | SimpleCov.formatter = Coveralls::SimpleCov::Formatter 24 | end 25 | SimpleCov.start do 26 | add_filter '/spec/' 27 | end 28 | 29 | require 'dockerspec/serverspec' 30 | require 'dockerspec/infrataster' 31 | require 'support/dockerspec_tests' 32 | 33 | require 'should_not/rspec' 34 | 35 | RSpec.configure do |config| 36 | # Prohibit using the should syntax 37 | config.expect_with :rspec do |spec| 38 | spec.syntax = :expect 39 | end 40 | 41 | config.order = 'random' 42 | 43 | config.color = true 44 | config.tty = true 45 | 46 | config.extend DockerspecTests 47 | end 48 | 49 | include DockerspecTests 50 | DockerspecTests.init_engines 51 | -------------------------------------------------------------------------------- /spec/unit/builder/logger_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Builder::Logger do 23 | context '.instance' do 24 | { 25 | 0 => Dockerspec::Builder::Logger::Silent, 26 | :silent => Dockerspec::Builder::Logger::Silent, 27 | 1 => Dockerspec::Builder::Logger::CI, 28 | :ci => Dockerspec::Builder::Logger::CI, 29 | 2 => Dockerspec::Builder::Logger::Info, 30 | :info => Dockerspec::Builder::Logger::Info, 31 | 3 => Dockerspec::Builder::Logger::Debug, 32 | :debug => Dockerspec::Builder::Logger::Debug 33 | }.each do |type, class_type| 34 | it "returns a #{class_type.name} object for #{type.inspect}" do 35 | expect(described_class.instance(type)).to be_a class_type 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/dockerspec/builder/logger/debug.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/builder/logger/info' 21 | 22 | module Dockerspec 23 | class Builder 24 | class Logger 25 | # 26 | # A {Dockerspec::Builder} logger to output all the provided information in 27 | # its raw original form. 28 | # 29 | class Debug < Info 30 | # 31 | # Prints the docker build chunk in raw. 32 | # 33 | # @param chunk [Hash] The docker build chunk. 34 | # 35 | # @return void 36 | # 37 | # @api public 38 | # 39 | def print_chunk(chunk) 40 | chunk_json = parse_chunk(chunk) 41 | print_status(chunk_json.delete('status')) 42 | @output.puts chunk_json.inspect 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/dockerspec/exceptions.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module Dockerspec 21 | # 22 | # An exception message raised when the arguments passed to `docker_run` are 23 | # wrong. 24 | # 25 | class DockerRunArgumentError < ArgumentError; end 26 | # 27 | # An exception message raised when there are errors related to Test Engines. 28 | # 29 | class EngineError < ArgumentError; end 30 | # 31 | # An exception message raised when there are errors related to the Runner. 32 | # 33 | class RunnerError < ArgumentError; end 34 | # 35 | # An exception message raised when there are errors running Docker or 36 | # building Docker images. 37 | # 38 | class DockerError < ArgumentError; end 39 | # 40 | # An exception message raised when there is an error with the `its_container` 41 | # resource. 42 | # 43 | class ItsContainerError < ArgumentError; end 44 | end 45 | -------------------------------------------------------------------------------- /spec/unit/engine/base_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Engine::Base do 23 | let(:runner) { double('Dockerspec::Runner::Base') } 24 | let(:ipaddress) { '11.22.33.44' } 25 | let(:container_name) { 'purple_orange' } 26 | subject { described_class.new(runner) } 27 | 28 | context '#container_name' do 29 | it 'gets the runner container name' do 30 | expect(runner).to receive(:container_name).with(no_args).once 31 | .and_return(container_name) 32 | expect(subject.send(:container_name)).to eq container_name 33 | end 34 | end 35 | 36 | context '#ipaddress' do 37 | it 'gets the runner IP address' do 38 | expect(runner).to receive(:ipaddress).with(no_args).once 39 | .and_return(ipaddress) 40 | expect(subject.send(:ipaddress)).to eq ipaddress 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/integration/dockerfiles/wrong_dockerfile_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe 'With a wrong Dockerfile' do 23 | describe 'with build errors' do 24 | let(:file) { DockerspecTests.fixture_file('WrongDockerfile') } 25 | let(:build) { docker_build(path: file, tag: 'wrong_dockerfile_spec') } 26 | # Until https://github.com/swipely/docker-api/pull/472 fixed: 27 | before { Docker::Image.create('fromImage' => 'alpine:3.2') } 28 | 29 | it 'raises docker error' do 30 | expect { build }.to raise_error Dockerspec::DockerError 31 | end 32 | 33 | it 'parses the build output' do 34 | expect { build }.to raise_error( 35 | Dockerspec::DockerError, /OUTPUT: .*Step [0-9]/m 36 | ) 37 | end 38 | 39 | it 'parses the build error' do 40 | expect { build }.to raise_error( 41 | Dockerspec::DockerError, 42 | /ERROR: +The command .* returned a non-zero code:/ 43 | ) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/integration/dockerfiles/from_tag_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe 'Build a Dockerfile from a tag' do 23 | describe docker_build(id: 'nginx:1.9', tag: 'from_tag_spec') do 24 | describe docker_run('from_tag_spec') do 25 | serverspec_tests do 26 | describe package('nginx') do 27 | it { should be_installed } 28 | end 29 | 30 | it 'is a Linux distro' do 31 | expect(command('uname').stdout).to include 'Linux' 32 | end 33 | end 34 | 35 | infrataster_tests do 36 | describe server(described_container) do 37 | describe http('/') do 38 | it 'responds content including "Welcome to nginx!"' do 39 | expect(response.body).to include 'Welcome to nginx!' 40 | end 41 | 42 | it 'responds as "nginx" server' do 43 | expect(response.headers['server']).to match(/nginx/i) 44 | end 45 | end 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/dockerspec/engine/specinfra/backend_hack.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'specinfra/backend/base' 21 | 22 | # 23 | # Add a method to the {Specinfra::Backend::Base} singleton class to set its 24 | # internal backend. 25 | # 26 | # This hack makes me want to poke my own eyes out :-( 27 | # 28 | Specinfra::Backend::Base.class_eval do 29 | # 30 | # Sets the internal backend instance. 31 | # 32 | # @param instance [Specinfra::Backend::Base] the backend object. 33 | # 34 | # @return [Specinfra::Backend::Base] 35 | # 36 | # @api public 37 | # 38 | def self.instance_set(instance) 39 | return if @instance == instance 40 | host_reset 41 | @instance = instance 42 | end 43 | 44 | # 45 | # Resets the information stored for a host. 46 | # 47 | # - Host inventory. 48 | # - Detected OS. 49 | # 50 | # @return void 51 | # 52 | # @api public 53 | # 54 | def self.host_reset 55 | property[:host_inventory] = property[:os] = nil 56 | Specinfra.backend.instance_variable_set(:@os_info, nil) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/unit/builder/logger/silent_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | require 'stringio' 22 | 23 | describe Dockerspec::Builder::Logger::Silent do 24 | let(:output) { StringIO.new } 25 | subject { described_class.new(output) } 26 | 27 | context '.new' do 28 | it 'creates an object' do 29 | expect(described_class.new).to be_a described_class 30 | end 31 | end 32 | 33 | context '#print_chunk' do 34 | it 'parses a valid JSON' do 35 | expect { subject.print_chunk('{"id": "0" }') }.not_to raise_error 36 | end 37 | 38 | it 'parses an invalid JSON' do 39 | expect { subject.print_chunk('{"wrong"}') }.not_to raise_error 40 | end 41 | 42 | it 'parses a ruby hash' do 43 | expect { subject.print_chunk('id' => '0') }.not_to raise_error 44 | end 45 | 46 | it 'does not output status' do 47 | subject.print_chunk('{"status": "downloading"}') 48 | expect(output.string).to eq '' 49 | end 50 | 51 | it 'does not output any stream' do 52 | subject.print_chunk('{"stream": "my stream"}') 53 | expect(output.string).to eq '' 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/dockerspec/helper/ci.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module Dockerspec 21 | module Helper 22 | # 23 | # Some helper methods for detecting Continuous Integration environments. 24 | # 25 | module CI 26 | # 27 | # Returns whether we are running on a [Continuous Integration] 28 | # (https://en.wikipedia.org/wiki/Continuous_integration) machine. 29 | # 30 | # @return [Boolean] `true` if we are inside a CI. 31 | # 32 | # @api public 33 | # 34 | def ci? 35 | ENV['CI'] == 'true' 36 | end 37 | 38 | # 39 | # Returns whether we are running on [Travis CI](https://travis-ci.org/). 40 | # 41 | # @return [Boolean] `true` if we are inside Travis CI. 42 | # 43 | # @api public 44 | # 45 | def travis_ci? 46 | ENV['TRAVIS'] == 'true' 47 | end 48 | 49 | # 50 | # Returns whether we are running on [CircleCI](https://circleci.com/). 51 | # 52 | # @return [Boolean] `true` if we are inside CircleCI. 53 | # 54 | # @api public 55 | # 56 | def circle_ci? 57 | ENV['CIRCLECI'] == 'true' 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/unit/builder/logger/debug_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | require 'stringio' 22 | 23 | describe Dockerspec::Builder::Logger::Debug do 24 | let(:output) { StringIO.new } 25 | subject { described_class.new(output) } 26 | 27 | context '.new' do 28 | it 'creates an object' do 29 | expect(described_class.new).to be_a described_class 30 | end 31 | end 32 | 33 | context '#print_chunk' do 34 | it 'parses a valid JSON' do 35 | expect { subject.print_chunk('{"id": "0" }') }.not_to raise_error 36 | end 37 | 38 | it 'parses an invalid JSON' do 39 | expect { subject.print_chunk('{"wrong"}') }.not_to raise_error 40 | end 41 | 42 | it 'parses a ruby hash' do 43 | expect { subject.print_chunk('id' => '0') }.not_to raise_error 44 | end 45 | 46 | it 'outputs status' do 47 | subject.print_chunk('{"status": "downloading"}') 48 | expect(output.string).to include 'downloading' 49 | end 50 | 51 | it 'outputs everything' do 52 | subject.print_chunk('{"any": "data"}') 53 | expect(output.string).to include 'any' 54 | expect(output.string).to include 'data' 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/unit/builder/image_gc_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Builder::ImageGC do 23 | subject { described_class.instance } 24 | before do 25 | subject.instance_variable_set(:@images, []) 26 | allow(ObjectSpace).to receive(:define_finalizer) 27 | end 28 | after { subject.finalize } # make sure there are no reamining images 29 | 30 | context '.new' do 31 | it 'defines a finalizer' do 32 | expect(ObjectSpace).to receive(:define_finalizer) 33 | Dockerspec::Builder::ImageGC.send(:new) 34 | end 35 | end 36 | 37 | context '#add' do 38 | let(:image1) { 'image1' } 39 | before { allow(Docker::Image).to receive(:remove) } 40 | 41 | it 'adds an image' do 42 | subject.add(image1) 43 | end 44 | end 45 | 46 | context '#finalize' do 47 | let(:image1) { 'image1' } 48 | let(:image2) { 'image2' } 49 | before do 50 | subject.add(image1) 51 | subject.add(image2) 52 | end 53 | 54 | it 'removes the images' do 55 | expect(Docker::Image).to receive(:remove).once.with(image1, force: true) 56 | expect(Docker::Image).to receive(:remove).once.with(image2, force: true) 57 | subject.finalize 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/integration/dockerfiles/from_file_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | serverspec_tests do 23 | describe 'Build a Dockerfile from a file' do 24 | file = DockerspecTests.fixture_file('Dockerfile1') 25 | 26 | describe docker_build(path: file, tag: 'from_file_spec') do 27 | describe docker_run('from_file_spec'), retry: 10 do 28 | its(:stdout) { should eq "STDOUT\n" } 29 | its(:stderr) { should eq "STDERR\n" } 30 | 31 | # Issue #2: https://github.com/zuazo/dockerspec/issues/2 32 | case os[:family] 33 | when 'debian' 34 | it 'is Debian' do 35 | expect(file('/etc/debian_version')).to exist 36 | end 37 | when 'alpine' 38 | it 'is Alpine' do 39 | expect(file('/etc/alpine-release')).to exist 40 | end 41 | else 42 | it 'Unknown OS' do 43 | raise "Unknown OS: #{os[:family]}" 44 | end 45 | end 46 | 47 | describe package('alpine-base') do 48 | it { should be_installed } 49 | end 50 | 51 | it 'is a Linux distro' do 52 | expect(command('uname').stdout).to include 'Linux' 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/dockerspec/builder/logger.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/builder/logger/silent' 21 | require 'dockerspec/builder/logger/ci' 22 | require 'dockerspec/builder/logger/info' 23 | require 'dockerspec/builder/logger/debug' 24 | 25 | module Dockerspec 26 | class Builder 27 | # 28 | # Creates an output logger for the {Dockerspec::Builder}. 29 | # 30 | class Logger 31 | # 32 | # Creates a logger object. 33 | # 34 | # @param type [Integer, Symbol] The logger to create. Possible values: 35 | # `:silent` or `0` (no output), 36 | # `:ci` or `1` (enables some outputs recommended for CI environments), 37 | # `:info` or `2` (gives information about main build steps), 38 | # `:debug` or `3` (outputs all the provided information in its raw 39 | # original form). 40 | # 41 | # @return [Dockerspec::Builder::Logger] The logger. 42 | # 43 | # @api public 44 | # 45 | def self.instance(type) 46 | case type.to_s.downcase 47 | when '0', 'silent' then Silent.new 48 | when '1', 'ci' then CI.new 49 | when '2', 'info' then Info.new 50 | else 51 | Debug.new 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/unit/engine/infrataster_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Engine::Infrataster do 23 | let(:container_name) { 'purple_orange' } 24 | let(:ipaddress) { '11.22.33.44' } 25 | let(:options) { { 'opt1' => 'val1' } } 26 | let(:runner) do 27 | double( 28 | 'Dockerspec::Runner::Base', 29 | container_name: container_name, ipaddress: ipaddress, options: options 30 | ) 31 | end 32 | subject { described_class.new(runner) } 33 | 34 | context '.new' do 35 | it 'creates a new instance' do 36 | expect(subject).to be_a described_class 37 | end 38 | end 39 | 40 | context '#when_container_ready' do 41 | it 'defines infrataster server' do 42 | expect(Infrataster::Server).to receive(:define).with( 43 | container_name.to_sym, 44 | ipaddress, 45 | options 46 | ) 47 | subject.when_container_ready 48 | end 49 | 50 | it 'defines infrataster server only once' do 51 | expect(Infrataster::Server).to receive(:define).once.with( 52 | container_name.to_sym, 53 | ipaddress, 54 | options 55 | ) 56 | subject.when_container_ready 57 | subject.when_container_ready 58 | subject.when_container_ready 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/dockerspec/builder/logger/ci.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/builder/logger/info' 21 | 22 | module Dockerspec 23 | class Builder 24 | class Logger 25 | # 26 | # A {Dockerspec::Builder} logger recommended for CI environments with 27 | # output timeouts. 28 | # 29 | class CI < Info 30 | # 31 | # Creates a CI logger instance. 32 | # 33 | # @param output [IO] the output stream. 34 | # 35 | # @api public 36 | # 37 | def initialize(output = STDOUT) 38 | super 39 | @buffer = '' 40 | @skip = false 41 | end 42 | 43 | protected 44 | 45 | # 46 | # Print a Docker build stream in the proper format. 47 | # 48 | # @param stream [String] The stream in raw. 49 | # 50 | # @return void 51 | # 52 | # @api private 53 | # 54 | def print_stream(stream) 55 | if stream =~ /^Step / 56 | @buffer = stream 57 | @skip = true 58 | else 59 | @buffer += stream 60 | @skip = false if stream =~ /^ ---> (Running in|\[Warning\]) / 61 | end 62 | return if @skip 63 | @output.puts @buffer 64 | @buffer = '' 65 | end 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/dockerspec/builder/image_gc.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'singleton' 21 | require 'dockerspec/docker_gem' 22 | 23 | module Dockerspec 24 | class Builder 25 | # 26 | # A class to manage docker image deletion. The class stores the images 27 | # created by {Dockerspec::Builder} objects and deletes them at the end of 28 | # the Ruby/RSpec run. 29 | # 30 | class ImageGC 31 | include Singleton 32 | 33 | # 34 | # The Image Garbage Collector constructor. 35 | # 36 | # @api public 37 | # 38 | def initialize 39 | @images = [] 40 | ObjectSpace.define_finalizer(self, proc { finalize }) 41 | end 42 | 43 | # 44 | # Adds a Docker image to be garbage deleted at the end. 45 | # 46 | # @param image [String] Docker image ID. 47 | # 48 | # @return void 49 | # 50 | # @api public 51 | # 52 | def add(image) 53 | @images << image 54 | end 55 | 56 | # 57 | # Removes all the Docker images. 58 | # 59 | # Automatically called at the end of the RSpec/Ruby run. 60 | # 61 | # @return void 62 | # 63 | # @api public 64 | # 65 | def finalize 66 | @images.each { |i| ::Docker::Image.remove(i, force: true) } 67 | @images = [] 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/integration/dockerfiles/with_env_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | require 'infrataster-plugin-mysql' 22 | 23 | describe 'Build a Docker container with specific environment' do 24 | password = 'B3xuQpGW6wbL6UGzqs5c' 25 | 26 | describe docker_run( 27 | 'mariadb:10.1.22', 28 | env: { MYSQL_ROOT_PASSWORD: password }, 29 | mysql: { user: 'root', password: password } 30 | ) do 31 | its(:stdout, retry: 30) { should include 'MySQL init process done.' } 32 | its(:stderr, retry: 30) { should include 'mysqld: ready for connections.' } 33 | 34 | serverspec_tests do 35 | describe command('mysqld -V') do 36 | its(:stdout) { should match(/^mysqld .*MariaDB/i) } 37 | end 38 | 39 | describe process('mysqld') do 40 | it { should be_running } 41 | end 42 | 43 | describe service('mysqld') do 44 | it { should be_running } 45 | end 46 | end 47 | 48 | infrataster_tests do 49 | describe server(described_container) do 50 | before(:all) { sleep(20) } # Wait until MySQL server is ready 51 | 52 | describe mysql_query('SHOW STATUS') do 53 | it 'returns positive uptime' do 54 | row = results.find { |r| r['Variable_name'] == 'Uptime' } 55 | expect(row['Value'].to_i).to be > 0 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/unit/engine/specinfra/backend_hack_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Specinfra::Backend::Base do 23 | let(:instance) { double('Specinfra::Backend::Base') } 24 | 25 | context '.instance_set!' do 26 | let(:property) { {} } 27 | before do 28 | allow(described_class).to receive(:property).and_return(property) 29 | described_class.instance_variable_set(:@instance, nil) 30 | end 31 | after { described_class.instance_variable_set(:@instance, nil) } 32 | 33 | it 'sets internal @instance value' do 34 | described_class.instance_set(instance) 35 | expect(described_class.instance_variable_get(:@instance)) 36 | .to eq instance 37 | end 38 | 39 | it 'resets the detected OS' do 40 | expect(property).to receive(:[]=).with(:os, nil) 41 | expect(property).to receive(:[]=).with(:host_inventory, nil) 42 | described_class.instance_set(instance) 43 | end 44 | 45 | context 'setting the same @instance' do 46 | before { described_class.instance_variable_set(:@instance, instance) } 47 | 48 | it 'does not set @instance value' do 49 | expect(described_class.instance_set(instance)).to be_nil 50 | end 51 | 52 | it 'does not reset detected OS' do 53 | expect(property).to_not receive(:[]=) 54 | described_class.instance_set(instance) 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/unit/runner/base_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Runner::Base do 23 | let(:engines) { double('Dockerspec::EngineList') } 24 | let(:container_name) { 'purple_orange' } 25 | let(:ipaddress) { '11.22.33.44' } 26 | let(:container_json) do 27 | { 28 | 'Name' => container_name, 29 | 'NetworkSettings' => { 30 | 'IPAddress' => ipaddress 31 | } 32 | } 33 | end 34 | before do 35 | stub_runner_base(engines) 36 | allow(ObjectSpace).to receive(:define_finalizer) 37 | end 38 | 39 | context '.container' do 40 | it 'raises an error' do 41 | expect { subject.run } 42 | .to raise_error Dockerspec::RunnerError, /#container method must/ 43 | end 44 | end 45 | 46 | context '#container_name' do 47 | let(:container) { double('Docker::Container', json: container_json) } 48 | before { allow(subject).to receive(:container).and_return(container) } 49 | 50 | it 'returns the container name' do 51 | expect(subject.container_name).to eq(container_name) 52 | end 53 | end 54 | 55 | context '#ipaddress' do 56 | let(:container) { double('Docker::Container', json: container_json) } 57 | before { allow(subject).to receive(:container).and_return(container) } 58 | 59 | it 'returns the container IP address' do 60 | expect(subject.ipaddress).to eq(ipaddress) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /dockerspec.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # -*- mode: ruby -*- 3 | # vi: set ft=ruby : 4 | 5 | # More info at http://guides.rubygems.org/specification-reference/ 6 | 7 | $LOAD_PATH.push File.expand_path('../lib', __FILE__) 8 | require 'dockerspec/version' 9 | 10 | Gem::Specification.new do |s| 11 | s.name = 'dockerspec' 12 | s.version = ::Dockerspec::VERSION 13 | s.date = '2017-08-30' 14 | s.platform = Gem::Platform::RUBY 15 | s.summary = 'Dockerspec' 16 | s.description = 17 | 'A small gem to run RSpec, Serverspec, Infrataster and Capybara tests '\ 18 | 'against Dockerfiles or Docker images easily.' 19 | s.license = 'Apache-2.0' 20 | s.authors = %(Xabier de Zuazo) 21 | s.email = 'xabier@zuazo.org' 22 | s.homepage = 'https://github.com/zuazo/dockerspec' 23 | s.require_path = 'lib' 24 | s.files = %w( 25 | LICENSE 26 | Rakefile 27 | .yardopts 28 | ) + Dir.glob('*.md') + Dir.glob('lib/**/*') 29 | s.test_files = Dir.glob('{test,spec,features}/*') 30 | s.required_ruby_version = Gem::Requirement.new('>= 2.0.0') 31 | 32 | s.add_dependency 'docker-api', '~> 1.22' 33 | s.add_dependency 'docker-compose-api', '~> 1.0' 34 | s.add_dependency 'rspec', '~> 3.0' 35 | s.add_dependency 'rspec-its', '~> 1.0' 36 | s.add_dependency 'rspec-retry', '~> 0.5.3' 37 | s.add_dependency 'serverspec', '~> 2.24' 38 | s.add_dependency 'infrataster', '~> 0.3.0' 39 | s.add_dependency 'specinfra-backend-docker_lxc', '~> 0.2.0' 40 | s.add_dependency 'specinfra-backend-docker_compose', '~> 0.1.0' 41 | s.add_dependency 'erubis', '~> 2.0' 42 | 43 | s.add_development_dependency 'rake', '~> 10.0' 44 | s.add_development_dependency 'rspec-core', '~> 3.1' 45 | s.add_development_dependency 'rspec-expectations', '~> 3.1' 46 | s.add_development_dependency 'rspec-mocks', '~> 3.1' 47 | s.add_development_dependency 'coveralls', '~> 0.7' 48 | s.add_development_dependency 'simplecov', '~> 0.9' 49 | s.add_development_dependency 'should_not', '~> 1.1' 50 | s.add_development_dependency 'rubocop', '~> 0.37.0' 51 | s.add_development_dependency 'yard', '~> 0.8' 52 | # Used for integration tests: 53 | s.add_development_dependency 'infrataster-plugin-mysql', '~> 0.2.0' 54 | end 55 | -------------------------------------------------------------------------------- /spec/unit/helper/ci_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | class TestDockerHelperCI 23 | include Dockerspec::Helper::CI 24 | end 25 | 26 | describe Dockerspec::Helper::CI do 27 | let(:env_vars) { %w(CI TRAVIS CIRCLECI) } 28 | before { @env_orig = env_vars.each_with_object({}) { |k, m| m[k] = ENV[k] } } 29 | after { @env_orig.each { |k, v| ENV[k] = v } } 30 | 31 | context '#ci?' do 32 | subject { TestDockerHelperCI.new.ci? } 33 | 34 | context 'on a CI' do 35 | before { ENV['CI'] = 'true' } 36 | it { should be true } 37 | end 38 | 39 | context 'outside a CI' do 40 | before { ENV.delete('CI') } 41 | it { should be false } 42 | end 43 | end 44 | 45 | context '#travis_ci?' do 46 | subject { TestDockerHelperCI.new.travis_ci? } 47 | 48 | context 'on Travis CI' do 49 | before { ENV['TRAVIS'] = 'true' } 50 | it { should be true } 51 | end 52 | 53 | context 'outside Travis CI' do 54 | before { ENV.delete('TRAVIS') } 55 | it { should be false } 56 | end 57 | end 58 | 59 | context '#circle_ci?' do 60 | subject { TestDockerHelperCI.new.circle_ci? } 61 | 62 | context 'on Circle CI' do 63 | before { ENV['CIRCLECI'] = 'true' } 64 | it { should be true } 65 | end 66 | 67 | context 'outside Cicle CI' do 68 | before { ENV.delete('CIRCLECI') } 69 | it { should be false } 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/unit/rspec/resources/its_container_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::RSpec::Resources::ItsContainer do 23 | let(:container_name) { 'webapp' } 24 | let(:container) { double('Docker::Container') } 25 | let(:compose) { double('Dockerspec::Runner::Compose', container: container) } 26 | subject { described_class.new(container_name, compose) } 27 | 28 | context '.new' do 29 | it 'creates an instance without errors' do 30 | expect(subject).to be_a(described_class) 31 | end 32 | end 33 | 34 | context '#restore_rspec_context' do 35 | let(:compose) { double('Dockerspec::Runner::Compose') } 36 | before do 37 | allow(compose).to receive(:restore_rspec_context) 38 | allow(compose).to receive(:select_container) 39 | end 40 | 41 | it 'restores rspec context' do 42 | expect(compose).to receive(:restore_rspec_context).once.with(no_args) 43 | subject.restore_rspec_context 44 | end 45 | 46 | it 'selects the container' do 47 | allow(compose).to receive(:select_container).once.with(container_name) 48 | subject.restore_rspec_context 49 | end 50 | end 51 | 52 | context '#container' do 53 | it 'returns the selected container' do 54 | expect(subject.container).to eq(container) 55 | end 56 | end 57 | 58 | context '#to_s' do 59 | it 'returns a description' do 60 | expect(subject.to_s).to include(container_name) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/unit/helper/rspec_example_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Helper::RSpecExampleHelpers do 23 | context '.search_objects_with' do 24 | let(:obj1) { 'dony' } 25 | let(:obj2) { 'leo' } 26 | let(:bad_obj1) { 'krang'.to_sym } 27 | let(:metadata) do 28 | { 29 | described_class: obj1, 30 | example_group: { 31 | described_class: bad_obj1, 32 | example_group: { 33 | described_class: obj2 34 | } 35 | } 36 | } 37 | end 38 | 39 | it 'returns found objects in reverse order' do 40 | expect( 41 | described_class.search_objects_with(metadata, :strip) 42 | ).to eq([obj1, obj2].reverse) 43 | end 44 | 45 | it 'returns no objects when not found' do 46 | expect( 47 | described_class.search_objects_with(metadata, :nonexistent) 48 | ).to eq([]) 49 | end 50 | end 51 | 52 | context '.restore_rspec_context' do 53 | let(:runner) { double('Dockerspec::Runner::Base') } 54 | let(:metadata) { { metadata: 'ok' } } 55 | before do 56 | allow(Dockerspec::Helper::RSpecExampleHelpers) 57 | .to receive(:search_objects_with) 58 | .with(metadata, :restore_rspec_context) 59 | .and_return([runner]) 60 | end 61 | 62 | it 'restores the runner' do 63 | expect(runner).to receive(:restore_rspec_context).once 64 | described_class.restore_rspec_context(metadata) 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/unit/builder/logger/info_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | require 'stringio' 22 | 23 | describe Dockerspec::Builder::Logger::Info do 24 | let(:output) { StringIO.new } 25 | subject { described_class.new(output) } 26 | 27 | context '.new' do 28 | it 'creates an object' do 29 | expect(described_class.new).to be_a described_class 30 | end 31 | end 32 | 33 | context '#print_chunk' do 34 | it 'parses a valid JSON' do 35 | expect { subject.print_chunk('{"id": "0" }') }.not_to raise_error 36 | end 37 | 38 | it 'parses an invalid JSON' do 39 | expect { subject.print_chunk('{"wrong"}') }.not_to raise_error 40 | end 41 | 42 | it 'parses a ruby hash' do 43 | expect { subject.print_chunk('id' => '0') }.not_to raise_error 44 | end 45 | 46 | it 'outputs status' do 47 | subject.print_chunk('{"status": "downloading"}') 48 | expect(output.string).to include 'downloading.' 49 | end 50 | 51 | it 'adds a dot for the same status update' do 52 | subject.print_chunk('{"status": "downloading", "id": 0}') 53 | subject.print_chunk('{"status": "downloading", "id": 1}') 54 | subject.print_chunk('{"status": "downloading", "id": 2}') 55 | expect(output.string).to include 'downloading...' 56 | end 57 | 58 | it 'outputs all streams' do 59 | subject.print_chunk('{"stream": "my stream"}') 60 | subject.print_chunk('{"stream": "my stream"}') 61 | expect(output.string).to include "my stream\nmy stream" 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/unit/docker_exception_parser_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::DockerExceptionParser do 23 | let(:error_msg) { DockerspecTests.error_example } 24 | let(:exception) { Exception.new(error_msg) } 25 | subject { described_class.new(exception) } 26 | 27 | context '.new' do 28 | it 'raises docker error' do 29 | expect { subject }.to raise_error Dockerspec::DockerError 30 | end 31 | 32 | it 'parses the build output' do 33 | expect { subject }.to raise_error( 34 | Dockerspec::DockerError, /OUTPUT: .*Step [0-9]/m 35 | ) 36 | end 37 | 38 | it 'parses the build error' do 39 | expect { subject }.to raise_error( 40 | Dockerspec::DockerError, 41 | /ERROR: +The command .* returned a non-zero code:/ 42 | ) 43 | end 44 | 45 | context 'for unknown message format' do 46 | let(:error_msg) { { 'unknown' => 'format' }.to_json } 47 | 48 | it 'raises the same exception' do 49 | expect { subject }.to raise_error(exception) 50 | end 51 | end 52 | 53 | context 'for bad errorDetail message format' do 54 | let(:error_msg) { { 'errorDetail' => 'format' }.to_json } 55 | 56 | it 'raises the same exception' do 57 | expect { subject }.to raise_error(exception) 58 | end 59 | end 60 | 61 | context 'when there are json errors' do 62 | before do 63 | expect(JSON).to receive(:parse).and_raise(JSON::ParserError) 64 | end 65 | 66 | it 'raises the same exception' do 67 | expect { subject }.to raise_error(exception) 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/unit/configuration_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Configuration do 23 | let(:engine) { Dockerspec::Engine::Base } 24 | let(:runner) { Dockerspec::Runner::Base } 25 | before do 26 | @instance_orig = Dockerspec::Configuration.instance_variable_get(:@instance) 27 | described_class.reset 28 | end 29 | after do 30 | Dockerspec::Configuration.instance_variable_set(:@instance, @instance_orig) 31 | end 32 | 33 | context '.add_engine & .engines' do 34 | it 'adds a engine' do 35 | described_class.add_engine(engine) 36 | expect(described_class.engines).to eq([engine]) 37 | end 38 | 39 | it 'ignores duplicated engines' do 40 | described_class.add_engine(engine) 41 | described_class.add_engine(engine) 42 | expect(described_class.engines).to eq([engine]) 43 | end 44 | end 45 | 46 | context '.docker_runner= & .docker_runner' do 47 | it 'returns Docker Runner by default' do 48 | described_class.reset 49 | expect(described_class.docker_runner).to eq(Dockerspec::Runner::Docker) 50 | end 51 | 52 | it 'sets the runner' do 53 | described_class.docker_runner = runner 54 | expect(described_class.docker_runner).to eq(runner) 55 | end 56 | end 57 | 58 | context '.compose_runner= & .compose_runner' do 59 | it 'returns Docker Compose by default' do 60 | described_class.reset 61 | expect(described_class.compose_runner).to eq(Dockerspec::Runner::Compose) 62 | end 63 | 64 | it 'sets the runner' do 65 | described_class.compose_runner = runner 66 | expect(described_class.compose_runner).to eq(runner) 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/unit/engine_list_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::EngineList do 23 | let(:runner) { double('Dockerspec::Runner::Base') } 24 | subject { described_class.new(runner) } 25 | let(:engine_class) { class_double('Dockerspec::Engine::Base') } 26 | let(:engine_classes) { [engine_class] } 27 | let(:engine) { double('Dockerspec::Engine::Base') } 28 | before do 29 | @instance_orig = Dockerspec::Configuration.instance_variable_get(:@instance) 30 | Dockerspec::Configuration.reset 31 | allow(Dockerspec::Configuration).to receive(:engines) 32 | .and_return(engine_classes) 33 | allow(engine_class).to receive(:new).and_return(engine) 34 | end 35 | after do 36 | Dockerspec::Configuration.instance_variable_set(:@instance, @instance_orig) 37 | end 38 | 39 | context '.new' do 40 | it 'reads engines from configuration' do 41 | expect(Dockerspec::Configuration).to receive(:engines).once 42 | .and_return(engine_classes) 43 | subject 44 | end 45 | 46 | it 'creates the engines' do 47 | expect(engine_class).to receive(:new).with(runner).once 48 | .and_return(engine) 49 | subject 50 | end 51 | 52 | context 'with no engines' do 53 | let(:engine_classes) { [] } 54 | 55 | it 'raises an error' do 56 | expect { subject }.to raise_error(/include the Test Engine/) 57 | end 58 | end 59 | end 60 | 61 | %w(before_running when_running restore when_container_ready).each do |meth| 62 | context ".#{meth}" do 63 | let(:opts) { { key1: 'val1' } } 64 | 65 | it "calls engine .#{meth} method" do 66 | expect(engine).to receive(meth).once.with(no_args) 67 | subject.send(meth) 68 | end 69 | 70 | it 'passes the options' do 71 | expect(engine).to receive(meth).once.with(opts) 72 | subject.send(meth, opts) 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/dockerspec/engine/infrataster.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'infrataster/rspec' 21 | require 'dockerspec/engine/base' 22 | require 'securerandom' 23 | 24 | module Dockerspec 25 | module Engine 26 | # 27 | # The Infrataster testing engine implementation. 28 | # 29 | class Infrataster < Base 30 | include ::Infrataster::Helpers::ResourceHelper 31 | 32 | # 33 | # Constructs a testing engine to use Infrataster. 34 | # 35 | # @param runner [Dockerspec::Runner::Base] The class that is being used 36 | # to run the Docker Containers. 37 | # 38 | # @return [Dockerspec::Engine::Specinfra] The engine. 39 | # 40 | # @api public 41 | # 42 | def initialize(runner) 43 | super 44 | @definitions = {} 45 | end 46 | 47 | # 48 | # Sets up Infrataster. 49 | # 50 | # @return void 51 | # 52 | # @raise [Dockerspec::RunnerError] When the `#container` method is no 53 | # implemented in the subclass or cannot select the container to test. 54 | # 55 | # @api public 56 | # 57 | def when_container_ready 58 | define_server 59 | end 60 | 61 | protected 62 | 63 | # 64 | # Defines the Infrataster server to test. 65 | # 66 | # It calls {Infrataster::Server.define} reading the internal IP address 67 | # from the Docker metadata. 68 | # 69 | # @return void 70 | # 71 | # @raise [Dockerspec::RunnerError] When the `#container` method is no 72 | # implemented in the subclass or cannot select the container to test. 73 | # 74 | # @api private 75 | # 76 | def define_server 77 | return if @definitions.key?(container_name) 78 | ::Infrataster::Server.define( 79 | container_name.to_sym, 80 | ipaddress, 81 | options 82 | ) 83 | @definitions[container_name] = ipaddress 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/unit/runner/config_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2017 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | class TestDockerspecRunnerHelpers 23 | include Dockerspec::Runner::ConfigHelpers 24 | end 25 | 26 | describe Dockerspec::Runner::ConfigHelpers do 27 | let(:container) { double('Docker::Container') } 28 | subject { TestDockerspecRunnerHelpers.new } 29 | before do 30 | allow(container).to receive(:logs).with(stdout: true).once 31 | .and_return('STDOUT') 32 | allow(container).to receive(:logs).with(stderr: true).once 33 | .and_return('STDERR') 34 | end 35 | 36 | context '#stdout' do 37 | before do 38 | expect(subject).to receive(:container).and_return(container) 39 | end 40 | 41 | it 'returns the stdout string' do 42 | expect(subject.stdout).to eq('STDOUT') 43 | end 44 | 45 | it 'filters until the first "\a"' do 46 | allow(container).to receive(:logs).with(stdout: true).once 47 | .and_return("\x01\x00\x00\x00\x00\x00\x00\aSTDOUT2") 48 | expect(subject.stdout).to eq('STDOUT2') 49 | end 50 | 51 | it 'does not filter the second "\a"' do 52 | allow(container).to receive(:logs).with(stdout: true).once 53 | .and_return("\x01\x00\x00\x00\x00\x00\x00\aSTDOUT\a2") 54 | expect(subject.stdout).to eq("STDOUT\a2") 55 | end 56 | end 57 | 58 | context '#stderr' do 59 | before do 60 | expect(subject).to receive(:container).and_return(container) 61 | end 62 | 63 | it 'returns the stderr string' do 64 | expect(subject.stderr).to eq('STDERR') 65 | end 66 | 67 | it 'filters until the first "\a"' do 68 | allow(container).to receive(:logs).with(stderr: true).once 69 | .and_return("\x02\x00\x00\x00\x00\x00\x00\aSTDERR2") 70 | expect(subject.stderr).to eq('STDERR2') 71 | end 72 | 73 | it 'does not filter the second "\a"' do 74 | allow(container).to receive(:logs).with(stderr: true).once 75 | .and_return("\x02\x00\x00\x00\x00\x00\x00\aSTDERR\a2") 76 | expect(subject.stderr).to eq("STDERR\a2") 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/unit/runner/serverspec/compose_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Runner::Serverspec::Compose do 23 | let(:file) { 'compose-file.yml' } 24 | let(:opts) { { file: file } } 25 | subject { described_class.new(opts) } 26 | let(:engines) { double('Dockerspec::EngineList') } 27 | let(:container_name) { 'webapp' } 28 | let(:container) { double('Docker::Container') } 29 | let(:compose_container) { double('ComposeContainer', container: container) } 30 | let(:containers) { { container_name => compose_container } } 31 | let(:compose) { double('DockerCompose', containers: containers) } 32 | let(:configuration) { double('Specinfra::Configuration') } 33 | let(:specinfra_backend) { double('Dockerspec::Engine::Specinfra::Backend') } 34 | before do 35 | stub_runner_compose(file, compose, engines) 36 | 37 | allow(Specinfra).to receive(:configuration).and_return(configuration) 38 | allow(Dockerspec::Helper::Docker).to receive(:lxc_execution_driver?) 39 | .and_return(false) 40 | allow(configuration).to receive(:backend) 41 | allow(configuration).to receive(:os) 42 | allow(configuration).to receive(:docker_wait) 43 | allow(configuration).to receive(:docker_compose_file) 44 | 45 | allow(Dockerspec::Engine::Specinfra::Backend) 46 | .to receive(:new).and_return(specinfra_backend) 47 | allow(specinfra_backend).to receive(:reset) 48 | allow(specinfra_backend).to receive(:save) 49 | end 50 | 51 | context '.new' do 52 | it 'runs without errors' do 53 | subject 54 | end 55 | end 56 | 57 | context '#compose' do 58 | let(:compose) { double('DockerCompose') } 59 | 60 | it 'returns backend compose attribute' do 61 | expect(specinfra_backend).to receive(:backend_instance_attribute).once 62 | .with(:compose).and_return(compose) 63 | expect(subject.send(:compose)).to eq(compose) 64 | end 65 | end 66 | 67 | context '#run' do 68 | it 'runs without errors' do 69 | subject.run 70 | end 71 | 72 | it 'sets docker compose file' do 73 | expect(configuration).to receive(:docker_compose_file).once.with(file) 74 | subject.run 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/unit/builder/logger/ci_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | require 'stringio' 22 | 23 | describe Dockerspec::Builder::Logger::CI do 24 | let(:output) { StringIO.new } 25 | subject { described_class.new(output) } 26 | 27 | context '.new' do 28 | it 'creates an object' do 29 | expect(described_class.new).to be_a described_class 30 | end 31 | 32 | it 'inherits from info logger' do 33 | expect(described_class.new).to be_a Dockerspec::Builder::Logger::Info 34 | end 35 | end 36 | 37 | context '#print_chunk' do 38 | it 'parses a valid JSON' do 39 | expect { subject.print_chunk('{"id": "0" }') }.not_to raise_error 40 | end 41 | 42 | it 'parses an invalid JSON' do 43 | expect { subject.print_chunk('{"wrong"}') }.not_to raise_error 44 | end 45 | 46 | it 'parses a ruby hash' do 47 | expect { subject.print_chunk('id' => '0') }.not_to raise_error 48 | end 49 | 50 | it 'outputs status' do 51 | subject.print_chunk('{"status": "downloading"}') 52 | expect(output.string).to include 'downloading.' 53 | end 54 | 55 | it 'adds a dot for the same status update' do 56 | subject.print_chunk('{"status": "downloading", "id": 0}') 57 | subject.print_chunk('{"status": "downloading", "id": 1}') 58 | subject.print_chunk('{"status": "downloading", "id": 2}') 59 | expect(output.string).to include 'downloading...' 60 | end 61 | 62 | it 'outputs run steps' do 63 | steps = [ 64 | { 'stream' => "Step 2 : MAINTAINER John Doe\n" }, 65 | { 'stream' => " ---> Running in 834c7d097fad\n" } 66 | ] 67 | subject.print_chunk(steps[0]) 68 | subject.print_chunk(steps[1]) 69 | expect(output.string).to include 'Step 2' 70 | expect(output.string).to include 'Running' 71 | end 72 | 73 | it 'does not output cached steps' do 74 | steps = [ 75 | { 'stream' => "Step 2 : MAINTAINER John Doe\n" }, 76 | { 'stream' => " ---> Using cache\n" } 77 | ] 78 | subject.print_chunk(steps[0]) 79 | subject.print_chunk(steps[1]) 80 | expect(output.string).to_not include 'Step 2' 81 | expect(output.string).to_not include 'Using cache' 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # -*- mode: ruby -*- 3 | # vi: set ft=ruby : 4 | 5 | # 6 | # Available Rake tasks: 7 | # 8 | # $ rake -T 9 | # rake clean # Clean some generated files 10 | # rake integration # Run the integration tests 11 | # rake integration:infrataster # Infrataster engine integration tests 12 | # rake integration:serverspec # Serverspec engine integration tests 13 | # rake rubocop # Run RuboCop style checks 14 | # rake style # Run all style checks 15 | # rake test # Run all the tests 16 | # rake unit # Run the unit tests 17 | # rake yard # Generate Ruby documentation 18 | # 19 | # More info at https://github.com/ruby/rake/blob/master/doc/rakefile.rdoc 20 | # 21 | 22 | require 'bundler' 23 | Bundler::GemHelper.install_tasks 24 | 25 | desc 'Clean some generated files' 26 | task :clean do 27 | %w( 28 | .bundle 29 | .cache 30 | coverage 31 | doc 32 | *.gem 33 | Gemfile.lock 34 | .inch 35 | vendor 36 | .yardoc 37 | ).each { |f| FileUtils.rm_rf(Dir.glob(f)) } 38 | end 39 | 40 | desc 'Generate Ruby documentation' 41 | task :yard do 42 | require 'yard' 43 | YARD::Rake::YardocTask.new do |t| 44 | t.stats_options = %w(--list-undoc) 45 | end 46 | end 47 | 48 | task doc: %w(yard) 49 | 50 | desc 'Run RuboCop style checks' 51 | task :rubocop do 52 | require 'rubocop/rake_task' 53 | RuboCop::RakeTask.new 54 | end 55 | 56 | desc 'Run all style checks' 57 | task style: %w(rubocop) 58 | 59 | require 'rspec/core/rake_task' 60 | 61 | # 62 | # Gest the tests directory name to use. 63 | # 64 | # @param name [String] The task name. 65 | # 66 | # @return [String] Subdirectory name inside *spec/*. 67 | # 68 | def rake_dir_from_name(name) 69 | if %w(serverspec infrataster).include?(name.to_s) 70 | 'integration' 71 | else 72 | name.to_s == 'test' ? '{unit,integration}' : name 73 | end 74 | end 75 | 76 | # 77 | # Generates RakeTask to run the tests. 78 | # 79 | # @param name [String] The task name. 80 | # 81 | # @param env [Hash] The environment variables to set. 82 | # 83 | # @return [RSpec::Core::RakeTask] The Generated RakeTask. 84 | # 85 | def rake_task(name, env = {}) 86 | dir = rake_dir_from_name(name) 87 | RSpec::Core::RakeTask.new(name) do |t| 88 | env.each { |k, v| ENV[k.to_s.upcase] = v.to_s } 89 | t.pattern = "spec/#{dir}/**{,/*/**}/*_spec.rb" 90 | t.verbose = true 91 | end 92 | end 93 | 94 | desc 'Run the unit tests' 95 | rake_task(:unit) 96 | 97 | namespace :integration do 98 | desc 'Serverspec engine integration tests' 99 | rake_task(:serverspec, serverspec: true) 100 | 101 | desc 'Infrataster engine integration tests' 102 | rake_task(:infrataster, infrataster: true) 103 | end 104 | 105 | desc 'Run all the integration tests' 106 | rake_task(:integration) 107 | 108 | desc 'Run all the tests' 109 | rake_task(:test) 110 | 111 | task default: %w(style test) 112 | -------------------------------------------------------------------------------- /lib/dockerspec/runner/config_helpers.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2017 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module Dockerspec 21 | module Runner 22 | # 23 | # Some helpers to get information from a running container. 24 | # 25 | module ConfigHelpers 26 | # 27 | # Parse the stdout/stderr log binary stream. 28 | # 29 | # @example 30 | # parse_log("String1") #=> "String1" 31 | # parse_log("\x02\x00\x00\x00\x00\x00\x00\aSTDERR") #=> "STDERR" 32 | # 33 | # @param log [String] log to parse in binary format. 34 | # @return [String] parsed log. 35 | # 36 | # @api private 37 | # 38 | def parse_log(log) 39 | log.sub(/^.*?\a/, '') 40 | end 41 | 42 | # 43 | # Returns the container *stdout* logs. 44 | # 45 | # @example Docker Run Example 46 | # describe docker_run('mysql') do 47 | # its(:stdout) { should include 'MySQL init process done.' } 48 | # end 49 | # 50 | # @example Docker Compose Example 51 | # describe docker_compose('.', wait: 30) do 52 | # describe its_container(:db) do 53 | # its(:stdout) { should include 'MySQL init process done.' } 54 | # end 55 | # end 56 | # 57 | # @return [String] The *stdout* logs. 58 | # 59 | # @raise [Dockerspec::RunnerError] When cannot select the container to 60 | # test. 61 | # 62 | # @api public 63 | # 64 | def stdout 65 | log = container.logs(stdout: true) 66 | parse_log(log) 67 | end 68 | 69 | # 70 | # Returns the container *stderr* logs. 71 | # 72 | # @example Docker Run Example 73 | # describe docker_run('mysql') do 74 | # its(:stderr) { should include 'mysqld: ready for connections.' } 75 | # end 76 | # 77 | # @example Docker Compose Example 78 | # describe docker_compose('.', wait: 30) do 79 | # describe its_container(:myapp) do 80 | # its(:stderr) { should eq '' } 81 | # end 82 | # end 83 | # 84 | # @return [String] The *stderr* logs. 85 | # 86 | # @raise [Dockerspec::RunnerError] When cannot select the container to 87 | # test. 88 | # 89 | # @api public 90 | # 91 | def stderr 92 | log = container.logs(stderr: true).to_s 93 | parse_log(log) 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/fixtures/container1.json: -------------------------------------------------------------------------------- 1 | { 2 | "Os" : "linux", 3 | "ContainerConfig" : { 4 | "Tty" : false, 5 | "AttachStdout" : false, 6 | "MacAddress" : "", 7 | "Env" : [ 8 | "PATH=/usr/sbin:/usr/bin:/sbin:/bin", 9 | "container=docker", 10 | "CRACKER=RANDOM;PATH=/tmp/bin:/sbin:/bin" 11 | ], 12 | "OpenStdin" : false, 13 | "OnBuild" : [ 14 | "RUN echo onbuild" 15 | ], 16 | "StdinOnce" : false, 17 | "Image" : "3076b83ce6c7e2282404a4e32abde5e75e4182b0ca388f26f301c182aa324e8b", 18 | "ExposedPorts" : { 19 | "80/tcp" : {} 20 | }, 21 | "Domainname" : "", 22 | "VolumeDriver" : "", 23 | "Entrypoint" : [ 24 | "sleep" 25 | ], 26 | "Labels" : { 27 | "serverspec" : "true", 28 | "testing" : "docker" 29 | }, 30 | "NetworkDisabled" : false, 31 | "PublishService" : "", 32 | "AttachStdin" : false, 33 | "WorkingDir" : "/opt", 34 | "User" : "nobody", 35 | "Volumes" : { 36 | "/var/tmp" : {}, 37 | "/tmp" : {} 38 | }, 39 | "Hostname" : "e615e9fecf7f", 40 | "AttachStderr" : false, 41 | "Cmd" : [ 42 | "/bin/sh", 43 | "-c", 44 | "#(nop) ONBUILD RUN echo onbuild" 45 | ] 46 | }, 47 | "Config" : { 48 | "StdinOnce" : false, 49 | "Image" : "3076b83ce6c7e2282404a4e32abde5e75e4182b0ca388f26f301c182aa324e8b", 50 | "ExposedPorts" : { 51 | "80/tcp" : {} 52 | }, 53 | "Domainname" : "", 54 | "VolumeDriver" : "", 55 | "AttachStdout" : false, 56 | "Tty" : false, 57 | "MacAddress" : "", 58 | "Env" : [ 59 | "PATH=/usr/sbin:/usr/bin:/sbin:/bin", 60 | "container=docker", 61 | "CRACKER=RANDOM;PATH=/tmp/bin:/sbin:/bin" 62 | ], 63 | "OpenStdin" : false, 64 | "OnBuild" : [ 65 | "RUN echo onbuild" 66 | ], 67 | "AttachStdin" : false, 68 | "WorkingDir" : "/opt", 69 | "User" : "nobody", 70 | "Volumes" : { 71 | "/tmp" : {}, 72 | "/var/tmp" : {} 73 | }, 74 | "Hostname" : "e615e9fecf7f", 75 | "AttachStderr" : false, 76 | "Cmd" : [ 77 | "infinity" 78 | ], 79 | "Entrypoint" : [ 80 | "sleep" 81 | ], 82 | "Labels" : { 83 | "serverspec" : "true", 84 | "testing" : "docker" 85 | }, 86 | "NetworkDisabled" : false, 87 | "PublishService" : "" 88 | }, 89 | "Created" : "2015-11-07T22:17:49.243247396Z", 90 | "DockerVersion" : "1.8.1", 91 | "Author" : "John Doe \"john.doe@example.com\"", 92 | "Architecture" : "amd64", 93 | "Comment" : "", 94 | "Id" : "081924b5ef98ecb156770575ee497af5adc9518d77fde4467786b8bc4858bb7a", 95 | "VirtualSize" : 125112632, 96 | "GraphDriver" : { 97 | "Data" : null, 98 | "Name" : "aufs" 99 | }, 100 | "Container" : "3340c90afda0b559004e434aa67e9a0a179363586dc133942d305f05b6296148", 101 | "Size" : 0, 102 | "Parent" : "3076b83ce6c7e2282404a4e32abde5e75e4182b0ca388f26f301c182aa324e8b" 103 | } 104 | -------------------------------------------------------------------------------- /lib/dockerspec/builder/logger/info.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module Dockerspec 21 | class Builder 22 | class Logger 23 | # 24 | # A {Dockerspec::Builder} logger that gives information about main build 25 | # steps. 26 | # 27 | class Info 28 | # 29 | # Creates a Info logger instance. 30 | # 31 | # @param output [IO] the output stream. 32 | # 33 | # @api public 34 | # 35 | def initialize(output = STDOUT) 36 | @output = output 37 | @status = nil 38 | end 39 | 40 | # 41 | # Prints the Docker build chunk. 42 | # 43 | # @param chunk [Hash] The docker build chunk. 44 | # 45 | # @return void 46 | # 47 | # @api public 48 | # 49 | def print_chunk(chunk) 50 | chunk_json = parse_chunk(chunk) 51 | print_status(chunk_json['status']) 52 | return unless chunk_json.key?('stream') 53 | print_stream(chunk_json['stream']) 54 | end 55 | 56 | protected 57 | 58 | # 59 | # Parses the Docker build process chunk. 60 | # 61 | # @param chunk [String] The chunk in JSON. 62 | # 63 | # @return [Hash] The chunk parsed as a Hash. 64 | # 65 | # @api private 66 | # 67 | def parse_chunk(chunk) 68 | return chunk if chunk.is_a?(Hash) 69 | JSON.parse(chunk) 70 | rescue JSON::ParserError 71 | { 'stream' => chunk } 72 | end 73 | 74 | # 75 | # Prints progress status in a shorter format. 76 | # 77 | # For example: `'Downloading.....\nExtracting..`'. 78 | # 79 | # @param status [String] The name of the current status. 80 | # 81 | # @return void 82 | # 83 | # @api private 84 | # 85 | def print_status(status) 86 | if status != @status 87 | @output.puts 88 | @status = status 89 | @output.print "#{status}." unless status.nil? 90 | elsif !status.nil? 91 | @output.print '.' 92 | end 93 | @output.flush 94 | end 95 | 96 | # 97 | # Prints the stream. 98 | # 99 | # @param stream [String] The text to print. 100 | # 101 | # @return void 102 | # 103 | # @api private 104 | # 105 | def print_stream(stream) 106 | @output.puts stream 107 | end 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /spec/integration/dockerfiles/from_dir_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | path = DockerspecTests.fixture_dir 23 | 24 | serverspec_tests do 25 | describe docker_build(path: path) do 26 | it { should have_maintainer 'John Doe "john.doe@example.com"' } 27 | it { should have_maintainer(/John Doe/) } 28 | it { should have_cmd %w(2 2000) } 29 | it { should have_cmd '2 2000' } 30 | it { should have_label 'description' } 31 | it { should have_label 'description' => 'My Container' } 32 | it { should have_expose 80 } 33 | it { should have_expose '80' } 34 | it { should have_expose(/80$/) } 35 | it { should have_env 'container' } 36 | it { should have_env 'container' => 'docker' } 37 | it { should have_env 'CRACKER' => 'RANDOM;PATH=/tmp/bin:/sbin:/bin' } 38 | it { should have_entrypoint ['sleep'] } 39 | it { should have_entrypoint 'sleep' } 40 | it { should have_volume '/volume1' } 41 | it { should have_volume %r{/vol.*2} } 42 | it { should have_user 'nobody' } 43 | it { should have_workdir '/opt' } 44 | it { should have_workdir %r{^/op} } 45 | it { should have_onbuild 'RUN echo onbuild' } 46 | 47 | its(:maintainer) { should eq 'John Doe "john.doe@example.com"' } 48 | its(:cmd) { should eq %w(2 2000) } 49 | its(:labels) { should include 'description' } 50 | its(:labels) { should include 'description' => 'My Container' } 51 | its(:exposes) { should include '80' } 52 | its(:env) { should include 'container' } 53 | its(:env) { should include 'container' => 'docker' } 54 | its(:env) { should include 'CRACKER' => 'RANDOM;PATH=/tmp/bin:/sbin:/bin' } 55 | its(:entrypoint) { should eq ['sleep'] } 56 | its(:volumes) { should include '/volume1' } 57 | its(:user) { should eq 'nobody' } 58 | its(:workdir) { should eq '/opt' } 59 | its(:onbuilds) { should include 'RUN echo onbuild' } 60 | 61 | its(:size) { should be < 20 * 2**20 } # 20M 62 | its(:arch) { should eq 'amd64' } 63 | its(:os) { should eq 'linux' } 64 | 65 | describe docker_run(described_image) do 66 | describe file('/tmp/file_example1') do 67 | it { should be_file } 68 | end 69 | 70 | describe file('/tmp/file_example2') do 71 | it { should be_file } 72 | end 73 | 74 | describe package('alpine-base') do 75 | it { should be_installed } 76 | end 77 | 78 | describe process('sleep ') do 79 | it { should be_running } 80 | its(:args) { should match(/2000/) } 81 | end 82 | 83 | it 'is a Linux distro' do 84 | expect(command('uname').stdout).to include 'Linux' 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/dockerspec/builder/matchers/helpers.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'rspec/expectations' 21 | 22 | module Dockerspec 23 | class Builder 24 | module Matchers 25 | # 26 | # Some helpers methods for the Docker build RSpec matchers. 27 | # 28 | module MatcherHelpers 29 | # 30 | # Checks whether a hash is a subhash of another. 31 | # 32 | # @example 33 | # self.sub_hash?({ a: 1, b: 2, c: 3 }, { a: 1 }) #=> true 34 | # self.sub_hash?({ a: 1, b: 2, c: 3 }, { b: 2, c: 3 }) #=> true 35 | # self.sub_hash?({ a: 1, b: 2, c: 3 }, { a: 2, b: 2 }) #=> false 36 | # self.sub_hash?({ a: 'Hello', b: 'World' }, { a: /H/, b: /Wor/ } 37 | # #=> true 38 | # self.sub_hash?({ a: 'Hello', b: 'World' }, { a: /Bye/ } #=> false 39 | # 40 | # @param hash [Hash] The hash in which to search. 41 | # @param sub_hash [Hash] The subhash. 42 | # 43 | # @return [Boolean] Whether it's a subhash. 44 | # 45 | # @api public 46 | # 47 | def sub_hash?(hash, sub_hash) 48 | sub_hash.all? do |sub_hash_key, sub_hash_value| 49 | next false unless hash.key?(sub_hash_key) 50 | if sub_hash_value.respond_to?(:match) 51 | !sub_hash_value.match(hash[sub_hash_key]).nil? 52 | else 53 | sub_hash_value == hash[sub_hash_key] 54 | end 55 | end 56 | end 57 | 58 | # 59 | # A matcher to check JSON values like `CMD` and `ENTRYPOINT`. 60 | # 61 | # The expected value can be in JSON (a Ruby array) or in String format 62 | # just like in the *Dockerfile*. 63 | # 64 | # The real (*got*) value will always be in array format. 65 | # 66 | # @example 67 | # self.maybe_json?([0, 1, 3, 4], [0, 1, 3, 4]) #=> true 68 | # self.maybe_json?([0, 1, 3, 4], [0, 1, 3, 5]) #=> false 69 | # self.maybe_json?(%w(hello world), 'hello world') #=> true 70 | # self.maybe_json?(%w(hello world), 'bye') #=> false 71 | # self.maybe_json?(%w(hello world), /llo wor/) #=> true 72 | # self.maybe_json?(%w(hello world), /bye/) #=> false 73 | # 74 | # @param got [Array] The received value. 75 | # @param expected [Array, String, Regexp] The expected value. 76 | # 77 | # @return [Boolean] Whether the expected value matches the real value. 78 | # 79 | # @api public 80 | # 81 | def maybe_json?(got, expected) 82 | return expected == got if expected.is_a?(Array) 83 | !expected.match(got.join(' ')).nil? 84 | end 85 | end 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/unit/builder/matchers/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | class TestDockerspecBuilderMatcherHelpers 23 | include Dockerspec::Builder::Matchers::MatcherHelpers 24 | end 25 | 26 | describe Dockerspec::Builder::Matchers::MatcherHelpers do 27 | subject { TestDockerspecBuilderMatcherHelpers.new } 28 | 29 | context '#sub_hash?' do 30 | it 'returns true when passing a subhash with a single key' do 31 | expect(subject.sub_hash?( 32 | { a: 1, b: 2, c: 3 }, 33 | a: 1 34 | )).to eq true 35 | end 36 | 37 | it 'returns true when passing a subhash with a multiple keys' do 38 | expect(subject.sub_hash?( 39 | { a: 1, b: 2, c: 3 }, 40 | b: 2, c: 3 41 | )).to eq true 42 | end 43 | 44 | it 'returns false for different hashes' do 45 | expect(subject.sub_hash?( 46 | { a: 1, b: 2, c: 3 }, 47 | a: 2, b: 2 48 | )).to eq false 49 | end 50 | 51 | it 'returns true when passing regexps' do 52 | expect(subject.sub_hash?( 53 | { a: 'Hello', b: 'World' }, 54 | a: /H/, b: /Wor/ 55 | )).to eq true 56 | end 57 | 58 | it 'returns false when the regexps does not match' do 59 | expect(subject.sub_hash?( 60 | { a: 'Hello', b: 'World' }, 61 | b: /Bye/ 62 | )).to eq false 63 | end 64 | end # context #sub_hash? 65 | 66 | context '#maybe_json?' do 67 | it 'returns true for the same arrays' do 68 | expect(subject.maybe_json?( 69 | [0, 1, 3, 4], 70 | [0, 1, 3, 4] 71 | )).to eq true 72 | end 73 | 74 | it 'returns true for the same arrays' do 75 | expect(subject.maybe_json?( 76 | [0, 1, 3, 4], 77 | [0, 1, 3, 5] 78 | )).to eq false 79 | end 80 | 81 | it 'returns true when passing the correct string' do 82 | expect(subject.maybe_json?( 83 | %w(hello world), 84 | 'hello world' 85 | )).to eq true 86 | end 87 | 88 | it 'returns false when passing wrong strings' do 89 | expect(subject.maybe_json?( 90 | %w(hello world), 91 | 'bye' 92 | )).to eq false 93 | end 94 | 95 | it 'returns true when passing regexps' do 96 | expect(subject.maybe_json?( 97 | %w(hello world), 98 | /llo wor/ 99 | )).to eq true 100 | end 101 | 102 | it 'returns false when passing wrong regexps' do 103 | expect(subject.maybe_json?( 104 | %w(hello world), 105 | /bye/ 106 | )).to eq false 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /spec/unit/engine/specinfra_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Engine::Specinfra do 23 | let(:runner) do 24 | double( 25 | 'Dockerspec::Runner::Base', backend_name: backend_name, options: opts 26 | ) 27 | end 28 | subject { described_class.new(runner) } 29 | let(:backend) { double('Dockerspec::Engine::Specinfra::Backend') } 30 | let(:backend_name) { 'docker_lxc' } 31 | let(:container_name) { 'webapp' } 32 | let(:family) { 'alpine' } 33 | let(:opts) { { container: container_name, family: family } } 34 | let(:specinfra_config) { double('Specinfra::Configuration') } 35 | before do 36 | allow(Dockerspec::Engine::Specinfra::Backend).to receive(:new) 37 | .and_return(backend) 38 | allow(backend).to receive(:reset) 39 | allow(backend).to receive(:restore_container) 40 | allow(Specinfra).to receive(:configuration).and_return(specinfra_config) 41 | allow(specinfra_config).to receive(:os) 42 | end 43 | 44 | context '.new' do 45 | it 'creates a new instance' do 46 | expect(subject).to be_a described_class 47 | end 48 | end 49 | 50 | context '#before_running' do 51 | it 'creates the backend' do 52 | expect(Dockerspec::Engine::Specinfra::Backend).to receive(:new).once 53 | .with(backend_name).and_return(backend) 54 | subject.before_running 55 | end 56 | 57 | it 'resets the backend' do 58 | expect(backend).to receive(:reset).once.with(no_args) 59 | subject.before_running 60 | end 61 | 62 | it 'sets up the container name' do 63 | expect(backend).to receive(:restore_container).once.with(container_name) 64 | subject.before_running 65 | end 66 | 67 | it 'sets up the family' do 68 | expect(specinfra_config).to receive(:os).once.with(family: family) 69 | subject.before_running 70 | end 71 | end 72 | 73 | context '#when_running' do 74 | it 'saves the backend' do 75 | subject.before_running 76 | expect(backend).to receive(:save).once.with(no_args) 77 | subject.when_running 78 | end 79 | end 80 | 81 | context '#restore' do 82 | before do 83 | allow(backend).to receive(:restore) 84 | allow(backend).to receive(:restore_container) 85 | end 86 | 87 | it 'restores the backend' do 88 | subject.before_running 89 | expect(backend).to receive(:restore).once.with(no_args) 90 | subject.restore 91 | end 92 | 93 | it 'sets up the container name' do 94 | subject.before_running 95 | expect(backend).to receive(:restore_container).once.with(container_name) 96 | subject.restore 97 | end 98 | 99 | it 'sets up the family' do 100 | subject.before_running 101 | expect(specinfra_config).to receive(:os).once.with(family: family) 102 | subject.restore 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/dockerspec/engine/specinfra.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/engine/base' 21 | require 'dockerspec/engine/specinfra/backend' 22 | 23 | module Dockerspec 24 | module Engine 25 | # 26 | # The Specinfra (Serverspec) testing engine implementation. 27 | # 28 | class Specinfra < Base 29 | # 30 | # Constructs a testing engine to use Specinfra (used by Serverspec). 31 | # 32 | # @param runner [Dockerspec::Runner::Base] The class that is being used 33 | # to run the Docker Containers. 34 | # 35 | # @return [Dockerspec::Engine::Specinfra] The engine. 36 | # 37 | # @api public 38 | # 39 | def initialize(runner) 40 | super 41 | @backend = nil 42 | end 43 | 44 | # 45 | # Sets the Specinfra configuration. 46 | # 47 | # - Resets the internal Specinfra backend reference. 48 | # - Sets the chosen container name with Docker Compose. 49 | # - Sets the `:family`. 50 | # 51 | # @return void 52 | # 53 | # @api private 54 | # 55 | def before_running 56 | if @backend.nil? 57 | @backend = Backend.new(backend_name) 58 | @backend.reset 59 | end 60 | setup_container_name 61 | setup_family 62 | end 63 | 64 | # 65 | # Saves the Specinfra backend reference internally to restore it later. 66 | # 67 | # @return void 68 | # 69 | # @api private 70 | # 71 | def when_running 72 | @backend.save 73 | end 74 | 75 | # 76 | # Restores the Specinfra backend instance to point to this object's 77 | # container. 78 | # 79 | # This is used to avoid Serverspec running against the previous started 80 | # container if you are testing multiple containers at the same time. 81 | # 82 | # @return void 83 | # 84 | # @api private 85 | # 86 | def restore 87 | @backend.restore 88 | setup_container_name 89 | setup_family 90 | end 91 | 92 | protected 93 | 94 | # 95 | # Gets the Specinfra backend name from the runner. 96 | # 97 | # @return [String] The backend name. 98 | # 99 | # @api private 100 | # 101 | def backend_name 102 | @runner.backend_name 103 | end 104 | 105 | # 106 | # Sets up the OS family. 107 | # 108 | # @return void 109 | # 110 | # @api private 111 | # 112 | def setup_family 113 | return unless options.key?(:family) 114 | ::Specinfra.configuration.os(family: options[:family]) 115 | end 116 | 117 | # 118 | # Selects the container to test. 119 | # 120 | # @return void 121 | # 122 | # @api private 123 | # 124 | def setup_container_name 125 | return unless options.key?(:container) 126 | @backend.restore_container(options[:container]) 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /lib/dockerspec/helper/rspec_example_helpers.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module Dockerspec 21 | module Helper 22 | # 23 | # Some Helper methods to work with RSpec Examples. 24 | # 25 | module RSpecExampleHelpers 26 | # 27 | # Checks if the parent RSpec example information exists in the metadata. 28 | # 29 | # @param metadata [Hash] RSpec metadata. 30 | # 31 | # @return [Boolean] Returns true if the parent metadata is available. 32 | # 33 | # @api private 34 | # 35 | def self.metadata_has_parent?(metadata) 36 | metadata.key?(:parent_example_group) || metadata.key?(:example_group) 37 | end 38 | 39 | # 40 | # Get the parent RSpec example metadata if available. 41 | # 42 | # @param metadata [Hash] RSpec metadata. 43 | # 44 | # @return [Hash] RSpec metadata from the parent example. 45 | # 46 | # @api private 47 | # 48 | def self.metadata_parent(metadata) 49 | if metadata.key?(:parent_example_group) 50 | metadata[:parent_example_group] 51 | elsif metadata.key?(:example_group) 52 | metadata[:example_group] 53 | end 54 | end 55 | 56 | # 57 | # Searches for an object in the description of the parent RSpec examples 58 | # that implements a specific method. 59 | # 60 | # @param metadata [Hash] RSpec metadata. 61 | # @param meth [Symbol] The method name. 62 | # 63 | # @return [Array] Returns the objects list. 64 | # 65 | # @api public 66 | # 67 | def self.search_objects_with(metadata, meth) 68 | o_ary = [] 69 | return o_ary if metadata.nil? 70 | if metadata[:described_class].respond_to?(meth) && 71 | metadata[:described_class] != self 72 | o_ary << metadata[:described_class] 73 | end 74 | return o_ary unless metadata_has_parent?(metadata) 75 | (search_objects_with(metadata_parent(metadata), meth) + o_ary).uniq 76 | end 77 | 78 | # 79 | # Restores the Docker running container instance in the Specinfra 80 | # internal reference. 81 | # 82 | # Gets the correct {Runner::Base} reference from the RSpec metadata. 83 | # 84 | # @example Restore Specinfra Backend 85 | # RSpec.configure do |c| 86 | # c.before(:each) do 87 | # metadata = RSpec.current_example.metadata 88 | # Dockerspec::Runner::Base.restore_rspec_context(metadata) 89 | # end 90 | # end 91 | # 92 | # @param metadata [Hash] RSpec metadata. 93 | # 94 | # @return void 95 | # 96 | # @api public 97 | # 98 | # @see restore 99 | # 100 | def self.restore_rspec_context(metadata) 101 | o_ary = 102 | Helper::RSpecExampleHelpers 103 | .search_objects_with(metadata, :restore_rspec_context) 104 | o_ary.each(&:restore_rspec_context) 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /spec/support/dockerspec_tests.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module DockerspecTests 21 | def serverspec_engine 22 | Dockerspec::Configuration.docker_runner = 23 | Dockerspec::Runner::Serverspec::Docker 24 | Dockerspec::Configuration.compose_runner = 25 | Dockerspec::Runner::Serverspec::Compose 26 | Dockerspec::Configuration.engines.replace([Dockerspec::Engine::Specinfra]) 27 | end 28 | 29 | def infrataster_engine 30 | Dockerspec::Configuration.docker_runner = Dockerspec::Runner::Docker 31 | Dockerspec::Configuration.compose_runner = Dockerspec::Runner::Compose 32 | Dockerspec::Configuration.engines.replace([Dockerspec::Engine::Infrataster]) 33 | end 34 | 35 | def default_engines 36 | serverspec_engine 37 | Dockerspec::Configuration.engines.push(Dockerspec::Engine::Infrataster) 38 | end 39 | 40 | def engine(name) 41 | send("#{name}_engine") 42 | end 43 | 44 | def all_engines_list 45 | %i(serverspec infrataster) 46 | end 47 | 48 | def init_engines 49 | unless all_engines_list.any? { |x| ENV.key?(x.to_s.upcase) } 50 | default_engines 51 | return 52 | end 53 | all_engines_list.each do |name| 54 | next if ENV[name.to_s.upcase].to_s != 'true' 55 | engine(name) 56 | end 57 | end 58 | 59 | def fixture_dir 60 | File.join(File.dirname(__FILE__), '..', 'fixtures') 61 | end 62 | 63 | def fixture_file(file) 64 | File.join(DockerspecTests.fixture_dir, file) 65 | end 66 | 67 | def error_example 68 | file = File.join(DockerspecTests.fixture_dir, 'error_example.log') 69 | IO.read(file) 70 | end 71 | 72 | def stub_runner_base(engines) 73 | allow(Dockerspec::EngineList).to receive(:new).and_return(engines) 74 | %i(before_running when_running when_container_ready).each do |m| 75 | allow(engines).to receive(m) 76 | end 77 | allow_any_instance_of(Dockerspec::Runner::Base).to receive(:sleep) 78 | allow(ObjectSpace).to receive(:define_finalizer) 79 | end 80 | 81 | def stub_engines(engines) 82 | allow(engines).to receive(:before_running) 83 | end 84 | 85 | def stub_dockercompose(compose) 86 | allow(DockerCompose).to receive(:load).and_return(compose) 87 | allow(compose).to receive(:start) 88 | allow(compose).to receive(:stop) 89 | allow(compose).to receive(:delete) 90 | end 91 | 92 | def stub_runner_compose(file, compose, engines) 93 | stub_runner_base(engines) 94 | stub_dockercompose(compose) 95 | stub_engines(engines) 96 | allow(Dockerspec::Runner::Compose).to receive(:current_instance=) 97 | allow(File).to receive(:directory?).and_call_original 98 | allow(File).to receive(:directory?).with(file).and_return(false) 99 | end 100 | end 101 | 102 | def serverspec_tests 103 | unless Dockerspec::Configuration.engines 104 | .include?(Dockerspec::Engine::Specinfra) 105 | return 106 | end 107 | yield 108 | end 109 | 110 | def infrataster_tests 111 | unless Dockerspec::Configuration.engines 112 | .include?(Dockerspec::Engine::Infrataster) 113 | return 114 | end 115 | yield 116 | end 117 | -------------------------------------------------------------------------------- /lib/dockerspec/runner/serverspec/compose.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'serverspec' 21 | require 'specinfra/backend/docker_compose_lxc' 22 | require 'dockerspec/runner/compose' 23 | require 'dockerspec/runner/serverspec/base' 24 | require 'dockerspec/runner/serverspec/rspec' 25 | 26 | module Dockerspec 27 | module Runner 28 | module Serverspec 29 | # 30 | # Runs Docker Compose using [Serverspec](http://serverspec.org/). 31 | # 32 | class Compose < Dockerspec::Runner::Compose 33 | include Base 34 | # 35 | # Constructs a Serverspec runner class to run Docker Compose. 36 | # 37 | # @example From a Directory 38 | # Dockerspec::Runner::Serverspec::Compose.new('directory1') 39 | # #=> # 40 | # 41 | # @example From a YAML File 42 | # Dockerspec::Runner::Serverspec::Compose.new('my/docker-compose.yml') 43 | # #=> # 44 | # 45 | # @example From a Directory or File Using Hash Format 46 | # Dockerspec::Runner::Serverspec::Compose.new(file: 'file.yml') 47 | # #=> # 48 | # 49 | # @param opts [String, Hash] The `:file` or a list of options. 50 | # 51 | # @option opts [String] :file The compose YAML file or a directory 52 | # containing the `'docker-compose.yml'` file. 53 | # @option opts [Boolean] :rm (calculated) Whether to remove the Docker 54 | # containers afterwards. 55 | # @option opts [Symbol, String] :family (calculated) The OS family if 56 | # is the the same for all the containers. 57 | # It's automatically detected by default, but can be used to 58 | # **speed up the tests**. Some possible values: 59 | # `:alpine`, `:arch`, `:coreos`, `:debian`, `:gentoo`, `:nixos`, 60 | # `:plamo`, `:poky`, `:redhat`, `:suse`. 61 | # @option opts [Symbol] :backend (calculated) Docker backend to use: 62 | # `:docker`, `:lxc`. 63 | # 64 | # @return [Dockerspec::Runner::Serverspec::Compose] Runner object. 65 | # 66 | # @api public 67 | # 68 | def initialize(*opts) 69 | super 70 | calculate_docker_backend_name('docker_compose') 71 | end 72 | 73 | protected 74 | 75 | # 76 | # Gets the internal {DockerCompose} object. 77 | # 78 | # @return [DockerCompose] The compose object. 79 | # 80 | # @api private 81 | # 82 | def compose 83 | @cached_compose ||= begin 84 | backend = Engine::Specinfra::Backend.new(backend_name) 85 | backend.backend_instance_attribute(:compose) 86 | end 87 | end 88 | 89 | # 90 | # Sets the Specinfra configuration and the engines. 91 | # 92 | # - Sets up the testing engines. 93 | # - Configures the compose file to use. 94 | # 95 | # @return void 96 | # 97 | # @api private 98 | # 99 | def before_running 100 | super 101 | Specinfra.configuration.docker_compose_file(@options[:file]) 102 | end 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/integration/dockerfiles/from_string_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | serverspec_tests do 23 | describe 'Build a Dockerfile from a string' do 24 | describe docker_build(string: 'FROM nginx:1.9', tag: 'from_string_spec') do 25 | describe docker_run('from_string_spec') do 26 | describe command('ls -al /') do 27 | its(:stdout) { should match(/bin/) } 28 | its(:exit_status) { should eq 0 } 29 | end 30 | 31 | describe command('nginx -v') do 32 | its(:stderr) { should match(/nginx version:/) } 33 | end 34 | 35 | describe file('/etc/nginx') do 36 | it { should exist } 37 | it { should be_directory } 38 | it { should be_mode 755 } 39 | end 40 | 41 | describe file('/usr/sbin/nginx') do 42 | it { should exist } 43 | it { should be_file } 44 | it { should be_mode 755 } 45 | it { should be_executable } 46 | it { should be_executable.by_user('root') } 47 | end 48 | 49 | describe file('/etc/nginx/nginx.conf') do 50 | it { should exist } 51 | it { should be_file } 52 | it { should contain 'access.log' } 53 | it { should contain('include').from(/^http {/).to(/^}/) } 54 | it { should be_mode 644 } 55 | it { should be_owned_by 'root' } 56 | it { should be_grouped_into 'root' } 57 | it { should be_readable } 58 | it { should be_readable.by_user('root') } 59 | it { should be_writable } 60 | it { should be_writable.by_user('root') } 61 | its(:size) { should < 641_021 } 62 | end 63 | 64 | describe group('root') do 65 | it { should exist } 66 | it { should have_gid 0 } 67 | end 68 | 69 | describe interface('eth0') do 70 | it { should exist } 71 | end 72 | 73 | describe package('nginx') do 74 | it { should be_installed } 75 | end 76 | 77 | describe process('nginx') do 78 | it { should be_running } 79 | its(:user) { should eq 'root' } 80 | its(:args) { should match(/-g daemon/) } 81 | end 82 | 83 | describe service('nginx') do 84 | it { should be_enabled } 85 | it { should be_running } 86 | end 87 | 88 | describe user('root') do 89 | it { should exist } 90 | it { should belong_to_group 'root' } 91 | it { should have_uid 0 } 92 | it { should have_home_directory '/root' } 93 | it { should have_login_shell '/bin/bash' } 94 | end 95 | end 96 | end 97 | context 'With string_build_path' do 98 | Dir.mktmpdir do |dir| 99 | File.write("#{dir}/test", 'rspec integration tests') 100 | dockerfile_string = "FROM nginx:1.9\nADD test /test" 101 | describe docker_build( 102 | string: dockerfile_string, 103 | string_build_path: dir) do 104 | describe docker_run(described_image) do 105 | describe command('cat /test') do 106 | its(:stdout) { should match(/rspec integration tests/) } 107 | its(:exit_status) { should eq 0 } 108 | end 109 | end 110 | end 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/dockerspec/builder/matchers.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'rspec/expectations' 21 | require 'dockerspec/builder/matchers/helpers' 22 | 23 | module Dockerspec 24 | class Builder 25 | # 26 | # Creates some RSpec *have_* matchers for Docker builds. 27 | # 28 | module Matchers 29 | extend RSpec::Matchers::DSL 30 | 31 | # 32 | # The matcher list with the type it belongs to. 33 | # 34 | # This is based on [the official *Dockerfile* parser code] 35 | # (https://github.com/docker/docker/tree/master/builder/dockerfile/parser) 36 | # . 37 | # 38 | # The possible types are: 39 | # 40 | # - `:string`: A simple string. For example the `MAINTAINER` instruction. 41 | # - `:json`: Can in JSON (a Ruby array) or in string format. For example 42 | # the `CMD` or the `ENTRYPOINT` instructions. 43 | # - `:hash`: A hash. For example the `ENV` or the `LABEL` instructions. 44 | # - `:array`: A array of values. For example the `EXPOSE` instruction. 45 | # 46 | PREDICATE_TYPES = { 47 | maintainer: :string, 48 | cmd: :json, 49 | label: :hash, 50 | expose: :array, 51 | env: :hash, 52 | entrypoint: :json, 53 | volume: :array, 54 | user: :string, 55 | workdir: :string, 56 | onbuild: :array, 57 | stopsignal: :string 58 | }.freeze 59 | 60 | PREDICATE_TYPES.each do |name, type| 61 | matcher_name = "have_#{name}".to_sym 62 | 63 | value_method = name 64 | verb = 'be' 65 | matcher matcher_name do |expected| 66 | case type 67 | when :string 68 | verb = 'match' 69 | 70 | match { |actual| !expected.match(actual.send(value_method)).nil? } 71 | when :json 72 | verb = 'be' 73 | 74 | include MatcherHelpers 75 | 76 | match { |actual| maybe_json?(actual.send(value_method), expected) } 77 | when :array 78 | value_method = "#{name}s" 79 | verb = 'include' 80 | 81 | # Allow ports to be passed as integer: 82 | if matcher_name == :have_expose && expected.is_a?(Numeric) 83 | expected = expected.to_s 84 | end 85 | 86 | match { |actual| !actual.send(value_method).grep(expected).empty? } 87 | when :hash 88 | value_method = "#{name}s" 89 | verb = 'contain' 90 | 91 | include MatcherHelpers 92 | 93 | match do |actual| 94 | actual = actual.send(value_method) 95 | break sub_hash?(actual, expected) if expected.is_a?(Hash) 96 | !actual.keys.grep(expected).empty? 97 | end 98 | end # case type 99 | 100 | failure_message do |actual| 101 | "expected `#{name.upcase}` to #{verb} `#{expected.inspect}`, "\ 102 | "got `#{actual.send(value_method).inspect}`" 103 | end 104 | 105 | failure_message_when_negated do |actual| 106 | "expected `#{name.upcase}` not to #{verb} "\ 107 | "`#{expected.inspect}`, got `#{actual.send(value_method).inspect}`" 108 | end 109 | end # matcher 110 | end # PREDICATE_TYPES each 111 | end 112 | end 113 | end 114 | 115 | RSpec.configure { |c| c.include(Dockerspec::Builder::Matchers) } 116 | -------------------------------------------------------------------------------- /lib/dockerspec/engine_list.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/configuration' 21 | require 'dockerspec/exceptions' 22 | 23 | module Dockerspec 24 | # 25 | # Manages the list of testing engines to use. 26 | # 27 | class EngineList 28 | # 29 | # A message with description on how to avoid the error when you forget 30 | # specifying the testing engine you want to use. 31 | # 32 | NO_ENGINES_MESSAGE = <<-EOE 33 | 34 | Remember to include the Test Engine you want to use. 35 | 36 | For example, to use Serverspec: 37 | 38 | require 'dockerspec/serverspec' 39 | 40 | EOE 41 | .freeze 42 | 43 | # 44 | # Constructs the list of engines. 45 | # 46 | # Initializes all the selected engines. 47 | # 48 | # @param runner [Dockerspec::Runner::Base] The class used to run the 49 | # docker container. 50 | # 51 | # @return [Dockerspec::EngineList] The list of engines. 52 | # 53 | # @raise [Dockerspec::EngineError] Raises this exception when the engine 54 | # list is empty. 55 | # 56 | # @api public 57 | # 58 | def initialize(runner) 59 | engine_classes = Configuration.engines 60 | @engines = 61 | engine_classes.map { |engine_class| engine_class.new(runner) } 62 | assert_engines! 63 | end 64 | 65 | # 66 | # Setups all the engines one by one. 67 | # 68 | # @param args [Mixed] Arguments to pass to the `#before_running` methods. 69 | # 70 | # @return void 71 | # 72 | # @api public 73 | # 74 | def before_running(*args) 75 | call_engines_method(:before_running, *args) 76 | end 77 | 78 | # 79 | # Notify the engines that the container to test is selected and ready. 80 | # 81 | # @param args [Mixed] Arguments to pass to the `#when_container_ready` 82 | # methods. 83 | # 84 | # @return void 85 | # 86 | # @api public 87 | # 88 | def when_container_ready(*args) 89 | call_engines_method(:when_container_ready, *args) 90 | end 91 | 92 | # 93 | # Saves all the engines one by one. 94 | # 95 | # @param args [Mixed] Arguments to pass to the `#when_running` methods. 96 | # 97 | # @return void 98 | # 99 | # @api public 100 | # 101 | def when_running(*args) 102 | call_engines_method(:when_running, *args) 103 | end 104 | 105 | # 106 | # Restores all the engines one by one. 107 | # 108 | # @param args [Mixed] Arguments to pass to the `#restore` methods. 109 | # 110 | # @return void 111 | # 112 | # @api public 113 | # 114 | def restore(*args) 115 | call_engines_method(:restore, *args) 116 | end 117 | 118 | protected 119 | 120 | # 121 | # Ensures that there has been chosen at least one engine. 122 | # 123 | # @return void 124 | # 125 | # @raise [Dockerspec::EngineError] Raises this exception when the engine 126 | # list is empty. 127 | # 128 | # @api private 129 | # 130 | def assert_engines! 131 | return unless @engines.empty? 132 | raise EngineError, NO_ENGINES_MESSAGE 133 | end 134 | 135 | # 136 | # Runs the same method on all the engines. 137 | # 138 | # @param method [String, Symbol] The method to run. 139 | # 140 | # @param args [Mixed] Arguments to pass to the methods. 141 | # 142 | # @return void 143 | # 144 | # @api private 145 | # 146 | def call_engines_method(method, *args) 147 | @engines.map { |engine| engine.send(method, *args) } 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /lib/dockerspec/rspec/resources/its_container.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/runner/compose' 21 | require 'dockerspec/runner/config_helpers' 22 | require 'dockerspec/helper/rspec_example_helpers' 23 | require 'dockerspec/exceptions' 24 | 25 | module Dockerspec 26 | module RSpec 27 | module Resources 28 | # 29 | # This generates the object to use within `its_container` calls. 30 | # 31 | class ItsContainer 32 | include Dockerspec::Runner::ConfigHelpers 33 | 34 | # 35 | # A message with description on how to avoid the error when you forget 36 | # specifying the docker container you want to test with Docker Compose. 37 | # 38 | NO_DOCKER_COMPOSE_MESSAGE = <<-EOE 39 | 40 | `its_container` can only be used within a `docker_compose` resource. 41 | 42 | For example: 43 | 44 | describe docker_compose('docker-compose.yml', wait: 30) do 45 | its_container(:mysql) do 46 | # [...] 47 | end 48 | end 49 | 50 | EOE 51 | .freeze 52 | 53 | # 54 | # Constructs a `its_container` object. 55 | # 56 | # @param container_name [String] The name of the container. 57 | # @param compose [Dockerspec::Runner::Compose] The compose object we 58 | # working with. 59 | # 60 | # @return [Dockerspec::RSpec::Resource::ItsContainer] The 61 | # `its_container` object. 62 | # 63 | # @api public 64 | # 65 | def initialize(container_name, compose) 66 | @container_name = container_name 67 | @compose = compose 68 | end 69 | 70 | # 71 | # Restores the testing context. 72 | # 73 | # This is required for tests to run correctly if we are testing 74 | # different containers within the same tests. That is because RSpec has 75 | # two stages, one in which it generates the tests and another in which 76 | # it runs them. 77 | # 78 | # This is called from the `before` block in the 79 | # *lib/dockerspec/runner/base.rb* file: 80 | # 81 | # ```ruby 82 | # RSpec.configure do |c| 83 | # c.before(:each) do 84 | # metadata = RSpec.current_example.metadata 85 | # Dockerspec::Helper::RSpecExampleHelpers 86 | # .restore_rspec_context(metadata) 87 | # end 88 | # end 89 | # ``` 90 | # 91 | # @return void 92 | # 93 | # @api public 94 | # 95 | def restore_rspec_context(opts = nil) 96 | @compose.select_container(@container_name, opts) 97 | @compose.restore_rspec_context 98 | end 99 | 100 | # 101 | # Gets the selected container object. 102 | # 103 | # This method is used in {Dockerspec::Runner::ConfigHelpers} to get 104 | # information from the selected container. 105 | # 106 | # @return [Docker::Container] The container object. 107 | # 108 | # @raise [Dockerspec::RunnerError] When cannot select the container to 109 | # test. 110 | # 111 | # @api public 112 | # 113 | def container 114 | @compose.container 115 | end 116 | 117 | # 118 | # Gets the description for the `its_container` resource. 119 | # 120 | # @return [String] The description. 121 | # 122 | # @api public 123 | # 124 | def to_s 125 | "\"#{@container_name}\" container" 126 | end 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /lib/dockerspec/helper/multiple_sources_description.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'rspec' 21 | require 'rspec/its' 22 | require 'erubis' 23 | require 'dockerspec/docker_gem' 24 | require 'dockerspec/builder/config_helpers' 25 | require 'dockerspec/builder/matchers' 26 | require 'dockerspec/builder/logger' 27 | require 'dockerspec/builder/image_gc' 28 | require 'dockerspec/helper/ci' 29 | 30 | module Dockerspec 31 | module Helper 32 | # 33 | # Methods to generate the correct object description for objects that 34 | # has a source attribute. 35 | # 36 | # Shortens the docker IDs automatically. 37 | # 38 | # Requirements: 39 | # 40 | # - `source` method: Returns the source you are using to generating your 41 | # object. 42 | # - `:@options` attribute: The options array with the configuration 43 | # options, including the source. 44 | # 45 | # Used by the {Dockerspec::Builder} and {Dockerspec::Runner} classes. 46 | # 47 | module MultipleSourcesDescription 48 | # 49 | # Generates a description of the object. 50 | # 51 | # @example 52 | # self.description('Docker Build from') 53 | # #=> "Docker Build from path: \".\"" 54 | # 55 | # @param prefix [String] The prefix to add to the description. 56 | # 57 | # @return [String] The object description. 58 | # 59 | # @api private 60 | # 61 | def description(prefix) 62 | value = @options[source] 63 | desc = send("description_from_#{source}", value) 64 | "#{prefix} #{source.to_s.tr('_', ' ')}: \"#{desc}\"" 65 | end 66 | 67 | protected 68 | 69 | # 70 | # Generates an adequate description of a Docker object description. 71 | # 72 | # Essentially it shortens the docker identifiers. 73 | # 74 | # @example 75 | # self.description_from_docker_object('debian') #=> "debian" 76 | # self.description_from_docker_object('92cc98ab560a92cc98ab560[...]') 77 | # #=> "92cc98ab560a" 78 | # 79 | # @param str [String] The description. 80 | # 81 | # @return [String] The description, shortened if necessary. 82 | # 83 | # @api private 84 | # 85 | def description_from_docker(str) 86 | return str unless str =~ /^[0-9a-f]+$/ 87 | str[0..11] 88 | end 89 | 90 | # 91 | # Generates a description from Docker ID. 92 | # 93 | alias description_from_id description_from_docker 94 | 95 | # 96 | # Generates a description from string. 97 | # 98 | # shortens the string. 99 | # 100 | # @example 101 | # self.description_from_string #=> "FROM nginx:1..." 102 | # 103 | # @return [String] A description. 104 | # 105 | # @api private 106 | # 107 | def description_from_string(str) 108 | len = 12 109 | return str unless str.length > len 110 | "#{str[0..len - 1]}..." # Is this length correct? 111 | end 112 | 113 | # 114 | # Generates a description of a file. 115 | # 116 | # Basically expands the path. 117 | # 118 | # @example 119 | # self.description_from_file("mydir") #=> "mydir" 120 | # 121 | # @return [String] A description. 122 | # 123 | # @api private 124 | # 125 | def description_from_file(str) 126 | File.expand_path(str) 127 | end 128 | 129 | # 130 | # Generates a description of a file. 131 | # 132 | # @example 133 | # self.description_from_template("mydir") #=> "mydir" 134 | # 135 | # @return [String] A description. 136 | # 137 | # @api private 138 | # 139 | alias description_from_path description_from_file 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /lib/dockerspec/runner/serverspec/docker.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'serverspec' 21 | require 'specinfra/backend/docker_lxc' 22 | require 'dockerspec/runner/docker' 23 | require 'dockerspec/runner/serverspec/base' 24 | require 'dockerspec/runner/serverspec/rspec' 25 | 26 | module Dockerspec 27 | module Runner 28 | module Serverspec 29 | # 30 | # Runs a Docker container using [Serverspec](http://serverspec.org/). 31 | # 32 | class Docker < Dockerspec::Runner::Docker 33 | include Base 34 | 35 | # 36 | # Constructs a Docker Serverspec runner class to run Docker images. 37 | # 38 | # @example From a Docker Container Image Tag 39 | # Dockerspec::Runner::Serverspec::Docker.new('myapp') 40 | # #=> # 41 | # 42 | # @example From a Docker Container Image Tag Using Hash Format 43 | # Dockerspec::Runner::Serverspec::Docker.new(tag: 'myapp') 44 | # #=> # 45 | # 46 | # @example From a Running Docker Container ID 47 | # Dockerspec::Runner::Serverspec::Docker.new(id: 'c51f86c28340') 48 | # #=> # 49 | # 50 | # @param opts [String, Hash] The `:tag` or a list of options. 51 | # 52 | # @option opts [String] :tag The Docker image tag name to run. 53 | # @option opts [String] :id The Docker container ID to use instead of 54 | # starting a new container. 55 | # @option opts [Boolean] :rm (calculated) Whether to remove the Docker 56 | # container afterwards. 57 | # @option opts [String] :path The environment `PATH` value of the 58 | # container. 59 | # @option opts [Hash, Array] :env Some `ENV` instructions to add to the 60 | # container. 61 | # @option opts [Symbol, String] :family (calculated) The OS family. 62 | # It's automatically detected by default, but can be used to 63 | # **speed up the tests**. Some possible values: 64 | # `:alpine`, `:arch`, `:coreos`, `:debian`, `:gentoo`, `:nixos`, 65 | # `:plamo`, `:poky`, `:redhat`, `:suse`. 66 | # @option opts [Integer] :wait Time to wait before running the tests. 67 | # @option opts [Symbol] :backend (calculated) Docker backend to use: 68 | # `:docker`, `:lxc`. 69 | # 70 | # @return [Dockerspec::Runner::Serverspec::Docker] Runner object. 71 | # 72 | # @api public 73 | # 74 | def initialize(*opts) 75 | super 76 | calculate_docker_backend_name('docker') 77 | end 78 | 79 | # 80 | # Gets the internal {Docker::Container} object. 81 | # 82 | # @return [Docker::Container] The container. 83 | # 84 | # @api public 85 | # 86 | def container 87 | @cached_container ||= begin 88 | backend = Engine::Specinfra::Backend.new(backend_name) 89 | backend.backend_instance_attribute(:container) 90 | end 91 | end 92 | 93 | protected 94 | 95 | # 96 | # Sets the engines and the Specinfra configuration. 97 | # 98 | # Sets the `:docker_image` or `:docker_container`. 99 | # 100 | # @return void 101 | # 102 | # @api private 103 | # 104 | def before_running 105 | super 106 | Specinfra.configuration.env(options[:env]) if options.key?(:env) 107 | if source == :id 108 | Specinfra.configuration.docker_container(id) 109 | else 110 | Specinfra.configuration.docker_image(image_id) 111 | end 112 | end 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/dockerspec/engine/base.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | module Dockerspec 21 | # 22 | # The classes behind this namespace are Testing Engines. These are the 23 | # frameworks used below to run the tests. For example `Specinfra` (used by 24 | # `Serverspec`). 25 | # 26 | module Engine 27 | # 28 | # A basic class with the minimal skeleton to create a Testing Engine. 29 | # 30 | class Base 31 | # 32 | # Constructs the engine. 33 | # 34 | # Saves the runner and the options. 35 | # 36 | # @param runner [Dockerspec::Runner::Base] The class that is being used 37 | # to run the Docker Containers. 38 | # 39 | # @return [Dockerspec::Engine::Base] The engine. 40 | # 41 | # @api public 42 | # 43 | def initialize(runner) 44 | @runner = runner 45 | end 46 | 47 | # 48 | # Runs the engine setup just before running docker. 49 | # 50 | # Also run when there is a change in the testing target like changing the 51 | # container you want to run the tests into with Docker Compose. 52 | # 53 | # Usually this is implemented to clean configurations from previous tests. 54 | # 55 | # Does nothing by default. 56 | # 57 | # @param args [Mixed] The passed arguments. 58 | # 59 | # @return void 60 | # 61 | # @api public 62 | # 63 | def before_running(*args); end 64 | 65 | # 66 | # Saves the engine status internally after starting the docker container. 67 | # 68 | # Does nothing by default. 69 | # 70 | # @param args [Mixed] The passed arguments. 71 | # 72 | # @return void 73 | # 74 | # @api public 75 | # 76 | def when_running(*args); end 77 | 78 | # 79 | # Runs when the container to test is ready. 80 | # 81 | # This is mainly used with Docker Compose to know when the container to 82 | # test is selected. 83 | # 84 | # Without Docker Compose this is called just after calling 85 | # {#when_running}. 86 | # 87 | # Does nothing by default. 88 | # 89 | # @param args [Mixed] The passed arguments. 90 | # 91 | # @return void 92 | # 93 | # @api public 94 | # 95 | def when_container_ready(*args); end 96 | 97 | # 98 | # Restores the engine internal status after running tests on other 99 | # containers. 100 | # 101 | # This is used to avoid the engine running against the last started 102 | # container if you are testing multiple containers at the same time. 103 | # 104 | # Does nothing by default. 105 | # 106 | # @param args [Mixed] The passed arguments. 107 | # 108 | # @return void 109 | # 110 | # @api public 111 | # 112 | def restore(*args); end 113 | 114 | protected 115 | 116 | # 117 | # Gets the configuration options from the runner. 118 | # 119 | # @return [Hash] Options list. 120 | # 121 | # @api private 122 | # 123 | def options 124 | @runner.options 125 | end 126 | 127 | # 128 | # Gets the Container Name. 129 | # 130 | # @return [String, nil] Container name. 131 | # 132 | # @raise [Dockerspec::RunnerError] When the `#container` method is no 133 | # implemented in the subclass or cannot select the container to test. 134 | # 135 | # @api private 136 | # 137 | def container_name 138 | @runner.container_name 139 | end 140 | 141 | # 142 | # Gets the Docker Container IP address. 143 | # 144 | # @return [String] IP address. 145 | # 146 | # @raise [Dockerspec::RunnerError] When the `#container` method is no 147 | # implemented in the subclass or cannot select the container to test. 148 | # 149 | # @api private 150 | # 151 | def ipaddress 152 | @runner.ipaddress 153 | end 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /spec/unit/engine/specinfra/backend_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | # A dummy Specinfra Backend for unit tests. 23 | class MockSpecinfraBackend 24 | attr_reader :instance 25 | 26 | def self.instance_set(i) 27 | @instance = i 28 | end 29 | end 30 | 31 | describe Dockerspec::Engine::Specinfra::Backend do 32 | shared_examples 'specinfra backend test' do 33 | let(:instance) { MockSpecinfraBackend.new } 34 | subject { described_class.new(backend) } 35 | 36 | context '#save' do 37 | let(:instance) { double('instance') } 38 | before { allow(backend_class).to receive(:instance).and_return(instance) } 39 | 40 | it 'saves the backend' do 41 | subject.save 42 | expect(subject.instance_variable_get(:@saved_backend_instance)) 43 | .to eq instance 44 | end 45 | end 46 | 47 | context '#restore' do 48 | let(:saved_instance) { double('instance') } 49 | before do 50 | subject.instance_variable_set(:@saved_backend_instance, saved_instance) 51 | allow(backend_class).to receive(:instance_set) 52 | allow(backend_class).to receive(:host_reset) 53 | ::Specinfra.configuration.backend = nil 54 | end 55 | 56 | it 'restores the backend' do 57 | expect(backend_class).to receive(:instance_set).with(saved_instance) 58 | subject.restore 59 | end 60 | 61 | it 'resets the host' do 62 | expect(backend_class).to receive(:host_reset) 63 | subject.restore 64 | end 65 | end 66 | 67 | context '#restore_container' do 68 | let(:config) { double('Specinfra::Configuration') } 69 | let(:current_name) { 'current' } 70 | before do 71 | allow(Specinfra).to receive(:configuration).and_return(config) 72 | allow(config).to receive(:docker_compose_container) 73 | .with(no_args).and_return(current_name) 74 | allow(backend_class).to receive(:host_reset) 75 | end 76 | 77 | it 'updates the container name' do 78 | new_name = 'new' 79 | expect(config).to receive(:docker_compose_container).with(new_name) 80 | .once 81 | subject.restore_container(new_name) 82 | end 83 | 84 | it 'does not update if not required' do 85 | expect(config).to_not receive(:docker_compose_container) 86 | .with(current_name) 87 | expect(backend_class).to_not receive(:host_reset) 88 | subject.restore_container(current_name) 89 | end 90 | 91 | it 'resets the host information' do 92 | new_name = 'new' 93 | allow(config).to receive(:docker_compose_container).with(new_name) 94 | expect(backend_class).to receive(:host_reset).once 95 | subject.restore_container(new_name) 96 | end 97 | end 98 | 99 | context '#reset' do 100 | it 'sets the instance to nil' do 101 | expect(backend_class).to receive(:instance_set).with(nil) 102 | subject.reset 103 | end 104 | end 105 | 106 | context '#backend_instance_attribute' do 107 | let(:instance) { double('instance') } 108 | before { allow(backend_class).to receive(:instance).and_return(instance) } 109 | 110 | it 'reads backend instance variable' do 111 | expect(instance).to receive(:instance_variable_get).once.with(:@compose) 112 | subject.backend_instance_attribute('compose') 113 | end 114 | end 115 | end # shared example 116 | 117 | context 'with a string as backend' do 118 | let(:backend) { 'base' } 119 | let(:backend_class) { 'backend_class' } 120 | before do 121 | allow(Specinfra::Backend).to receive(:const_get).with('Base') 122 | .and_return(backend_class) 123 | end 124 | 125 | include_examples 'specinfra backend test' 126 | end 127 | 128 | context 'with a class as backend' do 129 | let(:backend) { Specinfra::Backend::Base } 130 | let(:backend_class) { backend } 131 | 132 | include_examples 'specinfra backend test' 133 | end 134 | 135 | context 'with a class instance as backend' do 136 | let(:backend_class) { Specinfra::Backend::Base } 137 | let(:backend) { backend_class.new } 138 | 139 | include_examples 'specinfra backend test' 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /lib/dockerspec/engine/specinfra/backend.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'specinfra/backend/base' 21 | require 'dockerspec/engine/specinfra/backend_hack' 22 | 23 | module Dockerspec 24 | module Engine 25 | class Specinfra < Base 26 | # 27 | # A class to handle the underlying Specinfra backend. 28 | # 29 | # This class saves Specinfra instance in internally and then it is able 30 | # to recover it from there and setup the running environment accordingly. 31 | # 32 | # This class uses a small hack in the Specinfra class to reset its 33 | # internal singleton instance. 34 | # 35 | class Backend 36 | # 37 | # The Specinfra backend constructor. 38 | # 39 | # @param backend [Symbol, Specinfra::Backend::Base, Class] The backend 40 | # can be the backend name as a symbol, a Specinfra backend object or 41 | # a Specinfra backend class. 42 | # 43 | # @api public 44 | # 45 | def initialize(backend) 46 | @backend = backend 47 | end 48 | 49 | # 50 | # Saves the Specinfra backend instance reference internally. 51 | # 52 | # @return void 53 | # 54 | # @api public 55 | # 56 | def save 57 | @saved_backend_name = ::Specinfra.configuration.backend 58 | @saved_backend_instance = backend_instance 59 | end 60 | 61 | # 62 | # Restores the Specinfra backend instance. 63 | # 64 | # @return void 65 | # 66 | # @api public 67 | # 68 | def restore 69 | backend_class.instance_set(@saved_backend_instance) 70 | if ::Specinfra.configuration.backend != @saved_backend_name 71 | backend_class.host_reset 72 | ::Specinfra.configuration.backend = @saved_backend_name 73 | end 74 | end 75 | 76 | # 77 | # Restores the testing context for a container. 78 | # 79 | # Used with Docker Compose to choose the container to test. 80 | # 81 | # @param container_name [String, Symbol] The name of the container. 82 | # 83 | # @return void 84 | # 85 | # @api public 86 | # 87 | def restore_container(container_name) 88 | current_container_name = 89 | ::Specinfra.configuration.docker_compose_container 90 | return if current_container_name == container_name 91 | ::Specinfra.configuration.docker_compose_container(container_name) 92 | # TODO: Save the host family instead of always reseting it: 93 | backend_class.host_reset 94 | end 95 | 96 | # 97 | # Resets the Specinfra backend. 98 | # 99 | # @return void 100 | # 101 | # @api public 102 | # 103 | def reset 104 | backend_class.instance_set(nil) 105 | end 106 | 107 | # 108 | # Gets the internal attribute value from the Specinfra backend object. 109 | # 110 | # Used mainly to get information from the running containers like their 111 | # name or their IP address. 112 | # 113 | # @return [Mixed] The value of the attribute to read. 114 | # 115 | # @api public 116 | # 117 | def backend_instance_attribute(name) 118 | backend_instance.instance_variable_get("@#{name}".to_sym) 119 | end 120 | 121 | protected 122 | 123 | # 124 | # Returns the current Specinfra backend object. 125 | # 126 | # @return [Specinfra::Backend::Base] The Specinfra backend object. 127 | # 128 | # @api private 129 | # 130 | def backend_instance 131 | backend_class.instance 132 | end 133 | 134 | # 135 | # Returns the current Specinfra backend class. 136 | # 137 | # @return [Class] The Specinfra backend class. 138 | # 139 | # @api private 140 | # 141 | def backend_class 142 | @backend_class ||= begin 143 | return @backend.class if @backend.is_a?(::Specinfra::Backend::Base) 144 | ::Specinfra::Backend.const_get(@backend.to_s.to_camel_case) 145 | end 146 | end 147 | end 148 | end 149 | end 150 | end 151 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG for Dockerspec 2 | 3 | All notable changes to the [`dockerspec`](https://rubygems.org/gems/dockerspec/) RubyGem will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [Unreleased] 8 | [![Travis CI master Build Status](http://img.shields.io/travis/zuazo/dockerspec.svg?style=flat)](https://travis-ci.org/zuazo/dockerspec) 9 | 10 | ## [0.5.0] - 2017-08-30 11 | [![Travis CI 0.5.0 Build Status](http://img.shields.io/travis/zuazo/dockerspec/0.5.0.svg?style=flat)](https://travis-ci.org/zuazo/dockerspec) 12 | 13 | ### Added in 0.5.0 14 | - Support specifying a build path when building images from a string ([issue #13](https://github.com/zuazo/dockerspec/issues/13), thanks [John Meichle](https://github.com/jmeichle)). 15 | 16 | ### Fixed in 0.5.0 17 | - Fix OS detection with Specinfra `2.71`. 18 | - README: Fix small grammar error. 19 | 20 | ## [0.4.1] - 2017-03-21 21 | [![Travis CI 0.4.1 Build Status](http://img.shields.io/travis/zuazo/dockerspec/0.4.1.svg?style=flat)](https://travis-ci.org/zuazo/dockerspec) 22 | 23 | ### Fixed in 0.4.1 24 | - README: Fix latest dockerspec version in the instructions. 25 | 26 | ## [0.4.0] - 2017-03-20 27 | [![Travis CI 0.4.0 Build Status](http://img.shields.io/travis/zuazo/dockerspec/0.4.0.svg?style=flat)](https://travis-ci.org/zuazo/dockerspec) 28 | 29 | ### Added in 0.4.0 30 | - Integrate with [rspec-retry](https://github.com/NoRedInk/rspec-retry) gem. 31 | - Docker logs support ([issue #3](https://github.com/zuazo/dockerspec/issues/3), thanks [@axi43](https://github.com/axi43) for the idea). 32 | 33 | ### Changed in 0.4.0 34 | - Let user choose RSpec formatter ([issue #4](https://github.com/zuazo/dockerspec/issues/4), thanks [Luis Sagastume](https://github.com/zuazo/dockerspec/pull/4) for the help). 35 | 36 | ### Removed in 0.4.0 37 | - Drop Ruby `< 2.2` support. 38 | 39 | ### Fixed in 0.4.0 40 | - Be able to use os detection within test blocks ([issue #2](https://github.com/zuazo/dockerspec/issues/2), **special thanks to [Nan Liu](https://github.com/nanliu)** for his help and [his astonishing presentation](https://www.slideshare.net/NanLiu1/trust-but-verify-testing-with-docker-containers)). 41 | - Use `Integer` instead of `Fixnum`. 42 | 43 | ### Improved in 0.4.0 44 | - `ItsContainer`: rename container_name variable to avoid confussion. 45 | 46 | ### Documentation Changes in 0.4.0 47 | - Document `dir` parameter in `Builder#build_from_string`. 48 | - CHANGELOG: Follow "Keep a CHANGELOG". 49 | - Add GitHub templates. 50 | - README: 51 | - Document how to require the gem. 52 | - Add Presentations section. 53 | - Add a TOC. 54 | - Add documentation links. 55 | 56 | ## [0.3.0] - 2016-02-28 57 | [![Travis CI 0.3.0 Build Status](http://img.shields.io/travis/zuazo/dockerspec/0.3.0.svg?style=flat)](https://travis-ci.org/zuazo/dockerspec) 58 | 59 | ### Breaking Changes in 0.3.0 60 | - Enable `options[:rm]` by default. 61 | 62 | ### Added in 0.3.0 63 | - Add Docker Compose support. 64 | - Add Infrataster and Capybara support. 65 | - Add `:wait` option to `docker_run` and `docker_compose`. 66 | - Add `described_image` helper for `docker_run`. 67 | - Support integer values with `have_expose` matcher. 68 | - Make `require 'dockerspec'` optional. 69 | - Add support for multiple testing engines. 70 | - Add a `Configuration` class to setup the internal modularization. 71 | 72 | ### Fixed in 0.3.0 73 | - Fix `:env` in `docker_run` with Serverspec. 74 | - Fix *Must have id* error when building images from IDs with tags. 75 | 76 | ### Improved in 0.3.0 77 | - Update RuboCop to `0.37`, fix new offenses. 78 | - `Runner` classes split into `Engine::Base` and `Runner::Base`. 79 | - Rename many classes. 80 | 81 | ### Documentation Changes in 0.3.0 82 | - README: 83 | - Move the documentation below examples. 84 | - Add many examples. 85 | 86 | ## [0.2.0] - 2015-12-11 87 | [![Travis CI 0.2.0 Build Status](http://img.shields.io/travis/zuazo/dockerspec/0.2.0.svg?style=flat)](https://travis-ci.org/zuazo/dockerspec) 88 | 89 | ### Added in 0.2.0 90 | - Print Docker errors in a more readable format. 91 | 92 | ### Changed in 0.2.0 93 | - Set some opinionated RSpec configurations. 94 | 95 | ### Fixed in 0.2.0 96 | - Fix *undefined method* error in the outermost examples. 97 | 98 | ### Documentation Changes in 0.2.0 99 | - Add examples for `#have_cmd` using string format. 100 | - README: 101 | - Improve Ruby documentation. 102 | - Change gem badge to point to RubyGems. 103 | - Add Real-world examples section. 104 | 105 | ## 0.1.0 - 2015-12-09 106 | [![Travis CI 0.1.0 Build Status](http://img.shields.io/travis/zuazo/dockerspec/0.1.0.svg?style=flat)](https://travis-ci.org/zuazo/dockerspec) 107 | 108 | - Initial release of `dockerspec`. 109 | 110 | [Unreleased]: https://github.com/zuazo/dockerspec/compare/0.5.0...HEAD 111 | [0.5.0]: https://github.com/zuazo/dockerspec/compare/0.4.1...0.5.0 112 | [0.4.1]: https://github.com/zuazo/dockerspec/compare/0.4.0...0.4.1 113 | [0.4.0]: https://github.com/zuazo/dockerspec/compare/0.3.0...0.4.0 114 | [0.3.0]: https://github.com/zuazo/dockerspec/compare/0.2.0...0.3.0 115 | [0.2.0]: https://github.com/zuazo/dockerspec/compare/0.1.0...0.2.0 116 | -------------------------------------------------------------------------------- /lib/dockerspec/configuration.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'dockerspec/runner/docker' 21 | require 'dockerspec/runner/compose' 22 | 23 | module Dockerspec 24 | # 25 | # Saves internal configuration for {Dockerspec}. 26 | # 27 | # - The test engines to Run: Specinfra, ... 28 | # - The internal library used to run Docker. 29 | # 30 | class Configuration 31 | # 32 | # The {Dockerspec::Runner} class used to run Docker. 33 | # 34 | # @return [Class] The {Dockerspec::Runner::Base} class. 35 | # 36 | attr_accessor :docker_runner 37 | 38 | # 39 | # The {Dockerspec::Runner::Compose} class used to run Docker Compose. 40 | # 41 | # @return [Class] The {Dockerspec::Runner::Compose} class. 42 | # 43 | attr_accessor :compose_runner 44 | 45 | # 46 | # A list of test engines used to run the tests. 47 | # 48 | # @return [Array] A list of {Dockerspec::Engine::Base} classes. 49 | # 50 | attr_reader :engines 51 | 52 | class << self 53 | # 54 | # Adds a class to use as engine to run the tests. 55 | # 56 | # @example 57 | # Dockerspec.Configuration.add_engine Dockerspec::Engine::Specinfra 58 | # 59 | # @param engine [Class] A {Dockerspec::Engine::Base} subclass. 60 | # 61 | # @return void 62 | # 63 | # @api public 64 | # 65 | def add_engine(engine) 66 | instance.add_engine(engine) 67 | end 68 | 69 | # 70 | # Gets the engine classes used to run the tests. 71 | # 72 | # @return [Array] A list of {Dockerspec::Engine::Base} subclasses. 73 | # 74 | # @api public 75 | # 76 | def engines 77 | instance.engines 78 | end 79 | 80 | # 81 | # Sets the class used to create and start Docker Containers. 82 | # 83 | # @example 84 | # Dockerspec.Configuration.docker_runner = Dockerspec::Runner::Docker 85 | # 86 | # @param runner [Class] A {Dockerspec::Runner::Base} subclass. 87 | # 88 | # @return void 89 | # 90 | # @api public 91 | # 92 | def docker_runner=(runner) 93 | instance.docker_runner = runner 94 | end 95 | 96 | # 97 | # Gets the class used to create and start Docker Containers. 98 | # 99 | # @return [Class] A {Dockerspec::Runner::Base} subclass. 100 | # 101 | # @api public 102 | # 103 | def docker_runner 104 | instance.docker_runner 105 | end 106 | 107 | # 108 | # Sets the class used to start Docker Compose. 109 | # 110 | # @example 111 | # Dockerspec.Configuration.compose_runner = Dockerspec::Runner::Compose 112 | # 113 | # @param runner [Class] A {Dockerspec::Runner::Compose::Base} subclass. 114 | # 115 | # @return void 116 | # 117 | # @api public 118 | # 119 | def compose_runner=(runner) 120 | instance.compose_runner = runner 121 | end 122 | 123 | # 124 | # Gets the class used to start Docker Compose. 125 | # 126 | # @return [Class] A {Dockerspec::Runner::Compose::Base} subclass. 127 | # 128 | # @api public 129 | # 130 | def compose_runner 131 | instance.compose_runner 132 | end 133 | 134 | # 135 | # Resets the internal Configuration singleton object. 136 | # 137 | # @return void 138 | # 139 | # @api public 140 | # 141 | def reset 142 | @instance = nil 143 | end 144 | 145 | protected 146 | 147 | # 148 | # Creates the Configuration singleton instance. 149 | # 150 | # @return void 151 | # 152 | # @api private 153 | # 154 | def instance 155 | @instance ||= new 156 | end 157 | end 158 | 159 | # 160 | # Adds a class to use as engine to run the tests. 161 | # 162 | # @param engine [Class] A {Dockerspec::Engine::Base} subclass. 163 | # 164 | # @return void 165 | # 166 | # @api private 167 | # 168 | def add_engine(engine) 169 | @engines << engine 170 | @engines.uniq! 171 | end 172 | 173 | protected 174 | 175 | # 176 | # Constructs a configuration object. 177 | # 178 | # @return [Dockerspec::Configuretion] The configuration object. 179 | # 180 | # @api private 181 | # 182 | def initialize 183 | @docker_runner = Runner::Docker 184 | @compose_runner = Runner::Compose 185 | @engines = [] 186 | end 187 | end 188 | end 189 | -------------------------------------------------------------------------------- /lib/dockerspec/docker_exception_parser.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'json' 21 | require 'dockerspec/exceptions' 22 | 23 | module Dockerspec 24 | # 25 | # A class to parse `Docker::Error` exceptions. 26 | # 27 | class DockerExceptionParser 28 | # 29 | # Parses Docker exceptions. 30 | # 31 | # Raises the same exception if the format is unknown. 32 | # 33 | # @example 34 | # rescue ::Docker::Error::DockerError => e 35 | # DockerExceptionParser.new(e) 36 | # end 37 | # 38 | # @param e [Exception] The exception object to parse. 39 | # 40 | # @raise [Dockerspec::DockerError] When the exception format is known. 41 | # @raise [Exception] When the exception format is unknown. 42 | # 43 | # @api public 44 | # 45 | def initialize(e) 46 | e_ary = parse_exception(e) 47 | raise_docker_error_exception(e_ary) 48 | raise e 49 | end 50 | 51 | protected 52 | 53 | # 54 | # Parses the exception JSON message. 55 | # 56 | # The message must be a list of JSON messages merged by a new line. 57 | # 58 | # A valid exception message example: 59 | # 60 | # ``` 61 | # {"stream":"Step 1 : FROM alpine:3.2\n"} 62 | # {"stream":" ---\u003e d6ead20d5571\n"} 63 | # {"stream":"Step 2 : RUN apk add --update wrong-package-name\n"} 64 | # {"stream":" ---\u003e Running in 290a46fa8bf4\n"} 65 | # {"stream":"fetch http://dl-4.alpinelinux.org/alpine/v3.2/main/...\n"} 66 | # {"stream":"ERROR: unsatisfiable constraints:\n"} 67 | # {"stream":" wrong-package-name (missing):\n required by: world...\n"} 68 | # {"errorDetail":{"message":"The command ..."},"error":"The command ..."} 69 | # ``` 70 | # 71 | # @example 72 | # self.parse_exception(e) 73 | # #=> [{ "stream" => "Step 1 : FROM alpine:3.2\n" }, "errorDetail" => ... 74 | # 75 | # @param e [Exception] The exception object to parse. 76 | # 77 | # @return [Array] The list of JSON messages parsed. 78 | # 79 | # @return 80 | # 81 | # @api private 82 | # 83 | def parse_exception(e) 84 | msg = e.to_s 85 | json = msg.to_s.sub(/^Couldn't find id: /, '').split("\n").map(&:chomp) 86 | json.map { |str| JSON.parse(str) } 87 | rescue JSON::ParserError 88 | raise e 89 | end 90 | 91 | # 92 | # Gets the error message from the *errorDetail* field. 93 | # 94 | # @param e_ary [Array] The list of JSON messages already parsed. 95 | # 96 | # @return [String] The error message string. 97 | # 98 | # @api private 99 | # 100 | def parse_error_detail(e_ary) 101 | e_detail = e_ary.select { |x| x.is_a?(Hash) && x.key?('errorDetail') }[0] 102 | return nil unless e_detail.is_a?(Hash) 103 | return e_detail['message'] if e_detail.key?('message') 104 | return e_detail['error'] if e_detail.key?('error') 105 | end 106 | 107 | # 108 | # Gets all the console output from the stream logs. 109 | # 110 | # @param e_ary [Array] The list of JSON messages already parsed. 111 | # 112 | # @return [String] The generated stdout output. 113 | # 114 | # @api private 115 | # 116 | def parse_streams(e_ary) 117 | e_ary.map { |x| x.is_a?(Hash) && x['stream'] }.compact.join 118 | end 119 | 120 | # 121 | # Generates a formated error message. 122 | # 123 | # @param error [String] The error message. 124 | # @param output [String] The generated stdout output. 125 | # 126 | # @return [String] The resulting error message. 127 | # 128 | # @api private 129 | # 130 | def generate_error_message(error, output) 131 | [ 132 | "#{error}\n", 133 | "OUTPUT: \n#{output.gsub(/^/, ' ' * 8)}", 134 | "ERROR: #{error}\n\n" 135 | ].join("\n") 136 | end 137 | 138 | # 139 | # Raises the right {Dockerspec::DockerError} exception. 140 | # 141 | # Nothing is raised if the exception format is unknown. 142 | # 143 | # @param e_ary [Array] The list of JSON messages already parsed. 144 | # 145 | # @return void 146 | # 147 | # @raise [Dockerspec::DockerError] When the exception format is known. 148 | # 149 | # @api private 150 | # 151 | def raise_docker_error_exception(e_ary) 152 | e_ary.select { |x| x.is_a?(Hash) && x.key?('errorDetail') }[0] 153 | output = parse_streams(e_ary) 154 | error_msg = parse_error_detail(e_ary) 155 | return if error_msg.nil? 156 | raise DockerError, generate_error_message(error_msg, output) 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /spec/unit/builder/config_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | class TestDockerspecBuilderHelpers 23 | include Dockerspec::Builder::ConfigHelpers 24 | 25 | def initialize(image) 26 | @image = image 27 | end 28 | end 29 | 30 | describe Dockerspec::Builder::ConfigHelpers do 31 | let(:json_config) { {} } 32 | let(:json) { { 'Config' => json_config } } 33 | let(:image) { double('Docker::Image', json: json) } 34 | subject { TestDockerspecBuilderHelpers.new(image) } 35 | 36 | context '#image_config' do 37 | it 'returns JSON configuration' do 38 | expect(subject.image_config).to equal(json_config) 39 | end 40 | end 41 | 42 | context '#size' do 43 | it 'returns image size' do 44 | json['VirtualSize'] = 999 45 | expect(subject.size).to eq 999 46 | end 47 | end 48 | 49 | context '#architecture' do 50 | it 'returns architecture' do 51 | json['Architecture'] = 'amd64' 52 | expect(subject.architecture).to eq 'amd64' 53 | end 54 | end 55 | 56 | context '#arch' do 57 | it 'returns architecture' do 58 | json['Architecture'] = 'amd64' 59 | expect(subject.arch).to eq 'amd64' 60 | end 61 | end 62 | 63 | context '#os' do 64 | it 'returns OS' do 65 | json['Os'] = 'linux' 66 | expect(subject.os).to eq 'linux' 67 | end 68 | end 69 | 70 | context '#maintainer' do 71 | it 'returns author' do 72 | json['Author'] = 'John Doe' 73 | expect(subject.maintainer).to eq 'John Doe' 74 | end 75 | end 76 | 77 | context '#cmd' do 78 | it 'returns CMD' do 79 | json_config['Cmd'] = ['/bin/sh'] 80 | expect(subject.cmd).to eq ['/bin/sh'] 81 | end 82 | end 83 | 84 | context '#labels' do 85 | it 'returns LABELs' do 86 | json_config['Labels'] = { 'testing' => 'docker' } 87 | expect(subject.labels).to eq('testing' => 'docker') 88 | end 89 | end 90 | 91 | context '#label' do 92 | it 'returns the first LABEL' do 93 | json_config['Labels'] = { 'testing' => 'docker', 'serverspec' => 'true' } 94 | expect(subject.label).to eq 'testing=docker' 95 | end 96 | end 97 | 98 | context '#exposes' do 99 | it 'returns EXPOSEs' do 100 | json_config['ExposedPorts'] = { '80/tcp' => {}, '443/tcp' => {} } 101 | expect(subject.exposes).to eq %w(80 443) 102 | end 103 | end 104 | 105 | context '#expose' do 106 | it 'returns the first EXPOSE' do 107 | json_config['ExposedPorts'] = { '80/tcp' => {}, '443/tcp' => {} } 108 | expect(subject.expose).to eq '80' 109 | end 110 | end 111 | 112 | context '#envs' do 113 | it 'returns ENVs' do 114 | json_config['Env'] = %w( 115 | PATH=/usr/sbin:/usr/bin:/sbin:/bin 116 | container=docker 117 | ) 118 | expect(subject.envs).to eq( 119 | 'PATH' => '/usr/sbin:/usr/bin:/sbin:/bin', 120 | 'container' => 'docker' 121 | ) 122 | end 123 | end 124 | 125 | context '#env' do 126 | it 'returns ENVs' do 127 | json_config['Env'] = %w( 128 | PATH=/usr/sbin:/usr/bin:/sbin:/bin 129 | container=docker 130 | ) 131 | expect(subject.env).to eq( 132 | 'PATH' => '/usr/sbin:/usr/bin:/sbin:/bin', 133 | 'container' => 'docker' 134 | ) 135 | end 136 | end 137 | 138 | context '#entrypoing' do 139 | it 'returns ENTRYPOINT' do 140 | json_config['Entrypoint'] = ['sleep'] 141 | expect(subject.entrypoint).to eq ['sleep'] 142 | end 143 | end 144 | 145 | context '#volumes' do 146 | it 'returns VOLUMEs' do 147 | json_config['Volumes'] = { '/var/tmp' => {}, '/tmp' => {} } 148 | expect(subject.volumes).to eq %w(/var/tmp /tmp) 149 | end 150 | end 151 | 152 | context '#volume' do 153 | it 'returns the first VOLUME' do 154 | json_config['Volumes'] = { '/var/tmp' => {}, '/tmp' => {} } 155 | expect(subject.volume).to eq '/var/tmp' 156 | end 157 | end 158 | 159 | context '#user' do 160 | it 'returns USER' do 161 | json_config['User'] = 'nobody' 162 | expect(subject.user).to eq 'nobody' 163 | end 164 | end 165 | 166 | context '#workdir' do 167 | it 'returns WORKDIR' do 168 | json_config['WorkingDir'] = '/tmp' 169 | expect(subject.workdir).to eq '/tmp' 170 | end 171 | end 172 | 173 | context '#onbuilds' do 174 | it 'returns ONBUILDs' do 175 | json_config['OnBuild'] = ['RUN echo onbuild'] 176 | expect(subject.onbuilds).to eq ['RUN echo onbuild'] 177 | end 178 | end 179 | 180 | context '#onbuild' do 181 | it 'returns the first ONBUILD' do 182 | json_config['OnBuild'] = ['RUN echo onbuild', 'RUN sleep 2'] 183 | expect(subject.onbuild).to eq 'RUN echo onbuild' 184 | end 185 | end 186 | 187 | context '#stopsignal' do 188 | it 'returns STOPSIGNAL' do 189 | json_config['StopSignal'] = 'SIGTERM' 190 | expect(subject.stopsignal).to eq 'SIGTERM' 191 | end 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /spec/integration/builder/matchers_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Builder::Matchers do 23 | context docker_build(path: fixture_dir) do 24 | context ':string predicate type' do 25 | context 'failure message' do 26 | let(:test) { expect(subject).to have_maintainer 'who?' } 27 | 28 | it 'contains the matcher name' do 29 | expect { test }.to raise_error(/expected `MAINTAINER`/) 30 | end 31 | 32 | it 'contains the expected value' do 33 | expect { test }.to raise_error(/to match `"who\?"`/) 34 | end 35 | 36 | it 'contains the actual value' do 37 | expect { test }.to raise_error(/got `"John Doe/) 38 | end 39 | end 40 | 41 | context 'failure message when negated' do 42 | let(:test) { expect(subject).to_not have_maintainer(/John/) } 43 | 44 | it 'contains the matcher name' do 45 | expect { test }.to raise_error(/expected `MAINTAINER`/) 46 | end 47 | 48 | it 'contains the "not" expected value' do 49 | expect { test }.to raise_error(%r{not to match `/John/`}) 50 | end 51 | 52 | it 'contains the actual value' do 53 | expect { test }.to raise_error(/got `"John Doe/) 54 | end 55 | end 56 | end # context :string predicate type 57 | 58 | context ':json predicate type' do 59 | context 'failure message' do 60 | let(:test) { expect(subject).to have_cmd %w(badcmd) } 61 | 62 | it 'contains the matcher name' do 63 | expect { test }.to raise_error(/expected `CMD`/) 64 | end 65 | 66 | it 'contains the expected value' do 67 | expect { test }.to raise_error(/to be `\["badcmd"\]`/) 68 | end 69 | 70 | it 'contains the actual value' do 71 | expect { test }.to raise_error(/got `\["2", "2000"\]`/) 72 | end 73 | end 74 | 75 | context 'failure message when negated' do 76 | let(:test) { expect(subject).to_not have_cmd %w(2 2000) } 77 | 78 | it 'contains the matcher name' do 79 | expect { test }.to raise_error(/expected `CMD`/) 80 | end 81 | 82 | it 'contains the "not" expected value' do 83 | expect { test }.to raise_error(/not to be `\["2", "2000"\]`/) 84 | end 85 | 86 | it 'contains the actual value' do 87 | expect { test }.to raise_error(/got `\["2", "2000"\]`/) 88 | end 89 | end 90 | end # context :json predicate type 91 | 92 | context ':array predicate type' do 93 | context 'failure message' do 94 | let(:test) { expect(subject).to have_expose '90' } 95 | 96 | it 'contains the matcher name' do 97 | expect { test }.to raise_error(/expected `EXPOSE`/) 98 | end 99 | 100 | it 'contains the expected value' do 101 | expect { test }.to raise_error(/to include `"90"`/) 102 | end 103 | 104 | it 'contains the actual value' do 105 | expect { test }.to raise_error(/got `\["80"\]`/) 106 | end 107 | end 108 | 109 | context 'failure message when negated' do 110 | let(:test) { expect(subject).to_not have_expose '80' } 111 | 112 | it 'contains the matcher name' do 113 | expect { test }.to raise_error(/expected `EXPOSE`/) 114 | end 115 | 116 | it 'contains the "not" expected value' do 117 | expect { test }.to raise_error(/not to include `"80"`/) 118 | end 119 | 120 | it 'contains the actual value' do 121 | expect { test }.to raise_error(/got `\["80"\]`/) 122 | end 123 | end 124 | end # context :array predicate type 125 | 126 | context ':hash predicate type' do 127 | context 'failure message' do 128 | let(:test) { expect(subject).to have_label('badlabel' => 'badval') } 129 | 130 | it 'contains the matcher name' do 131 | expect { test }.to raise_error(/expected `LABEL`/) 132 | end 133 | 134 | it 'contains the expected value' do 135 | expect { test } 136 | .to raise_error(/to contain `{"badlabel"=>"badval"}`/) 137 | end 138 | 139 | it 'contains the actual value' do 140 | expect { test }.to raise_error(/got `{"description"=>"My /) 141 | end 142 | end 143 | 144 | context 'failure message when negated' do 145 | let(:test) do 146 | expect(subject).to_not have_label('description' => 'My Container') 147 | end 148 | 149 | it 'contains the matcher name' do 150 | expect { test }.to raise_error(/expected `LABEL`/) 151 | end 152 | 153 | it 'contains the "not" expected value' do 154 | expect { test } 155 | .to raise_error(/not to contain `{"description"=>"My Container"}`/) 156 | end 157 | 158 | it 'contains the actual value' do 159 | expect { test }.to raise_error(/got `{"description"=>"My /) 160 | end 161 | end 162 | end # context :hash predicate type 163 | end # docker build 164 | end 165 | -------------------------------------------------------------------------------- /spec/unit/runner/compose_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'spec_helper' 21 | 22 | describe Dockerspec::Runner::Compose do 23 | let(:file) { 'compose-file.yml' } 24 | let(:opts) { { file: file } } 25 | subject { described_class.new(opts) } 26 | let(:engines) { double('Dockerspec::EngineList') } 27 | let(:container_name) { 'webapp' } 28 | let(:container) { double('Docker::Container') } 29 | let(:compose_container) { double('ComposeContainer', container: container) } 30 | let(:containers) { { container_name => compose_container } } 31 | let(:compose) { double('DockerCompose', containers: containers) } 32 | before { stub_runner_compose(file, compose, engines) } 33 | 34 | context '.new' do 35 | it 'sets the .current_instance variable' do 36 | expect(Dockerspec::Runner::Compose).to receive(:current_instance=).once 37 | subject 38 | end 39 | 40 | it 'reads the configuration file' do 41 | expect(DockerCompose).to receive(:load).once.with(file) 42 | .and_return(compose) 43 | subject 44 | end 45 | 46 | it 'raises an error without file' do 47 | opts.delete(:file) 48 | expect { subject }.to raise_error(/`:file`/) 49 | end 50 | 51 | context 'when passing a directory' do 52 | before do 53 | allow(File).to receive(:directory?).with(file).and_return(true) 54 | end 55 | 56 | it 'reads the `docker-compose.yml` file' do 57 | real_file = File.join(file, 'docker-compose.yml') 58 | expect(DockerCompose).to receive(:load).once.with(real_file) 59 | .and_return(compose) 60 | subject 61 | end 62 | end 63 | end 64 | 65 | context '#select_container' do 66 | it 'selects the container' do 67 | subject.select_container(container_name) 68 | end 69 | 70 | it 'accepts options' do 71 | subject.select_container(container_name, opt1: 'val1') 72 | end 73 | 74 | it 'sets the engines as ready' do 75 | expect(engines).to receive(:when_container_ready).once 76 | subject.select_container(container_name) 77 | end 78 | end 79 | 80 | context '#options' do 81 | it 'returns options' do 82 | expect(subject.options).to be_a Hash 83 | end 84 | 85 | it 'returns container options if set' do 86 | opts = { opt1: 'val1' } 87 | subject.select_container(container_name, opts) 88 | expect(subject.options).to include opts 89 | end 90 | 91 | it 'can read docker compose wait from RSpec configuration' do 92 | wait = 30 93 | RSpec.configuration.docker_wait = wait 94 | expect(subject.options[:wait]).to eq wait 95 | RSpec.configuration.docker_wait = false 96 | end 97 | end 98 | 99 | context '#to_s' do 100 | it 'contains a description' do 101 | expect(subject.to_s).to include 'Docker Compose' 102 | end 103 | 104 | it 'contains the file path' do 105 | expect(subject.to_s).to include file 106 | end 107 | end 108 | 109 | context '#run' do 110 | context 'with docker wait set' do 111 | let(:docker_wait) { 10 } 112 | let(:time) { Time.new.utc } 113 | before do 114 | allow(Time).to receive(:new).and_return(time) 115 | opts[:wait] = docker_wait 116 | end 117 | 118 | it 'sleeps' do 119 | expect(subject).to receive(:sleep).once 120 | subject.run 121 | end 122 | 123 | context 'when compose start takes longer than wait' do 124 | before do 125 | allow(Time).to receive(:new).and_return(time, time + docker_wait + 1) 126 | end 127 | 128 | it 'does not sleep by default' do 129 | expect(subject).to_not receive(:sleep) 130 | subject 131 | end 132 | end 133 | end 134 | end 135 | 136 | context '#id' do 137 | let(:id) { 'b98ffa2251d3' } 138 | before { allow(container).to receive(:id).and_return(id) } 139 | 140 | it 'returns the id' do 141 | subject.select_container(container_name) 142 | expect(subject.id).to eq(id) 143 | end 144 | 145 | it 'raises an error when there is no container selected' do 146 | expect { subject.id } 147 | .to raise_error Dockerspec::RunnerError, /`its_container`/ 148 | end 149 | 150 | it 'raises an error when the container is not found' do 151 | allow(compose).to receive(:containers).and_return({}) 152 | subject.select_container(container_name) 153 | expect { subject.id } 154 | .to raise_error Dockerspec::RunnerError, /Container not found/ 155 | end 156 | end 157 | 158 | context '#finalize' do 159 | context 'with rm disabled' do 160 | before { opts[:rm] = false } 161 | 162 | it 'does nothing if :rm disabled' do 163 | expect(compose).to_not receive(:stop) 164 | expect(compose).to_not receive(:delete) 165 | subject.finalize 166 | end 167 | end 168 | 169 | context 'with rm enabled' do 170 | before { opts[:rm] = true } 171 | 172 | it 'stops compose' do 173 | expect(compose).to receive(:stop).once 174 | subject.finalize 175 | end 176 | 177 | it 'deletes compose' do 178 | expect(compose).to receive(:delete).once 179 | subject.finalize 180 | end 181 | end 182 | end 183 | end 184 | -------------------------------------------------------------------------------- /lib/dockerspec/runner/serverspec/base.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # Author:: Xabier de Zuazo () 4 | # Copyright:: Copyright (c) 2015-2016 Xabier de Zuazo 5 | # License:: Apache License, Version 2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | require 'serverspec' 21 | require 'dockerspec/runner/docker' 22 | require 'dockerspec/helper/docker' 23 | require 'dockerspec/docker_exception_parser' 24 | require 'dockerspec/runner/serverspec/rspec' 25 | 26 | # 27 | # Silence error: No backend type is specified. Fall back to :exec type. 28 | # 29 | Specinfra.configuration.backend(:base) 30 | 31 | module Dockerspec 32 | module Runner 33 | # 34 | # Contains the classes used to start docker containers using Serverspec. 35 | # 36 | module Serverspec 37 | # 38 | # Base class to be included by Serverspec runners. 39 | # 40 | # @example 41 | # module Dockerspec 42 | # module Runner 43 | # module Serverspec 44 | # class MyRunner 45 | # include Base 46 | # end 47 | # end 48 | # end 49 | # end 50 | # 51 | module Base 52 | # 53 | # The Specinfra backend name to use. 54 | # 55 | # @return [Symbol] The backend name. 56 | # 57 | attr_reader :backend_name 58 | 59 | # 60 | # Stops and deletes the Docker Container. 61 | # 62 | # Actually does nothing. Do no delete anything, lets Specinfra do that. 63 | # 64 | # @return void 65 | # 66 | # @api public 67 | # 68 | def finalize 69 | # Do not stop the container 70 | end 71 | 72 | # 73 | # Generates a description of the object. 74 | # 75 | # @example Running Against a Container Image Tag 76 | # self.description #=> "Serverspec on tag: \"debian\"" 77 | # 78 | # @example Running Against a Running Container ID 79 | # self.description #=> "Serverspec on id: \"92cc98ab560a\"" 80 | # 81 | # @return [String] The object description. 82 | # 83 | # @api private 84 | # 85 | def to_s 86 | description('Serverspec on') 87 | end 88 | 89 | protected 90 | 91 | # 92 | # Gets the default options configured using `RSpec.configuration`. 93 | # 94 | # @example 95 | # self.rspec_options #=> { :family => :debian } 96 | # 97 | # @return [Hash] The configuration options. 98 | # 99 | # @api private 100 | # 101 | def rspec_options 102 | config = ::RSpec.configuration 103 | super.tap do |opts| 104 | opts[:family] = config.family if config.family? 105 | end 106 | end 107 | 108 | # 109 | # Generates the correct Specinfra backend name to use from a name. 110 | # 111 | # @example 112 | # self.generate_docker_backend_name(:docker, :docker) #=> :docker 113 | # self.generate_docker_backend_name(:lxc, :docker) #=> :docker_lxc 114 | # self.generate_docker_backend_name(:docker_lxc, :docker) 115 | # #=> :docker_lxc 116 | # self.generate_docker_backend_name(:native, :docker) #=> :docker 117 | # 118 | # @param name [String, Symbol] The backend short (without the `docker` 119 | # prefix) or long name. 120 | # @param prefix [Symbol, String] The prefix to use: `:docker` or 121 | # `:docker_compose`. 122 | # 123 | # @return [Symbol] The backend name. 124 | # 125 | # @api private 126 | # 127 | def generate_docker_backend_name(name, prefix) 128 | return name.to_s.to_sym if name.to_s.start_with?(prefix) 129 | return prefix.to_sym if name.to_s.to_sym == :native 130 | "#{prefix}_#{name}".to_sym 131 | end 132 | 133 | # 134 | # Calculates and saves the correct docker Specinfra backend to use on 135 | # the system. 136 | # 137 | # Returns the LXC driver instead of the native driver when required. 138 | # 139 | # Reads the driver from the configuration options if set. 140 | # 141 | # @example Docker with Native Execution Driver 142 | # self.calculate_docker_backend_name(:docker) #=> :docker 143 | # 144 | # @example Docker with LXC Execution Driver 145 | # self.calculate_docker_backend_name(:docker) #=> :docker_lxc 146 | # 147 | # @example Compose with LXC Execution Driver 148 | # self.calculate_docker_backend_name(:compose) #=> :docker_compose_lxc 149 | # 150 | # @param prefix [Symbol, String] The prefix to use: `:docker` or 151 | # `:docker_compose`. 152 | # 153 | # @return [Symbol] The backend name. 154 | # 155 | # @api private 156 | # 157 | def calculate_docker_backend_name(prefix) 158 | @backend_name = 159 | if options.key?(:backend) 160 | generate_docker_backend_name(options[:backend], prefix) 161 | elsif Helper::Docker.lxc_execution_driver? 162 | "#{prefix}_lxc".to_sym 163 | else 164 | prefix.to_sym 165 | end 166 | end 167 | 168 | # 169 | # Starts the Docker container. 170 | # 171 | # @return void 172 | # 173 | # @raise [Dockerspec::DockerError] For underlaying docker errors. 174 | # 175 | # @api private 176 | # 177 | def run_container 178 | Specinfra.configuration.backend(@backend_name) 179 | rescue ::Docker::Error::DockerError => e 180 | DockerExceptionParser.new(e) 181 | end 182 | end 183 | end 184 | end 185 | end 186 | --------------------------------------------------------------------------------