Include resourcresource diff in PR description
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
Dustin 2024-09-05 21:19:23 -05:00
parent 34fbdc6e02
commit e138f25f3e
1 changed files with 56 additions and 14 deletions

View File

@ -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__':