Search code examples
github-enterprise

How to script user removal from Github enterprise


Github enterprise can generate a CSV report about dormant / inactive users, but you have then to manually remove these users and when you have many, it takes a lot of time.

Is there any API or script allowing to remove all these users from the enterprise, and all organizations it contains?


Solution

  • I had no luck to find such API, script, or command in Github CLI, so i asked Github support, and they shared this GraphQL API, which is quite a recent addition: https://docs.github.com/en/enterprise-cloud@latest/graphql/reference/mutations#removeenterprisemember

    My AI friend and I developed this Python script, that hopefully will be useful to you too. The little trick to know about GraphQL is that ids, for users, enterprise, etc are specific, you cannot use the same ids/slugs than the ones you would use with the REST API. The first step is to get the id of your Github enterprise, and then the id of the users you want to remove.

    import os
    import requests
    import csv
    
    # Set the name of the csv file containing the list of dormant users to remove
    # This file is generated by the Github Enterprise report "Dormant Users"
    dormant_users_csv_report_filename = "export-github-enterprise-1679693671.csv"
    
    # Set the name of the Github enterprise slug
    enterprise_slug = "github-enterprise"
    
    # Set up the GraphQL API endpoint
    api_url = "https://api.github.com/graphql"
    
    # Set up the GraphQL queries
    query_enterprise_id = """
    query ($enterprise: String!) {
      enterprise(slug: $enterprise) {
        id
      }
    }
    """
    
    query_user_id = """
    query ($login: String!) {
      user(login: $login) {
        id 
      }
    }
    """
    
    query_remove_user = """
    mutation ($enterpriseId: ID!, $userId: ID!) {
      removeEnterpriseMember(input: {enterpriseId: $enterpriseId, userId: $userId}) {
        clientMutationId
      }
    }
    """
    
    # Set up the authorization header with a bearer token
    access_token = os.environ["GITHUB_TOKEN"]
    headers = {
      "Authorization": f"Bearer {access_token}",
      "X-Github-Next-Global-ID": "1"  
    }
    
    # Get the enterprise ID
    response = requests.post(
        api_url, 
        json = {
          "query": query_enterprise_id, 
          "variables": { "enterprise": enterprise_slug }
        }, 
        headers = headers
    )
    if response.status_code != 200:
      print(f"Failed to get the enterprise ID")
      print(f"Response status code: {response.status_code}")
      print(f"Response message: {response.text}")
      exit(1)
    
    enterprise_id = response.json()["data"]["enterprise"]["id"]
    
    # Open the csv file and loop through the usernames to delete each one
    with open(dormant_users_csv_report_filename, 'r') as file:
        reader = csv.DictReader(file)
        for row in reader:
            username = row["login"]
            if username:
              # Get the user ID
              response = requests.post(
                  api_url, 
                  json = {
                    "query": query_user_id, 
                    "variables": { "login": username }
                  }, 
                  headers = headers
              )
              if response.status_code != 200:
                print(f"Failed to get the user ID for user '{username}'")
                print(f"Response status code: {response.status_code}")
                print(f"Response message: {response.text}")
                continue
    
              user_id = response.json()["data"]["user"]["id"]
    
              print(f"Deleting user '{username}")
              # Make the API request to remove the user from the enterprise
              response = requests.post(
                  api_url, 
                  json = {
                    "query": query_remove_user, 
                    "variables": { "enterpriseId": enterprise_id, "userId": user_id }
                  }, 
                  headers = headers
              )
    
              if response.status_code != 200:
                print(f"Failed to remove the user '{username}' from the enterprise")
                print(f"Response status code: {response.status_code}")
                print(f"Response message: {response.text}")
                continue
    
              if response.json().get("errors"):
                 print("Response contains an error: " + str(response.json()["errors"]))