def _set_pr_labels(self, pr_event, resources) -> bool: ''' @ return True if the required label was set ''' required_labels = { resource.source.get('label') for resource in resources if resource.source.get('label') is not None } if not required_labels: return False repo = pr_event.repository() repository_path = repo.repository_path() pr_number = pr_event.number() github_cfg = self.cfg_set.github() owner, name = repository_path.split('/') github_helper = GitHubRepositoryHelper( owner, name, github_cfg=github_cfg, ) sender_login = pr_event.sender()['login'] if pr_event.action() is PullRequestAction.OPENED: if github_helper.is_pr_created_by_org_member(pr_number): logger.info( f"New pull request by member of '{owner}' in '{repository_path}' found. " f"Setting required labels '{required_labels}'.") github_helper.add_labels_to_pull_request( pr_number, *required_labels) return True else: logger.debug( f"New pull request by member in '{repository_path}' found, but creator is not " f"member of '{owner}' - will not set required labels.") github_helper.add_comment_to_pr( pull_request_number=pr_number, comment= (f"Thank you @{sender_login} for your contribution. Before I can start " "building your PR, a member of the organization must set the required " f"label(s) {required_labels}. Once started, you can check the build " "status in the PR checks section below.")) return False elif pr_event.action() is PullRequestAction.SYNCHRONIZE: if github_helper.is_org_member(organization_name=owner, user_login=sender_login): logger.info( f"Update to pull request #{pr_event.number()} by org member '{sender_login}' " f" in '{repository_path}' found. Setting required labels '{required_labels}'." ) github_helper.add_labels_to_pull_request( pr_number, *required_labels) return True else: logger.debug( f"Update to pull request #{pr_event.number()} by '{sender_login}' " f" in '{repository_path}' found. Ignoring, since they are not an org member'." ) return False return False
def _set_pr_labels(self, pr_event, resources): required_labels = { resource.source.get('label') for resource in resources if resource.source.get('label') is not None } repo = pr_event.repository() repository_path = repo.repository_path() pr_number = pr_event.number() github_cfg = self.cfg_set.github() owner, name = repository_path.split('/') github_helper = GitHubRepositoryHelper( owner, name, github_cfg=github_cfg, ) if github_helper.is_pr_created_by_org_member(pr_number): app.logger.info( f"New pull request by member of '{owner}' in '{repository_path}' found. " f"Setting required labels '{required_labels}'." ) github_helper.add_labels_to_pull_request(pr_number, *required_labels) else: app.logger.debug( f"New pull request by member in '{repository_path}' found, but creator is not " f"member of '{owner}' - will not set required labels." )
def delete_releases( github_cfg_name: str, github_repository_owner: str, github_repository_name: str, release_name: [str], ): github_cfg = ctx().cfg_factory().github(github_cfg_name) github_helper = GitHubRepositoryHelper( owner=github_repository_owner, name=github_repository_name, github_cfg=github_cfg, ) github_helper.delete_releases(release_names=release_name)
def fetch_release_notes_from_prs( github_helper: GitHubRepositoryHelper, pr_numbers_in_range: typing.Set[str], cn_current_repo: ComponentName) -> [ReleaseNote]: # we should consider adding a release-note label to the PRs # to reduce the number of search results prs_iter = github_helper.search_issues_in_repo('type:pull is:closed') release_notes = list() for pr_iter in prs_iter: pr_dict = pr_iter.as_dict() pr_number = str(pr_dict['number']) if pr_number not in pr_numbers_in_range: continue release_notes_pr = extract_release_notes( reference_id=pr_number, text=pr_dict['body'], user_login=_.get(pr_dict, 'user.login'), cn_current_repo=cn_current_repo, reference_type=REF_TYPE_PULL_REQUEST) if not release_notes_pr: continue release_notes.extend(release_notes_pr) return release_notes
def release_tags(github_helper: GitHubRepositoryHelper, repo: git.Repo) -> [str]: def is_valid_semver(tag_name): try: parse_version_info(tag_name) return True except ValueError: warning('{tag} is not a valid SemVer string'.format(tag=tag_name)) return False release_tags = github_helper.release_tags() # you can remove the directive to disable the undefined-variable error once pylint is updated # with fix https://github.com/PyCQA/pylint/commit/db01112f7e4beadf7cd99c5f9237d580309f0494 # included # pylint: disable=undefined-variable tags = _ \ .chain(repo.tags) \ .map(lambda tag: {"tag": tag.name, "commit": tag.commit.hexsha}) \ .filter(lambda item: _.find(release_tags, lambda el: el == item['tag'])) \ .filter(lambda item: is_valid_semver(item['tag'])) \ .key_by('commit') \ .map_values('tag') \ .value() # pylint: enable=undefined-variable return tags
def github_helper_from_github_access( github_access=gci.componentmodel.GithubAccess, ): return GitHubRepositoryHelper( github_api=ccc.github.github_api_from_gh_access(github_access), owner=github_access.org_name(), name=github_access.repository_name(), )
def _notify_broken_definition_owners(self, failed_descriptor): definition_descriptor = failed_descriptor.definition_descriptor main_repo = definition_descriptor.main_repo github_cfg = github_cfg_for_hostname(self._cfg_set, main_repo['hostname']) github_api = _create_github_api_object(github_cfg) repo_owner, repo_name = main_repo['path'].split('/') githubrepobranch = GitHubRepoBranch( github_config=github_cfg, repo_owner=repo_owner, repo_name=repo_name, branch=main_repo['branch'], ) repo_helper = GitHubRepositoryHelper.from_githubrepobranch( githubrepobranch=githubrepobranch, ) codeowners_enumerator = CodeownersEnumerator() codeowners_resolver = CodeOwnerEntryResolver(github_api=github_api) recipients = set( codeowners_resolver.resolve_email_addresses( codeowners_enumerator.enumerate_remote_repo( github_repo_helper=repo_helper))) # in case no codeowners are available, resort to using the committer if not recipients: head_commit = repo_helper.repository.commit(main_repo['branch']) user_ids = { user_info.get('login') for user_info in (head_commit.committer, head_commit.author) if user_info.get('login') } for user_id in user_ids: user = github_api.user(user_id) if user.email: recipients.add(user.email) # if there are still no recipients available print a warning if not recipients: warning( textwrap.dedent(f""" Unable to determine recipient for pipeline '{definition_descriptor.pipeline_name}' found in branch '{main_repo['branch']}' ({main_repo['path']}). Please make sure that CODEOWNERS and committers have exposed a public e-mail address in their profile. """)) else: info( f'Sending notification e-mail to {recipients} ({main_repo["path"]})' ) email_cfg = self._cfg_set.email() _send_mail( email_cfg=email_cfg, recipients=recipients, subject='Your pipeline definition in {repo} is erroneous'. format(repo=main_repo['path'], ), mail_template= (f"The pipeline definition for pipeline '{definition_descriptor.pipeline_name}' " f" on branch '{main_repo['branch']}' contains errors.\n\n" f"Error details:\n{str(failed_descriptor.error_details)}"))
def github_api_for_pr_event( pr_event: PullRequestEvent, cfg_set: model.ConfigurationSet, ): repo = pr_event.repository() github_host = repo.github_host() repository_path = repo.repository_path() github_cfg = ccc.github.github_cfg_for_repo_url( repo_url=ci.util.urljoin(github_host, repository_path), cfg_factory=cfg_set, ) owner, name = repository_path.split('/') try: github_helper = GitHubRepositoryHelper( owner=owner, name=name, github_cfg=github_cfg, ) except NotFoundError: logger.warning( f"Unable to access repository '{repository_path}' on github '{github_host}'. " "Please make sure the repository exists and the technical user has the necessary " "permissions to access it." ) return None return github_helper
def generate_release_notes_cli( repo_dir: str, github_cfg_name: str, github_repository_owner: str, github_repository_name: str, repository_branch: str, commit_range: str=None ): github_cfg = ctx().cfg_factory().github(github_cfg_name) githubrepobranch = GitHubRepoBranch( github_config=github_cfg, repo_owner=github_repository_owner, repo_name=github_repository_name, branch=repository_branch, ) helper = GitHubRepositoryHelper.from_githubrepobranch( githubrepobranch=githubrepobranch, ) git_helper = GitHelper.from_githubrepobranch( repo_path=repo_dir, githubrepobranch=githubrepobranch, ) ReleaseNotes.create( github_helper=helper, git_helper=git_helper, repository_branch=repository_branch, commit_range=commit_range ).to_markdown()
def enumerate_remote_repo(self, github_repo_helper: GitHubRepositoryHelper): for path in self.CODEOWNERS_PATHS: try: yield from self._filter_codeowners_entries( github_repo_helper.retrieve_text_file_contents(file_path=path).split('\n') ) except NotFoundError: pass # ignore absent files
def github_helper_from_github_access( github_access=gci.componentmodel.GithubAccess, ): logger.info(f'Creating GH Repo-helper for {github_access.repoUrl}') return GitHubRepositoryHelper( github_api=ccc.github.github_api_from_gh_access(github_access), owner=github_access.org_name(), name=github_access.repository_name(), )
def validate_pipeline_definitions( cfg_factory, cfg_set: model.ConfigurationSet, whd_cfg: model.webhook_dispatcher.WebhookDispatcherConfig, pr_event: PullRequestEvent, github_helper: GitHubRepositoryHelper, tagging_label: str, ): repo_url = pr_event.repository().repository_url() job_mapping_set = cfg_set.job_mapping() job_mapping = job_mapping_set.job_mapping_for_repo_url(repo_url, cfg_set) pr_number = pr_event.number() try: validate_repository_pipelines( repo_url=pr_event.head_repository().repository_url(), cfg_set=cfg_factory.cfg_set(job_mapping.replication_ctx_cfg_set()), whd_cfg=whd_cfg, branch=pr_event.head_ref(), job_mapping=job_mapping, ) # validation succeeded. Remove the label again, if it is currently set. if tagging_label in pr_event.label_names(): logger.info( f'Pipeline-definition in PR #{pr_number} of repository {repo_url} passed ' 'validation again. Commenting on PR.' ) github_helper.remove_label_from_pull_request(pr_number, tagging_label) github_helper.add_comment_to_pr( pull_request_number=pr_number, comment='The pipeline-definition has been fixed.', ) except concourse.replicator.PipelineValidationError as e: # If validation fails add a comment on the PR iff we haven't already commented, as # tracked by label if tagging_label not in pr_event.label_names(): logger.warning( f'Pipeline-definition in PR #{pr_number} of repository {repo_url} failed ' 'validation. Commenting on PR.' ) github_helper.add_comment_to_pr( pull_request_number=pr_number, comment=( 'This PR proposes changes that would break the pipeline definition:\n' f'```\n{e}\n```\n' ), ) github_helper.add_labels_to_pull_request(pr_number, tagging_label)
def greatest_release_version( github_repository_url: CliHint(help='e.g.: https://github.com/gardener/cc-utils'), anonymous: CliHint( typehint=bool, help='Use anonymous access. Unauthenticated access is only possible on github.com.', ) = False, ignore_prereleases: CliHint( typehint=bool, help='Ignore prerelease-versions (e.g.: 1.2.3-foo)', ) = False, ): '''Find the release with the greatest name (according to semver) and print its semver-version. Note: - This will only consider releases whose names are either immediately parseable as semver- versions, or prefixed with a single character ('v'). - The 'v'-prefix (if present) will be not be present in the output. - If a release has no name, its tag will be used instead of its name. For more details on the ordering of semantic versioning, see 'https://www.semver.org'. ''' parse_result = urllib.parse.urlparse(github_repository_url) if not parse_result.netloc: raise ValueError(f'Could not determine host for github-url {github_repository_url}') host = parse_result.netloc try: path = parse_result.path.strip('/') org, repo = path.split('/') except ValueError as e: raise ValueError(f"Could not extract org- and repo-name. Error: {e}") if anonymous: if 'github.com' not in host: raise ValueError("Anonymous access is only possible for github.com") github_api = github3.GitHub() repo_helper = GitHubRepositoryHelper(owner=org, name=repo, github_api=github_api) else: repo_helper = ccc.github.repo_helper(host=host, org=org, repo=repo) print( find_greatest_github_release_version( releases=repo_helper.repository.releases(), warn_for_unparseable_releases=False, ignore_prerelease_versions=ignore_prereleases, ) )
def list_draft_releases( github_cfg_name: str, github_repository_owner: str, github_repository_name: str, only_outdated: bool = False, ): '''List all draft releases in a GitHub repository. If the `--only-outdated` flag is set, only outdated draft releases are printed. A draft release is considered outdated iff: 1: its version is smaller than the greatest release version (according to semver) AND 2a: it is NOT a hotfix draft release AND 2b: there are no hotfix draft releases with the same major and minor version OR 3a: it is a hotfix draft release AND 3b: there is a hotfix draft release of greater version (according to semver) with the same major and minor version Hotfix draft release in this context are draft releases with a semver patch version that is not equal to 0. ''' github_cfg = ctx().cfg_factory().github(github_cfg_name) github_helper = GitHubRepositoryHelper( owner=github_repository_owner, name=github_repository_name, github_cfg=github_cfg, ) if only_outdated: releases = [release for release in github_helper.repository.releases()] non_draft_releases = [ release for release in releases if not release.draft ] greatest_release_version = find_greatest_github_release_version( non_draft_releases) else: releases = github_helper.repository.releases() draft_releases = [release for release in releases if release.draft] if only_outdated: if greatest_release_version is not None: draft_releases = outdated_draft_releases( draft_releases=draft_releases, greatest_release_version=greatest_release_version, ) else: draft_releases = [] for draft_release in draft_releases: print(draft_release.name)
def _set_pr_labels(self, pr_event, resources): required_labels = { resource.source.get('label') for resource in resources if resource.source.get('label') is not None } repo = pr_event.repository() repository_path = repo.repository_path() pr_number = pr_event.number() github_cfg = self.cfg_set.github() owner, name = repository_path.split('/') github_helper = GitHubRepositoryHelper( owner, name, github_cfg=github_cfg, ) if pr_event.action() is PullRequestAction.OPENED: if github_helper.is_pr_created_by_org_member(pr_number): app.logger.info( f"New pull request by member of '{owner}' in '{repository_path}' found. " f"Setting required labels '{required_labels}'.") github_helper.add_labels_to_pull_request( pr_number, *required_labels) else: app.logger.debug( f"New pull request by member in '{repository_path}' found, but creator is not " f"member of '{owner}' - will not set required labels.") elif pr_event.action() is PullRequestAction.SYNCHRONIZE: sender_login = pr_event.sender()['login'] if github_helper.is_org_member(organization_name=owner, user_login=sender_login): app.logger.info( f"Update to pull request #{pr_event.number()} by org member '{sender_login}' " f" in '{repository_path}' found. Setting required labels '{required_labels}'." ) github_helper.add_labels_to_pull_request( pr_number, *required_labels) else: app.logger.debug( f"Update to pull request #{pr_event.number()} by '{sender_login}' " f" in '{repository_path}' found. Ignoring, since they are not an org member'." )
def deploy_and_run_smoketest_pipeline( config_dir: str, config_name: str, concourse_team_name: str, cc_pipelines_repo_dir: str, cc_utils_repo_dir: str, wait_for_job_execution: bool=False, ): config_factory = ConfigFactory.from_cfg_dir(cfg_dir=config_dir) config_set = config_factory.cfg_set(cfg_name=config_name) concourse_cfg = config_set.concourse() # as this is an integration test, hard-code assumptions about the layout of # our pipelines repository template_path = os.path.join(cc_utils_repo_dir, 'concourse', 'templates') template_include_dir = os.path.join(cc_utils_repo_dir, 'concourse') pipeline_name = 'cc-smoketest' # retrieve pipeline-definition from github at hardcoded location github_cfg = config_set.github() githubrepobranch = GitHubRepoBranch( github_config=github_cfg, repo_owner='kubernetes', repo_name='cc-smoketest', branch='master', ) helper = GitHubRepositoryHelper.from_githubrepobranch( githubrepobranch=githubrepobranch, ) pipeline_definition = yaml.load( helper.retrieve_text_file_contents( file_path='.ci/smoketest-pipeline.yaml', ), Loader=yaml.SafeLoader, ) definition_descriptor = DefinitionDescriptor( pipeline_name=pipeline_name, pipeline_definition=pipeline_definition[pipeline_name], main_repo={'path': 'kubernetes/cc-smoketest', 'branch': 'master'}, concourse_target_cfg=concourse_cfg, concourse_target_team=concourse_team_name, ) preprocessor = DefinitionDescriptorPreprocessor() template_retriever = TemplateRetriever(template_path=template_path) renderer = Renderer( template_retriever=template_retriever, template_include_dir=template_include_dir, cfg_set=config_set, ) deployer = ConcourseDeployer( unpause_pipelines=True, expose_pipelines=True ) definition_descriptor = preprocessor.process_definition_descriptor(definition_descriptor) rendering_result = renderer.render(definition_descriptor) info('deploying pipeline') deployment_result = deployer.deploy(rendering_result.definition_descriptor) if not deployment_result.deploy_status & DeployStatus.SUCCEEDED: fail('deployment failed')
def release_and_prepare_next_dev_cycle( githubrepobranch: GitHubRepoBranch, repository_version_file_path: str, release_version: str, repo_dir: str, release_notes_policy: str, release_commit_publishing_policy: str, release_commit_callback: str = None, next_version_callback: str = None, version_operation: str = "bump_minor", prerelease_suffix: str = "dev", author_name: str = "gardener-ci", author_email: str = "*****@*****.**", component_descriptor_file_path: str = None, slack_cfg_name: str = None, slack_channel: str = None, rebase_before_release: bool = False, commit_message_prefix: str = None, ): transaction_ctx = TransactionContext() # shared between all steps/trxs release_notes_policy = ReleaseNotesPolicy(release_notes_policy) release_commit_publishing_policy = ReleaseCommitPublishingPolicy( release_commit_publishing_policy) github_helper = GitHubRepositoryHelper.from_githubrepobranch( githubrepobranch) git_helper = GitHelper.from_githubrepobranch( githubrepobranch=githubrepobranch, repo_path=repo_dir, ) step_list = [] if rebase_before_release: rebase_step = RebaseStep( git_helper=git_helper, repository_branch=githubrepobranch.branch(), ) step_list.append(rebase_step) release_commit_step = ReleaseCommitStep( git_helper=git_helper, repo_dir=repo_dir, release_version=release_version, repository_version_file_path=repository_version_file_path, repository_branch=githubrepobranch.branch(), commit_message_prefix=commit_message_prefix, release_commit_callback=release_commit_callback, publishing_policy=release_commit_publishing_policy, ) step_list.append(release_commit_step) if version_operation != version.NOOP: next_cycle_commit_step = NextDevCycleCommitStep( git_helper=git_helper, repo_dir=repo_dir, release_version=release_version, repository_version_file_path=repository_version_file_path, repository_branch=githubrepobranch.branch(), version_operation=version_operation, prerelease_suffix=prerelease_suffix, next_version_callback=next_version_callback, publishing_policy=release_commit_publishing_policy, ) step_list.append(next_cycle_commit_step) github_release_step = GitHubReleaseStep( github_helper=github_helper, githubrepobranch=githubrepobranch, repo_dir=repo_dir, release_version=release_version, component_descriptor_file_path=component_descriptor_file_path, ) step_list.append(github_release_step) release_transaction = Transaction(ctx=transaction_ctx, steps=step_list) release_transaction.validate() if not release_transaction.execute(): raise RuntimeError('An error occurred while creating the Release.') publish_release_notes_step = PublishReleaseNotesStep( githubrepobranch=githubrepobranch, github_helper=github_helper, release_version=release_version, repo_dir=repo_dir, ) cleanup_draft_releases_step = TryCleanupDraftReleasesStep( github_helper=github_helper, ) cleanup_draft_releases_transaction = Transaction( ctx=transaction_ctx, steps=(cleanup_draft_releases_step, ), ) if not cleanup_draft_releases_transaction.execute(): ci.util.warning('An error occured while cleaning up draft releases') if release_notes_policy == ReleaseNotesPolicy.DISABLED: return info('release notes were disabled - skipping') elif release_notes_policy == ReleaseNotesPolicy.DEFAULT: pass else: raise NotImplementedError(release_notes_policy) release_notes_transaction = Transaction( ctx=transaction_ctx, steps=(publish_release_notes_step, ), ) release_notes_transaction.validate() if not release_notes_transaction.execute(): raise RuntimeError( 'An error occurred while publishing the release notes.') if slack_cfg_name and slack_channel: release_notes = transaction_ctx.step_output( publish_release_notes_step.name()).get('release notes') post_to_slack_step = PostSlackReleaseStep( slack_cfg_name=slack_cfg_name, slack_channel=slack_channel, release_version=release_version, release_notes=release_notes, githubrepobranch=githubrepobranch, ) slack_transaction = Transaction( ctx=transaction_ctx, steps=(post_to_slack_step, ), ) slack_transaction.validate() if not slack_transaction.execute(): raise RuntimeError( 'An error occurred while posting the release notes to Slack.')
def release_and_prepare_next_dev_cycle( githubrepobranch: GitHubRepoBranch, repository_version_file_path: str, release_version: str, repo_dir: str, release_notes_policy: str, release_commit_callback: str = None, next_version_callback: str = None, version_operation: str = "bump_minor", prerelease_suffix: str = "dev", author_name: str = "gardener-ci", author_email: str = "*****@*****.**", component_descriptor_file_path: str = None, slack_cfg_name: str = None, slack_channel: str = None, rebase_before_release: bool = False, ): release_notes_policy = ReleaseNotesPolicy(release_notes_policy) github_helper = GitHubRepositoryHelper.from_githubrepobranch( githubrepobranch) git_helper = GitHelper.from_githubrepobranch( githubrepobranch=githubrepobranch, repo_path=repo_dir, ) release_commits_step = ReleaseCommitsStep( git_helper=git_helper, repo_dir=repo_dir, release_version=release_version, repository_version_file_path=repository_version_file_path, repository_branch=githubrepobranch.branch(), version_operation=version_operation, prerelease_suffix=prerelease_suffix, release_commit_callback=release_commit_callback, next_version_callback=next_version_callback, rebase_before_release=rebase_before_release, ) github_release_step = GitHubReleaseStep( github_helper=github_helper, githubrepobranch=githubrepobranch, repo_dir=repo_dir, release_version=release_version, component_descriptor_file_path=component_descriptor_file_path, ) release_transaction = Transaction( release_commits_step, github_release_step, ) release_transaction.validate() if not release_transaction.execute(): raise RuntimeError('An error occurred while creating the Release.') publish_release_notes_step = PublishReleaseNotesStep( githubrepobranch=githubrepobranch, github_helper=github_helper, release_version=release_version, repo_dir=repo_dir, ) cleanup_draft_releases_step = CleanupDraftReleaseStep( github_helper=github_helper, release_version=release_version, ) if release_notes_policy == ReleaseNotesPolicy.DISABLED: return info('release notes were disabled - skipping') elif release_notes_policy == ReleaseNotesPolicy.DEFAULT: pass else: raise NotImplementedError(release_notes_policy) release_notes_steps = [ publish_release_notes_step, cleanup_draft_releases_step, ] release_notes_transaction = Transaction(*release_notes_steps) release_notes_transaction.validate() if not release_notes_transaction.execute(): raise RuntimeError( 'An error occurred while publishing the release notes.') if slack_cfg_name and slack_channel: context = release_notes_transaction.context() release_notes = context.step_output( publish_release_notes_step.name()).get('release notes') post_to_slack_step = PostSlackReleaseStep( slack_cfg_name=slack_cfg_name, slack_channel=slack_channel, release_version=release_version, release_notes=release_notes, githubrepobranch=githubrepobranch, ) slack_transaction = Transaction(post_to_slack_step) slack_transaction.validate() if not slack_transaction.execute(): raise RuntimeError( 'An error occurred while posting the release notes to Slack.')
output_dir = util.check_env('BINARY_PATH') repo_owner, repo_name = repo_owner_and_name.split('/') repo_path = pathlib.Path(repo_dir).resolve() output_path = pathlib.Path(output_dir).resolve() version_file_path = repo_path / VERSION_FILE_NAME version_file_contents = version_file_path.read_text() cfg_factory = util.ctx().cfg_factory() github_cfg = cfg_factory.github('github_com') github_repo_helper = GitHubRepositoryHelper( owner=repo_owner, name=repo_name, github_cfg=github_cfg, ) gh_release = github_repo_helper.repository.release_from_tag( version_file_contents) for dir, dirs, files in os.walk(os.path.join(output_path, "bin", "rel")): for binName in files: dir_path = pathlib.Path(dir).resolve() binFilePath = dir_path / binName gh_release.upload_asset( content_type='application/octet-stream', name=f'{binName}', asset=binFilePath.open(mode='rb'), )
def release_and_prepare_next_dev_cycle( component_name: str, githubrepobranch: GitHubRepoBranch, release_commit_publishing_policy: str, release_notes_policy: str, release_version: str, repo_hostname: str, repo_path: str, repo_dir: str, repository_version_file_path: str, git_tags: list, github_release_tag: dict, release_commit_callback_image_reference: str, component_descriptor_v2_path: str = None, ctf_path: str = None, next_cycle_commit_message_prefix: str = None, next_version_callback: str = None, prerelease_suffix: str = "dev", rebase_before_release: bool = False, release_on_github: bool = True, release_commit_callback: str = None, release_commit_message_prefix: str = None, slack_channel_configs: list = [], version_operation: str = "bump_minor", ): transaction_ctx = TransactionContext() # shared between all steps/trxs release_notes_policy = ReleaseNotesPolicy(release_notes_policy) release_commit_publishing_policy = ReleaseCommitPublishingPolicy( release_commit_publishing_policy) github_helper = GitHubRepositoryHelper.from_githubrepobranch( githubrepobranch) git_helper = GitHelper.from_githubrepobranch( githubrepobranch=githubrepobranch, repo_path=repo_dir, ) step_list = [] if rebase_before_release: rebase_step = RebaseStep( git_helper=git_helper, repository_branch=githubrepobranch.branch(), ) step_list.append(rebase_step) release_commit_step = ReleaseCommitStep( git_helper=git_helper, repo_dir=repo_dir, release_version=release_version, repository_version_file_path=repository_version_file_path, repository_branch=githubrepobranch.branch(), release_commit_message_prefix=release_commit_message_prefix, release_commit_callback=release_commit_callback, release_commit_callback_image_reference= release_commit_callback_image_reference, publishing_policy=release_commit_publishing_policy, ) step_list.append(release_commit_step) create_tag_step = CreateTagsStep( git_tags=git_tags, github_release_tag=github_release_tag, git_helper=git_helper, github_helper=github_helper, release_version=release_version, publishing_policy=release_commit_publishing_policy, ) step_list.append(create_tag_step) if version_operation != version.NOOP: next_cycle_commit_step = NextDevCycleCommitStep( git_helper=git_helper, repo_dir=repo_dir, release_version=release_version, repository_version_file_path=repository_version_file_path, repository_branch=githubrepobranch.branch(), version_operation=version_operation, prerelease_suffix=prerelease_suffix, next_version_callback=next_version_callback, publishing_policy=release_commit_publishing_policy, next_cycle_commit_message_prefix=next_cycle_commit_message_prefix, ) step_list.append(next_cycle_commit_step) if release_on_github: github_release_step = GitHubReleaseStep( github_helper=github_helper, githubrepobranch=githubrepobranch, repo_dir=repo_dir, component_name=component_name, release_version=release_version, ) step_list.append(github_release_step) upload_component_descriptor_step = UploadComponentDescriptorStep( github_helper=github_helper, component_descriptor_v2_path=component_descriptor_v2_path, ctf_path=ctf_path, release_on_github=release_on_github, ) step_list.append(upload_component_descriptor_step) release_transaction = Transaction( ctx=transaction_ctx, steps=step_list, ) release_transaction.validate() if not release_transaction.execute(): raise RuntimeError('An error occurred while creating the Release.') if release_on_github: publish_release_notes_step = PublishReleaseNotesStep( githubrepobranch=githubrepobranch, github_helper=github_helper, repository_hostname=repo_hostname, repository_path=repo_path, release_version=release_version, component_descriptor_v2_path=component_descriptor_v2_path, ctf_path=ctf_path, repo_dir=repo_dir, ) cleanup_draft_releases_step = TryCleanupDraftReleasesStep( github_helper=github_helper, ) cleanup_draft_releases_transaction = Transaction( ctx=transaction_ctx, steps=(cleanup_draft_releases_step, ), ) if not cleanup_draft_releases_transaction.execute(): logger.warning('An error occured while cleaning up draft releases') if release_notes_policy == ReleaseNotesPolicy.DISABLED: return logger.info('release notes were disabled - skipping') elif release_notes_policy == ReleaseNotesPolicy.DEFAULT: pass else: raise NotImplementedError(release_notes_policy) if release_on_github: release_notes_transaction = Transaction( ctx=transaction_ctx, steps=(publish_release_notes_step, ), ) release_notes_transaction.validate() if not release_notes_transaction.execute(): raise RuntimeError( 'An error occurred while publishing the release notes.') if slack_channel_configs: if not release_on_github: raise RuntimeError('Cannot post to slack without a github release') release_notes = transaction_ctx.step_output( publish_release_notes_step.name()).get('release notes') all_slack_releases_successful = True for slack_cfg in slack_channel_configs: slack_cfg_name = slack_cfg['slack_cfg_name'] slack_channel = slack_cfg['channel_name'] post_to_slack_step = PostSlackReleaseStep( slack_cfg_name=slack_cfg_name, slack_channel=slack_channel, release_version=release_version, release_notes=release_notes, githubrepobranch=githubrepobranch, ) slack_transaction = Transaction( ctx=transaction_ctx, steps=(post_to_slack_step, ), ) slack_transaction.validate() all_slack_releases_successful = (all_slack_releases_successful and slack_transaction.execute()) if not all_slack_releases_successful: raise RuntimeError( 'An error occurred while posting the release notes to Slack.')
def create_upgrade_pr( from_ref, to_ref, pull_request_util, upgrade_script_path, githubrepobranch: GitHubRepoBranch, repo_dir, github_cfg_name, cfg_factory, merge_policy, after_merge_callback=None, ): ls_repo = pull_request_util.repository # have component create upgradation diff cmd_env = os.environ.copy() cmd_env['DEPENDENCY_TYPE'] = to_ref.type_name() cmd_env['DEPENDENCY_NAME'] = to_ref.name() cmd_env['DEPENDENCY_VERSION'] = to_ref.version() cmd_env['REPO_DIR'] = repo_dir cmd_env['GITHUB_CFG_NAME'] = github_cfg_name # pass type-specific attributes if to_ref.type_name() == 'container_image': cmd_env['DEPENDENCY_IMAGE_REFERENCE'] = to_ref.image_reference() subprocess.run( [str(upgrade_script_path)], check=True, env=cmd_env ) commit_msg = 'Upgrade {cn}\n\nfrom {ov} to {nv}'.format( cn=to_ref.name(), ov=from_ref.version(), nv=to_ref.version(), ) # mv diff into commit and push it helper = gitutil.GitHelper.from_githubrepobranch( githubrepobranch=githubrepobranch, repo_path=repo_dir, ) commit = helper.index_to_commit(message=commit_msg) ci.util.info(f'commit for upgrade-PR: {commit.hexsha}') new_branch_name = ci.util.random_str(prefix='ci-', length=12) repo_branch = githubrepobranch.branch() head_sha = ls_repo.ref(f'heads/{repo_branch}').object.sha ls_repo.create_ref(f'refs/heads/{new_branch_name}', head_sha) def rm_pr_branch(): ls_repo.ref(f'heads/{new_branch_name}').delete() try: helper.push(from_ref=commit.hexsha, to_ref=f'refs/heads/{new_branch_name}') except: ci.util.warning('an error occurred - removing now useless pr-branch') rm_pr_branch() raise helper.repo.git.checkout('.') try: with tempfile.TemporaryDirectory() as temp_dir: from_github_cfg = cfg_factory.github(from_ref.config_name()) from_github_helper = GitHubRepositoryHelper( github_cfg=from_github_cfg, owner=from_ref.github_organisation(), name=from_ref.github_repo(), ) from_git_helper = gitutil.GitHelper.clone_into( target_directory=temp_dir, github_cfg=from_github_cfg, github_repo_path=from_ref.github_repo_path() ) commit_range = '{from_version}..{to_version}'.format( from_version=from_ref.version(), to_version=to_ref.version() ) release_note_blocks = ReleaseNotes.create( github_helper=from_github_helper, git_helper=from_git_helper, commit_range=commit_range ).release_note_blocks() if release_note_blocks: text = '*Release Notes*:\n{blocks}'.format( blocks=release_note_blocks ) else: text = pull_request_util.retrieve_pr_template_text() except: ci.util.warning('an error occurred during release notes processing (ignoring)') text = None import traceback ci.util.warning(traceback.format_exc()) pull_request = ls_repo.create_pull( title=github.util.PullRequestUtil.calculate_pr_title( reference=to_ref, from_version=from_ref.version(), to_version=to_ref.version() ), base=repo_branch, head=new_branch_name, body=text, ) if merge_policy is MergePolicy.MANUAL: return # auto-merge - todo: make configurable (e.g. merge method) pull_request.merge() rm_pr_branch() if after_merge_callback: subprocess.run( [os.path.join(repo_dir, after_merge_callback)], check=True, env=cmd_env )