├── .editorconfig ├── .envrc ├── .gitattributes ├── .github ├── CODEOWNERS ├── actions │ ├── chef-install │ │ └── action.yml │ ├── test-kitchen │ │ └── action.yml │ └── virtualbox-setup │ │ └── action.yml └── workflows │ ├── ci.yml │ └── stale.yml ├── .gitignore ├── .markdownlint-cli2.yaml ├── .mdlrc ├── .overcommit.yml ├── .rubocop.yml ├── .tool-versions ├── .vscode └── extensions.json ├── .yamllint ├── Berksfile ├── CHANGELOG-pre4.0.0.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dangerfile ├── LICENSE ├── README.md ├── TESTING.md ├── chefignore ├── documentation ├── .gitkeep ├── docker_container.md ├── docker_exec.md ├── docker_image.md ├── docker_image_prune.md ├── docker_installation_package.md ├── docker_installation_script.md ├── docker_installation_tarball.md ├── docker_network.md ├── docker_plugin.md ├── docker_registry.md ├── docker_service.md ├── docker_service_manager_execute.md ├── docker_service_manager_systemd.md ├── docker_swarm_init.md ├── docker_swarm_join.md ├── docker_swarm_service.md ├── docker_swarm_token.md ├── docker_tag.md ├── docker_volume.md └── docker_volume_prune.md ├── kitchen.dokken.yml ├── kitchen.exec.yml ├── kitchen.global.yml ├── kitchen.yml ├── lefthook.yml ├── libraries ├── base.rb ├── helpers_coerce.rb ├── helpers_container.rb ├── helpers_json.rb ├── helpers_network.rb ├── helpers_service.rb └── helpers_swarm.rb ├── metadata.rb ├── renovate.json ├── resources ├── container.rb ├── exec.rb ├── image.rb ├── image_prune.rb ├── installation_package.rb ├── installation_script.rb ├── installation_tarball.rb ├── network.rb ├── partial │ ├── _base.rb │ ├── _logging.rb │ └── _service_base.rb ├── plugin.rb ├── registry.rb ├── service.rb ├── service_base.rb ├── service_manager_execute.rb ├── service_manager_systemd.rb ├── swarm_init.rb ├── swarm_join.rb ├── swarm_service.rb ├── swarm_token.rb ├── tag.rb ├── volume.rb └── volume_prune.rb ├── spec ├── docker_test │ ├── container_spec.rb │ ├── exec_spec.rb │ ├── image_prune_spec.rb │ ├── image_spec.rb │ ├── installation_package_spec.rb │ ├── installation_tarball_spec.rb │ ├── network_spec.rb │ ├── plugin_spec.rb │ ├── registry_spec.rb │ ├── service_spec.rb │ └── volume_spec.rb ├── helpers_container_spec.rb ├── helpers_network_spec.rb ├── libraries │ ├── container_networks_spec.rb │ ├── container_spec.rb │ ├── image_prune_spec.rb │ └── registry_spec.rb ├── spec_helper.rb └── unit │ └── resources │ ├── swarm_init_spec.rb │ ├── swarm_join_spec.rb │ └── swarm_service_spec.rb ├── templates └── default │ ├── default │ └── docker-wait-ready.erb │ ├── sysconfig │ └── docker.erb │ ├── systemd │ ├── containerd.service.erb │ ├── docker.service-override.erb │ ├── docker.service.erb │ ├── docker.socket-override.erb │ ├── docker.socket.erb │ └── tmpfiles.d.conf.erb │ ├── sysvinit │ ├── docker-debian.erb │ └── docker-rhel.erb │ └── upstart │ └── docker.conf.erb └── test ├── cookbooks └── docker_test │ ├── CHANGELOG.md │ ├── files │ ├── Dockerfile_1 │ ├── Dockerfile_2 │ ├── Dockerfile_4 │ ├── Dockerfile_5 │ ├── image_3.tar │ └── image_3 │ │ └── Dockerfile │ ├── metadata.rb │ ├── recipes │ ├── auto.rb │ ├── container.rb │ ├── default.rb │ ├── exec.rb │ ├── image.rb │ ├── image_prune.rb │ ├── install_and_stop.rb │ ├── installation_package.rb │ ├── installation_script.rb │ ├── installation_tarball.rb │ ├── network.rb │ ├── plugin.rb │ ├── registry.rb │ ├── service.rb │ ├── smoke.rb │ ├── swarm.rb │ ├── swarm_service.rb │ ├── swarm_worker.rb │ ├── timeout.rb │ ├── volume.rb │ └── volume_prune.rb │ └── templates │ ├── nginx_forward_proxy │ └── proxy.conf.erb │ ├── registry │ └── auth │ │ ├── registry.conf.erb │ │ └── registry.password.erb │ └── squid_forward_proxy │ └── squid.conf.erb └── integration ├── install_and_stop └── inspec │ └── assert_functioning_spec.rb ├── installation_package └── inspec │ └── assert_functioning_spec.rb ├── installation_script_experimental └── inspec │ └── assert_functioning_spec.rb ├── installation_script_main └── inspec │ └── assert_functioning_spec.rb ├── installation_script_test └── inspec │ └── assert_functioning_spec.rb ├── installation_tarball └── inspec │ └── assert_functioning_spec.rb ├── network └── inspec │ └── assert_functioning_spec.rb ├── resources └── inspec │ └── assert_functioning_spec.rb ├── smoke └── inspec │ └── assert_functioning_spec.rb ├── swarm └── inspec │ └── swarm_test.rb └── volume └── inspec └── assert_functioning_spec.rb /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root=true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | indent_style = space 13 | indent_size = 2 14 | 15 | # Avoid issues parsing cookbook files later 16 | charset = utf-8 17 | 18 | # Avoid cookstyle warnings 19 | trim_trailing_whitespace = true 20 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use chefworkstation 2 | export KITCHEN_GLOBAL_YAML=kitchen.global.yml 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @sous-chefs/maintainers 2 | -------------------------------------------------------------------------------- /.github/actions/chef-install/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Install Chef' 2 | description: 'Installs Chef products on Windows or Linux/macOS' 3 | 4 | inputs: 5 | channel: 6 | description: 'Chef download channel' 7 | required: false 8 | default: 'stable' 9 | project: 10 | description: 'Chef project to download' 11 | required: false 12 | default: 'chef-workstation' 13 | version: 14 | description: 'Version of Chef product' 15 | required: false 16 | license-id: 17 | description: 'Chef license ID' 18 | required: true 19 | windows-path: 20 | description: 'Windows installation path' 21 | required: false 22 | default: 'C:\opscode' 23 | 24 | runs: 25 | using: "composite" 26 | steps: 27 | - name: Install Chef on Linux/macOS 28 | if: runner.os != 'Windows' 29 | shell: bash 30 | run: | 31 | curl -L https://chefdownload-commercial.chef.io/install.sh?license_id=${{ inputs.license-id }} -o chefDownload.sh 32 | sudo chmod +x chefDownload.sh 33 | sudo ./chefDownload.sh -c ${{ inputs.channel }} -P ${{ inputs.project }} ${{ inputs.version && format('-v {0}', inputs.version) }} 34 | rm -f chefDownload.sh 35 | 36 | - name: Install Chef on Windows 37 | if: runner.os == 'Windows' 38 | shell: pwsh 39 | run: | 40 | . { iwr -useb https://chefdownload-commercial.chef.io/install.ps1?license_id=${{ inputs.license-id }} } | iex; 41 | install -channel ${{ inputs.channel }} -project ${{ inputs.project }} ${{ inputs.version && format('-version {0}', inputs.version) }} 42 | 43 | - name: Add Windows Chef Path 44 | if: runner.os == 'Windows' 45 | shell: pwsh 46 | run: echo "${{ inputs.windows-path }}\bin" >> $env:GITHUB_PATH 47 | -------------------------------------------------------------------------------- /.github/actions/test-kitchen/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Run Test Kitchen' 2 | description: 'Runs Test Kitchen tests with configurable options' 3 | 4 | inputs: 5 | suite: 6 | description: 'Test Kitchen suite to run' 7 | required: false 8 | os: 9 | description: 'Operating system to test' 10 | required: false 11 | kitchen-yaml: 12 | description: 'Kitchen YAML file to use' 13 | required: false 14 | default: 'kitchen.dokken.yml' 15 | chef-version: 16 | description: 'Chef version to use' 17 | required: false 18 | default: 'latest' 19 | license-id: 20 | description: 'Chef license ID' 21 | required: true 22 | kitchen-command: 23 | description: 'Kitchen command to run (test, verify, etc)' 24 | required: false 25 | default: 'test' 26 | channel: 27 | description: 'Chef download channel' 28 | required: false 29 | default: 'stable' 30 | project: 31 | description: 'Chef project to download' 32 | required: false 33 | default: 'chef-workstation' 34 | version: 35 | description: 'Version of Chef product' 36 | required: false 37 | windows-path: 38 | description: 'Windows installation path' 39 | required: false 40 | default: 'C:\opscode' 41 | 42 | runs: 43 | using: "composite" 44 | steps: 45 | - name: Install Chef 46 | uses: ./.github/actions/chef-install 47 | with: 48 | version: ${{ inputs.chef-version }} 49 | license-id: ${{ inputs.license-id }} 50 | 51 | - name: Run Test Kitchen 52 | shell: bash 53 | run: kitchen ${{ inputs.kitchen-command }} ${{ inputs.suite }}${{ inputs.suite && inputs.os && '-' }}${{ inputs.os }} 54 | env: 55 | CHEF_LICENSE: ${{ inputs.license-id }} 56 | KITCHEN_LOCAL_YAML: ${{ inputs.kitchen-yaml }} 57 | -------------------------------------------------------------------------------- /.github/actions/virtualbox-setup/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup VirtualBox & Vagrant' 2 | description: 'Installs VirtualBox and Vagrant on Ubuntu runners' 3 | 4 | inputs: 5 | virtualbox-version: 6 | description: 'Version of VirtualBox to install' 7 | required: false 8 | default: '*' 9 | vagrant-version: 10 | description: 'Version of Vagrant to install' 11 | required: false 12 | default: 'latest' 13 | 14 | runs: 15 | using: "composite" 16 | steps: 17 | - name: Install VirtualBox & Vagrant 18 | shell: bash 19 | run: | 20 | sudo apt update && sudo apt install virtualbox -y 21 | wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg 22 | echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list 23 | sudo apt update && sudo apt install vagrant 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | 4 | "on": 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | lint-unit: 12 | uses: sous-chefs/.github/.github/workflows/lint-unit.yml@main 13 | permissions: 14 | actions: write 15 | checks: write 16 | pull-requests: write 17 | statuses: write 18 | issues: write 19 | 20 | integration: 21 | needs: lint-unit 22 | runs-on: ubuntu-24.04 23 | strategy: 24 | matrix: 25 | os: 26 | - almalinux-9 27 | - almalinux-10 28 | - amazonlinux-2023 29 | - centos-stream-9 30 | - centos-stream-10 31 | - debian-11 32 | - debian-12 33 | - ubuntu-2204 34 | - ubuntu-2404 35 | suite: 36 | - "installation-package" 37 | - "installation-tarball" 38 | - "install-and-stop" 39 | fail-fast: false 40 | steps: 41 | - name: Check out code 42 | uses: actions/checkout@v4 43 | 44 | - name: Test Kitchen 45 | uses: ./.github/actions/test-kitchen 46 | with: 47 | kitchen-yaml: kitchen.dokken.yml 48 | suite: ${{ matrix.suite }} 49 | os: ${{ matrix.os }} 50 | license-id: ${{ secrets.CHEF_LICENSE_KEY }} 51 | 52 | installation-script: 53 | needs: lint-unit 54 | runs-on: ubuntu-24.04 55 | strategy: 56 | matrix: 57 | os: 58 | - centos-stream-9 59 | - centos-stream-10 60 | - debian-11 61 | - debian-12 62 | - ubuntu-2204 63 | - ubuntu-2404 64 | suite: ["installation-script"] 65 | fail-fast: false 66 | steps: 67 | - name: Check out code 68 | uses: actions/checkout@v4 69 | 70 | - name: Test Kitchen 71 | uses: ./.github/actions/test-kitchen 72 | with: 73 | kitchen-yaml: kitchen.dokken.yml 74 | suite: ${{ matrix.suite }} 75 | os: ${{ matrix.os }} 76 | license-id: ${{ secrets.CHEF_LICENSE_KEY }} 77 | 78 | swarm: 79 | needs: lint-unit 80 | runs-on: ubuntu-24.04 81 | strategy: 82 | matrix: 83 | os: ["ubuntu-2204"] 84 | suite: ["swarm"] 85 | fail-fast: false 86 | steps: 87 | - name: Check out code 88 | uses: actions/checkout@v4 89 | 90 | - name: Setup VirtualBox & Vagrant 91 | uses: ./.github/actions/virtualbox-setup 92 | 93 | - name: Test Kitchen 94 | uses: ./.github/actions/test-kitchen 95 | with: 96 | kitchen-yaml: kitchen.yml 97 | suite: ${{ matrix.suite }} 98 | os: ${{ matrix.os }} 99 | license-id: ${{ secrets.CHEF_LICENSE_KEY }} 100 | 101 | smoke: 102 | needs: lint-unit 103 | runs-on: ubuntu-latest 104 | strategy: 105 | matrix: 106 | os: 107 | - "almalinux-8" 108 | - "almalinux-9" 109 | - "debian-11" 110 | - "debian-12" 111 | - "ubuntu-2004" 112 | - "ubuntu-2204" 113 | - "ubuntu-2404" 114 | suite: 115 | - "smoke" 116 | fail-fast: false 117 | steps: 118 | - name: Check out code 119 | uses: actions/checkout@v4 120 | 121 | - name: Setup VirtualBox & Vagrant 122 | uses: ./.github/actions/virtualbox-setup 123 | 124 | - name: Test Kitchen 125 | uses: ./.github/actions/test-kitchen 126 | with: 127 | kitchen-yaml: kitchen.yml 128 | suite: ${{ matrix.suite }} 129 | os: ${{ matrix.os }} 130 | license-id: ${{ secrets.CHEF_LICENSE_KEY }} 131 | 132 | final: 133 | needs: [lint-unit, installation-script, integration, swarm, smoke] 134 | runs-on: ubuntu-latest 135 | steps: 136 | - name: Complete 137 | run: echo "All tests passed" 138 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mark stale issues and pull requests 3 | 4 | "on": 5 | schedule: [cron: "0 0 * * *"] 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v9 12 | with: 13 | repo-token: ${{ secrets.GITHUB_TOKEN }} 14 | close-issue-message: > 15 | Closing due to inactivity. 16 | If this is still an issue please reopen or open another issue. 17 | Alternatively drop by the #sous-chefs channel on the [Chef Community Slack](http://community-slack.chef.io/) and we'll be happy to help! 18 | Thanks, Sous-Chefs. 19 | days-before-close: 7 20 | days-before-stale: 365 21 | stale-issue-message: > 22 | Marking stale due to inactivity. 23 | Remove stale label or comment or this will be closed in 7 days. 24 | Alternatively drop by the #sous-chefs channel on the [Chef Community Slack](http://community-slack.chef.io/) and we'll be happy to help! 25 | Thanks, Sous-Chefs. 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | .config 3 | InstalledFiles 4 | pkg 5 | test/tmp 6 | test/version_tmp 7 | tmp 8 | _Store 9 | *~ 10 | *# 11 | .#* 12 | \#*# 13 | *.un~ 14 | *.tmp 15 | *.bk 16 | *.bkup 17 | 18 | # editor files 19 | .idea 20 | .*.sw[a-z] 21 | 22 | # ruby/bundler/rspec files 23 | .ruby-version 24 | .ruby-gemset 25 | .rvmrc 26 | Gemfile.lock 27 | .bundle 28 | *.gem 29 | coverage 30 | spec/reports 31 | 32 | # YARD / rdoc artifacts 33 | .yardoc 34 | _yardoc 35 | doc/ 36 | rdoc 37 | 38 | # chef infra stuff 39 | Berksfile.lock 40 | .kitchen 41 | kitchen.local.yml 42 | vendor/ 43 | .coverage/ 44 | .zero-knife.rb 45 | Policyfile.lock.json 46 | 47 | # vagrant stuff 48 | .vagrant/ 49 | .vagrant.d/ 50 | -------------------------------------------------------------------------------- /.markdownlint-cli2.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | ul-indent: false # MD007 3 | line-length: false # MD013 4 | no-duplicate-heading: false # MD024 5 | reference-links-images: false # MD052 6 | ignores: 7 | - .github/copilot-instructions.md 8 | -------------------------------------------------------------------------------- /.mdlrc: -------------------------------------------------------------------------------- 1 | rules "~MD013", "~MD024", "~MD025" 2 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | PreCommit: 3 | TrailingWhitespace: 4 | enabled: true 5 | YamlLint: 6 | enabled: true 7 | required_executable: "yamllint" 8 | ChefSpec: 9 | enabled: true 10 | required_executable: "chef" 11 | command: ["chef", "exec", "rspec"] 12 | Cookstyle: 13 | enabled: true 14 | required_executable: "cookstyle" 15 | command: ["cookstyle"] 16 | MarkdownLint: 17 | enabled: false 18 | required_executable: "npx" 19 | command: ["npx", "markdownlint-cli2", "'**/*.md'"] 20 | include: ["**/*.md"] 21 | 22 | CommitMsg: 23 | HardTabs: 24 | enabled: true 25 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - cookstyle 3 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby system 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "chef-software.chef", 4 | "rebornix.ruby", 5 | "editorconfig.editorconfig", 6 | "DavidAnson.vscode-markdownlint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | rules: 4 | line-length: 5 | max: 256 6 | level: warning 7 | document-start: disable 8 | braces: 9 | forbid: false 10 | min-spaces-inside: 0 11 | max-spaces-inside: 1 12 | min-spaces-inside-empty: -1 13 | max-spaces-inside-empty: -1 14 | comments: 15 | min-spaces-from-content: 1 16 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.chef.io' 2 | 3 | metadata 4 | 5 | group :integration do 6 | cookbook 'docker_test', path: 'test/cookbooks/docker_test' 7 | end 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Guidelines 2 | 3 | This project follows the Chef Community Guidelines 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Please refer to 4 | [https://github.com/chef-cookbooks/community_cookbook_documentation/blob/main/CONTRIBUTING.MD](https://github.com/chef-cookbooks/community_cookbook_documentation/blob/main/CONTRIBUTING.MD) 5 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | # Reference: http://danger.systems/reference.html 2 | 3 | # A pull request summary is required. Add a description of the pull request purpose. 4 | # Changelog must be updated for each pull request that changes code. 5 | # Warnings will be issued for: 6 | # Pull request with more than 400 lines of code changed 7 | # Pull reqest that change more than 5 lines without test changes 8 | # Failures will be issued for: 9 | # Pull request without summary 10 | # Pull requests with code changes without changelog entry 11 | 12 | def code_changes? 13 | code = %w(libraries attributes recipes resources files templates) 14 | code.each do |location| 15 | return true unless git.modified_files.grep(/#{location}/).empty? 16 | end 17 | false 18 | end 19 | 20 | def test_changes? 21 | tests = %w(spec test kitchen.yml kitchen.dokken.yml) 22 | tests.each do |location| 23 | return true unless git.modified_files.grep(/#{location}/).empty? 24 | end 25 | false 26 | end 27 | 28 | failure 'Please provide a summary of your Pull Request.' if github.pr_body.length < 10 29 | 30 | warn 'This is a big Pull Request.' if git.lines_of_code > 400 31 | 32 | warn 'This is a Table Flip.' if git.lines_of_code > 2000 33 | 34 | # Require a CHANGELOG entry for non-test changes. 35 | if !git.modified_files.include?('CHANGELOG.md') && code_changes? 36 | failure 'Please include a CHANGELOG entry.' 37 | end 38 | 39 | # Require Major Minor Patch version labels 40 | unless github.pr_labels.grep /minor|major|patch/i 41 | warn 'Please add a release label to this pull request' 42 | end 43 | 44 | # A sanity check for tests. 45 | if git.lines_of_code > 5 && code_changes? && !test_changes? 46 | warn 'This Pull Request is probably missing tests.' 47 | end 48 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Please refer to [the community cookbook documentation on testing](https://github.com/chef-cookbooks/community_cookbook_documentation/blob/main/TESTING.MD). 4 | -------------------------------------------------------------------------------- /chefignore: -------------------------------------------------------------------------------- 1 | # Put files/directories that should be ignored in this file when uploading 2 | # to a Chef Infra Server or Supermarket. 3 | # Lines that start with '# ' are comments. 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | ehthumbs.db 9 | Icon? 10 | nohup.out 11 | Thumbs.db 12 | .envrc 13 | 14 | # EDITORS # 15 | ########### 16 | .#* 17 | .project 18 | .settings 19 | *_flymake 20 | *_flymake.* 21 | *.bak 22 | *.sw[a-z] 23 | *.tmproj 24 | *~ 25 | \#* 26 | REVISION 27 | TAGS* 28 | tmtags 29 | .vscode 30 | .editorconfig 31 | 32 | ## COMPILED ## 33 | ############## 34 | *.class 35 | *.com 36 | *.dll 37 | *.exe 38 | *.o 39 | *.pyc 40 | *.so 41 | */rdoc/ 42 | a.out 43 | mkmf.log 44 | 45 | # Testing # 46 | ########### 47 | .circleci/* 48 | .codeclimate.yml 49 | .delivery/* 50 | .foodcritic 51 | .kitchen* 52 | .mdlrc 53 | .overcommit.yml 54 | .rspec 55 | .rubocop.yml 56 | .travis.yml 57 | .watchr 58 | .yamllint 59 | azure-pipelines.yml 60 | Dangerfile 61 | examples/* 62 | features/* 63 | Guardfile 64 | kitchen*.yml 65 | mlc_config.json 66 | Procfile 67 | Rakefile 68 | spec/* 69 | test/* 70 | 71 | # SCM # 72 | ####### 73 | .git 74 | .gitattributes 75 | .gitconfig 76 | .github/* 77 | .gitignore 78 | .gitkeep 79 | .gitmodules 80 | .svn 81 | */.bzr/* 82 | */.git 83 | */.hg/* 84 | */.svn/* 85 | 86 | # Berkshelf # 87 | ############# 88 | Berksfile 89 | Berksfile.lock 90 | cookbooks/* 91 | tmp 92 | 93 | # Bundler # 94 | ########### 95 | vendor/* 96 | Gemfile 97 | Gemfile.lock 98 | 99 | # Policyfile # 100 | ############## 101 | Policyfile.rb 102 | Policyfile.lock.json 103 | 104 | # Documentation # 105 | ############# 106 | CODE_OF_CONDUCT* 107 | CONTRIBUTING* 108 | documentation/* 109 | TESTING* 110 | UPGRADING* 111 | 112 | # Vagrant # 113 | ########### 114 | .vagrant 115 | Vagrantfile 116 | -------------------------------------------------------------------------------- /documentation/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sous-chefs/docker/2a3dd8f8ab7785e43b3df11006918408ef08b89b/documentation/.gitkeep -------------------------------------------------------------------------------- /documentation/docker_exec.md: -------------------------------------------------------------------------------- 1 | # docker_exec 2 | 3 | The `docker_exec` resource allows you to execute commands inside of a running container. This is equivalent to using the `docker exec` command and is useful for running commands, scripts, or interactive shells within containers. 4 | 5 | ## Actions 6 | 7 | - `:run` - Executes the specified command inside the container 8 | 9 | ## Properties 10 | 11 | ### Required Properties 12 | 13 | - `container` - Name or ID of the container to execute the command in 14 | - `command` - Command to execute, structured as an Array similar to `CMD` in a Dockerfile (alias: `cmd`) 15 | 16 | ### Optional Properties 17 | 18 | - `host` - Docker daemon socket to connect to (default: ENV['DOCKER_HOST']) 19 | - `timeout` - Seconds to wait for the command to complete (default: 60) 20 | - `returns` - Expected return value(s) for the command (default: [0]). Can be a single integer or array of accepted values. 21 | 22 | ## Examples 23 | 24 | ### Basic Command Execution 25 | 26 | ```ruby 27 | docker_exec 'create_file' do 28 | container 'web_app' 29 | command ['touch', '/tmp/test_file'] 30 | end 31 | ``` 32 | 33 | ### Custom Return Values 34 | 35 | ```ruby 36 | docker_exec 'check_status' do 37 | container 'app' 38 | command ['grep', 'pattern', '/var/log/app.log'] 39 | returns [0, 1] # Accept both found (0) and not-found (1) as valid returns 40 | end 41 | ``` 42 | 43 | ### Long Running Commands 44 | 45 | ```ruby 46 | docker_exec 'database_backup' do 47 | container 'database' 48 | command ['pg_dump', '-U', 'postgres', 'mydb', '>', '/backup/db.sql'] 49 | timeout 300 # 5 minutes timeout for backup 50 | end 51 | ``` 52 | 53 | ### Multiple Commands with Shell 54 | 55 | ```ruby 56 | docker_exec 'setup_environment' do 57 | container 'web_app' 58 | command ['sh', '-c', 'mkdir -p /app/data && chown www-data:www-data /app/data'] 59 | end 60 | ``` 61 | 62 | ## Notes 63 | 64 | 1. The container must be running when executing commands 65 | 2. The `command` property must be an array where each argument is a separate element 66 | 3. Use `sh -c` when you need to use shell features like pipes or environment variables 67 | 4. Set appropriate timeouts for long-running commands 68 | 5. Use the `returns` property to handle commands that may have multiple valid exit codes 69 | 70 | ## Common Use Cases 71 | 72 | - Running database migrations 73 | - Installing system packages 74 | - Modifying configuration files 75 | - Running maintenance tasks 76 | - Health checks 77 | - Log inspection 78 | -------------------------------------------------------------------------------- /documentation/docker_image_prune.md: -------------------------------------------------------------------------------- 1 | # docker_image_prune 2 | 3 | The `docker_image_prune` is responsible for pruning Docker images from the system. It speaks directly to the [Docker Engine API](https://docs.docker.com/engine/api/v1.35/#operation/ImagePrune). 4 | Note - this is best implemented by subscribing to `docker_image` changes. There is no need to to clean up old images upon each converge. It is best done at the end of a chef run (delayed) only if a new image was pulled. 5 | 6 | ## Actions 7 | 8 | - `:prune` - Delete unused images 9 | 10 | ## Properties 11 | 12 | The `docker_image_prune` resource properties map to filters 13 | 14 | - `dangling` - When set to true (or 1), prune only unused and untagged images. When set to false (or 0), all unused images are pruned 15 | - `prune_until` - Prune images created before this timestamp. The `` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. 10m, 1h30m) computed relative to the daemon machine’s time. 16 | - `with_label/without_label` - (`label=`, `label==`, `label!=`, or `label!==`) Prune images with (or without, in case label!=... is used) the specified labels. 17 | - `host` - A string containing the host the API should communicate with. Defaults to `ENV['DOCKER_HOST']` if set. 18 | 19 | ## Examples 20 | 21 | - default action, default properties 22 | 23 | ```ruby 24 | docker_image_prune 'prune-old-images' 25 | ``` 26 | 27 | - All filters 28 | 29 | ```ruby 30 | docker_image_prune "prune-old-images" do 31 | dangling true 32 | prune_until '1h30m' 33 | with_label 'com.example.vendor=ACME' 34 | without_label 'no_prune' 35 | action :prune 36 | end 37 | ``` 38 | -------------------------------------------------------------------------------- /documentation/docker_installation_package.md: -------------------------------------------------------------------------------- 1 | # docker_installation_package 2 | 3 | The `docker_installation_package` resource is responsible for installing Docker via package manager. It supports both Debian/Ubuntu and RHEL/Fedora platforms. 4 | 5 | ## Actions 6 | 7 | - `:create` - Installs Docker package and sets up the Docker repository if enabled 8 | - `:delete` - Removes the Docker package 9 | 10 | ## Properties 11 | 12 | | Property | Type | Default | Description | 13 | |------------------|---------|------------------------|------------------------------------------------------------| 14 | | `setup_docker_repo` | Boolean | `true` | Whether to set up the Docker repository | 15 | | `repo_channel` | String | `'stable'` | Repository channel to use (`stable`, `test`, `nightly`) | 16 | | `package_name` | String | `'docker-ce'` | Name of the Docker package to install | 17 | | `package_version` | String | `nil` | Specific package version to install | 18 | | `version` | String | `nil` | Docker version to install (e.g., '20.10.23') | 19 | | `package_options` | String | `nil` | Additional options to pass to the package manager | 20 | | `site_url` | String | `'download.docker.com'`| Docker repository URL | 21 | 22 | ## Examples 23 | 24 | ### Install Latest Version of Docker 25 | 26 | ```ruby 27 | docker_installation_package 'default' do 28 | action :create 29 | end 30 | ``` 31 | 32 | ### Install Specific Version of Docker 33 | 34 | ```ruby 35 | docker_installation_package 'default' do 36 | version '20.10.23' 37 | action :create 38 | end 39 | ``` 40 | 41 | ### Install from Test Channel 42 | 43 | ```ruby 44 | docker_installation_package 'default' do 45 | repo_channel 'test' 46 | action :create 47 | end 48 | ``` 49 | 50 | ### Install Without Setting Up Docker Repository 51 | 52 | ```ruby 53 | docker_installation_package 'default' do 54 | setup_docker_repo false 55 | action :create 56 | end 57 | ``` 58 | 59 | ### Remove Docker Installation 60 | 61 | ```ruby 62 | docker_installation_package 'default' do 63 | action :delete 64 | end 65 | ``` 66 | 67 | ## Platform Support 68 | 69 | This resource supports the following platforms: 70 | 71 | ### Debian/Ubuntu 72 | 73 | - Debian 9 (Stretch) 74 | - Debian 10 (Buster) 75 | - Debian 11 (Bullseye) 76 | - Debian 12 (Bookworm) 77 | - Ubuntu 18.04 (Bionic) 78 | - Ubuntu 20.04 (Focal) 79 | - Ubuntu 22.04 (Jammy) 80 | - Ubuntu 24.04 (Noble) 81 | 82 | ### RHEL/Fedora 83 | 84 | - RHEL/CentOS 7 and later 85 | - Fedora (latest versions) 86 | 87 | ## Notes 88 | 89 | - The resource automatically handles architecture-specific package names and repositories 90 | - For Debian/Ubuntu systems, it installs `apt-transport-https` package as a prerequisite 91 | - Version strings are handled differently based on the Docker version and platform: 92 | - For versions < 18.06: Uses format like `VERSION~ce-0~debian` or `VERSION~ce-0~ubuntu` 93 | - For versions >= 18.09: Uses format like `5:VERSION~3-0~debian-CODENAME` or `5:VERSION~3-0~ubuntu-CODENAME` 94 | - For versions >= 23.0 on Ubuntu: Uses format like `5:VERSION-1~ubuntu.VERSION~CODENAME` 95 | -------------------------------------------------------------------------------- /documentation/docker_installation_script.md: -------------------------------------------------------------------------------- 1 | # docker_installation_script 2 | 3 | The `docker_installation_script` resource installs Docker on Linux systems using the official Docker installation scripts. This is also known as the "curl pipe bash" installation method. 4 | 5 | ## Actions 6 | 7 | - `:create` - Downloads and executes the Docker installation script 8 | - `:delete` - Removes Docker packages installed by the script 9 | 10 | ## Properties 11 | 12 | | Property | Type | Default | Description | 13 | |-------------|--------|----------------|-----------------------------------------------------------------------| 14 | | `repo` | String | `'main'` | Repository to use for installation. One of: `main`, `test`, or `experimental` | 15 | | `script_url`| String | Based on repo | URL of the installation script. Defaults to official Docker URLs based on the repo property | 16 | 17 | ## Examples 18 | 19 | ### Install Docker from Main Repository 20 | 21 | ```ruby 22 | docker_installation_script 'default' do 23 | action :create 24 | end 25 | ``` 26 | 27 | ### Install Docker from Test Repository 28 | 29 | ```ruby 30 | docker_installation_script 'default' do 31 | repo 'test' 32 | action :create 33 | end 34 | ``` 35 | 36 | ### Install Docker from Experimental Repository 37 | 38 | ```ruby 39 | docker_installation_script 'default' do 40 | repo 'experimental' 41 | action :create 42 | end 43 | ``` 44 | 45 | ### Install Docker Using Custom Script URL 46 | 47 | ```ruby 48 | docker_installation_script 'default' do 49 | script_url 'https://my-custom-docker-install.example.com/install.sh' 50 | action :create 51 | end 52 | ``` 53 | 54 | ### Remove Docker Installation 55 | 56 | ```ruby 57 | docker_installation_script 'default' do 58 | action :delete 59 | end 60 | ``` 61 | 62 | ## Notes 63 | 64 | - This resource is only available on Linux systems 65 | - The installation script requires `curl` to be installed (the resource will install it if missing) 66 | - The script is executed with `sh` shell 67 | - The installation is considered complete when `/usr/bin/docker` exists 68 | - When removing Docker, both `docker-ce` and `docker-engine` packages are removed 69 | - Default script URLs: 70 | - Main: 71 | - Test: 72 | 73 | ## Platform Support 74 | 75 | This resource is supported on all Linux platforms that can run the Docker installation scripts. 76 | -------------------------------------------------------------------------------- /documentation/docker_installation_tarball.md: -------------------------------------------------------------------------------- 1 | # docker_installation_tarball 2 | 3 | The `docker_installation_tarball` resource installs Docker on a system using pre-compiled binary tarballs from the official Docker downloads. 4 | 5 | ## Actions 6 | 7 | - `:create` - Downloads and installs Docker from a tarball 8 | - `:delete` - Removes Docker installed from a tarball 9 | 10 | ## Properties 11 | 12 | | Property | Type | Default | Description | 13 | |------------|--------|-------------------|--------------------------------------------| 14 | | `checksum` | String | Based on version | SHA256 checksum of the Docker tarball | 15 | | `source` | String | Based on version | URL of the Docker tarball | 16 | | `channel` | String | `'stable'` | Docker release channel to use | 17 | | `version` | String | `'20.10.11'` | Docker version to install | 18 | 19 | ## Examples 20 | 21 | ### Install Default Version 22 | 23 | ```ruby 24 | docker_installation_tarball 'default' do 25 | action :create 26 | end 27 | ``` 28 | 29 | ### Install Specific Version 30 | 31 | ```ruby 32 | docker_installation_tarball 'default' do 33 | version '19.03.15' 34 | action :create 35 | end 36 | ``` 37 | 38 | ### Install from Custom Source 39 | 40 | ```ruby 41 | docker_installation_tarball 'default' do 42 | source 'https://example.com/docker-20.10.11.tgz' 43 | checksum 'dd6ff72df1edfd61ae55feaa4aadb88634161f0aa06dbaaf291d1be594099ff3' 44 | action :create 45 | end 46 | ``` 47 | 48 | ### Remove Docker Installation 49 | 50 | ```ruby 51 | docker_installation_tarball 'default' do 52 | action :delete 53 | end 54 | ``` 55 | 56 | ## Platform Support 57 | 58 | This resource supports the following platforms: 59 | 60 | ### Linux 61 | 62 | - Version 18.03.1: checksum `0e245c42de8a21799ab11179a4fce43b494ce173a8a2d6567ea6825d6c5265aa` 63 | - Version 18.06.3: checksum `346f9394393ee8db5f8bd1e229ee9d90e5b36931bdd754308b2ae68884dd6822` 64 | - Version 18.09.9: checksum `82a362af7689038c51573e0fd0554da8703f0d06f4dfe95dd5bda5acf0ae45fb` 65 | - Version 19.03.15: checksum `5504d190eef37355231325c176686d51ade6e0cabe2da526d561a38d8611506f` 66 | - Version 20.10.11: checksum `dd6ff72df1edfd61ae55feaa4aadb88634161f0aa06dbaaf291d1be594099ff3` 67 | 68 | ### Darwin (macOS) 69 | 70 | - Version 18.03.1: checksum `bbfb9c599a4fdb45523496c2ead191056ff43d6be90cf0e348421dd56bc3dcf0` 71 | - Version 18.06.3: checksum `f7347ef27db9a438b05b8f82cd4c017af5693fe26202d9b3babf750df3e05e0c` 72 | - Version 18.09.9: checksum `ed83a3d51fef2bbcdb19d091ff0690a233aed4bbb47d2f7860d377196e0143a0` 73 | - Version 19.03.15: checksum `61672045675798b2075d4790665b74336c03b6d6084036ef22720af60614e50d` 74 | - Version 20.10.11: checksum `8f338ba618438fa186d1fa4eae32376cca58f86df2b40b5027c193202fad2acf` 75 | 76 | ## Notes 77 | 78 | - The resource automatically detects the system architecture and kernel type to download the appropriate tarball 79 | - Requires `tar` package to be installed (the resource will install it if missing) 80 | - Creates a `docker` system group 81 | - Filename format varies based on Docker version: 82 | - For versions >= 19.x.x: `docker-VERSION.tgz` 83 | - For version 18.09.x: `docker-VERSION.tgz` 84 | - For versions <= 18.06.x: `docker-VERSION-ce.tgz` 85 | -------------------------------------------------------------------------------- /documentation/docker_network.md: -------------------------------------------------------------------------------- 1 | # docker_network 2 | 3 | The `docker_network` resource is responsible for managing Docker named networks. Usage of `overlay` driver requires the `docker_service` to be configured to use a distributed key/value store like `etcd`, `consul`, or `zookeeper`. 4 | 5 | ## Actions 6 | 7 | - `:create` - create a network 8 | - `:delete` - delete a network 9 | - `:connect` - connect a container to a network 10 | - `:disconnect` - disconnect a container from a network 11 | 12 | ## Properties 13 | 14 | - `aux_address` - Auxiliary addresses for the network. Ex: `['a=192.168.1.5', 'b=192.168.1.6']` 15 | - `container` - Container-id/name to be connected/disconnected to/from the network. Used only by `:connect` and `:disconnect` actions 16 | - `driver` - The network driver to use. Defaults to `bridge`, other options include `overlay`. 17 | - `enable_ipv6` - Enable IPv6 on the network. Ex: true 18 | - `gateway` - Specify the gateway(s) for the network. Ex: `192.168.0.1` 19 | - `ip_range` - Specify a range of IPs to allocate for containers. Ex: `192.168.1.0/24` 20 | - `subnet` - Specify the subnet(s) for the network. Ex: `192.168.0.0/16` 21 | 22 | ## Examples 23 | 24 | Create a network and use it in a container 25 | 26 | ```ruby 27 | docker_network 'network_g' do 28 | driver 'overlay' 29 | subnet ['192.168.0.0/16', '192.170.0.0/16'] 30 | gateway ['192.168.0.100', '192.170.0.100'] 31 | ip_range '192.168.1.0/24' 32 | aux_address ['a=192.168.1.5', 'b=192.168.1.6', 'a=192.170.1.5', 'b=192.170.1.6'] 33 | end 34 | 35 | docker_container 'echo-base' do 36 | repo 'alpine' 37 | tag '3.1' 38 | command 'nc -ll -p 1337 -e /bin/cat' 39 | port '1337' 40 | network_mode 'network_g' 41 | action :run 42 | end 43 | ``` 44 | 45 | Connect to multiple networks 46 | 47 | ```ruby 48 | docker_network 'network_h1' do 49 | action :create 50 | end 51 | 52 | docker_network 'network_h2' do 53 | action :create 54 | end 55 | 56 | docker_container 'echo-base-networks_h' do 57 | repo 'alpine' 58 | tag '3.1' 59 | command 'nc -ll -p 1337 -e /bin/cat' 60 | port '1337' 61 | network_mode 'network_h1' 62 | action :run 63 | end 64 | 65 | docker_network 'network_h2' do 66 | container 'echo-base-networks_h' 67 | action :connect 68 | end 69 | ``` 70 | 71 | IPv6 enabled network 72 | 73 | ```ruby 74 | docker_network 'network_i1' do 75 | enable_ipv6 true 76 | subnet 'fd00:dead:beef::/48' 77 | action :create 78 | end 79 | ``` 80 | -------------------------------------------------------------------------------- /documentation/docker_plugin.md: -------------------------------------------------------------------------------- 1 | # docker_plugin 2 | 3 | The `docker_plugin` resource allows you to install, configure, enable, disable and remove [Docker Engine managed plugins](https://docs.docker.com/engine/extend/). 4 | 5 | ## Actions 6 | 7 | - `:install` - Install and configure a plugin if it is not already installed 8 | - `:update` - Re-configure a plugin 9 | - `:enable` - Enable a plugin (needs to be done after `:install` before it can 10 | be used) 11 | - `:disable` - Disable a plugin (needs to be done before removing a plugin) 12 | - `:remove` - Remove a disabled plugin 13 | 14 | ## Properties 15 | 16 | - `local_alias` - Local name for the plugin (defaults to the resource name). 17 | - `remote` - Ref of the plugin (e.g. `vieux/sshfs`). Defaults to `local_alias` or the resource name. Only used for `:install`. 18 | - `remote_tag` - Remote tag of the plugin to pull (e.g. `1.0.1`, defaults to `latest`) Only used for `:install`. 19 | - `options` - Hash of options to set on the plugin. Only used for `:update` and `:install`. 20 | - `grant_privileges` - Array of privileges or true. If it is true, all privileges requested by the plugin will be automatically granted (potentially dangerous). Otherwise, this must be an array in the same format as returned by the [`/plugins/privileges` docker API](https://docs.docker.com/engine/api/v1.37/#operation/GetPluginPrivileges) endpoint. If the array of privileges is not sufficient for the plugin, docker will reject it and the installation will fail. Defaults to `[]` (empty array => no privileges). Only used for `:install`. Does not modify the privileges of already-installed plugins. 21 | 22 | ## Examples 23 | 24 | ```ruby 25 | docker_plugin 'rbd' do 26 | remote 'wetopi/rbd' 27 | remote_tag '1.0.1' 28 | grant_privileges true 29 | options( 30 | 'RBD_CONF_POOL' => 'docker_volumes' 31 | ) 32 | end 33 | ``` 34 | -------------------------------------------------------------------------------- /documentation/docker_registry.md: -------------------------------------------------------------------------------- 1 | # docker_registry 2 | 3 | The `docker_registry` resource is responsible for managing the connection auth information to a Docker registry. 4 | 5 | ## Actions 6 | 7 | - `:login` - Login to the Docker Registry 8 | 9 | ## Properties 10 | 11 | - `email` 12 | - `password` 13 | - `serveraddress` 14 | - `username` 15 | 16 | ## Examples 17 | 18 | - Log into or register with public registry: 19 | 20 | ```ruby 21 | docker_registry 'https://index.docker.io/v1/' do 22 | username 'publicme' 23 | password 'hope_this_is_in_encrypted_databag' 24 | email 'publicme@computers.biz' 25 | end 26 | ``` 27 | 28 | Log into private registry with optional port: 29 | 30 | ```ruby 31 | docker_registry 'my local registry' do 32 | serveraddress 'https://registry.computers.biz:8443/' 33 | username 'privateme' 34 | password 'still_hope_this_is_in_encrypted_databag' 35 | email 'privateme@computers.biz' 36 | end 37 | ``` 38 | -------------------------------------------------------------------------------- /documentation/docker_service_manager_execute.md: -------------------------------------------------------------------------------- 1 | # docker_service_manager_execute 2 | 3 | The `docker_service_manager_execute` resource manages the Docker daemon using Chef's execute resources. This is a basic service manager that uses shell commands to start, stop, and restart the Docker daemon. 4 | 5 | ## Actions 6 | 7 | - `:start` - Starts the Docker daemon 8 | - `:stop` - Stops the Docker daemon 9 | - `:restart` - Restarts the Docker daemon (stop followed by start) 10 | 11 | ## Properties 12 | 13 | This resource inherits properties from the `docker_service_base` resource. Common properties include: 14 | 15 | | Property | Type | Default | Description | 16 | |-------------------|--------|------------|------------------------------------------------| 17 | | `docker_daemon_cmd`| String | Generated | Command to start the Docker daemon | 18 | | `logfile` | String | Based on name | Path to the log file | 19 | | `pidfile` | String | Based on name | Path to the PID file | 20 | | `http_proxy` | String | `nil` | HTTP proxy settings | 21 | | `https_proxy` | String | `nil` | HTTPS proxy settings | 22 | | `no_proxy` | String | `nil` | No proxy settings | 23 | | `tmpdir` | String | `nil` | Temporary directory path | 24 | 25 | ## Examples 26 | 27 | ### Basic Usage 28 | 29 | ```ruby 30 | docker_service_manager_execute 'default' do 31 | action :start 32 | end 33 | ``` 34 | 35 | ### Start Docker with Custom Settings 36 | 37 | ```ruby 38 | docker_service_manager_execute 'default' do 39 | http_proxy 'http://proxy.example.com:3128' 40 | https_proxy 'http://proxy.example.com:3128' 41 | no_proxy 'localhost,127.0.0.1' 42 | action :start 43 | end 44 | ``` 45 | 46 | ### Stop Docker Service 47 | 48 | ```ruby 49 | docker_service_manager_execute 'default' do 50 | action :stop 51 | end 52 | ``` 53 | 54 | ### Restart Docker Service 55 | 56 | ```ruby 57 | docker_service_manager_execute 'default' do 58 | action :restart 59 | end 60 | ``` 61 | 62 | ## Notes 63 | 64 | - This resource enables IPv4 and IPv6 forwarding using sysctl 65 | - The Docker daemon is started as a background process using bash 66 | - A wait script is created to ensure the daemon is ready before proceeding 67 | - The stop action uses a timeout of 10 seconds when stopping the daemon 68 | - The resource uses process checking to prevent duplicate daemon instances 69 | - Log output is redirected to the specified logfile 70 | - Environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY, TMPDIR) are passed to the daemon process 71 | 72 | ## Platform Support 73 | 74 | This resource should work on any platform that can run the Docker daemon, but it's primarily tested on Linux systems. 75 | -------------------------------------------------------------------------------- /documentation/docker_service_manager_systemd.md: -------------------------------------------------------------------------------- 1 | # docker_service_manager_systemd 2 | 3 | The `docker_service_manager_systemd` resource manages the Docker daemon using systemd. This is the preferred way to manage Docker on systems that use systemd as their init system. 4 | 5 | ## Actions 6 | 7 | - `:start` - Starts and enables the Docker daemon 8 | - `:stop` - Stops and disables the Docker daemon 9 | - `:restart` - Restarts the Docker daemon (stop followed by start) 10 | 11 | ## Properties 12 | 13 | This resource inherits properties from the `docker_service_base` resource. Common properties include: 14 | 15 | | Property | Type | Default | Description | 16 | |-------------------|---------|------------|--------------------------------------------------| 17 | | `docker_daemon_cmd`| String | Generated | Command to start the Docker daemon | 18 | | `docker_name` | String | `'docker'` | Name of the Docker service | 19 | | `connect_socket` | String | `nil` | Docker socket path | 20 | | `docker_containerd`| Boolean | - | Whether to use containerd | 21 | | `env_vars` | Hash | `{}` | Environment variables for the Docker daemon | 22 | | `systemd_socket_args`| Hash | `{}` | Additional systemd socket arguments | 23 | | `systemd_args` | Hash | `{}` | Additional systemd service arguments | 24 | 25 | ## Examples 26 | 27 | ### Basic Usage 28 | 29 | ```ruby 30 | docker_service_manager_systemd 'default' do 31 | action :start 32 | end 33 | ``` 34 | 35 | ### Custom Service Configuration 36 | 37 | ```ruby 38 | docker_service_manager_systemd 'default' do 39 | systemd_args({ 40 | 'TimeoutStartSec' => '0', 41 | 'ExecStartPost' => '/usr/bin/sleep 1' 42 | }) 43 | env_vars({ 44 | 'HTTP_PROXY' => 'http://proxy.example.com:3128', 45 | 'NO_PROXY' => 'localhost,127.0.0.1' 46 | }) 47 | action :start 48 | end 49 | ``` 50 | 51 | ### Using Custom Socket 52 | 53 | ```ruby 54 | docker_service_manager_systemd 'default' do 55 | connect_socket 'unix:///var/run/custom-docker.sock' 56 | action :start 57 | end 58 | ``` 59 | 60 | ### Stop Docker Service 61 | 62 | ```ruby 63 | docker_service_manager_systemd 'default' do 64 | action :stop 65 | end 66 | ``` 67 | 68 | ## Files Created/Modified 69 | 70 | The resource manages the following files: 71 | 72 | - `/lib/systemd/system/[docker_name].socket` - Main systemd socket file 73 | - `/etc/systemd/system/[docker_name].socket` - Socket override file 74 | - `/lib/systemd/system/[docker_name].service` - Main systemd service file 75 | - `/etc/systemd/system/[docker_name].service` - Service override file 76 | - `/etc/containerd/config.toml` - Containerd configuration 77 | - `/etc/systemd/system/containerd.service` - Containerd service file (if enabled) 78 | 79 | ## Notes 80 | 81 | - This resource is only available on Linux systems using systemd 82 | - Automatically creates and manages containerd configuration when needed 83 | - Supports both socket activation and direct service management 84 | - Handles systemd daemon-reload automatically when configurations change 85 | - Includes retry logic for service start operations 86 | - Creates a wait-ready script to ensure Docker is fully operational 87 | - Supports custom environment variables and systemd unit options 88 | - Can be used with custom Docker socket paths 89 | - Manages both the Docker service and its associated socket unit 90 | 91 | ## Platform Support 92 | 93 | This resource is supported on Linux distributions that use systemd as their init system, including: 94 | 95 | - Recent versions of Ubuntu (16.04+) 96 | - Recent versions of Debian (8+) 97 | - Recent versions of CentOS/RHEL (7+) 98 | - Recent versions of Fedora 99 | -------------------------------------------------------------------------------- /documentation/docker_swarm_init.md: -------------------------------------------------------------------------------- 1 | # docker_swarm_init 2 | 3 | The `docker_swarm_init` resource initializes a new Docker swarm cluster. 4 | 5 | ## Actions 6 | 7 | - `:init` - Initialize a new swarm 8 | - `:leave` - Leave the swarm (must be run on a manager node) 9 | 10 | ## Properties 11 | 12 | | Property | Type | Default | Description | 13 | |------------------------|---------------|---------|-----------------------------------------------| 14 | | `advertise_addr` | String | nil | Advertised address for other nodes to connect | 15 | | `autolock` | [true, false] | false | Enable manager auto-locking | 16 | | `cert_expiry` | String | nil | Validity period for node certificates | 17 | | `data_path_addr` | String | nil | Address for data path traffic | 18 | | `dispatcher_heartbeat` | String | nil | Dispatcher heartbeat period | 19 | | `force_new_cluster` | [true, false] | false | Force create a new cluster from current state | 20 | | `listen_addr` | String | nil | Listen address | 21 | | `max_snapshots` | Integer | nil | Number of snapshots to keep | 22 | | `snapshot_interval` | Integer | nil | Number of log entries between snapshots | 23 | | `task_history_limit` | Integer | nil | Task history retention limit | 24 | 25 | ## Examples 26 | 27 | ### Initialize a basic swarm 28 | 29 | ```ruby 30 | docker_swarm_init 'default' do 31 | advertise_addr '192.168.1.2' 32 | listen_addr '0.0.0.0:2377' 33 | end 34 | ``` 35 | 36 | ### Initialize a swarm with auto-locking enabled 37 | 38 | ```ruby 39 | docker_swarm_init 'secure' do 40 | advertise_addr '192.168.1.2' 41 | autolock true 42 | cert_expiry '48h' 43 | end 44 | ``` 45 | 46 | ### Leave a swarm 47 | 48 | ```ruby 49 | docker_swarm_init 'default' do 50 | action :leave 51 | end 52 | ``` 53 | 54 | ## Notes 55 | 56 | - Only initialize a swarm on one node - other nodes should join using `docker_swarm_join` 57 | - The node that initializes the swarm becomes the first manager 58 | - Auto-locking requires additional security steps to unlock managers after a restart 59 | - The worker token is automatically stored in node attributes for use by worker nodes 60 | -------------------------------------------------------------------------------- /documentation/docker_swarm_join.md: -------------------------------------------------------------------------------- 1 | # docker_swarm_join 2 | 3 | The `docker_swarm_join` resource allows a node to join an existing Docker swarm cluster. 4 | 5 | ## Actions 6 | 7 | - `:join` - Join a swarm cluster 8 | - `:leave` - Leave the swarm cluster (--force is always used) 9 | 10 | ## Properties 11 | 12 | | Property | Type | Default | Description | 13 | |------------------|--------|----------|--------------------------------------| 14 | | `token` | String | Required | Swarm join token (worker or manager) | 15 | | `manager_ip` | String | Required | IP address of a manager node | 16 | | `advertise_addr` | String | nil | Advertised address for this node | 17 | | `listen_addr` | String | nil | Listen address for the node | 18 | | `data_path_addr` | String | nil | Address for data path traffic | 19 | 20 | ## Examples 21 | 22 | ### Join a node to the swarm 23 | 24 | ```ruby 25 | docker_swarm_join 'worker' do 26 | token 'SWMTKN-1-xxxx' 27 | manager_ip '192.168.1.2' 28 | end 29 | ``` 30 | 31 | ### Join with custom network configuration 32 | 33 | ```ruby 34 | docker_swarm_join 'worker-custom' do 35 | token 'SWMTKN-1-xxxx' 36 | manager_ip '192.168.1.2' 37 | advertise_addr '192.168.1.3' 38 | listen_addr '0.0.0.0:2377' 39 | end 40 | ``` 41 | 42 | ### Leave the swarm 43 | 44 | ```ruby 45 | docker_swarm_join 'worker' do 46 | token 'SWMTKN-1-xxxx' 47 | manager_ip '192.168.1.2' 48 | action :leave 49 | end 50 | ``` 51 | 52 | ## Notes 53 | 54 | - The join token can be obtained from a manager node using `docker_swarm_token` 55 | - The default port for swarm communication is 2377 56 | - Use `advertise_addr` when the node has multiple network interfaces 57 | - The `:leave` action will always use the --force flag 58 | - The resource is idempotent and will not try to join if the node is already a swarm member 59 | -------------------------------------------------------------------------------- /documentation/docker_swarm_service.md: -------------------------------------------------------------------------------- 1 | # docker_swarm_service 2 | 3 | The `docker_swarm_service` resource manages Docker services in a swarm cluster. 4 | 5 | ## Actions 6 | 7 | - `:create` - Create a new service 8 | - `:update` - Update an existing service 9 | - `:delete` - Remove a service 10 | 11 | ## Properties 12 | 13 | | Property | Type | Default | Description | 14 | |-------------------|---------------|---------------|-----------------------------------------| 15 | | `service_name` | String | name_property | Name of the service | 16 | | `image` | String | nil | Docker image to use for the service | 17 | | `command` | String, Array | nil | Command to run in the container | 18 | | `env` | Array | nil | Environment variables | 19 | | `labels` | Hash | nil | Service labels | 20 | | `mounts` | Array | nil | Volume mounts | 21 | | `networks` | Array | nil | Networks to attach the service to | 22 | | `ports` | Array | nil | Port mappings | 23 | | `replicas` | Integer | nil | Number of replicas to run | 24 | | `secrets` | Array | nil | Docker secrets to expose to the service | 25 | | `configs` | Array | nil | Docker configs to expose to the service | 26 | | `constraints` | Array | nil | Placement constraints | 27 | | `preferences` | Array | nil | Placement preferences | 28 | | `endpoint_mode` | String | nil | Endpoint mode ('vip' or 'dnsrr') | 29 | | `update_config` | Hash | nil | Service update configuration | 30 | | `rollback_config` | Hash | nil | Service rollback configuration | 31 | | `restart_policy` | Hash | nil | Service restart policy | 32 | 33 | ## Examples 34 | 35 | ### Create a simple web service 36 | 37 | ```ruby 38 | docker_swarm_service 'web' do 39 | image 'nginx:latest' 40 | ports ['80:80'] 41 | replicas 2 42 | end 43 | ``` 44 | 45 | ### Create a service with environment variables and constraints 46 | 47 | ```ruby 48 | docker_swarm_service 'api' do 49 | image 'api:v1' 50 | env ['API_KEY=secret', 'DEBUG=1'] 51 | constraints ['node.role==worker'] 52 | replicas 3 53 | ports ['8080:8080'] 54 | restart_policy({ 'condition' => 'on-failure', 'max_attempts' => 3 }) 55 | end 56 | ``` 57 | 58 | ### Update an existing service 59 | 60 | ```ruby 61 | docker_swarm_service 'web' do 62 | image 'nginx:1.19' 63 | replicas 4 64 | action :update 65 | end 66 | ``` 67 | 68 | ### Delete a service 69 | 70 | ```ruby 71 | docker_swarm_service 'old-service' do 72 | action :delete 73 | end 74 | ``` 75 | 76 | ## Notes 77 | 78 | - The node must be a swarm manager to manage services 79 | - Service updates are performed in a rolling fashion by default 80 | - Use `update_config` to fine-tune the update behavior 81 | - Network attachments must be to overlay networks or networks with swarm scope 82 | -------------------------------------------------------------------------------- /documentation/docker_swarm_token.md: -------------------------------------------------------------------------------- 1 | # docker_swarm_token 2 | 3 | The `docker_swarm_token` resource manages Docker Swarm tokens for worker and manager nodes. 4 | 5 | ## Actions 6 | 7 | - `:read` - Read the current token value 8 | - `:rotate` - Rotate the token to a new value 9 | - `:remove` - Remove the token (not typically used) 10 | 11 | ## Properties 12 | 13 | | Property | Type | Default | Description | 14 | |--------------|---------------|---------------|---------------------------------------------------------------| 15 | | `token_type` | String | name_property | Type of token to manage. Must be either 'worker' or 'manager' | 16 | | `rotate` | [true, false] | false | Whether to rotate the token to a new value | 17 | 18 | ## Examples 19 | 20 | ### Read a worker token 21 | 22 | ```ruby 23 | docker_swarm_token 'worker' do 24 | action :read 25 | end 26 | ``` 27 | 28 | ### Rotate a manager token 29 | 30 | ```ruby 31 | docker_swarm_token 'manager' do 32 | rotate true 33 | action :read 34 | end 35 | ``` 36 | 37 | ## Notes 38 | 39 | - The token values are stored in `node.run_state['docker_swarm']` with keys `worker_token` and `manager_token` 40 | - Token rotation is a security feature that invalidates old tokens 41 | - Only swarm managers can read or rotate tokens 42 | -------------------------------------------------------------------------------- /documentation/docker_tag.md: -------------------------------------------------------------------------------- 1 | 2 | # docker_tag 3 | 4 | Docker tags work very much like hard links in a Unix filesystem. They are just references to an existing image. Therefore, the docker_tag resource has taken inspiration from the Chef `link` resource. 5 | 6 | ## Actions 7 | 8 | - `:tag` - Tags the image 9 | 10 | ## Properties 11 | 12 | - `target_repo` - The repo half of the source image identifier. 13 | - `target_tag` - The tag half of the source image identifier. 14 | - `to_repo` - The repo half of the new image identifier 15 | - `to_tag`- The tag half of the new image identifier 16 | 17 | ## Examples 18 | 19 | ```ruby 20 | docker_tag 'private repo tag for hello-again:1.0.1' do 21 | target_repo 'hello-again' 22 | target_tag 'v0.1.0' 23 | to_repo 'localhost:5043/someara/hello-again' 24 | to_tag 'latest' 25 | action :tag 26 | end 27 | ``` 28 | -------------------------------------------------------------------------------- /documentation/docker_volume.md: -------------------------------------------------------------------------------- 1 | 2 | # docker_volume 3 | 4 | The `docker_volume` resource is responsible for managing Docker named volumes. 5 | 6 | ## Actions 7 | 8 | - `:create` - create a volume 9 | - `:remove` - remove a volume 10 | 11 | ## Properties 12 | 13 | - `driver` - Name of the volume driver to use. Only used for `:create`. 14 | - `host` 15 | - `opts` - Options to pass to the volume driver. Only used for `:create`. 16 | - `volume` 17 | - `volume_name` - Name of the volume to operate on (defaults to the resource name). 18 | 19 | ## Examples 20 | 21 | Create a volume named 'hello' 22 | 23 | ```ruby 24 | docker_volume 'hello' do 25 | action :create 26 | end 27 | 28 | docker_container 'file_writer' do 29 | repo 'alpine' 30 | tag '3.1' 31 | volumes 'hello:/hello' 32 | command 'touch /hello/sean_was_here' 33 | action :run_if_missing 34 | end 35 | ``` 36 | -------------------------------------------------------------------------------- /documentation/docker_volume_prune.md: -------------------------------------------------------------------------------- 1 | # docker_volume_prune 2 | 3 | The `docker_volume_prune` resource removes all unused Docker volumes. Volumes that are still referenced by at least one container are not removed. 4 | 5 | ## Actions 6 | 7 | - `:prune` - Remove unused Docker volumes 8 | 9 | ## Properties 10 | 11 | | Property | Type | Default | Description | 12 | |-----------------|---------|----------------------|---------------------------------------------------| 13 | | `without_label` | String | `nil` | Only remove volumes without the specified label | 14 | | `with_label` | String | `nil` | Only remove volumes with the specified label | 15 | | `read_timeout` | Integer | `120` | HTTP read timeout for Docker API calls | 16 | | `host` | String | `ENV['DOCKER_HOST']` | Docker daemon socket to connect to | 17 | | `all` | Boolean | `false` | Remove all unused volumes, not just dangling ones | 18 | 19 | ## Examples 20 | 21 | ### Basic Usage - Remove Unused Volumes 22 | 23 | ```ruby 24 | docker_volume_prune 'prune' do 25 | action :prune 26 | end 27 | ``` 28 | 29 | ### Remove All Unused Volumes 30 | 31 | ```ruby 32 | docker_volume_prune 'prune_all' do 33 | all true 34 | action :prune 35 | end 36 | ``` 37 | 38 | ### Remove Volumes with Specific Label 39 | 40 | ```ruby 41 | docker_volume_prune 'prune_labeled' do 42 | with_label 'environment=test' 43 | action :prune 44 | end 45 | ``` 46 | 47 | ### Remove Volumes Without Specific Label 48 | 49 | ```ruby 50 | docker_volume_prune 'prune_without_label' do 51 | without_label 'environment=production' 52 | action :prune 53 | end 54 | ``` 55 | 56 | ### Custom Docker Host 57 | 58 | ```ruby 59 | docker_volume_prune 'prune' do 60 | host 'tcp://127.0.0.1:2375' 61 | action :prune 62 | end 63 | ``` 64 | 65 | ## Notes 66 | 67 | - Uses Docker Engine API v1.42 68 | - The prune operation removes all unused volumes that are not referenced by any containers 69 | - The operation is irreversible - once a volume is pruned, its data cannot be recovered 70 | - The resource logs the result of the prune operation 71 | - The `read_timeout` property can be adjusted if the operation takes longer than expected 72 | - Label filters can be used to selectively prune volumes based on their metadata 73 | 74 | ## Platform Support 75 | 76 | This resource is supported on any platform that can run the Docker daemon. 77 | -------------------------------------------------------------------------------- /kitchen.dokken.yml: -------------------------------------------------------------------------------- 1 | driver: 2 | name: dokken 3 | privileged: true 4 | chef_version: <%= ENV['CHEF_VERSION'] || 'current' %> 5 | 6 | transport: { name: dokken } 7 | provisioner: { name: dokken } 8 | 9 | platforms: 10 | - name: almalinux-8 11 | driver: 12 | image: dokken/almalinux-8 13 | pid_one_command: /usr/lib/systemd/systemd 14 | 15 | - name: almalinux-9 16 | driver: 17 | image: dokken/almalinux-9 18 | pid_one_command: /usr/lib/systemd/systemd 19 | 20 | - name: almalinux-10 21 | driver: 22 | image: dokken/almalinux-10 23 | pid_one_command: /usr/lib/systemd/systemd 24 | 25 | - name: amazonlinux-2023 26 | driver: 27 | image: dokken/amazonlinux-2023 28 | pid_one_command: /usr/lib/systemd/systemd 29 | 30 | - name: centos-stream-9 31 | driver: 32 | image: dokken/centos-stream-9 33 | pid_one_command: /usr/lib/systemd/systemd 34 | 35 | - name: centos-stream-10 36 | driver: 37 | image: dokken/centos-stream-10 38 | pid_one_command: /usr/lib/systemd/systemd 39 | 40 | - name: debian-11 41 | driver: 42 | image: dokken/debian-11 43 | pid_one_command: /bin/systemd 44 | 45 | - name: debian-12 46 | driver: 47 | image: dokken/debian-12 48 | pid_one_command: /bin/systemd 49 | 50 | - name: fedora-latest 51 | driver: 52 | image: dokken/fedora-latest 53 | pid_one_command: /usr/lib/systemd/systemd 54 | 55 | - name: opensuse-leap-15 56 | driver: 57 | image: dokken/opensuse-leap-15 58 | pid_one_command: /usr/lib/systemd/systemd 59 | 60 | - name: oraclelinux-8 61 | driver: 62 | image: dokken/oraclelinux-8 63 | pid_one_command: /usr/lib/systemd/systemd 64 | 65 | - name: oraclelinux-9 66 | driver: 67 | image: dokken/oraclelinux-9 68 | pid_one_command: /usr/lib/systemd/systemd 69 | 70 | - name: rockylinux-8 71 | driver: 72 | image: dokken/rockylinux-8 73 | pid_one_command: /usr/lib/systemd/systemd 74 | 75 | - name: rockylinux-9 76 | driver: 77 | image: dokken/rockylinux-9 78 | pid_one_command: /usr/lib/systemd/systemd 79 | 80 | - name: ubuntu-20.04 81 | driver: 82 | image: dokken/ubuntu-20.04 83 | pid_one_command: /bin/systemd 84 | 85 | - name: ubuntu-22.04 86 | driver: 87 | image: dokken/ubuntu-22.04 88 | pid_one_command: /bin/systemd 89 | 90 | - name: ubuntu-24.04 91 | driver: 92 | image: dokken/ubuntu-24.04 93 | pid_one_command: /bin/systemd 94 | -------------------------------------------------------------------------------- /kitchen.exec.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: { name: exec } 3 | transport: { name: exec } 4 | 5 | platforms: 6 | - name: macos-latest 7 | - name: windows-latest 8 | -------------------------------------------------------------------------------- /kitchen.global.yml: -------------------------------------------------------------------------------- 1 | --- 2 | provisioner: 3 | name: chef_infra 4 | product_name: chef 5 | product_version: <%= ENV['CHEF_VERSION'] || 'latest' %> 6 | channel: stable 7 | install_strategy: once 8 | chef_license: accept 9 | enforce_idempotency: <%= ENV['ENFORCE_IDEMPOTENCY'] || true %> 10 | multiple_converge: <%= ENV['MULTIPLE_CONVERGE'] || 2 %> 11 | deprecations_as_errors: true 12 | log_level: <%= ENV['CHEF_LOG_LEVEL'] || 'auto' %> 13 | 14 | verifier: 15 | name: inspec 16 | 17 | platforms: 18 | - name: almalinux-8 19 | - name: almalinux-9 20 | - name: amazonlinux-2023 21 | - name: centos-stream-9 22 | - name: debian-11 23 | - name: debian-12 24 | - name: fedora-latest 25 | - name: opensuse-leap-15 26 | - name: oraclelinux-8 27 | - name: oraclelinux-9 28 | - name: rockylinux-8 29 | - name: rockylinux-9 30 | - name: ubuntu-20.04 31 | - name: ubuntu-22.04 32 | - name: ubuntu-24.04 33 | -------------------------------------------------------------------------------- /kitchen.yml: -------------------------------------------------------------------------------- 1 | --- 2 | driver: 3 | name: vagrant 4 | customize: 5 | memory: 2048 6 | cpus: 2 7 | 8 | provisioner: 9 | name: chef_infra 10 | product_name: <%= ENV['CHEF_PRODUCT_NAME'] || 'chef' %> 11 | product_version: <%= ENV['CHEF_VERSION'] || 'latest' %> 12 | enforce_idempotency: true 13 | multiple_converge: 2 14 | deprecations_as_errors: true 15 | chef_license: accept-no-persist 16 | 17 | verifier: 18 | name: inspec 19 | 20 | platforms: 21 | - name: almalinux-8 22 | - name: almalinux-9 23 | - name: debian-11 24 | - name: debian-12 25 | - name: rockylinux-8 26 | - name: rockylinux-9 27 | - name: ubuntu-20.04 28 | - name: ubuntu-22.04 29 | - name: ubuntu-24.04 30 | 31 | suites: 32 | - name: installation_script 33 | excludes: 34 | - 'almalinux' 35 | - 'amazonlinux' 36 | - 'rockylinux-9' 37 | run_list: 38 | - recipe[docker_test::installation_script] 39 | 40 | - name: installation_package 41 | run_list: 42 | - recipe[docker_test::installation_package] 43 | excludes: 44 | - 'amazonlinux-2' 45 | 46 | - name: installation_tarball 47 | run_list: 48 | - recipe[docker_test::installation_tarball] 49 | 50 | - name: install_and_stop 51 | run_list: 52 | - recipe[docker_test::install_and_stop] 53 | 54 | ################## 55 | # resource testing 56 | ################## 57 | - name: resources 58 | provisioner: 59 | enforce_idempotency: false 60 | multiple_converge: 1 61 | run_list: 62 | - recipe[docker_test::default] 63 | - recipe[docker_test::image] 64 | - recipe[docker_test::container] 65 | - recipe[docker_test::exec] 66 | - recipe[docker_test::plugin] 67 | - recipe[docker_test::image_prune] 68 | - recipe[docker_test::volume_prune] 69 | 70 | - name: network 71 | provisioner: 72 | enforce_idempotency: false 73 | multiple_converge: 1 74 | run_list: 75 | - recipe[docker_test::default] 76 | - recipe[docker_test::network] 77 | 78 | - name: volume 79 | provisioner: 80 | enforce_idempotency: false 81 | multiple_converge: 1 82 | run_list: 83 | - recipe[docker_test::default] 84 | - recipe[docker_test::volume] 85 | - recipe[docker_test::volume_prune] 86 | 87 | - name: registry 88 | provisioner: 89 | enforce_idempotency: false 90 | multiple_converge: 1 91 | run_list: 92 | - recipe[docker_test::default] 93 | - recipe[docker_test::registry] 94 | 95 | #################### 96 | # swarm testing 97 | #################### 98 | 99 | - name: swarm 100 | driver: 101 | network: 102 | - ["private_network", {ip: "192.168.56.10"}] 103 | provisioner: 104 | enforce_idempotency: false 105 | multiple_converge: 1 106 | attributes: 107 | docker: 108 | swarm: 109 | init: 110 | advertise_addr: '192.168.56.10' 111 | listen_addr: '0.0.0.0:2377' 112 | rotate_token: true 113 | service: 114 | name: 'web' 115 | image: 'nginx:latest' 116 | publish: ['80:80'] 117 | replicas: 2 118 | run_list: 119 | - recipe[docker_test::swarm] 120 | - recipe[docker_test::swarm_service] 121 | 122 | - name: swarm_worker 123 | driver: 124 | network: 125 | - ["private_network", {ip: "192.168.56.11"}] 126 | provisioner: 127 | enforce_idempotency: false 128 | multiple_converge: 1 129 | attributes: 130 | docker: 131 | swarm: 132 | join: 133 | manager_ip: '192.168.56.10:2377' 134 | advertise_addr: '192.168.56.11' 135 | listen_addr: '0.0.0.0:2377' 136 | # Token will be obtained from the manager node 137 | run_list: 138 | - recipe[docker_test::swarm_worker] 139 | 140 | ############################# 141 | # quick service smoke testing 142 | ############################# 143 | 144 | - name: smoke 145 | run_list: 146 | - recipe[docker_test::smoke] 147 | 148 | ############################### 149 | # docker_swarm resources 150 | ############################### 151 | - name: swarm 152 | includes: 153 | - ubuntu-22.04 154 | provisioner: 155 | enforce_idempotency: false 156 | multiple_converge: 1 157 | attributes: 158 | docker: 159 | swarm: 160 | init: 161 | advertise_addr: '127.0.0.1' 162 | listen_addr: '0.0.0.0:2377' 163 | rotate_token: true 164 | service: 165 | name: 'web' 166 | image: 'nginx:latest' 167 | publish: ['80:80'] 168 | replicas: 2 169 | run_list: 170 | - recipe[docker_test::swarm] 171 | - recipe[docker_test::swarm_service] 172 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | commands: 3 | yamllint: 4 | tags: yaml style 5 | glob: "*.yml" 6 | run: yamllint {staged_files} 7 | stage_fixed: true 8 | rubocop: 9 | tags: backend style 10 | glob: "*.rb" 11 | exclude: '(^|/)(application|routes)\.rb$' 12 | run: chef exec rubocop {staged_files} 13 | stage_fixed: true 14 | rspec: 15 | tags: backend test 16 | glob: "spec/*.rb" 17 | run: chef exec rspec {staged_files} 18 | stage_fixed: true 19 | -------------------------------------------------------------------------------- /libraries/base.rb: -------------------------------------------------------------------------------- 1 | module Docker 2 | module Cookbook 3 | module Helpers 4 | # https://github.com/docker/docker/blob/4fcb9ac40ce33c4d6e08d5669af6be5e076e2574/registry/auth.go#L231 5 | def parse_registry_host(val) 6 | val.sub(%r{https?://}, '').split('/').first 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /libraries/helpers_coerce.rb: -------------------------------------------------------------------------------- 1 | module DockerCookbook 2 | module DockerHelpers 3 | module Build 4 | def coerce_buildargs(v) 5 | "{ #{v.map { |key, value| "\"#{key}\": \"#{value}\"" }.join(', ')} }" 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /libraries/helpers_container.rb: -------------------------------------------------------------------------------- 1 | module DockerCookbook 2 | module DockerHelpers 3 | module Container 4 | def cgroupv2? 5 | return if node.dig('filesystem', 'by_device').nil? 6 | node.dig('filesystem', 'by_device').key?('cgroup2') 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /libraries/helpers_json.rb: -------------------------------------------------------------------------------- 1 | module DockerCookbook 2 | module DockerHelpers 3 | module Json 4 | def prune_generate_json(dangling:, prune_until: nil, with_label: nil, without_label: nil, all: false) 5 | opts = { dangling: { "#{dangling}": true } } 6 | opts['until'] = { "#{prune_until}": true } if prune_until 7 | opts['label'] = { "#{with_label}": true } if with_label 8 | opts['label!'] = { "#{without_label}": true } if without_label 9 | opts['all'] = true if all 10 | 11 | 'filters=' + URI.encode_www_form_component(opts.to_json) 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /libraries/helpers_network.rb: -------------------------------------------------------------------------------- 1 | module DockerCookbook 2 | module DockerHelpers 3 | module Network 4 | # Gets the ip address from the existing container 5 | # current docker api of 1.16 does not have ['NetworkSettings']['Networks'] 6 | # For docker > 1.21 - use ['NetworkSettings']['Networks'] 7 | # 8 | # @param container [Docker::Container] A container object 9 | # @returns [String] An ip_address 10 | def ip_address_from_container_networks(container) 11 | # We use the first value in 'Networks' 12 | # We can't assume it will be 'bridged' 13 | # It might also not match the new_resource value 14 | if container.info['NetworkSettings'] && 15 | container.info['NetworkSettings']['Networks'] && 16 | container.info['NetworkSettings']['Networks'].values[0] && 17 | container.info['NetworkSettings']['Networks'].values[0]['IPAMConfig'] && 18 | container.info['NetworkSettings']['Networks'].values[0]['IPAMConfig']['IPv4Address'] 19 | # Return the ip address listed 20 | container.info['NetworkSettings']['Networks'].values[0]['IPAMConfig']['IPv4Address'] 21 | end 22 | end 23 | 24 | def normalize_container_network_mode(mode) 25 | return mode unless mode.is_a?(String) && mode.start_with?('container:') 26 | 27 | # Extract container name/id from network mode 28 | container_ref = mode.split(':', 2)[1] 29 | begin 30 | # Try to get the container by name or ID 31 | container = Docker::Container.get(container_ref, {}, connection) 32 | # Return normalized form with full container ID 33 | "container:#{container.id}" 34 | rescue Docker::Error::NotFoundError, Docker::Error::TimeoutError 35 | # If container not found, return original value 36 | mode 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /libraries/helpers_swarm.rb: -------------------------------------------------------------------------------- 1 | module DockerCookbook 2 | module DockerHelpers 3 | module Swarm 4 | def swarm_init_cmd(resource = nil) 5 | cmd = %w(docker swarm init) 6 | cmd << "--advertise-addr #{resource.advertise_addr}" if resource && resource.advertise_addr 7 | cmd << "--listen-addr #{resource.listen_addr}" if resource && resource.listen_addr 8 | cmd << '--force-new-cluster' if resource && resource.force_new_cluster 9 | cmd 10 | end 11 | 12 | def swarm_join_cmd(resource = nil) 13 | cmd = %w(docker swarm join) 14 | cmd << "--token #{resource.token}" if resource 15 | cmd << "--advertise-addr #{resource.advertise_addr}" if resource && resource.advertise_addr 16 | cmd << "--listen-addr #{resource.listen_addr}" if resource && resource.listen_addr 17 | cmd << resource.manager_ip if resource 18 | cmd 19 | end 20 | 21 | def swarm_leave_cmd(resource = nil) 22 | cmd = %w(docker swarm leave) 23 | cmd << '--force' if resource && resource.force 24 | cmd 25 | end 26 | 27 | def swarm_token_cmd(token_type) 28 | raise 'Token type must be worker or manager' unless %w(worker manager).include?(token_type) 29 | %w(docker swarm join-token -q) << token_type 30 | end 31 | 32 | def swarm_member? 33 | cmd = Mixlib::ShellOut.new('docker info --format "{{ .Swarm.LocalNodeState }}"') 34 | cmd.run_command 35 | return false if cmd.error? 36 | cmd.stdout.strip == 'active' 37 | end 38 | 39 | def swarm_manager? 40 | return false unless swarm_member? 41 | cmd = Mixlib::ShellOut.new('docker info --format "{{ .Swarm.ControlAvailable }}"') 42 | cmd.run_command 43 | return false if cmd.error? 44 | cmd.stdout.strip == 'true' 45 | end 46 | 47 | def swarm_worker? 48 | swarm_member? && !swarm_manager? 49 | end 50 | 51 | def service_exists?(name) 52 | cmd = Mixlib::ShellOut.new("docker service inspect #{name}") 53 | cmd.run_command 54 | !cmd.error? 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /metadata.rb: -------------------------------------------------------------------------------- 1 | name 'docker' 2 | maintainer 'Sous Chefs' 3 | maintainer_email 'help@sous-chefs.org' 4 | license 'Apache-2.0' 5 | description 'Provides docker_service, docker_image, and docker_container resources' 6 | version '11.9.2' 7 | source_url 'https://github.com/sous-chefs/docker' 8 | issues_url 'https://github.com/sous-chefs/docker/issues' 9 | chef_version '>= 16.0', '< 19.0' 10 | 11 | supports 'amazon' 12 | supports 'centos' 13 | supports 'scientific' 14 | supports 'oracle' 15 | supports 'debian' 16 | supports 'fedora' 17 | supports 'redhat' 18 | supports 'ubuntu' 19 | 20 | gem 'docker-api', '>= 2.3', '< 3' 21 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "packageRules": [ 5 | { 6 | "groupName": "Actions", 7 | "matchUpdateTypes": ["minor", "patch", "pin"], 8 | "automerge": true, 9 | "addLabels": ["Release: Patch", "Skip: Announcements"] 10 | }, 11 | { 12 | "groupName": "Actions", 13 | "matchUpdateTypes": ["major"], 14 | "automerge": false, 15 | "addLabels": ["Release: Patch", "Skip: Announcements"] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /resources/exec.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | property :host, [String, nil], default: lazy { ENV['DOCKER_HOST'] }, desired_state: false 5 | property :command, Array 6 | property :container, String 7 | property :timeout, Numeric, default: 60, desired_state: false 8 | property :container_obj, Docker::Container, desired_state: false 9 | property :returns, [ Integer, Array ], coerce: proc { |v| Array(v) }, default: [0], 10 | description: 'The return value for a command. This may be an array of accepted values. An exception is raised when the return value(s) do not match.' 11 | 12 | alias_method :cmd, :command 13 | 14 | action :run do 15 | converge_by "executing #{new_resource.command} on #{new_resource.container}" do 16 | with_retries { new_resource.container_obj Docker::Container.get(new_resource.container, {}, connection) } 17 | stdout, stderr, exit_code = new_resource.container_obj.exec(new_resource.command, wait: new_resource.timeout) 18 | Chef::Log.trace(stdout) 19 | Chef::Log.trace(stderr) 20 | unless new_resource.returns.include?(exit_code) 21 | raise "Expected process to exit with 0, but received #{exit_code}" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /resources/image.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | property :read_timeout, Integer, default: 120, desired_state: false 5 | property :host, [String, nil], default: lazy { ENV['DOCKER_HOST'] }, desired_state: false 6 | 7 | # https://docs.docker.com/engine/api/v1.35/#tag/Image 8 | property :destination, String 9 | property :force, [true, false], default: false, desired_state: false 10 | property :nocache, [true, false], default: false 11 | property :noprune, [true, false], default: false 12 | property :repo, String, name_property: true 13 | property :rm, [true, false], default: true 14 | property :source, String 15 | property :tag, String, default: 'latest' 16 | property :buildargs, [String, Hash], coerce: proc { |v| v.is_a?(String) ? v : coerce_buildargs(v) } 17 | 18 | alias_method :image, :repo 19 | alias_method :image_name, :repo 20 | alias_method :no_cache, :nocache 21 | alias_method :no_prune, :noprune 22 | 23 | include DockerCookbook::DockerHelpers::Build 24 | 25 | action :pull do 26 | # We already did the work, but we need to report what we did! 27 | converge_by "Pull image #{image_identifier}" do 28 | end if pull_image 29 | end 30 | 31 | action :build do 32 | converge_by "Build image #{image_identifier}" do 33 | build_image 34 | end 35 | end 36 | 37 | action :build_if_missing do 38 | return if Docker::Image.exist?(image_identifier, {}, connection) 39 | action_build 40 | end 41 | 42 | action :import do 43 | return if Docker::Image.exist?(image_identifier, {}, connection) 44 | converge_by "Import image #{image_identifier}" do 45 | import_image 46 | end 47 | end 48 | 49 | action :pull_if_missing do 50 | return if Docker::Image.exist?(image_identifier, {}, connection) 51 | action_pull 52 | end 53 | 54 | action :push do 55 | converge_by "Push image #{image_identifier}" do 56 | push_image 57 | end 58 | end 59 | 60 | action :remove do 61 | return unless Docker::Image.exist?(image_identifier, {}, connection) 62 | converge_by "Remove image #{image_identifier}" do 63 | remove_image 64 | end 65 | end 66 | 67 | action :save do 68 | converge_by "Save image #{image_identifier}" do 69 | save_image 70 | end 71 | end 72 | 73 | action :load do 74 | return if Docker::Image.exist?(image_identifier, {}, connection) 75 | converge_by "load image #{image_identifier}" do 76 | load_image 77 | end 78 | end 79 | 80 | action_class do 81 | def build_from_directory 82 | i = Docker::Image.build_from_dir( 83 | new_resource.source, 84 | { 85 | 'nocache' => new_resource.nocache, 86 | 'rm' => new_resource.rm, 87 | 'buildargs' => new_resource.buildargs, 88 | }, 89 | connection 90 | ) 91 | i.tag('repo' => new_resource.repo, 'tag' => new_resource.tag, 'force' => new_resource.force) 92 | end 93 | 94 | def build_from_dockerfile 95 | i = Docker::Image.build( 96 | IO.read(new_resource.source), 97 | { 98 | 'nocache' => new_resource.nocache, 99 | 'rm' => new_resource.rm, 100 | 'buildargs' => new_resource.buildargs, 101 | }, 102 | connection 103 | ) 104 | i.tag('repo' => new_resource.repo, 'tag' => new_resource.tag, 'force' => new_resource.force) 105 | end 106 | 107 | def build_from_tar 108 | i = Docker::Image.build_from_tar( 109 | ::File.open(new_resource.source, 'r'), 110 | { 111 | 'nocache' => new_resource.nocache, 112 | 'rm' => new_resource.rm, 113 | 'buildargs' => new_resource.buildargs, 114 | }, 115 | connection 116 | ) 117 | i.tag('repo' => new_resource.repo, 'tag' => new_resource.tag, 'force' => new_resource.force) 118 | end 119 | 120 | def build_image 121 | if ::File.directory?(new_resource.source) 122 | build_from_directory 123 | elsif ::File.extname(new_resource.source) == '.tar' 124 | build_from_tar 125 | else 126 | build_from_dockerfile 127 | end 128 | end 129 | 130 | def image_identifier 131 | "#{new_resource.repo}:#{new_resource.tag}" 132 | end 133 | 134 | def import_image 135 | with_retries do 136 | i = Docker::Image.import(new_resource.source, {}, connection) 137 | i.tag('repo' => new_resource.repo, 'tag' => new_resource.tag, 'force' => new_resource.force) 138 | end 139 | end 140 | 141 | def pull_image 142 | with_retries do 143 | creds = credentails 144 | original_image = Docker::Image.get(image_identifier, {}, connection) if Docker::Image.exist?(image_identifier, {}, connection) 145 | new_image = Docker::Image.create({ 'fromImage' => image_identifier }, creds, connection) 146 | 147 | !(original_image && original_image.id.start_with?(new_image.id)) 148 | end 149 | end 150 | 151 | def push_image 152 | with_retries do 153 | creds = credentails 154 | i = Docker::Image.get(image_identifier, {}, connection) 155 | i.push(creds, repo_tag: image_identifier) 156 | end 157 | end 158 | 159 | def remove_image 160 | with_retries do 161 | i = Docker::Image.get(image_identifier, {}, connection) 162 | i.remove(force: new_resource.force, noprune: new_resource.noprune) 163 | end 164 | end 165 | 166 | def save_image 167 | with_retries do 168 | Docker::Image.save(new_resource.repo, new_resource.destination, connection) 169 | end 170 | end 171 | 172 | def load_image 173 | with_retries do 174 | Docker::Image.load(new_resource.source, {}, connection) 175 | end 176 | end 177 | 178 | def credentails 179 | registry_host = parse_registry_host(new_resource.repo) 180 | node.run_state['docker_auth'] && node.run_state['docker_auth'][registry_host] || {} 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /resources/image_prune.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | property :read_timeout, Integer, default: 120, desired_state: false 5 | property :host, [String, nil], default: lazy { ENV['DOCKER_HOST'] }, desired_state: false 6 | 7 | # https://docs.docker.com/engine/api/v1.35/#operation/ImagePrune 8 | property :dangling, [true, false], default: true 9 | property :prune_until, String 10 | # https://docs.docker.com/engine/reference/builder/#label 11 | property :with_label, String 12 | property :without_label, String 13 | 14 | action :prune do 15 | # Have to call this method ourselves due to 16 | # https://github.com/swipely/docker-api/pull/507 17 | json = prune_generate_json(dangling: new_resource.dangling, prune_until: new_resource.prune_until, with_label: new_resource.with_label, without_label: new_resource.without_label) 18 | 19 | res = connection.post('/images/prune', json) 20 | Chef::Log.info res 21 | end 22 | 23 | action_class do 24 | include DockerCookbook::DockerHelpers::Json 25 | end 26 | -------------------------------------------------------------------------------- /resources/installation_script.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | provides :docker_installation, os: 'linux' 5 | property :repo, %w(stable test), default: 'stable', desired_state: false 6 | 7 | default_action :create 8 | 9 | action :create do 10 | raise 'Installation script not supported on AlmaLinux or Rocky Linux' if platform?('almalinux', 'rocky') 11 | 12 | package 'curl' do 13 | options '--allowerasing' 14 | not_if { platform_family?('rhel') && shell_out('rpm -q curl-minimal').exitstatus.zero? } 15 | end 16 | 17 | execute 'download docker installation script' do 18 | command 'curl -fsSL https://get.docker.com -o /opt/install-docker.sh' 19 | creates '/opt/install-docker.sh' 20 | end 21 | 22 | execute 'install docker' do 23 | command "sh /opt/install-docker.sh --channel #{new_resource.repo}" 24 | creates '/usr/bin/docker' 25 | end 26 | end 27 | 28 | action :delete do 29 | package %w(docker-ce docker-engine) do 30 | action :remove 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /resources/installation_tarball.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | resource_name :docker_installation_tarball 5 | provides :docker_installation_tarball 6 | 7 | property :checksum, String, default: lazy { default_checksum }, desired_state: false 8 | property :source, String, default: lazy { default_source }, desired_state: false 9 | property :channel, String, default: 'stable', desired_state: false 10 | property :version, String, default: '20.10.11', desired_state: false 11 | 12 | ################## 13 | # Property Helpers 14 | ################## 15 | 16 | def docker_kernel 17 | node['kernel']['name'] 18 | end 19 | 20 | def docker_arch 21 | node['kernel']['machine'] 22 | end 23 | 24 | def default_source 25 | "https://download.docker.com/#{docker_kernel.downcase}/static/#{channel}/#{docker_arch}/#{default_filename(version)}" 26 | end 27 | 28 | def default_filename(version) 29 | # https://download.docker.com/linux/static/stable/x86_64/ 30 | regex = /^(?\d*)\.(?\d*)\./ 31 | semver = regex.match(version) 32 | if semver['major'].to_i >= 19 33 | "docker-#{version}.tgz" 34 | elsif semver['major'].to_i == 18 && semver['minor'].to_i > 6 35 | "docker-#{version}.tgz" 36 | else 37 | "docker-#{version}-ce.tgz" 38 | end 39 | end 40 | 41 | def default_checksum 42 | case docker_kernel 43 | when 'Darwin' 44 | case version 45 | when '18.03.1' then 'bbfb9c599a4fdb45523496c2ead191056ff43d6be90cf0e348421dd56bc3dcf0' 46 | when '18.06.3' then 'f7347ef27db9a438b05b8f82cd4c017af5693fe26202d9b3babf750df3e05e0c' 47 | when '18.09.9' then 'ed83a3d51fef2bbcdb19d091ff0690a233aed4bbb47d2f7860d377196e0143a0' 48 | when '19.03.15' then '61672045675798b2075d4790665b74336c03b6d6084036ef22720af60614e50d' 49 | when '20.10.11' then '8f338ba618438fa186d1fa4eae32376cca58f86df2b40b5027c193202fad2acf' 50 | end 51 | when 'Linux' 52 | case version 53 | when '18.03.1' then '0e245c42de8a21799ab11179a4fce43b494ce173a8a2d6567ea6825d6c5265aa' 54 | when '18.06.3' then '346f9394393ee8db5f8bd1e229ee9d90e5b36931bdd754308b2ae68884dd6822' 55 | when '18.09.9' then '82a362af7689038c51573e0fd0554da8703f0d06f4dfe95dd5bda5acf0ae45fb' 56 | when '19.03.15' then '5504d190eef37355231325c176686d51ade6e0cabe2da526d561a38d8611506f' 57 | when '20.10.11' then 'dd6ff72df1edfd61ae55feaa4aadb88634161f0aa06dbaaf291d1be594099ff3' 58 | end 59 | end 60 | end 61 | 62 | ######### 63 | # Actions 64 | ######### 65 | 66 | action :create do 67 | package 'tar' 68 | 69 | # Pull a precompiled binary off the network 70 | remote_file docker_tarball do 71 | source new_resource.source 72 | checksum new_resource.checksum 73 | owner 'root' 74 | group 'root' 75 | mode '0755' 76 | action :create 77 | notifies :run, 'execute[extract tarball]', :immediately 78 | end 79 | 80 | execute 'extract tarball' do 81 | action :nothing 82 | command "tar -xzf #{docker_tarball} --strip-components=1 -C #{docker_bin_prefix}" 83 | end 84 | 85 | group 'docker' do 86 | system true 87 | append true 88 | end 89 | end 90 | 91 | action :delete do 92 | file docker_bin do 93 | action :delete 94 | end 95 | 96 | group 'docker' do 97 | action :delete 98 | end 99 | end 100 | 101 | ################ 102 | # Action Helpers 103 | ################ 104 | action_class do 105 | def docker_bin_prefix 106 | '/usr/bin' 107 | end 108 | 109 | def docker_bin 110 | "#{docker_bin_prefix}/docker" 111 | end 112 | 113 | def docker_tarball 114 | "#{Chef::Config[:file_cache_path]}/docker-#{new_resource.version}.tgz" 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /resources/partial/_base.rb: -------------------------------------------------------------------------------- 1 | require 'docker' 2 | require 'shellwords' 3 | 4 | ################ 5 | # Helper methods 6 | ################ 7 | 8 | def connection 9 | @connection ||= begin 10 | opts = {} 11 | opts[:read_timeout] = read_timeout if read_timeout 12 | opts[:write_timeout] = write_timeout if write_timeout 13 | 14 | if host =~ /^tcp:/ 15 | opts[:scheme] = 'https' if tls || !tls_verify.nil? 16 | opts[:ssl_ca_file] = tls_ca_cert if tls_ca_cert 17 | opts[:client_cert] = tls_client_cert if tls_client_cert 18 | opts[:client_key] = tls_client_key if tls_client_key 19 | end 20 | Docker::Connection.new(host || Docker.url, opts) 21 | end 22 | end 23 | 24 | def with_retries(&_block) 25 | tries = api_retries 26 | begin 27 | yield 28 | # Only catch errors that can be fixed with retries. 29 | rescue Docker::Error::ServerError, # 500 30 | Docker::Error::UnexpectedResponseError, # 400 31 | Docker::Error::TimeoutError, 32 | Docker::Error::IOError 33 | tries -= 1 34 | retry if tries > 0 35 | raise 36 | end 37 | end 38 | 39 | def call_action(_action) 40 | new_resource.run_action 41 | end 42 | 43 | ######### 44 | # Classes 45 | ######### 46 | 47 | class UnorderedArray < Array 48 | def ==(other) 49 | # If I (desired env) am a subset of the current env, let == return true 50 | other.is_a?(Array) && all? { |val| other.include?(val) } 51 | end 52 | end 53 | 54 | class PartialHash < Hash 55 | def ==(other) 56 | other.is_a?(Hash) && all? { |key, val| other.key?(key) && other[key] == val } 57 | end 58 | end 59 | 60 | ################ 61 | # Type Constants 62 | # 63 | # These will be used when declaring resource property types in the 64 | # docker_service, docker_container, and docker_image resource. 65 | # 66 | ################ 67 | 68 | UnorderedArrayType = property_type( 69 | is: [UnorderedArray, nil], 70 | coerce: proc { |v| v.nil? ? nil : UnorderedArray.new(Array(v)) } 71 | ) unless defined?(UnorderedArrayType) 72 | 73 | PartialHashType = property_type( 74 | is: [PartialHash, nil], 75 | coerce: proc { |v| v.nil? ? nil : PartialHash[v] } 76 | ) unless defined?(PartialHashType) 77 | 78 | property :api_retries, Integer, 79 | default: 3, 80 | desired_state: false 81 | 82 | property :read_timeout, Integer, 83 | default: 60, 84 | desired_state: false 85 | 86 | property :write_timeout, Integer, 87 | desired_state: false 88 | 89 | property :running_wait_time, Integer, 90 | default: 20, 91 | desired_state: false 92 | 93 | property :tls, [TrueClass, FalseClass, nil], 94 | default: lazy { ENV['DOCKER_TLS'] }, 95 | desired_state: false 96 | 97 | property :tls_verify, [TrueClass, FalseClass, nil], 98 | default: lazy { ENV['DOCKER_TLS_VERIFY'] }, 99 | desired_state: false 100 | 101 | property :tls_ca_cert, [String, nil], 102 | default: lazy { ENV['DOCKER_CERT_PATH'] ? "#{ENV['DOCKER_CERT_PATH']}/ca.pem" : nil }, 103 | desired_state: false 104 | 105 | property :tls_server_cert, String, 106 | desired_state: false 107 | 108 | property :tls_server_key, String, 109 | desired_state: false 110 | 111 | property :tls_client_cert, [String, nil], 112 | default: lazy { ENV['DOCKER_CERT_PATH'] ? "#{ENV['DOCKER_CERT_PATH']}/cert.pem" : nil }, 113 | desired_state: false 114 | 115 | property :tls_client_key, [String, nil], 116 | default: lazy { ENV['DOCKER_CERT_PATH'] ? "#{ENV['DOCKER_CERT_PATH']}/key.pem" : nil }, 117 | desired_state: false 118 | 119 | alias_method :tlscacert, :tls_ca_cert 120 | alias_method :tlscert, :tls_server_cert 121 | alias_method :tlskey, :tls_server_key 122 | alias_method :tlsverify, :tls_verify 123 | 124 | action_class do 125 | def parse_registry_host(val) 126 | # example values for val, can be prefixed with http(s):// : 127 | # image (=> Docker Hub) 128 | # organization/image (=> Docker Hub) 129 | # domain.ext/image (=> 3rd party registry) 130 | # domain.ext/.../image (=> 3rd party registry) 131 | # 132 | first_part = val.sub(%r{https?://}, '').split('/').first 133 | 134 | # looks like a host name of a custom docker registry 135 | return first_part if first_part.include?('.') 136 | 137 | # default host 138 | 'index.docker.io' 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /resources/partial/_logging.rb: -------------------------------------------------------------------------------- 1 | property :log_driver, 2 | equal_to: %w( json-file syslog journald gelf fluentd awslogs splunk etwlogs gcplogs logentries loki-docker none local ), 3 | default: 'json-file', 4 | desired_state: false 5 | 6 | property :log_opts, 7 | [Hash, nil], 8 | coerce: proc { |v| coerce_log_opts(v) }, 9 | desired_state: false 10 | 11 | def coerce_log_opts(v) 12 | case v 13 | when Hash, nil 14 | v 15 | else 16 | Array(v).each_with_object({}) do |log_opt, memo| 17 | key, value = log_opt.split('=', 2) 18 | memo[key] = value 19 | end 20 | end 21 | end 22 | 23 | # log_driver and log_opts really handle this 24 | def log_config(value = Chef::NOT_PASSED) 25 | if value != Chef::NOT_PASSED 26 | @log_config = value 27 | log_driver value['Type'] 28 | log_opts value['Config'] 29 | end 30 | return @log_config if defined?(@log_config) 31 | def_logcfg = {} 32 | def_logcfg['Type'] = log_driver if property_is_set?(:log_driver) 33 | def_logcfg['Config'] = log_opts if property_is_set?(:log_opts) 34 | def_logcfg = nil if def_logcfg.empty? 35 | def_logcfg 36 | end 37 | -------------------------------------------------------------------------------- /resources/partial/_service_base.rb: -------------------------------------------------------------------------------- 1 | ################ 2 | # Helper Methods 3 | ################ 4 | include DockerCookbook::DockerHelpers::Service 5 | 6 | ##################### 7 | # resource properties 8 | ##################### 9 | 10 | # Environment variables to docker service 11 | property :env_vars, Hash 12 | 13 | # daemon management 14 | property :instance, String, name_property: true, desired_state: false 15 | property :auto_restart, [true, false], default: false 16 | property :api_cors_header, String 17 | property :bridge, String 18 | property :bip, [IPV4_ADDR, IPV4_CIDR, IPV6_ADDR, IPV6_CIDR, nil] 19 | property :cluster_store, String 20 | property :cluster_advertise, String 21 | property :cluster_store_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 22 | property :daemon, [true, false], default: true 23 | property :data_root, String 24 | property :debug, [true, false], default: false 25 | property :dns, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 26 | property :dns_search, Array 27 | property :exec_driver, ['native', 'lxc', nil] 28 | property :exec_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 29 | property :fixed_cidr, String 30 | property :fixed_cidr_v6, String 31 | property :group, String, default: 'docker' 32 | property :host, [String, Array], coerce: proc { |v| coerce_host(v) }, desired_state: false 33 | property :icc, [true, false] 34 | property :insecure_registry, [Array, String, nil], coerce: proc { |v| coerce_insecure_registry(v) } 35 | property :ip, [IPV4_ADDR, IPV6_ADDR, nil] 36 | property :ip_forward, [true, false] 37 | property :ipv4_forward, [true, false], default: true 38 | property :ipv6_forward, [true, false], default: true 39 | property :ip_masq, [true, false] 40 | property :iptables, [true, false] 41 | property :ip6tables, [true, false] 42 | property :ipv6, [true, false] 43 | property :default_ip_address_pool, String 44 | property :log_level, %w(debug info warn error fatal) 45 | property :labels, [String, Array], coerce: proc { |v| coerce_daemon_labels(v) }, desired_state: false 46 | property :log_driver, %w(json-file syslog journald gelf fluentd awslogs splunk etwlogs gcplogs logentries loki-docker none local) 47 | property :log_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 48 | property :mount_flags, String 49 | property :mtu, String 50 | property :pidfile, String, default: lazy { "/var/run/#{docker_name}.pid" } 51 | property :registry_mirror, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 52 | property :storage_driver, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 53 | property :selinux_enabled, [true, false] 54 | property :storage_opts, Array 55 | property :default_ulimit, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 56 | property :userland_proxy, [true, false] 57 | property :disable_legacy_registry, [true, false] 58 | property :userns_remap, String 59 | property :live_restore, [true, false], default: false 60 | 61 | # These are options specific to systemd configuration such as 62 | # LimitNOFILE or TasksMax that you may wannt to use to customize 63 | # the environment in which Docker runs. 64 | property :systemd_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 65 | property :systemd_socket_opts, [String, Array], coerce: proc { |v| v.nil? ? nil : Array(v) } 66 | 67 | # These are unvalidated daemon arguments passed in as a string. 68 | property :misc_opts, String 69 | 70 | # environment variables to set before running daemon 71 | property :http_proxy, String 72 | property :https_proxy, String 73 | property :no_proxy, String 74 | property :tmpdir, String 75 | 76 | # logging 77 | property :logfile, String, default: '/var/log/docker.log' 78 | 79 | # docker-wait-ready timeout 80 | property :service_timeout, Integer, default: 20 81 | 82 | alias_method :label, :labels 83 | alias_method :run_group, :group 84 | alias_method :graph, :data_root 85 | 86 | action_class do 87 | def libexec_dir 88 | return '/usr/libexec/docker' if platform_family?('rhel') 89 | '/usr/lib/docker' 90 | end 91 | 92 | def create_docker_wait_ready 93 | directory libexec_dir do 94 | owner 'root' 95 | group 'root' 96 | mode '0755' 97 | action :create 98 | end 99 | 100 | template "#{libexec_dir}/#{docker_name}-wait-ready" do 101 | source 'default/docker-wait-ready.erb' 102 | owner 'root' 103 | group 'root' 104 | mode '0755' 105 | variables( 106 | docker_cmd: docker_cmd, 107 | libexec_dir: libexec_dir, 108 | service_timeout: new_resource.service_timeout 109 | ) 110 | cookbook 'docker' 111 | action :create 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /resources/plugin.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | property :local_alias, String, name_property: true 5 | property :remote_tag, String, default: 'latest' 6 | property :remote, String 7 | property :grant_privileges, [Array, true] 8 | property :options, Hash 9 | 10 | action :install do 11 | return if plugin_exists?(local_name) 12 | converge_by "Install plugin #{plugin_identifier} as #{local_name}" do 13 | install_plugin 14 | configure_plugin 15 | end 16 | end 17 | 18 | action :enable do 19 | converge_by "Enable plugin #{local_name}" do 20 | enable_plugin 21 | end unless plugin_enabled?(local_name) 22 | end 23 | 24 | action :disable do 25 | converge_by "Disable plugin #{local_name}" do 26 | disable_plugin 27 | end if plugin_enabled?(local_name) 28 | end 29 | 30 | action :update do 31 | converge_by "Configure plugin #{local_name}" do 32 | configure_plugin 33 | end 34 | end 35 | 36 | action :remove do 37 | converge_by "Remove plugin #{local_name}" do 38 | remove_plugin 39 | end 40 | end 41 | 42 | action_class do 43 | def remote_name 44 | return new_resource.remote unless new_resource.remote.nil? || new_resource.remote.empty? 45 | new_resource.local_alias 46 | end 47 | 48 | def plugin_identifier 49 | "#{remote_name}:#{new_resource.remote_tag}" 50 | end 51 | 52 | def local_name 53 | new_resource.local_alias 54 | end 55 | 56 | def plugin_exists?(name) 57 | Docker.connection.get("/plugins/#{name}/json") 58 | true 59 | rescue Docker::Error::NotFoundError 60 | false 61 | end 62 | 63 | def plugin_enabled?(name) 64 | JSON.parse(Docker.connection.get("/plugins/#{name}/json"))['Enabled'] 65 | end 66 | 67 | def install_plugin 68 | privileges = \ 69 | if new_resource.grant_privileges == true 70 | # user gave a blanket statement about privileges; fetch required privileges from Docker 71 | # we pass the identifier as both :name and :remote to accomodate different API versions 72 | JSON.parse Docker.connection.get('/plugins/privileges', 73 | name: plugin_identifier, 74 | remote: plugin_identifier) 75 | else 76 | # user gave a specific list of privileges 77 | new_resource.grant_privileges 78 | end 79 | 80 | # actually do the plugin install 81 | body = '' 82 | 83 | opts = { remote: plugin_identifier, name: local_name } 84 | Chef::Log.info("pulling plugin #{opts} with privileges #{privileges}") 85 | Docker.connection.post('/plugins/pull', opts, 86 | body: JSON.generate(privileges), 87 | response_block: response_block(body)) 88 | 89 | last_line = body.split("\n").select { |item| !item.empty? }.last 90 | info = JSON.parse last_line 91 | raise info['error'] if info.key?('error') 92 | end 93 | 94 | def response_block(body) 95 | lambda do |chunk, _remaining, _total| 96 | body << chunk 97 | end 98 | end 99 | 100 | def configure_plugin 101 | options_for_json = [] 102 | new_resource.options.each_pair do |k, v| 103 | options_for_json.push("#{k}=#{v}") 104 | end 105 | 106 | Docker.connection.post("/plugins/#{local_name}/set", {}, body: JSON.generate(options_for_json)) 107 | end 108 | 109 | def enable_plugin 110 | Docker.connection.post("/plugins/#{local_name}/enable", timeout: new_resource.read_timeout) 111 | end 112 | 113 | def disable_plugin 114 | Docker.connection.post("/plugins/#{local_name}/disable", timeout: new_resource.read_timeout) 115 | end 116 | 117 | def remove_plugin 118 | Docker.connection.delete("/plugins/#{local_name}") 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /resources/registry.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | property :email, String 5 | 6 | property :password, String, 7 | sensitive: true 8 | 9 | property :serveraddress, String, 10 | name_property: true 11 | 12 | property :username, String 13 | 14 | property :host, 15 | [String, nil], 16 | default: lazy { ENV['DOCKER_HOST'] }, 17 | desired_state: false 18 | 19 | action :login do 20 | tries = new_resource.api_retries 21 | 22 | registry_host = parse_registry_host(new_resource.serveraddress) 23 | 24 | (node.run_state['docker_auth'] ||= {})[registry_host] = { 25 | 'serveraddress' => registry_host, 26 | 'username' => new_resource.username, 27 | 'password' => new_resource.password, 28 | 'email' => new_resource.email, 29 | } 30 | 31 | begin 32 | Docker.connection.post( 33 | '/auth', {}, 34 | body: node.run_state['docker_auth'][registry_host].to_json 35 | ) 36 | rescue Docker::Error::ServerError, Docker::Error::UnauthorizedError 37 | raise Docker::Error::AuthenticationError, "#{new_resource.username} failed to authenticate with #{new_resource.serveraddress}" if (tries -= 1) == 0 38 | retry 39 | end 40 | 41 | true 42 | end 43 | -------------------------------------------------------------------------------- /resources/service.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | use 'partial/_service_base' 4 | 5 | resource_name :docker_service 6 | 7 | # register with the resource resolution system 8 | provides :docker_service 9 | 10 | # installation type and service_manager 11 | property :install_method, %w(script package tarball none auto), default: lazy { docker_install_method }, desired_state: false 12 | property :service_manager, %w(execute systemd none auto), default: 'auto', desired_state: false 13 | 14 | # docker_installation_script 15 | property :repo, String, desired_state: false 16 | property :script_url, String, desired_state: false 17 | 18 | # docker_installation_tarball 19 | property :checksum, String, desired_state: false 20 | property :docker_bin, String, desired_state: false 21 | property :source, String, desired_state: false 22 | 23 | # docker_installation_package 24 | property :package_version, String, desired_state: false 25 | property :package_name, String, desired_state: false 26 | property :setup_docker_repo, [true, false], desired_state: false 27 | 28 | # package and tarball 29 | property :version, String, desired_state: false 30 | property :package_options, String, desired_state: false 31 | 32 | action_class do 33 | def validate_install_method 34 | if new_resource.property_is_set?(:version) && 35 | new_resource.install_method != 'package' && 36 | new_resource.install_method != 'tarball' 37 | raise Chef::Exceptions::ValidationFailed, 'Version property only supported for package and tarball installation methods' 38 | end 39 | end 40 | 41 | def property_intersection(src, dest) 42 | src.class.properties.keys.intersection(dest.class.properties.keys) 43 | end 44 | 45 | def installation(&block) 46 | b = proc { 47 | copy_properties_from(new_resource, *property_intersection(new_resource, self), exclude: [:install_method]) 48 | instance_exec(&block) 49 | } 50 | 51 | case new_resource.install_method 52 | when 'auto' 53 | install = docker_installation(new_resource.name, &b) 54 | when 'script' 55 | install = docker_installation_script(new_resource.name, &b) 56 | when 'package' 57 | install = docker_installation_package(new_resource.name, &b) 58 | when 'tarball' 59 | install = docker_installation_tarball(new_resource.name, &b) 60 | when 'none' 61 | Chef::Log.info('Skipping Docker installation. Assuming it was handled previously.') 62 | return 63 | end 64 | install 65 | end 66 | 67 | def svc_manager(&block) 68 | b = proc { 69 | copy_properties_from(new_resource, *property_intersection(new_resource, self), 70 | exclude: [:service_manager, :install_method]) 71 | instance_exec(&block) 72 | } 73 | 74 | case new_resource.service_manager 75 | when 'auto' 76 | svc = docker_service_manager(new_resource.name, &b) 77 | when 'execute' 78 | svc = docker_service_manager_execute(new_resource.name, &b) 79 | when 'systemd' 80 | svc = docker_service_manager_systemd(new_resource.name, &b) 81 | when 'none' 82 | Chef::Log.info('Skipping Docker Server Manager. Assuming it was handled previously.') 83 | return 84 | end 85 | svc 86 | end 87 | end 88 | 89 | ######### 90 | # Actions 91 | ######### 92 | 93 | action :create do 94 | validate_install_method 95 | 96 | installation do 97 | action :create 98 | notifies :restart, new_resource, :immediately 99 | end 100 | end 101 | 102 | action :delete do 103 | installation do 104 | action :delete 105 | end 106 | end 107 | 108 | action :start do 109 | svc_manager do 110 | action :start 111 | end 112 | end 113 | 114 | action :stop do 115 | svc_manager do 116 | action :stop 117 | end 118 | end 119 | 120 | action :restart do 121 | svc_manager do 122 | action :restart 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /resources/service_base.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | use 'partial/_service_base' 4 | 5 | resource_name :docker_service_base 6 | provides :docker_service_base 7 | provides :docker_service_manager 8 | -------------------------------------------------------------------------------- /resources/service_manager_execute.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | use 'partial/_service_base' 4 | 5 | resource_name :docker_service_manager_execute 6 | provides :docker_service_manager_execute 7 | 8 | # Start the service 9 | action :start do 10 | # enable ipv4 forwarding 11 | execute 'enable net.ipv4.conf.all.forwarding' do 12 | command '/sbin/sysctl net.ipv4.conf.all.forwarding=1' 13 | not_if '/sbin/sysctl -q -n net.ipv4.conf.all.forwarding | grep ^1$' 14 | action :run 15 | end 16 | 17 | # enable ipv6 forwarding 18 | execute 'enable net.ipv6.conf.all.forwarding' do 19 | command '/sbin/sysctl net.ipv6.conf.all.forwarding=1' 20 | not_if '/sbin/sysctl -q -n net.ipv6.conf.all.forwarding | grep ^1$' 21 | action :run 22 | end 23 | 24 | # Go doesn't support detaching processes natively, so we have 25 | # to manually fork it from the shell with & 26 | # https://github.com/docker/docker/issues/2758 27 | bash "start docker #{name}" do 28 | code "#{docker_daemon_cmd} >> #{logfile} 2>&1 &" 29 | environment 'HTTP_PROXY' => http_proxy, 30 | 'HTTPS_PROXY' => https_proxy, 31 | 'NO_PROXY' => no_proxy, 32 | 'TMPDIR' => tmpdir 33 | not_if "ps -ef | grep -v grep | grep #{Shellwords.escape(docker_daemon_cmd)}" 34 | action :run 35 | end 36 | 37 | create_docker_wait_ready 38 | 39 | execute 'docker-wait-ready' do 40 | command "#{libexec_dir}/#{docker_name}-wait-ready" 41 | end 42 | end 43 | 44 | action :stop do 45 | execute "stop docker #{name}" do 46 | command "kill `cat #{pidfile}` && while [ -e #{pidfile} ]; do sleep 1; done" 47 | timeout 10 48 | only_if "#{docker_cmd} ps | head -n 1 | grep ^CONTAINER" 49 | end 50 | end 51 | 52 | action :restart do 53 | action_stop 54 | action_start 55 | end 56 | -------------------------------------------------------------------------------- /resources/service_manager_systemd.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | use 'partial/_service_base' 4 | 5 | resource_name :docker_service_manager_systemd 6 | provides :docker_service_manager_systemd 7 | 8 | provides :docker_service_manager, os: 'linux' do |_node| 9 | Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd) 10 | end 11 | 12 | action :start do 13 | create_docker_wait_ready 14 | 15 | # stock systemd socket file 16 | template "/lib/systemd/system/#{docker_name}.socket" do 17 | source 'systemd/docker.socket.erb' 18 | cookbook 'docker' 19 | owner 'root' 20 | group 'root' 21 | mode '0644' 22 | variables( 23 | config: new_resource, 24 | docker_name: docker_name, 25 | docker_socket: connect_socket 26 | ) 27 | action connect_socket.nil? ? :delete : :create 28 | not_if { docker_name == 'docker' && ::File.exist?('/lib/systemd/system/docker.socket') } 29 | end 30 | 31 | directory '/etc/containerd' 32 | 33 | file '/etc/containerd/config.toml' do 34 | content 'disabled_plugins = ["cri"]' 35 | action :create_if_missing 36 | end 37 | 38 | template '/etc/systemd/system/containerd.service' do 39 | source 'systemd/containerd.service.erb' 40 | cookbook 'docker' 41 | owner 'root' 42 | group 'root' 43 | mode '0644' 44 | variables(service_type: docker_containerd_service_type) 45 | not_if { ::File.exist?('/lib/systemd/system/containerd.service') } 46 | only_if { docker_containerd } 47 | notifies :run, 'execute[systemctl daemon-reload]', :immediately 48 | end 49 | 50 | # stock systemd unit file 51 | # See - https://github.com/docker/docker-ce-packaging/blob/master/systemd/docker.service 52 | template "/lib/systemd/system/#{docker_name}.service" do 53 | source 'systemd/docker.service.erb' 54 | cookbook 'docker' 55 | owner 'root' 56 | group 'root' 57 | mode '0644' 58 | variables( 59 | docker_name: docker_name, 60 | docker_daemon_cmd: docker_daemon_cmd, 61 | docker_socket: connect_socket, 62 | containerd: docker_containerd 63 | ) 64 | not_if { docker_name == 'docker' && ::File.exist?('/lib/systemd/system/docker.service') } 65 | end 66 | 67 | # this overrides the main systemd socket 68 | template "/etc/systemd/system/#{docker_name}.socket" do 69 | source 'systemd/docker.socket-override.erb' 70 | cookbook 'docker' 71 | owner 'root' 72 | group 'root' 73 | mode '0644' 74 | variables( 75 | config: new_resource, 76 | docker_name: docker_name, 77 | docker_socket: connect_socket, 78 | systemd_socket_args: systemd_socket_args 79 | ) 80 | action connect_socket.nil? ? :delete : :create 81 | end 82 | 83 | # this overrides the main systemd service 84 | template "/etc/systemd/system/#{docker_name}.service" do 85 | source 'systemd/docker.service-override.erb' 86 | cookbook 'docker' 87 | owner 'root' 88 | group 'root' 89 | mode '0644' 90 | variables( 91 | config: new_resource, 92 | docker_name: docker_name, 93 | docker_socket: connect_socket, 94 | docker_daemon_cmd: docker_daemon_cmd, 95 | systemd_args: systemd_args, 96 | docker_wait_ready: "#{libexec_dir}/#{docker_name}-wait-ready", 97 | env_vars: new_resource.env_vars, 98 | containerd: docker_containerd 99 | ) 100 | notifies :run, 'execute[systemctl daemon-reload]', :immediately 101 | notifies :run, "execute[systemctl try-restart #{docker_name}]", :immediately 102 | end 103 | 104 | # avoid 'Unit file changed on disk' warning 105 | execute 'systemctl daemon-reload' do 106 | command '/bin/systemctl daemon-reload' 107 | action :nothing 108 | end 109 | 110 | # restart if changes in template resources 111 | execute "systemctl try-restart #{docker_name}" do 112 | command "/bin/systemctl try-restart #{docker_name}" 113 | action :nothing 114 | end 115 | 116 | # service management resource 117 | service docker_name do 118 | provider Chef::Provider::Service::Systemd 119 | supports status: true 120 | action [:enable, :start] 121 | only_if { ::File.exist?("/lib/systemd/system/#{docker_name}.service") } 122 | retries 1 123 | end 124 | end 125 | 126 | action :stop do 127 | # service management resource 128 | service docker_name do 129 | provider Chef::Provider::Service::Systemd 130 | supports status: true 131 | action [:disable, :stop] 132 | only_if { ::File.exist?("/lib/systemd/system/#{docker_name}.service") } 133 | end 134 | 135 | systemd_unit "#{docker_name}.socket" do 136 | action [:disable, :stop] 137 | end 138 | end 139 | 140 | action :restart do 141 | action_stop 142 | action_start 143 | end 144 | -------------------------------------------------------------------------------- /resources/swarm_init.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | 3 | include DockerCookbook::DockerHelpers::Swarm 4 | 5 | resource_name :docker_swarm_init 6 | provides :docker_swarm_init 7 | 8 | property :advertise_addr, String 9 | property :listen_addr, String 10 | property :force_new_cluster, [true, false], default: false 11 | property :autolock, [true, false], default: false 12 | 13 | action :init do 14 | return if swarm_member? 15 | 16 | converge_by 'initializing docker swarm' do 17 | cmd = Mixlib::ShellOut.new(swarm_init_cmd(new_resource).join(' ')) 18 | cmd.run_command 19 | if cmd.error? 20 | raise "Failed to initialize swarm: #{cmd.stderr}" 21 | end 22 | end 23 | end 24 | 25 | action :leave do 26 | return unless swarm_member? 27 | 28 | converge_by 'leaving docker swarm' do 29 | cmd = Mixlib::ShellOut.new('docker swarm leave --force') 30 | cmd.run_command 31 | if cmd.error? 32 | raise "Failed to leave swarm: #{cmd.stderr}" 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /resources/swarm_join.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | 3 | include DockerCookbook::DockerHelpers::Swarm 4 | 5 | resource_name :docker_swarm_join 6 | provides :docker_swarm_join 7 | 8 | property :token, String, required: true 9 | property :manager_ip, String, required: true 10 | property :advertise_addr, String 11 | property :listen_addr, String 12 | property :data_path_addr, String 13 | 14 | action :join do 15 | return if swarm_member? 16 | 17 | converge_by 'joining docker swarm' do 18 | cmd = Mixlib::ShellOut.new(swarm_join_cmd.join(' ')) 19 | cmd.run_command 20 | if cmd.error? 21 | raise "Failed to join swarm: #{cmd.stderr}" 22 | end 23 | end 24 | end 25 | 26 | action :leave do 27 | return unless swarm_member? 28 | 29 | converge_by 'leaving docker swarm' do 30 | cmd = Mixlib::ShellOut.new('docker swarm leave --force') 31 | cmd.run_command 32 | if cmd.error? 33 | raise "Failed to leave swarm: #{cmd.stderr}" 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /resources/swarm_service.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | 3 | include DockerCookbook::DockerHelpers::Swarm 4 | 5 | resource_name :docker_swarm_service 6 | provides :docker_swarm_service 7 | 8 | property :service_name, String, name_property: true 9 | property :image, String, required: true 10 | property :command, [String, Array] 11 | property :replicas, Integer, default: 1 12 | property :env, [Array], default: [] 13 | property :labels, [Hash], default: {} 14 | property :mounts, [Array], default: [] 15 | property :networks, [Array], default: [] 16 | property :ports, [Array], default: [] 17 | property :constraints, [Array], default: [] 18 | property :secrets, [Array], default: [] 19 | property :configs, [Array], default: [] 20 | property :restart_policy, Hash, default: { condition: 'any' } 21 | 22 | # Health check 23 | property :healthcheck_cmd, String 24 | property :healthcheck_interval, String 25 | property :healthcheck_timeout, String 26 | property :healthcheck_retries, Integer 27 | 28 | load_current_value do |new_resource| 29 | cmd = Mixlib::ShellOut.new("docker service inspect #{new_resource.service_name}") 30 | cmd.run_command 31 | if cmd.error? 32 | current_value_does_not_exist! 33 | else 34 | service_info = JSON.parse(cmd.stdout).first 35 | image service_info['Spec']['TaskTemplate']['ContainerSpec']['Image'] 36 | command service_info['Spec']['TaskTemplate']['ContainerSpec']['Command'] 37 | env service_info['Spec']['TaskTemplate']['ContainerSpec']['Env'] 38 | replicas service_info['Spec']['Mode']['Replicated']['Replicas'] 39 | end 40 | end 41 | 42 | action :create do 43 | return unless swarm_manager? 44 | 45 | converge_if_changed do 46 | cmd = create_service_cmd(new_resource) 47 | 48 | converge_by "creating service #{new_resource.service_name}" do 49 | shell_out!(cmd.join(' ')) 50 | end 51 | end 52 | end 53 | 54 | action :update do 55 | return unless swarm_manager? 56 | return unless service_exists?(new_resource) 57 | 58 | converge_if_changed do 59 | cmd = update_service_cmd(new_resource) 60 | 61 | converge_by "updating service #{new_resource.service_name}" do 62 | shell_out!(cmd.join(' ')) 63 | end 64 | end 65 | end 66 | 67 | action :delete do 68 | return unless swarm_manager? 69 | return unless service_exists?(new_resource) 70 | 71 | converge_by "deleting service #{new_resource.service_name}" do 72 | shell_out!("docker service rm #{new_resource.service_name}") 73 | end 74 | end 75 | 76 | action_class do 77 | def create_service_cmd(new_resource) 78 | cmd = %w(docker service create) 79 | cmd << "--name #{new_resource.service_name}" 80 | cmd << "--replicas #{new_resource.replicas}" 81 | 82 | new_resource.env.each { |e| cmd << "--env #{e}" } 83 | new_resource.labels.each { |k, v| cmd << "--label #{k}=#{v}" } 84 | new_resource.mounts.each { |m| cmd << "--mount #{m}" } 85 | new_resource.networks.each { |n| cmd << "--network #{n}" } 86 | new_resource.ports.each { |p| cmd << "--publish #{p}" } 87 | new_resource.constraints.each { |c| cmd << "--constraint #{c}" } 88 | 89 | if new_resource.restart_policy 90 | cmd << "--restart-condition #{new_resource.restart_policy[:condition]}" 91 | cmd << "--restart-delay #{new_resource.restart_policy[:delay]}" if new_resource.restart_policy[:delay] 92 | cmd << "--restart-max-attempts #{new_resource.restart_policy[:max_attempts]}" if new_resource.restart_policy[:max_attempts] 93 | cmd << "--restart-window #{new_resource.restart_policy[:window]}" if new_resource.restart_policy[:window] 94 | end 95 | 96 | if new_resource.healthcheck_cmd 97 | cmd << "--health-cmd #{new_resource.healthcheck_cmd}" 98 | cmd << "--health-interval #{new_resource.healthcheck_interval}" if new_resource.healthcheck_interval 99 | cmd << "--health-timeout #{new_resource.healthcheck_timeout}" if new_resource.healthcheck_timeout 100 | cmd << "--health-retries #{new_resource.healthcheck_retries}" if new_resource.healthcheck_retries 101 | end 102 | 103 | cmd << new_resource.image 104 | cmd << new_resource.command if new_resource.command 105 | cmd 106 | end 107 | 108 | def update_service_cmd(new_resource) 109 | cmd = %w(docker service update) 110 | cmd << "--image #{new_resource.image}" 111 | cmd << "--replicas #{new_resource.replicas}" 112 | cmd << new_resource.service_name 113 | cmd 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /resources/swarm_token.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | 3 | include DockerCookbook::DockerHelpers::Swarm 4 | 5 | resource_name :docker_swarm_token 6 | provides :docker_swarm_token 7 | 8 | property :token_type, String, name_property: true, equal_to: %w(worker manager) 9 | property :rotate, [true, false], default: false 10 | 11 | load_current_value do |new_resource| 12 | if swarm_manager? 13 | cmd = Mixlib::ShellOut.new("docker swarm join-token -q #{new_resource.token_type}") 14 | cmd.run_command 15 | current_value_does_not_exist! if cmd.error? 16 | else 17 | current_value_does_not_exist! 18 | end 19 | end 20 | 21 | action :read do 22 | if swarm_manager? 23 | cmd = Mixlib::ShellOut.new(swarm_token_cmd(new_resource.token_type).join(' ')) 24 | cmd.run_command 25 | raise "Error getting #{new_resource.token_type} token: #{cmd.stderr}" if cmd.error? 26 | 27 | node.run_state['docker_swarm'] ||= {} 28 | node.run_state['docker_swarm']["#{new_resource.token_type}_token"] = cmd.stdout.strip 29 | end 30 | end 31 | 32 | action :rotate do 33 | return unless swarm_manager? 34 | 35 | converge_by "rotating #{new_resource.token_type} token" do 36 | cmd = Mixlib::ShellOut.new("docker swarm join-token --rotate -q #{new_resource.token_type}") 37 | cmd.run_command 38 | raise "Error rotating #{new_resource.token_type} token: #{cmd.stderr}" if cmd.error? 39 | 40 | node.run_state['docker_swarm'] ||= {} 41 | node.run_state['docker_swarm']["#{new_resource.token_type}_token"] = cmd.stdout.strip 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /resources/tag.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | property :target_repo, String, name_property: true 5 | property :target_tag, String 6 | property :to_repo, String 7 | property :to_tag, String 8 | property :force, [true, false], default: false, desired_state: false 9 | 10 | action :tag do 11 | return if new_resource.force == false && Docker::Image.exist?("#{new_resource.to_repo}:#{new_resource.to_tag}") 12 | begin 13 | converge_by "update #{new_resource.target_repo}:#{new_resource.target_tag} to #{new_resource.to_repo}:#{new_resource.to_tag}" do 14 | i = Docker::Image.get("#{new_resource.target_repo}:#{new_resource.target_tag}") 15 | i.tag('repo' => new_resource.to_repo, 'tag' => new_resource.to_tag, 'force' => new_resource.force) 16 | end 17 | rescue Docker::Error => e 18 | raise e.message 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /resources/volume.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | property :driver, String, desired_state: false 5 | property :host, [String, nil], default: lazy { ENV['DOCKER_HOST'] }, desired_state: false 6 | property :opts, Hash, desired_state: false 7 | property :volume, Docker::Volume, desired_state: false 8 | property :volume_name, String, name_property: true 9 | 10 | load_current_value do 11 | begin 12 | with_retries { volume Docker::Volume.get(volume_name, connection) } 13 | rescue Docker::Error::NotFoundError 14 | current_value_does_not_exist! 15 | end 16 | end 17 | 18 | action :create do 19 | converge_by "creating volume #{new_resource.volume_name}" do 20 | opts = {} 21 | opts['Driver'] = new_resource.driver if property_is_set?(:driver) 22 | opts['DriverOpts'] = new_resource.opts if property_is_set?(:opts) 23 | Docker::Volume.create(new_resource.volume_name, opts, connection) 24 | end if current_resource.nil? 25 | end 26 | 27 | action :remove do 28 | converge_by "removing volume #{new_resource.volume_name}" do 29 | current_resource.volume.remove 30 | end unless current_resource.nil? 31 | end 32 | -------------------------------------------------------------------------------- /resources/volume_prune.rb: -------------------------------------------------------------------------------- 1 | unified_mode true 2 | use 'partial/_base' 3 | 4 | property :read_timeout, Integer, default: 120, desired_state: false 5 | property :host, [String, nil], default: lazy { ENV['DOCKER_HOST'] }, desired_state: false 6 | 7 | # https://docs.docker.com/engine/api/v1.42/#tag/Volume/operation/VolumePrune 8 | property :all, [true, false], default: false 9 | # https://docs.docker.com/engine/reference/builder/#label 10 | property :with_label, String 11 | property :without_label, String 12 | 13 | action :prune do 14 | # Have to call this method ourselves due to 15 | # https://github.com/swipely/docker-api/pull/507 16 | json = prune_generate_json(with_label: new_resource.with_label, without_label: new_resource.without_label, all: new_resource.all) 17 | 18 | res = connection.post('/volumes/prune', json) 19 | Chef::Log.info res 20 | end 21 | 22 | action_class do 23 | include DockerCookbook::DockerHelpers::Json 24 | end 25 | -------------------------------------------------------------------------------- /spec/docker_test/exec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_test::exec' do 4 | cached(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04').converge(described_recipe) } 5 | 6 | it 'pull_if_missing docker_image[busybox]' do 7 | expect(chef_run).to pull_if_missing_docker_image('busybox') 8 | end 9 | 10 | it 'run docker_container[busybox_exec]' do 11 | expect(chef_run).to run_docker_container('busybox_exec').with( 12 | repo: 'busybox', 13 | command: ['sh', '-c', 'trap exit 0 SIGTERM; while :; do sleep 1; done'] 14 | ) 15 | end 16 | 17 | # TODO(ramereth): Disabling due to https://github.com/sous-chefs/docker/issues/1137 18 | # context 'testing default properties' do 19 | # it 'docker_exec[default]' do 20 | # expect(chef_run).to run_docker_exec('default').with( 21 | # host: nil, 22 | # command: nil, 23 | # container: nil, 24 | # timeout: 60, 25 | # container_obj: nil, 26 | # returns: [0] 27 | # ) 28 | # end 29 | # end 30 | 31 | context 'testing run action' do 32 | it 'run docker_exec[touch_it]' do 33 | expect(chef_run).to run_docker_exec('touch_it').with( 34 | container: 'busybox_exec', 35 | command: ['touch', '/tmp/onefile'], 36 | timeout: 120 37 | ) 38 | end 39 | 40 | it 'creates file[/marker_busybox_exec_onefile]' do 41 | expect(chef_run).to create_file('/marker_busybox_exec_onefile') 42 | end 43 | 44 | it 'run docker_exec[another]' do 45 | expect(chef_run).to run_docker_exec('poke_it').with( 46 | container: 'busybox_exec', 47 | command: ['touch', '/tmp/twofile'] 48 | ) 49 | end 50 | 51 | it 'creates file[/marker_busybox_exec_twofile]' do 52 | expect(chef_run).to create_file('/marker_busybox_exec_twofile') 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/docker_test/image_prune_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_test::image_prune' do 4 | context 'it steps over the provider' do 5 | cached(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04').converge(described_recipe) } 6 | 7 | context 'testing default action, default properties' do 8 | it 'prunes docker_image[hello-world]' do 9 | expect(chef_run).to prune_docker_image_prune('hello-world').with( 10 | dangling: true 11 | ) 12 | end 13 | 14 | it 'prunes docker_image[hello-world]' do 15 | expect(chef_run).to prune_docker_image_prune('prune-old-images').with( 16 | dangling: true, 17 | prune_until: '1h30m', 18 | with_label: 'com.example.vendor=ACME', 19 | without_label: 'no_prune' 20 | ) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/docker_test/installation_tarball_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_test::installation_tarball' do 4 | cached(:chef_run) do 5 | ChefSpec::SoloRunner.new(platform: 'ubuntu', 6 | version: '18.04', 7 | step_into: ['docker_installation_tarball']).converge(described_recipe) 8 | end 9 | 10 | # Coverage of all recent docker versions 11 | # To ensure test coverage and backwards compatibility 12 | # With the frequent changes in package naming convention 13 | # List generated from 14 | # https://download.docker.com/linux/static/stable/x86_64/ 15 | 16 | context 'tarball file names for Ubuntu 18.04' do 17 | cached(:chef_run) do 18 | ChefSpec::SoloRunner.new(platform: 'ubuntu', 19 | version: '18.04', 20 | step_into: ['docker_installation_package']).converge(described_recipe) 21 | end 22 | 23 | [ 24 | { docker_version: '18.03.1', expected: 'docker-18.03.1-ce.tgz' }, 25 | { docker_version: '18.06.3', expected: 'docker-18.06.3-ce.tgz' }, 26 | { docker_version: '18.09.0', expected: 'docker-18.09.0.tgz' }, 27 | { docker_version: '19.03.5', expected: 'docker-19.03.5.tgz' }, 28 | ].each do |suite| 29 | it 'generates the correct file name for tarball' do 30 | custom_resource = chef_run.docker_installation_tarball('default') 31 | actual = custom_resource.default_filename(suite[:docker_version]) 32 | expect(actual).to eq(suite[:expected]) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/docker_test/network_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_test::network' do 4 | cached(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04').converge(described_recipe) } 5 | 6 | context 'creates a network with unicode name' do 7 | it 'creates docker_network_seseme_straße' do 8 | expect(chef_run).to create_docker_network('seseme_straße') 9 | end 10 | end 11 | 12 | context 'creates a network with defaults' do 13 | it 'creates docker_network_a' do 14 | expect(chef_run).to create_docker_network('network_a') 15 | end 16 | 17 | it 'creates echo-base-network_a' do 18 | expect(chef_run).to run_docker_container('echo-base-network_a') 19 | end 20 | 21 | it 'creates echo-station-network_a' do 22 | expect(chef_run).to run_docker_container('echo-station-network_a') 23 | end 24 | end 25 | 26 | context 'when testing network deletion' do 27 | it 'creates network_b with the CLI' do 28 | expect(chef_run).to run_execute('create network_b').with( 29 | command: 'docker network create network_b' 30 | ) 31 | end 32 | 33 | it 'creates /marker_delete_network_b' do 34 | expect(chef_run).to create_file('/marker_delete_network_b') 35 | end 36 | 37 | it 'deletes docker_network[network_b]' do 38 | expect(chef_run).to delete_docker_network('network_b') 39 | end 40 | end 41 | 42 | context 'creates a network with subnet and gateway' do 43 | it 'creates docker_network_c' do 44 | expect(chef_run).to create_docker_network('network_c').with( 45 | subnet: '192.168.88.0/24', 46 | gateway: '192.168.88.1' 47 | ) 48 | end 49 | 50 | it 'creates echo-base-network_c' do 51 | expect(chef_run).to run_docker_container('echo-base-network_c') 52 | end 53 | 54 | it 'creates echo-station-network_c' do 55 | expect(chef_run).to run_docker_container('echo-station-network_c') 56 | end 57 | end 58 | 59 | context 'creates a network with aux_address' do 60 | it 'creates docker_network_d' do 61 | expect(chef_run).to create_docker_network('network_d').with( 62 | subnet: '192.168.89.0/24', 63 | gateway: '192.168.89.1', 64 | aux_address: ['a=192.168.89.2', 'b=192.168.89.3'] 65 | ) 66 | end 67 | 68 | it 'creates echo-base-network_d' do 69 | expect(chef_run).to run_docker_container('echo-base-network_d') 70 | end 71 | 72 | it 'creates echo-station-network_d' do 73 | expect(chef_run).to run_docker_container('echo-station-network_d') 74 | end 75 | end 76 | 77 | context 'creates a network with overlay driver' do 78 | it 'creates network_e' do 79 | expect(chef_run).to create_docker_network('network_e').with( 80 | driver: 'overlay' 81 | ) 82 | end 83 | end 84 | 85 | context 'creates a network with an ip-range' do 86 | it 'creates docker_network_f' do 87 | expect(chef_run).to create_docker_network('network_f').with( 88 | driver: 'bridge', 89 | subnet: '172.28.0.0/16', 90 | gateway: '172.28.5.254', 91 | ip_range: '172.28.5.0/24' 92 | ) 93 | end 94 | 95 | it 'creates echo-base-network_f' do 96 | expect(chef_run).to run_docker_container('echo-base-network_f') 97 | end 98 | 99 | it 'creates echo-station-network_f' do 100 | expect(chef_run).to run_docker_container('echo-station-network_f') 101 | end 102 | end 103 | 104 | context 'create an overlay network with multiple subnets' do 105 | it 'creates docker_network_g' do 106 | expect(chef_run).to create_docker_network('network_g').with( 107 | driver: 'overlay', 108 | subnet: ['192.168.0.0/16', '192.170.0.0/16'], 109 | gateway: ['192.168.0.100', '192.170.0.100'], 110 | ip_range: '192.168.1.0/24', 111 | aux_address: ['a=192.168.1.5', 'b=192.168.1.6', 'a=192.170.1.5', 'b=192.170.1.6'] 112 | ) 113 | end 114 | 115 | it 'creates echo-base-network_g' do 116 | expect(chef_run).to run_docker_container('echo-base-network_g') 117 | end 118 | 119 | it 'creates echo-station-network_g' do 120 | expect(chef_run).to run_docker_container('echo-station-network_g') 121 | end 122 | end 123 | 124 | context 'connect and disconnect a container' do 125 | it 'creates docker_network_h1' do 126 | expect(chef_run).to create_docker_network('network_h1') 127 | end 128 | 129 | it 'creates docker_network_h2' do 130 | expect(chef_run).to create_docker_network('network_h2') 131 | end 132 | 133 | it 'creates container1-network_h' do 134 | expect(chef_run).to run_docker_container('container1-network_h') 135 | end 136 | 137 | it 'creates /marker/network_h' do 138 | expect(chef_run).to create_file('/marker_network_h') 139 | end 140 | 141 | it 'connects container1-network_h with network_h2' do 142 | expect(chef_run).to connect_docker_network('network_h2 connector').with( 143 | container: 'container1-network_h' 144 | ) 145 | end 146 | 147 | it 'disconnects container1-network_h from network_h1' do 148 | expect(chef_run).to disconnect_docker_network('network_h1 disconnector').with( 149 | container: 'container1-network_h' 150 | ) 151 | end 152 | end 153 | 154 | context 'ipv6 network' do 155 | it 'creates docker_network_ipv6' do 156 | expect(chef_run).to create_docker_network('network_ipv6').with( 157 | enable_ipv6: true, 158 | subnet: 'fd00:dead:beef::/48' 159 | ) 160 | end 161 | 162 | it 'creates docker_network_ipv4' do 163 | expect(chef_run).to create_docker_network('network_ipv4') 164 | end 165 | end 166 | 167 | context 'internal network' do 168 | it 'creates docker_network_internal' do 169 | expect(chef_run).to create_docker_network('network_internal').with( 170 | internal: true 171 | ) 172 | end 173 | end 174 | end 175 | -------------------------------------------------------------------------------- /spec/docker_test/plugin_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_test::plugin' do 4 | cached(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04').converge(described_recipe) } 5 | 6 | let(:sshfs_caps) do 7 | [ 8 | { 9 | 'Name' => 'network', 10 | 'Value' => ['host'], 11 | }, 12 | { 13 | 'Name' => 'mount', 14 | 'Value' => ['/var/lib/docker/plugins/'], 15 | }, 16 | { 17 | 'Name' => 'mount', 18 | 'Value' => [''], 19 | }, 20 | { 21 | 'Name' => 'device', 22 | 'Value' => ['/dev/fuse'], 23 | }, 24 | { 25 | 'Name' => 'capabilities', 26 | 'Value' => ['CAP_SYS_ADMIN'], 27 | }, 28 | ] 29 | end 30 | 31 | context 'testing default action, default properties, but with privilege grant' do 32 | it 'installs vieux/sshfs' do 33 | expect(chef_run).to install_docker_plugin('vieux/sshfs').with( 34 | api_retries: 3, 35 | grant_privileges: sshfs_caps, 36 | remote_tag: 'latest' 37 | ) 38 | end 39 | end 40 | 41 | context 'reconfigure existing plugin' do 42 | it 'enables debug on vieux/sshfs' do 43 | expect(chef_run).to update_docker_plugin('configure vieux/sshfs').with( 44 | api_retries: 3, 45 | options: { 46 | 'DEBUG' => '1', 47 | }, 48 | local_alias: 'vieux/sshfs', 49 | remote_tag: 'latest' 50 | ) 51 | end 52 | end 53 | 54 | context 'testing the remove action' do 55 | it 'removes vieux/sshfs' do 56 | expect(chef_run).to remove_docker_plugin('remove vieux/sshfs').with( 57 | api_retries: 3, 58 | local_alias: 'vieux/sshfs', 59 | remote_tag: 'latest' 60 | ) 61 | end 62 | end 63 | 64 | context 'testing configure and install at the same time' do 65 | it 'installs wetopi/rbd' do 66 | expect(chef_run).to install_docker_plugin('rbd').with( 67 | remote: 'wetopi/rbd', 68 | remote_tag: '1.0.1', 69 | grant_privileges: true, 70 | options: { 71 | 'LOG_LEVEL' => '4', 72 | } 73 | ) 74 | end 75 | 76 | it 'removes wetopi/rbd again' do 77 | expect(chef_run).to remove_docker_plugin('remove rbd').with( 78 | local_alias: 'rbd' 79 | ) 80 | end 81 | end 82 | 83 | context 'install is idempotent' do 84 | it 'installs vieux/sshfs two times' do 85 | expect(chef_run).to install_docker_plugin('sshfs 2.1').with( 86 | remote: 'vieux/sshfs', 87 | remote_tag: 'latest', 88 | local_alias: 'sshfs', 89 | grant_privileges: true 90 | ) 91 | 92 | expect(chef_run).to install_docker_plugin('sshfs 2.2').with( 93 | remote: 'vieux/sshfs', 94 | remote_tag: 'latest', 95 | local_alias: 'sshfs', 96 | grant_privileges: true 97 | ) 98 | end 99 | end 100 | 101 | context 'test :enable / :disable action' do 102 | it 'enables sshfs' do 103 | expect(chef_run).to enable_docker_plugin('enable sshfs').with( 104 | local_alias: 'sshfs' 105 | ) 106 | end 107 | 108 | it 'disables sshfs' do 109 | expect(chef_run).to disable_docker_plugin('disable sshfs').with( 110 | local_alias: 'sshfs' 111 | ) 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /spec/docker_test/registry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_test::registry' do 4 | cached(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04').converge(described_recipe) } 5 | 6 | before do 7 | stub_command('/usr/bin/test -f /tmp/registry/tls/ca.pem').and_return(false) 8 | stub_command('/usr/bin/test -f /tmp/registry/tls/ca-key.pem').and_return(false) 9 | stub_command('/usr/bin/test -f /tmp/registry/tls/key.pem').and_return(false) 10 | stub_command('/usr/bin/test -f /tmp/registry/tls/cert.pem').and_return(false) 11 | stub_command('/usr/bin/test -f /tmp/registry/tls/server-key.pem').and_return(false) 12 | stub_command('/usr/bin/test -f /tmp/registry/tls/server.pem').and_return(false) 13 | stub_command('/usr/bin/test -f /tmp/registry/tls/client.csr').and_return(false) 14 | stub_command('/usr/bin/test -f /tmp/registry/tls/server.csr').and_return(false) 15 | stub_command("[ ! -z `docker ps -qaf 'name=registry_service$'` ]").and_return(false) 16 | stub_command("[ ! -z `docker ps -qaf 'name=registry_proxy$'` ]").and_return(false) 17 | stub_command('netstat -plnt | grep ":5000" && netstat -plnt | grep ":5043"').and_return(false) 18 | end 19 | 20 | context 'when compiling the recipe' do 21 | it 'creates directory[/tmp/registry/tls]' do 22 | expect(chef_run).to create_directory('/tmp/registry/tls').with( 23 | recursive: true 24 | ) 25 | end 26 | 27 | it 'runs bash[creating private key for docker server]' do 28 | expect(chef_run).to run_bash('creating private key for docker server') 29 | end 30 | 31 | it 'runs bash[generating CA private and public key]' do 32 | expect(chef_run).to run_bash('generating CA private and public key') 33 | end 34 | 35 | it 'runs bash[generating certificate request for server]' do 36 | expect(chef_run).to run_bash('generating certificate request for server') 37 | end 38 | 39 | it 'creates file[/tmp/registry/tls/server-extfile.cnf]' do 40 | expect(chef_run).to create_file('/tmp/registry/tls/server-extfile.cnf') 41 | end 42 | 43 | it 'runs bash[signing request for server]' do 44 | expect(chef_run).to run_bash('signing request for server') 45 | end 46 | 47 | it 'runs bash[creating private key for docker client]' do 48 | expect(chef_run).to run_bash('creating private key for docker client') 49 | end 50 | 51 | it 'runs bash[generating certificate request for client]' do 52 | expect(chef_run).to run_bash('generating certificate request for client') 53 | end 54 | 55 | it 'creates file[/tmp/registry/tls/client-extfile.cnf]' do 56 | expect(chef_run).to create_file('/tmp/registry/tls/client-extfile.cnf') 57 | end 58 | 59 | it 'runs bash[signing request for client]' do 60 | expect(chef_run).to run_bash('signing request for client') 61 | end 62 | 63 | it 'pulls docker_image[nginx]' do 64 | expect(chef_run).to pull_docker_image('nginx').with( 65 | tag: '1.9' 66 | ) 67 | end 68 | 69 | it 'pulls docker_image[registry]' do 70 | expect(chef_run).to pull_docker_image('registry').with( 71 | tag: '2.6.1' 72 | ) 73 | end 74 | 75 | it 'creates directory[/tmp/registry/auth]' do 76 | expect(chef_run).to create_directory('/tmp/registry/auth').with( 77 | recursive: true, 78 | owner: 'root', 79 | mode: '0755' 80 | ) 81 | end 82 | 83 | it 'creates template[/tmp/registry/auth/registry.conf]' do 84 | expect(chef_run).to create_template('/tmp/registry/auth/registry.conf').with( 85 | source: 'registry/auth/registry.conf.erb', 86 | owner: 'root', 87 | mode: '0755' 88 | ) 89 | end 90 | 91 | it 'runs execute[copy server cert for registry]' do 92 | expect(chef_run).to run_execute('copy server cert for registry').with( 93 | command: 'cp /tmp/registry/tls/server.pem /tmp/registry/auth/server.crt', 94 | creates: '/tmp/registry/auth/server.crt' 95 | ) 96 | end 97 | 98 | it 'runs execute[copy server key for registry]' do 99 | expect(chef_run).to run_execute('copy server key for registry').with( 100 | command: 'cp /tmp/registry/tls/server-key.pem /tmp/registry/auth/server.key', 101 | creates: '/tmp/registry/auth/server.key' 102 | ) 103 | end 104 | 105 | it 'creates template[/tmp/registry/auth/registry.password]' do 106 | expect(chef_run).to create_template('/tmp/registry/auth/registry.password').with( 107 | source: 'registry/auth/registry.password.erb', 108 | owner: 'root', 109 | mode: '0755' 110 | ) 111 | end 112 | 113 | it 'runs bash[start docker registry]' do 114 | expect(chef_run).to run_bash('start docker registry') 115 | end 116 | 117 | it 'runs bash[start docker registry proxy]' do 118 | expect(chef_run).to run_bash('start docker registry proxy') 119 | end 120 | 121 | it 'runs bash[wait for docker registry and proxy]' do 122 | expect(chef_run).to run_bash('wait for docker registry and proxy') 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /spec/docker_test/volume_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_test::volume' do 4 | cached(:chef_run) { ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '18.04').converge(described_recipe) } 5 | 6 | it 'pull_if_missing docker_image[alpine]' do 7 | expect(chef_run).to pull_if_missing_docker_image('alpine').with( 8 | tag: '3.1' 9 | ) 10 | end 11 | 12 | context 'testing remove action' do 13 | it 'executes docker creates volume --name remove_me' do 14 | expect(chef_run).to run_execute('docker volume create --name remove_me') 15 | end 16 | 17 | it 'creates file /marker_remove_me' do 18 | expect(chef_run).to create_file('/marker_remove_me') 19 | end 20 | 21 | it 'removes docker_volume[remove_me]' do 22 | expect(chef_run).to remove_docker_volume('remove_me') 23 | end 24 | end 25 | 26 | context 'testing create action' do 27 | it 'creates volume hello' do 28 | expect(chef_run).to create_docker_volume('hello') 29 | end 30 | 31 | it 'creates volume hello again' do 32 | expect(chef_run).to create_docker_volume('hello again').with( 33 | volume_name: 'hello_again' 34 | ) 35 | end 36 | 37 | context 'testing create action' do 38 | it 'runs file_writer' do 39 | expect(chef_run).to run_if_missing_docker_container('file_writer') 40 | end 41 | 42 | it 'runs file_writer' do 43 | expect(chef_run).to run_if_missing_docker_container('file_reader') 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/helpers_container_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'rspec' 2 | # require 'rspec/its' 3 | # require_relative '../libraries/helpers_container' 4 | # 5 | # class FakeContainerForTestingImageProperty 6 | # include DockerCookbook::DockerHelpers::Container 7 | # 8 | # def initialize(attributes = {}) 9 | # @attributes = attributes 10 | # end 11 | # 12 | # def repo(value = nil) 13 | # attributes['repo'] = value if value 14 | # attributes['repo'] 15 | # end 16 | # 17 | # def tag(value = nil) 18 | # attributes['tag'] = value if value 19 | # attributes['tag'] || 'latest' 20 | # end 21 | # 22 | # private 23 | # 24 | # attr_reader :attributes 25 | # end 26 | # 27 | # describe DockerCookbook::DockerHelpers::Container do 28 | # let(:helper) { FakeContainerForTestingImageProperty.new } 29 | # 30 | # describe '#image' do 31 | # subject { helper } 32 | # 33 | # context "If you say: repo 'blah'" do 34 | # before { helper.repo 'blah' } 35 | # its(:image) { is_expected.to eq('blah:latest') } 36 | # end 37 | # 38 | # context "If you say: repo 'blah'; tag '3.1'" do 39 | # before do 40 | # helper.repo 'blah' 41 | # helper.tag '3.1' 42 | # end 43 | # its(:image) { is_expected.to eq('blah:3.1') } 44 | # end 45 | # 46 | # context "If you say: image 'blah'" do 47 | # before { helper.image 'blah' } 48 | # its(:repo) { is_expected.to eq('blah') } 49 | # its(:tag) { is_expected.to eq('latest') } 50 | # end 51 | # 52 | # context "If you say: image 'blah:3.1'" do 53 | # before { helper.image 'blah:3.1' } 54 | # its(:repo) { is_expected.to eq('blah') } 55 | # its(:tag) { is_expected.to eq('3.1') } 56 | # end 57 | # 58 | # context "If you say: image 'repo/blah'" do 59 | # before { helper.image 'repo/blah' } 60 | # its(:repo) { is_expected.to eq('repo/blah') } 61 | # its(:tag) { is_expected.to eq('latest') } 62 | # end 63 | # 64 | # context "If you say: image 'repo/blah:3.1'" do 65 | # before { helper.image 'repo/blah:3.1' } 66 | # its(:repo) { is_expected.to eq('repo/blah') } 67 | # its(:tag) { is_expected.to eq('3.1') } 68 | # end 69 | # 70 | # context "If you say: image 'repo:1337/blah'" do 71 | # before { helper.image 'repo:1337/blah' } 72 | # its(:repo) { is_expected.to eq('repo:1337/blah') } 73 | # its(:tag) { is_expected.to eq('latest') } 74 | # end 75 | # 76 | # context "If you say: image 'repo:1337/blah:3.1'" do 77 | # before { helper.image 'repo:1337/blah:3.1' } 78 | # its(:repo) { is_expected.to eq('repo:1337/blah') } 79 | # its(:tag) { is_expected.to eq('3.1') } 80 | # end 81 | # end 82 | # end 83 | -------------------------------------------------------------------------------- /spec/helpers_network_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'rspec' 2 | # require_relative '../libraries/helpers_network' 3 | # 4 | # describe Class.new { include DockerCookbook::DockerHelpers::Network } do 5 | # subject(:helper) { Class.new { include DockerCookbook::DockerHelpers::Network } } 6 | # let(:subnets) do 7 | # %w( 8 | # 192.168.0.0/24 9 | # ) 10 | # end 11 | # 12 | # let(:ip_ranges) do 13 | # %w( 14 | # 192.168.0.31/28 15 | # ) 16 | # end 17 | # 18 | # let(:gateways) do 19 | # %w( 20 | # 192.168.0.34 21 | # ) 22 | # end 23 | # 24 | # let(:aux_addresses) do 25 | # %w( 26 | # foo=192.168.0.34 27 | # bar=192.168.0.124 28 | # ) 29 | # end 30 | # 31 | # describe '#consolidate_ipam' do 32 | # subject { described_class.new.consolidate_ipam(subnets, ip_ranges, gateways, aux_addresses) } 33 | # it 'should have a subnet' do 34 | # expect(subject).to include(include('Subnet' => '192.168.0.0/24')) 35 | # end 36 | # 37 | # it 'should have aux address' do 38 | # expect(subject).to include(include('AuxiliaryAddresses' => { 'foo' => '192.168.0.34', 'bar' => '192.168.0.124' })) 39 | # end 40 | # 41 | # it 'should have gateways' do 42 | # expect(subject).to include(include('Gateway' => '192.168.0.34')) 43 | # end 44 | # 45 | # it 'should have ip range' do 46 | # expect(subject).to include(include('IPRange' => '192.168.0.31/28')) 47 | # end 48 | # end 49 | # end 50 | -------------------------------------------------------------------------------- /spec/libraries/container_networks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'docker' 3 | require_relative '../../libraries/helpers_network' 4 | 5 | describe 'docker_container' do 6 | step_into :docker_container 7 | platform 'ubuntu' 8 | 9 | describe 'gets ip_address_from_container_networks' do 10 | include DockerCookbook::DockerHelpers::Network 11 | let(:options) { { 'id' => rand(10_000).to_s } } 12 | subject do 13 | Docker::Container.send(:new, Docker.connection, options) 14 | end 15 | 16 | # https://docs.docker.com/engine/api/version-history/#v121-api-changes 17 | context 'when docker API < 1.21' do 18 | let(:ip_address) { '10.0.0.1' } 19 | let(:options) do 20 | { 21 | 'id' => rand(10_000).to_s, 22 | 'IPAddress' => ip_address, 23 | } 24 | end 25 | 26 | it 'gets ip_address as nil' do 27 | actual = ip_address_from_container_networks(subject) 28 | expect { ip_address_from_container_networks(subject) }.not_to raise_error 29 | expect(actual).to eq(nil) 30 | end 31 | end 32 | 33 | context 'when docker API > 1.21' do 34 | let(:ip_address) { '10.0.0.1' } 35 | let(:options) do 36 | { 37 | 'id' => rand(10_000).to_s, 38 | 'NetworkSettings' => { 39 | 'Networks' => { 40 | 'bridge' => { 41 | 'IPAMConfig' => { 42 | 'IPv4Address' => ip_address, 43 | }, 44 | }, 45 | }, 46 | }, 47 | } 48 | end 49 | 50 | it 'gets ip_address' do 51 | actual = ip_address_from_container_networks(subject) 52 | expect(actual).to eq(ip_address) 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/libraries/image_prune_spec.rb: -------------------------------------------------------------------------------- 1 | # TODO: Refactor test 2 | require 'spec_helper' 3 | require_relative '../../libraries/helpers_json' 4 | 5 | RSpec.describe DockerCookbook::DockerHelpers::Json do 6 | class DummyClass < Chef::Node 7 | include DockerCookbook::DockerHelpers::Json 8 | end 9 | 10 | subject { DummyClass.new } 11 | 12 | describe '#generate_json' do 13 | it 'generates filter json' do 14 | dangling = true 15 | prune_until = '1h30m' 16 | with_label = 'com.example.vendor=ACME' 17 | without_label = 'no_prune' 18 | expected = 'filters=%7B%22dangling%22%3A%7B%22true%22%3Atrue%7D%2C%22until%22%3A%7B%221h30m%22%3Atrue%7D%2C%22label%22%3A%7B%22com.example.vendor%3DACME%22%3Atrue%7D%2C%22label%21%22%3A%7B%22no_prune%22%3Atrue%7D%7D' 19 | 20 | expect(subject.prune_generate_json(dangling: dangling, prune_until: prune_until, with_label: with_label, without_label: without_label)).to eq(expected) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/libraries/registry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_registry' do 4 | step_into :docker_registry 5 | platform 'ubuntu' 6 | 7 | # Info returned by docker api 8 | # https://docs.docker.com/engine/api/v1.39/#section/Authentication 9 | let(:auth) do 10 | { 11 | 'identitytoken' => '9cbafc023786cd7...', 12 | }.to_json 13 | end 14 | 15 | before do 16 | # Ensure docker api calls are mocked 17 | # It is low level much easier to do in Excon 18 | # Plus, the low level mock allows testing this cookbook 19 | # for multiple docker apis and docker-api gems 20 | # https://github.com/excon/excon#stubs 21 | Excon.defaults[:mock] = true 22 | Excon.stub({ method: :post, path: '/auth' }, body: auth, status: 200) 23 | end 24 | 25 | context 'logs into a docker registry with default options' do 26 | recipe do 27 | docker_registry 'chefspec_custom_registry' do 28 | email 'chefspec_email' 29 | password 'chefspec_password' 30 | username 'chefspec_username' 31 | end 32 | end 33 | 34 | it do 35 | expect { chef_run }.to_not raise_error 36 | expect(chef_run).to login_docker_registry('chefspec_custom_registry').with( 37 | email: 'chefspec_email', 38 | password: 'chefspec_password', 39 | username: 'chefspec_username' 40 | ) 41 | end 42 | end 43 | 44 | context 'logs into a docker registry with host' do 45 | recipe do 46 | docker_registry 'chefspec_custom_registry' do 47 | email 'chefspec_email' 48 | password 'chefspec_password' 49 | username 'chefspec_username' 50 | host 'chefspec_host' 51 | end 52 | end 53 | it { 54 | expect { chef_run }.to_not raise_error 55 | expect(chef_run).to login_docker_registry('chefspec_custom_registry').with( 56 | email: 'chefspec_email', 57 | password: 'chefspec_password', 58 | username: 'chefspec_username', 59 | host: 'chefspec_host' 60 | ) 61 | } 62 | end 63 | 64 | context 'logs into a docker registry with host environment variable' do 65 | recipe do 66 | docker_registry 'chefspec_custom_registry' do 67 | email 'chefspec_email' 68 | password 'chefspec_password' 69 | username 'chefspec_username' 70 | end 71 | end 72 | it { 73 | # Set the environment variable 74 | stub_const 'ENV', ENV.to_h.merge('DOCKER_HOST' => 'chefspec_host_environment_variable') 75 | 76 | expect { chef_run }.to_not raise_error 77 | expect(chef_run).to login_docker_registry('chefspec_custom_registry').with( 78 | email: 'chefspec_email', 79 | password: 'chefspec_password', 80 | username: 'chefspec_username', 81 | host: 'chefspec_host_environment_variable' 82 | ) 83 | } 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'chefspec' 2 | require 'chefspec/berkshelf' 3 | 4 | class RSpecHelper 5 | class << self 6 | attr_accessor :current_example 7 | end 8 | def self.reset! 9 | @current_example = nil 10 | end 11 | end 12 | 13 | RSpec.configure do |config| 14 | config.filter_run focus: true 15 | config.run_all_when_everything_filtered = true 16 | config.formatter = :documentation 17 | 18 | config.before :each do 19 | RSpecHelper.reset! 20 | RSpecHelper.current_example = self 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/unit/resources/swarm_init_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_swarm_init' do 4 | step_into :docker_swarm_init 5 | platform 'ubuntu' 6 | 7 | context 'when initializing a new swarm' do 8 | recipe do 9 | docker_swarm_init 'initialize' do 10 | advertise_addr '192.168.1.2' 11 | listen_addr '0.0.0.0:2377' 12 | end 13 | end 14 | 15 | before do 16 | # Mock the shell_out calls directly 17 | shellout = double('shellout') 18 | allow(Mixlib::ShellOut).to receive(:new).and_return(shellout) 19 | allow(shellout).to receive(:run_command) 20 | allow(shellout).to receive(:error?).and_return(false) 21 | allow(shellout).to receive(:stdout).and_return('') 22 | allow(shellout).to receive(:stderr).and_return('') 23 | end 24 | 25 | it 'converges successfully' do 26 | expect { chef_run }.to_not raise_error 27 | end 28 | 29 | it 'runs the swarm init command' do 30 | expect(Mixlib::ShellOut).to receive(:new).with(/docker swarm init/) 31 | chef_run 32 | end 33 | end 34 | 35 | context 'when swarm is already initialized' do 36 | recipe do 37 | docker_swarm_init 'initialize' 38 | end 39 | 40 | before do 41 | # Mock the shell_out calls directly 42 | shellout = double('shellout') 43 | allow(Mixlib::ShellOut).to receive(:new).and_return(shellout) 44 | allow(shellout).to receive(:run_command) 45 | allow(shellout).to receive(:error?).and_return(false) 46 | allow(shellout).to receive(:stdout).and_return('active') 47 | allow(shellout).to receive(:stderr).and_return('') 48 | end 49 | 50 | it 'does not run init command if already in swarm' do 51 | expect(Mixlib::ShellOut).not_to receive(:new).with(/docker swarm init/) 52 | chef_run 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/unit/resources/swarm_join_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'docker_swarm_join' do 4 | step_into :docker_swarm_join 5 | platform 'ubuntu' 6 | 7 | context 'when joining a swarm' do 8 | recipe do 9 | docker_swarm_join 'join' do 10 | token 'SWMTKN-1-random-token' 11 | manager_ip '192.168.1.1:2377' 12 | advertise_addr '192.168.1.2' 13 | end 14 | end 15 | 16 | before do 17 | # Mock the shell_out calls directly 18 | shellout = double('shellout') 19 | allow(Mixlib::ShellOut).to receive(:new).and_return(shellout) 20 | allow(shellout).to receive(:run_command) 21 | allow(shellout).to receive(:error?).and_return(false) 22 | allow(shellout).to receive(:stdout).and_return('') 23 | allow(shellout).to receive(:stderr).and_return('') 24 | end 25 | 26 | it 'converges successfully' do 27 | expect { chef_run }.to_not raise_error 28 | end 29 | 30 | it 'runs the swarm join command' do 31 | expect(Mixlib::ShellOut).to receive(:new).with(/docker swarm join/) 32 | chef_run 33 | end 34 | end 35 | 36 | context 'when already in a swarm' do 37 | recipe do 38 | docker_swarm_join 'join' do 39 | token 'SWMTKN-1-random-token' 40 | manager_ip '192.168.1.1:2377' 41 | end 42 | end 43 | 44 | before do 45 | # Mock the shell_out calls directly 46 | shellout = double('shellout') 47 | allow(Mixlib::ShellOut).to receive(:new).and_return(shellout) 48 | allow(shellout).to receive(:run_command) 49 | allow(shellout).to receive(:error?).and_return(false) 50 | allow(shellout).to receive(:stdout).and_return('active') 51 | allow(shellout).to receive(:stderr).and_return('') 52 | end 53 | 54 | it 'does not run join command if already in swarm' do 55 | expect(Mixlib::ShellOut).not_to receive(:new).with(/docker swarm join/) 56 | chef_run 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/unit/resources/swarm_service_spec.rb: -------------------------------------------------------------------------------- 1 | # require 'spec_helper' 2 | 3 | # describe 'docker_swarm_service' do 4 | # step_into :docker_swarm_service 5 | # platform 'ubuntu' 6 | 7 | # context 'when creating a service' do 8 | # recipe do 9 | # docker_swarm_service 'nginx' do 10 | # image 'nginx:latest' 11 | # replicas 2 12 | # ports %w(80:80) 13 | # end 14 | # end 15 | 16 | # before do 17 | # # Mock swarm status 18 | # allow_any_instance_of(Chef::Resource).to receive(:shell_out).with('docker info --format "{{ .Swarm.LocalNodeState }}"').and_return( 19 | # double(error?: false, stdout: "active\n") 20 | # ) 21 | # allow_any_instance_of(Chef::Resource).to receive(:shell_out).with('docker info --format "{{ .Swarm.ControlAvailable }}"').and_return( 22 | # double(error?: false, stdout: "true\n") 23 | # ) 24 | 25 | # # Mock service inspection 26 | # allow_any_instance_of(Chef::Resource).to receive(:shell_out).with('docker service inspect nginx').and_return( 27 | # double(error?: true, stdout: '', stderr: 'Error: no such service: nginx') 28 | # ) 29 | 30 | # # Mock service creation 31 | # allow_any_instance_of(Chef::Resource).to receive(:shell_out).with(/docker service create/).and_return( 32 | # double(error?: false, stdout: '') 33 | # ) 34 | # end 35 | 36 | # it 'converges successfully' do 37 | # expect { chef_run }.to_not raise_error 38 | # end 39 | # end 40 | 41 | # context 'when not a swarm manager' do 42 | # recipe do 43 | # docker_swarm_service 'nginx' do 44 | # image 'nginx:latest' 45 | # replicas 2 46 | # ports %w(80:80) 47 | # end 48 | # end 49 | 50 | # before do 51 | # # Mock swarm status - member but not manager 52 | # allow_any_instance_of(Chef::Resource).to receive(:shell_out).with('docker info --format "{{ .Swarm.LocalNodeState }}"').and_return( 53 | # double(error?: false, stdout: "active\n") 54 | # ) 55 | # allow_any_instance_of(Chef::Resource).to receive(:shell_out).with('docker info --format "{{ .Swarm.ControlAvailable }}"').and_return( 56 | # double(error?: false, stdout: "false\n") 57 | # ) 58 | 59 | # # Mock service inspection 60 | # allow_any_instance_of(Chef::Resource).to receive(:shell_out).with('docker service inspect nginx').and_return( 61 | # double(error?: true, stdout: '', stderr: 'Error: no such service: nginx') 62 | # ) 63 | # end 64 | 65 | # it 'does not create the service' do 66 | # expect(chef_run).to_not run_execute('create service nginx') 67 | # end 68 | # end 69 | # end 70 | -------------------------------------------------------------------------------- /templates/default/default/docker-wait-ready.erb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | i=0 3 | while [ $i -lt <%= @service_timeout * 2 %> ]; do 4 | <%= @docker_cmd %> ps | head -n 1 | grep ^CONTAINER > /dev/null 2>&1 5 | [ $? -eq 0 ] && break 6 | ((i++)) 7 | sleep 0.5 8 | done 9 | [ $i -eq <%= @service_timeout * 2 %> ] && exit 1 10 | exit 0 11 | -------------------------------------------------------------------------------- /templates/default/sysconfig/docker.erb: -------------------------------------------------------------------------------- 1 | # /etc/sysconfig/docker 2 | 3 | # If you need Docker to use an HTTP proxy, it can also be specified here. 4 | <% if @config.http_proxy %> 5 | export http_proxy="<%= @config.http_proxy %>" 6 | <% end %> 7 | <% if @config.https_proxy %> 8 | export https_proxy="<%= @config.https_proxy %>" 9 | <% end %> 10 | 11 | <% if @config.no_proxy %> 12 | export no_proxy="<%= @config.no_proxy %>" 13 | <% end %> 14 | -------------------------------------------------------------------------------- /templates/default/systemd/containerd.service.erb: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=containerd container runtime 3 | Documentation=https://containerd.io 4 | After=network.target local-fs.target 5 | 6 | [Service] 7 | ExecStartPre=-/sbin/modprobe overlay 8 | ExecStart=/usr/bin/containerd 9 | 10 | Type=<%= @service_type %> 11 | Delegate=yes 12 | KillMode=process 13 | Restart=always 14 | RestartSec=5 15 | # Having non-zero Limit*s causes performance problems due to accounting overhead 16 | # in the kernel. We recommend using cgroups to do container-local accounting. 17 | LimitNPROC=infinity 18 | LimitCORE=infinity 19 | LimitNOFILE=infinity 20 | # Comment TasksMax if your systemd version does not supports it. 21 | # Only systemd 226 and above support this version. 22 | TasksMax=infinity 23 | OOMScoreAdjust=-999 24 | 25 | [Install] 26 | WantedBy=multi-user.target 27 | -------------------------------------------------------------------------------- /templates/default/systemd/docker.service-override.erb: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Docker Application Container Engine 3 | Documentation=https://docs.docker.com 4 | <% if @containerd -%> 5 | BindsTo=containerd.service 6 | <% end %> 7 | <% if @docker_socket.nil? %> 8 | After=network-online.target firewalld.service containerd.service 9 | Wants=network-online.target 10 | <% else %> 11 | After=network-online.target <%= @docker_name %>.socket firewalld.service containerd.service 12 | Wants=network-online.target 13 | Requires=<%= @docker_name %>.socket 14 | <% end %> 15 | 16 | [Service] 17 | Type=notify 18 | <% if @config.http_proxy %> 19 | Environment="HTTP_PROXY=<%= @config.http_proxy %>" 20 | <% end %> 21 | <% if @config.https_proxy %> 22 | Environment="HTTPS_PROXY=<%= @config.https_proxy %>" 23 | <% end %> 24 | <% if @config.no_proxy %> 25 | Environment="NO_PROXY=<%= @config.no_proxy %>" 26 | <% end %> 27 | <% if @config.tmpdir %> 28 | Environment="TMPDIR=<%= @config.tmpdir %>" 29 | <% end %> 30 | <% @env_vars.each do |key, val| %> 31 | Environment="<%= key %>=<%= val %>" 32 | <% end unless @env_vars.nil? %> 33 | <% if @config.ipv4_forward %> 34 | ExecStartPre=/sbin/sysctl -w net.ipv4.ip_forward=1 35 | <% end %> 36 | <% if @config.ipv6_forward %> 37 | ExecStartPre=/sbin/sysctl -w net.ipv6.conf.all.forwarding=1 38 | <% end %> 39 | # the default is not to use systemd for cgroups because the delegate issues still 40 | # exists and systemd currently does not support the cgroup feature set required 41 | # for containers run by docker 42 | ExecStart=<%= @docker_daemon_cmd %> 43 | ExecReload=/bin/kill -s HUP $MAINPID 44 | <% if @config.mount_flags %> 45 | MountFlags=<%= @config.mount_flags %> 46 | <% end %> 47 | TimeoutSec=0 48 | RestartSec=2 49 | Restart=always 50 | <%= @systemd_args %> 51 | 52 | # Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229. 53 | # Both the old, and new location are accepted by systemd 229 and up, so using the old location 54 | # to make them work for either version of systemd. 55 | StartLimitBurst=3 56 | 57 | # Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230. 58 | # Both the old, and new name are accepted by systemd 230 and up, so using the old name to make 59 | # this option work for either version of systemd. 60 | StartLimitInterval=60s 61 | 62 | # Having non-zero Limit*s causes performance problems due to accounting overhead 63 | # in the kernel. We recommend using cgroups to do container-local accounting. 64 | LimitNOFILE=infinity 65 | LimitNPROC=infinity 66 | LimitCORE=infinity 67 | 68 | # Comment TasksMax if your systemd version does not support it. 69 | # Only systemd 226 and above support this option. 70 | TasksMax=infinity 71 | 72 | # set delegate yes so that systemd does not reset the cgroups of docker containers 73 | Delegate=yes 74 | 75 | # kill only the docker process, not all processes in the cgroup 76 | KillMode=process 77 | 78 | [Install] 79 | WantedBy=multi-user.target 80 | -------------------------------------------------------------------------------- /templates/default/systemd/docker.service.erb: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Docker Application Container Engine 3 | Documentation=https://docs.docker.com 4 | <% if @containerd -%> 5 | BindsTo=containerd.service 6 | <% end %> 7 | <% if @docker_socket.nil? %> 8 | After=network-online.target firewalld.service containerd.service 9 | Wants=network-online.target 10 | <% else %> 11 | After=network-online.target <%= @docker_name %>.socket firewalld.service containerd.service 12 | Wants=network-online.target 13 | Requires=<%= @docker_name %>.socket 14 | <% end %> 15 | 16 | [Service] 17 | Type=notify 18 | # the default is not to use systemd for cgroups because the delegate issues still 19 | # exists and systemd currently does not support the cgroup feature set required 20 | # for containers run by docker 21 | <% if @containerd %> 22 | ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock 23 | <% else %> 24 | ExecStart=/usr/bin/dockerd -H fd:// 25 | <% end %> 26 | ExecReload=/bin/kill -s HUP $MAINPID 27 | TimeoutSec=0 28 | RestartSec=2 29 | Restart=always 30 | 31 | # Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229. 32 | # Both the old, and new location are accepted by systemd 229 and up, so using the old location 33 | # to make them work for either version of systemd. 34 | StartLimitBurst=3 35 | 36 | # Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230. 37 | # Both the old, and new name are accepted by systemd 230 and up, so using the old name to make 38 | # this option work for either version of systemd. 39 | StartLimitInterval=60s 40 | 41 | # Having non-zero Limit*s causes performance problems due to accounting overhead 42 | # in the kernel. We recommend using cgroups to do container-local accounting. 43 | LimitNOFILE=infinity 44 | LimitNPROC=infinity 45 | LimitCORE=infinity 46 | 47 | # Comment TasksMax if your systemd version does not support it. 48 | # Only systemd 226 and above support this option. 49 | TasksMax=infinity 50 | 51 | # set delegate yes so that systemd does not reset the cgroups of docker containers 52 | Delegate=yes 53 | 54 | # kill only the docker process, not all processes in the cgroup 55 | KillMode=process 56 | 57 | [Install] 58 | WantedBy=multi-user.target 59 | -------------------------------------------------------------------------------- /templates/default/systemd/docker.socket-override.erb: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Docker Socket for the API 3 | PartOf=<%= @docker_name %>.service 4 | 5 | [Socket] 6 | ListenStream=<%= @docker_socket %> 7 | SocketGroup=<%= @config.group %> 8 | <%= @systemd_socket_args %> 9 | 10 | [Install] 11 | WantedBy=sockets.target 12 | -------------------------------------------------------------------------------- /templates/default/systemd/docker.socket.erb: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Docker Socket for the API 3 | PartOf=<%= @docker_name %>.service 4 | 5 | [Socket] 6 | ListenStream=<%= @docker_socket %> 7 | SocketMode=0660 8 | SocketUser=root 9 | SocketGroup=<%= @config.group %> 10 | 11 | [Install] 12 | WantedBy=sockets.target 13 | -------------------------------------------------------------------------------- /templates/default/systemd/tmpfiles.d.conf.erb: -------------------------------------------------------------------------------- 1 | d /var/run/docker 0755 root <%= @config.group || 'root' %> - 2 | -------------------------------------------------------------------------------- /templates/default/sysvinit/docker-debian.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | ### BEGIN INIT INFO 5 | # Provides: <%= @docker_name %> 6 | # Required-Start: $syslog $remote_fs 7 | # Required-Stop: $syslog $remote_fs 8 | # Should-Start: cgroupfs-mount cgroup-lite 9 | # Should-Stop: cgroupfs-mount cgroup-lite 10 | # Default-Start: 2 3 4 5 11 | # Default-Stop: 0 1 6 12 | # Short-Description: Create lightweight, portable, self-sufficient containers. 13 | # Description: 14 | # Docker is an open-source project to easily create lightweight, portable, 15 | # self-sufficient containers from any application. The same container that a 16 | # developer builds and tests on a laptop can run at scale, in production, on 17 | # VMs, bare metal, OpenStack clusters, public clouds and more. 18 | ### END INIT INFO 19 | 20 | export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin 21 | 22 | BASE=<%= @docker_name %> 23 | 24 | # modify these in /etc/default/$BASE (/etc/default/docker) 25 | DOCKER=<%= @dockerd_bin_link %> 26 | DOCKER_OPTS="<%= @docker_daemon_arg %> <%= @docker_daemon_opts %>" 27 | # This is the pid file managed by docker itself 28 | DOCKER_PIDFILE=/var/run/$BASE.pid 29 | # This is the pid file created/managed by start-stop-daemon 30 | DOCKER_SSD_PIDFILE=/var/run/$BASE-ssd.pid 31 | DOCKER_LOGFILE=/var/log/$BASE.log 32 | DOCKER_DESC="Docker" 33 | 34 | # Get lsb functions 35 | . /lib/lsb/init-functions 36 | 37 | if [ -f /etc/default/$BASE ]; then 38 | . /etc/default/$BASE 39 | fi 40 | 41 | # Check docker is present 42 | if [ ! -x $DOCKER ]; then 43 | log_failure_msg "$DOCKER not present or not executable" 44 | exit 1 45 | fi 46 | 47 | check_init() { 48 | # see also init_is_upstart in /lib/lsb/init-functions (which isn't available in Ubuntu 12.04, or we'd use it directly) 49 | if [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; then 50 | log_failure_msg "$DOCKER_DESC is managed via upstart, try using service $BASE $1" 51 | exit 1 52 | fi 53 | } 54 | 55 | fail_unless_root() { 56 | if [ "$(id -u)" != '0' ]; then 57 | log_failure_msg "$DOCKER_DESC must be run as root" 58 | exit 1 59 | fi 60 | } 61 | 62 | cgroupfs_mount() { 63 | # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount 64 | if grep -v '^#' /etc/fstab | grep -q cgroup \ 65 | || [ ! -e /proc/cgroups ] \ 66 | || [ ! -d /sys/fs/cgroup ]; then 67 | return 68 | fi 69 | if ! mountpoint -q /sys/fs/cgroup; then 70 | mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup 71 | fi 72 | ( 73 | cd /sys/fs/cgroup 74 | for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do 75 | mkdir -p $sys 76 | if ! mountpoint -q $sys; then 77 | if ! mount -n -t cgroup -o $sys cgroup $sys; then 78 | rmdir $sys || true 79 | fi 80 | fi 81 | done 82 | ) 83 | } 84 | 85 | case "$1" in 86 | start) 87 | check_init 88 | 89 | fail_unless_root 90 | 91 | cgroupfs_mount 92 | 93 | touch "$DOCKER_LOGFILE" 94 | chgrp docker "$DOCKER_LOGFILE" 95 | 96 | ulimit -n 1048576 97 | if [ "$BASH" ]; then 98 | ulimit -u 1048576 99 | else 100 | ulimit -p 1048576 101 | fi 102 | 103 | log_begin_msg "Starting $DOCKER_DESC: $BASE" 104 | start-stop-daemon --start --background \ 105 | --no-close \ 106 | --exec "$DOCKER" \ 107 | --pidfile "$DOCKER_SSD_PIDFILE" \ 108 | --make-pidfile \ 109 | -- \ 110 | $DOCKER_OPTS \ 111 | -p "$DOCKER_PIDFILE" \ 112 | >> "$DOCKER_LOGFILE" 2>&1 113 | 114 | <%= @docker_wait_ready %> > /dev/null 2>&1 115 | if [ $? -ne 0 ]; then 116 | log_failure_msg "<%= @docker_socket %> failed to start" 117 | exit 1 118 | fi 119 | log_end_msg $? 120 | ;; 121 | 122 | stop) 123 | check_init 124 | fail_unless_root 125 | log_begin_msg "Stopping $DOCKER_DESC: $BASE" 126 | start-stop-daemon --stop --pidfile "$DOCKER_SSD_PIDFILE" --retry 10 127 | log_end_msg $? 128 | ;; 129 | 130 | restart) 131 | check_init 132 | fail_unless_root 133 | docker_pid=`cat "$DOCKER_SSD_PIDFILE" 2>/dev/null` 134 | [ -n "$docker_pid" ] \ 135 | && ps -p $docker_pid > /dev/null 2>&1 \ 136 | && $0 stop 137 | $0 start 138 | ;; 139 | 140 | force-reload) 141 | check_init 142 | fail_unless_root 143 | $0 restart 144 | ;; 145 | 146 | status) 147 | check_init 148 | status_of_proc -p "$DOCKER_SSD_PIDFILE" "$DOCKER" "$DOCKER_DESC" 149 | ;; 150 | 151 | *) 152 | echo "Usage: service docker {start|stop|restart|status}" 153 | exit 1 154 | ;; 155 | esac 156 | -------------------------------------------------------------------------------- /templates/default/sysvinit/docker-rhel.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # /etc/rc.d/init.d/<%= @docker_name %> 4 | # 5 | # Daemon for docker.com 6 | # 7 | # chkconfig: 2345 95 95 8 | # description: Daemon for docker.com 9 | 10 | ### BEGIN INIT INFO 11 | # Provides: docker 12 | # Required-Start: $network cgconfig 13 | # Required-Stop: 14 | # Should-Start: 15 | # Should-Stop: 16 | # Default-Start: 2 3 4 5 17 | # Default-Stop: 0 1 6 18 | # Short-Description: start and stop docker 19 | # Description: Daemon for docker.com 20 | ### END INIT INFO 21 | 22 | # Source function library. 23 | . /etc/rc.d/init.d/functions 24 | 25 | prog="<%= @dockerd_bin_link %>" 26 | instance=<%= @docker_name %> 27 | unshare=/usr/bin/unshare 28 | exec="$prog" 29 | pidfile="/var/run/$instance.pid" 30 | lockfile="/var/lock/subsys/$instance" 31 | logfile="/var/log/$instance" 32 | 33 | [ -e /etc/sysconfig/$instance ] && . /etc/sysconfig/$instance 34 | 35 | prestart() { 36 | service cgconfig status > /dev/null 37 | 38 | if [[ $? != 0 ]]; then 39 | service cgconfig start 40 | fi 41 | 42 | } 43 | 44 | start() { 45 | [ -x $exec ] || exit 5 46 | 47 | check_for_cleanup 48 | 49 | if ! [ -f $pidfile ]; then 50 | prestart 51 | printf "Starting $prog:\t" 52 | echo "\n$(date)\n" >> $logfile 53 | "$unshare" -m -- <%= @docker_daemon_cmd %> &>> $logfile & 54 | <%= @docker_wait_ready %> 55 | [ $? -eq 0 ] && success || failure 56 | echo 57 | else 58 | failure 59 | echo 60 | printf "$pidfile still exists...\n" 61 | exit 7 62 | fi 63 | } 64 | 65 | stop() { 66 | echo -n $"Stopping $prog: " 67 | killproc -p $pidfile -d 300 $prog 68 | retval=$? 69 | echo 70 | [ $retval -eq 0 ] && rm -f $lockfile 71 | return $retval 72 | } 73 | 74 | restart() { 75 | stop 76 | start 77 | } 78 | 79 | reload() { 80 | restart 81 | } 82 | 83 | force_reload() { 84 | restart 85 | } 86 | 87 | rh_status() { 88 | status -p $pidfile $prog 89 | } 90 | 91 | rh_status_q() { 92 | rh_status >/dev/null 2>&1 93 | } 94 | 95 | 96 | check_for_cleanup() { 97 | if [ -f ${pidfile} ]; then 98 | /bin/ps -fp $(cat ${pidfile}) > /dev/null || rm ${pidfile} 99 | fi 100 | } 101 | 102 | case "$1" in 103 | start) 104 | rh_status_q && exit 0 105 | $1 106 | ;; 107 | stop) 108 | rh_status_q || exit 0 109 | $1 110 | ;; 111 | restart) 112 | $1 113 | ;; 114 | reload) 115 | rh_status_q || exit 7 116 | $1 117 | ;; 118 | force-reload) 119 | force_reload 120 | ;; 121 | status) 122 | rh_status 123 | ;; 124 | condrestart|try-restart) 125 | rh_status_q || exit 0 126 | restart 127 | ;; 128 | *) 129 | echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" 130 | exit 2 131 | esac 132 | 133 | exit $? 134 | -------------------------------------------------------------------------------- /templates/default/upstart/docker.conf.erb: -------------------------------------------------------------------------------- 1 | description "Docker daemon" 2 | 3 | start on (filesystem and net-device-up IFACE!=lo) 4 | stop on runlevel [!2345] 5 | limit nofile 524288 1048576 6 | limit nproc 524288 1048576 7 | 8 | respawn 9 | 10 | kill timeout 20 11 | 12 | pre-start script 13 | # see also https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount 14 | if grep -v '^#' /etc/fstab | grep -q cgroup \ 15 | || [ ! -e /proc/cgroups ] \ 16 | || [ ! -d /sys/fs/cgroup ]; then 17 | exit 0 18 | fi 19 | if ! mountpoint -q /sys/fs/cgroup; then 20 | mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup 21 | fi 22 | ( 23 | cd /sys/fs/cgroup 24 | for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do 25 | mkdir -p $sys 26 | if ! mountpoint -q $sys; then 27 | if ! mount -n -t cgroup -o $sys cgroup $sys; then 28 | rmdir $sys || true 29 | fi 30 | fi 31 | done 32 | ) 33 | end script 34 | 35 | script 36 | # modify these in /etc/default/$UPSTART_JOB (/etc/default/docker) 37 | if [ -f /etc/default/$UPSTART_JOB ]; then 38 | . /etc/default/$UPSTART_JOB 39 | fi 40 | exec <%= @docker_daemon_cmd %> <%= @docker_raw_logs_arg %> 41 | end script 42 | 43 | post-start script 44 | <%= @docker_wait_ready %> 45 | if [ $? -eq 0 ]; then 46 | echo "<%= @docker_socket %> is up" 47 | else 48 | echo "<%= @docker_socket %> failed to start" 49 | exit 1 50 | fi 51 | end script 52 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG for docker_test 2 | 3 | This file is used to list changes made in each version of docker_test. 4 | 5 | ## 0.5.1 6 | 7 | * Bugfix: Test docker_image :build for both file and directory source 8 | 9 | ## 0.5.0 10 | 11 | * Bugfix: Switch docker@0.25.0 deprecated dockerfile container LWRP attribute to source 12 | 13 | ## 0.4.0 14 | 15 | * Bugfix: Remove deprecated public_port in container_lwrp 16 | * Bugfix: Add `init_type false` for busybox test containers 17 | * Enhancement: Add tduffield/testcontainerd image, container, and tests 18 | 19 | ## 0.3.0 20 | 21 | * Enhancement: Change Dockerfile FROM to already downloaded busybox image instead of ubuntu 22 | 23 | ## 0.2.0 24 | 25 | * Added container_lwrp recipe 26 | * Removed default recipe from image_lwrp recipe 27 | 28 | ## 0.1.0 29 | 30 | * Initial release of docker_test 31 | 32 | --- 33 | Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown. 34 | 35 | The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown. 36 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/files/Dockerfile_1: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | RUN /bin/echo 'hello from image_1' 3 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/files/Dockerfile_2: -------------------------------------------------------------------------------- 1 | FROM busybox 2 | ADD foo.txt /tmp/foo.txt 3 | RUN /bin/echo 'hello from image_2' 4 | VOLUME /home 5 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/files/Dockerfile_4: -------------------------------------------------------------------------------- 1 | # Create a docker image that takes a long time to build 2 | 3 | # Centos as a base image. Any should work for the for loop test, but 4 | # CentOS is needed for the yum test. 5 | # Note that pulling the base image will not trigger a 6 | # timeout, regardless of how long it 7 | # takes 8 | FROM centos 9 | 10 | # Simply wait for wait for 30 minutes, output a status update every 10 seconds 11 | # This does not appear to trigger the timeout problem 12 | # RUN [ "bash", "-c", "for minute in {1..30} ; do for second in {0..59..10} ; do echo -n \" $minute:$second \" ; sleep 10 ; done ; done" ] 13 | 14 | # This triggers the timeout. 15 | # Sleep for 5 minutes, 3 times. 16 | # RUN [ "bash", "-c", "for minute in {0..10..5} ; do echo -n \" $minute \" ; sleep 300 ; done" ] 17 | 18 | # Let's try this next. 19 | # Sleep for 1 minutes, 15 time 20 | RUN [ "bash", "-c", "for minute in {0..15} ; do echo -n \" $minute \" ; sleep 60 ; done" ] 21 | 22 | # This should trigger the timeout unless you have a very fast Internet connection. 23 | # RUN \ 24 | # curl -SL https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -o epel.rpm \ 25 | # && yum install -y epel.rpm \ 26 | # && rm epel.rpm \ 27 | # && yum install -y \ 28 | # zarafa \ 29 | # supervisor \ 30 | # && yum clean all \ 31 | # && rm -rf /usr/share/man /etc/httpd/conf.d/ssl.conf 32 | 33 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/files/Dockerfile_5: -------------------------------------------------------------------------------- 1 | ARG IMAGE_TAG 2 | ARG IMAGE_NAME 3 | FROM $IMAGE_NAME:$IMAGE_TAG 4 | ARG IMAGE_NAME 5 | ENV image_name=$IMAGE_NAME 6 | RUN echo $image_name > /image_name -------------------------------------------------------------------------------- /test/cookbooks/docker_test/files/image_3.tar: -------------------------------------------------------------------------------- 1 | ./000755 000767 000024 00000000000 12555312104 012005 5ustar00somearastaff000000 000000 ./Dockerfile000644 000767 000024 00000000063 12555315430 014003 0ustar00somearastaff000000 000000 FROM alpine:3.1 2 | RUN /bin/echo 'hello from image_3' 3 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/files/image_3/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.1 2 | RUN /bin/echo 'hello from image_3' 3 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'docker_test' 2 | maintainer 'Sean OMeara' 3 | maintainer_email 'sean@sean.io' 4 | license 'Apache-2.0' 5 | description 'installs a buncha junk' 6 | version '0.6.0' 7 | 8 | depends 'docker' 9 | depends 'etcd' 10 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/auto.rb: -------------------------------------------------------------------------------- 1 | ################ 2 | # Docker service 3 | ################ 4 | 5 | docker_service 'default' do 6 | host 'unix:///var/run/docker.sock' 7 | install_method 'auto' 8 | service_manager 'auto' 9 | action [:create, :start] 10 | end 11 | 12 | docker_image 'alpine' do 13 | action :pull 14 | end 15 | 16 | docker_container 'an_echo_server' do 17 | repo 'alpine' 18 | command 'nc -ll -p 7 -e /bin/cat' 19 | port '7:7' 20 | action :run 21 | end 22 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/default.rb: -------------------------------------------------------------------------------- 1 | ################ 2 | # Setting up TLS 3 | ################ 4 | 5 | caname = 'docker_service_default' 6 | caroot = "/ca/#{caname}" 7 | 8 | directory caroot.to_s do 9 | recursive true 10 | action :create 11 | end 12 | 13 | # Self signed CA 14 | bash "#{caname} - generating CA private and public key" do 15 | cmd = 'openssl req' 16 | cmd += ' -x509' 17 | cmd += ' -nodes' 18 | cmd += ' -days 365' 19 | cmd += ' -sha256' 20 | cmd += " -subj '/CN=kitchen2docker/'" 21 | cmd += ' -newkey rsa:4096' 22 | cmd += " -keyout #{caroot}/ca-key.pem" 23 | cmd += " -out #{caroot}/ca.pem" 24 | cmd += ' 2>&1>/dev/null' 25 | code cmd 26 | not_if "/usr/bin/test -f #{caroot}/ca-key.pem" 27 | not_if "/usr/bin/test -f #{caroot}/ca.pem" 28 | action :run 29 | end 30 | 31 | # server certs 32 | bash "#{caname} - creating private key for docker server" do 33 | code "openssl genrsa -out #{caroot}/server-key.pem 4096" 34 | not_if "/usr/bin/test -f #{caroot}/server-key.pem" 35 | action :run 36 | end 37 | 38 | bash "#{caname} - generating certificate request for server" do 39 | cmd = 'openssl req' 40 | cmd += ' -new' 41 | cmd += ' -sha256' 42 | cmd += " -subj '/CN=#{node['hostname']}/'" 43 | cmd += " -key #{caroot}/server-key.pem" 44 | cmd += " -out #{caroot}/server.csr" 45 | code cmd 46 | only_if "/usr/bin/test -f #{caroot}/server-key.pem" 47 | not_if "/usr/bin/test -f #{caroot}/server.csr" 48 | action :run 49 | end 50 | 51 | file "#{caroot}/server-extfile.cnf" do 52 | content "subjectAltName = IP:#{node['ipaddress']},IP:127.0.0.1\n" 53 | action :create 54 | end 55 | 56 | bash "#{caname} - signing request for server" do 57 | cmd = 'openssl x509' 58 | cmd += ' -req' 59 | cmd += ' -days 365' 60 | cmd += ' -sha256' 61 | cmd += " -CA #{caroot}/ca.pem" 62 | cmd += " -CAkey #{caroot}/ca-key.pem" 63 | cmd += ' -CAcreateserial' 64 | cmd += " -in #{caroot}/server.csr" 65 | cmd += " -out #{caroot}/server.pem" 66 | cmd += " -extfile #{caroot}/server-extfile.cnf" 67 | not_if "/usr/bin/test -f #{caroot}/server.pem" 68 | code cmd 69 | action :run 70 | end 71 | 72 | # client certs 73 | bash "#{caname} - creating private key for docker client" do 74 | code "openssl genrsa -out #{caroot}/key.pem 4096" 75 | not_if "/usr/bin/test -f #{caroot}/key.pem" 76 | action :run 77 | end 78 | 79 | bash "#{caname} - generating certificate request for client" do 80 | cmd = 'openssl req' 81 | cmd += ' -new' 82 | cmd += " -subj '/CN=client/'" 83 | cmd += " -key #{caroot}/key.pem" 84 | cmd += " -out #{caroot}/client.csr" 85 | code cmd 86 | only_if "/usr/bin/test -f #{caroot}/key.pem" 87 | not_if "/usr/bin/test -f #{caroot}/client.csr" 88 | action :run 89 | end 90 | 91 | file "#{caroot}/client-extfile.cnf" do 92 | content "extendedKeyUsage = clientAuth\n" 93 | action :create 94 | end 95 | 96 | bash "#{caname} - signing request for client" do 97 | cmd = 'openssl x509' 98 | cmd += ' -req' 99 | cmd += ' -days 365' 100 | cmd += ' -sha256' 101 | cmd += " -CA #{caroot}/ca.pem" 102 | cmd += " -CAkey #{caroot}/ca-key.pem" 103 | cmd += ' -CAcreateserial' 104 | cmd += " -in #{caroot}/client.csr" 105 | cmd += " -out #{caroot}/cert.pem" 106 | cmd += " -extfile #{caroot}/client-extfile.cnf" 107 | code cmd 108 | not_if "/usr/bin/test -f #{caroot}/cert.pem" 109 | action :run 110 | end 111 | 112 | ################ 113 | # Etcd service 114 | ################ 115 | 116 | etcd_service 'etcd0' do 117 | advertise_client_urls "http://#{node['ipaddress']}:2379,http://0.0.0.0:4001" 118 | listen_client_urls 'http://0.0.0.0:2379,http://0.0.0.0:4001' 119 | initial_advertise_peer_urls "http://#{node['ipaddress']}:2380" 120 | listen_peer_urls 'http://0.0.0.0:2380' 121 | initial_cluster_token 'etcd0' 122 | initial_cluster "etcd0=http://#{node['ipaddress']}:2380" 123 | initial_cluster_state 'new' 124 | action [:create, :start] 125 | end 126 | 127 | ################ 128 | # Docker service 129 | ################ 130 | 131 | docker_service 'default' do 132 | host ['unix:///var/run/docker.sock', 'tcp://127.0.0.1:2376'] 133 | version node['docker']['version'] 134 | labels ['environment:test', 'foo:bar'] 135 | tls_verify true 136 | tls_ca_cert "#{caroot}/ca.pem" 137 | tls_server_cert "#{caroot}/server.pem" 138 | tls_server_key "#{caroot}/server-key.pem" 139 | tls_client_cert "#{caroot}/cert.pem" 140 | tls_client_key "#{caroot}/key.pem" 141 | cluster_store "etcd://#{node['ipaddress']}:4001" 142 | cluster_advertise "#{node['ipaddress']}:4001" 143 | install_method 'package' 144 | action [:create, :start] 145 | end 146 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/exec.rb: -------------------------------------------------------------------------------- 1 | docker_image 'busybox' do 2 | action :pull_if_missing 3 | end 4 | 5 | docker_container 'busybox_exec' do 6 | repo 'busybox' 7 | command 'sh -c "trap exit 0 SIGTERM; while :; do sleep 1; done"' 8 | end 9 | 10 | docker_exec 'touch_it' do 11 | container 'busybox_exec' 12 | command ['touch', '/tmp/onefile'] 13 | timeout 120 14 | not_if { ::File.exist?('/marker_busybox_exec_onefile') } 15 | end 16 | 17 | file '/marker_busybox_exec_onefile' 18 | 19 | docker_exec 'poke_it' do 20 | container 'busybox_exec' 21 | cmd ['touch', '/tmp/twofile'] 22 | not_if { ::File.exist?('/marker_busybox_exec_twofile') } 23 | end 24 | 25 | file '/marker_busybox_exec_twofile' 26 | 27 | # TODO(ramereth): Disabling due to https://github.com/sous-chefs/docker/issues/1137 28 | # docker_exec 'default' 29 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/image_prune.rb: -------------------------------------------------------------------------------- 1 | ######################### 2 | # :prune 3 | ######################### 4 | 5 | docker_image_prune 'hello-world' do 6 | dangling true 7 | end 8 | 9 | docker_image_prune 'prune-tagged-unused' do 10 | dangling false 11 | end 12 | 13 | docker_image_prune 'prune-old-images' do 14 | dangling true 15 | prune_until '1h30m' 16 | with_label 'com.example.vendor=ACME' 17 | without_label 'no_prune' 18 | action :prune 19 | end 20 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/install_and_stop.rb: -------------------------------------------------------------------------------- 1 | docker_service 'default' do 2 | action [:create, :stop] 3 | end 4 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/installation_package.rb: -------------------------------------------------------------------------------- 1 | docker_installation_package 'default' do 2 | # version node['docker']['version'] if node['docker']['version'] 3 | end 4 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/installation_script.rb: -------------------------------------------------------------------------------- 1 | docker_installation_script 'default' do 2 | repo node['docker']['repo'] 3 | action :create 4 | end 5 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/installation_tarball.rb: -------------------------------------------------------------------------------- 1 | docker_installation_tarball 'default' do 2 | version '20.10.11' 3 | end 4 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/plugin.rb: -------------------------------------------------------------------------------- 1 | ###################### 2 | # :install and :update 3 | ###################### 4 | 5 | sshfs_caps = [ 6 | { 7 | 'Name' => 'network', 8 | 'Value' => ['host'], 9 | }, 10 | { 11 | 'Name' => 'mount', 12 | 'Value' => ['/var/lib/docker/plugins/'], 13 | }, 14 | { 15 | 'Name' => 'mount', 16 | 'Value' => [''], 17 | }, 18 | { 19 | 'Name' => 'device', 20 | 'Value' => ['/dev/fuse'], 21 | }, 22 | { 23 | 'Name' => 'capabilities', 24 | 'Value' => ['CAP_SYS_ADMIN'], 25 | }, 26 | ] 27 | 28 | docker_plugin 'vieux/sshfs' do 29 | grant_privileges sshfs_caps 30 | end 31 | 32 | docker_plugin 'configure vieux/sshfs' do 33 | action :update 34 | local_alias 'vieux/sshfs' 35 | options( 36 | 'DEBUG' => '1' 37 | ) 38 | end 39 | 40 | docker_plugin 'remove vieux/sshfs' do 41 | local_alias 'vieux/sshfs' 42 | action :remove 43 | end 44 | 45 | ####################### 46 | # :install with options 47 | ####################### 48 | 49 | docker_plugin 'rbd' do 50 | remote 'wetopi/rbd' 51 | remote_tag '1.0.1' 52 | grant_privileges true 53 | options( 54 | 'LOG_LEVEL' => '4' 55 | ) 56 | end 57 | 58 | docker_plugin 'remove rbd' do 59 | local_alias 'rbd' 60 | action :remove 61 | end 62 | 63 | ####################################### 64 | # :install twice (should be idempotent) 65 | ####################################### 66 | 67 | docker_plugin 'sshfs 2.1' do 68 | local_alias 'sshfs' 69 | remote 'vieux/sshfs' 70 | remote_tag 'latest' 71 | grant_privileges true 72 | end 73 | 74 | docker_plugin 'sshfs 2.2' do 75 | local_alias 'sshfs' 76 | remote 'vieux/sshfs' 77 | remote_tag 'latest' 78 | grant_privileges true 79 | end 80 | 81 | docker_plugin 'enable sshfs' do 82 | local_alias 'sshfs' 83 | action :enable 84 | end 85 | 86 | docker_plugin 'disable sshfs' do 87 | local_alias 'sshfs' 88 | action :disable 89 | end 90 | 91 | docker_plugin 'remove sshfs again' do 92 | local_alias 'sshfs' 93 | action :remove 94 | end 95 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/registry.rb: -------------------------------------------------------------------------------- 1 | # We're going to need some SSL certificates for testing. 2 | 3 | caroot = '/tmp/registry/tls' 4 | 5 | directory caroot.to_s do 6 | recursive true 7 | action :create 8 | end 9 | 10 | # Self signed CA 11 | bash 'generating CA private and public key' do 12 | cmd = 'openssl req' 13 | cmd += ' -x509' 14 | cmd += ' -nodes' 15 | cmd += ' -days 365' 16 | cmd += ' -sha256' 17 | cmd += " -subj '/CN=kitchen2docker/'" 18 | cmd += ' -newkey rsa:4096' 19 | cmd += " -keyout #{caroot}/ca-key.pem" 20 | cmd += " -out #{caroot}/ca.pem" 21 | cmd += ' 2>&1>/dev/null' 22 | code cmd 23 | not_if "/usr/bin/test -f #{caroot}/ca-key.pem" 24 | not_if "/usr/bin/test -f #{caroot}/ca.pem" 25 | action :run 26 | end 27 | 28 | # server certs 29 | bash 'creating private key for docker server' do 30 | code "openssl genrsa -out #{caroot}/server-key.pem 4096" 31 | not_if "/usr/bin/test -f #{caroot}/server-key.pem" 32 | action :run 33 | end 34 | 35 | bash 'generating certificate request for server' do 36 | cmd = 'openssl req' 37 | cmd += ' -new' 38 | cmd += ' -sha256' 39 | cmd += " -subj '/CN=#{node['hostname']}/'" 40 | cmd += " -key #{caroot}/server-key.pem" 41 | cmd += " -out #{caroot}/server.csr" 42 | code cmd 43 | not_if "/usr/bin/test -f #{caroot}/server.csr" 44 | action :run 45 | end 46 | 47 | file "#{caroot}/server-extfile.cnf" do 48 | content "subjectAltName = IP:#{node['ipaddress']},IP:127.0.0.1\n" 49 | action :create 50 | end 51 | 52 | bash 'signing request for server' do 53 | cmd = 'openssl x509' 54 | cmd += ' -req' 55 | cmd += ' -days 365' 56 | cmd += ' -sha256' 57 | cmd += " -CA #{caroot}/ca.pem" 58 | cmd += " -CAkey #{caroot}/ca-key.pem" 59 | cmd += ' -CAcreateserial' 60 | cmd += " -in #{caroot}/server.csr" 61 | cmd += " -out #{caroot}/server.pem" 62 | cmd += " -extfile #{caroot}/server-extfile.cnf" 63 | not_if "/usr/bin/test -f #{caroot}/server.pem" 64 | code cmd 65 | action :run 66 | end 67 | 68 | # client certs 69 | bash 'creating private key for docker client' do 70 | code "openssl genrsa -out #{caroot}/key.pem 4096" 71 | not_if "/usr/bin/test -f #{caroot}/key.pem" 72 | action :run 73 | end 74 | 75 | bash 'generating certificate request for client' do 76 | cmd = 'openssl req' 77 | cmd += ' -new' 78 | cmd += " -subj '/CN=client/'" 79 | cmd += " -key #{caroot}/key.pem" 80 | cmd += " -out #{caroot}/client.csr" 81 | code cmd 82 | not_if "/usr/bin/test -f #{caroot}/client.csr" 83 | action :run 84 | end 85 | 86 | file "#{caroot}/client-extfile.cnf" do 87 | content "extendedKeyUsage = clientAuth\n" 88 | action :create 89 | end 90 | 91 | bash 'signing request for client' do 92 | cmd = 'openssl x509' 93 | cmd += ' -req' 94 | cmd += ' -days 365' 95 | cmd += ' -sha256' 96 | cmd += " -CA #{caroot}/ca.pem" 97 | cmd += " -CAkey #{caroot}/ca-key.pem" 98 | cmd += ' -CAcreateserial' 99 | cmd += " -in #{caroot}/client.csr" 100 | cmd += " -out #{caroot}/cert.pem" 101 | cmd += " -extfile #{caroot}/client-extfile.cnf" 102 | code cmd 103 | not_if "/usr/bin/test -f #{caroot}/cert.pem" 104 | action :run 105 | end 106 | 107 | # Set up a test registry to test :push 108 | # https://github.com/docker/distribution/blob/master/docs/authentication.md 109 | # 110 | 111 | docker_image 'nginx' do 112 | tag '1.9' 113 | end 114 | 115 | docker_image 'registry' do 116 | tag '2.6.1' 117 | end 118 | 119 | directory '/tmp/registry/auth' do 120 | recursive true 121 | owner 'root' 122 | mode '0755' 123 | action :create 124 | end 125 | 126 | template '/tmp/registry/auth/registry.conf' do 127 | source 'registry/auth/registry.conf.erb' 128 | owner 'root' 129 | mode '0755' 130 | action :create 131 | end 132 | 133 | # install certificates 134 | execute 'copy server cert for registry' do 135 | command "cp #{caroot}/server.pem /tmp/registry/auth/server.crt" 136 | creates '/tmp/registry/auth/server.crt' 137 | action :run 138 | end 139 | 140 | execute 'copy server key for registry' do 141 | command "cp #{caroot}/server-key.pem /tmp/registry/auth/server.key" 142 | creates '/tmp/registry/auth/server.key' 143 | action :run 144 | end 145 | 146 | # testuser / testpassword 147 | template '/tmp/registry/auth/registry.password' do 148 | source 'registry/auth/registry.password.erb' 149 | owner 'root' 150 | mode '0755' 151 | action :create 152 | end 153 | 154 | bash 'start docker registry' do 155 | code <<-EOF 156 | docker run \ 157 | -d \ 158 | -p 5000:5000 \ 159 | --name registry_service \ 160 | --restart=always \ 161 | registry 162 | EOF 163 | not_if "[ ! -z `docker ps -qaf 'name=registry_service$'` ]" 164 | end 165 | 166 | bash 'start docker registry proxy' do 167 | code <<-EOF 168 | docker run \ 169 | -d \ 170 | -p 5043:443 \ 171 | --name registry_proxy \ 172 | --restart=always \ 173 | -v /tmp/registry/auth/:/etc/nginx/conf.d \ 174 | nginx:1.9 175 | EOF 176 | not_if "[ ! -z `docker ps -qaf 'name=registry_proxy$'` ]" 177 | end 178 | 179 | bash 'wait for docker registry and proxy' do 180 | code <<-EOF 181 | i=0 182 | tries=20 183 | while true; do 184 | ((i++)) 185 | netstat -plnt | grep ":5000" && netstat -plnt | grep ":5043" 186 | [ $? -eq 0 ] && break 187 | [ $i -eq $tries ] && break 188 | sleep 1 189 | done 190 | EOF 191 | not_if 'netstat -plnt | grep ":5000" && netstat -plnt | grep ":5043"' 192 | end 193 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/service.rb: -------------------------------------------------------------------------------- 1 | docker_service 'default' do 2 | storage_driver 'overlay2' 3 | bip '10.10.10.0/24' 4 | default_ip_address_pool 'base=10.10.10.0/16,size=24' 5 | service_manager 'systemd' 6 | action [:create, :start] 7 | end 8 | 9 | docker_service 'one-mirror' do 10 | graph '/var/lib/docker-one' 11 | host 'unix:///var/run/docker-one.sock' 12 | registry_mirror 'https://mirror.gcr.io' 13 | service_manager 'systemd' 14 | action [:create, :start] 15 | end 16 | 17 | docker_service 'two-mirrors' do 18 | graph '/var/lib/docker-two' 19 | host 'unix:///var/run/docker-two.sock' 20 | registry_mirror ['https://mirror.gcr.io', 'https://another.mirror.io'] 21 | service_manager 'systemd' 22 | action [:create, :start] 23 | end 24 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/smoke.rb: -------------------------------------------------------------------------------- 1 | ######################### 2 | # service named 'default' 3 | ######################### 4 | 5 | docker_service 'default' do 6 | graph '/var/lib/docker' 7 | action [:create, :start] 8 | end 9 | 10 | ################ 11 | # simple process 12 | ################ 13 | 14 | docker_image 'busybox' do 15 | host 'unix:///var/run/docker.sock' 16 | end 17 | 18 | docker_container 'service default echo server' do 19 | container_name 'an_echo_server' 20 | repo 'busybox' 21 | command 'nc -ll -p 7 -e /bin/cat' 22 | port '7' 23 | action :run 24 | end 25 | 26 | ##################### 27 | # squid forward proxy 28 | ##################### 29 | 30 | directory '/etc/squid_forward_proxy' do 31 | recursive true 32 | owner 'root' 33 | mode '0755' 34 | action :create 35 | end 36 | 37 | template '/etc/squid_forward_proxy/squid.conf' do 38 | source 'squid_forward_proxy/squid.conf.erb' 39 | owner 'root' 40 | mode '0755' 41 | notifies :redeploy, 'docker_container[squid_forward_proxy]' 42 | action :create 43 | end 44 | 45 | docker_image 'ubuntu/squid' do 46 | tag 'latest' 47 | action :pull 48 | end 49 | 50 | docker_container 'squid_forward_proxy' do 51 | repo 'ubuntu/squid' 52 | tag 'latest' 53 | restart_policy 'on-failure' 54 | kill_after 5 55 | port '3128:3128' 56 | ulimits [ 57 | { 'Name' => 'nofile', 'Soft' => 40_960, 'Hard' => 40_960 }, 58 | ] 59 | volumes '/etc/squid_forward_proxy/squid.conf:/etc/squid/squid.conf' 60 | subscribes :redeploy, 'docker_image[ubuntu/squid]' 61 | action :run 62 | end 63 | 64 | ############# 65 | # service one 66 | ############# 67 | 68 | docker_service 'one' do 69 | graph '/var/lib/docker-one' 70 | host 'unix:///var/run/docker-one.sock' 71 | http_proxy 'http://127.0.0.1:3128' 72 | https_proxy 'http://127.0.0.1:3128' 73 | action :start 74 | end 75 | 76 | docker_image 'hello-world' do 77 | host 'unix:///var/run/docker-one.sock' 78 | tag 'latest' 79 | end 80 | 81 | docker_container 'hello-world' do 82 | host 'unix:///var/run/docker-one.sock' 83 | command '/hello' 84 | action :create 85 | end 86 | 87 | # Test case for digest image format 88 | docker_container 'sha256-test' do 89 | repo 'hello-world' 90 | tag 'sha256:d715f14f9eca81473d9112df50457893aa4d099adeb4729f679006bf5ea12407' 91 | action :run 92 | end 93 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/swarm.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'docker_test::installation_package' 2 | 3 | docker_swarm_init 'initialize swarm' do 4 | advertise_addr node['docker']['swarm']['init']['advertise_addr'] 5 | listen_addr node['docker']['swarm']['init']['listen_addr'] 6 | action :init 7 | end 8 | 9 | # Read or rotate the worker token 10 | docker_swarm_token 'worker' do 11 | rotate node['docker']['swarm']['rotate_token'] if node['docker']['swarm']['rotate_token'] 12 | action node['docker']['swarm']['rotate_token'] ? :rotate : :read 13 | notifies :create, 'ruby_block[save_token]', :immediately 14 | end 15 | 16 | # Save the token to a node attribute for use by workers 17 | ruby_block 'save_token' do 18 | block do 19 | node.override['docker']['swarm']['tokens'] ||= {} 20 | node.override['docker']['swarm']['tokens']['worker'] = node.run_state['docker_swarm']['worker_token'] 21 | end 22 | action :nothing 23 | end 24 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/swarm_service.rb: -------------------------------------------------------------------------------- 1 | # Wait a bit to ensure the swarm is ready 2 | ruby_block 'wait for swarm initialization' do 3 | block do 4 | sleep 10 5 | end 6 | action :run 7 | end 8 | 9 | docker_swarm_service node['docker']['swarm']['service']['name'] do 10 | image node['docker']['swarm']['service']['image'] 11 | ports node['docker']['swarm']['service']['publish'] 12 | replicas node['docker']['swarm']['service']['replicas'] 13 | action :create 14 | end 15 | 16 | # Add a test to verify the service is running 17 | ruby_block 'verify service' do 18 | block do 19 | 20.times do # try for about 1 minute 20 | cmd = Mixlib::ShellOut.new('docker service ls') 21 | cmd.run_command 22 | break if cmd.stdout =~ /#{node['docker']['swarm']['service']['name']}/ 23 | sleep 3 24 | end 25 | end 26 | action :run 27 | end 28 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/swarm_worker.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'test::installation_package' 2 | # We need to get the token from the manager node 3 | # In a real environment, you would use a more secure way to distribute the token 4 | ruby_block 'wait for manager' do 5 | block do 6 | # Simple wait to ensure manager is up 7 | sleep 10 8 | end 9 | action :run 10 | end 11 | 12 | docker_swarm_join 'join swarm' do 13 | advertise_addr node['docker']['swarm']['join']['advertise_addr'] 14 | listen_addr node['docker']['swarm']['join']['listen_addr'] 15 | manager_ip node['docker']['swarm']['join']['manager_ip'] 16 | token node['docker']['swarm']['join']['token'] 17 | action :join 18 | end 19 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/timeout.rb: -------------------------------------------------------------------------------- 1 | # service 2 | include_recipe 'docker_test::default' 3 | 4 | # Build an image that takes longer than two minutes 5 | # (the default read_timeout) to build 6 | # 7 | 8 | docker_image 'centos' 9 | 10 | # Make sure that the image does not exist, to avoid a cache hit 11 | # while building the docker image. This can legitimately fail 12 | # if the image does not exist. 13 | execute 'rmi kkeane/image.4' do 14 | command 'docker rmi kkeane/image.4:chef' 15 | ignore_failure true 16 | action :run 17 | end 18 | 19 | directory '/usr/local/src/container4' do 20 | action :create 21 | end 22 | 23 | cookbook_file '/usr/local/src/container4/Dockerfile' do 24 | source 'Dockerfile_4' 25 | action :create 26 | end 27 | 28 | docker_image 'timeout test image' do 29 | repo 'kkeane/image.4' 30 | read_timeout 3600 # 1 hour 31 | write_timeout 3600 # 1 hour 32 | tag 'chef' 33 | source '/usr/local/src/container4' 34 | action :build_if_missing 35 | end 36 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/volume.rb: -------------------------------------------------------------------------------- 1 | ########### 2 | # remove_me 3 | ########### 4 | 5 | execute 'docker volume create --name remove_me' do 6 | not_if { ::File.exist?('/marker_remove_me') } 7 | action :run 8 | end 9 | 10 | file '/marker_remove_me' do 11 | action :create 12 | end 13 | 14 | docker_volume 'remove_me' do 15 | action :remove 16 | end 17 | 18 | ####### 19 | # hello 20 | ####### 21 | 22 | docker_volume 'hello' do 23 | action :create 24 | end 25 | 26 | docker_volume 'hello again' do 27 | volume_name 'hello_again' 28 | action :create 29 | end 30 | 31 | ################## 32 | # hello containers 33 | ################## 34 | 35 | docker_image 'alpine' do 36 | tag '3.1' 37 | action :pull_if_missing 38 | end 39 | 40 | docker_container 'file_writer' do 41 | repo 'alpine' 42 | tag '3.1' 43 | volumes ['hello:/hello'] 44 | command 'touch /hello/sean_was_here' 45 | action :run_if_missing 46 | end 47 | 48 | docker_container 'file_reader' do 49 | repo 'alpine' 50 | tag '3.1' 51 | volumes ['hello:/hello'] 52 | command 'ls /hello/sean_was_here' 53 | action :run_if_missing 54 | end 55 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/recipes/volume_prune.rb: -------------------------------------------------------------------------------- 1 | ######################### 2 | # :prune 3 | ######################### 4 | 5 | docker_volume_prune 'all' do 6 | all true 7 | end 8 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/templates/nginx_forward_proxy/proxy.conf.erb: -------------------------------------------------------------------------------- 1 | server { 2 | resolver 8.8.8.8; 3 | listen 8080; 4 | location / { 5 | proxy_pass http://$http_host$request_uri; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/templates/registry/auth/registry.conf.erb: -------------------------------------------------------------------------------- 1 | upstream docker-registry { 2 | server <%= node['ipaddress'] %>:5000; 3 | } 4 | 5 | server { 6 | listen 443 ssl; 7 | server_name <%= node['ipaddress'] %>; 8 | 9 | # Disable SSL for testing registry 10 | ssl_certificate /etc/nginx/conf.d/server.crt; 11 | ssl_certificate_key /etc/nginx/conf.d/server.key; 12 | 13 | # disable any limits to avoid HTTP 413 for large image uploads 14 | client_max_body_size 0; 15 | 16 | # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486) 17 | chunked_transfer_encoding on; 18 | 19 | location /v2/ { 20 | # Do not allow connections from docker 1.5 and earlier 21 | # docker pre-1.6.0 did not properly set the user agent on ping, catch "Go *" user agents 22 | if ($http_user_agent ~* "^(docker\/1\.(3|4|5(?!\.[0-9]-dev))|Go ).*\$" ) { 23 | return 404; 24 | } 25 | 26 | # To add basic authentication to v2 use auth_basic setting plus add_header 27 | auth_basic "registry.localhost"; 28 | auth_basic_user_file /etc/nginx/conf.d/registry.password; 29 | add_header 'Docker-Distribution-Api-Version' 'registry/2.0' always; 30 | 31 | proxy_pass http://docker-registry; 32 | proxy_set_header Host $http_host; # required for docker client's sake 33 | proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP 34 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 35 | proxy_set_header X-Forwarded-Proto $scheme; 36 | proxy_read_timeout 900; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/templates/registry/auth/registry.password.erb: -------------------------------------------------------------------------------- 1 | testuser:$apr1$TPsqBp55$icazbv6goXik2yJVSlp7l1 2 | -------------------------------------------------------------------------------- /test/cookbooks/docker_test/templates/squid_forward_proxy/squid.conf.erb: -------------------------------------------------------------------------------- 1 | http_port 3128 2 | 3 | acl localnet src 10.0.0.0/8 # RFC1918 possible internal network 4 | acl localnet src 172.16.0.0/12 # RFC1918 possible internal network 5 | acl localnet src 192.168.0.0/16 # RFC1918 possible internal network 6 | acl localnet src fc00::/7 # RFC 4193 local private network range 7 | acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines 8 | 9 | acl SSL_ports port 443 10 | 11 | acl Safe_ports port 80 # http 12 | acl Safe_ports port 21 # ftp 13 | acl Safe_ports port 443 # https 14 | acl Safe_ports port 70 # gopher 15 | acl Safe_ports port 210 # wais 16 | acl Safe_ports port 280 # http-mgmt 17 | acl Safe_ports port 488 # gss-http 18 | acl Safe_ports port 591 # filemaker 19 | acl Safe_ports port 777 # multiling http 20 | acl Safe_ports port 1025-65535 # unregistered ports 21 | 22 | acl CONNECT method CONNECT 23 | 24 | http_access deny !Safe_ports 25 | http_access deny CONNECT !SSL_ports 26 | http_access allow localhost manager 27 | http_access deny manager 28 | 29 | # 30 | # INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS 31 | # 32 | 33 | http_access allow localnet 34 | http_access allow localhost 35 | http_access deny all 36 | 37 | coredump_dir /squid/var/cache/squid 38 | 39 | refresh_pattern ^ftp: 1440 20% 10080 40 | refresh_pattern ^gopher: 1440 0% 1440 41 | refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 42 | refresh_pattern . 0 20% 4320 43 | 44 | # access_log /dev/stdout 45 | -------------------------------------------------------------------------------- /test/integration/install_and_stop/inspec/assert_functioning_spec.rb: -------------------------------------------------------------------------------- 1 | describe command('/usr/bin/docker --version') do 2 | its(:exit_status) { should eq 0 } 3 | its('stdout') { should match(/2[0-9].[0-9]/) } 4 | end 5 | 6 | # NOTE: See https://github.com/sous-chefs/docker/pull/1194 7 | describe service('docker.service') do 8 | it { should be_installed } 9 | it { should_not be_running } 10 | it { should_not be_enabled } 11 | end 12 | 13 | describe service('docker.socket') do 14 | it { should be_installed } 15 | it { should_not be_running } 16 | it { should_not be_enabled } 17 | end 18 | -------------------------------------------------------------------------------- /test/integration/installation_package/inspec/assert_functioning_spec.rb: -------------------------------------------------------------------------------- 1 | describe command('/usr/bin/docker --version') do 2 | its(:exit_status) { should eq 0 } 3 | its(:stdout) { should match(/2[0-9]\.[0-9]+\./) } 4 | end 5 | -------------------------------------------------------------------------------- /test/integration/installation_script_experimental/inspec/assert_functioning_spec.rb: -------------------------------------------------------------------------------- 1 | describe command('/usr/bin/docker --version') do 2 | its(:exit_status) { should eq 0 } 3 | end 4 | -------------------------------------------------------------------------------- /test/integration/installation_script_main/inspec/assert_functioning_spec.rb: -------------------------------------------------------------------------------- 1 | describe command('/usr/bin/docker --version') do 2 | its(:exit_status) { should eq 0 } 3 | end 4 | -------------------------------------------------------------------------------- /test/integration/installation_script_test/inspec/assert_functioning_spec.rb: -------------------------------------------------------------------------------- 1 | describe command('/usr/bin/docker --version') do 2 | its(:exit_status) { should eq 0 } 3 | end 4 | -------------------------------------------------------------------------------- /test/integration/installation_tarball/inspec/assert_functioning_spec.rb: -------------------------------------------------------------------------------- 1 | describe command '/usr/bin/docker --version' do 2 | its(:exit_status) { should eq 0 } 3 | its(:stdout) { should match(/20.10.11/) } 4 | end 5 | 6 | describe group 'docker' do 7 | it { should exist } 8 | its('gid') { should < 1000 } 9 | end 10 | -------------------------------------------------------------------------------- /test/integration/smoke/inspec/assert_functioning_spec.rb: -------------------------------------------------------------------------------- 1 | # service named 'default' 2 | describe command('docker images') do 3 | its(:exit_status) { should eq 0 } 4 | its(:stdout) { should match(/busybox/) } 5 | end 6 | 7 | describe command('docker ps -a') do 8 | its(:exit_status) { should eq 0 } 9 | its(:stdout) { should match(/an_echo_server/) } 10 | end 11 | 12 | # service one 13 | describe command('docker --host=unix:///var/run/docker-one.sock images') do 14 | its(:exit_status) { should eq 0 } 15 | its(:stdout) { should match(/^hello-world/) } 16 | its(:stdout) { should_not match(/^alpine/) } 17 | end 18 | 19 | describe command('docker --host=unix:///var/run/docker-one.sock ps -a') do 20 | its(:exit_status) { should eq 0 } 21 | its(:stdout) { should match(/hello-world/) } 22 | its(:stdout) { should_not match(/an_echo_server/) } 23 | end 24 | 25 | if os.family == 'redhat' && os.release.to_i < 8 26 | # verify if docker-ce install was not changed 27 | describe command("rpm -V docker-ce | grep '^..5'") do 28 | its(:exit_status) { should eq 1 } 29 | its(:stdout) { should eq '' } 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/integration/swarm/inspec/swarm_test.rb: -------------------------------------------------------------------------------- 1 | control 'docker-swarm-1' do 2 | impact 1.0 3 | title 'Docker Swarm Installation' 4 | desc 'Verify Docker is installed and Swarm mode is active' 5 | 6 | describe command('docker --version') do 7 | its('exit_status') { should eq 0 } 8 | its('stdout') { should match(/Docker version/) } 9 | end 10 | 11 | describe command('docker info --format "{{ .Swarm.LocalNodeState }}"') do 12 | its('exit_status') { should eq 0 } 13 | its('stdout') { should match(/active/) } 14 | end 15 | 16 | describe command('docker info --format "{{ .Swarm.ControlAvailable }}"') do 17 | its('exit_status') { should eq 0 } 18 | its('stdout') { should match(/true/) } 19 | end 20 | end 21 | 22 | control 'docker-swarm-2' do 23 | impact 1.0 24 | title 'Docker Swarm Service' 25 | desc 'Verify the test service is running correctly' 26 | 27 | describe command('docker service ls --format "{{.Name}}"') do 28 | its('exit_status') { should eq 0 } 29 | its('stdout') { should match(/web/) } 30 | end 31 | 32 | describe command('docker service inspect web') do 33 | its('exit_status') { should eq 0 } 34 | its('stdout') { should match(/"Replicas":\s*2/) } 35 | 36 | describe json(content: command('docker service inspect web').stdout) do 37 | its([0, 'Spec', 'TaskTemplate', 'ContainerSpec', 'Image']) { should match(/^nginx:latest(@sha256:)?/) } 38 | end 39 | end 40 | 41 | describe command('docker service ps web --format "{{.CurrentState}}"') do 42 | its('exit_status') { should eq 0 } 43 | its('stdout') { should match(/Running/) } 44 | end 45 | end 46 | 47 | control 'docker-swarm-3' do 48 | impact 1.0 49 | title 'Docker Swarm Network' 50 | desc 'Verify swarm networking is configured correctly' 51 | 52 | describe command('docker network ls --filter driver=overlay --format "{{.Name}}"') do 53 | its('exit_status') { should eq 0 } 54 | its('stdout') { should match(/ingress/) } 55 | end 56 | 57 | describe port(2377) do 58 | it { should be_listening } 59 | its('protocols') { should include 'tcp' } 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/integration/volume/inspec/assert_functioning_spec.rb: -------------------------------------------------------------------------------- 1 | ########### 2 | # reference 3 | ########### 4 | 5 | # https://docs.docker.com/engine/reference/commandline/volume_create/ 6 | 7 | ########### 8 | # remove_me 9 | ########### 10 | 11 | describe command('docker volume ls -q') do 12 | its(:exit_status) { should eq 0 } 13 | its(:stdout) { should_not match(/^remove_me$/) } 14 | end 15 | 16 | ####### 17 | # hello 18 | ####### 19 | 20 | describe command('docker volume ls -q') do 21 | its(:exit_status) { should eq 0 } 22 | its(:stdout) { should match(/^hello$/) } 23 | its(:stdout) { should match(/^hello_again$/) } 24 | end 25 | 26 | ################## 27 | # hello containers 28 | ################## 29 | 30 | describe command("docker ps -qaf 'name=file_writer$'") do 31 | its(:exit_status) { should eq 0 } 32 | its(:stdout) { should_not be_empty } 33 | end 34 | 35 | describe command("docker ps -qaf 'name=file_reader$'") do 36 | its(:exit_status) { should eq 0 } 37 | its(:stdout) { should_not be_empty } 38 | end 39 | 40 | describe command('docker logs file_reader') do 41 | its(:exit_status) { should eq 0 } 42 | its(:stdout) { should match(%r{/hello/sean_was_here}) } 43 | end 44 | --------------------------------------------------------------------------------