Merge pull request #2141 from home-assistant/dev

Release 248
This commit is contained in:
Pascal Vizeli 2020-10-18 16:16:58 +02:00 committed by GitHub
commit ebf0fe8397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
238 changed files with 7865 additions and 7588 deletions

68
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,68 @@
<!--
You are amazing! Thanks for contributing to our project!
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->
## Proposed change
<!--
Describe the big picture of your changes here to communicate to the
maintainers why we should accept this pull request. If it fixes a bug
or resolves a feature request, be sure to link to that issue in the
additional information section.
-->
## Type of change
<!--
What type of change does your PR introduce to Home Assistant?
NOTE: Please, check only 1! box!
If your PR requires multiple boxes to be checked, you'll most likely need to
split it into multiple PRs. This makes things easier and faster to code review.
-->
- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (which adds functionality to the supervisor)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests
## Additional information
<!--
Details are important, and help maintainers processing your PR.
Please be sure to fill out additional details, if applicable.
-->
- This PR fixes or closes issue: fixes #
- This PR is related to issue:
- Link to documentation pull request:
## Checklist
<!--
Put an `x` in the boxes that apply. You can also fill these out after
creating the PR. If you're unsure about any of them, don't hesitate to ask.
We're here to help! This is simply a reminder of what we are going to look
for before merging your code.
-->
- [ ] The code change is tested and works locally.
- [ ] Local tests pass. **Your PR cannot be merged unless tests pass**
- [ ] There is no commented out code in this PR.
- [ ] I have followed the [development checklist][dev-checklist]
- [ ] The code has been formatted using Black (`black --fast supervisor tests`)
- [ ] Tests have been added to verify that the new code works.
If API endpoints of add-on configuration are added/changed:
- [ ] Documentation added/updated for [developers.home-assistant.io][docs-repository]
<!--
Thank you for contributing <3
Below, some useful links you could explore:
-->
[dev-checklist]: https://developers.home-assistant.io/docs/en/development_checklist.html
[docs-repository]: https://github.com/home-assistant/developers.home-assistant

View File

@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
id: python id: python
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
@ -69,7 +69,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -113,7 +113,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -157,7 +157,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -189,7 +189,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -230,7 +230,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -274,7 +274,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -306,7 +306,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@ -350,7 +350,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
@ -391,7 +391,7 @@ jobs:
-o console_output_style=count \ -o console_output_style=count \
tests tests
- name: Upload coverage artifact - name: Upload coverage artifact
uses: actions/upload-artifact@v2.1.4 uses: actions/upload-artifact@v2.2.0
with: with:
name: coverage-${{ matrix.python-version }} name: coverage-${{ matrix.python-version }}
path: .coverage path: .coverage
@ -404,7 +404,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.1.2 uses: actions/setup-python@v2.1.4
id: python id: python
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}

View File

@ -12,7 +12,7 @@ jobs:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Sentry Release - name: Sentry Release
uses: getsentry/action-release@v1.0.2 uses: getsentry/action-release@v1.1
env: env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}

1235
API.md

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
branches:
include:
- master
- dev
pr:
- dev
variables:
- name: versionHadolint
value: "v1.16.3"
jobs:
- job: "Tox"
pool:
vmImage: "ubuntu-latest"
steps:
- script: |
sudo apt-get update
sudo apt-get install -y libpulse0 libudev1
displayName: "Install Host library"
- task: UsePythonVersion@0
displayName: "Use Python 3.8"
inputs:
versionSpec: "3.8"
- script: pip install tox
displayName: "Install Tox"
- script: tox
displayName: "Run Tox"
- job: "JQ"
pool:
vmImage: "ubuntu-latest"
steps:
- script: sudo apt-get install -y jq
displayName: "Install JQ"
- bash: |
shopt -s globstar
cat **/*.json | jq '.'
displayName: "Run JQ"
- job: "Hadolint"
pool:
vmImage: "ubuntu-latest"
steps:
- script: sudo docker pull hadolint/hadolint:$(versionHadolint)
displayName: "Install Hadolint"
- script: |
sudo docker run --rm -i \
-v $(pwd)/.hadolint.yaml:/.hadolint.yaml:ro \
hadolint/hadolint:$(versionHadolint) < Dockerfile
displayName: "Run Hadolint"

@ -1 +1 @@
Subproject commit 9dabce1dd76e36e298bb752f7d5b834028f3cce3 Subproject commit 713e0579f8ccc73d1ea6c287d45a61372e5f39b7

View File

@ -1,20 +1,21 @@
aiohttp==3.6.2 aiohttp==3.6.3
async_timeout==3.0.1 async_timeout==3.0.1
attrs==20.2.0 attrs==20.2.0
brotlipy==0.7.0
cchardet==2.1.6 cchardet==2.1.6
colorlog==4.2.1 colorlog==4.4.0
cpe==1.2.1 cpe==1.2.1
cryptography==3.1 cryptography==3.1
debugpy==1.0.0rc2 debugpy==1.0.0
docker==4.3.1 docker==4.3.1
gitpython==3.1.8 gitpython==3.1.9
jinja2==2.11.2 jinja2==2.11.2
packaging==20.4 packaging==20.4
pulsectl==20.5.1 pulsectl==20.5.1
pytz==2020.1 pytz==2020.1
pyudev==0.22.0 pyudev==0.22.0
ruamel.yaml==0.15.100 ruamel.yaml==0.15.100
sentry-sdk==0.17.8 sentry-sdk==0.18.0
uvloop==0.14.0 uvloop==0.14.0
voluptuous==0.12.0 voluptuous==0.12.0
yarl==1.5.1 yarl==1.5.1

View File

@ -1,8 +1,8 @@
black==20.8b1 black==20.8b1
codecov==2.1.9 codecov==2.1.10
coverage==5.3 coverage==5.3
flake8-docstrings==1.5.0 flake8-docstrings==1.5.0
flake8==3.8.3 flake8==3.8.4
pre-commit==2.7.1 pre-commit==2.7.1
pydocstyle==5.1.1 pydocstyle==5.1.1
pylint==2.6.0 pylint==2.6.0
@ -10,5 +10,5 @@ pytest-aiohttp==0.3.0
pytest-asyncio==0.12.0 # NB!: Versions over 0.12.0 breaks pytest-aiohttp (https://github.com/aio-libs/pytest-aiohttp/issues/16) pytest-asyncio==0.12.0 # NB!: Versions over 0.12.0 breaks pytest-aiohttp (https://github.com/aio-libs/pytest-aiohttp/issues/16)
pytest-cov==2.10.1 pytest-cov==2.10.1
pytest-timeout==1.4.2 pytest-timeout==1.4.2
pytest==6.1.0 pytest==6.1.1
pyupgrade==2.7.2 pyupgrade==2.7.2

View File

@ -5,7 +5,7 @@
declare failed_count=0 declare failed_count=0
declare supervisor_state declare supervisor_state
bashio::log.info "Start local supervisor watchdog..." bashio::log.info "Starting local supervisor watchdog..."
while [[ failed_count -lt 2 ]]; while [[ failed_count -lt 2 ]];
do do
@ -31,4 +31,4 @@ do
done done
basio::exit.nok "Watchdog detect issue with Supervisor - take container down!" basio::exit.nok "Watchdog detected issue with Supervisor - taking container down!"

View File

@ -36,24 +36,24 @@ if __name__ == "__main__":
executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker") executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker")
loop.set_default_executor(executor) loop.set_default_executor(executor)
_LOGGER.info("Initialize Supervisor setup") _LOGGER.info("Initializing Supervisor setup")
coresys = loop.run_until_complete(bootstrap.initialize_coresys()) coresys = loop.run_until_complete(bootstrap.initialize_coresys())
loop.run_until_complete(coresys.core.connect()) loop.run_until_complete(coresys.core.connect())
bootstrap.supervisor_debugger(coresys) bootstrap.supervisor_debugger(coresys)
bootstrap.migrate_system_env(coresys) bootstrap.migrate_system_env(coresys)
_LOGGER.info("Setup Supervisor") _LOGGER.info("Setting up Supervisor")
loop.run_until_complete(coresys.core.setup()) loop.run_until_complete(coresys.core.setup())
loop.call_soon_threadsafe(loop.create_task, coresys.core.start()) loop.call_soon_threadsafe(loop.create_task, coresys.core.start())
loop.call_soon_threadsafe(bootstrap.reg_signal, loop, coresys) loop.call_soon_threadsafe(bootstrap.reg_signal, loop, coresys)
try: try:
_LOGGER.info("Run Supervisor") _LOGGER.info("Running Supervisor")
loop.run_forever() loop.run_forever()
finally: finally:
loop.close() loop.close()
_LOGGER.info("Close Supervisor") _LOGGER.info("Closing Supervisor")
sys.exit(0) sys.exit(0)

View File

@ -93,7 +93,7 @@ class AddonManager(CoreSysAttributes):
tasks.append(addon) tasks.append(addon)
# Evaluate add-ons which need to be started # Evaluate add-ons which need to be started
_LOGGER.info("Phase '%s' start %d add-ons", stage, len(tasks)) _LOGGER.info("Phase '%s' starting %d add-ons", stage, len(tasks))
if not tasks: if not tasks:
return return
@ -127,7 +127,7 @@ class AddonManager(CoreSysAttributes):
tasks.append(addon) tasks.append(addon)
# Evaluate add-ons which need to be stopped # Evaluate add-ons which need to be stopped
_LOGGER.info("Phase '%s' stop %d add-ons", stage, len(tasks)) _LOGGER.info("Phase '%s' stopping %d add-ons", stage, len(tasks))
if not tasks: if not tasks:
return return
@ -159,7 +159,9 @@ class AddonManager(CoreSysAttributes):
addon = Addon(self.coresys, slug) addon = Addon(self.coresys, slug)
if not addon.path_data.is_dir(): if not addon.path_data.is_dir():
_LOGGER.info("Create Home Assistant add-on data folder %s", addon.path_data) _LOGGER.info(
"Creating Home Assistant add-on data folder %s", addon.path_data
)
addon.path_data.mkdir() addon.path_data.mkdir()
# Setup/Fix AppArmor profile # Setup/Fix AppArmor profile
@ -346,7 +348,7 @@ class AddonManager(CoreSysAttributes):
return return
for addon in needs_repair: for addon in needs_repair:
_LOGGER.info("Start repair for add-on: %s", addon.slug) _LOGGER.info("Repairing for add-on: %s", addon.slug)
await self.sys_run_in_executor( await self.sys_run_in_executor(
self.sys_docker.network.stale_cleanup, addon.instance.name self.sys_docker.network.stale_cleanup, addon.instance.name
) )

View File

@ -463,7 +463,7 @@ class Addon(AddonModel):
if not self.path_data.is_dir(): if not self.path_data.is_dir():
return return
_LOGGER.info("Remove add-on data folder %s", self.path_data) _LOGGER.info("Removing add-on data folder %s", self.path_data)
await remove_data(self.path_data) await remove_data(self.path_data)
def write_pulse(self) -> None: def write_pulse(self) -> None:
@ -545,7 +545,7 @@ class Addon(AddonModel):
async def start(self) -> None: async def start(self) -> None:
"""Set options and start add-on.""" """Set options and start add-on."""
if await self.instance.is_running(): if await self.instance.is_running():
_LOGGER.warning("%s already running!", self.slug) _LOGGER.warning("%s is already running!", self.slug)
return return
# Access Token # Access Token
@ -567,7 +567,7 @@ class Addon(AddonModel):
raise AddonsError() from err raise AddonsError() from err
except DockerError as err: except DockerError as err:
self.state = AddonState.ERROR self.state = AddonState.ERROR
raise AddonsError(err) from err raise AddonsError() from err
else: else:
self.state = AddonState.STARTED self.state = AddonState.STARTED
@ -676,7 +676,7 @@ class Addon(AddonModel):
) )
try: try:
_LOGGER.info("Build snapshot for add-on %s", self.slug) _LOGGER.info("Building snapshot for add-on %s", self.slug)
await self.sys_run_in_executor(_write_tarfile) await self.sys_run_in_executor(_write_tarfile)
except (tarfile.TarError, OSError) as err: except (tarfile.TarError, OSError) as err:
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err) _LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
@ -731,7 +731,7 @@ class Addon(AddonModel):
# Check version / restore image # Check version / restore image
version = data[ATTR_VERSION] version = data[ATTR_VERSION]
if not await self.instance.exists(): if not await self.instance.exists():
_LOGGER.info("Restore/Install image for addon %s", self.slug) _LOGGER.info("Restore/Install of image for addon %s", self.slug)
image_file = Path(temp, "image.tar") image_file = Path(temp, "image.tar")
if image_file.is_file(): if image_file.is_file():
@ -742,7 +742,7 @@ class Addon(AddonModel):
await self.instance.install(version, restore_image) await self.instance.install(version, restore_image)
await self.instance.cleanup() await self.instance.cleanup()
elif self.instance.version != version or self.legacy: elif self.instance.version != version or self.legacy:
_LOGGER.info("Restore/Update image for addon %s", self.slug) _LOGGER.info("Restore/Update of image for addon %s", self.slug)
with suppress(DockerError): with suppress(DockerError):
await self.instance.update(version, restore_image) await self.instance.update(version, restore_image)
else: else:
@ -754,7 +754,7 @@ class Addon(AddonModel):
"""Restore data.""" """Restore data."""
shutil.copytree(Path(temp, "data"), self.path_data, symlinks=True) shutil.copytree(Path(temp, "data"), self.path_data, symlinks=True)
_LOGGER.info("Restore data for addon %s", self.slug) _LOGGER.info("Restoring data for addon %s", self.slug)
if self.path_data.is_dir(): if self.path_data.is_dir():
await remove_data(self.path_data) await remove_data(self.path_data)
try: try:
@ -778,4 +778,4 @@ class Addon(AddonModel):
if data[ATTR_STATE] == AddonState.STARTED: if data[ATTR_STATE] == AddonState.STARTED:
return await self.start() return await self.start()
_LOGGER.info("Finish restore for add-on %s", self.slug) _LOGGER.info("Finished restore for add-on %s", self.slug)

View File

@ -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
@ -22,6 +23,7 @@ from .network import APINetwork
from .observer import APIObserver from .observer import APIObserver
from .os import APIOS from .os import APIOS
from .proxy import APIProxy from .proxy import APIProxy
from .resolution import APIResoulution
from .security import SecurityMiddleware from .security import SecurityMiddleware
from .services import APIServices from .services import APIServices
from .snapshots import APISnapshots from .snapshots import APISnapshots
@ -42,7 +44,10 @@ class RestAPI(CoreSysAttributes):
self.security: SecurityMiddleware = SecurityMiddleware(coresys) self.security: SecurityMiddleware = SecurityMiddleware(coresys)
self.webapp: web.Application = web.Application( self.webapp: web.Application = web.Application(
client_max_size=MAX_CLIENT_SIZE, client_max_size=MAX_CLIENT_SIZE,
middlewares=[self.security.token_validation], middlewares=[
self.security.system_validation,
self.security.token_validation,
],
) )
# service stuff # service stuff
@ -51,26 +56,28 @@ class RestAPI(CoreSysAttributes):
async def load(self) -> None: async def load(self) -> None:
"""Register REST API Calls.""" """Register REST API Calls."""
self._register_supervisor() self._register_addons()
self._register_host() self._register_audio()
self._register_os() self._register_auth()
self._register_cli() self._register_cli()
self._register_observer() self._register_discovery()
self._register_multicast() self._register_dns()
self._register_network() self._register_docker()
self._register_hardware() self._register_hardware()
self._register_homeassistant() self._register_homeassistant()
self._register_proxy() self._register_host()
self._register_panel()
self._register_addons()
self._register_ingress()
self._register_snapshots()
self._register_discovery()
self._register_services()
self._register_info() self._register_info()
self._register_auth() self._register_ingress()
self._register_dns() self._register_multicast()
self._register_audio() self._register_network()
self._register_observer()
self._register_os()
self._register_panel()
self._register_proxy()
self._register_resolution()
self._register_services()
self._register_snapshots()
self._register_supervisor()
def _register_host(self) -> None: def _register_host(self) -> None:
"""Register hostcontrol functions.""" """Register hostcontrol functions."""
@ -185,6 +192,29 @@ class RestAPI(CoreSysAttributes):
self.webapp.add_routes([web.get("/info", api_info.info)]) self.webapp.add_routes([web.get("/info", api_info.info)])
def _register_resolution(self) -> None:
"""Register info functions."""
api_resolution = APIResoulution()
api_resolution.coresys = self.coresys
self.webapp.add_routes(
[
web.get("/resolution/info", api_resolution.info),
web.post(
"/resolution/suggestion/{suggestion}",
api_resolution.apply_suggestion,
),
web.delete(
"/resolution/suggestion/{suggestion}",
api_resolution.dismiss_suggestion,
),
web.delete(
"/resolution/issue/{issue}",
api_resolution.dismiss_issue,
),
]
)
def _register_auth(self) -> None: def _register_auth(self) -> None:
"""Register auth functions.""" """Register auth functions."""
api_auth = APIAuth() api_auth = APIAuth()
@ -324,7 +354,7 @@ class RestAPI(CoreSysAttributes):
web.post("/snapshots/new/partial", api_snapshots.snapshot_partial), web.post("/snapshots/new/partial", api_snapshots.snapshot_partial),
web.post("/snapshots/new/upload", api_snapshots.upload), web.post("/snapshots/new/upload", api_snapshots.upload),
web.get("/snapshots/{snapshot}/info", api_snapshots.info), web.get("/snapshots/{snapshot}/info", api_snapshots.info),
web.post("/snapshots/{snapshot}/remove", api_snapshots.remove), web.delete("/snapshots/{snapshot}", api_snapshots.remove),
web.post( web.post(
"/snapshots/{snapshot}/restore/full", api_snapshots.restore_full "/snapshots/{snapshot}/restore/full", api_snapshots.restore_full
), ),
@ -333,6 +363,8 @@ class RestAPI(CoreSysAttributes):
api_snapshots.restore_partial, api_snapshots.restore_partial,
), ),
web.get("/snapshots/{snapshot}/download", api_snapshots.download), web.get("/snapshots/{snapshot}/download", api_snapshots.download),
# Old, remove at end of 2020
web.post("/snapshots/{snapshot}/remove", api_snapshots.remove),
] ]
) )
@ -408,6 +440,20 @@ 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/info", api_docker.info),
web.get("/docker/registries", api_docker.registries),
web.post("/docker/registries", api_docker.create_registry),
web.delete("/docker/registries/{hostname}", 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()
@ -420,7 +466,7 @@ class RestAPI(CoreSysAttributes):
except OSError as err: except OSError as err:
_LOGGER.critical("Failed to create HTTP server at 0.0.0.0:80 -> %s", err) _LOGGER.critical("Failed to create HTTP server at 0.0.0.0:80 -> %s", err)
else: else:
_LOGGER.info("Start API on %s", self.sys_docker.network.supervisor) _LOGGER.info("Starting API on %s", self.sys_docker.network.supervisor)
async def stop(self) -> None: async def stop(self) -> None:
"""Stop RESTful API webserver.""" """Stop RESTful API webserver."""
@ -431,4 +477,4 @@ class RestAPI(CoreSysAttributes):
await self._site.stop() await self._site.stop()
await self._runner.cleanup() await self._runner.cleanup()
_LOGGER.info("Stop API on %s", self.sys_docker.network.supervisor) _LOGGER.info("Stopping API on %s", self.sys_docker.network.supervisor)

View File

@ -339,7 +339,7 @@ class APIAddons(CoreSysAttributes):
body: Dict[str, Any] = await api_validate(SCHEMA_SECURITY, request) body: Dict[str, Any] = await api_validate(SCHEMA_SECURITY, request)
if ATTR_PROTECTED in body: if ATTR_PROTECTED in body:
_LOGGER.warning("Protected flag changing for %s!", addon.slug) _LOGGER.warning("Changing protected flag for %s!", addon.slug)
addon.protected = body[ATTR_PROTECTED] addon.protected = body[ATTR_PROTECTED]
addon.save_persist() addon.save_persist()

76
supervisor/api/docker.py Normal file
View File

@ -0,0 +1,76 @@
"""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_LOGGING,
ATTR_PASSWORD,
ATTR_REGISTRIES,
ATTR_STORAGE,
ATTR_USERNAME,
ATTR_VERSION,
)
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()
@api_process
async def info(self, request: web.Request):
"""Get docker info."""
data_registries = {}
for hostname, registry in self.sys_docker.config.registries.items():
data_registries[hostname] = {
ATTR_USERNAME: registry[ATTR_USERNAME],
}
return {
ATTR_VERSION: self.sys_docker.info.version,
ATTR_STORAGE: self.sys_docker.info.storage,
ATTR_LOGGING: self.sys_docker.info.logging,
ATTR_REGISTRIES: data_registries,
}

View File

@ -39,6 +39,7 @@ SCHEMA_UPDATE = vol.Schema(
def interface_information(interface: NetworkInterface) -> dict: def interface_information(interface: NetworkInterface) -> dict:
"""Return a dict with information of a interface to be used in th API.""" """Return a dict with information of a interface to be used in th API."""
return { return {
ATTR_INTERFACE: interface.name,
ATTR_IP_ADDRESS: f"{interface.ip_address}/{interface.prefix}", ATTR_IP_ADDRESS: f"{interface.ip_address}/{interface.prefix}",
ATTR_GATEWAY: interface.gateway, ATTR_GATEWAY: interface.gateway,
ATTR_ID: interface.id, ATTR_ID: interface.id,
@ -69,8 +70,19 @@ class APINetwork(CoreSysAttributes):
async def interface_info(self, request: web.Request) -> Dict[str, Any]: async def interface_info(self, request: web.Request) -> Dict[str, Any]:
"""Return network information for a interface.""" """Return network information for a interface."""
req_interface = request.match_info.get(ATTR_INTERFACE) req_interface = request.match_info.get(ATTR_INTERFACE)
for interface in self.sys_host.network.interfaces:
if req_interface == self.sys_host.network.interfaces[interface].name: if req_interface.lower() == "default":
for interface in self.sys_host.network.interfaces:
if not self.sys_host.network.interfaces[interface].primary:
continue
return interface_information(
self.sys_host.network.interfaces[interface]
)
else:
for interface in self.sys_host.network.interfaces:
if req_interface != self.sys_host.network.interfaces[interface].name:
continue
return interface_information( return interface_information(
self.sys_host.network.interfaces[interface] self.sys_host.network.interfaces[interface]
) )

View File

@ -1,9 +1,9 @@
try { try {
new Function("import('/api/hassio/app/frontend_latest/entrypoint.db541070.js')")(); new Function("import('/api/hassio/app/frontend_latest/entrypoint.cbedde60.js')")();
} catch (err) { } catch (err) {
var el = document.createElement('script'); var el = document.createElement('script');
el.src = '/api/hassio/app/frontend_es5/entrypoint.aaeeb8e2.js'; el.src = '/api/hassio/app/frontend_es5/entrypoint.96d2427f.js';
document.body.appendChild(el); document.body.appendChild(el);
} }

View File

@ -0,0 +1,2 @@
(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([[528],{92914:function(n,e,r){"use strict";r.r(e),r.d(e,{codeMirror:function(){return c},codeMirrorCss:function(){return i}});var t=r(79074),s=r.n(t),o=r(49338),a=(r(22080),r(19393),r(47181));s().commands.save=function(n){(0,a.B)(n.getWrapperElement(),"editor-save")};var c=s(),i=o.Z}}]);
//# sourceMappingURL=chunk.0f03f80679461cebc400.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.0f03f80679461cebc400.js","sources":["webpack://home-assistant-frontend/chunk.0f03f80679461cebc400.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.1a2f51dc25683e4b2ff5.js","sources":["webpack:///chunk.1a2f51dc25683e4b2ff5.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.1abf54e424f4ec1a4321.js","sources":["webpack:///chunk.1abf54e424f4ec1a4321.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.307ceb960eca7aa2df23.js","sources":["webpack:///chunk.307ceb960eca7aa2df23.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.36fd5e024cde0b68de37.js","sources":["webpack://home-assistant-frontend/chunk.36fd5e024cde0b68de37.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.432a05023856cbf23e99.js","sources":["webpack://home-assistant-frontend/chunk.432a05023856cbf23e99.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.54f9c95d940bc3a71084.js","sources":["webpack://home-assistant-frontend/chunk.54f9c95d940bc3a71084.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.58899b77fdb7b6ef0605.js","sources":["webpack://home-assistant-frontend/chunk.58899b77fdb7b6ef0605.js"],"mappings":"AAAA","sourceRoot":""}

View File

@ -0,0 +1,2 @@
!function(){"use strict";var n,t={14971:function(n,t,e){e(58556);var r,o,i=e(91107),u=e(9902),s=e.n(u),f=e(62173),a={renderMarkdown:function(n,t){var e,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return r||(r=Object.assign({},(0,f.getDefaultWhiteList)(),{"ha-icon":["icon"],"ha-svg-icon":["path"]})),i.allowSvg?(o||(o=Object.assign({},r,{svg:["xmlns","height","width"],path:["transform","stroke","d"],img:["src"]})),e=o):e=r,(0,f.filterXSS)(s()(n,t),{whiteList:e})}};(0,i.Jj)(a)}},e={};function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{}};return t[n].call(o.exports,o,o.exports,r),o.exports}r.m=t,r.x=function(){r(14971)},r.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return r.d(t,{a:t}),t},r.d=function(n,t){for(var e in t)r.o(t,e)&&!r.o(n,e)&&Object.defineProperty(n,e,{enumerable:!0,get:t[e]})},r.f={},r.e=function(n){return Promise.all(Object.keys(r.f).reduce((function(t,e){return r.f[e](n,t),t}),[]))},r.u=function(n){return"chunk.bb85ff337912276d8e9b.js"},r.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},n=r.x,r.x=function(){return r.e(474).then(n)},r.p="/api/hassio/app/frontend_es5/",function(){var n={971:1};r.f.i=function(t,e){n[t]||importScripts(""+r.u(t))};var t=self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[],e=t.push.bind(t);t.push=function(t){var o=t[0],i=t[1],u=t[2];for(var s in i)r.o(i,s)&&(r.m[s]=i[s]);for(u&&u(r);o.length;)n[o.pop()]=1;e(t)}}(),r.x()}();
//# sourceMappingURL=chunk.67f2c1c40269e444b0e0.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.67f2c1c40269e444b0e0.js","sources":["webpack://home-assistant-frontend/chunk.67f2c1c40269e444b0e0.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.680a9d578b41cf01bfcd.js","sources":["webpack:///chunk.680a9d578b41cf01bfcd.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.70406e582810ae5fec16.js","sources":["webpack://home-assistant-frontend/chunk.70406e582810ae5fec16.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.77f2d29b4f5da94ea7d7.js","sources":["webpack:///chunk.77f2d29b4f5da94ea7d7.js"],"mappings":";AAAA","sourceRoot":""}

View File

@ -1,2 +0,0 @@
(self.webpackJsonp=self.webpackJsonp||[]).push([[1],{181:function(e,r,n){"use strict";n.r(r),n.d(r,"codeMirror",(function(){return c})),n.d(r,"codeMirrorCss",(function(){return i}));var a=n(170),o=n.n(a),s=n(177),t=(n(178),n(179),n(8));o.a.commands.save=function(e){Object(t.a)(e.getWrapperElement(),"editor-save")};var c=o.a,i=s.a}}]);
//# sourceMappingURL=chunk.844372507e83ac4a4c2a.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.844372507e83ac4a4c2a.js","sources":["webpack:///chunk.844372507e83ac4a4c2a.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.a38cb3d17010dfd8ff6e.js","sources":["webpack://home-assistant-frontend/chunk.a38cb3d17010dfd8ff6e.js"],"mappings":";AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.adf974a3f56fd8dc1c37.js","sources":["webpack:///chunk.adf974a3f56fd8dc1c37.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.b995d5ef1d64c7005d2c.js","sources":["webpack:///chunk.b995d5ef1d64c7005d2c.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.bb85ff337912276d8e9b.js","sources":["webpack://home-assistant-frontend/chunk.bb85ff337912276d8e9b.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.c3099d3d125acb803c0f.js","sources":["webpack://home-assistant-frontend/chunk.c3099d3d125acb803c0f.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.ce88ac273ca23dcb7f96.js","sources":["webpack://home-assistant-frontend/chunk.ce88ac273ca23dcb7f96.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.dc0f66c45c1518f1e16e.worker.js","sources":["webpack:///chunk.dc0f66c45c1518f1e16e.worker.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.f2d1cbf860fa69dbf667.js","sources":["webpack:///chunk.f2d1cbf860fa69dbf667.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,25 @@
/*! /*!
* The buffer module from node.js, for the browser. * The buffer module from node.js, for the browser.
* *
* @author Feross Aboukhadijeh <http://feross.org> * @author Feross Aboukhadijeh <https://feross.org>
* @license MIT * @license MIT
*/ */
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/*! ***************************************************************************** /*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved. Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use Licensed under the Apache License, Version 2.0 (the "License"); you may not use

View File

@ -0,0 +1 @@
{"version":3,"file":"entrypoint.96d2427f.js","sources":["webpack://home-assistant-frontend/entrypoint.96d2427f.js"],"mappings":";AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"entrypoint.aaeeb8e2.js","sources":["webpack:///entrypoint.aaeeb8e2.js"],"mappings":";AAAA","sourceRoot":""}

View File

@ -1,3 +1,3 @@
{ {
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.aaeeb8e2.js" "entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.96d2427f.js"
} }

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.05c74bd6edc0c5c372eb.js","sources":["webpack:///chunk.05c74bd6edc0c5c372eb.js"],"mappings":"AAAA;;;AAoMA;AACA;;;AAGA;;;;;AAKA;;AAEA;AAEA;AACA;;;;;;;AAQA;;;;;AAKA;;AAEA;AAEA;AACA;;;;;;;AAQA;;;;;AAOA;;;;;;;;;;;;;;;;AAuBA;AAknBA;;AAEA;;AAEA;AACA;;AAIA;AA0IA;;;;AAIA;;AAEA;AACA;;;AAGA;;;;AAIA;AACA;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;AA6HA;;;AAyGA;AACA;;;;;;;;AAQA;;AAGA;;;AAGA;;AAEA;AACA;;;;AAIA;;;;;;;AAQA;;;AAGA;;;;;AAvCA;;;;;;;;;;;;;;;AA8KA;;;AAkFA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;;AApBA;;;;;;;;;;;AA4CA;;;AA8GA;;AAEA;;;;AARA;;;;;;;;;;;;AAiCA;;AAiCA;AACA;AACA;;;AAMA;;;;AA6IA;;;AAMA;AACA;;;AAGA;;AAEA;;AAKA;;AAEA;;AAEA;;AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6EA;AAsLA;;;;AAIA;AACA;AACA;AACA;;;AAGA;;;;;;;;AAQA;AACA;AACA;;;;AAIA;AACA;;;AAGA;;;AAGA;AACA;;;;;;;AAOA;;;;;;;;;AASA;;AAEA;AACA;;;;AAIA;;AAEA;;;;AAIA;;;AAGA;;;;AAIA;AACA;AACA;;;AAGA;;;;;;AAMA;;AAEA;AACA;;;;AAIA;;;AAGA;;AAEA;;AAEA;AACA;AAIA;;;;;;AAMA;;AAEA;AACA;;AAEA;AAKA;;AAEA;;;;AAIA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;AAGA;;AAEA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;AACA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;;AAMA;;;AAGA;;;AAGA;;AAEA;;;;;;;;AAQA;AACA;;;;;AAKA;AACA;;;;;;;;AAQA;AACA;;;;AAIA;AACA;AACA;;;;;;;;;AASA;AACA;;;;AAIA;AACA;AACA;;;;;AAKA;;;AAGA;AACA;AACA;;;;AAIA;AACA;AACA;;;;;;;;AAQA;AACA;;;;AAIA;;AAEA;AACA;;;AAGA;AACA;;;AAGA;AACA;;;;;;AAMA;AACA;;;;AAIA;AACA;;;;AAIA;;AAEA;;;;;;;;;;AAUA;AACA;AACA;;;AAGA;;;AAGA;;;;AAIA;;;AAGA;AACA;;;;AAIA;AACA;AACA;;;;;;AAMA;AACA;;;;;;;;AAQA;;;;AAIA;;;;AAIA;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAocA;;;AAoFA;AACA;AACA;;;AARA;;;;;;AA4BA;AAqGA;;AAEA;;AAEA;AACA;AACA;;;AAGA;;;AAKA;;;;;;;;;AAgBA;;;AAiGA;AACA;;;AAPA;;;;;;AA2BA;;AAmQA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;;;AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"chunk.14261136b79b2dda3306.js","sources":["webpack://home-assistant-frontend/chunk.14261136b79b2dda3306.js"],"mappings":"AAAA;AAiPA;AACA;AACA;AANA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"version":3,"file":"chunk.1fce560aeae04197cd1a.js","sources":["webpack:///chunk.1fce560aeae04197cd1a.js"],"mappings":"AAAA;AA0OA;AACA;AACA;AANA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA","sourceRoot":""}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More