Repository: iamluc/docker-hostmanager Branch: master Commit: 256cd174b32d Files: 18 Total size: 19.2 KB Directory structure: gitextract_xtg16mqf/ ├── .dockerignore ├── .github/ │ └── workflows/ │ └── DockerHub.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENCE ├── README.md ├── bin/ │ └── docker-hostmanager ├── box.json ├── composer.json ├── phpunit.xml.dist ├── src/ │ ├── Command/ │ │ └── SynchronizeHostsCommand.php │ ├── Docker/ │ │ ├── Docker.php │ │ └── Event.php │ └── Synchronizer.php └── tests/ ├── DockerHostManager/ │ └── SynchronizerTest.php ├── Utils/ │ └── PropertyAccessor.php └── bootstrap.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .* vendor/ ================================================ FILE: .github/workflows/DockerHub.yml ================================================ name: Build and Push Docker images to Docker Hub on: push jobs: build_job: name: Build and push runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Run Buildx and push image run: | docker buildx create --use --name multi-arch-builder --platform "linux/arm64,linux/amd64" docker buildx build --platform "linux/arm64,linux/amd64" --tag ${{ secrets.DOCKERHUB_USERNAME }}/docker-hostmanager:latest --file Dockerfile --output type=image,push=true . ================================================ FILE: .gitignore ================================================ /vendor/ /bin/docker-hostmanager.phar .idea/ ================================================ FILE: .travis.yml ================================================ language: php sudo: false php: 5.6 cache: directories: - vendor - $HOME/.composer/cache install: composer install --prefer-source script: vendor/bin/phpunit --configuration phpunit.xml.dist before_deploy: - curl -LSs http://box-project.github.io/box2/installer.php | php - php box.phar build deploy: provider: releases skip_cleanup: true api_key: secure: "xYPmNWQNTRqLSQinnM64dRqDGAm6bKYODFmGdVLwjNqbICMHR0/sF00EPwBgIsXF100xbnjgJVRXD7lbL7nUYXWyzR2EcaW8yOxhYC0BYQMpOGI6RF0p+sMskCI5hb8Y+FT++nQZTwHPo9MtrAv2ec5r42RO2K0YM6WS6URsCqbTQnDtWcLReszRrLGVy41tkdlse9uqk63IbJmRwLunMkQBJ/BhVSRDl5Qm+Q3aDhjckZanX4QH0UrR75azut3CUIQ1l/wVF9dhKPHuvIc5+3qwkxqgOmaBFozE2hvlviWCunQsZMpaWG9L3v19VzuvypDvvvK+rhwytXsOO2gz0JGh/AL6TsonGqePYdESE7tBZ+sJz5tZ0q0yqEOLGSlxa7i5bF3KN3PCqK8eBdgBHWWDnWgO0blmPFKLYaehxZqnDHr8w5bHlW2yS1fYq8X5zkmz1fbkjpPFXX6TWsm8imlKsqzhSPBTrF+E/6f91TOLlv7tXIA0hi7Ex4ZOuzUCSs6qYWfPYPKWnguL8kmkG9wKnFahQwxVz2CM2ZPxhNQ8j03ao+wkBr6+pt6KhghqYJQ83c5GrzDXJWXbpuNdFt+RfPVLT0w17tWj3H80b/QZFa2TKQtSZ41jnAKPHHt3m178s3DfpQ6Hf1Zi1kvru0jiGKdph94j7zcYeB7uV3A=" file: bin/docker-hostmanager.phar on: tags: true ================================================ FILE: Dockerfile ================================================ FROM composer:1.4.3 ADD . /usr/local/src/docker-hostmanager RUN composer install --no-interaction --no-dev --prefer-dist --working-dir=/usr/local/src/docker-hostmanager \ && ln -s /usr/local/src/docker-hostmanager/bin/docker-hostmanager /usr/local/bin/docker-hostmanager ENV HOSTS_FILE=/hosts ENTRYPOINT ["/usr/local/bin/docker-hostmanager"] ================================================ FILE: LICENCE ================================================ Copyright 2016 Luc Vieillescazes 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ================================================ FILE: README.md ================================================ docker-hostmanager ================== ### ABOUT Update automatically your `/etc/hosts` to access running containers. Inspired by `vagrant-hostmanager`. Project homepage: [https://github.com/iamluc/docker-hostmanager](https://github.com/iamluc/docker-hostmanager) ### USAGE #### Linux The easiest way is to use the docker image ```console $ docker run -d --name docker-hostmanager --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /etc/hosts:/hosts iamluc/docker-hostmanager ``` *Note: the `--restart=always` option will make the container start automatically with your computer (recommended).* #### Mac OS Download the PHAR executable here : https://github.com/iamluc/docker-hostmanager/releases And then run it: ```console $ sudo php docker-hostmanager.phar synchronize-hosts ``` Note: We run the command as root as we need the permission to write file `/etc/hosts`. If you don't want to run the command as root, grant the correct permission to you user. Before running the command, don't forget to export your docker environment variables. i.e. ``` $ eval $(docker-machine env mybox) ``` Also, you should add a route to access containers inside your VM. ``` $ sudo route -n add 172.0.0.0/8 $(docker-machine ip $(docker-machine active)) ``` #### Windows If the host, dont use Docker ToolBox or not a Windows 10 PRO, then needs to mount the /c/Windows folder onto VirtualBox. ```console $ docker run -d --name docker-hostmanager --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /c/Windows/System32/drivers/etc/hosts:/hosts iamluc/docker-hostmanager ``` After run the container we need to add a route to access container subnets. ``` $ route /P add 172.17.0.0/12 192.168.99.100 ``` ### CONFIGURATION #### With networks When a container belongs to at least one network (typically when using a `docker-compose.yml` file in version >= 2), the name defined to access the container is `CONTAINER_NAME.CONTAINER_NETWORK`. It works also with the alias defined for the network. As a container can belongs to several networks at the same time, and thanks to alias, you can define how you want to access your container. **Example 1 (default network):** ```yaml version: '2' services: web: image: iamluc/symfony volumes: - .:/var/www/html ``` The container `web` will be accessible with `web.myapp_default` (if the docker-compose project name is `myapp`) **Example 2 (custom network name and alias):** ```yaml version: '3.5' networks: default: name: myapp services: web: image: iamluc/symfony volumes: - .:/var/www/html mysql: image: mysql networks: default: aliases: - bdd ``` The `web` container will be accessible with `web.myapp`. The `mysql` container will be accessible with `mysql.myapp` or `bdd.myapp` #### Without networks When a container has no defined network (only the default "bridge" one), it is accessible by its container name, concatened with the defined TLD (`.docker` by default). It is the case when you run a single container with the `docker` command or when you use a `docker-compose.yml` file in version 1. The `DOMAIN_NAME` environment variable lets you define additional hosts for your container. e.g.: ``` $ docker run -d -e DOMAIN_NAME=test.com,www.test.com my_image ``` ### Tests To run test, execute the following command : `vendor/bin/phpunit` ### LICENSE [MIT](https://opensource.org/licenses/MIT) ================================================ FILE: bin/docker-hostmanager ================================================ #!/usr/bin/env php add(new SynchronizeHostsCommand()); $application->setDefaultCommand('synchronize-hosts'); $application->run(); ================================================ FILE: box.json ================================================ { "main": "bin/docker-hostmanager", "output": "bin/docker-hostmanager.phar", "finder": [ { "name": ["*.php"], "exclude": ["Tests", "tests"], "in": ["src", "vendor"] } ], "chmod": "0755", "stub": true, "git-version": "package_version" } ================================================ FILE: composer.json ================================================ { "name": "iamluc/docker-hostmanager", "license": "MIT", "version": "0.0.4", "type": "project", "description": "Update /etc/hosts to access running containers", "keywords": ["docker", "hosts"], "repositories": [ {"type": "vcs", "url": "https://github.com/iamluc/docker-php.git"} ], "require": { "symfony/console": "^2.8|^3.0", "docker-php/docker-php": "dev-compat-docker-1.12" }, "require-dev": { "phpunit/phpunit": "^5.1" }, "bin": [ "bin/docker-hostmanager" ], "authors": [ { "name": "Luc Vieillescazes", "email": "luc@vieillescazes.net" } ], "autoload": { "psr-4": {"DockerHostManager\\": "src/", "Test\\": "tests"} } } ================================================ FILE: phpunit.xml.dist ================================================ tests ================================================ FILE: src/Command/SynchronizeHostsCommand.php ================================================ setName('synchronize-hosts') ->setDescription('Run the application') ->addOption( 'hosts_file', 'f', InputOption::VALUE_REQUIRED, 'The host file to update', getenv('HOSTS_FILE') ?: '/etc/hosts' ) ->addOption( 'tld', 't', InputOption::VALUE_REQUIRED, 'The TLD to use', getenv('TLD') ?: '.docker' ) ; } protected function execute(InputInterface $input, OutputInterface $output) { $app = new Synchronizer( new Docker(), $input->getOption('hosts_file'), $input->getOption('tld') ); $app->run(); } } ================================================ FILE: src/Docker/Docker.php ================================================ httpClient = $httpClient ?: DockerClient::createFromEnv(); $this->messageFactory = $messageFactory ?: new GuzzleMessageFactory(); parent::__construct($this->httpClient, $serializer, $this->messageFactory); } /** * @param callable $callback */ public function listenEvents(callable $callback) { $request = $this->messageFactory->createRequest('GET', '/events'); $response = $this->httpClient->sendRequest($request); $stream = $response->getBody(); while (!$stream->eof()) { $line = \GuzzleHttp\Psr7\readline($stream); if (null !== ($raw = json_decode($line, true))) { call_user_func($callback, new Event($raw)); } } } } ================================================ FILE: src/Docker/Event.php ================================================ status = $raw['status'] ?? $raw['Action'] ?? null; $this->id = $raw['id'] ?? $raw['Actor']['ID'] ?? null; $this->from = $raw['from'] ?? $raw['Actor']['Attributes']['image'] ?? null; $this->time = $raw['time'] ?? null; } /** * @return string */ public function getStatus() { return $this->status; } /** * @return string */ public function getId() { return $this->id; } /** * @return string */ public function getFrom() { return $this->from; } /** * @return string */ public function getTime() { return $this->time; } } ================================================ FILE: src/Synchronizer.php ================================================ docker = $docker; $this->hostsFile = $hostsFile; $this->tld = $tld; } public function run() { if (!is_writable($this->hostsFile)) { throw new \RuntimeException(sprintf('File "%s" is not writable.', $this->hostsFile)); } $this->init(); $this->listen(); } private function init() { foreach ($this->docker->getContainerManager()->findAll() as $containerConfig) { $response = $this->docker->getContainerManager()->find($containerConfig->getId(), [], ContainerManager::FETCH_RESPONSE); $container = json_decode(\GuzzleHttp\Psr7\copy_to_string($response->getBody()), true); if ($this->isExposed($container)) { $this->activeContainers[$container['Id']] = $container; } } $this->write(); } private function listen() { $this->docker->listenEvents(function (Event $event) { if (null === $event->getId()) { return; } try { $response = $this->docker->getContainerManager()->find($event->getId(), [], ContainerManager::FETCH_RESPONSE); $container = json_decode(\GuzzleHttp\Psr7\copy_to_string($response->getBody()), true); } catch (\Exception $e) { return; } if (null === $container) { return; } if ($this->isExposed($container)) { $this->activeContainers[$container['Id']] = $container; } else { unset($this->activeContainers[$container['Id']]); } $this->write(); }); } private function write() { $content = array_map('trim', file($this->hostsFile)); $res = preg_grep('/^'.self::START_TAG.'/', $content); $start = count($res) ? key($res) : count($content) + 1; $res = preg_grep('/^'.self::END_TAG.'/', $content); $end = count($res) ? key($res) : count($content) + 1; $hosts = array_merge( [self::START_TAG], array_map( function ($container) { return implode("\n", $this->getHostsLines($container)); }, $this->activeContainers ), [self::END_TAG] ); array_splice($content, $start, $end - $start + 1, $hosts); file_put_contents($this->hostsFile, implode("\n", $content)); } /** * @param $container * * @return array */ private function getHostsLines($container) { $lines = []; // Global if (!empty($container['NetworkSettings']['IPAddress'])) { $ip = $container['NetworkSettings']['IPAddress']; $lines[$ip] = implode(' ', $this->getContainerHosts($container)); } // Networks if (isset($container['NetworkSettings']['Networks']) && is_array($container['NetworkSettings']['Networks'])) { foreach ($container['NetworkSettings']['Networks'] as $networkName => $conf) { $ip = $conf['IPAddress']; $aliases = isset($conf['Aliases']) && is_array($conf['Aliases']) ? $conf['Aliases'] : []; $aliases[] = substr($container['Name'], 1); $hosts = []; foreach (array_unique($aliases) as $alias) { $hosts[] = $alias.'.'.$networkName; } $lines[$ip] = sprintf('%s%s', isset($lines[$ip]) ? $lines[$ip].' ' : '', implode(' ', $hosts)); } } array_walk($lines, function (&$host, $ip) { $host = $ip.' '.$host; }); return $lines; } /** * @param Container $container * * @return array */ private function getContainerHosts($container) { $hosts = [substr($container['Name'], 1).$this->tld]; if (isset($container['Config']['Env']) && is_array($container['Config']['Env'])) { $env = $container['Config']['Env']; foreach (preg_grep('/DOMAIN_NAME=/', $env) as $row) { $row = substr($row, strlen('DOMAIN_NAME=')); $hosts = array_merge($hosts, explode(',', $row)); } } return $hosts; } /** * @param Container $container * * @return bool */ private function isExposed($container) { if (empty($container['NetworkSettings']['Ports']) || empty($container['State']['Running'])) { return false; } return $container['State']['Running']; } } ================================================ FILE: tests/DockerHostManager/SynchronizerTest.php ================================================ prophesize('DockerHostManager\Docker\Docker'); $docker = $docker->reveal(); $application = new Synchronizer($docker, '/etc/hosts', 'docker'); $this->assertSame($docker, PropertyAccessor::getProperty($application, 'docker')); $this->assertSame('/etc/hosts', PropertyAccessor::getProperty($application, 'hostsFile')); $this->assertSame('docker', PropertyAccessor::getProperty($application, 'tld')); $this->assertInstanceOf(Docker::class, PropertyAccessor::getProperty($application, 'docker')); $this->assertInternalType('array', PropertyAccessor::getProperty($application, 'activeContainers')); } } ================================================ FILE: tests/Utils/PropertyAccessor.php ================================================ setAccessible(true); return $reflection->getValue($object); } } ================================================ FILE: tests/bootstrap.php ================================================