-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Daniel Abraham <[email protected]>
- Loading branch information
Showing
6 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# GitHub Copilot Registration Pruning | ||
|
||
This automation searches daily for all users in a GitHub organization that are actively using Copilot. | ||
If Copilot was not used in a preceding period, it automatically unregisters them, and then notifies them. | ||
Users can then optionally ask for their subscription to be reinstated. | ||
|
||
## Slack Usage | ||
|
||
- `/ak prune-idle-copilot-seats` invokes the automation immidetly. | ||
- `/ak find-idle-copilot-seats` just displays the potentially idle seats. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
version: v1 | ||
|
||
project: | ||
name: github_copilot | ||
vars: | ||
- name: GITHUB_ORG | ||
value: "yourorg" | ||
- name: IDLE_USAGE_THRESHOLD | ||
value: "72h" | ||
# If set, only manage GitHub Copilot subscriptions to the specified users, separated by commas. | ||
- name: LOGINS | ||
value: "" | ||
- name: LOG_CHANNEL | ||
value: "github-copilot-registrations" | ||
connections: | ||
- name: mygithub | ||
integration: github | ||
- name: myslack | ||
integration: slack | ||
triggers: | ||
- name: check_daily | ||
schedule: "@daily" | ||
call: triggers.star:on_schedule | ||
- name: check_now | ||
connection: myslack | ||
event_type: app_mention | ||
call: triggers.star:on_slack_app_mention |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
load("@github", "mygithub") | ||
load("@slack", "myslack") | ||
|
||
def _email_to_slack_user_id(email): | ||
resp = myslack.users_lookup_by_email(email) | ||
return resp.user.id if resp.ok else "" | ||
|
||
def github_username_to_slack_user_id(github_username, github_owner_org): | ||
resp = mygithub.get_user(github_username, owner = github_owner_org) | ||
github_user_link = "<%s|%s>" % (resp.htmlurl, github_username) | ||
|
||
if resp.type == "Bot": | ||
return "" | ||
|
||
# Try to match by the email address first. | ||
if resp.email: | ||
slack_user_id = _email_to_slack_user_id(resp.email) | ||
if slack_user_id: | ||
return slack_user_id | ||
|
||
# Otherwise, try to match by the user's full name. | ||
if not resp.name: | ||
return "" | ||
|
||
gh_full_name = resp.name.lower() | ||
for user in _slack_users(): | ||
slack_names = (user.profile.real_name.lower(), user.profile.real_name_normalized.lower()) | ||
if gh_full_name in slack_names: | ||
return user.id | ||
|
||
return "" | ||
|
||
def _slack_users(cursor = ""): | ||
resp = myslack.users_list(cursor, limit = 100) | ||
if not resp.ok: | ||
return [] | ||
|
||
users = resp.members | ||
if resp.response_metadata.next_cursor: | ||
users += _slack_users(resp.response_metadata.next_cursor) | ||
return users |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
{ | ||
"blocks": [ | ||
{ | ||
"type": "header", | ||
"text": { | ||
"type": "plain_text", | ||
"text": "You have been removed from the Copilot program due to inactivity", | ||
"emoji": true | ||
} | ||
}, | ||
{ | ||
"type": "section", | ||
"text": { | ||
"type": "mrkdwn", | ||
"text": "Reinstate" | ||
}, | ||
"accessory": { | ||
"type": "button", | ||
"text": { | ||
"type": "plain_text", | ||
"text": ":repeat:", | ||
"emoji": true | ||
}, | ||
"value": "reinstate", | ||
"action_id": "button-action" | ||
} | ||
}, | ||
{ | ||
"type": "section", | ||
"text": { | ||
"type": "mrkdwn", | ||
"text": "OK" | ||
}, | ||
"accessory": { | ||
"type": "button", | ||
"text": { | ||
"type": "plain_text", | ||
"text": ":relieved:", | ||
"emoji": true | ||
}, | ||
"value": "ok", | ||
"action_id": "button-action" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
load("env", "GITHUB_ORG", "IDLE_USAGE_THRESHOLD", "LOGINS", "LOG_CHANNEL") | ||
load("@github", "mygithub") | ||
load("@slack", "myslack") | ||
load("helpers.star", "github_username_to_slack_user_id") | ||
load("msg.json", "blocks") | ||
|
||
logins = LOGINS.split(",") | ||
idle_usage_threshold = time.parse_duration(IDLE_USAGE_THRESHOLD) | ||
|
||
def prune_idle_seats(): | ||
seats = find_idle_seats() | ||
new_idle_seats = [] | ||
for seat in seats: | ||
new_idle_seats.append(seat) | ||
|
||
print("new idle: {}".format(seat)) | ||
start("seats.star:engage_seat", {"seat": seat}) | ||
return new_idle_seats | ||
|
||
def _get_all_seats(): | ||
# TODO: pagination. | ||
return mygithub.list_copilot_seats(GITHUB_ORG).seats | ||
|
||
def find_idle_seats(): | ||
seats = _get_all_seats() | ||
|
||
t, idle_seats = time.now(), [] | ||
for seat in seats: | ||
if logins and (seat.assignee.login not in logins): | ||
print("skipping %s" % seat.assignee.login) | ||
continue | ||
|
||
delta = t - seat.last_activity_at | ||
is_idle = delta >= idle_usage_threshold | ||
|
||
print("{}: {} - {} = {} {} {}".format( | ||
seat.assignee.login, | ||
t, | ||
seat.last_activity_at, | ||
delta, | ||
">=" if is_idle else "<", | ||
idle_usage_threshold, | ||
)) | ||
|
||
if is_idle: | ||
idle_seats.append(seat) | ||
|
||
return idle_seats | ||
|
||
def engage_seat(seat): | ||
log = lambda msg: myslack.chat_post_message(LOG_CHANNEL, "{}: {}".format(seat.assignee.login, msg)) | ||
|
||
log("engaging") | ||
|
||
github_login = seat.assignee.login | ||
slack_id = github_username_to_slack_user_id(github_login, GITHUB_ORG) | ||
if not slack_id: | ||
print("No slack user found for github user %s" % github_login) | ||
return | ||
|
||
mygithub.remove_copilot_users(GITHUB_ORG, [github_login]) | ||
|
||
myslack.chat_post_message(slack_id, blocks=blocks) | ||
|
||
s = subscribe('myslack', 'data.type == "block_actions" && data.user.id == "{}"'.format(slack_id)) | ||
|
||
say = lambda msg: myslack.chat_post_message(slack_id, msg) | ||
|
||
value = next_event(s)["actions"][0].value | ||
|
||
if value == 'ok': | ||
say("Okey dokey!") | ||
log("ok") | ||
return | ||
|
||
if value == 'reinstate': | ||
log("reinstate") | ||
mygithub.add_copilot_users(GITHUB_ORG, [github_login]) | ||
say("You have been reinstated to the Copilot program.") | ||
return | ||
|
||
log("wierd response: {}".format(value)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
load("@slack", "myslack") | ||
load("seats.star", "prune_idle_seats", "find_idle_seats") | ||
|
||
def on_schedule(): | ||
print(prune_idle_seats()) | ||
|
||
def on_slack_app_mention(data): | ||
parts = data.text.split(" ") | ||
if len(parts) < 2: | ||
myslack.chat_post_message(data.channel, "unrecorgnized command", thread_ts=data.ts) | ||
return | ||
|
||
cmd = parts[1].strip() | ||
|
||
reply = lambda msg: myslack.chat_post_message(data.channel, msg, thread_ts=data.ts) | ||
logins = lambda seats: [seat.assignee.login for seat in seats] | ||
|
||
if cmd == "prune-idle-copilot-seats": | ||
seats = prune_idle_seats() | ||
reply("engaged {} new idle seats: {}".format(len(seats), logins(seats))) | ||
elif cmd == "find-idle-copilot-seats": | ||
seats = find_idle_seats() | ||
reply("found {} idle seats: {}".format(len(seats), logins(seats))) |