├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yaml ├── .gitignore ├── .markdownlint.yaml ├── .rubocop.yml ├── .yamllint ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── docker.ps1 ├── kitchen-docker.gemspec ├── kitchen.windows.yml ├── kitchen.yml ├── lib ├── docker │ └── version.rb ├── kitchen │ ├── docker │ │ ├── container.rb │ │ ├── container │ │ │ ├── linux.rb │ │ │ └── windows.rb │ │ ├── docker_version.rb │ │ ├── erb_context.rb │ │ └── helpers │ │ │ ├── cli_helper.rb │ │ │ ├── container_helper.rb │ │ │ ├── dockerfile_helper.rb │ │ │ ├── file_helper.rb │ │ │ ├── image_helper.rb │ │ │ └── inspec_helper.rb │ ├── driver │ │ └── docker.rb │ └── transport │ │ └── docker.rb └── train │ └── docker.rb ├── renovate.json └── test ├── Dockerfile ├── integration ├── capabilities │ └── disabled │ │ └── capabilities_drop_spec.rb ├── default │ └── disabled │ │ ├── default_spec.rb │ │ └── spec_helper.rb └── inspec │ └── inspec_spec.rb └── spec ├── docker_spec.rb └── spec_helper.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Lint & Unit 3 | 4 | "on": 5 | pull_request: 6 | 7 | jobs: 8 | lint-unit: 9 | uses: test-kitchen/.github/.github/workflows/lint-unit.yml@v0.1.2 10 | 11 | integration-windows: 12 | name: Windows ${{matrix.suite}} ${{matrix.os}} 13 | runs-on: windows-latest 14 | needs: lint-unit 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | suite: [default] 19 | os: [ubuntu-20.04] 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: "3.1" 25 | bundler-cache: true 26 | - name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v3 28 | - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} 29 | 30 | integration-linux: 31 | name: Linux ${{matrix.suite}} ${{matrix.os}} 32 | runs-on: ubuntu-latest 33 | needs: lint-unit 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | suite: 38 | - default 39 | - no-build-context 40 | - arm64 41 | - amd64 42 | - inspec 43 | os: 44 | - amazonlinux-2 45 | - ubuntu-1804 46 | - ubuntu-2004 47 | - fedora-latest 48 | - centos-7 49 | - oraclelinux-7 50 | - rockylinux-8 51 | - debian-11 52 | - debian-12 53 | - opensuse-15 54 | - dockerfile 55 | steps: 56 | - uses: actions/checkout@v4 57 | - uses: ruby/setup-ruby@v1 58 | with: 59 | ruby-version: "3.1" 60 | bundler-cache: true 61 | - name: Set up QEMU 62 | uses: docker/setup-qemu-action@v3 63 | - name: Set up Docker Buildx 64 | uses: docker/setup-buildx-action@v3 65 | - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} 66 | 67 | integration-capabilities: 68 | name: Linux ${{matrix.suite}} ${{matrix.os}} 69 | runs-on: ubuntu-latest 70 | needs: lint-unit 71 | strategy: 72 | fail-fast: false 73 | matrix: 74 | suite: 75 | - capabilities 76 | os: [debian-11, ubuntu-1804, ubuntu-2004] 77 | steps: 78 | - uses: actions/checkout@v4 79 | - uses: ruby/setup-ruby@v1 80 | with: 81 | ruby-version: "3.1" 82 | bundler-cache: true 83 | - name: Set up Docker Buildx 84 | uses: docker/setup-buildx-action@v3 85 | - run: bundle exec kitchen test ${{ matrix.suite }}-${{ matrix.os }} 86 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: release-please 3 | 4 | "on": 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: google-github-actions/release-please-action@v3 13 | id: release 14 | with: 15 | release-type: ruby 16 | package-name: kitchen-docker 17 | version-file: lib/kitchen/driver/docker_version.rb 18 | token: ${{ secrets.PORTER_GITHUB_TOKEN }} 19 | 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | if: ${{ steps.release.outputs.release_created }} 23 | 24 | - name: Build and publish to GitHub Package 25 | uses: actionshub/publish-gem-to-github@main 26 | if: ${{ steps.release.outputs.release_created }} 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | owner: ${{ secrets.OWNER }} 30 | 31 | - name: Build and publish to RubyGems 32 | uses: actionshub/publish-gem-to-rubygems@main 33 | if: ${{ steps.release.outputs.release_created }} 34 | with: 35 | token: ${{ secrets.RUBYGEMS_API_KEY }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | .kitchen/ 19 | .kitchen.local.yml 20 | Dockerfile 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | default: true 2 | MD013: false 3 | MD024: false 4 | MD026: false 5 | MD036: false 6 | MD012: false 7 | MD029: false 8 | MD004: false 9 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - chefstyle 3 | 4 | AllCops: 5 | TargetRubyVersion: 3.1 6 | Include: 7 | - "**/*.rb" 8 | Exclude: 9 | - "vendor/**/*" 10 | - "spec/**/*" 11 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | rules: 4 | line-length: 5 | max: 256 6 | level: warning 7 | document-start: disable 8 | braces: 9 | forbid: false 10 | min-spaces-inside: 0 11 | max-spaces-inside: 1 12 | min-spaces-inside-empty: -1 13 | max-spaces-inside-empty: -1 14 | comments: 15 | min-spaces-from-content: 1 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Kitchen-Docker Changelog 2 | 3 | Future CHANGELOG notes will be in GitHub release notes 4 | 5 | ## [3.2.0](https://github.com/test-kitchen/kitchen-docker/compare/v3.1.0...v3.2.0) (2023-11-27) 6 | 7 | 8 | ### Features 9 | 10 | * Tell the user when we can't remove the image if it's in use ([#406](https://github.com/test-kitchen/kitchen-docker/issues/406)) ([bcb7c2b](https://github.com/test-kitchen/kitchen-docker/commit/bcb7c2bc5144ec63c6bde7b8947de33d5484e718)) 11 | 12 | ## [3.1.0](https://github.com/test-kitchen/kitchen-docker/compare/v3.0.0...v3.1.0) (2023-11-27) 13 | 14 | 15 | ### Features 16 | 17 | * Update workflows ([#410](https://github.com/test-kitchen/kitchen-docker/issues/410)) ([01acb1e](https://github.com/test-kitchen/kitchen-docker/commit/01acb1e00e45d02f71f70f68903c39d320d962df)) 18 | 19 | ## 2.14.0 - November 13, 2023 20 | 21 | - Make sure the /etc/sudoers.d directory exists by @garethgreenaway in [#397](https://github.com/test-kitchen/kitchen-docker/pull/397) 22 | - Breaking almalinux platform out by @garethgreenaway [#398](https://github.com/test-kitchen/kitchen-docker/pull/398) 23 | - fix: parse_image_id: Process "docker build" output in reverse line order by @terminalmage in [#400](https://github.com/test-kitchen/kitchen-docker/pull/400) 24 | - Allow build temporary Dockerfile in configured custom_dir by @Val in [294](https://github.com/test-kitchen/kitchen-docker/pull/294) 25 | 26 | ## 2.13.0 - June 10, 2022 27 | 28 | - Added CentOSStream and PhotonOS - [@garethgreenaway](https://github.com/garethgreenaway) 29 | - Fixed image parser when output includes a duration timestamp - [@RulerOf](https://github.com/RulerOf) 30 | - Updated the test suites - [@RulerOf](https://github.com/RulerOf) 31 | 32 | ## 2.12.0 - December 22, 2021 33 | 34 | - Support Docker BuildKit - [@RulerOf](https://github.com/RulerOf) 35 | - Add new `docker_platform` config to allow specifying architectures - [@RulerOf](https://github.com/RulerOf) 36 | 37 | ## 2.11.0 - July 2, 2021 38 | 39 | - Update the development dependency on kitchen-inspec to 2.x 40 | - Retrieve hostname state data after container is launched to avoid failures when `use_internal_docker_network` is set 41 | - Add a new option for setting container isolation. See the readme for additional details 42 | - Support GPUs in containers with a new `gpus` option that takes the same arguments that would be passed to `docker run --gpus` 43 | - suse platform: use system script for ssh key initialization 44 | - Add support for the `--mount` docker CLI option. See the readme for additional details 45 | - Use sudo.d files instead of directly editing the sudoers file 46 | - Allow passing `--tmpfs` entries to the docker run command. See the readme for additional details 47 | - Use less verbose and quicker setup on Gentoo 48 | - Lowercase the instance-name to avoid issues since docker does not allow instance with capital cases 49 | - Fix the error "Could not parse Docker build output for image ID" by improving the output line matching 50 | - Add support for `almalinux` & `rockylinux` 51 | 52 | ## 2.10.0 - Mar 28, 2020 53 | 54 | - Switched from require to require_relative to slightly improve load time performance 55 | - Allow for train gem 3.x 56 | - Refactor driver to include Windows support (includes new transport for all supported platforms) 57 | 58 | ## 2.9.0 - Mar 15, 2019 59 | 60 | - Add automatic OS detection for amazonlinux, opensuse/leap, and opensuse/tumbleweed 61 | - On Fedora containers uses dnf to setup the OS not yum 62 | 63 | ## 2.8.0 - Jan 18, 2019 64 | 65 | - Add new config option `use_internal_docker_network`, which allows running Docker within Docker. See readme for usage details. 66 | - Resolve errors while loading libraries on archlinux 67 | - Fix failures on Ubuntu 18.04 68 | - Check if image exists before attempting to remove it so we don't fail 69 | - Add oraclelinux platform support 70 | - Prevent `uninitialized constant Kitchen::Driver::Docker::Base64` error by requiring `base64` 71 | 72 | ## 2.7.0 73 | 74 | - Support for SUSE-based container images. 75 | - Improved support for build context shipping. 76 | - Changed `use_sudo` to default to `false` in keeping with modern Docker usage. 77 | 78 | ## 2.6.0 79 | 80 | - Set container name with information from the run so you can identify them 81 | later on. 82 | - Upgrade to new driver base class structure. 83 | 84 | ## 2.5.0 85 | 86 | - [#209](https://github.com/portertech/kitchen-docker/pulls/209) Fix usage with Kitchen rake tasks. 87 | - Add `run_options` and `build_options` configuration. 88 | - [#195](https://github.com/portertech/kitchen-docker/pulls/195) Fix Arch Linux support. 89 | - Fix shell escaping for build paths and SSH keys. 90 | 91 | ## 2.4.0 92 | 93 | - [#148](https://github.com/portertech/kitchen-docker/issues/148) Restored support for older versions of Ruby. 94 | - [#149](https://github.com/portertech/kitchen-docker/pulls/149) Handle connecting to a container directly as root. 95 | - [#154](https://github.com/portertech/kitchen-docker/pulls/154) Improve container caching by reordering the build steps. 96 | - [#176](https://github.com/portertech/kitchen-docker/pulls/176) Expose proxy environment variables to the container automatically. 97 | - [#192](https://github.com/portertech/kitchen-docker/pulls/192) Set `$container=docker` for CentOS images. 98 | - [#196](https://github.com/portertech/kitchen-docker/pulls/196) Mutex SSH key generation for use with `kitchen -c`. 99 | - [#192](https://github.com/portertech/kitchen-docker/pulls/192) Don't wait when stopping a container. 100 | 101 | ## 2.3.0 102 | 103 | - `build_context` option (boolean) to enable/disable sending the build 104 | context to Docker. 105 | 106 | ## 2.2.0 107 | 108 | - Use a temporary file for each suite instance Docker container 109 | Dockerfile, instead of passing their contents via STDIN. This allows for 110 | the use of commands like ADD and COPY. **Users must now use Docker >= 1.5.0** 111 | - Passwordless suite instance Docker container login (SSH), using a 112 | generated key pair. 113 | - Support for sharing a host device with suite instance Docker containers. 114 | - README YAML highlighting. 115 | 116 | ## 2.1.0 117 | 118 | - Use `NUL` instead of `/dev/null` on Windows for output redirection 119 | 120 | ## 2.0.0 121 | 122 | - Use Docker `top` and `port` instead of `inspect` 123 | - Don't create the kitchen user if it already exists 124 | - Docker container capabilities options: cap_add, cap_drop 125 | - Docker security profile option (SELinux/AppArmor): security_opt 126 | - wait_for_sshd option (boolean) 127 | - Create `/etc/sudoers.d` if missing 128 | - Fixed option deprecation warnings, require Docker >= 1.2 129 | 130 | ## 1.7.0 131 | 132 | - Ensure a container id is set before attempting to inspect a container 133 | 134 | ## 1.6.0 135 | 136 | - `publish_all` option to publish all ports to the host interface 137 | - `instance_name` option to name the Docker container 138 | - `links` option to link suite instance Docker containers 139 | - `socket` option will now default to ENV `DOCKER_HOST` if set 140 | - Fixed verify dependencies output redirection 141 | - Added `fedora` to platform names 142 | - Support for `gentoo` and `gentoo-paludis` platforms 143 | - Adding sudo rule to `/etc/sudoers.d/#{username}` in addition to `/etc/sudoers` 144 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | # Integration testing gems. 7 | gem 'kitchen-inspec', '~> 2.0' 8 | gem 'train', '>= 2.1', '< 4.0' # validate 4.x when it's released 9 | end 10 | 11 | group :test do 12 | gem 'bundler' 13 | gem 'rake' 14 | gem 'rspec', '~> 3.2' 15 | gem 'rspec-its', '~> 1.2' 16 | end 17 | 18 | group :chefstyle do 19 | gem 'chefstyle', '~> 2.2', '>= 2.2.3' 20 | end 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kitchen-Docker 2 | 3 | [![Build Status](https://travis-ci.org/test-kitchen/kitchen-docker.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-docker) 4 | [![Gem Version](https://img.shields.io/gem/v/kitchen-docker.svg)](https://rubygems.org/gems/kitchen-docker) 5 | [![Coverage](https://img.shields.io/codecov/c/github/test-kitchen/kitchen-docker.svg)](https://codecov.io/github/test-kitchen/kitchen-docker) 6 | [![License](https://img.shields.io/badge/license-Apache_2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) 7 | 8 | A Test Kitchen Driver and Transport for Docker. 9 | 10 | ***MAINTAINERS WANTED***: This Test-Kitchen driver is currently without a maintainer and has many known issues. If you're interested in maintaining this driver for the long run including expanding the CI testing please reach out on [Chef Community Slack: #test-kitchen](https://chefcommunity.slack.com/archives/C2B6G1WCQ). Until such a time that this driver is maintained we highly recommend the [kitchen-dokken](https://github.com/test-kitchen/kitchen-dokken) for Chef Infra testing with Docker containers. 11 | 12 | ## Requirements 13 | 14 | * [Docker][docker_installation] **(>= 1.5)** 15 | 16 | ## Installation and Setup 17 | 18 | Please read the Test Kitchen [docs][test_kitchen_docs] for more details. 19 | 20 | Example (Linux) `.kitchen.local.yml`: 21 | 22 | ```yaml 23 | --- 24 | driver: 25 | name: docker 26 | env_variables: 27 | TEST_KEY: TEST_VALUE 28 | 29 | platforms: 30 | - name: ubuntu 31 | run_list: 32 | - recipe[apt] 33 | - name: centos 34 | driver_config: 35 | image: centos 36 | platform: rhel 37 | run_list: 38 | - recipe[yum] 39 | 40 | transport: 41 | name: docker 42 | ``` 43 | 44 | Example (Windows) `.kitchen.local.yml`: 45 | 46 | ```yaml 47 | --- 48 | driver: 49 | name: docker 50 | 51 | platforms: 52 | - name: windows 53 | driver_config: 54 | image: mcr.microsoft.com/windows/servercore:1607 55 | platform: windows 56 | run_list: 57 | - recipe[chef_client] 58 | 59 | transport: 60 | name: docker 61 | env_variables: 62 | TEST_KEY: TEST_VALUE 63 | ``` 64 | 65 | ## Default Configuration 66 | 67 | This driver can determine an image and platform type for a select number of 68 | platforms. 69 | 70 | Examples: 71 | 72 | ```yaml 73 | --- 74 | platforms: 75 | - name: ubuntu-18.04 76 | - name: centos-7 77 | ``` 78 | 79 | This will effectively generate a configuration similar to: 80 | 81 | ```yaml 82 | --- 83 | platforms: 84 | - name: ubuntu-18.04 85 | driver_config: 86 | image: ubuntu:18.04 87 | platform: ubuntu 88 | - name: centos-7 89 | driver_config: 90 | image: centos:7 91 | platform: centos 92 | ``` 93 | 94 | ## Configuration 95 | 96 | ### binary 97 | 98 | The Docker binary to use. 99 | 100 | The default value is `docker`. 101 | 102 | Examples: 103 | 104 | ```yaml 105 | binary: docker.io 106 | ``` 107 | 108 | ```yaml 109 | binary: /opt/docker 110 | ``` 111 | 112 | ### socket 113 | 114 | The Docker daemon socket to use. By default, Docker will listen on `unix:///var/run/docker.sock` (On Windows, `npipe:////./pipe/docker_engine`), 115 | and no configuration here is required. If Docker is binding to another host/port or Unix socket, you will need to set this option. 116 | If a TCP socket is set, its host will be used for SSH access to suite containers. 117 | 118 | Examples: 119 | 120 | ```yaml 121 | socket: unix:///tmp/docker.sock 122 | ``` 123 | 124 | ```yaml 125 | socket: tcp://docker.example.com:4242 126 | ``` 127 | 128 | If you are using the InSpec verifier on Windows, using named pipes for the Docker engine will not work with the Docker transport. 129 | Set the socket option with the TCP socket address of the Docker engine as shown below: 130 | 131 | ```yaml 132 | socket: tcp://localhost:2375 133 | ``` 134 | 135 | The Docker engine must be configured to listen on a TCP port (default port is 2375). This can be configured by editing the configuration file 136 | (usually located in `C:\ProgramData\docker\config\daemon.json`) and adding the hosts value: 137 | 138 | ```json 139 | "hosts": ["tcp://0.0.0.0:2375"] 140 | ``` 141 | 142 | Example configuration is shown below: 143 | 144 | ```json 145 | { 146 | "registry-mirrors": [], 147 | "insecure-registries": [], 148 | "debug": true, 149 | "experimental": false, 150 | "hosts": ["tcp://0.0.0.0:2375"] 151 | } 152 | ``` 153 | 154 | If you use [Boot2Docker](https://github.com/boot2docker/boot2docker) 155 | or [docker-machine](https://docs.docker.com/machine/get-started/) set 156 | your `DOCKER_HOST` environment variable properly with `export 157 | DOCKER_HOST=tcp://192.168.59.103:2375` or `eval "$(docker-machine env 158 | $MACHINE)"` then use the following: 159 | 160 | ```yaml 161 | socket: tcp://192.168.59.103:2375 162 | ``` 163 | 164 | ### image 165 | 166 | The Docker image to use as the base for the suite containers. You can find 167 | images using the [Docker Index][docker_index]. 168 | 169 | The default will be computed, using the platform name (see the Default 170 | Configuration section for more details). 171 | 172 | ### isolation 173 | 174 | The isolation technology for the container. This is not set by default and will use the default container isolation settings. 175 | 176 | For example, the following driver configuration options can be used to specify the container isolation technology for Windows containers: 177 | 178 | ```yaml 179 | # Hyper-V 180 | isolation: hyperv 181 | 182 | # Process 183 | isolation: process 184 | ``` 185 | 186 | ### platform 187 | 188 | The platform of the chosen image. This is used to properly bootstrap the 189 | suite container for Test Kitchen. Kitchen Docker currently supports: 190 | 191 | * `arch` 192 | * `debian` or `ubuntu` 193 | * `amazonlinux`, `rhel`, `centos`, `fedora`, `oraclelinux`, `almalinux` or `rockylinux` 194 | * `gentoo` or `gentoo-paludis` 195 | * `opensuse/tumbleweed`, `opensuse/leap`, `opensuse` or `sles` 196 | * `windows` 197 | 198 | The default will be computed, using the platform name (see the Default 199 | Configuration section for more details). 200 | 201 | ### require\_chef\_omnibus 202 | 203 | Determines whether or not a Chef [Omnibus package][chef_omnibus_dl] will be 204 | installed. There are several different behaviors available: 205 | 206 | * `true` - the latest release will be installed. Subsequent converges 207 | will skip re-installing if chef is present. 208 | * `latest` - the latest release will be installed. Subsequent converges 209 | will always re-install even if chef is present. 210 | * `` (ex: `10.24.0`) - the desired version string will 211 | be passed the the install.sh script. Subsequent converges will skip if 212 | the installed version and the desired version match. 213 | * `false` or `nil` - no chef is installed. 214 | 215 | The default value is `true`. 216 | 217 | ### disable\_upstart 218 | 219 | Disables upstart on Debian/Ubuntu containers, as many images do not support a 220 | working upstart. 221 | 222 | The default value is `true`. 223 | 224 | ### provision\_command 225 | 226 | Custom command(s) to be run when provisioning the base for the suite containers. 227 | 228 | Examples: 229 | 230 | ```yaml 231 | provision_command: curl -L https://www.opscode.com/chef/install.sh | bash 232 | ``` 233 | 234 | ```yaml 235 | provision_command: 236 | - apt-get install dnsutils 237 | - apt-get install telnet 238 | ``` 239 | 240 | ```yaml 241 | driver_config: 242 | provision_command: curl -L https://www.opscode.com/chef/install.sh | bash 243 | require_chef_omnibus: false 244 | ``` 245 | 246 | ### env_variables 247 | 248 | Adds environment variables to Docker container 249 | 250 | Examples: 251 | 252 | ```yaml 253 | env_variables: 254 | TEST_KEY_1: TEST_VALUE 255 | SOME_VAR: SOME_VALUE 256 | ``` 257 | 258 | ### use\_cache 259 | 260 | This determines if the Docker cache is used when provisioning the base for suite 261 | containers. 262 | 263 | The default value is `true`. 264 | 265 | ### use\_sudo 266 | 267 | This determines if Docker commands are run with `sudo`. 268 | 269 | The default value depends on the type of socket being used. For local sockets, the default value is `true`. For remote sockets, the default value is `false`. 270 | 271 | This should be set to `false` if you're using boot2docker, as every command passed into the VM runs as root by default. 272 | 273 | ### remove\_images 274 | 275 | This determines if images are automatically removed when the suite container is 276 | destroyed. 277 | 278 | The default value is `false`. 279 | 280 | ### run\_command 281 | 282 | Sets the command used to run the suite container. 283 | 284 | The default value is `/usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes -o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid`. 285 | 286 | Examples: 287 | 288 | ```yaml 289 | run_command: /sbin/init 290 | ``` 291 | 292 | ### memory 293 | 294 | Sets the memory limit for the suite container in bytes. Otherwise use Dockers 295 | default. You can read more about `memory.limit_in_bytes` [here][memory_limit]. 296 | 297 | ### cpu 298 | 299 | Sets the CPU shares (relative weight) for the suite container. Otherwise use 300 | Dockers defaults. You can read more about cpu.shares [here][cpu_shares]. 301 | 302 | ### volume 303 | 304 | Adds a data volume(s) to the suite container. 305 | 306 | Examples: 307 | 308 | ```yaml 309 | volume: /ftp 310 | ``` 311 | 312 | ```yaml 313 | volume: 314 | - /ftp 315 | - /srv 316 | ``` 317 | 318 | ### volumes\_from 319 | 320 | Mount volumes managed by other containers. 321 | 322 | Examples: 323 | 324 | ```yaml 325 | volumes_from: repos 326 | ``` 327 | 328 | ```yaml 329 | volumes_from: 330 | - repos 331 | - logging 332 | - rvm 333 | ``` 334 | 335 | ### mount 336 | 337 | Attach a filesystem mount to the container (**NOTE:** supported only in docker 338 | 17.05 and newer). 339 | 340 | Examples: 341 | 342 | ```yaml 343 | mount: type=volume,source=my-volume,destination=/path/in/container 344 | ``` 345 | 346 | ```yaml 347 | mount: 348 | - type=volume,source=my-volume,destination=/path/in/container 349 | - type=tmpfs,tmpfs-size=512M,destination=/path/to/tmpdir 350 | ``` 351 | 352 | ### tmpfs 353 | 354 | Adds a tmpfs volume(s) to the suite container. 355 | 356 | Examples: 357 | 358 | ```yaml 359 | tmpfs: /tmp 360 | ``` 361 | 362 | ```yaml 363 | tmpfs: 364 | - /tmp:exec 365 | - /run 366 | ``` 367 | 368 | ### dns 369 | 370 | Adjusts `resolv.conf` to use the dns servers specified. Otherwise use 371 | Dockers defaults. 372 | 373 | Examples: 374 | 375 | ```yaml 376 | dns: 8.8.8.8 377 | ``` 378 | 379 | ```yaml 380 | dns: 381 | - 8.8.8.8 382 | - 8.8.4.4 383 | ``` 384 | 385 | ### http\_proxy 386 | 387 | Sets an http proxy for the suite container using the `http_proxy` environment variable. 388 | 389 | Examples: 390 | 391 | ```yaml 392 | http_proxy: http://proxy.host.com:8080 393 | ``` 394 | 395 | ### https\_proxy 396 | 397 | Sets an https proxy for the suite container using the `https_proxy` environment variable. 398 | 399 | Examples: 400 | 401 | ```yaml 402 | https_proxy: http://proxy.host.com:8080 403 | ``` 404 | 405 | ### forward 406 | 407 | Set suite container port(s) to forward to the host machine. You may specify 408 | the host (public) port in the mappings, if not, Docker chooses for you. 409 | 410 | Examples: 411 | 412 | ```yaml 413 | forward: 80 414 | ``` 415 | 416 | ```yaml 417 | forward: 418 | - 22:2222 419 | - 80:8080 420 | ``` 421 | 422 | ### hostname 423 | 424 | Set the suite container hostname. Otherwise use Dockers default. 425 | 426 | Examples: 427 | 428 | ```yaml 429 | hostname: foobar.local 430 | ``` 431 | 432 | ### privileged 433 | 434 | Run the suite container in privileged mode. This allows certain functionality 435 | inside the Docker container which is not otherwise permitted. 436 | 437 | The default value is `false`. 438 | 439 | Examples: 440 | 441 | ```yaml 442 | privileged: true 443 | ``` 444 | 445 | ### cap\_add 446 | 447 | Adds a capability to the running container. 448 | 449 | Examples: 450 | 451 | ```yaml 452 | cap_add: 453 | - SYS_PTRACE 454 | 455 | ``` 456 | 457 | ### cap\_drop 458 | 459 | Drops a capability from the running container. 460 | 461 | Examples: 462 | 463 | ```yaml 464 | cap_drop: 465 | - CHOWN 466 | ``` 467 | 468 | ### security\_opt 469 | 470 | Apply a security profile to the Docker container. Allowing finer granularity of 471 | access control than privileged mode, through leveraging SELinux/AppArmor 472 | profiles to grant access to specific resources. 473 | 474 | Examples: 475 | 476 | ```yaml 477 | security_opt: 478 | - apparmor:my_profile 479 | ``` 480 | 481 | ### dockerfile 482 | 483 | Use a custom Dockerfile, instead of having Kitchen-Docker build one for you. 484 | 485 | Examples: 486 | 487 | ```yaml 488 | dockerfile: test/Dockerfile 489 | ``` 490 | 491 | ### instance\_name 492 | 493 | Set the name of container to link to other container(s). 494 | 495 | Examples: 496 | 497 | ```yaml 498 | instance_name: web 499 | ``` 500 | 501 | ### links 502 | 503 | Set ```instance_name```(and alias) of other container(s) that connect from the suite container. 504 | 505 | Examples: 506 | 507 | ```yaml 508 | links: db:db 509 | ``` 510 | 511 | ```yaml 512 | links: 513 | - db:db 514 | - kvs:kvs 515 | ``` 516 | 517 | ### publish\_all 518 | 519 | Publish all exposed ports to the host interfaces. 520 | This option used to communicate between some containers. 521 | 522 | The default value is `false`. 523 | 524 | Examples: 525 | 526 | ```yaml 527 | publish_all: true 528 | ``` 529 | 530 | ### devices 531 | 532 | Share a host device with the container. Host device must be an absolute path. 533 | 534 | Examples: 535 | 536 | ```yaml 537 | devices: /dev/vboxdrv 538 | ``` 539 | 540 | ```yaml 541 | devices: 542 | - /dev/vboxdrv 543 | - /dev/vboxnetctl 544 | ``` 545 | 546 | ### build_context 547 | 548 | Transfer the cookbook directory (cwd) as build context. This is required for 549 | Dockerfile commands like ADD and COPY. When using a remote Docker server, the 550 | whole directory has to be copied, which can be slow. 551 | 552 | The default value is `true` for local Docker and `false` for remote Docker. 553 | 554 | Examples: 555 | 556 | ```yaml 557 | build_context: true 558 | ``` 559 | 560 | ### build_options 561 | 562 | Extra command-line options to pass to `docker build` when creating the image. 563 | 564 | Examples: 565 | 566 | ```yaml 567 | build_options: --rm=false 568 | ``` 569 | 570 | ```yaml 571 | build_options: 572 | rm: false 573 | build-arg: something 574 | ``` 575 | 576 | ### run_options 577 | 578 | Extra command-line options to pass to `docker run` when starting the container. 579 | 580 | Examples: 581 | 582 | ```yaml 583 | run_options: --ip=1.2.3.4 584 | ``` 585 | 586 | ```yaml 587 | run_options: 588 | tmpfs: 589 | - /run/lock 590 | - /tmp 591 | net: br3 592 | ``` 593 | 594 | ### build_tempdir 595 | 596 | Relative (to `build_context`) temporary directory path for built Dockerfile. 597 | 598 | Example: 599 | 600 | ```yaml 601 | build_tempdir: .kitchen 602 | ``` 603 | 604 | ### use_internal_docker_network 605 | 606 | If you want to use kitchen-docker from within another Docker container you'll 607 | need to set this to true. When set to true uses port 22 as the SSH port and 608 | the IP of the container that chef is going to run in as the hostname so that 609 | you can connect to it over SSH from within another Docker container. 610 | 611 | Examples: 612 | 613 | ```yaml 614 | use_internal_docker_network: true 615 | ``` 616 | 617 | ### docker_platform 618 | 619 | Configure the CPU platform (architecture) used by docker to build the image. 620 | 621 | Examples: 622 | 623 | ```yaml 624 | docker_platform: linux/arm64 625 | ``` 626 | 627 | ```yaml 628 | docker_platform: linux/amd64 629 | ``` 630 | 631 | ## Development 632 | 633 | * Source hosted at [GitHub][repo] 634 | * Report issues/questions/feature requests on [GitHub Issues][issues] 635 | 636 | Pull requests are very welcome! Make sure your patches are well tested. 637 | Ideally create a topic branch for every separate change you make. For 638 | example: 639 | 640 | 1. Fork the repo 641 | 2. Create your feature branch (`git checkout -b my-new-feature`) 642 | 3. Commit your changes (`git commit -am 'Added some feature'`) 643 | 4. Push to the branch (`git push origin my-new-feature`) 644 | 5. Create new Pull Request 645 | 646 | ## License 647 | 648 | ```text 649 | Copyright 2013-2016, [Sean Porter](https://github.com/portertech) 650 | Copyright 2015-2016, [Noah Kantrowitz](https://github.com/coderanger) 651 | 652 | Licensed under the Apache License, Version 2.0 (the "License"); 653 | you may not use this file except in compliance with the License. 654 | You may obtain a copy of the License at 655 | 656 | http://www.apache.org/licenses/LICENSE-2.0 657 | 658 | Unless required by applicable law or agreed to in writing, software 659 | distributed under the License is distributed on an "AS IS" BASIS, 660 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 661 | See the License for the specific language governing permissions and 662 | limitations under the License. 663 | ``` 664 | 665 | [issues]: https://github.com/test-kitchen/kitchen-docker/issues 666 | [repo]: https://github.com/test-kitchen/kitchen-docker 667 | [docker_installation]: https://docs.docker.com/installation/#installation 668 | [docker_index]: https://index.docker.io/ 669 | [test_kitchen_docs]: https://kitchen.ci/docs/getting-started/introduction/ 670 | [chef_omnibus_dl]: https://downloads.chef.io/chef-client/ 671 | [cpu_shares]: https://docs.fedoraproject.org/en-US/Fedora/17/html/Resource_Management_Guide/sec-cpu.html 672 | [memory_limit]: https://docs.fedoraproject.org/en-US/Fedora/17/html/Resource_Management_Guide/sec-memory.html 673 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | # Create the spec task. 4 | require "rspec/core/rake_task" 5 | RSpec::Core::RakeTask.new(:test, :tag) do |t, args| 6 | t.rspec_opts = [].tap do |a| 7 | a << "--color" 8 | a << "--format #{ENV["CI"] ? "documentation" : "progress"}" 9 | a << "--backtrace" if ENV["VERBOSE"] || ENV["DEBUG"] 10 | a << "--seed #{ENV["SEED"]}" if ENV["SEED"] 11 | a << "--tag #{args[:tag]}" if args[:tag] 12 | a << "--default-path test" 13 | a << "-I test/spec" 14 | end.join(" ") 15 | end 16 | -------------------------------------------------------------------------------- /docker.ps1: -------------------------------------------------------------------------------- 1 | # This script is used to configure the Docker service for Windows builds in Travis CI 2 | Write-Host "Configuring Docker service to listen on TCP port 2375..." 3 | $dockerSvcArgs = (Get-WmiObject Win32_Service | ?{$_.Name -eq 'docker'} | Select PathName).PathName 4 | $dockerSvcArgs = "$dockerSvcArgs -H tcp://0.0.0.0:2375 -H npipe:////./pipe/docker_engine" 5 | Write-Host "Docker Service Args: $dockerSvcArgs" 6 | 7 | Get-WmiObject Win32_Service -Filter "Name='docker'" | Invoke-WmiMethod -Name Change -ArgumentList @($null,$null,$null,$null,$null, $dockerSvcArgs) | Out-Null 8 | 9 | Restart-Service docker -Force -Verbose 10 | -------------------------------------------------------------------------------- /kitchen-docker.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("../lib", __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require "kitchen/docker/docker_version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "kitchen-docker" 7 | spec.version = Kitchen::Docker::DOCKER_VERSION 8 | spec.authors = ["Sean Porter"] 9 | spec.email = ["portertech@gmail.com"] 10 | spec.description = %q{A Docker Driver for Test Kitchen} 11 | spec.summary = spec.description 12 | spec.homepage = "https://github.com/test-kitchen/kitchen-docker" 13 | spec.license = "Apache 2.0" 14 | 15 | spec.files = `git ls-files`.split($/) 16 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 17 | spec.require_paths = ["lib"] 18 | 19 | spec.add_dependency "test-kitchen", ">= 1.0.0", "< 4.0" 20 | end 21 | -------------------------------------------------------------------------------- /kitchen.windows.yml: -------------------------------------------------------------------------------- 1 | # <% # Make sure the local copy of the driver is loaded %> 2 | # <% lib = File.expand_path('../lib', __FILE__) %> 3 | # <% $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) %> 4 | --- 5 | driver: 6 | name: docker 7 | provision_command: 8 | - powershell -ExecutionPolicy Bypass -NoLogo -Command . { iwr -useb https://omnitruck.chef.io/install.ps1 } ^| iex; install 9 | - powershell -Command $path=$env:Path + ';c:\opscode\chef\embedded\bin'; Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\' -Name Path -Value $path 10 | 11 | transport: 12 | name: docker 13 | socket: tcp://localhost:2375 14 | 15 | provisioner: 16 | name: dummy 17 | 18 | platforms: 19 | - name: windows 20 | driver_config: 21 | image: mcr.microsoft.com/windows/servercore:1809 22 | platform: windows 23 | 24 | suites: 25 | - name: default 26 | - name: context 27 | driver: 28 | build_context: false 29 | - name: inspec 30 | driver: 31 | provision_command: echo 1 32 | verifier: 33 | name: inspec 34 | -------------------------------------------------------------------------------- /kitchen.yml: -------------------------------------------------------------------------------- 1 | # <% # Make sure the local copy of the driver is loaded %> 2 | # <% lib = File.expand_path('../lib', __FILE__) %> 3 | # <% $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) %> 4 | --- 5 | driver: 6 | name: docker 7 | provision_command: curl -L https://www.chef.io/chef/install.sh | bash 8 | 9 | transport: 10 | name: docker 11 | 12 | provisioner: 13 | name: dummy 14 | 15 | platforms: 16 | - name: amazonlinux-2 17 | - name: ubuntu-18.04 18 | - name: ubuntu-20.04 19 | - name: fedora-latest 20 | driver: 21 | provision_command: 22 | - yum install libxcrypt-compat -y 23 | - curl -L https://www.chef.io/chef/install.sh | bash 24 | - name: centos-7 25 | - name: oraclelinux-7 26 | - name: rockylinux-8 27 | - name: debian-11 28 | - name: debian-12 29 | - name: opensuse-15 30 | driver: 31 | image: opensuse/leap:15 32 | - name: dockerfile 33 | driver: 34 | username: dockerfile 35 | password: dockerfile 36 | dockerfile: test/Dockerfile 37 | run_command: /sbin/init 38 | 39 | suites: 40 | - name: default 41 | - name: no_build_context 42 | driver: 43 | build_context: false 44 | - name: capabilities 45 | includes: [debian-11, ubuntu-18.04, ubuntu-20.04] 46 | driver: 47 | provision_command: 48 | - curl -L https://www.chef.io/chef/install.sh | bash 49 | - apt-get install -y net-tools 50 | cap_drop: 51 | - NET_ADMIN 52 | - name: arm64 53 | driver: 54 | docker_platform: linux/arm64 55 | - name: amd64 56 | driver: 57 | docker_platform: linux/amd64 58 | - name: inspec 59 | driver: 60 | provision_command: true 61 | verifier: 62 | name: inspec 63 | -------------------------------------------------------------------------------- /lib/docker/version.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | begin 15 | require "docker" 16 | 17 | # Override API_VERSION constant in docker-api gem to use version 1.24 of the Docker API 18 | # This override is for the docker-api gem to communicate to the Docker engine on Windows 19 | module Docker 20 | VERSION = "0.0.0".freeze 21 | API_VERSION = "1.24".freeze 22 | end 23 | rescue LoadError => e 24 | logger.debug("[Docker] docker-api gem not found for InSpec verifier. #{e}") 25 | end 26 | -------------------------------------------------------------------------------- /lib/kitchen/docker/container.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require_relative "helpers/cli_helper" 15 | require_relative "helpers/container_helper" 16 | require_relative "helpers/file_helper" 17 | require_relative "helpers/image_helper" 18 | 19 | module Kitchen 20 | module Docker 21 | class Container 22 | include Kitchen::Docker::Helpers::CliHelper 23 | include Kitchen::Docker::Helpers::ContainerHelper 24 | include Kitchen::Docker::Helpers::FileHelper 25 | include Kitchen::Docker::Helpers::ImageHelper 26 | 27 | def initialize(config) 28 | @config = config 29 | end 30 | 31 | def create(state) 32 | if container_exists?(state) 33 | info("Container ID #{state[:container_id]} already exists.") 34 | elsif !container_exists?(state) && state[:container_id] 35 | raise ActionFailed, "Container ID #{state[:container_id]} was found in the kitchen state data, "\ 36 | "but the container does not exist." 37 | end 38 | 39 | state[:username] = @config[:username] 40 | end 41 | 42 | def destroy(state) 43 | info("[Docker] Destroying Docker container #{state[:container_id]}") if state[:container_id] 44 | remove_container(state) if container_exists?(state) 45 | 46 | if @config[:remove_images] && state[:image_id] 47 | remove_image(state) if image_exists?(state) 48 | end 49 | end 50 | 51 | def hostname(state) 52 | hostname = "localhost" 53 | 54 | if remote_socket? 55 | hostname = socket_uri.host 56 | elsif @config[:use_internal_docker_network] 57 | hostname = container_ip_address(state) 58 | end 59 | 60 | hostname 61 | end 62 | 63 | def upload(locals, remote) 64 | files = locals 65 | files = Array(locals) unless locals.is_a?(Array) 66 | 67 | files.each do |file| 68 | copy_file_to_container(@config, file, remote) 69 | end 70 | 71 | files 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/kitchen/docker/container/linux.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require "base64" unless defined?(Base64) 15 | require "openssl" unless defined?(OpenSSL) 16 | require "securerandom" unless defined?(SecureRandom) 17 | require "shellwords" unless defined?(Shellwords) 18 | 19 | require_relative "../container" 20 | require_relative "../helpers/dockerfile_helper" 21 | 22 | module Kitchen 23 | module Docker 24 | class Container 25 | class Linux < Kitchen::Docker::Container 26 | include Kitchen::Docker::Helpers::DockerfileHelper 27 | 28 | MUTEX_FOR_SSH_KEYS = Mutex.new 29 | 30 | def initialize(config) 31 | super 32 | end 33 | 34 | def create(state) 35 | super 36 | 37 | debug("Creating Linux container") 38 | generate_keys 39 | 40 | state[:ssh_key] = @config[:private_key] 41 | state[:image_id] = build_image(state, dockerfile) unless state[:image_id] 42 | state[:container_id] = run_container(state, 22) unless state[:container_id] 43 | state[:hostname] = hostname(state) 44 | state[:port] = container_ssh_port(state) 45 | end 46 | 47 | def execute(command) 48 | # Create temp script file and upload files to container 49 | debug("Executing command on Linux container (Platform: #{@config[:platform]})") 50 | filename = "docker-#{::SecureRandom.uuid}.sh" 51 | temp_file = "./.kitchen/temp/#{filename}" 52 | create_temp_file(temp_file, command) 53 | 54 | remote_path = @config[:temp_dir] 55 | debug("Creating directory #{remote_path} on container") 56 | create_dir_on_container(@config, remote_path) 57 | 58 | debug("Uploading temp file #{temp_file} to #{remote_path} on container") 59 | upload(temp_file, remote_path) 60 | 61 | debug("Deleting temp file from local filesystem") 62 | ::File.delete(temp_file) 63 | 64 | # Replace any environment variables used in the path and execute script file 65 | debug("Executing temp script #{remote_path}/#{filename} on container") 66 | remote_path = replace_env_variables(@config, remote_path) 67 | 68 | container_exec(@config, "/bin/bash #{remote_path}/#{filename}") 69 | rescue => e 70 | raise "Failed to execute command on Linux container. #{e}" 71 | end 72 | 73 | protected 74 | 75 | def generate_keys 76 | MUTEX_FOR_SSH_KEYS.synchronize do 77 | if !File.exist?(@config[:public_key]) || !File.exist?(@config[:private_key]) 78 | private_key = OpenSSL::PKey::RSA.new(2048) 79 | blobbed_key = Base64.encode64(private_key.to_blob).gsub("\n", "") 80 | public_key = "ssh-rsa #{blobbed_key} kitchen_docker_key" 81 | File.open(@config[:private_key], "w") do |file| 82 | file.write(private_key) 83 | file.chmod(0600) 84 | end 85 | File.open(@config[:public_key], "w") do |file| 86 | file.write(public_key) 87 | file.chmod(0600) 88 | end 89 | end 90 | end 91 | end 92 | 93 | def parse_container_ssh_port(output) 94 | _host, port = output.split(":") 95 | port.to_i 96 | rescue => e 97 | raise ActionFailed, "Could not parse Docker port output for container SSH port. #{e}" 98 | end 99 | 100 | def container_ssh_port(state) 101 | return 22 if @config[:use_internal_docker_network] 102 | 103 | output = docker_command("port #{state[:container_id]} 22/tcp") 104 | parse_container_ssh_port(output) 105 | rescue => e 106 | raise ActionFailed, "Docker reports container has no ssh port mapped. #{e}" 107 | end 108 | 109 | def dockerfile 110 | return dockerfile_template if @config[:dockerfile] 111 | 112 | from = "FROM #{@config[:image]}" 113 | platform = dockerfile_platform 114 | username = @config[:username] 115 | public_key = IO.read(@config[:public_key]).strip 116 | homedir = username == "root" ? "/root" : "/home/#{username}" 117 | base = dockerfile_base_linux(username, homedir) 118 | 119 | custom = "" 120 | Array(@config[:provision_command]).each do |cmd| 121 | custom << "RUN #{cmd}\n" 122 | end 123 | 124 | ssh_key = "RUN echo #{Shellwords.escape(public_key)} >> #{homedir}/.ssh/authorized_keys" 125 | 126 | # Empty string to ensure the file ends with a newline. 127 | output = [from, dockerfile_proxy_config, platform, base, custom, ssh_key, ""].join("\n") 128 | debug("--- Start Dockerfile ---") 129 | debug(output.strip) 130 | debug("--- End Dockerfile ---") 131 | output 132 | end 133 | end 134 | end 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /lib/kitchen/docker/container/windows.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require "securerandom" unless defined?(SecureRandom) 15 | 16 | require_relative "../container" 17 | 18 | module Kitchen 19 | module Docker 20 | class Container 21 | class Windows < Kitchen::Docker::Container 22 | def initialize(config) 23 | super 24 | end 25 | 26 | def create(state) 27 | super 28 | 29 | debug("Creating Windows container") 30 | state[:username] = @config[:username] 31 | state[:image_id] = build_image(state, dockerfile) unless state[:image_id] 32 | state[:container_id] = run_container(state) unless state[:container_id] 33 | state[:hostname] = hostname(state) 34 | end 35 | 36 | def execute(command) 37 | # Create temp script file and upload files to container 38 | debug("Executing command on Windows container") 39 | filename = "docker-#{::SecureRandom.uuid}.ps1" 40 | temp_file = ".\\.kitchen\\temp\\#{filename}" 41 | create_temp_file(temp_file, command) 42 | 43 | remote_path = @config[:temp_dir].tr("/", "\\") 44 | debug("Creating directory #{remote_path} on container") 45 | create_dir_on_container(@config, remote_path) 46 | 47 | debug("Uploading temp file #{temp_file} to #{remote_path} on container") 48 | upload(temp_file, remote_path) 49 | 50 | debug("Deleting temp file from local filesystem") 51 | ::File.delete(temp_file) 52 | 53 | # Replace any environment variables used in the path and execute script file 54 | debug("Executing temp script #{remote_path}\\#{filename} on container") 55 | remote_path = replace_env_variables(@config, remote_path) 56 | cmd = build_powershell_command("-File #{remote_path}\\#{filename}") 57 | 58 | container_exec(@config, cmd) 59 | rescue => e 60 | raise "Failed to execute command on Windows container. #{e}" 61 | end 62 | 63 | protected 64 | 65 | def dockerfile 66 | raise ActionFailed, "Unknown platform '#{@config[:platform]}'" unless @config[:platform] == "windows" 67 | return dockerfile_template if @config[:dockerfile] 68 | 69 | from = "FROM #{@config[:image]}" 70 | 71 | custom = "" 72 | Array(@config[:provision_command]).each do |cmd| 73 | custom << "RUN #{cmd}\n" 74 | end 75 | 76 | output = [from, dockerfile_proxy_config, custom, ""].join("\n") 77 | debug("--- Start Dockerfile ---") 78 | debug(output.strip) 79 | debug("--- End Dockerfile ---") 80 | output 81 | end 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/kitchen/docker/docker_version.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2014, Sean Porter 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | module Kitchen 17 | module Docker 18 | # Version string for Docker Kitchen driver 19 | DOCKER_VERSION = "3.0.0".freeze 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/kitchen/docker/erb_context.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2014, Sean Porter 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | require "erb" unless defined?(Erb) 17 | 18 | module Kitchen 19 | module Docker 20 | class ERBContext 21 | def initialize(config = {}) 22 | config.each do |key, value| 23 | instance_variable_set("@" + key.to_s, value) 24 | end 25 | end 26 | 27 | def get_binding 28 | binding 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/kitchen/docker/helpers/cli_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require "kitchen" 15 | require "kitchen/configurable" 16 | require "kitchen/logging" 17 | require "kitchen/shell_out" 18 | 19 | module Kitchen 20 | module Docker 21 | module Helpers 22 | # rubocop:disable Metrics/ModuleLength, Style/Documentation 23 | module CliHelper 24 | include Configurable 25 | include Logging 26 | include ShellOut 27 | 28 | # rubocop:disable Metrics/AbcSize 29 | def docker_command(cmd, options = {}) 30 | docker = config[:binary].dup 31 | docker << " -H #{config[:socket]}" if config[:socket] 32 | docker << " --tls" if config[:tls] 33 | docker << " --tlsverify" if config[:tls_verify] 34 | docker << " --tlscacert=#{config[:tls_cacert]}" if config[:tls_cacert] 35 | docker << " --tlscert=#{config[:tls_cert]}" if config[:tls_cert] 36 | docker << " --tlskey=#{config[:tls_key]}" if config[:tls_key] 37 | logger.debug("docker_command: #{docker} #{cmd} shell_opts: #{docker_shell_opts(options)}") 38 | run_command("#{docker} #{cmd}", docker_shell_opts(options)) 39 | end 40 | # rubocop:enable Metrics/AbcSize 41 | 42 | # Copied from kitchen because we need stderr 43 | # rubocop:disable Metrics/MethodLength, Metrics/AbcSize 44 | def run_command(cmd, options = {}) 45 | if options.fetch(:use_sudo, false) 46 | cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}" 47 | end 48 | subject = "[#{options.fetch(:log_subject, "local")} command]" 49 | 50 | debug("#{subject} BEGIN (#{cmd})") 51 | sh = Mixlib::ShellOut.new(cmd, shell_opts(options)) 52 | sh.run_command 53 | debug("#{subject} END #{Util.duration(sh.execution_time)}") 54 | sh.error! 55 | sh.stdout + sh.stderr 56 | rescue Mixlib::ShellOut::ShellCommandFailed => ex 57 | raise ShellCommandFailed, ex.message 58 | rescue Exception => error # rubocop:disable Lint/RescueException 59 | error.extend(Kitchen::Error) 60 | raise 61 | end 62 | # rubocop:enable Metrics/MethodLength, Metrics/AbcSize 63 | 64 | # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize 65 | def build_run_command(image_id, transport_port = nil) 66 | cmd = "run -d" 67 | cmd << " -i" if config[:interactive] 68 | cmd << " -t" if config[:tty] 69 | cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables] 70 | cmd << " -p #{transport_port}" unless transport_port.nil? 71 | Array(config[:forward]).each { |port| cmd << " -p #{port}" } 72 | Array(config[:dns]).each { |dns| cmd << " --dns #{dns}" } 73 | Array(config[:add_host]).each { |host, ip| cmd << " --add-host=#{host}:#{ip}" } 74 | Array(config[:volume]).each { |volume| cmd << " -v #{volume}" } 75 | Array(config[:volumes_from]).each { |container| cmd << " --volumes-from #{container}" } 76 | Array(config[:links]).each { |link| cmd << " --link #{link}" } 77 | Array(config[:devices]).each { |device| cmd << " --device #{device}" } 78 | Array(config[:mount]).each { |mount| cmd << " --mount #{mount}" } 79 | Array(config[:tmpfs]).each { |tmpfs| cmd << " --tmpfs #{tmpfs}" } 80 | cmd << " --name #{config[:instance_name]}" if config[:instance_name] 81 | cmd << " -P" if config[:publish_all] 82 | cmd << " -h #{config[:hostname]}" if config[:hostname] 83 | cmd << " -m #{config[:memory]}" if config[:memory] 84 | cmd << " -c #{config[:cpu]}" if config[:cpu] 85 | cmd << " --gpus #{config[:gpus]}" if config[:gpus] 86 | cmd << " -e http_proxy=#{config[:http_proxy]}" if config[:http_proxy] 87 | cmd << " -e https_proxy=#{config[:https_proxy]}" if config[:https_proxy] 88 | cmd << " --privileged" if config[:privileged] 89 | cmd << " --isolation #{config[:isolation]}" if config[:isolation] 90 | Array(config[:cap_add]).each { |cap| cmd << " --cap-add=#{cap}" } if config[:cap_add] 91 | Array(config[:cap_drop]).each { |cap| cmd << " --cap-drop=#{cap}" } if config[:cap_drop] 92 | Array(config[:security_opt]).each { |opt| cmd << " --security-opt=#{opt}" } if config[:security_opt] 93 | cmd << " --platform=#{config[:docker_platform]}" if config[:docker_platform] 94 | extra_run_options = config_to_options(config[:run_options]) 95 | cmd << " #{extra_run_options}" unless extra_run_options.empty? 96 | cmd << " #{image_id} #{config[:run_command]}" 97 | logger.debug("build_run_command: #{cmd}") 98 | cmd 99 | end 100 | # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize 101 | 102 | # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize 103 | def build_exec_command(state, command) 104 | cmd = "exec" 105 | cmd << " -d" if config[:detach] 106 | cmd << build_env_variable_args(config[:env_variables]) if config[:env_variables] 107 | cmd << " --privileged" if config[:privileged] 108 | cmd << " -t" if config[:tty] 109 | cmd << " -i" if config[:interactive] 110 | cmd << " -u #{config[:username]}" if config[:username] 111 | cmd << " -w #{config[:working_dir]}" if config[:working_dir] 112 | cmd << " #{state[:container_id]}" 113 | cmd << " #{command}" 114 | logger.debug("build_exec_command: #{cmd}") 115 | cmd 116 | end 117 | # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/AbcSize 118 | 119 | def build_copy_command(local_file, remote_file, opts = {}) 120 | cmd = "cp" 121 | cmd << " -a" if opts[:archive] 122 | cmd << " #{local_file} #{remote_file}" 123 | cmd 124 | end 125 | 126 | def build_powershell_command(args) 127 | cmd = "powershell -ExecutionPolicy Bypass -NoLogo " 128 | cmd << args 129 | logger.debug("build_powershell_command: #{cmd}") 130 | cmd 131 | end 132 | 133 | def build_env_variable_args(vars) 134 | raise ActionFailed, "Environment variables are not of a Hash type" unless vars.is_a?(Hash) 135 | 136 | args = "" 137 | vars.each do |k, v| 138 | args << " -e #{k.to_s.strip}=\"#{v.to_s.strip}\"" 139 | end 140 | 141 | args 142 | end 143 | 144 | def dev_null 145 | case RbConfig::CONFIG["host_os"] 146 | when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ 147 | "NUL" 148 | else 149 | "/dev/null" 150 | end 151 | end 152 | 153 | def docker_shell_opts(options = {}) 154 | options[:live_stream] = nil if options[:suppress_output] 155 | options.delete(:suppress_output) 156 | 157 | options 158 | end 159 | 160 | # Convert the config input for `:build_options` or `:run_options` in to a 161 | # command line string for use with Docker. 162 | # 163 | # @since 2.5.0 164 | # @param config [nil, String, Array, Hash] Config data to convert. 165 | # @return [String] 166 | # rubocop:disable Metrics/CyclomaticComplexity 167 | def config_to_options(config) 168 | case config 169 | when nil 170 | "" 171 | when String 172 | config 173 | when Array 174 | config.map { |c| config_to_options(c) }.join(" ") 175 | when Hash 176 | config.map { |k, v| Array(v).map { |c| "--#{k}=#{Shellwords.escape(c)}" }.join(" ") }.join(" ") 177 | end 178 | end 179 | # rubocop:enable Metrics/CyclomaticComplexity 180 | end 181 | # rubocop:enable Metrics/ModuleLength, Style/Documentation 182 | end 183 | end 184 | end 185 | -------------------------------------------------------------------------------- /lib/kitchen/docker/helpers/container_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require "erb" unless defined?(Erb) 15 | require "json" unless defined?(JSON) 16 | require "shellwords" unless defined?(Shellwords) 17 | require "tempfile" unless defined?(Tempfile) 18 | require "uri" unless defined?(URI) 19 | 20 | require "kitchen" 21 | require "kitchen/configurable" 22 | require_relative "../erb_context" 23 | require_relative "cli_helper" 24 | 25 | module Kitchen 26 | module Docker 27 | module Helpers 28 | # rubocop:disable Metrics/ModuleLength, Style/Documentation 29 | module ContainerHelper 30 | include Configurable 31 | include Kitchen::Docker::Helpers::CliHelper 32 | 33 | def parse_container_id(output) 34 | container_id = output.chomp 35 | 36 | unless [12, 64].include?(container_id.size) 37 | raise ActionFailed, "Could not parse Docker run output for container ID" 38 | end 39 | 40 | container_id 41 | end 42 | 43 | def dockerfile_template 44 | template = IO.read(File.expand_path(config[:dockerfile])) 45 | context = Kitchen::Docker::ERBContext.new(config.to_hash) 46 | ERB.new(template).result(context.get_binding) 47 | end 48 | 49 | def remote_socket? 50 | config[:socket] ? socket_uri.scheme == "tcp" : false 51 | end 52 | 53 | def socket_uri 54 | URI.parse(config[:socket]) 55 | end 56 | 57 | def dockerfile_path(file) 58 | config[:build_context] ? Pathname.new(file.path).relative_path_from(Pathname.pwd).to_s : file.path 59 | end 60 | 61 | def container_exists?(state) 62 | state[:container_id] && !!docker_command("top #{state[:container_id]}") rescue false 63 | end 64 | 65 | def container_exec(state, command) 66 | cmd = build_exec_command(state, command) 67 | docker_command(cmd) 68 | rescue => e 69 | raise "Failed to execute command on Docker container. #{e}" 70 | end 71 | 72 | def create_dir_on_container(state, path) 73 | path = replace_env_variables(state, path) 74 | cmd = "mkdir -p #{path}" 75 | 76 | if state[:platform].include?("windows") 77 | psh = "-Command if(-not (Test-Path \'#{path}\')) { New-Item -Path \'#{path}\' -Force }" 78 | cmd = build_powershell_command(psh) 79 | end 80 | 81 | cmd = build_exec_command(state, cmd) 82 | docker_command(cmd) 83 | rescue => e 84 | raise "Failed to create directory #{path} on container. #{e}" 85 | end 86 | 87 | def copy_file_to_container(state, local_file, remote_file) 88 | debug("Copying local file #{local_file} to #{remote_file} on container") 89 | 90 | remote_file = replace_env_variables(state, remote_file) 91 | 92 | remote_file = "#{state[:container_id]}:#{remote_file}" 93 | cmd = build_copy_command(local_file, remote_file) 94 | docker_command(cmd) 95 | rescue => e 96 | raise "Failed to copy file #{local_file} to container. #{e}" 97 | end 98 | 99 | # rubocop:disable Metrics/AbcSize, Metrics/MethodLength 100 | def container_env_variables(state) 101 | # Retrieves all environment variables from inside container 102 | vars = {} 103 | 104 | if state[:platform].include?("windows") 105 | cmd = build_powershell_command("-Command [System.Environment]::GetEnvironmentVariables() ^| ConvertTo-Json") 106 | cmd = build_exec_command(state, cmd) 107 | stdout = docker_command(cmd, suppress_output: !logger.debug?).strip 108 | vars = ::JSON.parse(stdout) 109 | else 110 | cmd = build_exec_command(state, "printenv") 111 | stdout = docker_command(cmd, suppress_output: !logger.debug?).strip 112 | stdout.split("\n").each { |line| vars[line.split("=")[0]] = line.split("=")[1] } 113 | end 114 | 115 | vars 116 | end 117 | # rubocop:enable Metrics/AbcSize, Metrics/MethodLength 118 | 119 | def replace_env_variables(state, str) 120 | if str.include?("$env:") 121 | key = str[/\$env:(.*?)(\\|$)/, 1] 122 | value = container_env_variables(state)[key].to_s.strip 123 | str = str.gsub("$env:#{key}", value) 124 | elsif str.include?("$") 125 | key = str[%r{\$(.*?)(/|$)}, 1] 126 | value = container_env_variables(state)[key].to_s.strip 127 | str = str.gsub("$#{key}", value) 128 | end 129 | 130 | str 131 | end 132 | 133 | def run_container(state, transport_port = nil) 134 | cmd = build_run_command(state[:image_id], transport_port) 135 | output = docker_command(cmd) 136 | parse_container_id(output) 137 | end 138 | 139 | def container_ip_address(state) 140 | cmd = "inspect --format '{{ .NetworkSettings.IPAddress }}'" 141 | cmd << " #{state[:container_id]}" 142 | docker_command(cmd).strip 143 | rescue 144 | raise ActionFailed, "Error getting internal IP of Docker container" 145 | end 146 | 147 | def remove_container(state) 148 | container_id = state[:container_id] 149 | docker_command("stop -t 0 #{container_id}") 150 | docker_command("rm #{container_id}") 151 | end 152 | 153 | def dockerfile_proxy_config 154 | env_variables = "" 155 | if config[:http_proxy] 156 | env_variables << "ENV http_proxy #{config[:http_proxy]}\n" 157 | env_variables << "ENV HTTP_PROXY #{config[:http_proxy]}\n" 158 | end 159 | 160 | if config[:https_proxy] 161 | env_variables << "ENV https_proxy #{config[:https_proxy]}\n" 162 | env_variables << "ENV HTTPS_PROXY #{config[:https_proxy]}\n" 163 | end 164 | 165 | if config[:no_proxy] 166 | env_variables << "ENV no_proxy #{config[:no_proxy]}\n" 167 | env_variables << "ENV NO_PROXY #{config[:no_proxy]}\n" 168 | end 169 | 170 | env_variables 171 | end 172 | end 173 | # rubocop:enable Metrics/ModuleLength, Style/Documentation 174 | end 175 | end 176 | end 177 | -------------------------------------------------------------------------------- /lib/kitchen/docker/helpers/dockerfile_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require "kitchen" 15 | require "kitchen/configurable" 16 | 17 | module Kitchen 18 | module Docker 19 | module Helpers 20 | module DockerfileHelper 21 | include Configurable 22 | 23 | def dockerfile_platform 24 | case config[:platform] 25 | when "arch" 26 | arch_platform 27 | when "debian", "ubuntu" 28 | debian_platform 29 | when "fedora" 30 | fedora_platform 31 | when "gentoo" 32 | gentoo_platform 33 | when "gentoo-paludis" 34 | gentoo_paludis_platform 35 | when "opensuse/tumbleweed", "opensuse/leap", "opensuse", "sles" 36 | opensuse_platform 37 | when "rhel", "centos", "oraclelinux", "amazonlinux" 38 | rhel_platform 39 | when "centosstream" 40 | centosstream_platform 41 | when "almalinux" 42 | almalinux_platform 43 | when "rockylinux" 44 | rockylinux_platform 45 | when "photon" 46 | photonos_platform 47 | else 48 | raise ActionFailed, "Unknown platform '#{config[:platform]}'" 49 | end 50 | end 51 | 52 | def arch_platform 53 | # See https://bugs.archlinux.org/task/47052 for why we 54 | # blank out limits.conf. 55 | <<-CODE 56 | RUN pacman --noconfirm -Sy archlinux-keyring 57 | RUN pacman-db-upgrade 58 | RUN pacman --noconfirm -Syu openssl openssh sudo curl 59 | RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key 60 | RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key 61 | RUN echo >/etc/security/limits.conf 62 | CODE 63 | end 64 | 65 | def debian_platform 66 | disable_upstart = <<-CODE 67 | RUN [ ! -f "/sbin/initctl" ] || dpkg-divert --local --rename --add /sbin/initctl \ 68 | && ln -sf /bin/true /sbin/initctl 69 | CODE 70 | packages = <<-CODE 71 | ENV DEBIAN_FRONTEND noninteractive 72 | ENV container docker 73 | RUN apt-get update 74 | RUN apt-get install -y sudo openssh-server curl lsb-release 75 | CODE 76 | config[:disable_upstart] ? disable_upstart + packages : packages 77 | end 78 | 79 | def fedora_platform 80 | <<-CODE 81 | ENV container docker 82 | RUN dnf clean all 83 | RUN dnf install -y sudo openssh-server openssh-clients which curl 84 | RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N '' 85 | RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N '' 86 | CODE 87 | end 88 | 89 | def gentoo_platform 90 | <<-CODE 91 | RUN emerge-webrsync 92 | RUN emerge --quiet --noreplace net-misc/openssh app-admin/sudo 93 | RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key 94 | RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key 95 | CODE 96 | end 97 | 98 | def gentoo_paludis_platform 99 | <<-CODE 100 | RUN cave sync 101 | RUN cave resolve -zx net-misc/openssh app-admin/sudo 102 | RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key 103 | RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key 104 | CODE 105 | end 106 | 107 | def opensuse_platform 108 | <<-CODE 109 | ENV container docker 110 | RUN zypper install -y sudo openssh which curl gawk 111 | RUN /usr/sbin/sshd-gen-keys-start 112 | CODE 113 | end 114 | 115 | def rhel_platform 116 | <<-CODE 117 | ENV container docker 118 | RUN yum clean all 119 | RUN yum install -y sudo openssh-server openssh-clients which curl 120 | RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N '' 121 | RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N '' 122 | CODE 123 | end 124 | 125 | def centosstream_platform 126 | <<-CODE 127 | ENV container docker 128 | RUN yum clean all 129 | RUN yum install -y sudo openssh-server openssh-clients which 130 | RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N '' 131 | RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N '' 132 | CODE 133 | end 134 | 135 | def almalinux_platform 136 | <<-CODE 137 | ENV container docker 138 | RUN yum clean all 139 | RUN yum install -y sudo openssh-server openssh-clients which 140 | RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N '' 141 | RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N '' 142 | CODE 143 | end 144 | 145 | def rockylinux_platform 146 | <<-CODE 147 | ENV container docker 148 | RUN yum clean all 149 | RUN yum install -y sudo openssh-server openssh-clients which 150 | RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N '' 151 | RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N '' 152 | CODE 153 | end 154 | 155 | def photonos_platform 156 | <<-CODE 157 | ENV container docker 158 | RUN tdnf clean all 159 | RUN tdnf install -y sudo openssh-server openssh-clients which curl 160 | RUN [ -f "/etc/ssh/ssh_host_ecdsa_key" ] || ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key -N '' 161 | RUN [ -f "/etc/ssh/ssh_host_ed25519_key" ] || ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N '' 162 | CODE 163 | end 164 | 165 | def dockerfile_base_linux(username, homedir) 166 | <<-CODE 167 | RUN if ! getent passwd #{username}; then \ 168 | useradd -d #{homedir} -m -s /bin/bash -p '*' #{username}; \ 169 | fi 170 | RUN mkdir -p /etc/sudoers.d 171 | RUN chmod 0750 /etc/sudoers.d 172 | RUN echo "#{username} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/#{username} 173 | RUN echo "Defaults !requiretty" >> /etc/sudoers.d/#{username} 174 | RUN mkdir -p #{homedir}/.ssh 175 | RUN chown -R #{username} #{homedir}/.ssh 176 | RUN chmod 0700 #{homedir}/.ssh 177 | RUN touch #{homedir}/.ssh/authorized_keys 178 | RUN chown #{username} #{homedir}/.ssh/authorized_keys 179 | RUN chmod 0600 #{homedir}/.ssh/authorized_keys 180 | RUN mkdir -p /run/sshd 181 | CODE 182 | end 183 | end 184 | end 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /lib/kitchen/docker/helpers/file_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require "fileutils" unless defined?(FileUtils) 15 | 16 | module Kitchen 17 | module Docker 18 | module Helpers 19 | module FileHelper 20 | def create_temp_file(file, contents) 21 | debug("[Docker] Creating temp file #{file}") 22 | debug("[Docker] --- Start Temp File Contents ---") 23 | debug(contents) 24 | debug("[Docker] --- End Temp File Contents ---") 25 | 26 | begin 27 | path = ::File.dirname(file) 28 | ::FileUtils.mkdir_p(path) unless ::Dir.exist?(path) 29 | file = ::File.open(file, "w") 30 | file.write(contents) 31 | rescue IOError => e 32 | raise "Failed to write temp file. Error Details: #{e}" 33 | ensure 34 | file.close unless file.nil? 35 | end 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/kitchen/docker/helpers/image_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require "kitchen" 15 | require "kitchen/configurable" 16 | require "pathname" unless defined?(Pathname) 17 | require_relative "cli_helper" 18 | require_relative "container_helper" 19 | 20 | module Kitchen 21 | module Docker 22 | module Helpers 23 | module ImageHelper 24 | include Configurable 25 | include Kitchen::Docker::Helpers::CliHelper 26 | include Kitchen::Docker::Helpers::ContainerHelper 27 | 28 | def parse_image_id(output) 29 | output.split("\n").reverse_each do |line| 30 | if line =~ /writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i 31 | img_id = line[/writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i, 1] 32 | return img_id 33 | end 34 | if line =~ /image id|build successful|successfully built/i 35 | img_id = line.split(/\s+/).last 36 | return img_id 37 | end 38 | end 39 | raise ActionFailed, "Could not parse Docker build output for image ID" 40 | end 41 | 42 | def remove_image(state) 43 | image_id = state[:image_id] 44 | if image_in_use?(state) 45 | info("[Docker] Image ID #{image_id} is in use. Skipping removal") 46 | else 47 | info("[Docker] Removing image with Image ID #{image_id}.") 48 | docker_command("rmi #{image_id}") 49 | end 50 | end 51 | 52 | def image_in_use?(state) 53 | docker_command("ps -a", suppress_output: !logger.debug?).include?(state[:image_id]) 54 | end 55 | 56 | def build_image(state, dockerfile) 57 | cmd = "build" 58 | cmd << " --no-cache" unless config[:use_cache] 59 | cmd << " --platform=#{config[:docker_platform]}" if config[:docker_platform] 60 | extra_build_options = config_to_options(config[:build_options]) 61 | cmd << " #{extra_build_options}" unless extra_build_options.empty? 62 | dockerfile_contents = dockerfile 63 | file = Tempfile.new("Dockerfile-kitchen", Pathname.pwd + config[:build_tempdir]) 64 | cmd << " -f #{Shellwords.escape(dockerfile_path(file))}" if config[:build_context] 65 | build_context = config[:build_context] ? "." : "-" 66 | output = begin 67 | file.write(dockerfile) 68 | file.close 69 | docker_command("#{cmd} #{build_context}", 70 | input: dockerfile_contents, 71 | environment: { BUILDKIT_PROGRESS: "plain" }) 72 | ensure 73 | file.close unless file.closed? 74 | file.unlink 75 | end 76 | 77 | parse_image_id(output) 78 | end 79 | 80 | def image_exists?(state) 81 | state[:image_id] && !!docker_command("inspect --type=image #{state[:image_id]}") rescue false 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/kitchen/docker/helpers/inspec_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | # This helper should be removed when the kitchen-inspec gem has been updated to include these runner options 15 | begin 16 | require "kitchen/verifier/inspec" 17 | 18 | # Add runner options for Docker transport for kitchen-inspec gem 19 | module Kitchen 20 | module Docker 21 | module Helpers 22 | module InspecHelper 23 | Kitchen::Verifier::Inspec.class_eval do 24 | def runner_options_for_docker(config_data) 25 | opts = { 26 | "backend" => "docker", 27 | "logger" => logger, 28 | "host" => config_data[:container_id], 29 | } 30 | logger.debug "Connect to Container: #{opts["host"]}" 31 | opts 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | rescue LoadError => e 39 | logger.debug("[Docker] kitchen-inspec gem not found for InSpec verifier. #{e}") 40 | end 41 | -------------------------------------------------------------------------------- /lib/kitchen/driver/docker.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2014, Sean Porter 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | require "kitchen" 17 | require "json" unless defined?(JSON) 18 | require "securerandom" unless defined?(SecureRandom) 19 | require "net/ssh" unless defined?(Net::SSH) 20 | 21 | require "kitchen/driver/base" 22 | 23 | require_relative "../docker/container/linux" 24 | require_relative "../docker/container/windows" 25 | require_relative "../docker/helpers/cli_helper" 26 | require_relative "../docker/helpers/container_helper" 27 | 28 | module Kitchen 29 | module Driver 30 | # Docker driver for Kitchen. 31 | # 32 | # @author Sean Porter 33 | class Docker < Kitchen::Driver::Base 34 | include Kitchen::Docker::Helpers::CliHelper 35 | include Kitchen::Docker::Helpers::ContainerHelper 36 | include ShellOut 37 | 38 | default_config :binary, "docker" 39 | default_config :build_options, nil 40 | default_config :build_tempdir, Dir.pwd 41 | default_config :cap_add, nil 42 | default_config :cap_drop, nil 43 | default_config :disable_upstart, true 44 | default_config :env_variables, nil 45 | default_config :isolation, nil 46 | default_config :interactive, false 47 | default_config :private_key, File.join(Dir.pwd, ".kitchen", "docker_id_rsa") 48 | default_config :privileged, false 49 | default_config :public_key, File.join(Dir.pwd, ".kitchen", "docker_id_rsa.pub") 50 | default_config :publish_all, false 51 | default_config :remove_images, false 52 | default_config :run_options, nil 53 | default_config :security_opt, nil 54 | default_config :tls, false 55 | default_config :tls_cacert, nil 56 | default_config :tls_cert, nil 57 | default_config :tls_key, nil 58 | default_config :tls_verify, false 59 | default_config :tty, false 60 | default_config :use_cache, true 61 | default_config :use_internal_docker_network, false 62 | default_config :use_sudo, false 63 | default_config :wait_for_transport, true 64 | 65 | default_config :build_context do |driver| 66 | !driver.remote_socket? 67 | end 68 | 69 | default_config :image, &:default_image 70 | 71 | default_config :instance_name do |driver| 72 | # Borrowed from kitchen-rackspace 73 | [ 74 | driver.instance.name.gsub(/\W/, ""), 75 | (Etc.getlogin || "nologin").gsub(/\W/, ""), 76 | Socket.gethostname.gsub(/\W/, "")[0..20], 77 | Array.new(8) { rand(36).to_s(36) }.join, 78 | ].join("-").downcase 79 | end 80 | 81 | default_config :platform, &:default_platform 82 | 83 | default_config :run_command do |driver| 84 | if driver.windows_os? 85 | # Launch arbitrary process to keep the Windows container alive 86 | # If running in interactive mode, launch powershell.exe instead 87 | if driver[:interactive] 88 | "powershell.exe" 89 | else 90 | "ping -t localhost" 91 | end 92 | else 93 | "/usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes "\ 94 | "-o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid" 95 | end 96 | end 97 | 98 | default_config :socket do |driver| 99 | socket = "unix:///var/run/docker.sock" 100 | socket = "npipe:////./pipe/docker_engine" if driver.windows_os? 101 | ENV["DOCKER_HOST"] || socket 102 | end 103 | 104 | default_config :username do |driver| 105 | # Return nil to prevent username from being added to Docker 106 | # command line args for Windows if a username was not specified 107 | if driver.windows_os? 108 | nil 109 | else 110 | "kitchen" 111 | end 112 | end 113 | 114 | def verify_dependencies 115 | run_command("#{config[:binary]} >> #{dev_null} 2>&1", quiet: true, use_sudo: config[:use_sudo]) 116 | rescue 117 | raise UserError, "You must first install the Docker CLI tool https://www.docker.com/get-started" 118 | end 119 | 120 | def create(state) 121 | container.create(state) 122 | 123 | wait_for_transport(state) 124 | end 125 | 126 | def destroy(state) 127 | container.destroy(state) 128 | end 129 | 130 | def wait_for_transport(state) 131 | if config[:wait_for_transport] 132 | instance.transport.connection(state, &:wait_until_ready) 133 | end 134 | end 135 | 136 | def default_image 137 | platform, release = instance.platform.name.split("-") 138 | if platform == "centos" && release 139 | release = "centos" + release.split(".").first 140 | end 141 | release ? [platform, release].join(":") : platform 142 | end 143 | 144 | def default_platform 145 | instance.platform.name.split("-").first 146 | end 147 | 148 | protected 149 | 150 | def container 151 | @container ||= if windows_os? 152 | Kitchen::Docker::Container::Windows.new(config) 153 | else 154 | Kitchen::Docker::Container::Linux.new(config) 155 | end 156 | @container 157 | end 158 | end 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /lib/kitchen/transport/docker.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | require "kitchen" 15 | 16 | require_relative "../docker/container/linux" 17 | require_relative "../docker/container/windows" 18 | 19 | require_relative "../docker/helpers/inspec_helper" 20 | 21 | require_relative "../../docker/version" 22 | require_relative "../../train/docker" 23 | 24 | module Kitchen 25 | module Transport 26 | class Docker < Kitchen::Transport::Base 27 | class DockerFailed < TransportFailed; end 28 | 29 | kitchen_transport_api_version 1 30 | plugin_version Kitchen::VERSION 31 | 32 | default_config :binary, "docker" 33 | default_config :env_variables, nil 34 | default_config :interactive, false 35 | default_config :privileged, false 36 | default_config :tls, false 37 | default_config :tls_cacert, nil 38 | default_config :tls_cert, nil 39 | default_config :tls_key, nil 40 | default_config :tls_verify, false 41 | default_config :tty, false 42 | default_config :working_dir, nil 43 | 44 | default_config :socket do |transport| 45 | socket = "unix:///var/run/docker.sock" 46 | socket = "npipe:////./pipe/docker_engine" if transport.windows_os? 47 | ENV["DOCKER_HOST"] || socket 48 | end 49 | 50 | default_config :temp_dir do |transport| 51 | if transport.windows_os? 52 | "$env:TEMP" 53 | else 54 | "/tmp" 55 | end 56 | end 57 | 58 | default_config :username do |transport| 59 | # Return an empty string to prevent username from being added to Docker 60 | # command line args for Windows if a username was not specified 61 | if transport.windows_os? 62 | nil 63 | else 64 | "kitchen" 65 | end 66 | end 67 | 68 | def connection(state, &block) 69 | options = config.to_hash.merge(state) 70 | options[:platform] = instance.platform.name 71 | 72 | # Set value for DOCKER_HOST environment variable for the docker-api gem 73 | # This allows Windows systems to use the TCP socket for the InSpec verifier 74 | # See the lib/docker.rb file here: https://github.com/swipely/docker-api/blob/master/lib/docker.rb 75 | # default_socket_url is set to a Unix socket and env_url requires an environment variable to be set 76 | ENV["DOCKER_HOST"] = options[:socket] if !options[:socket].nil? && ENV["DOCKER_HOST"].nil? 77 | 78 | Kitchen::Transport::Docker::Connection.new(options, &block) 79 | end 80 | 81 | class Connection < Kitchen::Transport::Docker::Connection 82 | # Include the InSpec patches to be able to execute tests on Windows containers 83 | include Kitchen::Docker::Helpers::InspecHelper 84 | 85 | def execute(command) 86 | return if command.nil? 87 | 88 | debug("[Docker] Executing command: #{command}") 89 | info("[Docker] Executing command on container") 90 | 91 | container.execute(command) 92 | rescue => e 93 | raise DockerFailed, "Docker failed to execute command on container. Error Details: #{e}" 94 | end 95 | 96 | def upload(locals, remote) 97 | container.upload(locals, remote) 98 | end 99 | 100 | def container 101 | @container ||= if @options[:platform].include?("windows") 102 | Kitchen::Docker::Container::Windows.new(@options) 103 | else 104 | Kitchen::Docker::Container::Linux.new(@options) 105 | end 106 | @container 107 | end 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/train/docker.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | # Monkey patched Docker train transport to support running the InSpec verifier on Windows 15 | begin 16 | # Requires train gem with a minimum version of 2.1.0 17 | require "train" 18 | 19 | module Train::Transports 20 | # Patched train transport with Windows support for InSpec verifier 21 | class Docker < Train.plugin(1) 22 | name "docker" 23 | 24 | include_options Train::Extras::CommandWrapper 25 | option :host, required: true 26 | 27 | def connection(state = {}, &block) 28 | opts = merge_options(options, state || {}) 29 | validate_options(opts) 30 | 31 | if @connection && @connection_options == opts 32 | reuse_connection(&block) 33 | else 34 | create_new_connection(opts, &block) 35 | end 36 | end 37 | 38 | private 39 | 40 | # Creates a new Docker connection instance and save it for potential future 41 | # reuse. 42 | # 43 | # @param options [Hash] connection options 44 | # @return [Docker::Connection] a Docker connection instance 45 | # @api private 46 | def create_new_connection(options, &block) 47 | if @connection 48 | logger.debug("[Docker] shutting previous connection #{@connection}") 49 | @connection.close 50 | end 51 | 52 | @connection_options = options 53 | @connection = Connection.new(options, &block) 54 | end 55 | 56 | # Return the last saved Docker connection instance. 57 | # 58 | # @return [Docker::Connection] a Docker connection instance 59 | # @api private 60 | def reuse_connection 61 | logger.debug("[Docker] reusing existing connection #{@connection}") 62 | yield @connection if block_given? 63 | @connection 64 | end 65 | end 66 | end 67 | 68 | class Train::Transports::Docker 69 | class Connection < BaseConnection 70 | def initialize(conf) 71 | super(conf) 72 | @id = options[:host] 73 | @container = ::Docker::Container.get(@id) || 74 | raise("Can't find Docker container #{@id}") 75 | @cmd_wrapper = nil 76 | @cmd_wrapper = CommandWrapper.load(self, @options) 77 | self # rubocop:disable Lint/Void TODO: Remove in future pass 78 | end 79 | 80 | def uri 81 | if @container.nil? 82 | "docker://#{@id}" 83 | else 84 | "docker://#{@container.id}" 85 | end 86 | end 87 | 88 | private 89 | 90 | def file_via_connection(path) 91 | if os.aix? 92 | Train::File::Remote::Aix.new(self, path) 93 | elsif os.solaris? 94 | Train::File::Remote::Unix.new(self, path) 95 | elsif os.windows? 96 | Train::File::Remote::Windows.new(self, path) 97 | else 98 | Train::File::Remote::Linux.new(self, path) 99 | end 100 | end 101 | 102 | def platform_specific_cmd(cmd) 103 | return cmd if @container.info.nil? 104 | 105 | if @container.info["Platform"] == "windows" 106 | ["cmd.exe", "/c", cmd] 107 | else 108 | ["/bin/sh", "-c", cmd] 109 | end 110 | end 111 | 112 | def run_command_via_connection(cmd, &_data_handler) 113 | cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil? 114 | stdout, stderr, exit_status = @container.exec(platform_specific_cmd(cmd)) 115 | CommandResult.new(stdout.join, stderr.join, exit_status) 116 | rescue ::Docker::Error::DockerError => _ 117 | raise 118 | rescue => _ 119 | # @TODO: differentiate any other error 120 | raise 121 | end 122 | end 123 | end 124 | rescue LoadError => e 125 | logger.debug("[Docker] train gem not found for InSpec verifier. #{e}") 126 | end 127 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | ":disableDependencyDashboard", 6 | "schedule:automergeEarlyMondays" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | RUN yum clean all 3 | RUN yum install -y sudo openssh-server openssh-clients which curl htop 4 | RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key 5 | RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key 6 | RUN mkdir -p /var/run/sshd 7 | RUN useradd -d /home/<%= @username %> -m -s /bin/bash <%= @username %> 8 | RUN echo <%= "#{@username}:#{@password}" %> | chpasswd 9 | RUN echo '<%= @username %> ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 10 | RUN mkdir -p /home/<%= @username %>/.ssh 11 | RUN chown -R <%= @username %> /home/<%= @username %>/.ssh 12 | RUN chmod 0700 /home/<%= @username %>/.ssh 13 | RUN touch /home/<%= @username %>/.ssh/authorized_keys 14 | RUN chown <%= @username %> /home/<%= @username %>/.ssh/authorized_keys 15 | RUN chmod 0600 /home/<%= @username %>/.ssh/authorized_keys 16 | RUN curl -L https://www.chef.io/chef/install.sh | bash 17 | RUN echo '<%= IO.read(@public_key).strip %>' >> /home/<%= @username %>/.ssh/authorized_keys 18 | -------------------------------------------------------------------------------- /test/integration/capabilities/disabled/capabilities_drop_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Disable now busser-serever is gone. 18 | # require 'serverspec' 19 | # set :backend, :exec 20 | 21 | # describe command('/sbin/ifconfig eth0 multicast') do 22 | # its(:exit_status) { is_expected.to_not eq 0 } 23 | # its(:stderr) { is_expected.to match /Operation not permitted/ } 24 | # end 25 | -------------------------------------------------------------------------------- /test/integration/default/disabled/default_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Disable now busser-serever is gone. 18 | # require 'serverspec' 19 | # require 'spec_helper' 20 | 21 | # # Just make sure the image launched and is reachable. 22 | # describe command('true') do 23 | # its(:exit_status) { is_expected.to eq 0 } 24 | # end 25 | -------------------------------------------------------------------------------- /test/integration/default/disabled/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | 15 | # case RbConfig::CONFIG['host_os'] 16 | # when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ 17 | # set :backend, :cmd 18 | # set :os, :family => 'windows' 19 | # else 20 | # set :backend, :exec 21 | # end 22 | -------------------------------------------------------------------------------- /test/integration/inspec/inspec_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Just make sure the image launched and is reachable. 18 | if os[:family] == "windows" 19 | describe command("echo 1") do 20 | its(:exit_status) { is_expected.to eq 0 } 21 | end 22 | else 23 | describe command("true") do 24 | its(:exit_status) { is_expected.to eq 0 } 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/spec/docker_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "spec_helper" 18 | 19 | describe Kitchen::Driver::Docker do 20 | describe "#config_to_options" do 21 | let(:config) {} 22 | subject { described_class.new.send(:config_to_options, config) } 23 | 24 | context "with nil" do 25 | let(:config) { nil } 26 | it { is_expected.to eq "" } 27 | end # /context with nil 28 | 29 | context "with a string" do 30 | let(:config) { "--foo" } 31 | it { is_expected.to eq "--foo" } 32 | end # /context with a string 33 | 34 | context "with a string with spaces" do 35 | let(:config) { "--foo bar" } 36 | it { is_expected.to eq "--foo bar" } 37 | end # /context with a string with spaces 38 | 39 | context "with an array of strings" do 40 | let(:config) { %w{--foo --bar} } 41 | it { is_expected.to eq "--foo --bar" } 42 | end # /context with an array of strings 43 | 44 | context "with an array of hashes" do 45 | let(:config) { [{ foo: "bar" }, { other: "baz" }] } 46 | it { is_expected.to eq "--foo=bar --other=baz" } 47 | end # /context with an array of hashes 48 | 49 | context "with a hash of strings" do 50 | let(:config) { { foo: "bar", other: "baz" } } 51 | it { is_expected.to eq "--foo=bar --other=baz" } 52 | end # /context with a hash of strings 53 | 54 | context "with a hash of arrays" do 55 | let(:config) { { foo: %w{bar baz} } } 56 | it { is_expected.to eq "--foo=bar --foo=baz" } 57 | end # /context with a hash of arrays 58 | 59 | context "with a hash of strings with spaces" do 60 | let(:config) { { foo: "bar two", other: "baz" } } 61 | it { is_expected.to eq '--foo=bar\\ two --other=baz' } 62 | end # /context with a hash of strings with spaces 63 | end # /describe #config_to_options 64 | end 65 | -------------------------------------------------------------------------------- /test/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2016, Noah Kantrowitz 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | require "rake" 18 | require "rspec" 19 | require "rspec/its" 20 | 21 | require "kitchen/driver/docker" 22 | 23 | RSpec.configure do |config| 24 | # Basic configuraiton 25 | config.run_all_when_everything_filtered = true 26 | config.filter_run(:focus) 27 | 28 | # Run specs in random order to surface order dependencies. If you find an 29 | # order dependency and want to debug it, you can fix the order by providing 30 | # the seed, which is printed after each run. 31 | # --seed 1234 32 | config.order = "random" 33 | end 34 | --------------------------------------------------------------------------------