Include resourcresource diff in PR description
infra/updatebot/pipeline/head This commit looks good
Details
infra/updatebot/pipeline/head This commit looks good
Details
Naturally, the PR will include the diff of the configuration changes the update process makes, but that doesn't necessarily show what will actually change in the cluster. This is true of the `images` setting in Kustomize configuration, and will become even more important when we start updating remote manifest references. To get a better idea of what will actually change when the update is applied, we now try to run `kubectl diff` for each project after making all changes. The output is then included in the PR description.master
parent
34fbdc6e02
commit
e138f25f3e
64
updatebot.py
64
updatebot.py
|
@ -5,6 +5,7 @@ import logging
|
||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
@ -130,6 +131,10 @@ class BaseProject(abc.ABC, pydantic.BaseModel):
|
||||||
def apply_updates(self, basedir: Path) -> Iterable[tuple[ImageDef, str]]:
|
def apply_updates(self, basedir: Path) -> Iterable[tuple[ImageDef, str]]:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def resource_diff(self, basedir: Path) -> Optional[str]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class KustomizeProject(BaseProject):
|
class KustomizeProject(BaseProject):
|
||||||
kind: Literal['kustomize']
|
kind: Literal['kustomize']
|
||||||
|
@ -169,6 +174,25 @@ class KustomizeProject(BaseProject):
|
||||||
yaml.dump(kustomization, f)
|
yaml.dump(kustomization, f)
|
||||||
yield (image, version)
|
yield (image, version)
|
||||||
|
|
||||||
|
def resource_diff(self, basedir: Path) -> Optional[str]:
|
||||||
|
path = basedir / self.path
|
||||||
|
cmd = ['kubectl', 'diff', '-k', path]
|
||||||
|
try:
|
||||||
|
p = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
encoding='utf-8',
|
||||||
|
)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
log.warning('Cannot generate resource diff: %s', e)
|
||||||
|
return None
|
||||||
|
if p.returncode != 0 and not p.stdout:
|
||||||
|
log.error('Failed to generate resource diff: %s', p.stderr)
|
||||||
|
return None
|
||||||
|
return p.stdout
|
||||||
|
|
||||||
|
|
||||||
Project = KustomizeProject
|
Project = KustomizeProject
|
||||||
|
|
||||||
|
@ -204,7 +228,11 @@ class RepoConfig(pydantic.BaseModel):
|
||||||
return data['clone_url']
|
return data['clone_url']
|
||||||
|
|
||||||
def create_pr(
|
def create_pr(
|
||||||
self, title: str, source_branch: str, target_branch: str
|
self,
|
||||||
|
title: str,
|
||||||
|
source_branch: str,
|
||||||
|
target_branch: str,
|
||||||
|
body: Optional[str] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
session = _get_session()
|
session = _get_session()
|
||||||
r = session.post(
|
r = session.post(
|
||||||
|
@ -216,6 +244,7 @@ class RepoConfig(pydantic.BaseModel):
|
||||||
'title': title,
|
'title': title,
|
||||||
'base': target_branch,
|
'base': target_branch,
|
||||||
'head': source_branch,
|
'head': source_branch,
|
||||||
|
'body': body,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
log.log(TRACE, '%r', r.content)
|
log.log(TRACE, '%r', r.content)
|
||||||
|
@ -244,10 +273,12 @@ class Arguments:
|
||||||
projects: list[str]
|
projects: list[str]
|
||||||
|
|
||||||
|
|
||||||
def update_project(repo: git.Repo, project: Project) -> Optional[str]:
|
def update_project(
|
||||||
|
repo: git.Repo, project: Project
|
||||||
|
) -> tuple[Optional[str], Optional[str]]:
|
||||||
basedir = Path(repo.working_dir)
|
basedir = Path(repo.working_dir)
|
||||||
title = None
|
title = None
|
||||||
for (image, version) in project.apply_updates(basedir):
|
for image, version in project.apply_updates(basedir):
|
||||||
log.info('Updating %s to %s', image.name, version)
|
log.info('Updating %s to %s', image.name, version)
|
||||||
if repo.index.diff(None):
|
if repo.index.diff(None):
|
||||||
log.debug('Committing changes to %s', project.path)
|
log.debug('Committing changes to %s', project.path)
|
||||||
|
@ -261,7 +292,8 @@ def update_project(repo: git.Repo, project: Project) -> Optional[str]:
|
||||||
title = c.summary
|
title = c.summary
|
||||||
else:
|
else:
|
||||||
log.info('No changes to commit')
|
log.info('No changes to commit')
|
||||||
return title
|
diff = project.resource_diff(basedir)
|
||||||
|
return title, diff
|
||||||
|
|
||||||
|
|
||||||
def parse_args() -> Arguments:
|
def parse_args() -> Arguments:
|
||||||
|
@ -322,29 +354,39 @@ def main() -> None:
|
||||||
log.debug('Checking out new branch: %s', args.branch_name)
|
log.debug('Checking out new branch: %s', args.branch_name)
|
||||||
repo.heads[0].checkout(force=True, B=args.branch_name)
|
repo.heads[0].checkout(force=True, B=args.branch_name)
|
||||||
title = None
|
title = None
|
||||||
|
description = None
|
||||||
if project.name not in projects:
|
if project.name not in projects:
|
||||||
continue
|
continue
|
||||||
title = update_project(repo, project)
|
title, diff = update_project(repo, project)
|
||||||
if not title:
|
if not title:
|
||||||
log.info('No changes made')
|
log.info('No changes made')
|
||||||
continue
|
continue
|
||||||
|
if diff:
|
||||||
|
description = (
|
||||||
|
'<details>\n<summary>Resource diff</summary>\n\n'
|
||||||
|
f'```diff\n{diff}```\n'
|
||||||
|
'</details>'
|
||||||
|
)
|
||||||
|
if not args.dry_run:
|
||||||
repo.head.reference.set_tracking_branch(
|
repo.head.reference.set_tracking_branch(
|
||||||
git.RemoteReference(
|
git.RemoteReference(
|
||||||
repo, f'refs/remotes/origin/{args.branch_name}'
|
repo, f'refs/remotes/origin/{args.branch_name}'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if not args.dry_run:
|
|
||||||
repo.remote().push(force=True)
|
repo.remote().push(force=True)
|
||||||
config.repo.create_pr(
|
config.repo.create_pr(
|
||||||
title, args.branch_name, config.repo.branch
|
title,
|
||||||
|
args.branch_name,
|
||||||
|
config.repo.branch,
|
||||||
|
description,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log.info(
|
print(
|
||||||
'Would create PR %s → %s: %s',
|
'Would create PR',
|
||||||
config.repo.branch,
|
f'{args.branch_name} → {config.repo.branch}:',
|
||||||
args.branch_name,
|
|
||||||
title,
|
title,
|
||||||
)
|
)
|
||||||
|
print(description or '')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in New Issue