Commit 7a56c51e authored by Abhishek's avatar Abhishek
Browse files

Divided Script into 3 parts

parent 6572433e
Pipeline #11968 skipped with stage
55e00a0b57a3fa3e5d2566f5eab59aeae509b2d6
...@@ -23,6 +23,7 @@ RUN apt-get update && apt-get install -y \ ...@@ -23,6 +23,7 @@ RUN apt-get update && apt-get install -y \
sudo \ sudo \
mariadb-client \ mariadb-client \
curl \ curl \
zip \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Add custom PHP settings for Moodle # Add custom PHP settings for Moodle
......
NEW_COURSE_ID=24
NEW_ASSIGNMENT_ID=17
No preview for this file type
DEBUG: Data object being sent to add_moduleinfo(): DEBUG: Data object being sent to add_moduleinfo():
stdClass Object stdClass Object
( (
[course] => 2 [course] => 24
[modulename] => assign [modulename] => assign
[name] => Fully Automated DTA Assignment [name] => Fully Automated DTA Assignment
[intro] => This assignment was created by the final automation script. [intro] => This assignment was created by the DTA automation script.
[introformat] => 1 [introformat] => 1
[section] => 1 [section] => 1
[visible] => 1 [visible] => 1
[assignsubmission_dta_enabled] => 1 [assignsubmission_dta_enabled] => 1
[assignsubmission_file_enabled] => 1 [assignsubmission_file_enabled] => 1
[dta] => 1 [dta] => 1
[tests_draft_dta] => 879454372 [tests_draft_dta] => 617567178
[assignsubmission_file_maxsizebytes] => 0 [assignsubmission_file_maxsizebytes] => 0
[cmidnumber] => [cmidnumber] =>
[assignsubmission_file_maxfiles] => 20 [assignsubmission_file_maxfiles] => 20
...@@ -28,30 +28,72 @@ stdClass Object ...@@ -28,30 +28,72 @@ stdClass Object
[markingworkflow] => 0 [markingworkflow] => 0
[markingallocation] => 0 [markingallocation] => 0
[attemptreopenmethod] => none [attemptreopenmethod] => none
[allowsubmissionsfromdate] => 1756162043 [allowsubmissionsfromdate] => 1760036445
[duedate] => 1756766843 [duedate] => 1760641245
[cutoffdate] => 0 [cutoffdate] => 0
[gradingduedate] => 0 [gradingduedate] => 0
[module] => 1 [module] => 1
[timemodified] => 1756162043 [timemodified] => 1760036445
[alwaysshowdescription] => 0 [alwaysshowdescription] => 0
[completionsubmit] => 0 [completionsubmit] => 0
) )
DEBUG: Course object before add_moduleinfo():
stdClass Object
(
[id] => 24
[category] => 1
[sortorder] => 10001
[fullname] => DTA Test Course
[shortname] => DTA-Course-1760036443
[idnumber] =>
[summary] =>
[summaryformat] => 1
[format] => topics
[showgrades] => 1
[newsitems] => 5
[startdate] => 0
[enddate] => 0
[relativedatesmode] => 0
[marker] => 0
[maxbytes] => 0
[legacyfiles] => 0
[showreports] => 0
[visible] => 1
[visibleold] => 1
[downloadcontent] =>
[groupmode] => 0
[groupmodeforce] => 0
[defaultgroupingid] => 0
[lang] =>
[calendartype] =>
[theme] =>
[timecreated] => 1760036443
[timemodified] => 1760036443
[requested] => 0
[enablecompletion] => 1
[completionnotify] => 0
[cacherev] => 1760036447
[originalcourseid] =>
[showactivitydates] => 0
[showcompletionconditions] => 1
[pdfexportfont] =>
)
DEBUG: Returned object from add_moduleinfo(): DEBUG: Returned object from add_moduleinfo():
stdClass Object stdClass Object
( (
[course] => 2 [course] => 24
[modulename] => assign [modulename] => assign
[name] => Fully Automated DTA Assignment [name] => Fully Automated DTA Assignment
[intro] => This assignment was created by the final automation script. [intro] => This assignment was created by the DTA automation script.
[introformat] => 1 [introformat] => 1
[section] => 1 [section] => 1
[visible] => 1 [visible] => 1
[assignsubmission_dta_enabled] => 1 [assignsubmission_dta_enabled] => 1
[assignsubmission_file_enabled] => 1 [assignsubmission_file_enabled] => 1
[dta] => 1 [dta] => 1
[tests_draft_dta] => 879454372 [tests_draft_dta] => 617567178
[assignsubmission_file_maxsizebytes] => 0 [assignsubmission_file_maxsizebytes] => 0
[cmidnumber] => [cmidnumber] =>
[assignsubmission_file_maxfiles] => 20 [assignsubmission_file_maxfiles] => 20
...@@ -68,16 +110,16 @@ stdClass Object ...@@ -68,16 +110,16 @@ stdClass Object
[markingworkflow] => 0 [markingworkflow] => 0
[markingallocation] => 0 [markingallocation] => 0
[attemptreopenmethod] => none [attemptreopenmethod] => none
[allowsubmissionsfromdate] => 1756162043 [allowsubmissionsfromdate] => 1760036445
[duedate] => 1756766843 [duedate] => 1760641245
[cutoffdate] => 0 [cutoffdate] => 0
[gradingduedate] => 0 [gradingduedate] => 0
[module] => 1 [module] => 1
[timemodified] => 1756162043 [timemodified] => 1760036445
[alwaysshowdescription] => 0 [alwaysshowdescription] => 0
[completionsubmit] => 0 [completionsubmit] => 0
[instance] => 1 [instance] => 17
[coursemodule] => 2 [coursemodule] => 40
[groupingid] => 0 [groupingid] => 0
[completion] => 0 [completion] => 0
[completionview] => 0 [completionview] => 0
...@@ -100,10 +142,10 @@ stdClass Object ...@@ -100,10 +142,10 @@ stdClass Object
( (
[context:protected] => core\context\module Object [context:protected] => core\context\module Object
( (
[_id:protected] => 14 [_id:protected] => 80
[_contextlevel:protected] => 70 [_contextlevel:protected] => 70
[_instanceid:protected] => 2 [_instanceid:protected] => 40
[_path:protected] => /1/3/13/14 [_path:protected] => /1/3/79/80
[_depth:protected] => 4 [_depth:protected] => 4
[_locked:protected] => 0 [_locked:protected] => 0
) )
...@@ -116,4 +158,4 @@ stdClass Object ...@@ -116,4 +158,4 @@ stdClass Object
[showgradingmanagement] => [showgradingmanagement] =>
) )
1 17
TEACHER_USER_ID=3
STUDENT_USER_ID=4
#!/bin/bash
# --- URLs ---
MOODLE_URL="${MOODLE_URL:-http://localhost:8080}"
BACKEND_URL="${BACKEND_URL:-http://localhost:8081}"
# --- Admin credentials ---
MOODLE_ADMIN_USER="user"
MOODLE_ADMIN_PASS="QAYwsx@12345"
# --- Teacher automation user ---
TEACHER_USER_NAME="teacheruser"
TEACHER_USER_PASS="AutomationPassword123"
# --- Student user ---
STUDENT_USER_NAME="tstudent"
STUDENT_USER_PASS="StudentPassword123"
# --- Runner repo (externally configurable) ---
RUNNER_REPO="${RUNNER_REPO:-https://transfer.hft-stuttgart.de/gitlab/cota/examples/cota-runner-jdk21-junit5.git}"
# --- Files (externally configurable) ---
DTA_CONFIG_FILE="${DTA_CONFIG_FILE:-teacher-dta-dir-test-runner-jdk21.txt}"
DTA_STUDENT_SUBMISSION_FILE="${DTA_STUDENT_SUBMISSION_FILE:-student-dir.txt}"
# --- Containers & DB ---
MOODLE_CONTAINER="moodle-web-official"
MOODLE_SYSTEM_USER="www-data"
MOODLE_ROOT_IN_CONTAINER="/var/www/html"
CUSTOM_SERVICE_NAME="dta_automation_service"
DB_CONTAINER_NAME="moodle-db-official"
DB_ROOT_PASSWORD="verysecretpassword"
DB_NAME="moodle"
#!/bin/bash
set -e
source "$(dirname "$0")/script_env.sh"
#
# DEFINITIVE Moodle CI/CD AUTOMATION SCRIPT V10 (ROBUST ERROR CHECKING)
echo "🚀 Starting Moodle Automation Script..."
echo " -> Using Moodle URL: $MOODLE_URL"
echo " -> Using Backend URL: $BACKEND_URL"
# GL: - consider placing the following steps 1 and 2 in a dockerfile and create your own image.
echo " -> Waiting for Moodle web server to be ready..."
timeout=300 # sometimes time taken is more than 300 seconds need to check
until curl --output /dev/null --silent --head --fail "$MOODLE_URL/login/index.php"; do
timeout=$((timeout - 5))
if [ $timeout -le 0 ]; then
echo "❌ ERROR: Moodle web server did not become ready after 5 minutes."
echo "--- MOODLE CONTAINER LOGS ---"
docker logs "$MOODLE_CONTAINER"
exit 1
fi
echo " -> Moodle not yet ready, waiting 5 more seconds..."
sleep 5
done
echo "✅ Moodle web server is responsive."
if ! docker ps --format '{{.Names}}' | grep -qw "$MOODLE_CONTAINER"; then
echo "❌ Moodle container not running." && exit 1
fi
# Clone plugin repo + zip
REPO_URL="https://transfer.hft-stuttgart.de/gitlab/cota/moodle-assignsubmission_dta.git"
CLONE_DIR="tmp_moodle_plugin"
PLUGIN_FOLDER="dta"
PLUGIN_ZIP_NAME="dta.zip"
# Fetch latest remote commit hash
LATEST_REMOTE_HASH=$(git ls-remote "$REPO_URL" HEAD | awk '{print $1}')
# If a record of last commit exists, load it
LAST_COMMIT_FILE=".last_dta_commit"
SHOULD_CLONE=false
# Check if plugin zip or commit info is missing
if [ ! -f "$PLUGIN_ZIP_NAME" ] || [ ! -f "$LAST_COMMIT_FILE" ]; then
echo "ℹ️ No existing zip or commit info found — will clone."
SHOULD_CLONE=true
else
LAST_HASH=$(cat "$LAST_COMMIT_FILE")
if [ "$LATEST_REMOTE_HASH" != "$LAST_HASH" ]; then
echo "🔁 New commit detected in DTA repo — will re-clone."
SHOULD_CLONE=true
else
echo "✅ DTA plugin is up to date — using existing $PLUGIN_ZIP_NAME."
fi
fi
if [ "$SHOULD_CLONE" = true ]; then
rm -rf "$CLONE_DIR" "$PLUGIN_ZIP_NAME"
echo "📌 Cloning DTA plugin repository..."
git clone --depth 1 "$REPO_URL" "$CLONE_DIR"
if [ ! -d "$CLONE_DIR/$PLUGIN_FOLDER" ]; then
echo "❌ Folder '$PLUGIN_FOLDER' not found in repo."
exit 1
fi
echo "📦 Creating plugin ZIP..."
cd "$CLONE_DIR"
zip -r "../$PLUGIN_ZIP_NAME" "$PLUGIN_FOLDER"
cd ..
rm -rf "$CLONE_DIR"
echo "$LATEST_REMOTE_HASH" > "$LAST_COMMIT_FILE"
echo "✅ Created fresh $PLUGIN_ZIP_NAME (commit $LATEST_REMOTE_HASH)"
fi
if [ ! -f "$PLUGIN_ZIP_NAME" ]; then
echo "❌ Plugin ZIP file not found at $PLUGIN_ZIP_NAME."
exit 1
fi
chmod 644 "$PLUGIN_ZIP_NAME"
echo "✅ Created $PLUGIN_ZIP_NAME."
# --- Step 1: Copy ZIP into container ---
echo "📌 Copying $PLUGIN_ZIP_NAME into Moodle container..."
docker cp "$PLUGIN_ZIP_NAME" "$MOODLE_CONTAINER:/tmp/$PLUGIN_ZIP_NAME"
# Install plugin in container
echo "📦 Step 1: Installing DTA plugin..."
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" bash -c "set -e; \
mkdir -p '$MOODLE_ROOT_IN_CONTAINER/mod/assign/submission'; \
unzip -o '/tmp/$PLUGIN_ZIP_NAME' -d '$MOODLE_ROOT_IN_CONTAINER/mod/assign/submission'; \
php '$MOODLE_ROOT_IN_CONTAINER/admin/cli/upgrade.php' --non-interactive; \
php '$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php'"
echo "✅ Plugin installed."
# Enable web services
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=enablemobilewebservice --set=1
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=curlsecurityblockedhosts --set=""
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=curlsecurityallowedport --set=""
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=webserviceuploaddisabled --set=0
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=enablewsauthpassword --set=1
# Create API + student user
PHP_USER_SCRIPT_PATH="/tmp/create_api_user.php"
LOCAL_PHP_USER_SCRIPT_PATH="/tmp/local_create_api_user.php"
cat > "$LOCAL_PHP_USER_SCRIPT_PATH" <<EOF
<?php
define('CLI_SCRIPT', true);
require('/var/www/html/config.php');
require_once(\$CFG->libdir.'/clilib.php');
require_once(\$CFG->dirroot.'/user/lib.php');
global \$DB;
\$username = \$argv[1]; \$password = \$argv[2];
if (!\$DB->record_exists('user', ['username' => \$username])) {
\$user = new stdClass();
\$user->username = \$username; \$user->firstname = 'API'; \$user->lastname = 'User';
\$user->email = 'api.user@example.com'; \$user->confirmed = 1;
\$user->mnethostid = \$CFG->mnet_localhost_id; \$user->auth = 'manual';
\$userid = user_create_user(\$user, false, false);
\$user = \$DB->get_record('user', ['id' => \$userid]);
\update_internal_user_password(\$user, \$password);
echo "Created user '\$username' with ID: \$userid\n";
// Create a new role for API users if it doesn't exist
\$rolename = 'API User';
\$apicapability = 'moodle/webservice:createtoken';
\$restapicapability = 'webservice/rest:use';
\$coursecapability = 'moodle/course:create';
\$enrolcapability = 'enrol/manual:enrol';
if (!\$apirole = \$DB->get_record('role', ['shortname' => 'apiuser'])) {
\$apiroleid = create_role(\$rolename, 'apiuser', 'Role for API access');
\$apirole = \$DB->get_record('role', ['id' => \$apiroleid]);
echo "Created role '\$rolename' with ID: \$apiroleid\n";
}
// Ensure the capability is assigned to the role
\$context = \core\context\system::instance();
assign_capability(\$apicapability, CAP_ALLOW, \$apirole->id, \$context->id);
assign_capability(\$restapicapability, CAP_ALLOW, \$apirole->id, \$context->id);
assign_capability(\$coursecapability, CAP_ALLOW, \$apirole->id, \$context->id);
assign_capability(\$enrolcapability, CAP_ALLOW, \$apirole->id, \$context->id);
echo "Assigned capability '\$apicapability' to role '\$rolename'\n";
// Assign our automation user to this new role at the system level
role_assign(\$apirole->id, \$userid, \$context->id);
// Get the IDs for the roles we need to assign
\$managerroleid = \$DB->get_field('role', 'id', ['shortname' => 'manager']);
\$coursecreatorroleid = \$DB->get_field('role', 'id', ['shortname' => 'coursecreator']);
\$systemcontext = \core\context\system::instance();
// Assign the 'Manager' and 'Course creator' roles to the new user at the system context
role_assign(\$managerroleid, \$userid, \$systemcontext->id);
role_assign(\$coursecreatorroleid, \$userid, \$systemcontext->id);
echo "Assigned user '\$username' to role '\$rolename'\n";
} else {
\$userid = \$DB->get_field('user', 'id', ['username' => \$username]);
echo "User '\$username' already exists with ID: \$userid\n";
}
// Create Student User
\$student_username = \$argv[3]; \$student_password = \$argv[4];
if (!\$DB->record_exists('user', ['username' => \$student_username])) {
\$student = new stdClass(); \$student->username = \$student_username; \$student->firstname = 'Test'; \$student->lastname = 'Student';
\$student->email = 'student@example.com'; \$student->confirmed = 1; \$student->mnethostid = \$CFG->mnet_localhost_id; \$student->auth = 'manual';
\$studentid = user_create_user(\$student, false, false);
\$student = \$DB->get_record('user', ['id' => \$studentid]);
\update_internal_user_password(\$student, \$student_password);
echo "Created user '\$student_username' with ID: \$studentid\n";
// Create a new role for API users if it doesn't exist
\$rolename = 'API Student user';
\$apicapability = 'moodle/webservice:createtoken';
\$restapicapability = 'webservice/rest:use';
if (!\$apirole = \$DB->get_record('role', ['shortname' => 'apistudentuser'])) {
\$apiroleid = create_role(\$rolename, 'apistudentuser', 'Role for API access');
\$apirole = \$DB->get_record('role', ['id' => \$apiroleid]);
echo "Created role '\$rolename' with ID: \$apiroleid\n";
}
// Ensure the capability is assigned to the role
\$context = \core\context\system::instance();
assign_capability(\$apicapability, CAP_ALLOW, \$apirole->id, \$context->id);
assign_capability(\$restapicapability, CAP_ALLOW, \$apirole->id, \$context->id);
echo "Assigned capability '\$apicapability' to role '\$rolename'\n";
// Assign our automation user to this new role at the system level
role_assign(\$apirole->id, \$studentid, \$context->id);
echo "Assigned user '\$username' to role '\$rolename'\n";
}
EOF
docker cp "$LOCAL_PHP_USER_SCRIPT_PATH" "$MOODLE_CONTAINER:$PHP_USER_SCRIPT_PATH"
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$PHP_USER_SCRIPT_PATH" "$TEACHER_USER_NAME" "$TEACHER_USER_PASS" "$STUDENT_USER_NAME" "$STUDENT_USER_PASS"
docker exec "$MOODLE_CONTAINER" rm "$PHP_USER_SCRIPT_PATH"
rm "$LOCAL_PHP_USER_SCRIPT_PATH"
echo "✅ Automation and student users configured."
echo "⚙️ Step 2: Creating and configuring a dedicated web service..."
docker exec -i "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" <<EOF
-- Enable Web Services globally in Moodle's config
INSERT INTO mdl_config (name, value) VALUES ('enablewebservices', '1') ON DUPLICATE KEY UPDATE value = '1';
INSERT INTO mdl_config (name, value) VALUES ('webserviceprotocols', 'rest') ON DUPLICATE KEY UPDATE value = 'rest';
# --- End of addition --
-- Create our custom service, now with a required capability set for robustness
INSERT IGNORE INTO mdl_external_services (name, shortname, enabled, requiredcapability, restrictedusers, uploadfiles, downloadfiles)
VALUES ('DTA Automation Service', '$CUSTOM_SERVICE_NAME', 1, '', 0, 1, 1); -- <-- The capability fix
-- Get the ID of our custom service
SET @serviceid = (SELECT id FROM mdl_external_services WHERE shortname = '$CUSTOM_SERVICE_NAME');
-- Add the required functions to OUR service
INSERT IGNORE INTO mdl_external_services_functions (externalserviceid, functionname) VALUES
(@serviceid, 'core_user_create_users'),
(@serviceid, 'core_course_create_courses'),
(@serviceid, 'enrol_manual_enrol_users'),
(@serviceid, 'core_files_upload'),
(@serviceid, 'mod_assign_save_submission'),
(@serviceid, 'mod_assign_get_submission_status'),
(@serviceid, 'mod_assign_get_submissions'),
(@serviceid, 'mod_assign_save_grade'),
(@serviceid, 'assignsubmission_dta_save_submission');
# -- Authorize the teacher user to use this service.
SET @automationuserid = (SELECT id FROM mdl_user WHERE username = '$TEACHER_USER_NAME' AND deleted = 0 ORDER BY id ASC LIMIT 1);
INSERT IGNORE INTO mdl_external_services_users (externalserviceid, userid, iprestriction, validuntil, timecreated)
VALUES (@serviceid, @automationuserid , NULL, NULL, UNIX_TIMESTAMP());
-- Also authorize the student user to use the service to get a token
SET @studentid = (SELECT id FROM mdl_user WHERE username = '$STUDENT_USER_NAME');
INSERT IGNORE INTO mdl_external_services_users (externalserviceid, userid) VALUES (@serviceid, @studentid);
EOF
STUDENT_USER_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_user WHERE username='$STUDENT_USER_NAME';")
echo "✅ Custom web service created and user authorized."
echo " -> Purging Moodle caches.."
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php"
sleep 15
# --- Step 6: Assign System Roles via API ---
echo "🛡️ Step 6: Assigning System Roles to teacher User..."
# We use the main site admin's token for this one-time setup task, as it's guaranteed to have permissions.
ADMIN_SETUP_TOKEN_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/login/token.php" --data-urlencode "username=$MOODLE_ADMIN_USER" --data-urlencode "password=$MOODLE_ADMIN_PASS" --data-urlencode "service=moodle_mobile_app")
ADMIN_SETUP_TOKEN=$(echo "$ADMIN_SETUP_TOKEN_RESPONSE" | jq -r '.token')
# Get IDs needed for role assignment
TEACHER_USER_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_user WHERE username='$TEACHER_USER_NAME';")
# ROLE IDs of Manager and coursecreator
MANAGER_ROLE_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_role WHERE shortname='manager';")
COURSE_CREATOR_ROLE_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_role WHERE shortname='coursecreator';")
SYSTEM_CONTEXT_ID=10 # The ID for the System context is always 1
# Assign Manager and Course Creator roles to the Teacher
curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \
-d "wstoken=$ADMIN_SETUP_TOKEN" -d "wsfunction=core_role_assign_roles" -d "moodlewsrestformat=json" \
-d "assignments[0][roleid]=$MANAGER_ROLE_ID" -d "assignments[0][userid]=$TEACHER_USER_ID" -d "assignments[0][contextid]=$SYSTEM_CONTEXT_ID" \
-d "assignments[1][roleid]=$COURSE_CREATOR_ROLE_ID" -d "assignments[1][userid]=$TEACHER_USER_ID" -d "assignments[1][contextid]=$SYSTEM_CONTEXT_ID" > /dev/null
echo "✅ Assigned System Roles to Teacher User."
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php"
sleep 15
echo " -> Purging Moodle caches to apply service changes..."
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php"
echo "✅ Caches purged."
sleep 5
ENV_STORE="./moodle_env_runtime.sh"
echo "TEACHER_USER_ID=$TEACHER_USER_ID" > "$ENV_STORE"
echo "STUDENT_USER_ID=$STUDENT_USER_ID" >> "$ENV_STORE"
echo "✅ Stored runtime environment variables in $ENV_STORE."
# GL: - potential dockerfile content until here
#!/bin/bash
set -e
source "$(dirname "$0")/script_env.sh"
# GL: - second file from here
# Ensure config file exists
echo "🔐 Step 3: Obtaining API Token...$TEACHER_USER_NAME..$TEACHER_USER_PASS"
TOKEN_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/login/token.php" \
--data-urlencode "username=$TEACHER_USER_NAME" \
--data-urlencode "password=$TEACHER_USER_PASS" \
--data-urlencode "service=$CUSTOM_SERVICE_NAME")
# Check for a Moodle error in the response FIRST
if echo "$TOKEN_RESPONSE" | jq -e 'if type=="object" and .error then true else false end' > /dev/null; then
echo "❌ Moodle API returned an error when requesting a token:"
echo "$TOKEN_RESPONSE" | jq .
exit 1
fi
TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token')
echo "✅ Successfully obtained API token. $TOKEN"
TMP_RUNNER_DIR="tmp_runner"
if [ ! -f "$DTA_CONFIG_FILE" ]; then
echo "⚠️ Config file $DTA_CONFIG_FILE not found locally. Fetching from repo $RUNNER_REPO ..."
rm -rf "$TMP_RUNNER_DIR"
git clone --depth 1 "$RUNNER_REPO" "$TMP_RUNNER_DIR"
if [ -f "$TMP_RUNNER_DIR/$DTA_CONFIG_FILE" ]; then
cp "$TMP_RUNNER_DIR/$DTA_CONFIG_FILE" .
echo "✅ Downloaded $DTA_CONFIG_FILE from $RUNNER_REPO"
else
echo "❌ ERROR: $DTA_CONFIG_FILE not found in repo $RUNNER_REPO"
exit 1
fi
rm -rf "$TMP_RUNNER_DIR"
fi
# --- Step 5: Upload Configuration File for DTA Plugin ---
echo "📤 Step 5: Uploading config file for DTA plugin..."
FILE_UPLOAD_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/upload.php" \
-F "token=$TOKEN" -F "filearea=draft" -F "itemid=0" -F "file=@$DTA_CONFIG_FILE")
DTA_FILE_ITEMID=$(echo "$FILE_UPLOAD_RESPONSE" | jq -r 'if type=="array" then .[0].itemid else .itemid end')
if [ -z "$DTA_FILE_ITEMID" ] || [ "$DTA_FILE_ITEMID" == "null" ]; then
echo "❌ Failed to upload DTA config file. Response:"
echo "$FILE_UPLOAD_RESPONSE" | jq .
exit 1
fi
echo "✅ DTA config file uploaded. Received Item ID: $DTA_FILE_ITEMID"
# GL: optionally for every test, rather avoid to save test execution time
# --- Step 4: Create a Course ---
echo "📚 Step 4: Creating a new course..."
COURSE_SHORTNAME="DTA-Course-$(date +%s)"
COURSE_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \
-d "wstoken=$TOKEN" -d "wsfunction=core_course_create_courses" -d "moodlewsrestformat=json" \
-d "courses[0][fullname]=DTA Test Course" -d "courses[0][shortname]=$COURSE_SHORTNAME" -d "courses[0][categoryid]=1")
# First, check if the response is a Moodle exception object.
# The `jq -e` command sets an exit code, which works perfectly with `if`.
if echo "$COURSE_RESPONSE" | jq -e 'if type=="object" and .exception then true else false end' > /dev/null; then
echo "❌ Moodle API returned an exception during course creation:"
echo "$COURSE_RESPONSE" | jq . # Pretty-print the JSON error
exit 1
fi
# If not an exception, try to parse the ID. This handles both single-object and array responses.
NEW_COURSE_ID=$(echo "$COURSE_RESPONSE" | jq -r 'if type=="array" then .[0].id else .id end')
if [ -z "$NEW_COURSE_ID" ] || [ "$NEW_COURSE_ID" == "null" ]; then
echo "❌ Failed to parse a valid Course ID from the API response."
echo "Full API Response:"
echo "$COURSE_RESPONSE" | jq .
exit 1
fi
echo "✅ Successfully created course '$COURSE_SHORTNAME' with ID: $NEW_COURSE_ID"
# --- Step 5: Enroll Teacher User into the New Course ---
echo "🧑‍🏫 Step 5: Enrolling teacher in the new course..."
# First, get the User ID for your teacher user
TEACHER_USER_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_user WHERE username='$TEACHER_USER_NAME';")
# The roleid for a Teacher is typically 3 (editingteacher) or 4 (teacher). We'll use 3.
TEACHER_ROLE_ID=3
# Add the enrol_manual_enrol_users function to our service
docker exec -i "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -e "INSERT IGNORE INTO mdl_external_services_functions (externalserviceid, functionname) VALUES ((SELECT id FROM mdl_external_services WHERE shortname = '$CUSTOM_SERVICE_NAME'), 'enrol_manual_enrol_users');"
# Purge cache to recognize the new function
docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php" > /dev/null
ENROL_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \
-d "wstoken=$TOKEN" -d "wsfunction=enrol_manual_enrol_users" -d "moodlewsrestformat=json" \
-d "enrolments[0][roleid]=$TEACHER_ROLE_ID" \
-d "enrolments[0][userid]=$TEACHER_USER_ID" \
-d "enrolments[0][courseid]=$NEW_COURSE_ID")
# A successful response is null, so we check for an exception instead.
if echo "$ENROL_RESPONSE" | jq -e 'if type=="object" and .exception then true else false end' > /dev/null; then
echo "❌ Moodle API returned an exception during user enrollment:"
echo "$ENROL_RESPONSE" | jq .
exit 1
fi
echo "✅ Successfully enrolled you in the course."
# --- Step 6: Create Assignment via Custom PHP Script ---
echo "📝 Step 6: Creating assignment with detailed error logging..."
PHP_ASSIGN_SCRIPT_PATH="/tmp/create_assignment.php"
LOCAL_PHP_ASSIGN_SCRIPT_PATH="/tmp/local_create_assignment.php"
# --- This PHP script now has a try/catch block for detailed errors ---
cat > "$LOCAL_PHP_ASSIGN_SCRIPT_PATH" <<EOF
<?php
define('CLI_SCRIPT', true);
require('/var/www/html/config.php');
require_once(\$CFG->dirroot.'/course/modlib.php');
global \$DB, \$USER;
// programmatically "log in" as the teacher user
// This gives file_save_draft_area_files the user context it needs.
\$adminusername = "$TEACHER_USER_NAME";
\$adminuser = \$DB->get_record('user', ['username' => \$adminusername, 'deleted' => 0], '*', MUST_EXIST);
\core\session\manager::set_user(\$adminuser);
\$USER = \$adminuser;
// The global $USER object is now populated.
if (count(\$argv) < 3) { exit("Error: Course ID and DTA Item ID are required.\n"); }
\$courseid = (int)\$argv[1];
\$dta_itemid = (int)\$argv[2];
\$course = \$DB->get_record('course', ['id' => \$courseid], '*', MUST_EXIST);
\$data = new stdClass();
// --- Core Module Details ---
\$data->course = \$courseid;
\$data->modulename = 'assign';
\$data->name = 'Fully Automated DTA Assignment';
\$data->intro = 'This assignment was created by the DTA automation script.';
\$data->introformat = FORMAT_HTML;
\$data->section = 1;
\$data->visible = 1;
// --- Submission Settings ---
\$data->assignsubmission_dta_enabled = 1;
\$data->assignsubmission_file_enabled = 1;
// This assumes the form element for your file manager is named 'package_filemanager'
// The final property name becomes assignsubmission_dta_package_filemanager
\$data->dta = 1; // Explicitly enable the dta group of settings
\$data->tests_draft_dta = \$dta_itemid;
\$data->assignsubmission_file_maxsizebytes = 0;
\$data->cmidnumber = '';
\$data->assignsubmission_file_maxfiles = 20;
\$data->assignsubmission_onlinetext_enabled = 0;
\$data->submissiondrafts = 0; // Final submissions cannot be edited
\$data->requiresubmissionstatement = 0; // The direct fix for the error
\$data->requireallteammemberssubmit = 0;
\$data->teamsubmission = 0;
// --- Notification Settings ---
\$data->sendnotifications = 1;
\$data->sendlatenotifications = 0;
\$data->sendstudentnotifications = 1;
// --- Grading Settings ---
\$data->grade = 100; // Max grade
\$data->blindmarking = 0;
\$data->markingworkflow = 0;
\$data->markingallocation = 0;
\$data->attemptreopenmethod = 'none';
// --- Date Settings ---
\$data->allowsubmissionsfromdate = time();
\$data->duedate = time() + (7 * 24 * 60 * 60); // Due in 7 days
\$data->cutoffdate = 0; // No hard cutoff
\$data->gradingduedate = 0;
// these fields are necessary but we don't need to change them ---
\$data->module = \$DB->get_field('modules', 'id', ['name' => 'assign'], MUST_EXIST);
\$data->timemodified = time();
\$data->alwaysshowdescription = 0;
\$data->completionsubmit = 0;
// --- DEBUG: Print the exact data object we are sending ---
fwrite(STDERR, "DEBUG: Data object being sent to add_moduleinfo():\n");
fwrite(STDERR, print_r(\$data, true) . "\n");
try {
// Attempt to create the assignment
\$cm = add_moduleinfo(\$data, \$course);
fwrite(STDERR, "DEBUG: Course object before add_moduleinfo():\n");
fwrite(STDERR, print_r(\$course, true) . "\n");
fwrite(STDERR, "DEBUG: Returned object from add_moduleinfo():\n");
fwrite(STDERR, print_r(\$cm, true) . "\n");
if (!\$cm || !is_object(\$cm) || !\$cm->coursemodule) {
fwrite(STDERR, "Error: add_moduleinfo() returned invalid object\n");
exit(1);
}
// Some versions: id = course module id, instance = assignment id
\$assignmentid = property_exists(\$cm, 'instance') ? \$cm->instance : null;
\$coursemoduleid = property_exists(\$cm, 'coursemodule') ? \$cm->coursemodule : null;
if (!\$assignmentid) {
fwrite(STDERR, "Error: Could not extract assignment ID\n");
exit(1);
}
rebuild_course_cache(\$course->id, true);
// ✅ Echo only the assignment ID (for Bash)
echo \$assignmentid . "\n";
} catch (Exception \$e) {
// If anything goes wrong, catch the exception and print all details
echo "\n!!!!!! SCRIPT FAILED: An Exception was caught! !!!!!!\n\n";
echo "Error Type: " . get_class(\$e) . "\n";
echo "Error Message: " . \$e->getMessage() . "\n\n";
// Moodle's database exceptions often have extra useful debug info
if (!empty(\$e->debuginfo)) {
echo "Moodle Debug Info:\n" . \$e->debuginfo . "\n\n";
}
echo "Stack Trace:\n" . \$e->getTraceAsString() . "\n";
exit(1); // Exit with an error code
}
EOF
docker cp "$LOCAL_PHP_ASSIGN_SCRIPT_PATH" "$MOODLE_CONTAINER:$PHP_ASSIGN_SCRIPT_PATH"
NEW_ASSIGNMENT_ID=$(docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$PHP_ASSIGN_SCRIPT_PATH" "$NEW_COURSE_ID" "$DTA_FILE_ITEMID" 2>&1 | tee full_output.log | tail -n1)
docker exec "$MOODLE_CONTAINER" rm "$PHP_ASSIGN_SCRIPT_PATH"
rm "$LOCAL_PHP_ASSIGN_SCRIPT_PATH"
echo " -> Created assignment with ID: $NEW_ASSIGNMENT_ID"
echo "✅ Test scenario created."
ENV_STORE="./course_runtime.sh"
echo "NEW_COURSE_ID=$NEW_COURSE_ID" > "$ENV_STORE"
echo "NEW_ASSIGNMENT_ID=$NEW_ASSIGNMENT_ID" >> "$ENV_STORE"
echo "✅ Stored runtime environment variables in $ENV_STORE."
\ No newline at end of file
#!/bin/bash
set -e
source "$(dirname "$0")/script_env.sh"
source "$(dirname "$0")/course_runtime.sh"
TMP_RUNNER_DIR="tmp_runner"
if [ ! -f "$DTA_STUDENT_SUBMISSION_FILE" ]; then
echo "⚠️ Config file $DTA_STUDENT_SUBMISSION_FILE not found locally. Fetching from repo $RUNNER_REPO ..."
rm -rf "$TMP_RUNNER_DIR"
git clone --depth 1 "$RUNNER_REPO" "$TMP_RUNNER_DIR"
if [ -f "$TMP_RUNNER_DIR/$DTA_STUDENT_SUBMISSION_FILE" ]; then
cp "$TMP_RUNNER_DIR/$DTA_STUDENT_SUBMISSION_FILE" .
echo "✅ Downloaded $DTA_STUDENT_SUBMISSION_FILE from $RUNNER_REPO"
else
echo "❌ ERROR: $DTA_STUDENT_SUBMISSION_FILE not found in repo $RUNNER_REPO"
exit 1
fi
rm -rf "$TMP_RUNNER_DIR"
fi
# --- Step 8: Student Submission Workflow ---
echo "🧑‍🎓 Step 8: Beginning student submission workflow..."
STUDENT_USER_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_user WHERE username='$STUDENT_USER_NAME';")
# Role 5 is the standard 'Student' role
STUDENT_ROLE_ID=5
STUDENT_ENROL_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \
-d "wstoken=$TOKEN" -d "wsfunction=enrol_manual_enrol_users" -d "moodlewsrestformat=json" \
-d "enrolments[0][roleid]=$STUDENT_ROLE_ID" \
-d "enrolments[0][userid]=$STUDENT_USER_ID" \
-d "enrolments[0][courseid]=$NEW_COURSE_ID")
echo " -> Enrolled student in course."
echo " -> Getting token for student..."
STUDENT_TOKEN_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/login/token.php" \
--data-urlencode "username=$STUDENT_USER_NAME" --data-urlencode "password=$STUDENT_USER_PASS" --data-urlencode "service=$CUSTOM_SERVICE_NAME")
STUDENT_TOKEN=$(echo "$STUDENT_TOKEN_RESPONSE" | jq -r '.token')
if [ -z "$STUDENT_TOKEN" ] || [ "$STUDENT_TOKEN" == "null" ]; then echo "❌ Failed to get API token for student. Response: $STUDENT_TOKEN_RESPONSE" && exit 1; fi
echo " -> Successfully obtained token for student. $STUDENT_TOKEN"
echo " -> Uploading student submission file..."
STUDENT_FILE_UPLOAD_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/upload.php" -F "token=$STUDENT_TOKEN" -F "filearea=draft" -F "itemid=0" -F "file=@$DTA_STUDENT_SUBMISSION_FILE")
STUDENT_FILE_ITEMID=$(echo "$STUDENT_FILE_UPLOAD_RESPONSE" | jq -r 'if type=="array" then .[0].itemid else .itemid end')
echo " -> Student file uploaded. Item ID: $STUDENT_FILE_ITEMID"
echo " -> Saving final submission using our custom web service..."
SAVE_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \
-d "wstoken=$STUDENT_TOKEN" \
-d "wsfunction=assignsubmission_dta_save_submission" \
-d "moodlewsrestformat=json" \
-d "assignmentid=$NEW_ASSIGNMENT_ID" \
-d "userid=$STUDENT_USER_ID" \
-d "itemid=$STUDENT_FILE_ITEMID")
if echo "$SAVE_RESPONSE" | jq -e 'if type=="object" and .exception then true else false end' > /dev/null; then
echo "❌ Moodle API returned an exception during submission save:"
echo "$SAVE_RESPONSE" | jq .
exit 1
fi
echo "✅ Submission successfully sent to Moodle, which has triggered the backend grading."
# --- Step 7: Verify Grade and Feedback in Moodle Database ---
echo "🧐 Step 7: Verifying grade and feedback has been posted back to Moodle..."
echo " -> Waiting 20 seconds for DTA backend to make the callback..."
sleep 20
SUBMISSION_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_assign_submission WHERE assignment = '$NEW_ASSIGNMENT_ID' AND userid = '$STUDENT_USER_ID';")
SUMMARY_COUNT=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT COUNT(*) FROM mdl_assignsubmission_dta_summary WHERE submission_id = '$SUBMISSION_ID';")
if [ "$SUMMARY_COUNT" -eq 0 ]; then
echo "❌ ERROR: No feedback summary found in the database for submission ID $SUBMISSION_ID."
echo "The backend callback likely failed. Check the backend logs:"
docker logs backend
exit 1
fi
echo "--- FINAL MOODLE DATABASE STATUS ---"
echo "✅ Found DTA summary record in the database."
echo "Detailed Test Results from mdl_assignsubmission_dta_result:"
docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -e "SELECT name, state, failure_type, failure_reason FROM mdl_assignsubmission_dta_result WHERE submission_id = '$SUBMISSION_ID';"
STATUS_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \
-d "wstoken=$TOKEN" \
-d "wsfunction=mod_assign_get_submission_status" \
-d "moodlewsrestformat=json" \
-d "assignid=$NEW_ASSIGNMENT_ID" \
-d "userid=$STUDENT_USER_ID")
GRADE_STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.lastattempt.gradingstatus')
# if [ "$GRADE_STATUS" == "graded" ]; then
# echo "🎉 SUCCESS! The submission is marked as 'graded' in the Moodle GUI."
# else
# echo "⚠️ WARNING: The submission status is '$GRADE_STATUS', not 'graded'. The backend wrote to its custom tables but may have failed to update the core gradebook."
# exit 1
# fi
echo "🎉🎉 Moodle automation complete. The environment is fully configured. 🎉🎉"
dta::https://transfer.hft-stuttgart.de/gitlab/cota/examples/cota-submission-calc-jdk.git::none::none::/
dta::https://transfer.hft-stuttgart.de/gitlab/cota/examples/cota-test-course-jdk.git::none::none::/topic02/exercise04::hftstuttgart/cota-runner-jdk21-junit5:latest
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment