Support for installing add-ons from password protected registries (#2038)
This commit is contained in:
parent
998dd5387b
commit
f6019b4e68
28
API.md
28
API.md
|
@ -1233,3 +1233,31 @@ We support:
|
||||||
"password": "new-password"
|
"password": "new-password"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Docker Registries
|
||||||
|
|
||||||
|
You can configure password-protected Docker registries that can be used as a
|
||||||
|
source when pulling docker images.
|
||||||
|
|
||||||
|
- GET `/docker/registries`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hostname": {
|
||||||
|
"username": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- POST `/docker/registries`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"{hostname}": {
|
||||||
|
"username": "...",
|
||||||
|
"password": "...",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- POST `/docker/registries/{hostname}/remove`
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .auth import APIAuth
|
||||||
from .cli import APICli
|
from .cli import APICli
|
||||||
from .discovery import APIDiscovery
|
from .discovery import APIDiscovery
|
||||||
from .dns import APICoreDNS
|
from .dns import APICoreDNS
|
||||||
|
from .docker import APIDocker
|
||||||
from .hardware import APIHardware
|
from .hardware import APIHardware
|
||||||
from .homeassistant import APIHomeAssistant
|
from .homeassistant import APIHomeAssistant
|
||||||
from .host import APIHost
|
from .host import APIHost
|
||||||
|
@ -71,6 +72,7 @@ class RestAPI(CoreSysAttributes):
|
||||||
self._register_auth()
|
self._register_auth()
|
||||||
self._register_dns()
|
self._register_dns()
|
||||||
self._register_audio()
|
self._register_audio()
|
||||||
|
self._register_docker()
|
||||||
|
|
||||||
def _register_host(self) -> None:
|
def _register_host(self) -> None:
|
||||||
"""Register hostcontrol functions."""
|
"""Register hostcontrol functions."""
|
||||||
|
@ -408,6 +410,21 @@ class RestAPI(CoreSysAttributes):
|
||||||
panel_dir = Path(__file__).parent.joinpath("panel")
|
panel_dir = Path(__file__).parent.joinpath("panel")
|
||||||
self.webapp.add_routes([web.static("/app", panel_dir)])
|
self.webapp.add_routes([web.static("/app", panel_dir)])
|
||||||
|
|
||||||
|
def _register_docker(self) -> None:
|
||||||
|
"""Register docker configuration functions."""
|
||||||
|
api_docker = APIDocker()
|
||||||
|
api_docker.coresys = self.coresys
|
||||||
|
|
||||||
|
self.webapp.add_routes(
|
||||||
|
[
|
||||||
|
web.get("/docker/registries", api_docker.registries),
|
||||||
|
web.post("/docker/registries", api_docker.create_registry),
|
||||||
|
web.post(
|
||||||
|
"/docker/registries/{hostname}/remove", api_docker.remove_registry
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
"""Run RESTful API webserver."""
|
"""Run RESTful API webserver."""
|
||||||
await self._runner.setup()
|
await self._runner.setup()
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
"""Init file for Supervisor Home Assistant RESTful API."""
|
||||||
|
import logging
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from ..const import ATTR_HOSTNAME, ATTR_PASSWORD, ATTR_REGISTRIES, ATTR_USERNAME
|
||||||
|
from ..coresys import CoreSysAttributes
|
||||||
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCHEMA_DOCKER_REGISTRY = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Coerce(str): {
|
||||||
|
vol.Required(ATTR_USERNAME): str,
|
||||||
|
vol.Required(ATTR_PASSWORD): str,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class APIDocker(CoreSysAttributes):
|
||||||
|
"""Handle RESTful API for Docker configuration."""
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def registries(self, request) -> Dict[str, Any]:
|
||||||
|
"""Return the list of registries."""
|
||||||
|
data_registries = {}
|
||||||
|
for hostname, registry in self.sys_docker.config.registries.items():
|
||||||
|
data_registries[hostname] = {
|
||||||
|
ATTR_USERNAME: registry[ATTR_USERNAME],
|
||||||
|
}
|
||||||
|
|
||||||
|
return {ATTR_REGISTRIES: data_registries}
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def create_registry(self, request: web.Request):
|
||||||
|
"""Create a new docker registry."""
|
||||||
|
body = await api_validate(SCHEMA_DOCKER_REGISTRY, request)
|
||||||
|
|
||||||
|
for hostname, registry in body.items():
|
||||||
|
self.sys_docker.config.registries[hostname] = registry
|
||||||
|
|
||||||
|
self.sys_docker.config.save_data()
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def remove_registry(self, request: web.Request):
|
||||||
|
"""Delete a docker registry."""
|
||||||
|
hostname = request.match_info.get(ATTR_HOSTNAME)
|
||||||
|
del self.sys_docker.config.registries[hostname]
|
||||||
|
self.sys_docker.config.save_data()
|
|
@ -20,6 +20,7 @@ FILE_HASSIO_ADDONS = Path(SUPERVISOR_DATA, "addons.json")
|
||||||
FILE_HASSIO_AUTH = Path(SUPERVISOR_DATA, "auth.json")
|
FILE_HASSIO_AUTH = Path(SUPERVISOR_DATA, "auth.json")
|
||||||
FILE_HASSIO_CONFIG = Path(SUPERVISOR_DATA, "config.json")
|
FILE_HASSIO_CONFIG = Path(SUPERVISOR_DATA, "config.json")
|
||||||
FILE_HASSIO_DISCOVERY = Path(SUPERVISOR_DATA, "discovery.json")
|
FILE_HASSIO_DISCOVERY = Path(SUPERVISOR_DATA, "discovery.json")
|
||||||
|
FILE_HASSIO_DOCKER = Path(SUPERVISOR_DATA, "docker.json")
|
||||||
FILE_HASSIO_HOMEASSISTANT = Path(SUPERVISOR_DATA, "homeassistant.json")
|
FILE_HASSIO_HOMEASSISTANT = Path(SUPERVISOR_DATA, "homeassistant.json")
|
||||||
FILE_HASSIO_INGRESS = Path(SUPERVISOR_DATA, "ingress.json")
|
FILE_HASSIO_INGRESS = Path(SUPERVISOR_DATA, "ingress.json")
|
||||||
FILE_HASSIO_SERVICES = Path(SUPERVISOR_DATA, "services.json")
|
FILE_HASSIO_SERVICES = Path(SUPERVISOR_DATA, "services.json")
|
||||||
|
@ -222,6 +223,7 @@ ATTR_PROTECTED = "protected"
|
||||||
ATTR_PROVIDERS = "providers"
|
ATTR_PROVIDERS = "providers"
|
||||||
ATTR_RATING = "rating"
|
ATTR_RATING = "rating"
|
||||||
ATTR_REFRESH_TOKEN = "refresh_token"
|
ATTR_REFRESH_TOKEN = "refresh_token"
|
||||||
|
ATTR_REGISTRIES = "registries"
|
||||||
ATTR_REPOSITORIES = "repositories"
|
ATTR_REPOSITORIES = "repositories"
|
||||||
ATTR_REPOSITORY = "repository"
|
ATTR_REPOSITORY = "repository"
|
||||||
ATTR_SCHEMA = "schema"
|
ATTR_SCHEMA = "schema"
|
||||||
|
|
|
@ -10,8 +10,16 @@ import docker
|
||||||
from packaging import version as pkg_version
|
from packaging import version as pkg_version
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from ..const import DNS_SUFFIX, DOCKER_IMAGE_DENYLIST, SOCKET_DOCKER
|
from ..const import (
|
||||||
|
ATTR_REGISTRIES,
|
||||||
|
DNS_SUFFIX,
|
||||||
|
DOCKER_IMAGE_DENYLIST,
|
||||||
|
FILE_HASSIO_DOCKER,
|
||||||
|
SOCKET_DOCKER,
|
||||||
|
)
|
||||||
from ..exceptions import DockerAPIError, DockerError, DockerNotFound, DockerRequestError
|
from ..exceptions import DockerAPIError, DockerError, DockerNotFound, DockerRequestError
|
||||||
|
from ..utils.json import JsonConfig
|
||||||
|
from ..validate import SCHEMA_DOCKER_CONFIG
|
||||||
from .network import DockerNetwork
|
from .network import DockerNetwork
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
@ -64,6 +72,19 @@ class DockerInfo:
|
||||||
return self.storage != "overlay2" or self.logging != "journald"
|
return self.storage != "overlay2" or self.logging != "journald"
|
||||||
|
|
||||||
|
|
||||||
|
class DockerConfig(JsonConfig):
|
||||||
|
"""Home Assistant core object for Docker configuration."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the JSON configuration."""
|
||||||
|
super().__init__(FILE_HASSIO_DOCKER, SCHEMA_DOCKER_CONFIG)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def registries(self) -> Dict[str, Any]:
|
||||||
|
"""Return credentials for docker registries."""
|
||||||
|
return self._data.get(ATTR_REGISTRIES, {})
|
||||||
|
|
||||||
|
|
||||||
class DockerAPI:
|
class DockerAPI:
|
||||||
"""Docker Supervisor wrapper.
|
"""Docker Supervisor wrapper.
|
||||||
|
|
||||||
|
@ -77,6 +98,7 @@ class DockerAPI:
|
||||||
)
|
)
|
||||||
self.network: DockerNetwork = DockerNetwork(self.docker)
|
self.network: DockerNetwork = DockerNetwork(self.docker)
|
||||||
self._info: DockerInfo = DockerInfo.new(self.docker.info())
|
self._info: DockerInfo = DockerInfo.new(self.docker.info())
|
||||||
|
self.config: DockerConfig = DockerConfig()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def images(self) -> docker.models.images.ImageCollection:
|
def images(self) -> docker.models.images.ImageCollection:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from typing import Any, Awaitable, Dict, List, Optional
|
from typing import Any, Awaitable, Dict, List, Optional
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
@ -9,7 +10,7 @@ from packaging import version as pkg_version
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from . import CommandReturn
|
from . import CommandReturn
|
||||||
from ..const import LABEL_ARCH, LABEL_VERSION
|
from ..const import ATTR_PASSWORD, ATTR_USERNAME, LABEL_ARCH, LABEL_VERSION
|
||||||
from ..coresys import CoreSys, CoreSysAttributes
|
from ..coresys import CoreSys, CoreSysAttributes
|
||||||
from ..exceptions import DockerAPIError, DockerError, DockerNotFound, DockerRequestError
|
from ..exceptions import DockerAPIError, DockerError, DockerNotFound, DockerRequestError
|
||||||
from ..utils import process_lock
|
from ..utils import process_lock
|
||||||
|
@ -17,6 +18,8 @@ from .stats import DockerStats
|
||||||
|
|
||||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
IMAGE_WITH_HOST = re.compile(r"^((?:[a-z0-9]+(?:-[a-z0-9]+)*\.)+[a-z]{2,})\/.+")
|
||||||
|
|
||||||
|
|
||||||
class DockerInterface(CoreSysAttributes):
|
class DockerInterface(CoreSysAttributes):
|
||||||
"""Docker Supervisor interface."""
|
"""Docker Supervisor interface."""
|
||||||
|
@ -84,6 +87,17 @@ class DockerInterface(CoreSysAttributes):
|
||||||
"""Pull docker image."""
|
"""Pull docker image."""
|
||||||
return self.sys_run_in_executor(self._install, tag, image, latest)
|
return self.sys_run_in_executor(self._install, tag, image, latest)
|
||||||
|
|
||||||
|
def _docker_login(self, hostname: str) -> None:
|
||||||
|
"""Try to log in to the registry if there are credentials available."""
|
||||||
|
if hostname in self.sys_docker.config.registries:
|
||||||
|
credentials = self.sys_docker.config.registries[hostname]
|
||||||
|
|
||||||
|
self.sys_docker.docker.login(
|
||||||
|
registry=hostname,
|
||||||
|
username=credentials[ATTR_USERNAME],
|
||||||
|
password=credentials[ATTR_PASSWORD],
|
||||||
|
)
|
||||||
|
|
||||||
def _install(
|
def _install(
|
||||||
self, tag: str, image: Optional[str] = None, latest: bool = False
|
self, tag: str, image: Optional[str] = None, latest: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -95,6 +109,10 @@ class DockerInterface(CoreSysAttributes):
|
||||||
|
|
||||||
_LOGGER.info("Pull image %s tag %s.", image, tag)
|
_LOGGER.info("Pull image %s tag %s.", image, tag)
|
||||||
try:
|
try:
|
||||||
|
# If the image name contains a path to a registry, try to log in
|
||||||
|
path = IMAGE_WITH_HOST.match(image)
|
||||||
|
if path:
|
||||||
|
self._docker_login(path.group(1))
|
||||||
docker_image = self.sys_docker.images.pull(f"{image}:{tag}")
|
docker_image = self.sys_docker.images.pull(f"{image}:{tag}")
|
||||||
if latest:
|
if latest:
|
||||||
_LOGGER.info("Tag image %s with version %s as latest", image, tag)
|
_LOGGER.info("Tag image %s with version %s as latest", image, tag)
|
||||||
|
|
|
@ -44,6 +44,7 @@ class SnapshotManager(CoreSysAttributes):
|
||||||
# set general data
|
# set general data
|
||||||
snapshot.store_homeassistant()
|
snapshot.store_homeassistant()
|
||||||
snapshot.store_repositories()
|
snapshot.store_repositories()
|
||||||
|
snapshot.store_dockerconfig()
|
||||||
|
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
|
@ -227,6 +228,10 @@ class SnapshotManager(CoreSysAttributes):
|
||||||
_LOGGER.info("Restore %s run folders", snapshot.slug)
|
_LOGGER.info("Restore %s run folders", snapshot.slug)
|
||||||
await snapshot.restore_folders()
|
await snapshot.restore_folders()
|
||||||
|
|
||||||
|
# Restore docker config
|
||||||
|
_LOGGER.info("Restore %s run Docker Config", snapshot.slug)
|
||||||
|
snapshot.restore_dockerconfig()
|
||||||
|
|
||||||
# Start homeassistant restore
|
# Start homeassistant restore
|
||||||
_LOGGER.info("Restore %s run Home-Assistant", snapshot.slug)
|
_LOGGER.info("Restore %s run Home-Assistant", snapshot.slug)
|
||||||
snapshot.restore_homeassistant()
|
snapshot.restore_homeassistant()
|
||||||
|
@ -293,6 +298,10 @@ class SnapshotManager(CoreSysAttributes):
|
||||||
await self.lock.acquire()
|
await self.lock.acquire()
|
||||||
|
|
||||||
async with snapshot:
|
async with snapshot:
|
||||||
|
# Restore docker config
|
||||||
|
_LOGGER.info("Restore %s run Docker Config", snapshot.slug)
|
||||||
|
snapshot.restore_dockerconfig()
|
||||||
|
|
||||||
# Stop Home-Assistant for config restore
|
# Stop Home-Assistant for config restore
|
||||||
if FOLDER_HOMEASSISTANT in folders:
|
if FOLDER_HOMEASSISTANT in folders:
|
||||||
await self.sys_homeassistant.core.stop()
|
await self.sys_homeassistant.core.stop()
|
||||||
|
|
|
@ -21,18 +21,22 @@ from ..const import (
|
||||||
ATTR_BOOT,
|
ATTR_BOOT,
|
||||||
ATTR_CRYPTO,
|
ATTR_CRYPTO,
|
||||||
ATTR_DATE,
|
ATTR_DATE,
|
||||||
|
ATTR_DOCKER,
|
||||||
ATTR_FOLDERS,
|
ATTR_FOLDERS,
|
||||||
ATTR_HOMEASSISTANT,
|
ATTR_HOMEASSISTANT,
|
||||||
ATTR_IMAGE,
|
ATTR_IMAGE,
|
||||||
ATTR_NAME,
|
ATTR_NAME,
|
||||||
|
ATTR_PASSWORD,
|
||||||
ATTR_PORT,
|
ATTR_PORT,
|
||||||
ATTR_PROTECTED,
|
ATTR_PROTECTED,
|
||||||
ATTR_REFRESH_TOKEN,
|
ATTR_REFRESH_TOKEN,
|
||||||
|
ATTR_REGISTRIES,
|
||||||
ATTR_REPOSITORIES,
|
ATTR_REPOSITORIES,
|
||||||
ATTR_SIZE,
|
ATTR_SIZE,
|
||||||
ATTR_SLUG,
|
ATTR_SLUG,
|
||||||
ATTR_SSL,
|
ATTR_SSL,
|
||||||
ATTR_TYPE,
|
ATTR_TYPE,
|
||||||
|
ATTR_USERNAME,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
ATTR_WAIT_BOOT,
|
ATTR_WAIT_BOOT,
|
||||||
ATTR_WATCHDOG,
|
ATTR_WATCHDOG,
|
||||||
|
@ -131,6 +135,16 @@ class Snapshot(CoreSysAttributes):
|
||||||
"""Return snapshot Home Assistant data."""
|
"""Return snapshot Home Assistant data."""
|
||||||
return self._data[ATTR_HOMEASSISTANT]
|
return self._data[ATTR_HOMEASSISTANT]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def docker(self):
|
||||||
|
"""Return snapshot Docker config data."""
|
||||||
|
return self._data.get(ATTR_DOCKER, {})
|
||||||
|
|
||||||
|
@docker.setter
|
||||||
|
def docker(self, value):
|
||||||
|
"""Set the Docker config data."""
|
||||||
|
self._data[ATTR_DOCKER] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self):
|
||||||
"""Return snapshot size."""
|
"""Return snapshot size."""
|
||||||
|
@ -481,3 +495,29 @@ class Snapshot(CoreSysAttributes):
|
||||||
Return a coroutine.
|
Return a coroutine.
|
||||||
"""
|
"""
|
||||||
return self.sys_store.update_repositories(self.repositories)
|
return self.sys_store.update_repositories(self.repositories)
|
||||||
|
|
||||||
|
def store_dockerconfig(self):
|
||||||
|
"""Store the configuration for Docker."""
|
||||||
|
self.docker = {
|
||||||
|
ATTR_REGISTRIES: {
|
||||||
|
registry: {
|
||||||
|
ATTR_USERNAME: credentials[ATTR_USERNAME],
|
||||||
|
ATTR_PASSWORD: self._encrypt_data(credentials[ATTR_PASSWORD]),
|
||||||
|
}
|
||||||
|
for registry, credentials in self.sys_docker.config.registries.items()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def restore_dockerconfig(self):
|
||||||
|
"""Restore the configuration for Docker."""
|
||||||
|
if ATTR_REGISTRIES in self.docker:
|
||||||
|
self.sys_docker.config.registries.update(
|
||||||
|
{
|
||||||
|
registry: {
|
||||||
|
ATTR_USERNAME: credentials[ATTR_USERNAME],
|
||||||
|
ATTR_PASSWORD: self._decrypt_data(credentials[ATTR_PASSWORD]),
|
||||||
|
}
|
||||||
|
for registry, credentials in self.docker[ATTR_REGISTRIES].items()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.sys_docker.config.save_data()
|
||||||
|
|
|
@ -8,6 +8,7 @@ from ..const import (
|
||||||
ATTR_BOOT,
|
ATTR_BOOT,
|
||||||
ATTR_CRYPTO,
|
ATTR_CRYPTO,
|
||||||
ATTR_DATE,
|
ATTR_DATE,
|
||||||
|
ATTR_DOCKER,
|
||||||
ATTR_FOLDERS,
|
ATTR_FOLDERS,
|
||||||
ATTR_HOMEASSISTANT,
|
ATTR_HOMEASSISTANT,
|
||||||
ATTR_IMAGE,
|
ATTR_IMAGE,
|
||||||
|
@ -32,7 +33,13 @@ from ..const import (
|
||||||
SNAPSHOT_FULL,
|
SNAPSHOT_FULL,
|
||||||
SNAPSHOT_PARTIAL,
|
SNAPSHOT_PARTIAL,
|
||||||
)
|
)
|
||||||
from ..validate import docker_image, network_port, repositories, version_tag
|
from ..validate import (
|
||||||
|
SCHEMA_DOCKER_CONFIG,
|
||||||
|
docker_image,
|
||||||
|
network_port,
|
||||||
|
repositories,
|
||||||
|
version_tag,
|
||||||
|
)
|
||||||
|
|
||||||
ALL_FOLDERS = [
|
ALL_FOLDERS = [
|
||||||
FOLDER_HOMEASSISTANT,
|
FOLDER_HOMEASSISTANT,
|
||||||
|
@ -84,6 +91,7 @@ SCHEMA_SNAPSHOT = vol.Schema(
|
||||||
},
|
},
|
||||||
extra=vol.REMOVE_EXTRA,
|
extra=vol.REMOVE_EXTRA,
|
||||||
),
|
),
|
||||||
|
vol.Optional(ATTR_DOCKER, default=dict): SCHEMA_DOCKER_CONFIG,
|
||||||
vol.Optional(ATTR_FOLDERS, default=list): vol.All(
|
vol.Optional(ATTR_FOLDERS, default=list): vol.All(
|
||||||
[vol.In(ALL_FOLDERS)], vol.Unique()
|
[vol.In(ALL_FOLDERS)], vol.Unique()
|
||||||
),
|
),
|
||||||
|
|
|
@ -27,13 +27,16 @@ from .const import (
|
||||||
ATTR_LOGGING,
|
ATTR_LOGGING,
|
||||||
ATTR_MULTICAST,
|
ATTR_MULTICAST,
|
||||||
ATTR_OBSERVER,
|
ATTR_OBSERVER,
|
||||||
|
ATTR_PASSWORD,
|
||||||
ATTR_PORT,
|
ATTR_PORT,
|
||||||
ATTR_PORTS,
|
ATTR_PORTS,
|
||||||
ATTR_REFRESH_TOKEN,
|
ATTR_REFRESH_TOKEN,
|
||||||
|
ATTR_REGISTRIES,
|
||||||
ATTR_SESSION,
|
ATTR_SESSION,
|
||||||
ATTR_SSL,
|
ATTR_SSL,
|
||||||
ATTR_SUPERVISOR,
|
ATTR_SUPERVISOR,
|
||||||
ATTR_TIMEZONE,
|
ATTR_TIMEZONE,
|
||||||
|
ATTR_USERNAME,
|
||||||
ATTR_UUID,
|
ATTR_UUID,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
ATTR_WAIT_BOOT,
|
ATTR_WAIT_BOOT,
|
||||||
|
@ -45,6 +48,7 @@ from .const import (
|
||||||
from .utils.validate import validate_timezone
|
from .utils.validate import validate_timezone
|
||||||
|
|
||||||
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
RE_REPOSITORY = re.compile(r"^(?P<url>[^#]+)(?:#(?P<branch>[\w\-]+))?$")
|
||||||
|
RE_REGISTRY = re.compile(r"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$")
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
@ -181,6 +185,20 @@ SCHEMA_SUPERVISOR_CONFIG = vol.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA_DOCKER_CONFIG = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(ATTR_REGISTRIES, default=dict): vol.Schema(
|
||||||
|
{
|
||||||
|
vol.All(str, vol.Match(RE_REGISTRY)): {
|
||||||
|
vol.Required(ATTR_USERNAME): str,
|
||||||
|
vol.Required(ATTR_PASSWORD): str,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SCHEMA_AUTH_CONFIG = vol.Schema({sha256: sha256})
|
SCHEMA_AUTH_CONFIG = vol.Schema({sha256: sha256})
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue