|
| 1 | +import argparse |
| 2 | +import json |
| 3 | +import re |
| 4 | +import sys |
| 5 | + |
| 6 | +from distutils.util import strtobool |
| 7 | +from helpers import * |
| 8 | +from images_api import * |
| 9 | + |
| 10 | + |
| 11 | +def parse_args(): |
| 12 | + parser = argparse.ArgumentParser(description='Returns list of Docker images to build for a given workflow') |
| 13 | + parser.add_argument('-i', '--images', required=True, help='Comma-separated docker images') |
| 14 | + parser.add_argument('-d', '--dockerfiles_root', required=True, help='Path to dockerfiles') |
| 15 | + parser.add_argument('-r', '--registry', required=True, help='Docker registry name') |
| 16 | + parser.add_argument('-s', '--commit', required=False, help='Commit SHA. If not set, --pr is used') |
| 17 | + parser.add_argument('-b', '--docker_builder', required=False, help='Docker buildx builder name') |
| 18 | + parser.add_argument('--pr', type=int, required=False, help='PR number, if event is pull_request') |
| 19 | + parser.add_argument('--head_tag_file', default='.github/dockerfiles/docker_tag', help='Head docker tag file path') |
| 20 | + parser.add_argument('--base_tag_file', default=None, required=False, help='Base docker tag file path') |
| 21 | + parser.add_argument('--ref_name', required=False, default='', help='GitHub ref name') |
| 22 | + parser.add_argument('--repo', default='openvinotoolkit/openvino', help='GitHub repository') |
| 23 | + parser.add_argument('--docker_env_changed', type=lambda x: bool(strtobool(x)), default=True, |
| 24 | + help='Whether PR changes docker env') |
| 25 | + parser.add_argument('--dockerfiles_changed', type=lambda x: bool(strtobool(x)), default=True, |
| 26 | + help='Whether PR changes dockerfiles') |
| 27 | + parser.add_argument('--action_path', default='.github/actions/handle_docker', help='Path to this GitHub action') |
| 28 | + parser.add_argument('--push', action='store_true', required=False, help='Whether to push images to registry') |
| 29 | + parser.add_argument('--dry_run', action='store_true', required=False, help='Dry run') |
| 30 | + args = parser.parse_args() |
| 31 | + return args |
| 32 | + |
| 33 | + |
| 34 | +def main(): |
| 35 | + init_logger() |
| 36 | + logger = logging.getLogger(__name__) |
| 37 | + args = parse_args() |
| 38 | + for arg, value in sorted(vars(args).items()): |
| 39 | + logger.info(f"Argument {arg}: {value}") |
| 40 | + |
| 41 | + head_tag = Path(args.head_tag_file).read_text().strip() |
| 42 | + |
| 43 | + base_tag_exists = args.base_tag_file and Path(args.base_tag_file).exists() |
| 44 | + base_tag = Path(args.base_tag_file).read_text().strip() if base_tag_exists else None |
| 45 | + |
| 46 | + all_dockerfiles = Path(args.dockerfiles_root).rglob('**/*/Dockerfile') |
| 47 | + |
| 48 | + images = ImagesHandler(args.dry_run) |
| 49 | + for image in all_dockerfiles: |
| 50 | + images.add_from_dockerfile(image, args.dockerfiles_root, args.registry, head_tag, base_tag) |
| 51 | + |
| 52 | + requested_images = set(args.images.split(',')) |
| 53 | + skip_workflow = False |
| 54 | + missing_only = False |
| 55 | + |
| 56 | + merge_queue_target_branch = next(iter(re.findall(f'^gh-readonly-queue/(.*)/', args.ref_name)), None) |
| 57 | + |
| 58 | + if args.pr: |
| 59 | + environment_affected = args.docker_env_changed or args.dockerfiles_changed |
| 60 | + if environment_affected: |
| 61 | + expected_tag = f'pr-{args.pr}' |
| 62 | + |
| 63 | + if head_tag != expected_tag: |
| 64 | + logger.error(f"Please update docker tag in {args.head_tag_file} to {expected_tag}") |
| 65 | + sys.exit(1) |
| 66 | + |
| 67 | + elif merge_queue_target_branch: |
| 68 | + environment_affected = head_tag != base_tag |
| 69 | + if environment_affected: |
| 70 | + logger.info(f"Environment is affected by PR(s) in merge group") |
| 71 | + else: |
| 72 | + environment_affected = False |
| 73 | + |
| 74 | + if environment_affected: |
| 75 | + changeset = get_changeset(args.repo, args.pr, merge_queue_target_branch, args.commit) |
| 76 | + changed_dockerfiles = [p for p in changeset if p.startswith(args.dockerfiles_root) and p.endswith('Dockerfile')] |
| 77 | + |
| 78 | + if args.docker_env_changed: |
| 79 | + logger.info(f"Common docker environment is modified, will build all requested images") |
| 80 | + changed_images = requested_images |
| 81 | + else: |
| 82 | + logger.info(f"Common docker environment is not modified, will build only changed and missing images") |
| 83 | + changed_images = set([name_from_dockerfile(d, args.dockerfiles_root) for d in changed_dockerfiles]) |
| 84 | + |
| 85 | + unchanged_images = requested_images - changed_images |
| 86 | + unchanged_with_no_base = images.get_missing(unchanged_images, base=True) |
| 87 | + |
| 88 | + if unchanged_with_no_base: |
| 89 | + logger.info("The following images were unchanged, but will be built anyway since the base for them " |
| 90 | + f"is missing in registry: {unchanged_with_no_base}") |
| 91 | + |
| 92 | + images_to_tag = unchanged_images.difference(unchanged_with_no_base) |
| 93 | + images_to_build = requested_images.intersection(changed_images).union(unchanged_with_no_base) |
| 94 | + |
| 95 | + only_dockerfiles_changed = len(changeset) == len(changed_dockerfiles) |
| 96 | + if only_dockerfiles_changed and not images_to_build: |
| 97 | + skip_workflow = True |
| 98 | + else: |
| 99 | + logger.info(f"Environment is not affected, will build only missing images, if any") |
| 100 | + images_to_build = requested_images |
| 101 | + images_to_tag = [] |
| 102 | + missing_only = True |
| 103 | + |
| 104 | + if not images_to_build: |
| 105 | + logger.info(f"No images to build, will return the list of pre-built images with a new tag") |
| 106 | + |
| 107 | + built_images = images.build(images_to_build, missing_only, args.push, args.docker_builder) |
| 108 | + if not built_images: |
| 109 | + logger.info(f"No images were built, a new tag will be applied to a pre-built base image if needed") |
| 110 | + |
| 111 | + # When a custom builder is used, it allows to push the image automatically once built. Otherwise, pushing manually |
| 112 | + if args.push and not args.docker_builder: |
| 113 | + images.push(images_to_build, missing_only) |
| 114 | + |
| 115 | + if environment_affected and base_tag: |
| 116 | + images.tag(images_to_tag) |
| 117 | + |
| 118 | + images_output = images_to_output(images.get(requested_images)) |
| 119 | + set_github_output("images", json.dumps(images_output)) |
| 120 | + |
| 121 | + if skip_workflow: |
| 122 | + logger.info(f"Docker image changes are irrelevant for current workflow, workflow may be skipped") |
| 123 | + set_github_output("skip_workflow", str(skip_workflow)) |
| 124 | + |
| 125 | + |
| 126 | +main() |
0 commit comments