├── .gitignore ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── example ├── README.md ├── Vagrantfile ├── deploy.py └── tests.py ├── pyinfra_docker ├── __init__.py └── docker.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | build/ 4 | dist/ 5 | venv/ 6 | .*swp 7 | pyinfra-debug.log 8 | 9 | .vagrant* 10 | 11 | example/ssh_config 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Nick Barrett 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyinfra Docker 2 | 3 | A basic [pyinfra](https://pyinfra.com) deploy that installs and optionally configures Docker on the target hosts. Officially tested & supported Linux distributions: 4 | 5 | + Ubuntu 16/18/20 6 | + Debian 8/9/10 7 | + CentOS 7/8 8 | 9 | This deploy installs packages in the `docker-ce` ecosystem (`docker-ce`/`docker-ce-cli`/`docker-ce-rootless-extras`) You can specify `docker_version` in the host data and it will install that version for all `docker-ce` packages. 10 | 11 | ## Usage 12 | 13 | See [the example](https://github.com/Fizzadar/pyinfra-docker/tree/master/example) for a more complete example. 14 | 15 | ```py 16 | from pyinfra_docker import deploy_docker 17 | deploy_docker() 18 | ``` 19 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # pyinfra-docker Example 2 | 3 | See [`deploy.py`](./deploy.py) for example code to use `pyinfra-docker`. 4 | 5 | ## Quickstart 6 | 7 | You will need [Vagrant](https://vagrantup.com) installed. 8 | 9 | ```sh 10 | # Bring up the virtual machine instances 11 | vagrant up 12 | 13 | # Install pyinfra-docker + pyinfra dependency 14 | pip install -e '../' 15 | 16 | # Deploy docker to each virtual machine 17 | pyinfra @vagrant deploy.py 18 | ``` 19 | 20 | ## Tests 21 | 22 | ```sh 23 | # Ensure we have test packages installed 24 | pip install -e '../[test]' 25 | 26 | # Dump vagrant SSH config 27 | vagrant ssh-config > ssh_config 28 | 29 | # Run pytest 30 | pytest --ssh-config ssh_config tests.py 31 | ``` 32 | -------------------------------------------------------------------------------- /example/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure('2') do |config| 2 | # Disable /vagrant synced folder 3 | config.vm.synced_folder '.', '/vagrant', disabled: true 4 | 5 | config.vm.define :ubuntu16 do |ubuntu| 6 | ubuntu.vm.box = 'bento/ubuntu-16.04' 7 | end 8 | 9 | config.vm.define :ubuntu18 do |ubuntu| 10 | ubuntu.vm.box = 'bento/ubuntu-18.04' 11 | end 12 | 13 | config.vm.define :ubuntu20 do |ubuntu| 14 | ubuntu.vm.box = 'bento/ubuntu-20.04' 15 | end 16 | 17 | config.vm.define :debian9 do |debian| 18 | debian.vm.box = 'bento/debian-9.11' 19 | end 20 | 21 | config.vm.define :debian10 do |debian| 22 | debian.vm.box = 'bento/debian-10' 23 | end 24 | 25 | config.vm.define :centos7 do |centos| 26 | centos.vm.box = 'bento/centos-7' 27 | end 28 | 29 | config.vm.define :almalinux8 do |almalinux| 30 | almalinux.vm.box = 'bento/almalinux-8' 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /example/deploy.py: -------------------------------------------------------------------------------- 1 | from pyinfra import config 2 | from pyinfra.operations import server 3 | 4 | from pyinfra_docker import deploy_docker 5 | 6 | config.SUDO = True 7 | 8 | 9 | deploy_docker( 10 | config={ 11 | "dns": ["8.8.8.8", "8.8.4.4"], 12 | "debug": True, 13 | }, 14 | ) 15 | 16 | 17 | server.service( 18 | name="Ensure docker service is running", 19 | service="docker", 20 | running=True, 21 | enabled=True, 22 | ) 23 | -------------------------------------------------------------------------------- /example/tests.py: -------------------------------------------------------------------------------- 1 | testinfra_hosts = [ 2 | "ssh://ubuntu16", 3 | "ssh://ubuntu18", 4 | "ssh://ubuntu20", 5 | "ssh://debian9", 6 | "ssh://debian10", 7 | "ssh://centos7", 8 | "ssh://almalinux8", 9 | ] 10 | 11 | 12 | def test_docker_running_and_enabled(host): 13 | docker = host.service("docker") 14 | assert docker.is_running 15 | assert docker.is_enabled 16 | -------------------------------------------------------------------------------- /pyinfra_docker/__init__.py: -------------------------------------------------------------------------------- 1 | from .docker import deploy_docker # noqa 2 | -------------------------------------------------------------------------------- /pyinfra_docker/docker.py: -------------------------------------------------------------------------------- 1 | import json 2 | from io import StringIO 3 | 4 | from pyinfra import host 5 | from pyinfra.api.deploy import deploy 6 | from pyinfra.api.exceptions import DeployError 7 | from pyinfra.api.util import make_hash 8 | from pyinfra.facts.deb import DebPackages 9 | from pyinfra.facts.rpm import RpmPackages 10 | from pyinfra.facts.server import Command, LinuxDistribution, LsbRelease, Which 11 | from pyinfra.operations import apt, dnf, files, yum 12 | 13 | 14 | DEFAULTS = { 15 | "docker_version": None, 16 | } 17 | 18 | 19 | def get_pkgs_to_install(operator): 20 | docker_packages = [ 21 | "docker-ce", 22 | "docker-ce-cli", 23 | "docker-ce-rootless-extras", 24 | ] 25 | if not host.data.docker_version: 26 | return docker_packages 27 | 28 | return [f"{pkg}{operator}{host.data.docker_version}" for pkg in docker_packages] 29 | 30 | 31 | 32 | def _apt_install(packages): 33 | apt.packages( 34 | name="Install apt requirements to use HTTPS", 35 | packages=["apt-transport-https", "ca-certificates"], 36 | update=True, 37 | cache_time=3600, 38 | ) 39 | 40 | lsb_release = host.get_fact(LsbRelease) 41 | lsb_id = lsb_release["id"].lower() 42 | 43 | apt.key( 44 | name="Download the Docker apt key", 45 | src="https://download.docker.com/linux/{0}/gpg".format(lsb_id), 46 | ) 47 | 48 | dpkg_arch = host.get_fact(Command, command="dpkg --print-architecture") 49 | 50 | add_apt_repo = apt.repo( 51 | name="Add the Docker apt repo", 52 | src=( 53 | f"deb [arch={dpkg_arch}] https://download.docker.com/linux/{lsb_id}" 54 | f" {lsb_release['codename']} stable" 55 | ), 56 | filename="docker-ce-stable", 57 | ) 58 | 59 | apt.packages( 60 | name="Install Docker via apt", 61 | packages=packages, 62 | update=add_apt_repo.changed, # update if we added the repo 63 | allow_downgrades=True, 64 | ) 65 | 66 | 67 | def _yum_or_dnf_install(yum_or_dnf, packages): 68 | yum_or_dnf.repo( 69 | name="Add the Docker yum repo", 70 | src="https://download.docker.com/linux/centos/docker-ce.repo", 71 | ) 72 | 73 | # Installing Docker on CentOS 8 is currently broken and requires this hack 74 | # See: https://github.com/docker/for-linux/issues/873 75 | extra_install_args = "" 76 | linux_distro = host.get_fact(LinuxDistribution) 77 | if linux_distro["name"] == "CentOS" and linux_distro["major"] == 8: 78 | extra_install_args = "--nobest" 79 | 80 | yum_or_dnf.packages( 81 | name="Install Docker via yum", 82 | packages=packages, 83 | extra_install_args=extra_install_args, 84 | ) 85 | 86 | 87 | @deploy("Deploy Docker", data_defaults=DEFAULTS) 88 | def deploy_docker(config=None): 89 | """ 90 | Install Docker on the target machine. 91 | 92 | Args: 93 | config: filename or dict of JSON data 94 | """ 95 | 96 | if host.get_fact(DebPackages): 97 | packages = get_pkgs_to_install('=') 98 | _apt_install(packages) 99 | elif host.get_fact(RpmPackages): 100 | packages = get_pkgs_to_install('-') 101 | _yum_or_dnf_install( 102 | dnf if host.get_fact(Which, command="dnf") else yum, 103 | packages, 104 | ) 105 | else: 106 | raise DeployError( 107 | ( 108 | "Neither apt or yum were found, " 109 | "pyinfra-docker cannot provision this machine!" 110 | ), 111 | ) 112 | 113 | config_file = config 114 | 115 | # If config is a dictionary, turn it into a JSON file for the config 116 | if isinstance(config, dict): 117 | config_hash = make_hash(config) 118 | 119 | # Turn into a file-like object and name such that we only generate one 120 | # operation hash between multiple hosts (with the same config). 121 | config_file = StringIO(json.dumps(config, indent=4)) 122 | config_file.__name__ = config_hash 123 | 124 | if config: 125 | files.put( 126 | name="Upload the Docker daemon.json", 127 | src=config_file, 128 | dest="/etc/docker/daemon.json", 129 | ) 130 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [flake8] 5 | max-line-length = 100 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from io import open 2 | 3 | from setuptools import find_packages, setup 4 | 5 | with open("README.md", "r", encoding="utf-8") as f: 6 | readme = f.read() 7 | 8 | if __name__ == "__main__": 9 | setup( 10 | version="2.1", 11 | name="pyinfra-docker", 12 | description="Install & configure Docker with `pyinfra`.", 13 | long_description=readme, 14 | long_description_content_type="text/markdown", 15 | url="https://github.com/Fizzadar/pyinfra-docker", 16 | author="Nick / Fizzadar", 17 | author_email="nick@fizzadar.com", 18 | license="MIT", 19 | packages=find_packages(), 20 | install_requires=("pyinfra>=2,<3",), 21 | extras_require={ 22 | "test": [ 23 | "pytest", 24 | "pytest-testinfra", 25 | ], 26 | "lint": [ 27 | "black", 28 | "isort", 29 | "flake8", 30 | "flake8-black", 31 | "flake8-isort", 32 | "flake8-commas", 33 | ], 34 | }, 35 | include_package_data=True, 36 | ) 37 | --------------------------------------------------------------------------------