mirror of
https://github.com/hassio-addons/repository-updater.git
synced 2025-05-07 04:21:32 +00:00
211 lines
7.2 KiB
Python
211 lines
7.2 KiB
Python
"""
|
|
Repository module.
|
|
|
|
Contains the add-ons repository representation / configuration
|
|
and handles the automated maintenance / updating of it.
|
|
"""
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
from typing import List
|
|
|
|
import click
|
|
import crayons
|
|
import yaml
|
|
from git import Repo
|
|
from github.GithubException import UnknownObjectException
|
|
from github.Repository import Repository as GitHubRepository
|
|
from jinja2 import Environment, FileSystemLoader
|
|
|
|
from .addon import Addon
|
|
from .const import CHANNELS
|
|
from .github import GitHub
|
|
|
|
|
|
class Repository:
|
|
"""Represents an Home Assistant add-ons repository."""
|
|
|
|
addons: List[Addon]
|
|
github: GitHub
|
|
github_repository: GitHubRepository
|
|
git_repo: Repo
|
|
force: bool
|
|
channel: str
|
|
|
|
def __init__(self, github: GitHub, repository: str, addon: str, force: bool):
|
|
"""Initialize new add-on Repository object."""
|
|
self.github = github
|
|
self.force = force
|
|
self.addons = []
|
|
|
|
click.echo(
|
|
'Locating add-on repository "%s"...' % crayons.yellow(repository), nl=False
|
|
)
|
|
self.github_repository = github.get_repo(repository)
|
|
click.echo(crayons.green("Found!"))
|
|
|
|
self.clone_repository()
|
|
self.load_repository(addon)
|
|
|
|
def update(self):
|
|
"""Update this repository using configuration and data gathered."""
|
|
self.generate_readme()
|
|
needs_push = self.commit_changes(":books: Updated README")
|
|
|
|
for addon in self.addons:
|
|
if addon.needs_update(self.force):
|
|
click.echo(crayons.green("-" * 50, bold=True))
|
|
click.echo(crayons.green(f"Updating add-on {addon.repository_target}"))
|
|
needs_push = self.update_addon(addon) or needs_push
|
|
|
|
if needs_push:
|
|
click.echo(crayons.green("-" * 50, bold=True))
|
|
click.echo("Pushing updates onto Git add-ons repository...", nl=False)
|
|
self.git_repo.git.push()
|
|
click.echo(crayons.green("Done"))
|
|
|
|
def commit_changes(self, message):
|
|
"""Commit current Repository changes."""
|
|
click.echo("Committing changes...", nl=False)
|
|
|
|
if not self.git_repo.is_dirty(untracked_files=True):
|
|
click.echo(crayons.yellow("Skipped, no changes."))
|
|
return False
|
|
|
|
self.git_repo.git.add(".")
|
|
self.git_repo.git.commit("--no-gpg-sign", "-m", message)
|
|
click.echo(crayons.green("Done: ") + crayons.cyan(message))
|
|
return True
|
|
|
|
def update_addon(self, addon):
|
|
"""Update repository for a specific add-on."""
|
|
addon.update()
|
|
self.generate_readme()
|
|
|
|
if addon.latest_is_release:
|
|
message = ":tada: Release of add-on %s %s" % (
|
|
addon.name,
|
|
addon.current_version,
|
|
)
|
|
else:
|
|
message = ":arrow_up: Updating add-on %s to %s" % (
|
|
addon.name,
|
|
addon.current_version,
|
|
)
|
|
if self.force:
|
|
message += " (forced update)"
|
|
|
|
return self.commit_changes(message)
|
|
|
|
def load_repository(self, addon: str):
|
|
"""Load repository configuration from remote repository and add-ons."""
|
|
click.echo("Locating repository add-on list...", nl=False)
|
|
try:
|
|
config = self.github_repository.get_contents(".addons.yml")
|
|
except UnknownObjectException:
|
|
print(
|
|
"Seems like the repository does not contain an "
|
|
".addons.yml file, falling back to legacy file."
|
|
)
|
|
try:
|
|
config = self.github_repository.get_contents(".hassio-addons.yml")
|
|
except UnknownObjectException:
|
|
print(
|
|
"Seems like the repository does not contain an "
|
|
".hassio-addons.yml file either."
|
|
)
|
|
sys.exit(1)
|
|
|
|
config = yaml.safe_load(config.decoded_content)
|
|
click.echo(crayons.green("Loaded!"))
|
|
|
|
if config["channel"] not in CHANNELS:
|
|
click.echo(
|
|
crayons.red(
|
|
'Channel "%s" is not a valid channel identifier' % config["channel"]
|
|
)
|
|
)
|
|
sys.exit(1)
|
|
|
|
self.channel = config["channel"]
|
|
click.echo("Repository channel: %s" % crayons.magenta(self.channel))
|
|
|
|
if addon:
|
|
click.echo(crayons.yellow('Only updating addon "%s" this run!' % addon))
|
|
|
|
click.echo("Start loading repository add-ons:")
|
|
for target, addon_config in config["addons"].items():
|
|
click.echo(crayons.cyan("-" * 50, bold=True))
|
|
click.echo(crayons.cyan(f"Loading add-on {target}"))
|
|
self.addons.append(
|
|
Addon(
|
|
self.github,
|
|
self.git_repo,
|
|
target,
|
|
addon_config["image"],
|
|
self.github.get_repo(addon_config["repository"]),
|
|
addon_config["target"],
|
|
self.channel,
|
|
(
|
|
not addon
|
|
or addon_config["repository"] == addon
|
|
or target == addon
|
|
),
|
|
)
|
|
)
|
|
click.echo(crayons.cyan("-" * 50, bold=True))
|
|
click.echo("Done loading all repository add-ons")
|
|
|
|
def clone_repository(self):
|
|
"""Clone the add-on repository to a local working directory."""
|
|
click.echo("Cloning add-on repository...", nl=False)
|
|
self.git_repo = self.github.clone(
|
|
self.github_repository, tempfile.mkdtemp(prefix="repoupdater")
|
|
)
|
|
click.echo(crayons.green("Cloned!"))
|
|
|
|
def generate_readme(self):
|
|
"""Re-generate the repository readme based on a template."""
|
|
click.echo("Re-generating add-on repository README.md file...", nl=False)
|
|
|
|
if not os.path.exists(os.path.join(self.git_repo.working_dir, ".README.j2")):
|
|
click.echo(crayons.blue("skipping"))
|
|
return
|
|
|
|
addon_data = []
|
|
for addon in self.addons:
|
|
data = addon.get_template_data()
|
|
if data:
|
|
addon_data.append(addon.get_template_data())
|
|
|
|
addon_data = sorted(addon_data, key=lambda x: x["name"])
|
|
|
|
jinja = Environment(
|
|
loader=FileSystemLoader(self.git_repo.working_dir),
|
|
trim_blocks=True,
|
|
extensions=["jinja2.ext.loopcontrols"],
|
|
)
|
|
|
|
with open(
|
|
os.path.join(self.git_repo.working_dir, "README.md"), "w", encoding="utf8"
|
|
) as outfile:
|
|
outfile.write(
|
|
jinja.get_template(".README.j2").render(
|
|
addons=addon_data,
|
|
channel=self.channel,
|
|
description=self.github_repository.description,
|
|
homepage=self.github_repository.homepage,
|
|
issues=self.github_repository.issues_url,
|
|
name=self.github_repository.full_name,
|
|
repo=self.github_repository.html_url,
|
|
)
|
|
)
|
|
|
|
click.echo(crayons.green("Done"))
|
|
|
|
def cleanup(self):
|
|
"""Cleanup after you leave."""
|
|
click.echo("Cleanup...", nl=False)
|
|
shutil.rmtree(self.git_repo.working_dir, True)
|
|
click.echo(crayons.green("Done"))
|