Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to export a list of repositories last updated before a specified time #65

Merged
merged 16 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions src/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,120 @@ def repositories_create_dep_enforcement_pr(
)


@repositories_cli.command("archivable")
@click.option(
"-f",
"--format",
prompt="Output format",
type=click.Choice(
["human", "ghas", "json", "list"],
case_sensitive=False,
),
default="human",
)
@click.option(
"-u", "--last_updated_before", prompt="Last updated before YYYY-MM-DD", type=str
)
@click.argument("output", type=click.File("w"))
@click.option(
"-t",
"--token",
prompt=False,
type=str,
default=None,
hide_input=True,
confirmation_prompt=False,
show_envvar=True,
)
@click.option("-o", "--organization", prompt="Organization name", type=str)
def repositories_archivable(
last_updated_before: str,
format: str,
output: Any,
organization: str,
token: str,
) -> bool:
"""Find potentially archivable repositories"""

try:
threshold_date = datetime.strptime(last_updated_before, "%Y-%m-%d")
except Exception:
click.echo(f"Invalid time: {last_updated_before}")
return False

# 1. Get list of non-archived repositories
res = repositories.get_org_repositories(
status="public",
organization=organization,
token=token,
language="",
default_branch="",
license="",
archived=False,
disabled=False,
)

for repo in res:
print(f"default branch: {repo.default_branch}")

branch_last_commit_date = repositories.get_default_branch_last_updated(
token=token,
organization=organization,
repository_name=repo.name,
default_branch=repo.default_branch,
)
if not branch_last_commit_date:
return False

click.echo(f"{branch_last_commit_date}, {threshold_date}")

if branch_last_commit_date > threshold_date:
continue

if "human" == format:
output.write(repo + "\n")
click.echo(repo)
elif "ghas" == format:
output.write(
json.dumps([{"login": organization, "repos": repo.to_ghas()}]) + "\n"
)
click.echo([{"login": organization, "repos": repo.to_ghas()}])
elif "json" == format:
output.write(json.dumps(repo.to_json()) + "\n")
click.echo(repo.to_json())
elif "list" == format:
output.write(repo.name + "\n")
click.echo(repo.name)

return True


@repositories_cli.command("archive")
@click.option(
"-r",
"--repository",
prompt="Repository name",
)
@click.option(
"-t",
"--token",
prompt=False,
type=str,
default=None,
hide_input=True,
confirmation_prompt=False,
show_envvar=True,
)
@click.option("-o", "--organization", prompt="Organization name", type=str)
def repositories_archive(
repository: str,
organization: str,
token: str,
) -> None:
"""Archive a repository"""
click.echo(repositories.archive(organization, token, repository))


#########
# Teams #
#########
Expand Down Expand Up @@ -882,5 +996,90 @@ def mass_deploy(
)


@mass_cli.command("archive")
@click.argument("input_repos_list", type=click.File("r"))
@click.option(
"-t",
"--token",
prompt=False,
type=str,
default=None,
hide_input=True,
confirmation_prompt=False,
show_envvar=True,
)
@click.option("-o", "--organization", prompt="Organization name", type=str)
def mass_issue_archive(
input_repos_list: Any,
organization: str,
token: str,
) -> None:
"""Create an issue to inform that repositories will be archived at a specific date."""

repos_list = input_repos_list.readlines()

for repo in repos_list:

repo = repo.rstrip("\n")

click.echo(f"{repo}...", nl=False)

if repositories.archive(
organization=organization, token=token, repository=repo
):
click.echo(" Archived.")
else:
click.echo(" Not Archived.", err=True)


@mass_cli.command("issue_upcoming_archive")
@click.argument("input_repos_list", type=click.File("r"))
@click.option(
"-u",
"--archived_date",
prompt="Target date when the repositories will be archived",
type=str,
)
@click.option(
"-t",
"--token",
prompt=False,
type=str,
default=None,
hide_input=True,
confirmation_prompt=False,
show_envvar=True,
)
@click.option("-o", "--organization", prompt="Organization name", type=str)
def mass_archive(
input_repos_list: Any,
archived_date: str,
organization: str,
token: str,
) -> None:
"""Mass archive a list of repositories"""

repos_list = input_repos_list.readlines()

for repo in repos_list:

repo = repo.rstrip("\n")

issue_creation = issues.create(
Fixed Show fixed Hide fixed
title=f"This repository will be archived on {archived_date} :warning: :wastebasket:",
content=f"""
Hello,

Due to inactivity, this repository will be archived automatically on {archived_date}.

If you think this is a mistake, please informthe Security team *ASAP* on Slack at `#github-appsec-security.`

Thanks! :handshake:""",
repository=repo,
organization=organization,
token=token,
)


if __name__ == "__main__":
main()
43 changes: 42 additions & 1 deletion src/ghas_cli/utils/repositories.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python3

from typing import List
from typing import List, Any
import base64
import requests
from . import network
import time
import datetime


class Repository:
Expand Down Expand Up @@ -216,6 +217,46 @@ def get_org_repositories(
return repos_list


def get_default_branch_last_updated(
token: str, organization: str, repository_name: str, default_branch: str
) -> Any:
"""
Return the latest commit date on the default branch
"""
headers = network.get_github_headers(token)

branch_res = requests.get(
url=f"https://api.github.com/repos/{organization}/{repository_name}/branches/{default_branch}",
headers=headers,
)

if branch_res.status_code != 200:
return False

branch_res = branch_res.json()

return datetime.datetime.strptime(
branch_res["commit"]["commit"]["author"]["date"].split("T")[0], "%Y-%m-%d"
)


def archive(organization: str, token: str, repository: str) -> bool:
headers = network.get_github_headers(token)

payload = {"archived": True}

status = requests.patch(
url=f"https://api.github.com/repos/{organization}/{repository}",
headers=headers,
json=payload,
)

if status.status_code != 200:
return False
else:
return True


def check_dependabot_alerts_enabled(
token: str, organization: str, repository_name: str
) -> bool:
Expand Down