├── .gitignore ├── .kitchen.yml ├── .travis.yml ├── Berksfile ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── TODO ├── chef-provisioning-docker.gemspec ├── lib └── chef │ └── provisioning │ ├── docker_driver.rb │ ├── docker_driver │ ├── chef_zero_http_proxy.rb │ ├── docker_container_machine.rb │ ├── docker_run_options.rb │ ├── docker_transport.rb │ ├── driver.rb │ └── version.rb │ └── driver_init │ └── docker.rb ├── spec ├── docker_support.rb ├── integration │ └── primitives_spec.rb └── spec_helper.rb └── test ├── Dockerfiles └── chef-client-11.10.4 │ ├── Dockerfile │ ├── client.rb │ └── validation.pem ├── demo ├── Berksfile ├── Berksfile.lock └── run.sh ├── ec2.rb ├── integration ├── cookbooks │ └── docker-tests │ │ ├── metadata.rb │ │ └── recipes │ │ ├── change-container-state.rb │ │ ├── create-containers.rb │ │ ├── create-user.rb │ │ └── default.rb └── default │ └── serverspec │ ├── Gemfile │ └── default_spec.rb ├── mongo_cluster.rb └── mongo_host.rb /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .vagrant 3 | Berksfile.lock 4 | *.gem 5 | *.rbc 6 | .bundle 7 | .config 8 | .yardoc 9 | Gemfile.lock 10 | InstalledFiles 11 | _yardoc 12 | coverage 13 | doc/ 14 | lib/bundler/man 15 | pkg 16 | rdoc 17 | spec/reports 18 | test/tmp 19 | test/version_tmp 20 | tmp 21 | *.bundle 22 | *.so 23 | *.o 24 | *.a 25 | mkmf.log 26 | *.sw* 27 | clients 28 | nodes 29 | docs/examples/clients 30 | docs/examples/nodes 31 | docs/examples/data_bags 32 | x.rb 33 | .idea/ 34 | .kitchen/* 35 | -------------------------------------------------------------------------------- /.kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: vagrant 4 | synced_folders: 5 | - ['.', '/opt/chef-provisioning-docker'] 6 | 7 | provisioner: 8 | name: chef_zero 9 | 10 | platforms: 11 | - name: ubuntu-14.04 12 | - name: centos-7.1 13 | 14 | suites: 15 | - name: default 16 | run_list: 17 | - recipe[docker-tests] 18 | - recipe[docker-tests::create-containers] 19 | - recipe[docker-tests::change-container-state] 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | branches: 4 | only: 5 | - master 6 | rvm: 7 | - 2.2.5 8 | - 2.3.1 9 | script: bundle exec rake build 10 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.chef.io' 2 | 3 | cookbook 'docker', '>= 2.5.8' 4 | 5 | group :integration do 6 | cookbook 'ubuntu' 7 | cookbook 'yum-epel' 8 | cookbook 'openssh' 9 | cookbook 'docker-tests', :path => './test/integration/cookbooks/docker-tests' 10 | end 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [0.11.0](https://github.com/chef/chef-provisioning-docker/tree/0.11.0) (2018-03-12) 4 | 5 | - Update from ChefMetal to Chef::Provisioning 6 | - Added the ability to override default proxy gateway 7 | - Updated the required chef-provisioning release to 2.0+, which also increases the required Ruby release to 2.2.2+ 8 | 9 | ## [0.10.0](https://github.com/chef/chef-provisioning-docker/tree/0.10.0) (2016-08-29) 10 | 11 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.9.0...0.10.0) 12 | 13 | **Merged pull requests:** 14 | 15 | - Loosen chef-provisioning dep and test on modern rubies [#102](https://github.com/chef/chef-provisioning-docker/pull/102) ([tas50](https://github.com/tas50)) 16 | - specify machine_options correctly in README [#96](https://github.com/chef/chef-provisioning-docker/pull/96) ([jgoulah](https://github.com/jgoulah)) 17 | 18 | ## [v0.9.0](https://github.com/chef/chef-provisioning-docker/tree/v0.9.0) (2016-03-25) 19 | 20 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v1.0.0.beta.3...v0.9.0) 21 | 22 | **Merged pull requests:** 23 | 24 | - avoid overwriting client_key when merging config [#92](https://github.com/chef/chef-provisioning-docker/pull/92) ([jgoulah](https://github.com/jgoulah)) 25 | 26 | ## [v1.0.0.beta.3](https://github.com/chef/chef-provisioning-docker/tree/v1.0.0.beta.3) (2016-03-24) 27 | 28 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v1.0.0.beta.2...v1.0.0.beta.3) 29 | 30 | **Merged pull requests:** 31 | 32 | - fix explanation of the recipe [#89](https://github.com/chef/chef-provisioning-docker/pull/89) ([jgoulah](https://github.com/jgoulah)) 33 | - remove logic that changes hostname to an ip [#88](https://github.com/chef/chef-provisioning-docker/pull/88) ([jgoulah](https://github.com/jgoulah)) 34 | - don't pass empty volumes - fixes issue #86 [#87](https://github.com/chef/chef-provisioning-docker/pull/87) ([jgoulah](https://github.com/jgoulah)) 35 | 36 | ## [v1.0.0.beta.2](https://github.com/chef/chef-provisioning-docker/tree/v1.0.0.beta.2) (2016-03-24) 37 | 38 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v1.0.0.beta.1...v1.0.0.beta.2) 39 | 40 | **Closed issues:** 41 | 42 | - volumes are not mounting properly [#85](https://github.com/chef/chef-provisioning-docker/issues/85) 43 | - Update /etc/hosts of Docker container [#82](https://github.com/chef/chef-provisioning-docker/issues/82) 44 | - chef server URL being set to "172.17.0.1" and fails SSL verify [#79](https://github.com/chef/chef-provisioning-docker/issues/79) 45 | - docker-machine support [#78](https://github.com/chef/chef-provisioning-docker/issues/78) 46 | 47 | **Merged pull requests:** 48 | 49 | - Add all options [#83](https://github.com/chef/chef-provisioning-docker/pull/83) ([jkeiser](https://github.com/jkeiser)) 50 | 51 | ## [v1.0.0.beta.1](https://github.com/chef/chef-provisioning-docker/tree/v1.0.0.beta.1) (2016-03-11) 52 | 53 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.8.0...v1.0.0.beta.1) 54 | 55 | **Merged pull requests:** 56 | 57 | - Docker toolbox and chef-zero support [#81](https://github.com/chef/chef-provisioning-docker/pull/81) ([jkeiser](https://github.com/jkeiser)) 58 | 59 | ## [v0.8.0](https://github.com/chef/chef-provisioning-docker/tree/v0.8.0) (2016-02-03) 60 | 61 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.7...v0.8.0) 62 | 63 | **Implemented enhancements:** 64 | 65 | - Add gemspec files to allow bundler to run from the gem [#68](https://github.com/chef/chef-provisioning-docker/pull/68) ([ksubrama](https://github.com/ksubrama)) 66 | - leverage exec for transport and implements connect_to_machine and stop_machine [#56](https://github.com/chef/chef-provisioning-docker/pull/56) ([mwrock](https://github.com/mwrock)) 67 | 68 | **Fixed bugs:** 69 | 70 | - gemspec relies on very old chef-provisioning version [#45](https://github.com/chef/chef-provisioning-docker/issues/45) 71 | 72 | **Closed issues:** 73 | 74 | - Is it possible not to have chef run every time the container starts up? [#74](https://github.com/chef/chef-provisioning-docker/issues/74) 75 | - container will not stay running [#71](https://github.com/chef/chef-provisioning-docker/issues/71) 76 | 77 | **Merged pull requests:** 78 | 79 | - Bump revision to 0.8.0, add changelog generator [#76](https://github.com/chef/chef-provisioning-docker/pull/76) ([jkeiser](https://github.com/jkeiser)) 80 | - Adding a CONTRIBUTING document [#64](https://github.com/chef/chef-provisioning-docker/pull/64) ([tyler-ball](https://github.com/tyler-ball)) 81 | - Initial .travis.yml. [#62](https://github.com/chef/chef-provisioning-docker/pull/62) ([randomcamel](https://github.com/randomcamel)) 82 | - adding kitchen tests to validate basic driver functionality [#57](https://github.com/chef/chef-provisioning-docker/pull/57) ([mwrock](https://github.com/mwrock)) 83 | 84 | ## [v0.7](https://github.com/chef/chef-provisioning-docker/tree/v0.7) (2015-06-22) 85 | 86 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.6...v0.7) 87 | 88 | **Fixed bugs:** 89 | 90 | - chef-provisioning-docker doesn't implement :destroy for machine_image [#50](https://github.com/chef/chef-provisioning-docker/issues/50) 91 | 92 | **Closed issues:** 93 | 94 | - Monkey patch EventOutputStream [#49](https://github.com/chef/chef-provisioning-docker/issues/49) 95 | - Monkey patch EventOutputStream [#48](https://github.com/chef/chef-provisioning-docker/issues/48) 96 | - Unable to activate chef-provisioning-docker-0.6 [#46](https://github.com/chef/chef-provisioning-docker/issues/46) 97 | - Connection to Docker image fails [#44](https://github.com/chef/chef-provisioning-docker/issues/44) 98 | - Chef::Exceptions::ContentLengthMismatch: Response body length 8006 does not match HTTP Content-Length header 12829 [#38](https://github.com/chef/chef-provisioning-docker/issues/38) 99 | - Machine image gets immediately deleted after creation [#37](https://github.com/chef/chef-provisioning-docker/issues/37) 100 | - NoMethodError: undefined method `[]' while creating machine from image [#31](https://github.com/chef/chef-provisioning-docker/issues/31) 101 | - Modify way of how use local image as base image [#27](https://github.com/chef/chef-provisioning-docker/issues/27) 102 | - Containers get created in a stopped state. [#24](https://github.com/chef/chef-provisioning-docker/issues/24) 103 | 104 | **Merged pull requests:** 105 | 106 | - Clean up specs/dependencies/Gemfiles. [#58](https://github.com/chef/chef-provisioning-docker/pull/58) ([randomcamel](https://github.com/randomcamel)) 107 | - Implement machine_image :destroy, add specs [#51](https://github.com/chef/chef-provisioning-docker/pull/51) ([randomcamel](https://github.com/randomcamel)) 108 | - Added support for chef-provisioning 1.0 [#47](https://github.com/chef/chef-provisioning-docker/pull/47) ([marc-](https://github.com/marc-)) 109 | - re-order paragraphs so that they are attached to the right code snippet [#43](https://github.com/chef/chef-provisioning-docker/pull/43) ([jamesc](https://github.com/jamesc)) 110 | 111 | ## [v0.6](https://github.com/chef/chef-provisioning-docker/tree/v0.6) (2015-05-12) 112 | 113 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.5.2...v0.6) 114 | 115 | **Closed issues:** 116 | 117 | - In fresh container : /etc/chef/client.pem: no such file or directory [#35](https://github.com/chef/chef-provisioning-docker/issues/35) 118 | - NameError: undefined local variable or method `chef_version' [#30](https://github.com/chef/chef-provisioning-docker/issues/30) 119 | - Add volumes support [#29](https://github.com/chef/chef-provisioning-docker/issues/29) 120 | - docker_options/base_image with docker-api v1.20.0 result in a 404 error [#28](https://github.com/chef/chef-provisioning-docker/issues/28) 121 | - Docker::Error::ServerError: Invalid registry endpoint on CentOS 7 [#25](https://github.com/chef/chef-provisioning-docker/issues/25) 122 | 123 | **Merged pull requests:** 124 | 125 | - Added reference to Docker volumes documentation [#41](https://github.com/chef/chef-provisioning-docker/pull/41) ([marc-](https://github.com/marc-)) 126 | - Added volumes support chef/chef-provisioning-docker#29 [#33](https://github.com/chef/chef-provisioning-docker/pull/33) ([marc-](https://github.com/marc-)) 127 | 128 | ## [v0.5.2](https://github.com/chef/chef-provisioning-docker/tree/v0.5.2) (2015-02-26) 129 | 130 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.5.1...v0.5.2) 131 | 132 | **Closed issues:** 133 | 134 | - ERROR: Connection refused [#23](https://github.com/chef/chef-provisioning-docker/issues/23) 135 | - Will the re-write have the ability to do docker commands from the provisioning run? [#5](https://github.com/chef/chef-provisioning-docker/issues/5) 136 | 137 | ## [v0.5.1](https://github.com/chef/chef-provisioning-docker/tree/v0.5.1) (2014-12-15) 138 | 139 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.5...v0.5.1) 140 | 141 | **Fixed bugs:** 142 | 143 | - Could not find the file /etc/chef/client.pem in container [#13](https://github.com/chef/chef-provisioning-docker/issues/13) 144 | - NoMethodError `\<\<' for #\ 145 | 146 | 147 | #9 148 | 149 | 150 | **Closed issues:** 151 | 152 | - Any plan to port this docker provisioner to the latest chef-metal v0.13 ? [#2](https://github.com/chef/chef-provisioning-docker/issues/2) 153 | 154 | **Merged pull requests:** 155 | 156 | - Fix README typo: CHEF_DRIVER env variable [#19](https://github.com/chef/chef-provisioning-docker/pull/19) ([breezeight](https://github.com/breezeight)) 157 | - added env vars as parameters #14 [#15](https://github.com/chef/chef-provisioning-docker/pull/15) ([matiasdecarli](https://github.com/matiasdecarli)) 158 | 159 | ## [v0.5](https://github.com/chef/chef-provisioning-docker/tree/v0.5) (2014-11-05) 160 | 161 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.4.3...v0.5) 162 | 163 | **Merged pull requests:** 164 | 165 | - Rename to chef-provisioning-docker [#17](https://github.com/chef/chef-provisioning-docker/pull/17) ([jkeiser](https://github.com/jkeiser)) 166 | 167 | ## [v0.4.3](https://github.com/chef/chef-provisioning-docker/tree/v0.4.3) (2014-10-06) 168 | 169 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.4.2...v0.4.3) 170 | 171 | **Closed issues:** 172 | 173 | - Publishing ports [#10](https://github.com/chef/chef-provisioning-docker/issues/10) 174 | 175 | **Merged pull requests:** 176 | 177 | - added the option to use 2 different ports #10 [#12](https://github.com/chef/chef-provisioning-docker/pull/12) ([matiasdecarli](https://github.com/matiasdecarli)) 178 | 179 | ## [v0.4.2](https://github.com/chef/chef-provisioning-docker/tree/v0.4.2) (2014-09-23) 180 | 181 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.4.1...v0.4.2) 182 | 183 | **Fixed bugs:** 184 | 185 | - superclass mismatch for class DockerContainer [#6](https://github.com/chef/chef-provisioning-docker/issues/6) 186 | 187 | **Closed issues:** 188 | 189 | - Enable to provisoning a container [#8](https://github.com/chef/chef-provisioning-docker/issues/8) 190 | 191 | ## [v0.4.1](https://github.com/chef/chef-provisioning-docker/tree/v0.4.1) (2014-08-20) 192 | 193 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.4.0...v0.4.1) 194 | 195 | ## [v0.4.0](https://github.com/chef/chef-provisioning-docker/tree/v0.4.0) (2014-08-19) 196 | 197 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.2...v0.4.0) 198 | 199 | **Merged pull requests:** 200 | 201 | - Cleanup URL parsing and support remote Docker API endpoints [#4](https://github.com/chef/chef-provisioning-docker/pull/4) ([johnewart](https://github.com/johnewart)) 202 | - Machine image support along with the new driver API [#3](https://github.com/chef/chef-provisioning-docker/pull/3) ([johnewart](https://github.com/johnewart)) 203 | 204 | ## [v0.2](https://github.com/chef/chef-provisioning-docker/tree/v0.2) (2014-04-14) 205 | 206 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.1.1...v0.2) 207 | 208 | ## [v0.1.1](https://github.com/chef/chef-provisioning-docker/tree/v0.1.1) (2014-04-12) 209 | 210 | [Full Changelog](https://github.com/chef/chef-provisioning-docker/compare/v0.1...v0.1.1) 211 | 212 | ## [v0.1](https://github.com/chef/chef-provisioning-docker/tree/v0.1) (2014-04-11) 213 | 214 | - _This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)_ 215 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Chef Provisioning Docker 2 | 3 | ## Resource/Provider Acceptance Criteria 4 | 5 | All resources and providers in Chef Provisioning Docker must fulfill the [common acceptance criteria] 6 | (https://github.com/chef/chef-provisioning/blob/master/CONTRIBUTING.md) for 7 | all Chef Provisioning projects. 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chef-provisioning-docker 2 | 3 | [![Build Status](https://travis-ci.org/chef/chef-provisioning-docker.svg?branch=master)](https://travis-ci.org/chef/chef-provisioning-docker) [![Gem Version](https://badge.fury.io/rb/chef-provisioning-docker.svg)](http://badge.fury.io/rb/chef-provisioning-docker) 4 | 5 | How to use: 6 | 7 | First you need to ensure that Docker is running. This can be done on a Linux host using Docker's installers or on OSX using boot2docker. Once you have that, you can install the dependencies with Bundler and then use the Docker like the following: 8 | 9 | ``` 10 | CHEF_DRIVER=docker bundle exec chef-client -z docker_ubuntu_image.rb 11 | ``` 12 | 13 | This will run Chef-zero and use the description stored in docker_ubuntu_image.rb (the second example below). Note that some configuration syntax is likely to change a little bit so be sure to check the documentation. 14 | 15 | ## Machine creation 16 | 17 | Using this , you can then define a machine similar to the following example: 18 | 19 | ```ruby 20 | require 'chef/provisioning/docker_driver' 21 | with_driver 'docker' 22 | 23 | machine 'wario' do 24 | recipe 'openssh::default' 25 | 26 | machine_options( 27 | docker_options: { 28 | base_image: { 29 | name: 'ubuntu', 30 | repository: 'ubuntu', 31 | tag: '14.04' 32 | }, 33 | :command => '/usr/sbin/sshd -p 8022 -D', 34 | 35 | #ENV (Environment Variables) 36 | #Set any env var in the container by using one or more -e flags, even overriding those already defined by the developer with a Dockerfile ENV 37 | :env => { 38 | "deep" => 'purple', 39 | "led" => 'zeppelin' 40 | }, 41 | 42 | # Ports can be one of two forms: 43 | # src_port (string or integer) is a pass-through, i.e 8022 or "9933" 44 | # src:dst (string) is a map from src to dst, i.e "8022:8023" maps 8022 externally to 8023 in the container 45 | 46 | # Example (multiple): 47 | :ports => [8022, "8023:9000", "9500"], 48 | 49 | # Examples (single): 50 | :ports => 1234, 51 | :ports => "2345:6789", 52 | 53 | # Volumes can be one of three forms: 54 | # src_volume (string) is volume to add to container, i.e. creates new volume inside container at "/tmp" 55 | # src:dst (string) mounts host's directory src to container's dst, i.e "/tmp:/tmp1" mounts host's directory /tmp to container's /tmp1 56 | # src:dst:mode (string) mounts host's directory src to container's dst with the specified mount option, i.e "/:/rootfs:ro" mounts read-only host's root (/) folder to container's /rootfs 57 | # See more details on Docker volumes at https://github.com/docker/docker/blob/master/docs/sources/userguide/dockervolumes.md . 58 | 59 | # Example (single): 60 | :volumes => "/tmp", 61 | 62 | # Example (multiple): 63 | :volumes => ["/tmp:/tmp", "/:/rootfs:ro"], 64 | 65 | # if you need to keep stdin open (i.e docker run -i) 66 | # :keep_stdin_open => true 67 | 68 | }, 69 | # optional, default timeout is 600 70 | docker_connection: { 71 | :read_timeout => 1000, 72 | } 73 | ) 74 | 75 | end 76 | ``` 77 | 78 | ## Machine images 79 | 80 | This supports the new machine image paradigm; with Docker you can build a base image, save that and use it to create a new container. Here is an example of this: 81 | 82 | ```ruby 83 | require 'chef/provisioning/docker_driver' 84 | 85 | machine_image 'ssh_server' do 86 | recipe 'openssh' 87 | 88 | machine_options( 89 | :docker_options => { 90 | :base_image => { 91 | :name => 'ubuntu', 92 | :repository => 'ubuntu', 93 | :tag => '14.04' 94 | } 95 | } 96 | ) 97 | end 98 | 99 | machine 'ssh00' do 100 | from_image 'ssh_server' 101 | 102 | machine_options( 103 | :docker_options => { 104 | :command => '/usr/sbin/sshd -D -o UsePAM=no -o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid', 105 | :ports => [22] 106 | } 107 | ) 108 | end 109 | ``` 110 | 111 | This will create a docker container based on Ubuntu 14.04 and then execute the openssh recipe and run the /usr/sbin/sshd command as the container's run command. 112 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'bundler/gem_tasks' 3 | 4 | task :spec do 5 | require File.expand_path('spec/run') 6 | end 7 | 8 | require "github_changelog_generator/task" 9 | 10 | GitHubChangelogGenerator::RakeTask.new :changelog do |config| 11 | config.future_release = Chef::Provisioning::DockerDriver::VERSION 12 | config.enhancement_labels = "enhancement,Enhancement,New Feature".split(",") 13 | config.bug_labels = "bug,Bug,Improvement,Upstream Bug".split(",") 14 | config.exclude_labels = "duplicate,question,invalid,wontfix,no_changelog,Exclude From Changelog,Question".split(",") 15 | end 16 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | - docker inspect -> node attributes 2 | - container_configuration/host_configuration not working 3 | - stop docker image if it's running? Don't use the container name for intermediates unless you have to? 4 | - make sure to start container after converge, even if converge didn't run 5 | - throw away container if converge updates 0 resources 6 | - throw away container if converge fails? 7 | - create real machine_image resource that doesn't register against the chef server 8 | - chef-provisioning: make a freaking "chef-provisioning login" that works or I will murder you in your sleep. 9 | - chef-provisioning: make "chef-provisioning cluster" that runs the recipes againgst your knife.rb without registering the workstation with the server 10 | - chef-provisioning: high pri invert complete true, stop stripping automatic and default attributes 11 | - chef-provisioning: rationalize provisioner_options :symbol vs. 'symbol' 12 | - chef-provisioning: two fog provisioners don't share key_pairs :( Fix yer credentials man 13 | - chef-provisioning: :converge action doesn't honor "recipe" 14 | - chef-provisioning: gangbusters error handling (real exceptions) 15 | - chef-provisioning: grab stack trace from remote machine when there is an issue 16 | * chef-provisioning: lxc is broken 17 | - chef-provisioning: first_time and one_time recipes that don't affect the run list 18 | - chef-provisioning: remove any dependencies that require compiling, or package them somehow (omnibus that shit?) 19 | - chef-provisioning-fog: ssh_username and other similar options should not be compute_options, they should be bootstrap options. 20 | - consider renaming provisioner_options to machine_options 21 | - chef-provisioning: clear with_provisioner_options when new with_provisioner is called 22 | - chef-provisioning: find out why chef-provisioning takes so long to install 23 | - chef-provisioning-fog: if in ec2, use private ip by default 24 | * chef-provisioning: allow ssl cert upload, set ssl_verify_mode :verify_peer. run knife ssl check, if it fails, error (and let the user override at their own peril) 25 | * chef-provisioning: validator credentials don't work; transitive credentials require upload 26 | Done 27 | * chef-provisioning: upload files 28 | * chef-provisioning: increase timeout for chef-client in particular, add timeout support to provisioner 29 | -------------------------------------------------------------------------------- /chef-provisioning-docker.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__) + '/lib') 2 | require 'chef/provisioning/docker_driver/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'chef-provisioning-docker' 6 | s.version = Chef::Provisioning::DockerDriver::VERSION 7 | s.platform = Gem::Platform::RUBY 8 | s.extra_rdoc_files = ['README.md', 'LICENSE' ] 9 | s.summary = 'Provisioner for creating Docker containers in Chef Provisioning.' 10 | s.description = s.summary 11 | s.author = 'Tom Duffield' 12 | s.email = 'tom@chef.io' 13 | s.homepage = 'https://github.com/chef/chef-provisioning-docker' 14 | 15 | s.add_dependency 'chef' 16 | s.add_dependency 'chef-provisioning', '>= 2.0', '< 3.0' 17 | s.add_dependency 'docker-api', '~> 1.26', '>= 1.26.2' 18 | s.add_dependency 'minitar' 19 | s.add_dependency 'sys-proctable' 20 | 21 | s.add_development_dependency 'rspec' 22 | s.add_development_dependency 'rake' 23 | s.add_development_dependency 'github_changelog_generator' 24 | 25 | s.bindir = "bin" 26 | s.executables = %w( ) 27 | 28 | s.require_path = 'lib' 29 | s.files = %w(Gemfile Rakefile LICENSE README.md) + Dir.glob("*.gemspec") + 30 | Dir.glob("{distro,lib,tasks,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } 31 | end 32 | -------------------------------------------------------------------------------- /lib/chef/provisioning/docker_driver.rb: -------------------------------------------------------------------------------- 1 | require 'chef/provisioning' 2 | require 'chef/provisioning/docker_driver/driver' 3 | -------------------------------------------------------------------------------- /lib/chef/provisioning/docker_driver/chef_zero_http_proxy.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'uri' 3 | 4 | class ChefZeroHttpProxy 5 | 6 | def initialize(local_address, local_port, remote_address, remote_port) 7 | @local_address = local_address 8 | @local_port = local_port 9 | @remote_address = remote_address 10 | @remote_port = remote_port 11 | end 12 | 13 | def run 14 | begin 15 | Chef::Log.debug("Running proxy main loop on #{@local_address}:#{@local_port}!") 16 | 17 | # Start our server to handle connections (will raise things on errors) 18 | @socket = TCPServer.new @local_address, @local_port 19 | 20 | # Handle every request in another thread 21 | loop do 22 | s = @socket.accept 23 | Thread.new s, &method(:handle_request) 24 | end 25 | 26 | ensure 27 | @socket.close if @socket 28 | end 29 | end 30 | 31 | def handle_request(to_client) 32 | begin 33 | request_line = to_client.readline 34 | 35 | verb = request_line[/^\w+/] 36 | url = request_line[/^\w+\s+(\S+)/, 1] 37 | version = request_line[/HTTP\/(1\.\d)\s*$/, 1] 38 | uri = URI::parse url 39 | 40 | # Show what got requested 41 | Chef::Log.debug("[C->S]: #{verb} -> #{url}") 42 | 43 | querystr = if uri.query 44 | "#{uri.path}?#{uri.query}" 45 | else 46 | uri.path 47 | end 48 | 49 | to_server = TCPSocket.new(@remote_address, @remote_port) 50 | 51 | to_server.write("#{verb} #{querystr} HTTP/#{version}\r\n") 52 | 53 | content_len = 0 54 | 55 | loop do 56 | line = to_client.readline 57 | 58 | if line =~ /^Content-Length:\s+(\d+)\s*$/ 59 | content_len = $1.to_i 60 | end 61 | 62 | # Strip proxy headers 63 | if line =~ /^proxy/i 64 | next 65 | elsif line.strip.empty? 66 | to_server.write("Connection: close\r\n\r\n") 67 | 68 | if content_len >= 0 69 | to_server.write(to_client.read(content_len)) 70 | Chef::Log.debug("[C->S]: Wrote #{content_len} bytes") 71 | end 72 | 73 | break 74 | else 75 | to_server.write(line) 76 | end 77 | end 78 | 79 | buff = '' 80 | while to_server.read(8192, buff) 81 | to_client.write(buff) 82 | end 83 | 84 | rescue 85 | Chef::Log.error $! 86 | raise 87 | 88 | ensure 89 | # Close the sockets 90 | to_client.close 91 | to_server.close 92 | end 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /lib/chef/provisioning/docker_driver/docker_container_machine.rb: -------------------------------------------------------------------------------- 1 | require 'chef/provisioning/machine/unix_machine' 2 | require 'chef/provisioning/docker_driver/docker_run_options' 3 | 4 | class Chef 5 | module Provisioning 6 | module DockerDriver 7 | class DockerContainerMachine < Chef::Provisioning::Machine::UnixMachine 8 | 9 | # Expects a machine specification, a usable transport and convergence strategy 10 | # Options is expected to contain the optional keys 11 | # :command => the final command to execute 12 | # :ports => a list of port numbers to listen on 13 | def initialize(machine_spec, transport, convergence_strategy, connection, command = nil) 14 | super(machine_spec, transport, convergence_strategy) 15 | @command = command 16 | @transport = transport 17 | @connection = connection 18 | end 19 | 20 | def setup_convergence(action_handler) 21 | # Build a converge container to converge in 22 | transport.container = build_converge_container(action_handler) 23 | unless transport.container.info['State']['Running'] 24 | action_handler.perform_action "start converge container chef-converge.#{machine_spec.name}" do 25 | transport.container.start! 26 | end 27 | end 28 | super(action_handler) 29 | # Commit after convergence setup (such as the install of Chef) 30 | # to break up the cost of the commit and avoid read timeouts 31 | transport.container.commit 32 | end 33 | 34 | def converge(action_handler) 35 | # First, grab and start the converge container if it's there ... 36 | transport.container = converge_container_for(machine_spec) 37 | if !transport.container 38 | raise "No converge container found! Did you run `:converge` without first running `:setup`?" 39 | end 40 | unless transport.container.info['State']['Running'] 41 | action_handler.perform_action "start converge container chef-converge.#{machine_spec.name}" do 42 | transport.container.start! 43 | end 44 | end 45 | 46 | # Then, converge ... 47 | super(action_handler) 48 | 49 | # Save the converged image ... 50 | converged_image = commit_converged_image(action_handler, machine_spec, transport.container) 51 | 52 | # Build the new container 53 | transport.container = create_container(action_handler, machine_spec, converged_image) 54 | 55 | # Finally, start it! 56 | action_handler.perform_action "start container #{machine_spec.name}" do 57 | transport.container.start! 58 | end 59 | end 60 | 61 | private 62 | 63 | def container_config(action_handler, machine_spec) 64 | docker_options = machine_spec.reference['docker_options'] || {} 65 | 66 | # We're going to delete things to make it easier on ourselves, back it up 67 | docker_options = docker_options.dup 68 | 69 | # Bring in from_image 70 | if machine_spec.from_image 71 | docker_options['base_image'] ||= {} 72 | docker_options['base_image']['name'] = machine_spec.from_image 73 | end 74 | 75 | # Respect :container_config 76 | config = stringize_keys(docker_options.delete('container_config') || {}) 77 | 78 | # Respect :base_image 79 | image = base_image(action_handler, docker_options.delete('base_image')) 80 | config['Image'] = image if image 81 | 82 | # Respect everything else 83 | DockerRunOptions.include_command_line_options_in_container_config(config, docker_options) 84 | end 85 | 86 | # Get the converge container for this machine 87 | def converge_container_for(machine_spec) 88 | begin 89 | Docker::Container.get("chef-converge.#{machine_spec.name}", {}, @connection) 90 | rescue Docker::Error::NotFoundError 91 | end 92 | end 93 | 94 | def container_for(machine_spec) 95 | begin 96 | Docker::Container.get(machine_spec.name, {}, @connection) 97 | rescue Docker::Error::NotFoundError 98 | end 99 | end 100 | 101 | # Builds a container that has the same properties as the final container, 102 | # but with a couple of tweaks to allow processes to run and converge the 103 | # container. 104 | def build_converge_container(action_handler) 105 | # If a converge container already exists, do nothing. TODO check if it's different!!! 106 | converge_container = converge_container_for(machine_spec) 107 | if converge_container 108 | return converge_container 109 | end 110 | 111 | # Create a chef-capable container (just like the final one, but with --net=host 112 | # and a command that keeps it open). Base it on the image. 113 | config = container_config(action_handler, machine_spec) 114 | config.merge!( 115 | 'name' => "chef-converge.#{machine_spec.reference['container_name']}", 116 | 'Cmd' => [ "/bin/sh", "-c", "while true;do sleep 1000; done" ], 117 | ) 118 | # If we're using Docker Toolkit, we need to use host networking for the converge 119 | # so we can open up the port we need. Don't force it in other cases, though. 120 | if transport.is_local_machine(URI(transport.config[:chef_server_url]).host) && 121 | transport.docker_toolkit_transport(@connection.url) 122 | config['HostConfig'] ||= {} 123 | config['HostConfig'].merge!('NetworkMode' => 'host') 124 | # These are incompatible with NetworkMode: host 125 | config['HostConfig'].delete('Links') 126 | config['HostConfig'].delete('ExtraHosts') 127 | config.delete('NetworkSettings') 128 | end 129 | # Don't use any resources that need to be shared (such as exposed ports) 130 | config.delete('ExposedPorts') 131 | 132 | Chef::Log.debug("Creating converge container with config #{config} ...") 133 | action_handler.perform_action "create container to converge #{machine_spec.name}" do 134 | # create deletes the name :( 135 | Docker::Container.create(config.dup, @connection) 136 | converge_container = Docker::Container.get(config['name'], {}, @connection) 137 | Chef::Log.debug("Created converge container #{converge_container.id}") 138 | end 139 | converge_container 140 | end 141 | 142 | # Commit the converged container to an image. Called by converge. 143 | def commit_converged_image(action_handler, machine_spec, converge_container) 144 | # Commit the converged container to an image 145 | converged_image = nil 146 | action_handler.perform_action "commit and delete converged container for #{machine_spec.name}" do 147 | converged_image = converge_container.commit 148 | converge_container.stop! 149 | converge_container.delete 150 | end 151 | converged_image 152 | end 153 | 154 | # Create the final container from the converged image 155 | def create_container(action_handler, machine_spec, converged_image) 156 | # Check if the container already exists. 157 | container = container_for(machine_spec) 158 | if container 159 | # If it's the same image, just return; don't stop and start. 160 | if container.info['Image'] == converged_image.id 161 | return container 162 | else 163 | # If the container exists but is based on an old image, destroy it. 164 | action_handler.perform_action "stop and delete running container for #{machine_spec.name}" do 165 | container.stop! 166 | container.delete 167 | end 168 | end 169 | end 170 | 171 | # Create the new container 172 | config = container_config(action_handler, machine_spec) 173 | config.merge!( 174 | 'name' => machine_spec.reference['container_name'], 175 | 'Image' => converged_image.id 176 | ) 177 | action_handler.perform_action "create final container for #{machine_spec.name}" do 178 | container = Docker::Container.create(config, @connection) 179 | machine_spec.reference['container_id'] = container.id 180 | machine_spec.save(action_handler) 181 | end 182 | container 183 | end 184 | 185 | def stringize_keys(hash) 186 | hash.each_with_object({}) do |(k,v),hash| 187 | v = stringize_keys(v) if v.is_a?(Hash) 188 | hash[k.to_s] = v 189 | end 190 | end 191 | 192 | def base_image(action_handler, base_image_value) 193 | case base_image_value 194 | when Hash 195 | params = base_image_value.dup 196 | if !params['fromImage'] 197 | params['fromImage'] = params.delete('name') 198 | params['fromImage'] = "#{params['fromImage']}:#{params.delete('tag')}" if params['tag'] 199 | end 200 | when String 201 | params = { 'fromImage' => base_image_value } 202 | when nil 203 | return nil 204 | else 205 | raise "Unexpected type #{base_image_value.class} for docker_options[:base_image]!" 206 | end 207 | 208 | image_name = params['fromImage'] 209 | repo, image_name = params['fromImage'].split('/', 2) if params['fromImage'].include?('/') 210 | 211 | begin 212 | image = Docker::Image.get(image_name, {}, @connection) 213 | rescue Docker::Error::NotFoundError 214 | # If it's not found, pull it. 215 | action_handler.perform_action "pull #{params}" do 216 | image = Docker::Image.create(params, @connection) 217 | end 218 | end 219 | 220 | image.id 221 | end 222 | end 223 | end 224 | end 225 | end 226 | -------------------------------------------------------------------------------- /lib/chef/provisioning/docker_driver/docker_run_options.rb: -------------------------------------------------------------------------------- 1 | class Chef 2 | module Provisioning 3 | module DockerDriver 4 | # 5 | # Allows the user to specify docker options that calculate the desired container config 6 | # 7 | # Command line options follow later in the file (search for `cli_option :command` for the first one). 8 | # 9 | # The API allows these settings: 10 | # 11 | # (https://docs.docker.com/engine/reference/api/docker_remote_api_v1.22/#create-a-container) 12 | # 13 | # ArgsEscaped - bool // True if command is already escaped (Windows specific) 14 | # AttachStderr - Boolean value, attaches to stderr. 15 | # - `docker run --attach STDERR` 16 | # AttachStdin - Boolean value, attaches to stdin. 17 | # - `docker run --attach STDIN` 18 | # - `docker run --interactive` 19 | # AttachStdout - Boolean value, attaches to stdout. 20 | # - `docker run --attach STDOUT` 21 | # Cmd - Command to run specified as a string or an array of strings. 22 | # - `docker run COMMAND` 23 | # Cpuset - Deprecated please don’t use. Use CpusetCpus instead. 24 | # Domainname - A string value containing the domain name to use for the container. 25 | # Entrypoint - Set the entry point for the container as a string or an array of strings. 26 | # - `docker run --entrypoint "COMMAND"` 27 | # Env - A list of environment variables in the form of ["VAR=value"[,"VAR2=value2"]] 28 | # - `docker --env "A=B"` 29 | # ExposedPorts - An object mapping ports to an empty object in the form of: "ExposedPorts": { "/: {}" } 30 | # - `docker run --expose PORT` 31 | # - `docker run --publish 8080:8081` 32 | # HostConfig/AutoRemove - bool // Automatically remove container when it exits 33 | # - `docker run --rm` 34 | # HostConfig/Binds – A list of volume bindings for this container. Each volume binding is a string in one of these forms: 35 | # - container_path to create a new volume for the container 36 | # - host_path:container_path to bind-mount a host path into the container 37 | # - host_path:container_path:ro to make the bind-mount read-only inside the container. 38 | # - volume_name:container_path to bind-mount a volume managed by a volume plugin into the container. 39 | # - volume_name:container_path:ro to make the bind mount read-only inside the container. 40 | # - `docker run --volume /host/path:/container/path` 41 | # HostConfig/BlkioBps - uint64 // Maximum Bytes per second for the container system drive 42 | # HostConfig/BlkioDeviceReadBps - Limit read rate (bytes per second) from a device in the form of: "BlkioDeviceReadBps": [{"Path": "device_path", "Rate": rate}], for example: "BlkioDeviceReadBps": [{"Path": "/dev/sda", "Rate": "1024"}]" 43 | # - `docker run --device-read-bps=/dev/sda:1mb` 44 | # HostConfig/BlkioDeviceReadIOps - Limit read rate (IO per second) from a device in the form of: "BlkioDeviceReadIOps": [{"Path": "device_path", "Rate": rate}], for example: "BlkioDeviceReadIOps": [{"Path": "/dev/sda", "Rate": "1000"}] 45 | # - `docker run --device-read-iops=/dev/sda:1000` 46 | # HostConfig/BlkioDeviceWriteBps - Limit write rate (bytes per second) to a device in the form of: "BlkioDeviceWriteBps": [{"Path": "device_path", "Rate": rate}], for example: "BlkioDeviceWriteBps": [{"Path": "/dev/sda", "Rate": "1024"}]" 47 | # - `docker run --device-write-bps=/dev/sda:1mb` 48 | # HostConfig/BlkioDeviceWriteIOps - Limit write rate (IO per second) to a device in the form of: "BlkioDeviceWriteIOps": [{"Path": "device_path", "Rate": rate}], for example: "BlkioDeviceWriteIOps": [{"Path": "/dev/sda", "Rate": "1000"}] 49 | # - `docker run --device-write-iops=/dev/sda:1000` 50 | # HostConfig/BlkioIOps - uint64 // Maximum IOps for the container system drive 51 | # HostConfig/BlkioWeight - Block IO weight (relative weight) accepts a weight value between 10 and 1000. 52 | # - `docker run --blkio-weight 0` 53 | # HostConfig/BlkioWeightDevice - Block IO weight (relative device weight) in the form of: "BlkioWeightDevice": [{"Path": "device_path", "Weight": weight}] 54 | # - `docker run --blkio-weight-device path:weight` 55 | # HostConfig/CapAdd - A list of kernel capabilities to add to the container. 56 | # - `docker run --cap-add capability` 57 | # HostConfig/CapDrop - A list of kernel capabilities to drop from the container. 58 | # - `docker run --cap-drop capability` 59 | # HostConfig/CgroupParent - Path to cgroups under which the container’s cgroup is created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups are created if they do not already exist. 60 | # - `docker run --cgroup-parent parent` 61 | # HostConfig/ConsoleSize - [2]int // Initial console size 62 | # HostConfig/ContainerIDFile - string // File (path) where the containerId is written 63 | # - `docker run --cidfile file` 64 | # HostConfig/CpuPeriod - The length of a CPU period in microseconds. 65 | # - `docker run --cpu-period 0` 66 | # HostConfig/CpuQuota - Microseconds of CPU time that the container can get in a CPU period. 67 | # - `docker run --cpu-quota 0` 68 | # HostConfig/CpuShares - An integer value containing the container’s CPU Shares (ie. the relative weight vs other containers). 69 | # - `docker run --cpu-shares 0` 70 | # HostConfig/CpusetCpus - String value containing the cgroups CpusetCpus to use. 71 | # - `docker run --cpuset-cpus 0-3` 72 | # HostConfig/CpusetMems - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. 73 | # - `docker run --cpuset-mems 0-3` 74 | # HostConfig/Devices - A list of devices to add to the container specified as a JSON object in the form { "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"} 75 | # - `docker run --device path_on_host:path_in_container:cgroup_permissions` 76 | # HostConfig/DiskQuota - int64 // Disk limit (in bytes) 77 | # HostConfig/Dns - A list of DNS servers for the container to use. 78 | # - `docker run --dns ip` 79 | # HostConfig/DnsOptions - A list of DNS options 80 | # - `docker run --dns-opt a=b` 81 | # HostConfig/DnsSearch - A list of DNS search domains 82 | # - `docker run --dns-opt domain` 83 | # HostConfig/ExtraHosts - A list of hostnames/IP mappings to add to the container’s /etc/hosts file. Specified in the form ["hostname:IP"]. 84 | # - `docker run --add-host host:ip` 85 | # HostConfig/GroupAdd - A list of additional groups that the container process will run as 86 | # - `docker run --group-add blah` 87 | # HostConfig/IpcMode - IpcMode // IPC namespace to use for the container 88 | # - `docker run --ipc host` 89 | # HostConfig/Isolation - Isolation // Isolation technology of the container (eg default, hyperv) 90 | # - `docker run --isolation host` 91 | # HostConfig/KernelMemory - Kernel memory limit in bytes. 92 | # - `docker run --kernel-memory 4m` 93 | # HostConfig/Links - A list of links for the container. Each link entry should be in the form of container_name:alias. 94 | # - `docker run --link containername` 95 | # HostConfig/LogConfig - Log configuration for the container, specified as a JSON object in the form { "Type": "", "Config": {"key1": "val1"}}. Available types: json-file, syslog, journald, gelf, awslogs, splunk, none. json-file logging driver. 96 | # HostConfig/LogConfig/Type 97 | # - `docker run --log-driver driver` 98 | # HostConfig/LogConfig/Config 99 | # - `docker run --log-opt a=b` 100 | # HostConfig/Memory - Memory limit in bytes. 101 | # - `docker run --memory 4G` 102 | # HostConfig/MemoryReservation - Memory soft limit in bytes. 103 | # - `docker run --memory-reservation 4G` 104 | # HostConfig/MemorySwap - Total memory limit (memory + swap); set -1 to enable unlimited swap. You must use this with memory and make the swap value larger than memory. 105 | # - `docker run --memory-swap 4G` 106 | # HostConfig/MemorySwappiness - Tune a container’s memory swappiness behavior. Accepts an integer between 0 and 100. 107 | # - `docker run --memory-swappiness 50` 108 | # HostConfig/NetworkMode - Sets the networking mode for the container. Supported standard values are: bridge, host, none, and container:. Any other value is taken as a custom network’s name to which this container should connect to. 109 | # - `docker run --net host` 110 | # HostConfig/OomKillDisable - Boolean value, whether to disable OOM Killer for the container or not. 111 | # - `docker run --oom-kill-disable` 112 | # HostConfig/OomScoreAdj - An integer value containing the score given to the container in order to tune OOM killer preferences. 113 | # - `docker run --oom-score-adj 1` 114 | # HostConfig/PidMode - PidMode // PID namespace to use for the container 115 | # - `docker run --pid host` 116 | # HostConfig/PidsLimit - int64 // Setting pids limit for a container 117 | # HostConfig/PortBindings - A map of exposed container ports and the host port they should map to. A JSON object in the form { /: [{ "HostPort": "" }] } Take note that port is specified as a string and not an integer value. 118 | # - `docker run --publish 8080:8081` 119 | # HostConfig/Privileged - Gives the container full access to the host. Specified as a boolean value. 120 | # - `docker run --privileged` 121 | # HostConfig/PublishAllPorts - Allocates a random host port for all of a container’s exposed ports. Specified as a boolean value. 122 | # - `docker run --publish-all` 123 | # HostConfig/ReadonlyRootfs - Mount the container’s root filesystem as read only. Specified as a boolean value. 124 | # - `docker run --read-only` 125 | # HostConfig/RestartPolicy – The behavior to apply when the container exits. The value is an object with a Name property of either "always" to always restart, "unless-stopped" to restart always except when user has manually stopped the container or "on-failure" to restart only when the container exit code is non-zero. If on-failure is used, MaximumRetryCount controls the number of times to retry before giving up. The default is not to restart. (optional) An ever increasing delay (double the previous delay, starting at 100mS) is added before each restart to prevent flooding the server. 126 | # - `docker run --restart no` 127 | # HostConfig/RestartPolicy/Name 128 | # - `docker run --restart always` 129 | # HostConfig/RestartPolicy/MaximumRetryCount 130 | # - `docker run --restart on-failure:20` 131 | # HostConfig/SandboxSize - uint64 // System drive will be expanded to at least this size (in bytes) 132 | # HostConfig/SecurityOpt - A list of string values to customize labels for MLS systems, such as SELinux. 133 | # - docker run --security-opt label:disabled 134 | # HostConfig/ShmSize - Size of /dev/shm in bytes. The size must be greater than 0. If omitted the system uses 64MB. 135 | # - `docker run --shm-size 4m` 136 | # HostConfig/StorageOpt - []string // Storage driver options per container. 137 | # HostConfig/Sysctls - map[string]string // List of Namespaced sysctls used for the container 138 | # HostConfig/Tmpfs - map[string]string // List of tmpfs (mounts) used for the container 139 | # HostConfig/Ulimits - A list of ulimits to set in the container, specified as { "Name": , "Soft": , "Hard": }, for example: Ulimits: { "Name": "nofile", "Soft": 1024, "Hard": 2048 } 140 | # - `docker run --ulimit /dev/sda:1024:2048` 141 | # HostConfig/UTSMode - UTSMode // UTS namespace to use for the container 142 | # - `docker run --uts host` 143 | # HostConfig/UsernsMode - UsernsMode // The user namespace to use for the container 144 | # HostConfig/VolumeDriver - Driver that this container users to mount volumes. 145 | # - `docker run --volume-driver supervolume` 146 | # HostConfig/VolumesFrom - A list of volumes to inherit from another container. Specified in the form [:] 147 | # - `docker run --volumes-from db` 148 | # Hostname - A string value containing the hostname to use for the container. 149 | # - `docker run --hostname blah` 150 | # Image - A string specifying the image name to use for the container. 151 | # - `docker run IMAGE_NAME ...` 152 | # Labels - Adds a map of labels to a container. To specify a map: {"key":"value"[,"key2":"value2"]} 153 | # - `docker run --label a=b` 154 | # MacAddress - string // Mac Address of the container 155 | # - `docker run --mac-address 92:d0:c6:0a:29:33` 156 | # Mounts - An array of mount points in the container. 157 | # NetworkDisabled - Boolean value, when true disables networking for the container 158 | # NetworkSettings//IPAMConfig/IPv4Address 159 | # - `docker run --ip address` 160 | # NetworkSettings//IPAMConfig/IPv6Address 161 | # - `docker run --ip6 address` 162 | # NetworkSettings//Aliases 163 | # - `docker run --net-alias blah` 164 | # OnBuild - []string // ONBUILD metadata that were defined on the image Dockerfile 165 | # OpenStdin - Boolean value, opens stdin, 166 | # - `docker run --interactive` 167 | # PublishService - string // Name of the network service exposed by the container 168 | # StdinOnce - Boolean value, close stdin after the 1 attached client disconnects. 169 | # StopSignal - Signal to stop a container as a string or unsigned integer. SIGTERM by default. 170 | # - `docker run --stop-signal SIGKILL` 171 | # Tty - Boolean value, Attach standard streams to a tty, including stdin if it is not closed. 172 | # - `docker run --tty` 173 | # User - A string value specifying the user inside the container. 174 | # - `docker run --user bob:wheel` 175 | # Volumes - map[string]struct{} // List of volumes (mounts) used for the container 176 | # - `docker run --volume blarghle` 177 | # WorkingDir - A string specifying the working directory for commands to run in. 178 | # - `docker run --workdir /home/uber` 179 | # 180 | # The following are runtime updateable: 181 | # HostConfig/BlkioBps 182 | # HostConfig/BlkioIOps 183 | # HostConfig/BlkioWeight 184 | # HostConfig/BlkioWeightDevice 185 | # HostConfig/BlkioDeviceReadBps 186 | # HostConfig/BlkioDeviceWriteBps 187 | # HostConfig/BlkioDeviceReadIOps 188 | # HostConfig/BlkioDeviceWriteIOps 189 | # HostConfig/CgroupParent 190 | # HostConfig/CpuPeriod 191 | # HostConfig/CpuQuota 192 | # HostConfig/CpuShares 193 | # HostConfig/CpusetCpus 194 | # HostConfig/CpusetMems 195 | # HostConfig/Devices 196 | # HostConfig/DiskQuota 197 | # HostConfig/KernelMemory 198 | # HostConfig/Memory 199 | # HostConfig/MemoryReservation 200 | # HostConfig/MemorySwap 201 | # HostConfig/MemorySwappiness 202 | # HostConfig/OomKillDisable 203 | # HostConfig/PidsLimit 204 | # HostConfig/RestartPolicy 205 | # HostConfig/SandboxSize 206 | # HostConfig/Ulimits 207 | # 208 | class DockerRunOptions 209 | def self.include_command_line_options_in_container_config(config, docker_options) 210 | 211 | # Grab the command line options we've begun supporting 212 | # The following are command line equivalents for `docker run` 213 | docker_options.each do |key, value| 214 | # Remove -, symbolize key 215 | key = key.to_s.gsub('-', '_').to_sym 216 | option = cli_options[key] 217 | if !option 218 | raise "Unknown option in docker_options: #{key.inspect}" 219 | end 220 | 221 | # Figure out the new value 222 | if option[:type] == :boolean 223 | value == !!value 224 | elsif option[:type] == Array 225 | value = Array(value) 226 | elsif option[:type] == Integer 227 | value = parse_int(value) if value.is_a?(String) 228 | elsif option[:type].is_a?(String) 229 | # If it's A:B:C, translate [ "a:b:c", "d:e:f" ] -> [ { "A" => "a", "B" => "b", "C" => "c" }, { "A" => "d", "B" => "e", "C" => "f" } ] 230 | names = option[:type].split(":") 231 | if names.size == 2 && value.is_a?(Hash) 232 | value.map { |key,value| { names[0] => key, names[1] => value } } 233 | else 234 | Array(value).map do |item| 235 | item_values = item.split(":", names.size) 236 | item = Hash[names.zip(item_values)] 237 | end 238 | end 239 | end 240 | 241 | option[:api].each do |api| 242 | # Grab the parent API key so we know what we're setting 243 | api_parent = config 244 | api.split("/")[0..-2].each do |api_key| 245 | api_parent[api_key] = {} if !api_parent[api_key] 246 | api_parent = api_parent[api_key] 247 | end 248 | api_key = api.split("/")[-1] if api 249 | 250 | # Bring in the current value 251 | if option[:type] == Array || option[:type].is_a?(String) 252 | api_parent[api_key] ||= [] 253 | api_parent[api_key] += value 254 | else 255 | api_parent[api_key] = value 256 | end 257 | end 258 | 259 | # Call the block (if any) 260 | if option[:block] 261 | option[:block].call(config, value) 262 | end 263 | end 264 | config 265 | end 266 | 267 | def self.cli_options 268 | @cli_options ||= {} 269 | end 270 | 271 | def self.cli_option(option_name, type=nil, aliases: nil, api: nil, &block) 272 | api = Array(api) 273 | cli_options[option_name] = { type: type, api: Array(api), block: block } 274 | Array(aliases).each do |option_name| 275 | cli_options[option_name] = { type: type, api: Array(api), block: block } 276 | end 277 | end 278 | 279 | # docker run [OPTIONS] IMAGE COMMAND 280 | cli_option :command do |config, command| 281 | command = Shellwords.split(command) if command.is_a?(String) 282 | config["Cmd"] = command 283 | end 284 | 285 | # docker run [OPTIONS] IMAGE COMMAND 286 | cli_option :image, api: "Image" 287 | 288 | # -a, --attach=[] Attach to STDIN, STDOUT or STDERR 289 | cli_option :attach, Array, aliases: :a do |config,value| 290 | Array(value).each do |stream| 291 | # STDIN -> Stdin 292 | stream = stream.to_s.downcase.capitalize 293 | config["Attach#{stream}"] = true 294 | end 295 | end 296 | # --add-host=[] Add a custom host-to-IP mapping (host:ip) 297 | cli_option :add_host, Array, api: "HostConfig/ExtraHosts", aliases: :add_hosts 298 | # --blkio-weight=0 Block IO weight (relative weight) 299 | cli_option :blkio_weight, api: "HostConfig/BlkioWeight" 300 | # --blkio-weight-device=[] Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`) 301 | cli_option :blkio_weight, Array, api: "HostConfig/BlkioWeightDevice", aliases: :blkio_weights 302 | # --cap-add=[] Add Linux capabilities 303 | cli_option :cap_add, Array, api: "HostConfig/CapAdd" 304 | # --cap-drop=[] Drop Linux capabilities 305 | cli_option :cap_drop, Array, api: "HostConfig/CapDrop" 306 | # --cgroup-parent="" Optional parent cgroup for the container 307 | cli_option :cgroup_parent, api: "HostConfig/CgroupParent" 308 | # --cidfile="" Write the container ID to the file 309 | cli_option :cidfile, api: "HostConfig/ContainerIDFile" 310 | # --cpu-period=0 Limit CPU CFS (Completely Fair Scheduler) period 311 | cli_option :cpu_period, api: "HostConfig/CpuShares" 312 | # --cpu-quota=0 Limit CPU CFS (Completely Fair Scheduler) quota 313 | cli_option :cpu_quota, api: "HostConfig/CpuQuota" 314 | # --cpu-shares=0 CPU shares (relative weight) 315 | cli_option :cpu_period, api: "HostConfig/CpuPeriod" 316 | # --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) 317 | cli_option :cpuset_cpus, api: "HostConfig/CpusetCpus" 318 | # --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) 319 | cli_option :cpuset_mems, api: "HostConfig/CpusetMems" 320 | # --device=[] Add a host device to the container 321 | cli_option :device, "PathOnHost:PathInContainer", api: "HostConfig/Devices", aliases: :devices 322 | # --device-read-bps=[] Limit read rate (bytes per second) from a device (e.g., --device-read-bps=/dev/sda:1mb) 323 | cli_option :device_read_bps, "Path:Rate", api: "HostConfig/BlkioDeviceReadBps" 324 | # --device-read-iops=[] Limit read rate (IO per second) from a device (e.g., --device-read-iops=/dev/sda:1000) 325 | cli_option :device_read_iops, "Path:Rate", api: "HostConfig/BlkioDeviceReadIOps" 326 | # --device-write-bps=[] Limit write rate (bytes per second) to a device (e.g., --device-write-bps=/dev/sda:1mb) 327 | cli_option :device_write_bps, "Path:Rate", api: "HostConfig/BlkioDeviceWriteBps" 328 | # --device-write-iops=[] Limit write rate (IO per second) to a device (e.g., --device-write-bps=/dev/sda:1000) 329 | cli_option :device_write_iops, "Path:Rate", api: "HostConfig/BlkioDeviceWriteIOps" 330 | # --dns=[] Set custom DNS servers 331 | cli_option :dns, Array, api: "HostConfig/Dns" 332 | # --dns-opt=[] Set custom DNS options 333 | cli_option :dns_opt, Array, api: "HostConfig/DnsOptions", aliases: :dns_opts 334 | # --dns-search=[] Set custom DNS search domains 335 | cli_option :dns_search, Array, api: "HostConfig/DnsSearch" 336 | # --entrypoint="" Overwrite the default ENTRYPOINT of the image 337 | cli_option :entrypoint do |config, command| 338 | command = Shellwords.split(command) if command.is_a?(String) 339 | config["Entrypoint"] = command 340 | end 341 | # -e, --env=[] Set environment variables 342 | cli_option :env, aliases: :e do |config, env| 343 | if env.is_a?(Hash) 344 | env = env.map { |k,v| "#{k}=#{v}" } 345 | end 346 | config["Env"] ||= [] 347 | config["Env"] += Array(env) 348 | end 349 | # --expose=[] Expose a port or a range of ports 350 | cli_option :expose do |config, value| 351 | config["ExposedPorts"] ||= {} 352 | Array(value).each do |port| 353 | parse_port(port).each do |host_ip, host_port, container_port| 354 | config["ExposedPorts"][container_port] = {} 355 | end 356 | end 357 | end 358 | # --group-add=[] Add additional groups to run as 359 | cli_option :group_add, Array, api: 'HostConfig/GroupAdd' 360 | # -h, --hostname="" Container host name 361 | cli_option :hostname, api: 'HostConfig/Hostname', aliases: :h 362 | # -i, --interactive Keep STDIN open even if not attached 363 | cli_option :interactive, :boolean, api: [ 'OpenStdin', 'AttachStdin' ] 364 | # --ip="" Container IPv4 address (e.g. 172.30.100.104) 365 | cli_option :ip do |config, value| 366 | # Where this goes depends on the network! TODO doesn't work with `--net` 367 | config["NetworkSettings"] ||= {} 368 | network = config["NetworkMode"] || "default" 369 | config["NetworkSettings"][network] ||= {} 370 | config["NetworkSettings"][network]["IPAMConfig"] ||= {} 371 | config["NetworkSettings"][network]["IPAMConfig"]["IPv4Address"] = value 372 | end 373 | # --ip6="" Container IPv6 address (e.g. 2001:db8::33) 374 | cli_option :ip6 do |config, value| 375 | # Where this goes depends on the network! TODO doesn't work with `--net` 376 | config["NetworkSettings"] ||= {} 377 | network = config["NetworkMode"] || "default" 378 | config["NetworkSettings"][network] ||= {} 379 | config["NetworkSettings"][network]["IPAMConfig"] ||= {} 380 | config["NetworkSettings"][network]["IPAMConfig"]["IPv6Address"] = value 381 | end 382 | # --ipc="" IPC namespace to use 383 | cli_option :ipc, api: 'HostConfig/IpcMode' do |config, value| 384 | # TODO this should ONLY be set if security-opt isn't set at all. 385 | config["HostConfig"]["SecurityOpt"] ||= [ "label:disable" ] 386 | end 387 | # --isolation="" Container isolation technology 388 | cli_option :isolation, api: 'Isolation' 389 | # --kernel-memory="" Kernel memory limit 390 | cli_option :kernel_memory, Integer, api: 'KernelMemory' 391 | # -l, --label=[] Set metadata on the container (e.g., --label=com.example.key=value) 392 | cli_option :label, Array, api: "Labels", aliases: [ :l, :labels ] 393 | # --link=[] Add link to another container 394 | cli_option :link, Array, api: "HostConfig/Links", aliases: :links 395 | # --log-driver="" Logging driver for container 396 | cli_option :log_driver, api: "HostConfig/LogConfig/Type" 397 | # --log-opt=[] Log driver specific options 398 | cli_option :log_opt, aliases: :log_opts do |config, value| 399 | config["HostConfig"] ||= {} 400 | config["HostConfig"]["LogConfig"] ||= {} 401 | config["HostConfig"]["LogConfig"]["Type"] ||= {} 402 | Array(value).each do |keyval| 403 | k,v = keyval.split("=", 2) 404 | config["HostConfig"]["LogConfig"][k] = v 405 | end 406 | end 407 | # --mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33) 408 | cli_option :mac_address, api: "MacAddress" 409 | # -m, --memory="" Memory limit 410 | cli_option :memory, Integer, api: "HostConfig/Memory", aliases: :m 411 | # --memory-reservation="" Memory soft limit 412 | cli_option :memory_reservation, Integer, api: "HostConfig/MemoryReservation" 413 | # --memory-swap="" A positive integer equal to memory plus swap. Specify -1 to enable unlimited swap. 414 | cli_option :memory_swap, Integer, api: "HostConfig/MemorySwap" 415 | # --memory-swappiness="" Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. 416 | cli_option :memory_swappiness, Integer, api: "HostConfig/MemorySwappiness" 417 | # --net="bridge" Connect a container to a network 418 | # 'bridge': create a network stack on the default Docker bridge 419 | # 'none': no networking 420 | # 'container:': reuse another container's network stack 421 | # 'host': use the Docker host network stack 422 | # '|': connect to a user-defined network 423 | cli_option :net do |config, value| 424 | value = value.to_s 425 | old_network = config["NetworkMode"] || "default" 426 | config["NetworkMode"] = value 427 | # If we already stored stuff in the default network, move it to the new network 428 | if config["NetworkSettings"] && config["NetworkSettings"][old_network] 429 | config["NetworkSettings"][value] = config["NetworkSettings"].delete(old_network) 430 | end 431 | end 432 | # --net-alias=[] Add network-scoped alias for the container 433 | cli_option :net_alias, aliases: :net_aliases do |config, value| 434 | # Where this goes depends on the network! TODO doesn't work with `--net` 435 | config["NetworkSettings"] ||= {} 436 | network = config["NetworkMode"] || "default" 437 | config["NetworkSettings"][network] ||= {} 438 | config["NetworkSettings"][network]["Aliases"] ||= [] 439 | config["NetworkSettings"][network]["Aliases"] += Array(value) 440 | end 441 | # --oom-kill-disable Whether to disable OOM Killer for the container or not 442 | cli_option :oom_kill_disable, :boolean, api: "HostConfig/OomKillDisable" 443 | # --oom-score-adj=0 Tune the host's OOM preferences for containers (accepts -1000 to 1000) 444 | cli_option :oom_score_adj, Integer, api: "HostConfig/OomScoreAdj" 445 | # --pid="" PID namespace to use 446 | cli_option :pid, api: "HostConfig/PidMode" do |config, value| 447 | # TODO this should ONLY be set if security-opt isn't set at all. 448 | config["HostConfig"]["SecurityOpt"] ||= [ "label:disable" ] 449 | end 450 | # --privileged Give extended privileges to this container 451 | cli_option :privileged, :boolean, api: "HostConfig/Privileged" 452 | # -p, --publish=[] Publish a container's port(s) to the host 453 | cli_option :publish, aliases: [ :p, :ports ] do |config, value| 454 | config["HostConfig"] ||= {} 455 | config["HostConfig"]["PortBindings"] ||= {} 456 | config["ExposedPorts"] ||= {} 457 | 458 | Array(value).each do |port| 459 | parse_port(port).each do |host_ip, host_port, container_port| 460 | config["HostConfig"]["PortBindings"][container_port] ||= [] 461 | config["HostConfig"]["PortBindings"][container_port] << { "HostIp" => host_ip, "HostPort" => host_port } 462 | config["ExposedPorts"][container_port] = {} 463 | end 464 | end 465 | end 466 | # -P, --publish-all Publish all exposed ports to random ports 467 | cli_option :publish_all, :boolean, api: "HostConfig/PublishAllPorts", aliases: :P 468 | # --read-only Mount the container's root filesystem as read only 469 | cli_option :read_only, :boolean, api: "HostConfig/ReadonlyRootfs" 470 | # --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) 471 | cli_option :restart do |config, value| 472 | name, retries = value.split(':') 473 | config["HostConfig"] ||= {} 474 | config["HostConfig"]["RestartPolicy"] ||= {} 475 | config["HostConfig"]["RestartPolicy"]["Name"] = name 476 | if retries 477 | config["HostConfig"]["RestartPolicy"]["MaximumRetryCount"] = retries 478 | else 479 | config["HostConfig"]["RestartPolicy"].delete("MaximumRetryCount") 480 | end 481 | end 482 | # --rm Automatically remove the container when it exits 483 | cli_option :rm, :boolean, api: "HostConfig/AutoRemove" 484 | # --shm-size=[] Size of `/dev/shm`. The format is ``. `number` must be greater than `0`. Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`. 485 | cli_option :shm_size, Integer, api: "HostConfig/ShmSize", aliases: :shm_sizes 486 | # --security-opt=[] Security Options 487 | cli_option :security_opt, Array, api: "HostConfig/SecurityOpt", aliases: :security_opts 488 | # --stop-signal="SIGTERM" Signal to stop a container 489 | cli_option :stop_signal, api: "StopSignal" 490 | # -t, --tty Allocate a pseudo-TTY 491 | cli_option :tty, :boolean, api: "Tty", aliases: :tty 492 | # -u, --user="" Username or UID (format: [:]) 493 | cli_option :user, api: "User", aliases: :u 494 | # --ulimit=[] Ulimit options 495 | cli_option :ulimit, aliases: :ulimits do |config, value| 496 | config["HostConfig"] ||= {} 497 | config["HostConfig"]["Ulimits"] ||= [] 498 | value.each do |ulimit| 499 | type, values = ulimit.split("=", 2) 500 | soft, hard = values.split(":", 2) 501 | config["HostConfig"]["Ulimits"] << { "Name" => type, "Soft" => soft, "Hard" => hard } 502 | end 503 | end 504 | # --uts="" UTS namespace to use 505 | cli_option :uts, api: "HostConfig/UTSMode" 506 | # -v, --volume=[host-src:]container-dest[:] 507 | # Bind mount a volume. The comma-delimited 508 | # `options` are [rw|ro], [z|Z], or 509 | # [[r]shared|[r]slave|[r]private]. The 510 | # 'host-src' is an absolute path or a name 511 | # value. 512 | cli_option :volume, aliases: [ :v, :volumes ] do |config, value| 513 | # Things without : in them at all, are just volumes. 514 | binds, volumes = Array(value).partition { |v| v.include?(':') } 515 | config["HostConfig"] ||= {} 516 | unless binds.empty? 517 | config["HostConfig"]["Binds"] ||= [] 518 | config["HostConfig"]["Binds"] += binds 519 | end 520 | unless volumes.empty? 521 | config["Volumes"] ||= [] 522 | config["Volumes"] += volumes 523 | end 524 | end 525 | # --volume-driver="" Container's volume driver 526 | cli_option :volume_driver, api: "HostConfig/VolumeDriver" 527 | # -w, --workdir="" Working directory inside the container 528 | cli_option :workdir, api: "WorkingDir", aliases: :w 529 | # --volumes-from=[] Mount volumes from the specified container(s) 530 | cli_option :volumes_from, Array, api: 'HostConfig/VolumesFrom' 531 | 532 | # Not relevant to API or Chef: 533 | # -d, --detach Run container in background and print container ID 534 | # --detach-keys Specify the escape key sequence used to detach a container 535 | # --disable-content-trust=true Skip image verification 536 | # --env-file=[] Read in a file of environment variables 537 | # --help Print usage 538 | # --label-file=[] Read in a file of labels (EOL delimited) 539 | # --name="" Assign a name to the container 540 | # --sig-proxy=true Proxy received signals to the process 541 | 542 | private 543 | 544 | # Lifted from docker cookbook 545 | def self.parse_port(v) 546 | parts = v.to_s.split(':') 547 | case parts.length 548 | when 3 549 | host_ip = parts[0] 550 | host_port = parts[1] 551 | container_port = parts[2] 552 | when 2 553 | host_ip = '0.0.0.0' 554 | host_port = parts[0] 555 | container_port = parts[1] 556 | when 1 557 | host_ip = '' 558 | host_port = '' 559 | container_port = parts[0] 560 | end 561 | port_range, protocol = container_port.split('/') 562 | if port_range.include?('-') 563 | port_range = container_port.split('-') 564 | port_range.map!(&:to_i) 565 | Chef::Log.fatal("FATAL: Invalid port range! #{container_port}") if port_range[0] > port_range[1] 566 | port_range = (port_range[0]..port_range[1]).to_a 567 | end 568 | # qualify the port-binding protocol even when it is implicitly tcp #427. 569 | protocol = 'tcp' if protocol.nil? 570 | Array(port_range).map do |port| 571 | [ host_ip, host_port, "#{port}/#{protocol}"] 572 | end 573 | end 574 | 575 | def self.parse_int(value) 576 | value = value.upcase 577 | if value.end_with?("TB") || value.end_with?("T") 578 | value.to_i * 1024*1024*1024*1024 579 | elsif value.end_with?("GB") || value.end_with?("G") 580 | value.to_i * 1024*1024*1024 581 | elsif value.end_with?("MB") || value.end_with?("M") 582 | value.to_i * 1024*1024 583 | elsif value.end_with?("KB") || value.end_with?("K") 584 | value.to_i * 1024 585 | else 586 | value.to_i 587 | end 588 | end 589 | 590 | end 591 | end 592 | end 593 | end 594 | -------------------------------------------------------------------------------- /lib/chef/provisioning/docker_driver/docker_transport.rb: -------------------------------------------------------------------------------- 1 | require 'chef/provisioning/transport' 2 | require 'chef/provisioning/transport/ssh' 3 | require 'chef/provisioning/docker_driver/chef_zero_http_proxy' 4 | require 'docker' 5 | require 'archive/tar/minitar' 6 | require 'shellwords' 7 | require 'uri' 8 | require 'socket' 9 | require 'mixlib/shellout' 10 | require 'sys/proctable' 11 | require 'tempfile' 12 | 13 | class Chef 14 | module Provisioning 15 | module DockerDriver 16 | class DockerTransport < Chef::Provisioning::Transport 17 | def initialize(container, config) 18 | @container = container 19 | @config = config 20 | end 21 | 22 | attr_reader :config 23 | attr_accessor :container 24 | 25 | def execute(command, timeout: nil, keep_stdin_open: nil, tty: nil, detached: nil, **options) 26 | opts = {} 27 | opts[:tty] = tty unless tty.nil? 28 | opts[:detached] = detached unless detached.nil? 29 | opts[:stdin] = keep_stdin_open unless keep_stdin_open.nil? 30 | opts[:wait] = timeout unless timeout.nil? 31 | 32 | command = Shellwords.split(command) if command.is_a?(String) 33 | Chef::Log.debug("execute #{command.inspect} on container #{container.id} with options #{opts}'") 34 | response = container.exec(command, opts) do |stream, chunk| 35 | case stream 36 | when :stdout 37 | stream_chunk(options, chunk, nil) 38 | when :stderr 39 | stream_chunk(options, nil, chunk) 40 | end 41 | end 42 | 43 | Chef::Log.debug("Execute complete: status #{response[2]}") 44 | 45 | DockerResult.new(command.join(' '), options, response[0].join, response[1].join, response[2]) 46 | end 47 | 48 | def read_file(path) 49 | begin 50 | tarfile = '' 51 | # NOTE: this would be more efficient if we made it a stream and passed that to Minitar 52 | container.archive_out(path) do |block| 53 | tarfile << block 54 | end 55 | rescue Docker::Error::NotFoundError 56 | return nil 57 | rescue Docker::Error::ServerError 58 | if $!.message =~ /500/ || $!.message =~ /Could not find the file/ 59 | return nil 60 | else 61 | raise 62 | end 63 | end 64 | 65 | output = '' 66 | Archive::Tar::Minitar::Input.open(StringIO.new(tarfile)) do |inp| 67 | inp.each do |entry| 68 | while next_output = entry.read 69 | output << next_output 70 | end 71 | entry.close 72 | end 73 | end 74 | 75 | output 76 | end 77 | 78 | def write_file(path, content) 79 | tar = StringIO.new(Docker::Util.create_tar(path => content)) 80 | container.archive_in_stream('/') { tar.read } 81 | end 82 | 83 | def download_file(path, local_path) 84 | file = File.open(local_path, 'wb') 85 | begin 86 | file.write(read_file(path)) 87 | file.close 88 | rescue 89 | File.delete(file) 90 | end 91 | end 92 | 93 | def upload_file(local_path, path) 94 | write_file(path, IO.read(local_path, mode: "rb")) 95 | end 96 | 97 | def make_url_available_to_remote(local_url) 98 | uri = URI(local_url) 99 | 100 | if uri.scheme == "chefzero" || is_local_machine(uri.host) 101 | # chefzero: URLs are just http URLs with a shortcut if you are in-process. 102 | # The remote machine is definitely not in-process. 103 | uri.scheme = "http" if uri.scheme == "chefzero" 104 | 105 | if docker_toolkit_transport 106 | # Forward localhost on docker_machine -> chef-zero. The container will 107 | # be able to access this because it was started with --net=host. 108 | uri = docker_toolkit_transport.make_url_available_to_remote(uri.to_s) 109 | uri = URI(uri) 110 | @docker_toolkit_transport_thread ||= Thread.new do 111 | begin 112 | docker_toolkit_transport.send(:session).loop { true } 113 | rescue 114 | Chef::Log.error("SSH forwarding loop failed: #{$!}") 115 | raise 116 | end 117 | Chef::Log.debug("Session loop completed normally") 118 | end 119 | else 120 | # We are the host. Find the docker machine's gateway (us) and talk to that; 121 | # and set up a little proxy that will forward the container's requests to 122 | # chef-zero 123 | result = execute('ip route list', :read_only => true) 124 | 125 | Chef::Log.debug("IP route: #{result.stdout}") 126 | 127 | if result.stdout =~ /default via (\S+)/ 128 | 129 | old_uri = uri.dup 130 | 131 | uri.host = ENV["PROXY_HOST_OVERRIDE"] ? ENV["PROXY_HOST_OVERRIDE"] : $1 132 | 133 | if !@proxy_thread 134 | # Listen to docker instances only, and forward to localhost 135 | @proxy_thread = Thread.new do 136 | begin 137 | Chef::Log.debug("Starting proxy thread: #{old_uri.host}:#{old_uri.port} <--> #{uri.host}:#{uri.port}") 138 | ChefZeroHttpProxy.new(uri.host, uri.port, old_uri.host, old_uri.port).run 139 | rescue 140 | Chef::Log.error("Proxy thread unable to start: #{$!}") 141 | end 142 | end 143 | end 144 | else 145 | raise "Cannot forward port: ip route ls did not show default in expected format.\nSTDOUT: #{result.stdout}" 146 | end 147 | 148 | end 149 | end 150 | 151 | uri.to_s 152 | end 153 | 154 | def disconnect 155 | if @docker_toolkit_transport_thread 156 | @docker_toolkit_transport_thread.kill 157 | @docker_toolkit_transport_thread = nil 158 | end 159 | end 160 | 161 | def available? 162 | end 163 | 164 | def is_local_machine(host) 165 | local_addrs = Socket.ip_address_list 166 | host_addrs = Addrinfo.getaddrinfo(host, nil) 167 | local_addrs.any? do |local_addr| 168 | host_addrs.any? do |host_addr| 169 | local_addr.ip_address == host_addr.ip_address 170 | end 171 | end 172 | end 173 | 174 | def docker_toolkit_transport(connection_url=nil) 175 | if !defined?(@docker_toolkit_transport) 176 | # Figure out which docker-machine this container is in 177 | begin 178 | docker_machines = `docker-machine ls --format "{{.Name}},{{.URL}}"` 179 | rescue Errno::ENOENT 180 | Chef::Log.debug("docker-machine ls returned ENOENT: Docker Toolkit is presumably not installed.") 181 | @docker_toolkit_transport = nil 182 | return 183 | end 184 | 185 | connection_url ||= container.connection.url 186 | 187 | Chef::Log.debug("Found docker machines:") 188 | docker_machine = nil 189 | docker_machines.lines.each do |line| 190 | machine_name, machine_url = line.chomp.split(',', 2) 191 | Chef::Log.debug("- #{machine_name} at URL #{machine_url.inspect}") 192 | if machine_url == connection_url 193 | Chef::Log.debug("Docker machine #{machine_name} at URL #{machine_url} matches the container's URL #{connection_url}! Will use it for port forwarding.") 194 | docker_machine = machine_name 195 | end 196 | end 197 | if !docker_machine 198 | Chef::Log.debug("Docker Toolkit is installed, but no Docker machine's URL matches #{connection_url.inspect}. Assuming docker must be installed as well ...") 199 | @docker_toolkit_transport = nil 200 | return 201 | end 202 | 203 | # Get the SSH information for the docker-machine 204 | docker_toolkit_json = `docker-machine inspect #{docker_machine}` 205 | machine_info = JSON.parse(docker_toolkit_json, create_additions: false)["Driver"] 206 | ssh_host = machine_info["IPAddress"] 207 | ssh_username = machine_info["SSHUser"] 208 | ssh_options = { 209 | # port: machine_info["SSHPort"], seems to be bad information (44930???) 210 | keys: [ machine_info["SSHKeyPath"] ], 211 | keys_only: true 212 | } 213 | 214 | Chef::Log.debug("Docker Toolkit is installed. Will use SSH transport with docker-machine #{docker_machine.inspect} to perform port forwarding.") 215 | @docker_toolkit_transport = Chef::Provisioning::Transport::SSH.new(ssh_host, ssh_username, ssh_options, {}, Chef::Config) 216 | end 217 | @docker_toolkit_transport 218 | end 219 | 220 | class DockerResult 221 | def initialize(command, options, stdout, stderr, exitstatus) 222 | @command = command 223 | @options = options 224 | @stdout = stdout 225 | @stderr = stderr 226 | @exitstatus = exitstatus 227 | end 228 | 229 | attr_reader :command 230 | attr_reader :options 231 | attr_reader :stdout 232 | attr_reader :stderr 233 | attr_reader :exitstatus 234 | 235 | def error! 236 | if exitstatus != 0 237 | msg = "Error: command '#{command}' exited with code #{exitstatus}.\n" 238 | msg << "STDOUT: #{stdout}" if !options[:stream] && !options[:stream_stdout] && Chef::Config.log_level != :debug 239 | msg << "STDERR: #{stderr}" if !options[:stream] && !options[:stream_stderr] && Chef::Config.log_level != :debug 240 | raise msg 241 | end 242 | end 243 | end 244 | end 245 | end 246 | end 247 | end 248 | -------------------------------------------------------------------------------- /lib/chef/provisioning/docker_driver/driver.rb: -------------------------------------------------------------------------------- 1 | require 'chef/mixin/shell_out' 2 | require 'chef/provisioning/driver' 3 | require 'chef/provisioning/docker_driver/version' 4 | require 'chef/provisioning/docker_driver/docker_transport' 5 | require 'chef/provisioning/docker_driver/docker_container_machine' 6 | require 'chef/provisioning/convergence_strategy/install_cached' 7 | require 'chef/provisioning/convergence_strategy/no_converge' 8 | require 'chef/mash' 9 | 10 | require 'yaml' 11 | require 'docker/container' 12 | require 'docker' 13 | 14 | class Chef 15 | module Provisioning 16 | module DockerDriver 17 | # Provisions machines using Docker 18 | class Driver < Chef::Provisioning::Driver 19 | 20 | include Chef::Mixin::ShellOut 21 | 22 | attr_reader :connection 23 | 24 | # URL scheme: 25 | # docker: 26 | # canonical URL calls realpath on 27 | def self.from_url(driver_url, config) 28 | Driver.new(driver_url, config) 29 | end 30 | 31 | def driver_url 32 | "docker:#{Docker.url}" 33 | end 34 | 35 | def initialize(driver_url, config) 36 | super 37 | url = Driver.connection_url(driver_url) 38 | 39 | if url 40 | # Export this as it's expected 41 | # to be set for command-line utilities 42 | ENV['DOCKER_HOST'] = url 43 | Chef::Log.debug("Setting Docker URL to #{url}") 44 | end 45 | 46 | ENV['DOCKER_HOST'] ||= url if url 47 | Docker.logger = Chef::Log 48 | options = Docker.options.dup || {} 49 | options.merge!(read_timeout: 600) 50 | options.merge!(config[:docker_connection].hash_dup) if config && config[:docker_connection] 51 | @connection = Docker::Connection.new(url || Docker.url, options) 52 | end 53 | 54 | def self.canonicalize_url(driver_url, config) 55 | url = Driver.connection_url(driver_url) 56 | [ "docker:#{url}", config ] 57 | end 58 | 59 | # Parse the url from a URL, try to clean it up 60 | # Returns a proper URL from the driver_url string. Examples include: 61 | # docker:/var/run/docker.sock => unix:///var/run/docker.sock 62 | # docker:192.168.0.1:1234 => tcp://192.168.0.1:1234 63 | def self.connection_url(driver_url) 64 | scheme, url = driver_url.split(':', 2) 65 | 66 | if url && url.size > 0 67 | # Clean up the URL with the protocol if needed (within reason) 68 | case url 69 | when /^\d+\.\d+\.\d+\.\d+:\d+$/ 70 | "tcp://#{url}" 71 | when /^\// 72 | "unix://#{url}" 73 | when /^(tcp|unix)/ 74 | url 75 | else 76 | "tcp://#{url}" 77 | end 78 | end 79 | end 80 | 81 | def allocate_machine(action_handler, machine_spec, machine_options) 82 | machine_spec.from_image = from_image_from_action_handler( 83 | action_handler, 84 | machine_spec 85 | ) 86 | 87 | # Grab options from existing machine (TODO seems wrong) and set the machine_spec to that 88 | docker_options = machine_options[:docker_options] 89 | container_id = nil 90 | image_id = machine_options[:image_id] 91 | if machine_spec.reference 92 | container_name = machine_spec.reference['container_name'] 93 | container_id = machine_spec.reference['container_id'] 94 | image_id ||= machine_spec.reference['image_id'] 95 | docker_options ||= machine_spec.reference['docker_options'] 96 | end 97 | container_name ||= machine_spec.name 98 | machine_spec.reference = { 99 | 'driver_url' => driver_url, 100 | 'driver_version' => Chef::Provisioning::DockerDriver::VERSION, 101 | 'allocated_at' => Time.now.utc.to_s, 102 | 'host_node' => action_handler.host_node, 103 | 'container_name' => container_name, 104 | 'image_id' => image_id, 105 | 'docker_options' => stringize_keys(docker_options), 106 | 'container_id' => container_id 107 | } 108 | end 109 | 110 | def ready_machine(action_handler, machine_spec, machine_options) 111 | machine_for(machine_spec, machine_options) 112 | end 113 | 114 | def start_machine(action_handler, machine_spec, machine_options) 115 | container = container_for(machine_spec) 116 | if container && !container.info['State']['Running'] 117 | action_handler.perform_action "start container #{machine_spec.name}" do 118 | container.start! 119 | end 120 | end 121 | end 122 | 123 | # Connect to machine without acquiring it 124 | def connect_to_machine(machine_spec, machine_options) 125 | Chef::Log.debug('Connect to machine') 126 | machine_for(machine_spec, machine_options) 127 | end 128 | 129 | def destroy_machine(action_handler, machine_spec, machine_options) 130 | container = container_for(machine_spec) 131 | if container 132 | image_id = container.info['Image'] 133 | action_handler.perform_action "stop and destroy container #{machine_spec.name}" do 134 | container.stop 135 | container.delete 136 | end 137 | end 138 | end 139 | 140 | def stop_machine(action_handler, machine_spec, machine_options) 141 | container = container_for(machine_spec) 142 | if container.info['State']['Running'] 143 | action_handler.perform_action "stop container #{machine_spec.name}" do 144 | container.stop! 145 | end 146 | end 147 | end 148 | 149 | # 150 | # Images 151 | # 152 | 153 | def allocate_image(action_handler, image_spec, image_options, machine_spec, machine_options) 154 | tag_container_image(action_handler, machine_spec, image_spec) 155 | 156 | # Set machine options on the image to match our newly created image 157 | image_spec.reference = { 158 | 'driver_url' => driver_url, 159 | 'driver_version' => Chef::Provisioning::DockerDriver::VERSION, 160 | 'allocated_at' => Time.now.to_i, 161 | 'docker_options' => { 162 | 'base_image' => { 163 | 'name' => image_spec.name 164 | } 165 | } 166 | } 167 | 168 | # Workaround for chef/chef-provisioning-docker#37 169 | machine_spec.attrs[:keep_image] = true 170 | end 171 | 172 | def ready_image(action_handler, image_spec, image_options) 173 | Chef::Log.debug('READY IMAGE!') 174 | end 175 | 176 | # workaround for https://github.com/chef/chef-provisioning/issues/358. 177 | def destroy_image(action_handler, image_spec, image_options, machine_options={}) 178 | image = image_for(image_spec) 179 | image.delete unless image.nil? 180 | end 181 | 182 | private 183 | 184 | def tag_container_image(action_handler, machine_spec, image_spec) 185 | container = container_for(machine_spec) 186 | existing_image = image_for(image_spec) 187 | unless existing_image && existing_image.id == container.info['Image'] 188 | image = Docker::Image.get(container.info['Image'], {}, @connection) 189 | action_handler.perform_action "tag image #{container.info['Image']} as chef-images/#{image_spec.name}" do 190 | image.tag('repo' => image_spec.name, 'force' => true) 191 | end 192 | end 193 | end 194 | 195 | def to_camel_case(name) 196 | name.split('_').map { |x| x.capitalize }.join("") 197 | end 198 | 199 | def to_snake_case(name) 200 | # ExposedPorts -> _exposed_ports 201 | name = name.gsub(/[A-Z]/) { |x| "_#{x.downcase}" } 202 | # _exposed_ports -> exposed_ports 203 | name = name[1..-1] if name.start_with?('_') 204 | name 205 | end 206 | 207 | def from_image_from_action_handler(action_handler, machine_spec) 208 | case action_handler 209 | when Chef::Provisioning::AddPrefixActionHandler 210 | machines = action_handler.action_handler.provider.new_resource.machines 211 | this_machine = machines.select { |m| m.name == machine_spec.name}.first 212 | this_machine.from_image 213 | else 214 | action_handler.provider.new_resource.from_image 215 | end 216 | end 217 | 218 | def machine_for(machine_spec, machine_options) 219 | Chef::Log.debug('machine_for...') 220 | docker_options = machine_options[:docker_options] || Mash.from_hash(machine_spec.reference['docker_options'] || {}) 221 | 222 | container = container_for(machine_spec) 223 | 224 | if machine_spec.from_image 225 | convergence_strategy = Chef::Provisioning::ConvergenceStrategy::NoConverge.new({}, config) 226 | else 227 | convergence_strategy = Chef::Provisioning::ConvergenceStrategy::InstallCached. 228 | new(machine_options[:convergence_options], config) 229 | end 230 | 231 | transport = DockerTransport.new(container, config) 232 | 233 | Chef::Provisioning::DockerDriver::DockerContainerMachine.new( 234 | machine_spec, 235 | transport, 236 | convergence_strategy, 237 | @connection, 238 | docker_options[:command] 239 | ) 240 | end 241 | 242 | def container_for(machine_spec) 243 | begin 244 | Docker::Container.get(machine_spec.name, {}, @connection) 245 | rescue Docker::Error::NotFoundError 246 | end 247 | end 248 | 249 | def image_for(image_spec) 250 | begin 251 | Docker::Image.get(image_spec.name, {}, @connection) 252 | rescue Docker::Error::NotFoundError 253 | end 254 | end 255 | 256 | def stringize_keys(hash) 257 | if hash 258 | hash.each_with_object({}) do |(k,v),hash| 259 | v = stringize_keys(v) if v.is_a?(Hash) 260 | hash[k.to_s] = v 261 | end 262 | end 263 | end 264 | end 265 | end 266 | end 267 | end 268 | -------------------------------------------------------------------------------- /lib/chef/provisioning/docker_driver/version.rb: -------------------------------------------------------------------------------- 1 | class Chef 2 | module Provisioning 3 | module DockerDriver 4 | VERSION = '0.11.0' 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/chef/provisioning/driver_init/docker.rb: -------------------------------------------------------------------------------- 1 | require 'chef/provisioning/docker_driver/driver' 2 | 3 | Chef::Provisioning.register_driver_class('docker', Chef::Provisioning::DockerDriver::Driver) 4 | -------------------------------------------------------------------------------- /spec/docker_support.rb: -------------------------------------------------------------------------------- 1 | module DockerSupport 2 | require 'cheffish/rspec/chef_run_support' 3 | def self.extended(other) 4 | other.extend Cheffish::RSpec::ChefRunSupport 5 | end 6 | 7 | require 'chef/provisioning/docker_driver' 8 | 9 | def with_docker(description, *tags, &block) 10 | context_block = proc do 11 | docker_driver = Chef::Provisioning.driver_for_url("docker") 12 | 13 | @@driver = docker_driver 14 | def self.driver 15 | @@driver 16 | end 17 | 18 | module_eval(&block) 19 | end 20 | 21 | when_the_repository "exists and #{description}", *tags, &context_block 22 | end 23 | end 24 | 25 | module DockerConfig 26 | def chef_config 27 | @chef_config ||= { 28 | driver: Chef::Provisioning.driver_for_url("docker"), 29 | } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/integration/primitives_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'time' 3 | 4 | describe "chef-provisioning-docker" do 5 | extend DockerSupport 6 | include DockerConfig 7 | 8 | when_the_chef_12_server "exists", organization: "foo", server_scope: :context, port: 8900..9000 do 9 | with_docker "integration tests" do 10 | 11 | # owing to how RSpec works, things defined by let() are not accessible in the recipes we define inside 12 | # expect_converge{}. 13 | ubuntu_options = { 14 | :base_image => { 15 | :name => 'ubuntu', 16 | :repository => 'ubuntu', 17 | :tag => '14.04' 18 | }, 19 | } 20 | 21 | let(:iso_date) { Time.now.iso8601.gsub(':', '') } 22 | docker_driver = driver 23 | 24 | context "machine_image resource" do 25 | 26 | let(:spec_image_tag) { "docker_image_spec_#{iso_date}" } 27 | 28 | after(:each) { 29 | image = docker_driver.find_image("chef", spec_image_tag) 30 | image.delete(force: true) unless image.nil? 31 | } 32 | 33 | it ":create succeeds" do 34 | tag = spec_image_tag 35 | 36 | expect_converge { 37 | 38 | machine_image tag do 39 | machine_options :docker_options => ubuntu_options 40 | action :create 41 | end 42 | }.not_to raise_error 43 | 44 | expect(docker_driver.find_image("chef", tag)).not_to be_nil 45 | end 46 | 47 | it ":destroy succeeds with an existing image" do 48 | tag = spec_image_tag 49 | 50 | expect_converge { 51 | machine_image tag do 52 | machine_options :docker_options => ubuntu_options 53 | action :create 54 | end 55 | 56 | machine_image tag do 57 | action :destroy 58 | end 59 | }.not_to raise_error 60 | 61 | expect(docker_driver.find_image("chef", tag)).to be_nil 62 | end 63 | 64 | it ":destroy succeeds with a non-existent image" do 65 | tag = "bogus_image" 66 | expect_converge { 67 | machine_image tag do 68 | action :destroy 69 | end 70 | }.not_to raise_error 71 | end 72 | end 73 | end 74 | end 75 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # require "mixlib/shellout" 2 | require 'chef' 3 | require 'chef/mixins' 4 | require 'chef/dsl' 5 | require 'chef/application' 6 | require 'chef/applications' 7 | 8 | require 'chef/shell' 9 | require 'chef/util/file_edit' 10 | 11 | require 'chef/config' 12 | require 'docker_support' 13 | 14 | RSpec.configure do |config| 15 | config.expect_with :rspec do |expectations| 16 | # This option will default to `true` in RSpec 4. It makes the `description` 17 | # and `failure_message` of custom matchers include text for helper methods 18 | # defined using `chain`, e.g.: 19 | # be_bigger_than(2).and_smaller_than(4).description 20 | # # => "be bigger than 2 and smaller than 4" 21 | # ...rather than: 22 | # # => "be bigger than 2" 23 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 24 | end 25 | 26 | config.mock_with :rspec do |mocks| 27 | mocks.verify_partial_doubles = true 28 | end 29 | 30 | config.filter_run :focus 31 | config.run_all_when_everything_filtered = true 32 | end 33 | -------------------------------------------------------------------------------- /test/Dockerfiles/chef-client-11.10.4/Dockerfile: -------------------------------------------------------------------------------- 1 | # DOCKER-VERSION 0.9 2 | FROM phusion/baseimage:0.9.9 3 | MAINTAINER Tom Duffield "tom@getchef.com" 4 | 5 | ENV HOME /root 6 | RUN /etc/my_init.d/00_regen_ssh_host_keys.sh 7 | CMD ["/sbin/my_init"] 8 | 9 | RUN apt-get -y update 10 | RUN DEBIAN_FRONTEND=noninteractive apt-get -y install curl build-essential libxml2-dev libxslt-dev git 11 | RUN curl -L https://www.opscode.com/chef/install.sh | bash 12 | RUN mkdir /etc/chef 13 | ADD validation.pem /etc/chef/validation.pem 14 | ADD client.rb /etc/chef/client.rb 15 | ENV PATH /opt/chef/embedded/bin:/opt/chef/bin:$PATH 16 | RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 17 | -------------------------------------------------------------------------------- /test/Dockerfiles/chef-client-11.10.4/client.rb: -------------------------------------------------------------------------------- 1 | node_name ENV['CONTAINER_NAME'] 2 | log_level :info 3 | log_location STDOUT 4 | chef_server_url ENV['CHEF_SERVER_URL'] 5 | validation_client_name ENV['VALIDATION_CLIENT_NAME'] 6 | -------------------------------------------------------------------------------- /test/Dockerfiles/chef-client-11.10.4/validation.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAvkqsYNwAbLr966l6sM1p7/6FrsGTy0Rv/BhBjj7vpkQxtF6d 3 | yfyQqPrsqmjPyaX3OtsyzmPGCJgYTw3fgYvXPAH56CgmeHS8P1YuYw9odSgL9RzS 4 | Jl2WtytMuLzObnWmxnLsh7x8C4ZMWu/931wVz3Zz3d7rkjgeqbm4xfOtZiiA8pmF 5 | x1GcpIEzcJvzLcbjUnTfUqk5KxoqufF5cHBXtBoyQHdCZL6EugbFP28BpvsGwmtb 6 | ndK/s0wCE5KKhNGsB50WlVo8qkEyeLvdgnxGkvRm/HGEMEM5wj9Uqb0WteALw+I6 7 | y/mPb5ohWPYZ+YqHmQg0jdyKRMwcePXJU1zT7wIDAQABAoIBAAgrsKcgCyygAtMh 8 | ndEFoOzbIuZUDYKG18AdUoNHFIcxGmlCPsgzfgqja5INmg5wf+MZEl9mTVSFI1YR 9 | pG0tkfpTo2Vig/RcHtEhZDCAst2E8K5/pE5damFHDK5J/RslFhojSSCy6SidhZ3J 10 | /D6g95bG4Ua/kaHfowFqphJp2iIuvAg6dvkSXK2UGjYKlEYLhQGYNVs/gGz1Bh4H 11 | aPoCxcw1F68MkvKX8ppo1JYNF6feDEkL6RaY9+FXkL03BqE9eYF4QogRFE86mjkH 12 | JPVW6WhCpsbOQpZyx8GM4Vei+WnO/NsvkN756cgpd4Q0pL5+lJ4hhUOjy8BCnf3C 13 | 3ZLfRqECgYEA7kEZ9V4XME9UNYYZpvaoj6oUSsqNW5fSRxofC/cBEEFZykI3Ht0J 14 | x5lwOWxHx0YqqH16YdTbaur883dUvTNvB6g93JBFrcmCq5EyGAMWHzt2kNXHhVCz 15 | 5A4BRpWrxtd2f6sfvWffWJgw1wWCrrDBaQdakPgeMmNinS4jUDxcCLcCgYEAzHcM 16 | SDhYSQpTCbrEUOOfmIOyAZFPYuaprdqiTVKFT+keTQ1UJz6p5R+MRD1CAD/ojahy 17 | 37hnXgiWMO1pTTPmfHCtpVbPYkXS9HhtBmiq3KL+j5rViUVTdJrk0c6I8BffBll+ 18 | nkYxEhTLwM20eEfv9Hl3eUR/oHjw193B+e63JokCgYAIteCZE+ONJJlTi5+MdyAa 19 | gIFVaqpYWuiZKN9RtKoZSgpaVqllO3eMEJQFxWuqYWpV1qCOyxVqh0TQMeCrOiIp 20 | CYGZerzRabiZRSmrrxEz+xyOlwJAYX7yK4Dob7hkAKSYbk0uw4lq18WRVuY8NMId 21 | cONiwW95MCD+1hz8ZfiHaQKBgQCVVZXY4Z5Jqoq/nnNoCPg1smo+jt0GhSYE3pyQ 22 | EOdhWAhTipqeFzyLJiGSbZNZD1RQsmlEUTDeEzNvBNdWFcQPu1R4YBET25+aAqLO 23 | fX/dqgZ26EVx/vpXPhJF0q1dfnHb911tSCLq69ltNlDLUO9HBbhnD8qPcAWoBEV6 24 | wAlw0QKBgQCPUkPdS7fieTofIxOFbMO7MlSy37bD6W6+EQ96RqqzA+Sl/zv/c5Vd 25 | njPiOU05drqR/KvjnRRmYIoK1yh7TGj3oJ8P6LuLNkUF+3FQW4i7XZY90939Wd6e 26 | PHiQaz+EoDRdA04hH4Nb8fN4+OWnUyeCi9VjEQ8ujpR9iCo6EfWyoQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/demo/Berksfile: -------------------------------------------------------------------------------- 1 | source "https://api.berkshelf.com" 2 | 3 | cookbook "ntp", github: "gmiranda23/ntp" 4 | cookbook "docker", github: "tduffield/chef-docker" 5 | cookbook "mongodb", github: "edelight/chef-mongodb" 6 | -------------------------------------------------------------------------------- /test/demo/Berksfile.lock: -------------------------------------------------------------------------------- 1 | DEPENDENCIES 2 | docker 3 | git: git://github.com/tduffield/chef-docker.git 4 | revision: 94812f83e33ce640ccdd5ee576ed93ec1d3004c7 5 | mongodb 6 | git: git://github.com/edelight/chef-mongodb.git 7 | revision: 7931af10235925b676a66435b765fd96a5a1c705 8 | ntp 9 | git: git://github.com/gmiranda23/ntp.git 10 | revision: 335a88eea48d3f4a8765a37941980be4df0f97ac 11 | 12 | GRAPH 13 | apt (2.3.8) 14 | aufs (0.1.1) 15 | apt (>= 0.0.0) 16 | modules (>= 0.0.0) 17 | build-essential (2.0.0) 18 | chef_handler (1.1.5) 19 | device-mapper (0.1.0) 20 | apt (>= 0.0.0) 21 | modules (>= 0.0.0) 22 | dmg (2.2.0) 23 | docker (0.33.0) 24 | apt (>= 0.0.0) 25 | aufs (>= 0.1.1) 26 | device-mapper (>= 0.1.0) 27 | git (>= 0.0.0) 28 | golang (>= 0.0.0) 29 | homebrew (>= 0.0.0) 30 | lxc (>= 1.1.6) 31 | modules (>= 0.0.0) 32 | runit (>= 0.0.0) 33 | sysctl (>= 0.0.0) 34 | yum-epel (>= 0.0.0) 35 | dpkg_autostart (0.1.10) 36 | git (4.0.0) 37 | build-essential (>= 0.0.0) 38 | dmg (>= 0.0.0) 39 | runit (>= 1.0.0) 40 | windows (>= 0.0.0) 41 | yum (~> 3.0) 42 | yum-epel (>= 0.0.0) 43 | golang (1.3.0) 44 | homebrew (1.5.4) 45 | lxc (1.1.8) 46 | dpkg_autostart (~> 0.1.10) 47 | modules (0.2.0) 48 | mongodb (0.16.0) 49 | apt (>= 1.8.2) 50 | python (>= 0.0.0) 51 | runit (>= 1.5.0) 52 | yum (>= 3.0.0) 53 | ntp (1.6.3) 54 | python (1.4.6) 55 | build-essential (>= 0.0.0) 56 | yum-epel (>= 0.0.0) 57 | runit (1.5.10) 58 | build-essential (>= 0.0.0) 59 | yum (~> 3.0) 60 | yum-epel (>= 0.0.0) 61 | sysctl (0.3.5) 62 | windows (1.30.2) 63 | chef_handler (>= 0.0.0) 64 | yum (3.1.6) 65 | yum-epel (0.3.4) 66 | yum (~> 3.0) 67 | -------------------------------------------------------------------------------- /test/demo/run.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./cookbooks 2 | #berks vendor ./cookbooks 3 | docker run \ 4 | -e CONTAINER_NAME=demo \ 5 | -e CHEF_SERVER_URL=https://api.opscode.com/organizations/tomduffield-personal \ 6 | -e VALIDATION_CLIENT_NAME=tomduffield-personal \ 7 | -d \ 8 | --name="demo" \ 9 | chef/ubuntu_12.04:11.10.4 \ 10 | bash -c 'while true; do sleep 100000; done' 11 | -------------------------------------------------------------------------------- /test/ec2.rb: -------------------------------------------------------------------------------- 1 | require 'chef/provisioning/fog' 2 | 3 | ec2testdir = File.expand_path('./ec2creds') 4 | 5 | directory ec2testdir 6 | 7 | with_fog_ec2_provisioner 8 | 9 | fog_key_pair 'me' do 10 | private_key_path "#{ec2testdir}/me" 11 | public_key_path "#{ec2testdir}/me.pub" 12 | end 13 | -------------------------------------------------------------------------------- /test/integration/cookbooks/docker-tests/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'docker-tests' 2 | version '0.1.0' 3 | maintainer '' 4 | maintainer_email '' 5 | depends 'ubuntu' 6 | depends 'apt-docker' 7 | depends 'yum-docker' 8 | depends 'yum-epel' 9 | depends 'docker' 10 | -------------------------------------------------------------------------------- /test/integration/cookbooks/docker-tests/recipes/change-container-state.rb: -------------------------------------------------------------------------------- 1 | machine 'ssh2' do 2 | action :destroy 3 | end 4 | 5 | machine 'ssh3' do 6 | action :stop 7 | end 8 | -------------------------------------------------------------------------------- /test/integration/cookbooks/docker-tests/recipes/create-containers.rb: -------------------------------------------------------------------------------- 1 | machine 'ssh1' do 2 | recipe 'docker-tests::create-user' 3 | recipe 'openssh::default' 4 | machine_options :docker_options => { 5 | :base_image => { 6 | :name => 'ubuntu', 7 | :repository => 'ubuntu', 8 | :tag => '14.04' 9 | }, 10 | :command => '/usr/sbin/sshd -D -o UsePAM=no', 11 | :ports => [22], 12 | } 13 | end 14 | 15 | machine_image 'ssh' do 16 | recipe 'docker-tests::create-user' 17 | recipe 'openssh::default' 18 | machine_options :docker_options => { 19 | :base_image => { 20 | :name => 'ubuntu', 21 | :repository => 'ubuntu', 22 | :tag => '14.04' 23 | } 24 | } 25 | end 26 | 27 | machine_batch do 28 | machine 'ssh2' do 29 | from_image 'ssh' 30 | machine_options :docker_options => { 31 | :command => '/usr/sbin/sshd -D -o UsePAM=no', 32 | :ports => [22], 33 | } 34 | end 35 | 36 | machine 'ssh3' do 37 | from_image 'ssh' 38 | machine_options :docker_options => { 39 | :command => '/usr/sbin/sshd -D -o UsePAM=no', 40 | :ports => [22], 41 | } 42 | end 43 | end -------------------------------------------------------------------------------- /test/integration/cookbooks/docker-tests/recipes/create-user.rb: -------------------------------------------------------------------------------- 1 | directory '/var/run/sshd' 2 | 3 | user 'vagrant' do 4 | action :create 5 | supports manage_home: true 6 | home '/home/vagrant' 7 | shell '/bin/bash' 8 | end 9 | 10 | bash 'create password' do 11 | code <<-EOS 12 | usermod -p "`openssl passwd -1 'vagrant'`" vagrant 13 | EOS 14 | end 15 | 16 | bash 'add vagrant to sudoers' do 17 | user 'root' 18 | cwd '/tmp' 19 | code <<-EOH 20 | echo 'vagrant ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers 21 | EOH 22 | end 23 | -------------------------------------------------------------------------------- /test/integration/cookbooks/docker-tests/recipes/default.rb: -------------------------------------------------------------------------------- 1 | case node.platform_family 2 | when 'ubuntu', 'debian' 3 | include_recipe 'ubuntu' 4 | include_recipe 'apt-docker' 5 | when 'rhel' 6 | include_recipe 'yum-epel' 7 | include_recipe 'yum-docker' 8 | end 9 | 10 | docker_service 'default' 11 | 12 | ENV['CHEF_DRIVER'] = 'docker' 13 | 14 | bash 'build and install chef-provisioning-docker' do 15 | cwd '/opt/chef-provisioning-docker' 16 | code <<-EOS 17 | rm -f chef-provisioning-docker-*.gem 18 | /opt/chef/embedded/bin/gem build chef-provisioning-docker.gemspec 19 | EOS 20 | action :nothing 21 | end.run_action(:run) 22 | 23 | chef_gem 'chef-provisioning-docker' do 24 | source Dir[ '/opt/chef-provisioning-docker/*.gem' ].first 25 | clear_sources true 26 | action [:remove, :install] 27 | end 28 | 29 | require 'chef/provisioning/docker_driver' 30 | -------------------------------------------------------------------------------- /test/integration/default/serverspec/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'docker-api' 4 | -------------------------------------------------------------------------------- /test/integration/default/serverspec/default_spec.rb: -------------------------------------------------------------------------------- 1 | require 'serverspec' 2 | require 'docker' 3 | 4 | set :backend, :exec 5 | 6 | describe file('/tmp/kitchen/nodes/ssh1.json') do 7 | it { should be_file } 8 | end 9 | 10 | describe file('/tmp/kitchen/nodes/ssh2.json') do 11 | it { should_not be_file } 12 | end 13 | 14 | describe file('/tmp/kitchen/nodes/ssh3.json') do 15 | it { should be_file } 16 | end 17 | 18 | describe 'containers and images' do 19 | it 'ssh1 is a running container' do 20 | container = Docker::Container.get('ssh1') 21 | expect(container.info['State']['Running']).to be true 22 | end 23 | 24 | it 'ssh2 has no container' do 25 | expect { Docker::Container.get('ssh2') }.to raise_error(Docker::Error::NotFoundError) 26 | end 27 | 28 | it 'ssh3 is a stopped container' do 29 | container = Docker::Container.get('ssh3') 30 | expect(container.info['State']['Running']).to be false 31 | end 32 | 33 | it 'ssh is an image' do 34 | expect { Docker::Image.get('ssh') }.not_to raise_error 35 | end 36 | 37 | it 'ssh1 has ssh port open and can cat chef client file' do 38 | container = Docker::Container.get('ssh1') 39 | ip = container.info['NetworkSettings']['IPAddress'] 40 | connection = container.info['NetworkSettings']['IPAddress'] 41 | file = Net::SSH.start(ip, 'vagrant', password: 'vagrant').exec!('sudo cat /etc/chef/client.rb') 42 | expect(file).to include 'ssh1' 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/mongo_cluster.rb: -------------------------------------------------------------------------------- 1 | 1.upto(2) do |i| 2 | machine "cluster#{i}" do 3 | recipe 'mongo_host' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/mongo_host.rb: -------------------------------------------------------------------------------- 1 | require 'chef/provisioning/docker_driver' 2 | with_provisioner Chef::Provisioning::DockerDriver::DockerProvisioner.new 3 | 4 | base_port = 27020 5 | 6 | 1.upto(2) do |i| 7 | port = base_port + i 8 | 9 | machine "mongodb#{i}" do 10 | provisioner_options :image_name => 'chef/ubuntu_12.04:11.10.4', 11 | :seed_command => 'chef-client -d -o mongodb::replicaset', 12 | :run_options => { 13 | :port => "#{port}:#{port}", 14 | :env => [ 15 | 'CONTAINER_NAME' => "mongodb#{i}", 16 | 'CHEF_SERVER_URL' => 'https://api.opscode.com/organizations/tomduffield-personal', 17 | 'VALIDATION_CLIENT_NAME' => 'tomduffield-personal-validator' 18 | ] 19 | } 20 | recipe 'mongodb::replicaset' 21 | attribute %w(mongodb config host), node['fqdn'] 22 | attribute %w(mongodb config port), port 23 | end 24 | end 25 | --------------------------------------------------------------------------------