Skip to content

Commit

Permalink
Move user retirement scripts code from the tubular repo (#34063)
Browse files Browse the repository at this point in the history
* refactor: Migragte user retirement scripts code from the tubular repo
  • Loading branch information
farhan authored Feb 22, 2024
1 parent 20570ff commit 65ea55c
Show file tree
Hide file tree
Showing 48 changed files with 7,808 additions and 1 deletion.
33 changes: 33 additions & 0 deletions .github/workflows/units-test-scripts-user-retirement.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: units-test-scripts-user-retirement

on:
pull_request:
push:
branches:
- master

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
python-version: [ '3.8' ]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r scripts/user_retirement/requirements/testing.txt
- name: Run pytest
run: |
pytest scripts/user_retirement
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ REQ_FILES = \
requirements/edx/development \
requirements/edx/assets \
requirements/edx/semgrep \
scripts/xblock/requirements
scripts/xblock/requirements \
scripts/user_retirement/requirements/base \
scripts/user_retirement/requirements/testing

define COMMON_CONSTRAINTS_TEMP_COMMENT
# This is a temporary solution to override the real common_constraints.txt\n# In edx-lint, until the pyjwt constraint in edx-lint has been removed.\n# See BOM-2721 for more details.\n# Below is the copied and edited version of common_constraints\n
Expand Down
100 changes: 100 additions & 0 deletions scripts/user_retirement/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
User Retirement Scripts
=======================

`This <https://github.com/openedx/edx-platform/tree/master/scripts/user_retirement>`_ directory contains python scripts which are migrated from the `tubular <https://github.com/openedx/tubular/tree/master/scripts>`_ respository.
These scripts are intended to drive the user retirement workflow which involves handling the deactivation or removal of user accounts as part of the platform's management process.

These scripts could be called from any automation/CD framework.

How to run the scripts
======================

Download the Scripts
--------------------

To download the scripts, you can perform a partial clone of the edx-platform repository to obtain only the required scripts. The following steps demonstrate how to achieve this. Alternatively, you may choose other utilities or libraries for the partial clone.

.. code-block:: bash
[email protected]:openedx/edx-platform.git
branch=master
directory=scripts/user_retirement
git clone --branch $branch --single-branch --depth=1 --filter=tree:0 $repo_url
cd edx-platform
git sparse-checkout init --cone
git sparse-checkout set $directory
Create Python Virtual Environment
---------------------------------

Create a Python virtual environment using Python 3.8:

.. code-block:: bash
python3.8 -m venv ../venv
source ../venv/bin/activate
Install Pip Packages
--------------------

Install the required pip packages using the provided requirements file:

.. code-block:: bash
pip install -r scripts/user_retirement/requirements/base.txt
In-depth Documentation and Configuration Steps
----------------------------------------------

For in-depth documentation and essential configurations follow these docs

`Documentation <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/latest/configuration/user_retire/index.html>`_

`Configuration Docs <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/latest/configuration/user_retire/driver_setup.html>`_


Execute Script
--------------

Execute the following shell command to establish entry points for the scripts

.. code-block:: bash
chmod +x scripts/user_retirement/entry_points.sh
source scripts/user_retirement/entry_points.sh
To retire a specific learner, you can use the provided example script:

.. code-block:: bash
retire_one_learner.py \
--config_file=src/config.yml \
--username=user1
Make sure to replace ``src/config.yml`` with the actual path to your configuration file and ``user1`` with the actual username.

You can also execute Python scripts directly using the file path:

.. code-block:: bash
python scripts/user_retirement/retire_one_learner.py \
--config_file=src/config.yml \
--username=user1
Feel free to customize these steps according to your specific environment and requirements.

Run Test Cases
==============

Before running test cases, install the testing requirements:

.. code-block:: bash
pip install -r scripts/user_retirement/requirements/testing.txt
Run the test cases using pytest:

.. code-block:: bash
pytest scripts/user_retirement
Empty file.
7 changes: 7 additions & 0 deletions scripts/user_retirement/entry_points.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
alias get_learners_to_retire.py='python scripts/user_retirement/get_learners_to_retire.py'
alias replace_usernames.py='python scripts/user_retirement/replace_usernames.py'
alias retire_one_learner.py='python scripts/user_retirement/retire_one_learner.py'
alias retirement_archive_and_cleanup.py='python scripts/user_retirement/retirement_archive_and_cleanup.py'
alias retirement_bulk_status_update.py='python scripts/user_retirement/retirement_bulk_status_update.py'
alias retirement_partner_report.py='python scripts/user_retirement/retirement_partner_report.py'
105 changes: 105 additions & 0 deletions scripts/user_retirement/get_learners_to_retire.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#! /usr/bin/env python3

"""
Command-line script to retrieve list of learners that have requested to be retired.
The script calls the appropriate LMS endpoint to get this list of learners.
"""

import io
import logging
import sys
from os import path

import click
import yaml

# Add top-level project path to sys.path before importing scripts code
sys.path.append(path.abspath(path.join(path.dirname(__file__), '../..')))

from scripts.user_retirement.utils.edx_api import LmsApi
from scripts.user_retirement.utils.jenkins import export_learner_job_properties

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
LOG = logging.getLogger(__name__)


@click.command("get_learners_to_retire")
@click.option(
'--config_file',
help='File in which YAML config exists that overrides all other params.'
)
@click.option(
'--cool_off_days',
help='Number of days a learner should be in the retirement queue before being actually retired.',
default=7
)
@click.option(
'--output_dir',
help="Directory in which to write the Jenkins properties files.",
default='./jenkins_props'
)
@click.option(
'--user_count_error_threshold',
help="If more users than this number are returned we will error out instead of retiring. This is a failsafe"
"against attacks that somehow manage to add users to the retirement queue.",
default=300
)
@click.option(
'--max_user_batch_size',
help="This setting will only get at most X number of users. If this number is lower than the user_count_error_threshold"
"setting then it will not error.",
default=200
)
def get_learners_to_retire(config_file,
cool_off_days,
output_dir,
user_count_error_threshold,
max_user_batch_size):
"""
Retrieves a JWT token as the retirement service user, then calls the LMS
endpoint to retrieve the list of learners awaiting retirement.
"""
if not config_file:
click.echo('A config file is required.')
sys.exit(-1)

with io.open(config_file, 'r') as config:
config_yaml = yaml.safe_load(config)

user_count_error_threshold = int(user_count_error_threshold)
cool_off_days = int(cool_off_days)

client_id = config_yaml['client_id']
client_secret = config_yaml['client_secret']
lms_base_url = config_yaml['base_urls']['lms']
retirement_pipeline = config_yaml['retirement_pipeline']
end_states = [state[1] for state in retirement_pipeline]
states_to_request = ['PENDING'] + end_states

api = LmsApi(lms_base_url, lms_base_url, client_id, client_secret)

# Retrieve the learners to retire and export them to separate Jenkins property files.
learners_to_retire = api.learners_to_retire(states_to_request, cool_off_days, max_user_batch_size)
if max_user_batch_size:
learners_to_retire = learners_to_retire[:max_user_batch_size]
learners_to_retire_cnt = len(learners_to_retire)

if learners_to_retire_cnt > user_count_error_threshold:
click.echo(
'Too many learners to retire! Expected {} or fewer, got {}!'.format(
user_count_error_threshold,
learners_to_retire_cnt
)
)
sys.exit(-1)

export_learner_job_properties(
learners_to_retire,
output_dir
)


if __name__ == "__main__":
# pylint: disable=unexpected-keyword-arg, no-value-for-parameter
# If using env vars to provide params, prefix them with "RETIREMENT_", e.g. RETIREMENT_CLIENT_ID
get_learners_to_retire(auto_envvar_prefix='RETIREMENT')
Empty file.
Loading

0 comments on commit 65ea55c

Please sign in to comment.