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

Move user retirement scripts code from the tubular repo #34063

Merged
merged 10 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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>`_
farhan marked this conversation as resolved.
Show resolved Hide resolved

`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
Loading