async def http_query(self, method, path, data={}, params={}, timeout=300): """ Makes a query to the docker daemon :param method: HTTP method :param path: Endpoint in API :param data: Dictionnary with the body. Will be transformed to a JSON :param params: Parameters added as a query arg :param timeout: Timeout :returns: HTTP response """ data = json.dumps(data) if timeout is None: timeout = 60 * 60 * 24 * 31 # One month timeout if path == 'version': url = "http://docker/v1.12/" + path # API of docker v1.0 else: url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path try: if path != "version": # version is use by check connection await self._check_connection() if self._session is None or self._session.closed: connector = self.connector() self._session = aiohttp.ClientSession(connector=connector) response = await self._session.request(method, url, params=params, data=data, headers={ "content-type": "application/json", }, timeout=timeout) except (aiohttp.ClientResponseError, aiohttp.ClientOSError) as e: raise DockerError("Docker has returned an error: {}".format( str(e))) except (asyncio.TimeoutError): raise DockerError("Docker timeout " + method + " " + path) if response.status >= 300: body = await response.read() try: body = json.loads(body.decode("utf-8"))["message"] except ValueError: pass log.debug("Query Docker %s %s params=%s data=%s Response: %s", method, path, params, data, body) if response.status == 304: raise DockerHttp304Error( "Docker has returned an error: {} {}".format( response.status, body)) elif response.status == 404: raise DockerHttp404Error( "Docker has returned an error: {} {}".format( response.status, body)) else: raise DockerError("Docker has returned an error: {} {}".format( response.status, body)) return response
def connector(self): if self._connector is None or self._connector.closed: if not sys.platform.startswith("linux"): raise DockerError("Docker is supported only on Linux") try: self._connector = aiohttp.connector.UnixConnector( self._server_url, conn_timeout=2, limit=None) except (aiohttp.errors.ClientOSError, FileNotFoundError): raise DockerError("Can't connect to docker daemon") return self._connector
async def pull_image(self, image, progress_callback=None): """ Pulls an image from the Docker repository :params image: Image name :params progress_callback: A function that receive a log message about image download progress """ try: await self.query("GET", "images/{}/json".format(image)) return # We already have the image skip the download except DockerHttp404Error: pass if progress_callback: progress_callback("Pulling '{}' from docker hub".format(image)) try: response = await self.http_query("POST", "images/create", params={"fromImage": image}, timeout=None) except DockerError as e: raise DockerError( "Could not pull the '{}' image from Docker Hub, please check your Internet connection (original error: {})" .format(image, e)) # The pull api will stream status via an HTTP JSON stream content = "" while True: try: chunk = await response.content.read(CHUNK_SIZE) except aiohttp.ServerDisconnectedError: log.error( "Disconnected from server while pulling Docker image '{}' from docker hub" .format(image)) break except asyncio.TimeoutError: log.error( "Timeout while pulling Docker image '{}' from docker hub". format(image)) break if not chunk: break content += chunk.decode("utf-8") try: while True: content = content.lstrip(" \r\n\t") answer, index = json.JSONDecoder().raw_decode(content) if "progress" in answer and progress_callback: progress_callback("Pulling image {}:{}: {}".format( image, answer["id"], answer["progress"])) content = content[index:] except ValueError: # Partial JSON pass response.close() if progress_callback: progress_callback("Success pulling image {}".format(image))
def _check_connection(self): if not self._connected: try: self._connected = True connector = self.connector() version = yield from self.query("GET", "version") except (aiohttp.errors.ClientOSError, FileNotFoundError): self._connected = False raise DockerError("Can't connect to docker daemon") docker_version = parse_version(version['ApiVersion']) if docker_version < parse_version(DOCKER_MINIMUM_API_VERSION): raise DockerError( "Docker version is {}. GNS3 requires a minimum version of {}" .format(version["Version"], DOCKER_MINIMUM_VERSION)) preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION) if docker_version >= preferred_api_version: self._api_version = DOCKER_PREFERRED_API_VERSION