Skip to content

feat: add script to create organization team linked to IdP group#157

Open
joshjohanning wants to merge 1 commit intomainfrom
organization-idp-linked-team
Open

feat: add script to create organization team linked to IdP group#157
joshjohanning wants to merge 1 commit intomainfrom
organization-idp-linked-team

Conversation

@joshjohanning
Copy link
Owner

New script for team creation and IdP group linking:

  • Added create-team-and-link-idp-group.sh to automate creating a GitHub organization team and linking it to an IdP external group.

Copilot AI review requested due to automatic review settings March 3, 2026 21:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new gh-cli utility script to automate creating an organization team and linking it to an IdP external group (team synchronization), and documents the script in the gh-cli README.

Changes:

  • Added create-team-and-link-idp-group.sh to create a team, locate an external IdP group by name, and link the team to that group
  • Implemented an optional --secret team privacy mode
  • Documented the new script in gh-cli/README.md

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
gh-cli/create-team-and-link-idp-group.sh New script to create a team, remove the creator membership, and link to an external IdP group
gh-cli/README.md Adds a new README entry for the script with a basic usage snippet
Comments suppressed due to low confidence (1)

gh-cli/create-team-and-link-idp-group.sh:48

  • This script relies on users setting GH_HOST, but many other gh-cli scripts accept an explicit optional hostname argument and pass it via gh api --hostname ... (e.g. gh-cli/change-repository-visibility.sh:6-16, gh-cli/get-enterprise-members.sh:12-16). To stay consistent and to make GHES/GHE Data Residency usage less error-prone, consider adding an optional --hostname <host> (defaulting to github.com) and passing it to every gh api call.
# Usage:
#   ./create-team-and-link-idp-group.sh <org> <team-name> <idp-group-name> [--secret]
#
# Notes:
#   - The script paginates through external groups to find the target group
#   - If the IdP group is not found, the script exits with an error
#   - The team is created with 'closed' (visible to org members) privacy by default
#   - Pass --secret to create a 'secret' (only visible to team members) team
#   - For GHES / GHE Data Residency, set GH_HOST before running

if [ "$#" -lt 3 ]; then
  echo "Usage: $0 <org> <team-name> <idp-group-name> [--secret]"
  echo ""
  echo "Example: $0 my-org my-team \"Engineering Team\""
  exit 1
fi

org="$1"
team_name="$2"
idp_group_name="$3"

privacy="closed"
if [ "${4}" = "--secret" ]; then
  privacy="secret"
fi

# --- Find the external IdP group by display name ---
echo "Searching for external group '$idp_group_name' in organization '$org'..."

group_id=$(gh api \
  --method GET \
  --paginate \
  "/orgs/$org/external-groups" \
  | jq -r --arg name "$idp_group_name" '.groups[] | select(.group_name | ascii_downcase == ($name | ascii_downcase)) | .group_id')

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +16 to +38
# ./create-team-and-link-idp-group.sh <org> <team-name> <idp-group-name> [--secret]
#
# Notes:
# - The script paginates through external groups to find the target group
# - If the IdP group is not found, the script exits with an error
# - The team is created with 'closed' (visible to org members) privacy by default
# - Pass --secret to create a 'secret' (only visible to team members) team
# - For GHES / GHE Data Residency, set GH_HOST before running

if [ "$#" -lt 3 ]; then
echo "Usage: $0 <org> <team-name> <idp-group-name> [--secret]"
echo ""
echo "Example: $0 my-org my-team \"Engineering Team\""
exit 1
fi

org="$1"
team_name="$2"
idp_group_name="$3"

privacy="closed"
if [ "${4}" = "--secret" ]; then
privacy="secret"
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script only supports a 4th positional argument when it is exactly --secret; any other 4th argument is silently ignored. This makes mistakes hard to detect and blocks adding other options (e.g., hostname). Consider parsing optional flags (and erroring on unknown flags) so invocations like --secret/--hostname <host> are handled explicitly.

This issue also appears on line 15 of the same file.

Suggested change
# ./create-team-and-link-idp-group.sh <org> <team-name> <idp-group-name> [--secret]
#
# Notes:
# - The script paginates through external groups to find the target group
# - If the IdP group is not found, the script exits with an error
# - The team is created with 'closed' (visible to org members) privacy by default
# - Pass --secret to create a 'secret' (only visible to team members) team
# - For GHES / GHE Data Residency, set GH_HOST before running
if [ "$#" -lt 3 ]; then
echo "Usage: $0 <org> <team-name> <idp-group-name> [--secret]"
echo ""
echo "Example: $0 my-org my-team \"Engineering Team\""
exit 1
fi
org="$1"
team_name="$2"
idp_group_name="$3"
privacy="closed"
if [ "${4}" = "--secret" ]; then
privacy="secret"
# ./create-team-and-link-idp-group.sh [--secret] [--hostname <host>] <org> <team-name> <idp-group-name>
#
# Notes:
# - The script paginates through external groups to find the target group
# - If the IdP group is not found, the script exits with an error
# - The team is created with 'closed' (visible to org members) privacy by default
# - Pass --secret to create a 'secret' (only visible to team members) team
# - For GHES / GHE Data Residency, set GH_HOST or pass --hostname before running
usage() {
echo "Usage: $0 [--secret] [--hostname <host>] <org> <team-name> <idp-group-name>"
echo ""
echo "Example: $0 --secret my-org my-team \"Engineering Team\""
}
org=""
team_name=""
idp_group_name=""
privacy="closed"
while [ "$#" -gt 0 ]; do
case "$1" in
--secret)
privacy="secret"
shift
;;
--hostname)
if [ -z "${2:-}" ]; then
echo "Error: --hostname requires a hostname value" >&2
usage
exit 1
fi
GH_HOST="$2"
shift 2
;;
--*)
echo "Error: unknown option: $1" >&2
usage
exit 1
;;
*)
if [ -z "$org" ]; then
org="$1"
elif [ -z "$team_name" ]; then
team_name="$1"
elif [ -z "$idp_group_name" ]; then
idp_group_name="$1"
else
echo "Error: too many positional arguments: $1" >&2
usage
exit 1
fi
shift
;;
esac
done
if [ -z "$org" ] || [ -z "$team_name" ] || [ -z "$idp_group_name" ]; then
echo "Error: missing required arguments" >&2
usage
exit 1

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +61
group_id=$(gh api \
--method GET \
--paginate \
"/orgs/$org/external-groups" \
| jq -r --arg name "$idp_group_name" '.groups[] | select(.group_name | ascii_downcase == ($name | ascii_downcase)) | .group_id')

if [ -n "$group_id" ]; then
echo "Found external group '$idp_group_name' with group_id: $group_id"
else
echo "Error: external group '$idp_group_name' not found in organization '$org'."
echo "Available groups:"
gh api \
--method GET \
--paginate \
"/orgs/$org/external-groups" \
--jq '.groups[] | " - \(.group_name) (id: \(.group_id))"'
exit 1
fi
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

group_id is derived with a jq filter that can emit multiple group_id values if more than one external group matches the display name. In that case, group_id will contain newlines and the subsequent link request will be invalid/ambiguous. Consider enforcing a single match (error if 0 or >1 matches) or selecting deterministically (e.g., first match) and making that behavior explicit in the output.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +98
gh api \
--method DELETE \
"/orgs/$org/teams/$team_slug/memberships/$current_user" \
--silent 2>/dev/null && echo "Removed '$current_user' from team '$team_slug'." \
|| echo "User '$current_user' was not a member of team '$team_slug' (this is OK)."
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The membership removal step treats any failure from the DELETE request as "this is OK" by discarding stderr and not distinguishing 404 (not a member) from real errors like 403 (insufficient permissions) or 5xx. That can hide a failure to remove the creating user and cause the later external-group linking to fail with a less clear error. Consider checking the HTTP status / exit code and only ignoring the specific "not found" case while surfacing other failures.

Suggested change
gh api \
--method DELETE \
"/orgs/$org/teams/$team_slug/memberships/$current_user" \
--silent 2>/dev/null && echo "Removed '$current_user' from team '$team_slug'." \
|| echo "User '$current_user' was not a member of team '$team_slug' (this is OK)."
delete_output=$(gh api \
--method DELETE \
"/orgs/$org/teams/$team_slug/memberships/$current_user" 2>&1)
delete_status=$?
if [ "$delete_status" -eq 0 ]; then
echo "Removed '$current_user' from team '$team_slug'."
elif echo "$delete_output" | grep -q "HTTP 404"; then
echo "User '$current_user' was not a member of team '$team_slug' (this is OK)."
else
echo "Error: failed to remove '$current_user' from team '$team_slug'." >&2
echo "$delete_output" >&2
exit 1
fi

Copilot uses AI. Check for mistakes.
Creates an organization team and links it to an Identity Provider (IdP) external group.

```shell
./create-team-and-link-idp-group.sh <org> <team-name> <idp-group-name>
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README usage snippet doesn’t mention the script’s optional --secret flag (and if you add hostname support, that should be documented here too). Update this usage block to match the script’s supported arguments so users don’t miss the privacy option.

Suggested change
./create-team-and-link-idp-group.sh <org> <team-name> <idp-group-name>
./create-team-and-link-idp-group.sh <org> <team-name> <idp-group-name> [--secret] [--hostname <github-hostname>]

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants